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({