From 7bab3ef97d8825fc408a2aec75b8970c6be04883 Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 12 Jun 2025 16:50:04 +0900 Subject: [PATCH] =?UTF-8?q?WIP:=20=ED=9A=8C=EC=9B=90=20=EB=93=B1=EB=A1=9D?= =?UTF-8?q?=20=ED=8F=BC=20=EC=9E=91=EC=97=85=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../users/register/page.tsx | 2 + apps/cs/src/entities/code/ui/code-select.tsx | 6 +- apps/cs/src/entities/user/index.ts | 2 + .../src/entities/user/model/user-interface.ts | 90 ++- apps/cs/src/pages/users/api/zip-service.ts | 41 ++ apps/cs/src/pages/users/index.ts | 1 + .../src/pages/users/model/table-columns.tsx | 8 +- apps/cs/src/pages/users/ui/create-page.tsx | 64 ++ apps/cs/src/pages/users/ui/users-form.tsx | 628 ++++++++++++++++++ .../src/pages/users/ui/zip-search-dialog.tsx | 217 ++++++ apps/cs/src/shared/config/api.ts | 1 + 11 files changed, 1019 insertions(+), 41 deletions(-) create mode 100644 apps/cs/app/(with-base-layout)/users/register/page.tsx create mode 100644 apps/cs/src/pages/users/api/zip-service.ts create mode 100644 apps/cs/src/pages/users/ui/create-page.tsx create mode 100644 apps/cs/src/pages/users/ui/users-form.tsx create mode 100644 apps/cs/src/pages/users/ui/zip-search-dialog.tsx diff --git a/apps/cs/app/(with-base-layout)/users/register/page.tsx b/apps/cs/app/(with-base-layout)/users/register/page.tsx new file mode 100644 index 000000000..31e36e41b --- /dev/null +++ b/apps/cs/app/(with-base-layout)/users/register/page.tsx @@ -0,0 +1,2 @@ +import { CreatePage } from "@/pages/users"; +export default CreatePage; diff --git a/apps/cs/src/entities/code/ui/code-select.tsx b/apps/cs/src/entities/code/ui/code-select.tsx index 850282375..08df4ce51 100644 --- a/apps/cs/src/entities/code/ui/code-select.tsx +++ b/apps/cs/src/entities/code/ui/code-select.tsx @@ -18,7 +18,6 @@ type CodeSelectProps = { function CodeSelect({ upCd, - defaultValue, placeholder = "선택", className, ...props @@ -29,13 +28,10 @@ function CodeSelect({ enabled: !!upCd, }); - const defaultValueText = - code?.find((item) => item.lwpoCd === defaultValue)?.lwpoCdNm ?? placeholder; - return ( { + field.onChange(e); + setIsValidUserId(false); + }} + className="w-[24rem] !text-[1.3rem]" + placeholder="아이디를 입력하세요" + disabled={isModifyMode} + /> + + + {!isModifyMode && ( + + )} + + )} + /> + + + + {/* 비밀번호 */} + + 비밀번호 + + ( + + + { + field.onChange(e); + handlePasswordChange(e.target.value); + }} + className="w-[24rem] !text-[1.3rem]" + placeholder="비밀번호를 입력하세요" + /> + + + + )} + /> + + + + {/* 비밀번호 확인 */} + + 비밀번호 확인 + +
+ + handlePasswordConfirmChange(e.target.value) + } + className="w-[24rem] !text-[1.3rem]" + placeholder="비밀번호를 다시 입력하세요" + /> + {!passwordMatch && passwordConfirm && ( +

+ 비밀번호가 일치하지 않습니다. +

+ )} +
+
+
+ + {/* 비밀번호 힌트 */} + + 비밀번호 힌트 + + ( + + + + + + )} + /> + + + + {/* 비밀번호 정답 */} + + 비밀번호 정답 + + ( + + + + + + )} + /> + + + + {/* 이름 */} + + 이름 + + ( + + + + + + )} + /> + + + + {/* 연락처 */} + + 연락처 + + ( + + + + + + )} + /> + + + + + ( + + + + + + )} + /> + + + + {/* 이메일 */} + + 이메일 + + ( +
+ + + { + field.onChange(e); + setIsValidEmail(false); + }} + className="w-[24rem] !text-[1.3rem]" + placeholder="이메일을 입력하세요" + /> + + + +
+ )} + /> +
+
+ + {/* 주소 */} + + 주소 + +
+
+ ( + + + + + + )} + /> + +
+ ( + + + + + + )} + /> +
+
+
+ + + ( + + + + + + )} + /> + + + + {/* 기관구분 */} + + 기관구분 + + ( + + + { + field.onChange(value); + form.setValue("dtlInstCd", ""); + form.setValue("laeSeCd", ""); + form.setValue("enoCd", ""); + }} + className="w-[16rem]" + placeholder="기관 선택" + /> + + + + )} + /> + + + + {/* 세부기관 */} + + 세부기관 + + ( + + + + + + )} + /> + + + + {/* 소속 */} + + 소속 + + ( + + + + + + )} + /> + + + + {/* 환경청 */} + + 환경청 + + ( + + + + + + )} + /> + + + + {/* 환경청 기관/업체명 */} + + 기관/업체명 + + ( + + + + + + )} + /> + + + + + + + ); +} + +export { UsersForm }; diff --git a/apps/cs/src/pages/users/ui/zip-search-dialog.tsx b/apps/cs/src/pages/users/ui/zip-search-dialog.tsx new file mode 100644 index 000000000..be59c61ee --- /dev/null +++ b/apps/cs/src/pages/users/ui/zip-search-dialog.tsx @@ -0,0 +1,217 @@ +import { useState, useRef, useCallback, useEffect } from "react"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { + useReactTable, + getCoreRowModel, + getPaginationRowModel, + type PaginationState, +} from "@tanstack/react-table"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogTrigger, + DialogClose, + Button, + Input, + Label, + DataTable, +} from "@common/components"; +import { ENDPOINT } from "@/shared/config"; +import { ZipApi, type Zip, type ZipSearchParams } from "../api/zip-service"; + +interface ZipSearchDialogProps { + onSelectAddress: (data: { zip: string; addr: string }) => void; +} + +function ZipSearchDialog({ onSelectAddress }: ZipSearchDialogProps) { + const [open, setOpen] = useState(false); + const [selectedAddress, setSelectedAddress] = useState(null); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + const [searchQuery, setSearchQuery] = useState({ + roadNm: "", + lotnoMno: null, + lotnoSno: null, + }); + const hasFetchedOnce = useRef(false); + + const fetchZipData = useCallback( + () => + ZipApi.searchZip({ + ...searchQuery, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + [pagination.pageIndex, pagination.pageSize], + ); + + const { data, refetch } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.ZIPS, pagination], + queryFn: fetchZipData, + placeholderData: keepPreviousData, + enabled: false, + }); + + const handleSearch = async () => { + await refetch(); + setSelectedAddress(null); + hasFetchedOnce.current = true; + }; + + const handleRowClick = (row: Zip) => { + table.setRowSelection({ + [row.zipId.toString()]: true, + }); + setSelectedAddress(row); + }; + + const handleSelectAddress = () => { + onSelectAddress({ + zip: selectedAddress?.zoneNo ?? "", + addr: selectedAddress?.zipAddr ?? "", + }); + setOpen(false); + }; + + const table = useReactTable({ + data: data?.content ?? [], + columns: [ + { + accessorKey: "zoneNo", + header: "우편번호", + }, + { + accessorKey: "zipAddr", + header: "주소", + }, + ], + state: { + pagination, + }, + rowCount: data?.totalElements ?? 0, + manualPagination: true, + enableMultiRowSelection: false, + getRowId: (row) => row.zipId.toString(), + onPaginationChange: setPagination, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + }); + + useEffect(() => { + if (hasFetchedOnce.current) refetch(); + }, [pagination, refetch]); + + return ( + + + + + + + 주소 검색 + + +
{ + e.preventDefault(); + e.stopPropagation(); + handleSearch(); + }} + className="mb-4 space-y-2" + > +
+
+
+ + + setSearchQuery({ + ...searchQuery, + roadNm: e.target.value, + }) + } + placeholder="예: 테헤란로" + className="!text-[1.3rem]" + /> +
+ +
+ + { + const value = e.target.value + ? parseInt(e.target.value) + : null; + setSearchQuery({ + ...searchQuery, + lotnoMno: value, + }); + }} + placeholder="예: 123" + className="!text-[1.3rem]" + /> +
+ +
+ + { + const value = e.target.value + ? parseInt(e.target.value) + : null; + setSearchQuery({ + ...searchQuery, + lotnoSno: value, + }); + }} + placeholder="예: 45" + className="!text-[1.3rem]" + /> +
+
+ +
+ +
+
+
+ + {data?.content.length && ( + + )} + + + + + + + +
+
+ ); +} + +export { ZipSearchDialog }; diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index 3eba9fa91..da5edaea1 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -44,6 +44,7 @@ export const ENDPOINT = { SURVEY: "/cms-service/api/v1/srvy", SURVEY_QUESTION: "/cms-service/api/v1/srvy-qitem", SURVEY_ARTICLE: "/cms-service/api/v1/srvy-artcl", + ZIPS: "/cms-service/api/v1/zips", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs",