iipbs_cs #6

Merged
chanii8 merged 10 commits from iipbs_cs into main 2025-03-14 18:43:20 +09:00
7 changed files with 323 additions and 35 deletions
Showing only changes of commit 64a615258d - Show all commits
+2
View File
@@ -0,0 +1,2 @@
import { RegisterPage } from "@/pages/site";
export default RegisterPage;
+10 -1
View File
@@ -5,7 +5,7 @@
@apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666];
}
.card-border {
@apply shadow-blur border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)];
@apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur;
}
.card-header {
@apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4;
@@ -13,4 +13,13 @@
.card-footer {
@apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4;
}
[data-mode="dark"] {
.card-border {
@apply border-[#3f4042] bg-[rgba(255,255,255,0.065)];
}
.card-header {
@apply border-b-[#3f4042];
}
}
}
+1 -1
View File
@@ -28,7 +28,7 @@ function CodeSelect({ upCd, defaultValue, ...props }: CodeSelectProps) {
return (
<Select {...props}>
<SelectTrigger className="w-[12rem] text-[1.3rem] text-label">
<SelectTrigger className="w-[12rem]">
<SelectValue placeholder={defaultValueText} />
</SelectTrigger>
<SelectContent>
@@ -0,0 +1,212 @@
"use client";
import { useState } from "react";
import {
Dialog,
DialogTrigger,
Button,
DialogContent,
DialogHeader,
DialogTitle,
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
Input,
Checkbox,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
DialogFooter,
DialogClose,
} from "@common/components";
export type Domain = {
id: number;
domainUrl: string;
isRepresentative: boolean;
isActive: "true" | "false";
};
type DomainManagementDialogProps = {
triggerDisabled?: boolean;
handleSave: (rows: Domain[]) => void;
};
function DomainManagementDialog({
triggerDisabled,
handleSave,
}: DomainManagementDialogProps) {
const [rows, setRows] = useState<Domain[]>([]);
const [rowSelection, setRowSelection] = useState<Domain["id"][]>([]);
const onSave = () => {
handleSave(rows);
};
const handleAddDomain = () => {
const newDomain: Domain = {
id: Date.now(),
domainUrl: "",
isRepresentative: rows.length === 0,
isActive: "true",
};
setRows([...rows, newDomain]);
};
const handleDeleteDomain = () => {
const filteredRows = rows.filter((row) => !rowSelection.includes(row.id));
const hasRepresentative = filteredRows.some((row) => row.isRepresentative);
let updatedRows = filteredRows;
if (!hasRepresentative && filteredRows.length > 0) {
updatedRows = filteredRows.map((row, index) =>
index === 0 ? { ...row, isRepresentative: true } : row,
);
}
setRows(updatedRows);
setRowSelection([]);
};
const handleDomainUrlChange = (id: number, value: string) => {
setRows(
rows.map((row) => (row.id === id ? { ...row, domainUrl: value } : row)),
);
};
const handleRepresentativeChange = (id: number) => {
setRows(
rows.map((row) => ({
...row,
isRepresentative: row.id === id,
})),
);
};
const handleActiveChange = (id: number, value: "true" | "false") => {
setRows(
rows.map((row) => (row.id === id ? { ...row, isActive: value } : row)),
);
};
const toggleRowSelection = (id: number) => {
setRowSelection((prev) => {
if (prev.includes(id)) {
return prev.filter((item) => item !== id);
} else {
return [...prev, id];
}
});
};
return (
<Dialog>
<DialogTrigger asChild>
<Button disabled={triggerDisabled}>도메인 관리</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-[600px]">
<DialogHeader>
<DialogTitle>도메인 관리</DialogTitle>
</DialogHeader>
<div className="mb-4 flex justify-end gap-2">
<Button size="sm" color="white" onClick={handleAddDomain}>
항목 추가
</Button>
<Button
size="sm"
color="red"
onClick={handleDeleteDomain}
disabled={rowSelection.length === 0}
>
항목 삭제
</Button>
</div>
<div className="card card-border">
<Table variant="secondary">
<colgroup>
<col width="8%" />
<col width="12%" />
<col width="40%" />
<col width="20%" />
<col width="20%" />
</colgroup>
<TableHeader>
<TableRow>
<TableHead>NO</TableHead>
<TableHead>선택</TableHead>
<TableHead>도메인URL</TableHead>
<TableHead>대표URL</TableHead>
<TableHead>사용여부</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((row, idx) => (
<TableRow key={row.id}>
<TableCell align="center">{idx + 1}</TableCell>
<TableCell align="center">
<Checkbox
checked={rowSelection.includes(row.id)}
onCheckedChange={() => toggleRowSelection(row.id)}
className="cursor-pointer"
/>
</TableCell>
<TableCell align="center">
<Input
value={row.domainUrl}
onChange={(e) =>
handleDomainUrlChange(row.id, e.target.value)
}
className="!text-[1.3rem]"
/>
</TableCell>
<TableCell align="center">
<Checkbox
checked={row.isRepresentative}
onCheckedChange={() => handleRepresentativeChange(row.id)}
className="cursor-pointer"
/>
</TableCell>
<TableCell align="center">
<Select
value={row.isActive}
onValueChange={(value: "true" | "false") =>
handleActiveChange(row.id, value)
}
>
<SelectTrigger className="mx-auto w-[100px]">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="true">예</SelectItem>
<SelectItem value="false">아니오</SelectItem>
</SelectContent>
</Select>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
<DialogFooter className="!justify-center">
<DialogClose asChild>
<Button size="lg" onClick={onSave}>
저장
</Button>
</DialogClose>
<DialogClose asChild>
<Button size="lg" color="white">
취소
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
export { DomainManagementDialog };
@@ -18,6 +18,8 @@ import {
RadioGroup,
RadioGroupItem,
Checkbox,
DialogFooter,
DialogClose,
} from "@common/components";
type IP = {
@@ -34,7 +36,7 @@ function IPManagementDialog({ triggerDisabled }: IPManagementDialogProps) {
const [rows, setRows] = useState<IP[]>([]);
const [rowSelection, setRowSelection] = useState<IP["id"][]>([]);
const handleSave = () => {};
const onSave = () => {};
const handleAddIP = () => {
const newIP: IP = {
@@ -86,9 +88,6 @@ function IPManagementDialog({ triggerDisabled }: IPManagementDialogProps) {
<Button size="sm" color="green">
엑셀 업로드
</Button>
<Button size="sm" onClick={handleSave}>
저장
</Button>
<Button size="sm" color="white" onClick={handleAddIP}>
항목 추가
</Button>
@@ -102,7 +101,7 @@ function IPManagementDialog({ triggerDisabled }: IPManagementDialogProps) {
</Button>
</div>
<div className="card card-border">
<Table>
<Table variant="secondary">
<colgroup>
<col width="10%" />
<col width="10%" />
@@ -160,6 +159,18 @@ function IPManagementDialog({ triggerDisabled }: IPManagementDialogProps) {
</TableBody>
</Table>
</div>
<DialogFooter className="!justify-center">
<DialogClose asChild>
<Button size="lg" onClick={onSave}>
저장
</Button>
</DialogClose>
<DialogClose asChild>
<Button size="lg" color="white">
취소
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
+2 -2
View File
@@ -3,8 +3,8 @@ import { BaseLayout } from "@common/components";
function SitePageLayout({ children }: { children: React.ReactNode }) {
return (
<BaseLayout>
<div className="h-auto min-h-full bg-[#f1f6f9]">
<h2 className="px-[1.8rem] pt-[2rem] text-[2.4rem] font-semibold text-[#4c5e6f]">
<div className="bg-container h-auto min-h-full">
<h2 className="text-title px-[1.8rem] pt-[2rem] text-[2.4rem] font-semibold">
사이트 관리
</h2>
{children}
+80 -26
View File
@@ -1,6 +1,7 @@
"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { SiteApi } from "@/entities/site";
import { ENDPOINT } from "@/shared/config/api";
import {
@@ -19,25 +20,45 @@ import { useQuery } from "@tanstack/react-query";
import { CodeSelect } from "@/entities/code";
import { SitePageLayout } from "./layout";
import { IPManagementDialog } from "./ip-management-dialog";
import {
DomainManagementDialog,
type Domain,
} from "./domain-management-dialog";
import type { ISiteDetail } from "@/entities/site/model/site-interface";
type ViewState = Partial<ISiteDetail>;
function RegisterPage({ siteId }: { siteId: string }) {
const router = useRouter();
const { data: siteDetail } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId],
queryFn: () => SiteApi.siteDetail(siteId),
select: (data) => {
setSiteKndCd(data.siteKndCd);
handleViewStateChange("siteKndCd")(data.siteKndCd);
handleViewStateChange("siteSkn")(data.siteSkn);
return data;
},
enabled: !!siteId,
});
const [siteKndCd, setSiteKndCd] = useState<string>("");
const [, setSiteSkinCd] = useState<string>("");
const [viewState, setViewState] = useState<ViewState>({});
const [representativeDomain, setRepresentativeDomain] = useState<string>("");
const handleViewStateChange = (key: keyof ISiteDetail) => (value: string) => {
setViewState({ ...viewState, [key]: value });
};
const handleDomainSave = (rows: Domain[]) => {
setRepresentativeDomain(
rows.find((row) => row.isRepresentative)?.domainUrl || "",
);
};
return (
<SitePageLayout>
<div className="card card-border !mt-6">
<div className="card-header">
<h3 className="text-2xl font-medium text-[#666]">사이트 등록/수정</h3>
<h3 className="text-2xl font-medium text-label">사이트 등록/수정</h3>
</div>
<div className="card">
<Table variant="secondary">
@@ -53,6 +74,9 @@ function RegisterPage({ siteId }: { siteId: string }) {
className="w-[12rem] !text-[1.3rem]"
defaultValue={siteDetail?.siteId}
disabled={!!siteId}
onChange={(e) =>
handleViewStateChange("siteId")(e.target.value)
}
/>
</TableCell>
</TableRow>
@@ -63,11 +87,13 @@ function RegisterPage({ siteId }: { siteId: string }) {
<CodeSelect
upCd="SITEKNDCD"
defaultValue={siteDetail?.siteKndCd}
onValueChange={(value) => {
setSiteKndCd(value);
}}
onValueChange={handleViewStateChange("siteKndCd")}
/>
<IPManagementDialog triggerDisabled={siteKndCd === "O"} />
{siteId && (
<IPManagementDialog
triggerDisabled={viewState.siteKndCd === "O"}
/>
)}
</div>
</TableCell>
</TableRow>
@@ -77,9 +103,7 @@ function RegisterPage({ siteId }: { siteId: string }) {
<CodeSelect
upCd="SITESKINCD"
defaultValue={siteDetail?.siteSkn}
onValueChange={(value) => {
setSiteSkinCd(value);
}}
onValueChange={handleViewStateChange("siteSkn")}
/>
</TableCell>
</TableRow>
@@ -90,6 +114,9 @@ function RegisterPage({ siteId }: { siteId: string }) {
className="!text-[1.3rem]"
placeholder="사이트명을 입력하십시오."
defaultValue={siteDetail?.siteNm}
onChange={(e) =>
handleViewStateChange("siteNm")(e.target.value)
}
/>
</TableCell>
</TableRow>
@@ -100,9 +127,23 @@ function RegisterPage({ siteId }: { siteId: string }) {
className="!text-[1.3rem]"
placeholder="사이트 설명을 입력하십시오."
defaultValue={siteDetail?.siteExpln}
onChange={(e) =>
handleViewStateChange("siteExpln")(e.target.value)
}
/>
</TableCell>
</TableRow>
<TableRow className={`${!siteId ? "hidden" : ""}`}>
<TableHead>사이트 도메인</TableHead>
<TableCell className="border">
<div className="flex gap-2">
<div className="bg-form h-[3.2rem] w-[21.5rem] cursor-not-allowed rounded-md border border-input px-3 py-2 text-[1.3rem] opacity-50">
{representativeDomain}
</div>
<DomainManagementDialog handleSave={handleDomainSave} />
</div>
</TableCell>
</TableRow>
<TableRow>
<TableHead>사이트 하단 표기 주소</TableHead>
<TableCell className="border">
@@ -110,6 +151,9 @@ function RegisterPage({ siteId }: { siteId: string }) {
className="!text-[1.3rem]"
placeholder="사이트 하단 주소를 입력하십시오."
defaultValue={siteDetail?.siteAddr}
onChange={(e) =>
handleViewStateChange("siteAddr")(e.target.value)
}
/>
</TableCell>
</TableRow>
@@ -120,6 +164,9 @@ function RegisterPage({ siteId }: { siteId: string }) {
className="!text-[1.3rem]"
placeholder="사이트 하단 전화번호를 입력하십시오."
defaultValue={siteDetail?.telNo}
onChange={(e) =>
handleViewStateChange("telNo")(e.target.value)
}
/>
</TableCell>
</TableRow>
@@ -130,6 +177,9 @@ function RegisterPage({ siteId }: { siteId: string }) {
className="!text-[1.3rem]"
placeholder="사이트 하단 팩스번호를 입력하십시오."
defaultValue={siteDetail?.faxNumber}
onChange={(e) =>
handleViewStateChange("faxNumber")(e.target.value)
}
/>
</TableCell>
</TableRow>
@@ -140,6 +190,9 @@ function RegisterPage({ siteId }: { siteId: string }) {
className="!text-[1.3rem]"
placeholder="사이트 표기 내용을 입력하십시오. (ex. COPYRIGHT c 2013 KECO. ALL RIGHTS RESERVED.)"
defaultValue={siteDetail?.lwndCn}
onChange={(e) =>
handleViewStateChange("lwndCn")(e.target.value)
}
/>
</TableCell>
</TableRow>
@@ -150,6 +203,9 @@ function RegisterPage({ siteId }: { siteId: string }) {
className="!text-[1.3rem]"
placeholder="아이콘 파일 경로 및 파일명을 입력하여 주십시오."
defaultValue={siteDetail?.bkmkIcon}
onChange={(e) =>
handleViewStateChange("bkmkIcon")(e.target.value)
}
/>
</TableCell>
</TableRow>
@@ -157,18 +213,17 @@ function RegisterPage({ siteId }: { siteId: string }) {
<TableHead>기본 사이트 여부</TableHead>
<TableCell className="border">
<RadioGroup
name="basicSiteYn"
defaultValue={siteDetail?.basicSiteYn}
onValueChange={(value) => {
console.log(value);
}}
onValueChange={handleViewStateChange("basicSiteYn")}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="default" id="r1" />
<Label htmlFor="r1">예</Label>
<RadioGroupItem value="Y" id="basicSiteY" />
<Label htmlFor="basicSiteY">예</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="comfortable" id="r2" />
<Label htmlFor="r2">아니오</Label>
<RadioGroupItem value="N" id="basicSiteN" />
<Label htmlFor="basicSiteN">아니오</Label>
</div>
</RadioGroup>
</TableCell>
@@ -177,18 +232,17 @@ function RegisterPage({ siteId }: { siteId: string }) {
<TableHead>사용 여부</TableHead>
<TableCell className="border">
<RadioGroup
name="useYn"
defaultValue={siteDetail?.useYn}
onValueChange={(value) => {
console.log(value);
}}
onValueChange={handleViewStateChange("useYn")}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="default" id="r1" />
<Label htmlFor="r1">사용</Label>
<RadioGroupItem value="Y" id="useY" />
<Label htmlFor="useY">사용</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="comfortable" id="r2" />
<Label htmlFor="r2">사용안함</Label>
<RadioGroupItem value="N" id="useN" />
<Label htmlFor="useN">사용안함</Label>
</div>
</RadioGroup>
</TableCell>
@@ -199,7 +253,7 @@ function RegisterPage({ siteId }: { siteId: string }) {
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
<Button size="lg">저장</Button>
<Button size="lg" color="white">
<Button size="lg" color="white" onClick={() => router.back()}>
취소
</Button>
<Button size="lg" color="red">