From 999fd610afca16b23ec4777ee7d7c6ecd8c6d8ce Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 6 Aug 2026 16:17:44 +0900 Subject: [PATCH] =?UTF-8?q?[as]=20feat:=20=EA=B2=B0=EC=9E=AC=EC=84=A0=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/entities/elapr/lib/apln-validators.ts | 5 ++-- .../ui/elapr-tmplt-list.tsx | 20 +++++++++---- apps/as/src/shared/lib/index.ts | 1 + .../src/shared/lib/use-mutation-wrapper.tsx | 29 +++++++++++++++++++ 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 apps/as/src/shared/lib/use-mutation-wrapper.tsx diff --git a/apps/as/src/entities/elapr/lib/apln-validators.ts b/apps/as/src/entities/elapr/lib/apln-validators.ts index 91f1a95cf..8b75b4279 100644 --- a/apps/as/src/entities/elapr/lib/apln-validators.ts +++ b/apps/as/src/entities/elapr/lib/apln-validators.ts @@ -16,7 +16,7 @@ export function validateAtrzOrder(rows: AtrzOrderValidateRow[]): string | null { const visibleRows = rows.filter((row) => !row.isDeleted); const finalCount = visibleRows.filter((row) => row.atrzSeComCd === ATRZ_FINAL_CD).length; - if (finalCount === 0) { + if (visibleRows.length > 0 && finalCount === 0) { return "최종승인자는 반드시 등록해야 합니다."; } if (finalCount > 1) { @@ -52,7 +52,8 @@ export function validateAllRowsForSave( } const visibleRows = getVisibleRows(rows); - if (visibleRows.length === 0) { + const hasDeletedDirtyRows = rows.some((row) => row.isDeleted && row.isDirty); + if (visibleRows.length === 0 && !hasDeletedDirtyRows) { return "저장할 결재선이 없습니다."; } diff --git a/apps/as/src/pages/approval-line-management/ui/elapr-tmplt-list.tsx b/apps/as/src/pages/approval-line-management/ui/elapr-tmplt-list.tsx index 7955f8b6b..790c6e408 100644 --- a/apps/as/src/pages/approval-line-management/ui/elapr-tmplt-list.tsx +++ b/apps/as/src/pages/approval-line-management/ui/elapr-tmplt-list.tsx @@ -3,13 +3,15 @@ import { useMemo, useState } from "react"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { useQueryClient } from "@tanstack/react-query"; import { getCoreRowModel, type RowSelectionState } from "@tanstack/react-table"; -import { useDataTable } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import { useDataTable, useMutationWrapper } from "@/shared/lib"; import { type ElaprTmplt, + elaprTmpltService, useCreateElaprTmplt, - useDeleteElaprTmplt, useElaprTmpltPage, useUpdateElaprTmplt, } from "@/entities/elapr"; @@ -26,6 +28,7 @@ interface ElaprTmpltListProps { function ElaprTmpltList({ selectedTmpltId, onSelectTmplt }: ElaprTmpltListProps) { const { confirm } = useConfirm(); + const queryClient = useQueryClient(); const [dialogMode, setDialogMode] = useState<"create" | "edit" | null>(null); const [editingTmplt, setEditingTmplt] = useState(null); const [rowSelection, setRowSelection] = useState({}); @@ -38,7 +41,14 @@ function ElaprTmpltList({ selectedTmpltId, onSelectTmplt }: ElaprTmpltListProps) const createMutation = useCreateElaprTmplt(); const updateMutation = useUpdateElaprTmplt(); - const deleteMutation = useDeleteElaprTmplt(); + const { mutateAsync: deleteTmplt } = useMutationWrapper({ + mutationFn: (elaprTmpltId: string) => elaprTmpltService.remove(elaprTmpltId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.AS_ELAPR_SERVICE.ELAPR_TMPLT], + }); + }, + }); const columns = useMemo( () => @@ -49,13 +59,13 @@ function ElaprTmpltList({ selectedTmpltId, onSelectTmplt }: ElaprTmpltListProps) }, onDelete: async (row) => { if (!(await confirm("선택한 결재서식을 삭제하시겠습니까?"))) return; - await deleteMutation.mutateAsync(row.elaprTmpltId); + await deleteTmplt(row.elaprTmpltId); if (selectedTmpltId === row.elaprTmpltId) { onSelectTmplt(null); } }, }), - [confirm, deleteMutation, onSelectTmplt, selectedTmpltId], + [confirm, deleteTmplt, onSelectTmplt, selectedTmpltId], ); const { tableComponent } = useDataTable({ diff --git a/apps/as/src/shared/lib/index.ts b/apps/as/src/shared/lib/index.ts index d9f0f248c..2373d23ca 100644 --- a/apps/as/src/shared/lib/index.ts +++ b/apps/as/src/shared/lib/index.ts @@ -5,5 +5,6 @@ export { queryClient } from "./query-client"; export { useAlertStore } from "./use-alert-store"; export { useDataTable } from "./use-data-table"; export { useMenuStore } from "./use-menu-store"; +export { useMutationWrapper } from "./use-mutation-wrapper"; export { getDefaultFileKey, useTus } from "./use-tus"; export { useUserStore } from "./use-user-store"; diff --git a/apps/as/src/shared/lib/use-mutation-wrapper.tsx b/apps/as/src/shared/lib/use-mutation-wrapper.tsx new file mode 100644 index 000000000..3ed4806f7 --- /dev/null +++ b/apps/as/src/shared/lib/use-mutation-wrapper.tsx @@ -0,0 +1,29 @@ +import { useMutation, type UseMutationOptions } from "@tanstack/react-query"; +import { AxiosError } from "axios"; + +import { useAlertStore } from "./use-alert-store"; + +export const useMutationWrapper = < + TData = unknown, + TError = unknown, + TVariables = void, + TContext = unknown, +>( + mutationOptions: UseMutationOptions, +) => { + const alert = useAlertStore((state) => state.setMessage); + return useMutation({ + ...mutationOptions, + onError: (error) => { + let errorMessage = ""; + + if (error instanceof AxiosError) { + if (error.response?.data?.message) errorMessage += `${error.response.data.message}\n`; + if (error.response?.data?.errors[0]?.message) + errorMessage += `${error.response.data.errors[0].message}\n`; + } + + alert(`${errorMessage}`); + }, + }); +};