From 6bfde59abf7a6fd1971066bd5d4e6f46b8fd064a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EC=B2=A0?= Date: Thu, 19 Jun 2025 20:09:33 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/(with-base-layout)/loginlog/page.tsx | 2 + .../pages/loginlog/api/loginlog-service.ts | 27 ++ apps/cs/src/pages/loginlog/index.ts | 1 + .../loginlog/model/loginlog-interface.ts | 38 +++ .../pages/loginlog/model/table-columns.tsx | 64 ++++ .../src/pages/loginlog/ui/loginlog-dialog.tsx | 280 ++++++++++++++++++ .../src/pages/loginlog/ui/loginlog-page.tsx | 95 ++++++ apps/cs/src/shared/config/api.ts | 1 + 8 files changed, 508 insertions(+) create mode 100644 apps/cs/app/(with-base-layout)/loginlog/page.tsx create mode 100644 apps/cs/src/pages/loginlog/api/loginlog-service.ts create mode 100644 apps/cs/src/pages/loginlog/index.ts create mode 100644 apps/cs/src/pages/loginlog/model/loginlog-interface.ts create mode 100644 apps/cs/src/pages/loginlog/model/table-columns.tsx create mode 100644 apps/cs/src/pages/loginlog/ui/loginlog-dialog.tsx create mode 100644 apps/cs/src/pages/loginlog/ui/loginlog-page.tsx diff --git a/apps/cs/app/(with-base-layout)/loginlog/page.tsx b/apps/cs/app/(with-base-layout)/loginlog/page.tsx new file mode 100644 index 000000000..960f03e05 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/loginlog/page.tsx @@ -0,0 +1,2 @@ +import { LoginlogPage } from "@/pages/loginlog"; +export default LoginlogPage; diff --git a/apps/cs/src/pages/loginlog/api/loginlog-service.ts b/apps/cs/src/pages/loginlog/api/loginlog-service.ts new file mode 100644 index 000000000..a813ba897 --- /dev/null +++ b/apps/cs/src/pages/loginlog/api/loginlog-service.ts @@ -0,0 +1,27 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + LoginlogInfo, + LoginlogList, + LoginlogParams, + LoginlogService, +} from "../model/loginlog-interface"; + +export class LoginlogApiService implements LoginlogService { + public async getLoginlogList(params: LoginlogParams): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.LOGINLOG}`, + { params }, + ); + return response.data; + } + + public async getLoginlogInfo(lgnSn: string): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.LOGINLOG}/${lgnSn}`, + ); + return response.data; + } +} + +export const LoginlogApi = new LoginlogApiService(); diff --git a/apps/cs/src/pages/loginlog/index.ts b/apps/cs/src/pages/loginlog/index.ts new file mode 100644 index 000000000..4e5e70ea8 --- /dev/null +++ b/apps/cs/src/pages/loginlog/index.ts @@ -0,0 +1 @@ +export { LoginlogPage } from "./ui/loginlog-page"; diff --git a/apps/cs/src/pages/loginlog/model/loginlog-interface.ts b/apps/cs/src/pages/loginlog/model/loginlog-interface.ts new file mode 100644 index 000000000..37d6a33b9 --- /dev/null +++ b/apps/cs/src/pages/loginlog/model/loginlog-interface.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface LoginlogParams extends PageableParams { + lgnSn?: string; + userId?: string; + siteId?: string; +} + +export interface Loginlog { + lgnSn: string; + lgnDt: string; + userId: string; + lgnIp: string; + lgnMethodCd: string; + errOcrnYn: string; + lgnErrCd: string; + siteId: string; +} + +export interface LoginlogInfo { + lgnSn: string; + lgnDt: string; + userId: string; + lgnIp: string; + lgnMethodCd: string; + errOcrnYn: string; + lgnErrCd: string; + siteId: string; +} + +export type LoginlogList = PageableList; + +export interface LoginlogService { + getLoginlogList: (params: LoginlogParams) => Promise; + getLoginlogInfo: (lgnSn: string) => Promise; + +} diff --git a/apps/cs/src/pages/loginlog/model/table-columns.tsx b/apps/cs/src/pages/loginlog/model/table-columns.tsx new file mode 100644 index 000000000..298ee7d32 --- /dev/null +++ b/apps/cs/src/pages/loginlog/model/table-columns.tsx @@ -0,0 +1,64 @@ +import dayjs from "dayjs"; +import { useState } from "react"; +import type { ColumnDef } from "@tanstack/react-table"; +import { Button } from "@common/components"; +import type { UpCode } from "@/entities/code"; +import { LoginlogDialog } from "../ui/loginlog-dialog"; + +export const columns: ColumnDef[] = [ + { + id: "no", + header: "NO", + cell: ({ row, table }) => { + const totalRows = table.getRowCount(); + const { pageIndex, pageSize } = table.getState().pagination; + return totalRows - (pageIndex * pageSize + row.index); + }, + }, + { + accessorKey: "upCd", + header: "상위코드", + }, + { + accessorKey: "upCdNm", + header: "상위코드명", + }, + { + accessorKey: "cdSe", + header: "코드구분", + }, + { + accessorKey: "useYn", + header: "사용여부", + }, + { + accessorKey: "regDt", + header: "등록일", + cell: ({ row }) => { + const date = dayjs(row.original.regDt); + return date.format("YYYY-MM-DD"); + }, + }, + { + id: "actions", + header: "관리", + cell: ({ row }) => ( +
e.stopPropagation()}> + +
+ ), + }, +]; + +const LoginlogButton = ({ upCd }: { upCd: string }) => { + const [dialogOpen, setDialogOpen] = useState(false); + return ( + + + + ); +}; diff --git a/apps/cs/src/pages/loginlog/ui/loginlog-dialog.tsx b/apps/cs/src/pages/loginlog/ui/loginlog-dialog.tsx new file mode 100644 index 000000000..55aea3ac2 --- /dev/null +++ b/apps/cs/src/pages/loginlog/ui/loginlog-dialog.tsx @@ -0,0 +1,280 @@ +import { memo } from "react"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useQuery } from "@tanstack/react-query"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { + Button, + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Input, + RadioGroup, + RadioGroupItem, + Label, + DialogTrigger, +} from "@common/components"; +import { + CodeSelect, + UpCodeApi, + upCodeFormSchema, + type UpCodeFormData, +} from "@/entities/code"; +import { SiteSelect } from "@/entities/site"; +import { ENDPOINT } from "@/shared/config"; +import { useAlertStore, useMutationWrapper } from "@/shared/lib"; +import { Form, FormField, FormItem, FormControl } from "@/shared/ui/form"; + +type LoginlogDialogProps = { + upCd?: string; + children?: React.ReactNode; + open?: boolean; + onOpenChange?: (open: boolean) => void; +}; + +const LoginlogDialog = memo( + ({ + upCd, + children, + open, + onOpenChange, + ...props + }: LoginlogDialogProps) => { + const isRegistration = !upCd; + + const { confirm } = useConfirm(); + const alert = useAlertStore((state) => state.setMessage); + + const { data: detailData } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.UP_CODE, upCd], + queryFn: () => UpCodeApi.upCodeDetail(upCd!), + enabled: open && !!upCd, + }); + + const { mutateAsync: createUpCode } = useMutationWrapper({ + mutationFn: (data: UpCodeFormData) => UpCodeApi.createUpCode(data), + onSuccess: () => { + alert("등록되었습니다.", () => onOpenChange?.(false)); + }, + }); + + const { mutateAsync: updateUpCode } = useMutationWrapper({ + mutationFn: (data: UpCodeFormData) => UpCodeApi.updateUpCode(upCd!, data), + onSuccess: () => { + alert("수정되었습니다.", () => onOpenChange?.(false)); + }, + }); + + const form = useForm({ + resolver: zodResolver(upCodeFormSchema), + values: { + upCd: detailData?.upCd ?? "", + upCdNm: detailData?.upCdNm ?? "", + cdSe: detailData?.cdSe ?? "", + siteId: detailData?.siteId ?? "", + upCdExpln: detailData?.upCdExpln ?? "", + useYn: detailData?.useYn ?? "Y", + delYn: "N", + }, + }); + + const onSubmit = + (callback: (data: UpCodeFormData) => void) => + async (data: UpCodeFormData) => { + const confirmMessage = isRegistration + ? "등록하시겠습니까?" + : "수정하시겠습니까?"; + + if (await confirm(confirmMessage)) callback(data); + }; + + return ( + + {children} + + + + {isRegistration ? "상위코드 등록" : "상위코드 관리"} + + +
+ + + + + + + + + 상위코드 + + ( + + + + + + )} + /> + + + + 상위코드명 + + ( + + + + + + )} + /> + + + + 상위코드설명 + + ( + + + + + + )} + /> + + + + 코드구분 + + ( + + + + + + )} + /> + + + + 사이트 + + ( + + + + + + )} + /> + + + + 사용여부 + + ( + + + +
+ + +
+
+ + +
+
+
+
+ )} + /> +
+
+
+
+ + + + + + +
+ +
+
+ ); + }, + (prevProps, nextProps) => { + return ( + prevProps.upCd === nextProps.upCd && prevProps.open === nextProps.open + ); + }, +); + +LoginlogDialog.displayName = "LoginlogDialog"; + +export { LoginlogDialog }; diff --git a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx new file mode 100644 index 000000000..01fcf07fd --- /dev/null +++ b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx @@ -0,0 +1,95 @@ +import { useState } from "react"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { + getCoreRowModel, + useReactTable, + type PaginationState, +} from "@tanstack/react-table"; +import { Button, DataTable, Input, Label } from "@common/components"; +import { LoginlogApi} from "../api/loginlog-service"; +import { type LoginlogParams} from "../model/loginlog-interface"; + +import { ENDPOINT } from "@/shared/config"; +import { columns } from "../model/table-columns"; +import { LoginlogDialog } from "./loginlog-dialog"; + +type LoginlogPageProps = { + lgnSn?: string; +}; + +function LoginlogPage({ lgnSn }: LoginlogPageProps) { + const [dialogOpen, setDialogOpen] = useState(false); + const [loginlogQuery, setLoginlogQuery] = useState({ + userId: "", + siteId: "", + }); + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const { data: loginlogList, refetch } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, lgnSn, pagination], + queryFn: () => + LoginlogApi.getLoginlogList({ + ...loginlogQuery, + lgnSn, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + enabled: !!lgnSn, + placeholderData: keepPreviousData, + }); + + const table = useReactTable({ + data: loginlogList?.content ?? [], + columns, + rowCount: loginlogList?.totalElements ?? 0, + state: { pagination }, + manualPagination: true, + enableMultiRowSelection: false, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + getRowId: (row) => row.lwpoCd, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + refetch(); + }; + + return ( +
+
+
+ + + setLoginlogQuery({ + ...loginlogQuery, + userId: e.target.value, + }) + } + /> + +
+
+
+
+

하위코드 목록

+
+
+ +
+
+
+ ); +} + +export { LoginlogPage }; diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index da5edaea1..7025266ca 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -45,6 +45,7 @@ export const ENDPOINT = { SURVEY_QUESTION: "/cms-service/api/v1/srvy-qitem", SURVEY_ARTICLE: "/cms-service/api/v1/srvy-artcl", ZIPS: "/cms-service/api/v1/zips", + LOGINLOG: "/cms-service/api/v1/loginlog", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs",