diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..c3f502a19 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml +# 에디터 기반 HTTP 클라이언트 요청 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/apps/common/features/start_13000.sh b/apps/common/features/start_13000.sh new file mode 100755 index 000000000..e1ba84bbf --- /dev/null +++ b/apps/common/features/start_13000.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +PORT=13000 +LOG_FILE="start_13000.log" + +# 1. 기존 포트 사용 프로세스 종료 +PID=$(lsof -t -i :$PORT) +if [ -n "$PID" ]; then + echo "Killing process on port $PORT (PID: $PID)" + kill -9 $PID +fi + +# 2. 백그라운드로 앱 시작 (로그 파일로 리디렉션) +echo "Starting app on port $PORT..." +nohup pnpm start > $LOG_FILE 2>&1 & + +# 3. PID 확인 +sleep 1 +NEW_PID=$(lsof -t -i :$PORT) +if [ -n "$NEW_PID" ]; then + echo "App started successfully (PID: $NEW_PID)" + echo "Logs: tail -f $LOG_FILE" +else + echo "Failed to start app. Check logs: $LOG_FILE" +fi \ No newline at end of file diff --git a/apps/cs/src/entities/group-user/model/group-user-interface.ts b/apps/cs/src/entities/group-user/model/group-user-interface.ts index 8935bfacc..52fa97fce 100644 --- a/apps/cs/src/entities/group-user/model/group-user-interface.ts +++ b/apps/cs/src/entities/group-user/model/group-user-interface.ts @@ -3,6 +3,7 @@ import type { PageableList, PageableParams } from "@/shared/model"; // 그룹 사용자 검색 파라미터 인터페이스 export interface GroupUserSearchParams extends PageableParams { keyword?: string; + userNm?: string; } // 그룹 사용자 목록 응답 인터페이스 diff --git a/apps/cs/src/entities/group/index.ts b/apps/cs/src/entities/group/index.ts index da1d72a41..30dc07714 100644 --- a/apps/cs/src/entities/group/index.ts +++ b/apps/cs/src/entities/group/index.ts @@ -3,4 +3,5 @@ export { groupFormSchema, type GroupFormData, type GroupListResponseDto, + type GroupSearchParams, } from "./model/group-interface"; diff --git a/apps/cs/src/pages/code/model/lwpocode-table-columns.tsx b/apps/cs/src/pages/code/model/lwpocode-table-columns.tsx index 1bea87275..a119d88d9 100644 --- a/apps/cs/src/pages/code/model/lwpocode-table-columns.tsx +++ b/apps/cs/src/pages/code/model/lwpocode-table-columns.tsx @@ -5,56 +5,62 @@ import type { LwpoCode } from "@/entities/code"; import { LwpoCodeManagementDialog } from "../ui/lwpocode-dialog"; import { useState } from "react"; -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); +// columns를 함수로 변경하여 onAfterSave를 주입받음 +export function getColumns(onAfterSave: () => void) : ColumnDef[] { + return [ + { + id: "no", + header: "NO", + cell: ({ row, table }) => { + const totalRows = table.getRowCount(); + const { pageIndex, pageSize } = table.getState().pagination; + return totalRows - (pageIndex * pageSize + row.index); + }, }, - }, - { - accessorKey: "lwpoCd", - header: "하위코드", - }, - { - accessorKey: "lwpoCdNm", - header: "하위코드명", - }, - { - accessorKey: "useYn", - header: "사용 여부", - }, - { - accessorKey: "regDt", - header: "등록일", - cell: ({ row }) => { - const date = dayjs(row.original.regDt); - return date.format("YYYY-MM-DD"); + { + accessorKey: "lwpoCd", + header: "하위코드", }, - }, - { - id: "actions", - header: "관리", - cell: ({ row }) => ( -
e.stopPropagation()}> - -
- ), - }, -]; + { + accessorKey: "lwpoCdNm", + 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 LwpoCodeManageButton = ({ lwpoCd, upCd, + onAfterSave, }: { lwpoCd: string; upCd: string; + onAfterSave: () => void; }) => { const [dialogOpen, setDialogOpen] = useState(false); return ( @@ -63,6 +69,7 @@ const LwpoCodeManageButton = ({ upCd={upCd} open={dialogOpen} onOpenChange={setDialogOpen} + onAfterSave={onAfterSave} > diff --git a/apps/cs/src/pages/code/ui/lwpocode-dialog.tsx b/apps/cs/src/pages/code/ui/lwpocode-dialog.tsx index d6c6bb685..d21f5b60a 100644 --- a/apps/cs/src/pages/code/ui/lwpocode-dialog.tsx +++ b/apps/cs/src/pages/code/ui/lwpocode-dialog.tsx @@ -38,6 +38,7 @@ type ManagementLwpoCodeDialogProps = { children?: React.ReactNode; open?: boolean; onOpenChange?: (open: boolean) => void; + onAfterSave?: () => void; // 콜백 추가 }; const LwpoCodeManagementDialog = memo( @@ -47,6 +48,7 @@ const LwpoCodeManagementDialog = memo( children, open, onOpenChange, + onAfterSave, // 콜백 사용 ...props }: ManagementLwpoCodeDialogProps) => { const isRegistration = !lwpoCd; @@ -57,7 +59,7 @@ const LwpoCodeManagementDialog = memo( const { data: detailData } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, lwpoCd], queryFn: () => LwpoCodeApi.lwpoCodeDetail(upCd!, lwpoCd!), - enabled: !!upCd && !isRegistration, + enabled: !!upCd && !isRegistration && open, }); const { data: upCodeData } = useQuery({ @@ -69,7 +71,10 @@ const LwpoCodeManagementDialog = memo( const { mutateAsync: createLwpoCode } = useMutationWrapper({ mutationFn: (data: LwpoCodeFormData) => LwpoCodeApi.createLwpoCode(data), onSuccess: () => { - alert("등록되었습니다.", () => onOpenChange?.(false)); + alert("등록되었습니다.", () => { + onOpenChange?.(false); + onAfterSave?.(); + }); queryClient.invalidateQueries({ queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd], }); @@ -80,7 +85,10 @@ const LwpoCodeManagementDialog = memo( mutationFn: (data: LwpoCodeFormData) => LwpoCodeApi.updateLwpoCode(upCd!, lwpoCd!, data), onSuccess: () => { - alert("수정되었습니다.", () => onOpenChange?.(false)); + alert("수정되었습니다.", () => { + onOpenChange?.(false); + onAfterSave?.(); + }); }, }); @@ -99,15 +107,38 @@ const LwpoCodeManagementDialog = memo( }, }); - const onSubmit = - (callback: (data: LwpoCodeFormData) => void) => - async (data: LwpoCodeFormData) => { - const confirmMessage = isRegistration - ? "등록하시겠습니까?" - : "수정하시겠습니까?"; - - if (await confirm(confirmMessage)) callback(data); - }; + const handleRegister = async () => { + if (!upCd) { + alert("상위코드가 없습니다."); + return; + } + const confirmMessage = "등록하시겠습니까?"; + if (await confirm(confirmMessage)) { + const rawValues = form.getValues(); + const values = { + ...rawValues, + upCd: upCd, + sortSeq: Number(rawValues.sortSeq) || 0, + }; + await createLwpoCode(values); + } + }; + const handleSave = async () => { + if (!upCd) { + alert("상위코드가 없습니다."); + return; + } + const confirmMessage = "수정하시겠습니까?"; + if (await confirm(confirmMessage)) { + const rawValues = form.getValues(); + const values = { + ...rawValues, + upCd: upCd, + sortSeq: Number(rawValues.sortSeq) || 0, + }; + await updateLwpoCode(values); + } + }; return ( @@ -120,9 +151,14 @@ const LwpoCodeManagementDialog = memo(
{ + e.preventDefault(); + if (isRegistration) { + handleRegister(); + } else { + handleSave(); + } + }} className="space-y-4" > @@ -298,7 +334,11 @@ const LwpoCodeManagementDialog = memo(
- diff --git a/apps/cs/src/pages/code/ui/lwpocode-section.tsx b/apps/cs/src/pages/code/ui/lwpocode-section.tsx index 15773c5f4..ac3d8218c 100644 --- a/apps/cs/src/pages/code/ui/lwpocode-section.tsx +++ b/apps/cs/src/pages/code/ui/lwpocode-section.tsx @@ -5,7 +5,7 @@ import { Button, Input, Label } from "@common/components"; import { LwpoCodeApi, type LwpoCodeParams } from "@/entities/code"; import { ENDPOINT } from "@/shared/config"; import { useAlertStore, useDataTable } from "@/shared/lib"; -import { columns } from "../model/lwpocode-table-columns"; +import { getColumns } from "../model/lwpocode-table-columns"; import { LwpoCodeManagementDialog } from "./lwpocode-dialog"; type LwpoCodeSectionProps = { @@ -24,7 +24,7 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) { pageIndex: 0, pageSize: 10, }); - + const query = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.LWPO_CODE, upCd, searchParams, pagination], queryFn: () => @@ -34,10 +34,17 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) { page: pagination.pageIndex, size: pagination.pageSize, }), - enabled: !!upCd, + enabled: !!upCd , placeholderData: keepPreviousData, - }); + }); + // 등록/수정 후 목록 재조회 콜백 + const handleAfterSave = () => { + query.refetch(); + }; + const columns = getColumns(handleAfterSave); + + const { mutateAsync: codeRefreshAll } = useMutation({ mutationFn: LwpoCodeApi.codeRefreshAll, onSuccess: () => { @@ -118,7 +125,9 @@ function LwpoCodeSection({ upCd }: LwpoCodeSectionProps) { diff --git a/apps/cs/src/pages/groups/ui/group-users-section.tsx b/apps/cs/src/pages/groups/ui/group-users-section.tsx index dc16fda60..4b7170e3a 100644 --- a/apps/cs/src/pages/groups/ui/group-users-section.tsx +++ b/apps/cs/src/pages/groups/ui/group-users-section.tsx @@ -12,7 +12,7 @@ import { type RowSelectionState, } from "@tanstack/react-table"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { Button } from "@common/components"; +import { Button,Input, Label } from "@common/components"; import { GroupUsersApi } from "@/entities/group-user"; import { ENDPOINT } from "@/shared/config"; import { useAlertStore, useDataTable } from "@/shared/lib"; @@ -32,13 +32,17 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) { pageIndex: 0, pageSize: 10, }); + + const [userNm, setUserNm] = useState(""); + const [rowSelection, setRowSelection] = useState({}); const query = useQuery({ - queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId, pagination], + queryKey: [ENDPOINT.CMS_SERVICE.GROUP_USERS, groupId, pagination, userNm], queryFn: () => GroupUsersApi.findPageGroupUser({ keyword: groupId, + userNm: userNm, page: pagination.pageIndex, size: pagination.pageSize, }), @@ -46,6 +50,20 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) { placeholderData: keepPreviousData, }); + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setPagination({ + ...pagination, + pageIndex: 0, + }); + // userNm이 이미 state에 반영되어 있으므로 별도 처리 불필요 + // 쿼리키에 userNm이 포함되어 있어 자동 refetch됨 + }; + + const handleReset = () => { + setUserNm(""); + }; + const { tableComponent, tableInstance: table } = useDataTable({ query, tableOptions: { @@ -97,6 +115,29 @@ function GroupUsersSection({ groupId }: GroupUsersSectionProps) { return (
+
+ + + setUserNm(e.target.value)} + /> + + + +

