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",