diff --git a/apps/as/app/global-not-found.tsx b/apps/as/app/global-not-found.tsx new file mode 100644 index 000000000..06af4da07 --- /dev/null +++ b/apps/as/app/global-not-found.tsx @@ -0,0 +1,45 @@ +import type { Metadata } from "next"; +import Image from "next/image"; + +import "@/app/styles/globals.css"; + +export const metadata: Metadata = { + title: "행정지원시스템", + description: "페이지가 존재하지 않습니다. 관리자에게 문의하여 주십시오.", +}; + +export default function GlobalNotFound() { + return ( + + +
+
+

+ 행정지원시스템에 오신 것을 + 환영합니다. +

+

+ 오류가 발생하였습니다. +

+
+ +
+ +

+ 페이지가 존재하지 않습니다. +

+

+ 관리자에게 문의하여 주십시오 +

+
+
+ + + ); +} diff --git a/apps/as/next.config.ts b/apps/as/next.config.ts index d3afd4d70..33da97ea4 100644 --- a/apps/as/next.config.ts +++ b/apps/as/next.config.ts @@ -4,6 +4,9 @@ const nextConfig: NextConfig = { output: "standalone", reactStrictMode: true, transpilePackages: ["@common/assets"], + experimental: { + globalNotFound: true, + }, }; export default nextConfig; diff --git a/apps/as/public/images/magnifying-glass-with-warning.png b/apps/as/public/images/magnifying-glass-with-warning.png new file mode 100644 index 000000000..b184da207 Binary files /dev/null and b/apps/as/public/images/magnifying-glass-with-warning.png differ 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/pages/approval-line-management/ui/sortable-apln-row.tsx b/apps/as/src/pages/approval-line-management/ui/sortable-apln-row.tsx index b0a23ee1e..d9f3ddf73 100644 --- a/apps/as/src/pages/approval-line-management/ui/sortable-apln-row.tsx +++ b/apps/as/src/pages/approval-line-management/ui/sortable-apln-row.tsx @@ -82,7 +82,13 @@ function SortableAplnRow({