그룹 사용자 목록

diff --git a/apps/cs/src/pages/groups/ui/groups-section.tsx b/apps/cs/src/pages/groups/ui/groups-section.tsx index 8acc7f019..9c7f5b6ca 100644 --- a/apps/cs/src/pages/groups/ui/groups-section.tsx +++ b/apps/cs/src/pages/groups/ui/groups-section.tsx @@ -7,14 +7,14 @@ import { useQueryClient, } from "@tanstack/react-query"; import { getCoreRowModel, type PaginationState } from "@tanstack/react-table"; -import { Button } from "@common/components"; -import { GroupApi } from "@/entities/group"; +import { Button, Input, Label } from "@common/components"; +import { GroupApi , type GroupSearchParams} from "@/entities/group"; import { ENDPOINT } from "@/shared/config"; import { useDataTable } from "@/shared/lib"; import { createColumns } from "../model/groups-table-columns"; import { GroupsDialog } from "./groups-dialog"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; -import { useAlertStore, useMutationWrapper } from "@/shared/lib"; +import { useAlertStore, useMutationWrapper } from "@/shared/lib"; type GroupsSectionProps = { handleSelectedGroup: (groupId: string) => void; @@ -26,10 +26,20 @@ function GroupsSection({ handleSelectedGroup }: GroupsSectionProps) { pageSize: 10, }); + const [groupNm, setGroupNm] = useState(""); + const [searchParams, setSearchParams] = useState({ + groupNm: "", + delYn :"N", + sort : "groupId,desc", + }); + + + const query = useQuery({ - queryKey: [ENDPOINT.CMS_SERVICE.GROUPS, pagination], + queryKey: [ENDPOINT.CMS_SERVICE.GROUPS,searchParams, pagination], queryFn: () => GroupApi.findPageGroup({ + ...searchParams, page: pagination.pageIndex, size: pagination.pageSize, }), @@ -53,6 +63,27 @@ function GroupsSection({ handleSelectedGroup }: GroupsSectionProps) { await deleteGroup(groupId); } }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setPagination({ + ...pagination, + pageIndex: 0, + }); + setSearchParams({ + ...searchParams, + groupNm, + }); + }; + + const handleReset = () => { + setGroupNm(""); + setSearchParams({ + ...searchParams, + groupNm: "", + delYn:"N" + }); + }; const handleRowClick = (row: { groupId: string }) => { table.setRowSelection({ [row.groupId]: true }); handleSelectedGroup(row.groupId); // 1. 리스트에서 선택된 groupId 저장 @@ -77,6 +108,43 @@ function GroupsSection({ handleSelectedGroup }: GroupsSectionProps) { return (
+
+
+ + setGroupNm(e.target.value)} + /> + + + + + setSearchParams({ + ...searchParams, + delYn: e.target.checked ? "Y" : "N", + }) + } + /> +
+

그룹 목록

diff --git a/apps/cs/start_cs.sh b/apps/cs/start_cs.sh new file mode 100755 index 000000000..7fa2c18a7 --- /dev/null +++ b/apps/cs/start_cs.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +PORT=3000 +LOG_FILE="start_cs.log" + +# 1. 기존 포트 사용 프로세스 종료 +PID=$(lsof -t -i :$PORT) +if [ -n "$PID" ]; then + echo "Killing process on port $PORT (PID: $PID)" + kill -9 $PID +fi + +# 2. 백그라운드로 앱 시작 (로그 파일로 리디렉션) +echo "Starting app on port $PORT..." +nohup pnpm start > $LOG_FILE 2>&1 & + +# 3. PID 확인 +sleep 1 +NEW_PID=$(lsof -t -i :$PORT) +if [ -n "$NEW_PID" ]; then + echo "App started successfully (PID: $NEW_PID)" + echo "Logs: tail -f $LOG_FILE" +else + echo "Failed to start app. Check logs: $LOG_FILE" +fi \ No newline at end of file