From c87ad8ea1baaba4f5961fd82586c9f2c41f12d2d Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 14 Jul 2026 15:03:45 +0900 Subject: [PATCH] =?UTF-8?q?[as]=20refactor:=20=EA=B2=B0=EC=9E=AC=EC=84=A0?= =?UTF-8?q?=20=EC=84=A0=ED=83=9D=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/as/src/entities/elapr/index.ts | 9 + .../elapr}/model/reviewer-option.ts | 0 .../elapr/ui}/aprvr-picker-dialog.tsx | 2 +- .../elapr/ui}/elapr-apln-picker-grid.tsx | 14 +- .../elapr/ui}/elapr-tmplt-picker-dialog.tsx | 329 +++++++++--------- .../elapr/ui}/elapr-tmplt-select-table.tsx | 3 +- .../elapr/ui}/sortable-apln-picker-row.tsx | 3 +- .../ui/components/elapr-apln-picker-grid.tsx | 206 ----------- .../ui/components/elapr-approval-dialog.tsx | 5 +- .../components/elapr-tmplt-picker-dialog.tsx | 163 --------- .../components/elapr-tmplt-select-table.tsx | 104 ------ .../ui/components/floating-action-buttons.tsx | 7 +- .../components/sortable-apln-picker-row.tsx | 138 -------- .../pages/instl-sncn/model/reviewer-option.ts | 5 - .../ui/components/aprvr-picker-dialog.tsx | 125 ------- .../ui/components/elapr-approval-dialog.tsx | 5 +- .../ui/components/floating-action-buttons.tsx | 7 +- 17 files changed, 196 insertions(+), 929 deletions(-) rename apps/as/src/{pages/fnrs-cnsltn => entities/elapr}/model/reviewer-option.ts (100%) rename apps/as/src/{pages/fnrs-cnsltn/ui/components => entities/elapr/ui}/aprvr-picker-dialog.tsx (95%) rename apps/as/src/{pages/instl-sncn/ui/components => entities/elapr/ui}/elapr-apln-picker-grid.tsx (91%) rename apps/as/src/{pages/instl-sncn/ui/components => entities/elapr/ui}/elapr-tmplt-picker-dialog.tsx (89%) rename apps/as/src/{pages/instl-sncn/ui/components => entities/elapr/ui}/elapr-tmplt-select-table.tsx (94%) rename apps/as/src/{pages/instl-sncn/ui/components => entities/elapr/ui}/sortable-apln-picker-row.tsx (95%) delete mode 100644 apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-apln-picker-grid.tsx delete mode 100644 apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-tmplt-picker-dialog.tsx delete mode 100644 apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-tmplt-select-table.tsx delete mode 100644 apps/as/src/pages/fnrs-cnsltn/ui/components/sortable-apln-picker-row.tsx delete mode 100644 apps/as/src/pages/instl-sncn/model/reviewer-option.ts delete mode 100644 apps/as/src/pages/instl-sncn/ui/components/aprvr-picker-dialog.tsx diff --git a/apps/as/src/entities/elapr/index.ts b/apps/as/src/entities/elapr/index.ts index 577742c77..b72bcdb1f 100644 --- a/apps/as/src/entities/elapr/index.ts +++ b/apps/as/src/entities/elapr/index.ts @@ -102,3 +102,12 @@ export type { ElaprTmpltService, ElaprTmpltUpdateRequest, } from "./model/elapr-tmplt-interface"; +export type { ReviewerOption } from "./model/reviewer-option"; +export { AprvrPickerDialog } from "./ui/aprvr-picker-dialog"; +export { ElaprAplnPickerGrid } from "./ui/elapr-apln-picker-grid"; +export { + ElaprTmpltPickerDialog, + type ElaprTmpltPickerConfirmPayload, +} from "./ui/elapr-tmplt-picker-dialog"; +export { ElaprTmpltSelectTable } from "./ui/elapr-tmplt-select-table"; +export { SortableAplnPickerRow } from "./ui/sortable-apln-picker-row"; diff --git a/apps/as/src/pages/fnrs-cnsltn/model/reviewer-option.ts b/apps/as/src/entities/elapr/model/reviewer-option.ts similarity index 100% rename from apps/as/src/pages/fnrs-cnsltn/model/reviewer-option.ts rename to apps/as/src/entities/elapr/model/reviewer-option.ts diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/aprvr-picker-dialog.tsx b/apps/as/src/entities/elapr/ui/aprvr-picker-dialog.tsx similarity index 95% rename from apps/as/src/pages/fnrs-cnsltn/ui/components/aprvr-picker-dialog.tsx rename to apps/as/src/entities/elapr/ui/aprvr-picker-dialog.tsx index dd241a514..272f1f3cf 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/aprvr-picker-dialog.tsx +++ b/apps/as/src/entities/elapr/ui/aprvr-picker-dialog.tsx @@ -17,7 +17,7 @@ import { TableRow, } from "@common/components"; -import { type ReviewerOption } from "../../model/reviewer-option"; +import { type ReviewerOption } from "../model/reviewer-option"; const PAGE_SIZE = 500; diff --git a/apps/as/src/pages/instl-sncn/ui/components/elapr-apln-picker-grid.tsx b/apps/as/src/entities/elapr/ui/elapr-apln-picker-grid.tsx similarity index 91% rename from apps/as/src/pages/instl-sncn/ui/components/elapr-apln-picker-grid.tsx rename to apps/as/src/entities/elapr/ui/elapr-apln-picker-grid.tsx index 6268058f0..92c3d58c5 100644 --- a/apps/as/src/pages/instl-sncn/ui/components/elapr-apln-picker-grid.tsx +++ b/apps/as/src/entities/elapr/ui/elapr-apln-picker-grid.tsx @@ -6,13 +6,6 @@ import { closestCenter, DndContext, type DragEndEvent } from "@dnd-kit/core"; import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable"; import { useAlertStore } from "@/shared/lib"; -import { - type ElaprAplnRow, - useAplnRows, - useDndSensors, - useElaprAplnPage, - validateAplnRow, -} from "@/entities/elapr"; import { Button, Skeleton, @@ -23,7 +16,12 @@ import { TableRow, } from "@common/components"; -import type { ReviewerOption } from "../../model/reviewer-option"; +import { useElaprAplnPage } from "../api/queries"; +import { validateAplnRow } from "../lib/apln-validators"; +import { useAplnRows } from "../lib/use-apln-rows"; +import { useDndSensors } from "../lib/use-dnd-sensors"; +import type { ElaprAplnRow } from "../model/elapr-apln-row-types"; +import type { ReviewerOption } from "../model/reviewer-option"; import { AprvrPickerDialog } from "./aprvr-picker-dialog"; import { SortableAplnPickerRow } from "./sortable-apln-picker-row"; diff --git a/apps/as/src/pages/instl-sncn/ui/components/elapr-tmplt-picker-dialog.tsx b/apps/as/src/entities/elapr/ui/elapr-tmplt-picker-dialog.tsx similarity index 89% rename from apps/as/src/pages/instl-sncn/ui/components/elapr-tmplt-picker-dialog.tsx rename to apps/as/src/entities/elapr/ui/elapr-tmplt-picker-dialog.tsx index 945a2cfc0..aa70932e9 100644 --- a/apps/as/src/pages/instl-sncn/ui/components/elapr-tmplt-picker-dialog.tsx +++ b/apps/as/src/entities/elapr/ui/elapr-tmplt-picker-dialog.tsx @@ -1,163 +1,166 @@ -"use client"; - -import { useCallback, useState } from "react"; - -import { useAlertStore } from "@/shared/lib"; -import { - type ElaprAplnRow, - type ElaprTmplt, - getVisibleRows, - useElaprTmpltPage, - validateAllRowsForSave, - validateAtrzOrder, - validateUniqueSeqNo, -} from "@/entities/elapr"; -import { - Button, - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@common/components"; - -import { ElaprAplnPickerGrid } from "./elapr-apln-picker-grid"; -import { ElaprTmpltSelectTable } from "./elapr-tmplt-select-table"; - -const PAGE_SIZE = 100; - -interface ElaprTmpltPickerConfirmPayload { - tmplt: ElaprTmplt; - aplnRows: ElaprAplnRow[]; -} - -interface ElaprTmpltPickerDialogProps { - open: boolean; - onClose: () => void; - onSelect?: (tmplt: ElaprTmplt) => void; - onConfirm?: (payload: ElaprTmpltPickerConfirmPayload) => void; -} - -function ElaprTmpltPickerDialog({ - open, - onClose, - onSelect, - onConfirm, -}: ElaprTmpltPickerDialogProps) { - const alert = useAlertStore((state) => state.setMessage); - const [selectedTmplt, setSelectedTmplt] = useState(null); - const [aplnRows, setAplnRows] = useState([]); - const [isEditingLocked, setIsEditingLocked] = useState(false); - - const tmpltQuery = useElaprTmpltPage({ - page: 0, - size: PAGE_SIZE, - sort: "regDt,desc", - docSeComCd: "INSTL", - }); - - const handleRowsChange = useCallback((rows: ElaprAplnRow[]) => { - setAplnRows(rows); - }, []); - - const handleEditingLockedChange = useCallback((locked: boolean) => { - setIsEditingLocked(locked); - }, []); - - function handleSelectTmplt(tmplt: ElaprTmplt | null) { - setSelectedTmplt(tmplt); - setAplnRows([]); - setIsEditingLocked(false); - } - - function handleOpenChange(isOpen: boolean) { - if (!isOpen) { - setSelectedTmplt(null); - setAplnRows([]); - setIsEditingLocked(false); - onClose(); - } - } - - function handleConfirm() { - if (!selectedTmplt) { - alert("결재서식을 선택해 주세요."); - return; - } - - const rowsError = validateAllRowsForSave(aplnRows, isEditingLocked); - if (rowsError) { - alert(rowsError); - return; - } - - const orderError = validateAtrzOrder(aplnRows); - if (orderError) { - alert(orderError); - return; - } - - const seqError = validateUniqueSeqNo(aplnRows); - if (seqError) { - alert(seqError); - return; - } - - const visibleRows = getVisibleRows(aplnRows); - - const payload: ElaprTmpltPickerConfirmPayload = { - tmplt: selectedTmplt, - aplnRows: visibleRows, - }; - - if (onConfirm) { - onConfirm(payload); - } else { - onSelect?.(selectedTmplt); - } - - setSelectedTmplt(null); - setAplnRows([]); - setIsEditingLocked(false); - onClose(); - } - - return ( - - - - 결재선 선택 - - -
- - -
- - - - - -
-
- ); -} - -export { ElaprTmpltPickerDialog }; -export type { ElaprTmpltPickerConfirmPayload }; +"use client"; + +import { useCallback, useState } from "react"; + +import { useAlertStore } from "@/shared/lib"; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@common/components"; + +import { useElaprTmpltPage } from "../api/queries"; +import { + getVisibleRows, + validateAllRowsForSave, + validateAtrzOrder, + validateUniqueSeqNo, +} from "../lib/apln-validators"; +import type { ElaprAplnRow } from "../model/elapr-apln-row-types"; +import type { ElaprTmplt } from "../model/elapr-tmplt-interface"; + +import { ElaprAplnPickerGrid } from "./elapr-apln-picker-grid"; +import { ElaprTmpltSelectTable } from "./elapr-tmplt-select-table"; + +const PAGE_SIZE = 100; + +interface ElaprTmpltPickerConfirmPayload { + tmplt: ElaprTmplt; + aplnRows: ElaprAplnRow[]; +} + +interface ElaprTmpltPickerDialogProps { + open: boolean; + onClose: () => void; + docSeComCd: string; + onSelect?: (tmplt: ElaprTmplt) => void; + onConfirm?: (payload: ElaprTmpltPickerConfirmPayload) => void; +} + +function ElaprTmpltPickerDialog({ + open, + onClose, + docSeComCd, + onSelect, + onConfirm, +}: ElaprTmpltPickerDialogProps) { + const alert = useAlertStore((state) => state.setMessage); + const [selectedTmplt, setSelectedTmplt] = useState(null); + const [aplnRows, setAplnRows] = useState([]); + const [isEditingLocked, setIsEditingLocked] = useState(false); + + const tmpltQuery = useElaprTmpltPage({ + page: 0, + size: PAGE_SIZE, + sort: "regDt,desc", + docSeComCd, + }); + + const handleRowsChange = useCallback((rows: ElaprAplnRow[]) => { + setAplnRows(rows); + }, []); + + const handleEditingLockedChange = useCallback((locked: boolean) => { + setIsEditingLocked(locked); + }, []); + + function handleSelectTmplt(tmplt: ElaprTmplt | null) { + setSelectedTmplt(tmplt); + setAplnRows([]); + setIsEditingLocked(false); + } + + function handleOpenChange(isOpen: boolean) { + if (!isOpen) { + setSelectedTmplt(null); + setAplnRows([]); + setIsEditingLocked(false); + onClose(); + } + } + + function handleConfirm() { + if (!selectedTmplt) { + alert("결재서식을 선택해 주세요."); + return; + } + + const rowsError = validateAllRowsForSave(aplnRows, isEditingLocked); + if (rowsError) { + alert(rowsError); + return; + } + + const orderError = validateAtrzOrder(aplnRows); + if (orderError) { + alert(orderError); + return; + } + + const seqError = validateUniqueSeqNo(aplnRows); + if (seqError) { + alert(seqError); + return; + } + + const visibleRows = getVisibleRows(aplnRows); + + const payload: ElaprTmpltPickerConfirmPayload = { + tmplt: selectedTmplt, + aplnRows: visibleRows, + }; + + if (onConfirm) { + onConfirm(payload); + } else { + onSelect?.(selectedTmplt); + } + + setSelectedTmplt(null); + setAplnRows([]); + setIsEditingLocked(false); + onClose(); + } + + return ( + + + + 결재선 선택 + + +
+ + +
+ + + + + +
+
+ ); +} + +export { ElaprTmpltPickerDialog }; +export type { ElaprTmpltPickerConfirmPayload }; diff --git a/apps/as/src/pages/instl-sncn/ui/components/elapr-tmplt-select-table.tsx b/apps/as/src/entities/elapr/ui/elapr-tmplt-select-table.tsx similarity index 94% rename from apps/as/src/pages/instl-sncn/ui/components/elapr-tmplt-select-table.tsx rename to apps/as/src/entities/elapr/ui/elapr-tmplt-select-table.tsx index cd96ebe09..b93ead98d 100644 --- a/apps/as/src/pages/instl-sncn/ui/components/elapr-tmplt-select-table.tsx +++ b/apps/as/src/entities/elapr/ui/elapr-tmplt-select-table.tsx @@ -7,9 +7,10 @@ import { createColumnHelper, getCoreRowModel, type RowSelectionState } from "@ta import { useDataTable } from "@/shared/lib"; import type { PageResponse } from "@/shared/model"; -import type { ElaprTmplt } from "@/entities/elapr"; import { Skeleton } from "@common/components"; +import type { ElaprTmplt } from "../model/elapr-tmplt-interface"; + const columnHelper = createColumnHelper(); interface ElaprTmpltSelectTableProps { diff --git a/apps/as/src/pages/instl-sncn/ui/components/sortable-apln-picker-row.tsx b/apps/as/src/entities/elapr/ui/sortable-apln-picker-row.tsx similarity index 95% rename from apps/as/src/pages/instl-sncn/ui/components/sortable-apln-picker-row.tsx rename to apps/as/src/entities/elapr/ui/sortable-apln-picker-row.tsx index 07a9567cc..90d8380b7 100644 --- a/apps/as/src/pages/instl-sncn/ui/components/sortable-apln-picker-row.tsx +++ b/apps/as/src/entities/elapr/ui/sortable-apln-picker-row.tsx @@ -5,7 +5,6 @@ import { CSS } from "@dnd-kit/utilities"; import { MoveVertical } from "lucide-react"; import { findCodeLabel, useCodeList } from "@/entities/code"; -import type { ElaprAplnRow } from "@/entities/elapr"; import { Button, Select, @@ -17,6 +16,8 @@ import { TableRow, } from "@common/components"; +import type { ElaprAplnRow } from "../model/elapr-apln-row-types"; + interface SortableAplnPickerRowProps { row: ElaprAplnRow; displaySeqNo: number; diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-apln-picker-grid.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-apln-picker-grid.tsx deleted file mode 100644 index a22a2e880..000000000 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-apln-picker-grid.tsx +++ /dev/null @@ -1,206 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -import { closestCenter, DndContext, type DragEndEvent } from "@dnd-kit/core"; -import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable"; - -import { useAlertStore } from "@/shared/lib"; -import { - type ElaprAplnRow, - useAplnRows, - useDndSensors, - useElaprAplnPage, - validateAplnRow, -} from "@/entities/elapr"; -import { - Button, - Skeleton, - Table, - TableBody, - TableHead, - TableHeader, - TableRow, -} from "@common/components"; - -import type { ReviewerOption } from "../../model/reviewer-option"; - -import { AprvrPickerDialog } from "./aprvr-picker-dialog"; -import { SortableAplnPickerRow } from "./sortable-apln-picker-row"; - -interface ElaprAplnPickerGridProps { - elaprTmpltId: string | null; - onRowsChange?: (rows: ElaprAplnRow[]) => void; - onEditingLockedChange?: (locked: boolean) => void; -} - -function ElaprAplnPickerGrid({ - elaprTmpltId, - onRowsChange, - onEditingLockedChange, -}: ElaprAplnPickerGridProps) { - const [pickerOpen, setPickerOpen] = useState(false); - const [pickingRowKey, setPickingRowKey] = useState(null); - - const alert = useAlertStore((state) => state.setMessage); - - const aplnQuery = useElaprAplnPage({ - elaprTmpltId: elaprTmpltId ?? undefined, - page: 0, - size: 200, - sort: "atrzSeqNo,asc", - }); - - const { - rows, - visibleRows, - editingRowKey, - isEditingLocked, - addRow, - startEdit, - updateRow, - cancelEdit, - commitEdit, - markDeleted, - reorderRows, - } = useAplnRows({ - elaprTmpltId, - serverRows: aplnQuery.data?.content, - }); - - useEffect(() => { - onRowsChange?.(rows); - }, [rows, onRowsChange]); - - useEffect(() => { - onEditingLockedChange?.(isEditingLocked); - }, [isEditingLocked, onEditingLockedChange]); - - const sensors = useDndSensors(); - const isDisabled = !elaprTmpltId; - const pickingRow = pickingRowKey ? rows.find((r) => r.rowKey === pickingRowKey) : undefined; - - function handleDragEnd(event: DragEndEvent) { - const { active, over } = event; - if (!over || active.id === over.id) return; - - reorderRows(String(active.id), String(over.id)); - } - - function handleRowChange(row: ElaprAplnRow, patch: Partial) { - updateRow(row.rowKey, patch); - } - - function handleRowSave(row: ElaprAplnRow) { - const fieldError = validateAplnRow(row); - if (fieldError) { - alert(fieldError); - return; - } - - commitEdit(row.rowKey); - } - - function handleRowDelete(row: ElaprAplnRow) { - markDeleted(row.rowKey); - } - - function handleReviewerSelect(reviewer: ReviewerOption) { - if (!pickingRowKey) return; - updateRow(pickingRowKey, { - aprvrUuid: reviewer.uuid, - aprvrNm: reviewer.name, - }); - setPickingRowKey(null); - } - - return ( -
-
-

결재선 관리

-
- -
-
- - {aplnQuery.isPending && elaprTmpltId ? ( -
- - -
- ) : !elaprTmpltId ? ( -

결재서식을 선택하세요.

- ) : visibleRows.length === 0 ? ( -

- 등록된 결재선이 없습니다. [결재선 추가]로 결재자를 등록하세요. -

- ) : ( - - - - - - - - - - - - - - 결재선아이디 - 결재순서 - 결재구분 - 결재자 - 관리 - - - - r.rowKey)} - strategy={verticalListSortingStrategy} - > - {visibleRows.map((row, index) => ( - startEdit(row.rowKey)} - onSave={() => handleRowSave(row)} - onCancel={cancelEdit} - onDelete={() => handleRowDelete(row)} - onChange={(patch) => handleRowChange(row, patch)} - onPickAprvr={() => { - setPickingRowKey(row.rowKey); - setPickerOpen(true); - }} - /> - ))} - - -
-
- )} - - -
- ); -} - -export { ElaprAplnPickerGrid }; diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-approval-dialog.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-approval-dialog.tsx index a6737ec3e..53b6337c8 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-approval-dialog.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-approval-dialog.tsx @@ -10,11 +10,13 @@ import { AxiosError } from "axios"; import { ENDPOINT } from "@/shared/config"; import { useAlertStore } from "@/shared/lib"; import { + AprvrPickerDialog, buildPrgrsSttsSavePayload, elaprPrgrsSttsService, findCurrentUserPendingApprovalRequest, hasUnsavedPrgrsSttsChanges, isPrgrsSttsRowEditable, + type ReviewerOption, useDndSensors, useElaprDocPrgrsByDocNo, usePrgrsSttsRows, @@ -41,9 +43,6 @@ import { Textarea, } from "@common/components"; -import type { ReviewerOption } from "../../model/reviewer-option"; - -import { AprvrPickerDialog } from "./aprvr-picker-dialog"; import { SortablePrgrsSttsRow } from "./sortable-prgrs-stts-row"; interface ElaprApprovalDialogProps { diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-tmplt-picker-dialog.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-tmplt-picker-dialog.tsx deleted file mode 100644 index 3f59f602b..000000000 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-tmplt-picker-dialog.tsx +++ /dev/null @@ -1,163 +0,0 @@ -"use client"; - -import { useCallback, useState } from "react"; - -import { useAlertStore } from "@/shared/lib"; -import { - type ElaprAplnRow, - type ElaprTmplt, - getVisibleRows, - useElaprTmpltPage, - validateAllRowsForSave, - validateAtrzOrder, - validateUniqueSeqNo, -} from "@/entities/elapr"; -import { - Button, - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@common/components"; - -import { ElaprAplnPickerGrid } from "./elapr-apln-picker-grid"; -import { ElaprTmpltSelectTable } from "./elapr-tmplt-select-table"; - -const PAGE_SIZE = 100; - -interface ElaprTmpltPickerConfirmPayload { - tmplt: ElaprTmplt; - aplnRows: ElaprAplnRow[]; -} - -interface ElaprTmpltPickerDialogProps { - open: boolean; - onClose: () => void; - onSelect?: (tmplt: ElaprTmplt) => void; - onConfirm?: (payload: ElaprTmpltPickerConfirmPayload) => void; -} - -function ElaprTmpltPickerDialog({ - open, - onClose, - onSelect, - onConfirm, -}: ElaprTmpltPickerDialogProps) { - const alert = useAlertStore((state) => state.setMessage); - const [selectedTmplt, setSelectedTmplt] = useState(null); - const [aplnRows, setAplnRows] = useState([]); - const [isEditingLocked, setIsEditingLocked] = useState(false); - - const tmpltQuery = useElaprTmpltPage({ - page: 0, - size: PAGE_SIZE, - sort: "regDt,desc", - docSeComCd: "FNRS", - }); - - const handleRowsChange = useCallback((rows: ElaprAplnRow[]) => { - setAplnRows(rows); - }, []); - - const handleEditingLockedChange = useCallback((locked: boolean) => { - setIsEditingLocked(locked); - }, []); - - function handleSelectTmplt(tmplt: ElaprTmplt | null) { - setSelectedTmplt(tmplt); - setAplnRows([]); - setIsEditingLocked(false); - } - - function handleOpenChange(isOpen: boolean) { - if (!isOpen) { - setSelectedTmplt(null); - setAplnRows([]); - setIsEditingLocked(false); - onClose(); - } - } - - function handleConfirm() { - if (!selectedTmplt) { - alert("결재서식을 선택해 주세요."); - return; - } - - const rowsError = validateAllRowsForSave(aplnRows, isEditingLocked); - if (rowsError) { - alert(rowsError); - return; - } - - const orderError = validateAtrzOrder(aplnRows); - if (orderError) { - alert(orderError); - return; - } - - const seqError = validateUniqueSeqNo(aplnRows); - if (seqError) { - alert(seqError); - return; - } - - const visibleRows = getVisibleRows(aplnRows); - - const payload: ElaprTmpltPickerConfirmPayload = { - tmplt: selectedTmplt, - aplnRows: visibleRows, - }; - - if (onConfirm) { - onConfirm(payload); - } else { - onSelect?.(selectedTmplt); - } - - setSelectedTmplt(null); - setAplnRows([]); - setIsEditingLocked(false); - onClose(); - } - - return ( - - - - 결재선 선택 - - -
- - -
- - - - - -
-
- ); -} - -export { ElaprTmpltPickerDialog }; -export type { ElaprTmpltPickerConfirmPayload }; diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-tmplt-select-table.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-tmplt-select-table.tsx deleted file mode 100644 index 50bdb33b9..000000000 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/elapr-tmplt-select-table.tsx +++ /dev/null @@ -1,104 +0,0 @@ -"use client"; - -import { useEffect, useMemo, useState } from "react"; - -import type { UseQueryResult } from "@tanstack/react-query"; -import { createColumnHelper, getCoreRowModel, type RowSelectionState } from "@tanstack/react-table"; - -import { useDataTable } from "@/shared/lib"; -import type { PageResponse } from "@/shared/model"; -import type { ElaprTmplt } from "@/entities/elapr"; -import { Skeleton } from "@common/components"; - -const columnHelper = createColumnHelper(); - -interface ElaprTmpltSelectTableProps { - query: UseQueryResult>; - selectedTmpltId: string | null; - onSelectTmplt: (tmplt: ElaprTmplt | null) => void; -} - -function ElaprTmpltSelectTable({ - query, - selectedTmpltId, - onSelectTmplt, -}: ElaprTmpltSelectTableProps) { - const [rowSelection, setRowSelection] = useState({}); - - useEffect(() => { - if (selectedTmpltId) { - setRowSelection({ [selectedTmpltId]: true }); - } else { - setRowSelection({}); - } - }, [selectedTmpltId]); - - const columns = useMemo( - () => [ - columnHelper.accessor("elaprTmpltId", { - header: "전자결재서식아이디", - meta: { align: "left" }, - }), - columnHelper.accessor("elaprTmpltNm", { - header: "결재서식명", - meta: { align: "left" }, - }), - columnHelper.accessor("docSeComNm", { - header: "문서구분", - cell: ({ getValue, row }) => getValue() ?? row.original.docSeComCd ?? "—", - }), - ], - [], - ); - - const { tableComponent } = useDataTable({ - query, - tableOptions: { - columns, - state: { rowSelection }, - enableRowSelection: true, - enableMultiRowSelection: false, - getRowId: (row) => row.elaprTmpltId, - onRowSelectionChange: (updater) => { - const next = typeof updater === "function" ? updater(rowSelection) : updater; - setRowSelection(next); - const selectedId = Object.keys(next)[0]; - if (!selectedId) { - onSelectTmplt(null); - return; - } - const selected = query.data?.content.find((t) => t.elaprTmpltId === selectedId); - onSelectTmplt(selected ?? null); - }, - getCoreRowModel: getCoreRowModel(), - }, - dataTableOptions: { - onRowClick: (row) => { - setRowSelection({ [row.elaprTmpltId]: true }); - onSelectTmplt(row); - }, - }, - }); - - return ( -
-
-

전자결재서식

-
- {query.isPending && ( -
- - -
- )} - {query.isError && ( -

- 결재서식 목록을 불러오지 못했습니다. -

- )} - {!query.isPending && !query.isError && tableComponent} -
- ); -} - -export { ElaprTmpltSelectTable }; diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx index bf3d02823..6dc25c16c 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/components/floating-action-buttons.tsx @@ -13,6 +13,8 @@ import { buildElaprPrgrsSttsListFromAplnRows, type ElaprDmndSaveRequestDto, elaprDmndService, + type ElaprTmpltPickerConfirmPayload, + ElaprTmpltPickerDialog, hasCurrentUserPendingApprovalRequest, useElaprDocPrgrsByDocNo, } from "@/entities/elapr"; @@ -44,10 +46,6 @@ import { type ChecklistFormState } from "../../model/view-types"; import type { TechReviewOpinionSectionHandle } from "../sections/tech-review-opinion-section"; import { ElaprApprovalDialog } from "./elapr-approval-dialog"; -import { - type ElaprTmpltPickerConfirmPayload, - ElaprTmpltPickerDialog, -} from "./elapr-tmplt-picker-dialog"; interface FloatingActionButtonsProps { data: FnrsCnsltnMngDetResponseDto; @@ -513,6 +511,7 @@ function FloatingActionButtons({ setActiveDialog(null)} + docSeComCd={docSeComCd} onConfirm={handleElaprTmpltConfirm} /> diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/components/sortable-apln-picker-row.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/components/sortable-apln-picker-row.tsx deleted file mode 100644 index 46af147ca..000000000 --- a/apps/as/src/pages/fnrs-cnsltn/ui/components/sortable-apln-picker-row.tsx +++ /dev/null @@ -1,138 +0,0 @@ -"use client"; - -import { useSortable } from "@dnd-kit/sortable"; -import { CSS } from "@dnd-kit/utilities"; -import { MoveVertical } from "lucide-react"; - -import { CodeSelect, findCodeLabel, useCodeList } from "@/entities/code"; -import type { ElaprAplnRow } from "@/entities/elapr"; -import { Button, TableCell, TableRow } from "@common/components"; - -interface SortableAplnPickerRowProps { - row: ElaprAplnRow; - displaySeqNo: number; - isEditing: boolean; - isLocked: boolean; - dragDisabled: boolean; - onStartEdit: () => void; - onSave: () => void; - onCancel: () => void; - onDelete: () => void; - onChange: (patch: Partial) => void; - onPickAprvr: () => void; -} - -function SortableAplnPickerRow({ - row, - displaySeqNo, - isEditing, - isLocked, - dragDisabled, - onStartEdit, - onSave, - onCancel, - onDelete, - onChange, - onPickAprvr, -}: SortableAplnPickerRowProps) { - const { data: atrzSeCodes } = useCodeList("ATRZSE"); - - const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ - id: row.rowKey, - disabled: dragDisabled, - }); - - const style = { - transform: CSS.Transform.toString(transform), - transition, - opacity: isDragging ? 0.6 : 1, - }; - - const rowDisabled = isLocked && !isEditing; - const atrzSeLabel = row.atrzSeComNm || findCodeLabel(atrzSeCodes, row.atrzSeComCd); - - return ( - - - - - {row.isNew ? "-" : (row.aplnId ?? "-")} - {displaySeqNo} - - {isEditing ? ( - - onChange({ - atrzSeComCd: v, - atrzSeComNm: findCodeLabel(atrzSeCodes, v), - }) - } - placeholder="결재구분" - /> - ) : ( - atrzSeLabel || "-" - )} - - -
- {row.aprvrNm || row.aprvrUuid || "-"} - {isEditing && ( - - )} -
-
- -
- {isEditing ? ( - <> - - - - ) : ( - <> - - - - )} -
-
-
- ); -} - -export { SortableAplnPickerRow }; diff --git a/apps/as/src/pages/instl-sncn/model/reviewer-option.ts b/apps/as/src/pages/instl-sncn/model/reviewer-option.ts deleted file mode 100644 index 2c9a72ef8..000000000 --- a/apps/as/src/pages/instl-sncn/model/reviewer-option.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ReviewerOption { - uuid: string; - name: string; - deptNm: string; -} diff --git a/apps/as/src/pages/instl-sncn/ui/components/aprvr-picker-dialog.tsx b/apps/as/src/pages/instl-sncn/ui/components/aprvr-picker-dialog.tsx deleted file mode 100644 index dd241a514..000000000 --- a/apps/as/src/pages/instl-sncn/ui/components/aprvr-picker-dialog.tsx +++ /dev/null @@ -1,125 +0,0 @@ -"use client"; - -import { RvwMnpwMngResponseDto, useRvwMnpwMngPage } from "@/entities/rvw-mnpw"; -import { - Button, - Dialog, - DialogContent, - DialogFooter, - DialogHeader, - DialogTitle, - Skeleton, - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@common/components"; - -import { type ReviewerOption } from "../../model/reviewer-option"; - -const PAGE_SIZE = 500; - -interface AprvrPickerDialogProps { - open: boolean; - onOpenChange: (open: boolean) => void; - atrzSeComCd: string; - onSelect: (reviewer: ReviewerOption) => void; -} - -function AprvrPickerDialog({ open, onOpenChange, atrzSeComCd, onSelect }: AprvrPickerDialogProps) { - const { data, isPending, isError } = useRvwMnpwMngPage( - { - atrzSeComCd, - page: 0, - size: PAGE_SIZE, - sort: "userUuidNm,asc", - }, - { enabled: open }, - ); - - const items = data?.content ?? []; - - function fromRvwMnpwMng(item: RvwMnpwMngResponseDto): ReviewerOption { - return { - uuid: item.userUuid, - name: item.userUuidNm ?? "", - deptNm: item.ognzNm ?? "", - }; - } - - function handleSelect(item: (typeof items)[number]) { - onSelect(fromRvwMnpwMng(item)); - onOpenChange(false); - } - - return ( - - - - 검토인력 선택 - - -
- {isPending ? ( -
- - -
- ) : isError ? ( -

- 검토인력 목록을 불러오지 못했습니다. -

- ) : items.length === 0 ? ( -

- 배치된 검토인력이 없습니다. -

- ) : ( - - - - - - - - - - - 성명 - 부서 - 직위 - 직급 - 선택 - - - - {items.map((item) => ( - - {item.userUuidNm || item.userUuid} - {item.ognzNm || "-"} - {item.jbpsSeNm || "-"} - {item.jbgdSeNm || "-"} - - - - - ))} - -
- )} -
- - - - -
-
- ); -} - -export { AprvrPickerDialog }; diff --git a/apps/as/src/pages/instl-sncn/ui/components/elapr-approval-dialog.tsx b/apps/as/src/pages/instl-sncn/ui/components/elapr-approval-dialog.tsx index a6737ec3e..53b6337c8 100644 --- a/apps/as/src/pages/instl-sncn/ui/components/elapr-approval-dialog.tsx +++ b/apps/as/src/pages/instl-sncn/ui/components/elapr-approval-dialog.tsx @@ -10,11 +10,13 @@ import { AxiosError } from "axios"; import { ENDPOINT } from "@/shared/config"; import { useAlertStore } from "@/shared/lib"; import { + AprvrPickerDialog, buildPrgrsSttsSavePayload, elaprPrgrsSttsService, findCurrentUserPendingApprovalRequest, hasUnsavedPrgrsSttsChanges, isPrgrsSttsRowEditable, + type ReviewerOption, useDndSensors, useElaprDocPrgrsByDocNo, usePrgrsSttsRows, @@ -41,9 +43,6 @@ import { Textarea, } from "@common/components"; -import type { ReviewerOption } from "../../model/reviewer-option"; - -import { AprvrPickerDialog } from "./aprvr-picker-dialog"; import { SortablePrgrsSttsRow } from "./sortable-prgrs-stts-row"; interface ElaprApprovalDialogProps { diff --git a/apps/as/src/pages/instl-sncn/ui/components/floating-action-buttons.tsx b/apps/as/src/pages/instl-sncn/ui/components/floating-action-buttons.tsx index ba1f79441..90bc8018e 100644 --- a/apps/as/src/pages/instl-sncn/ui/components/floating-action-buttons.tsx +++ b/apps/as/src/pages/instl-sncn/ui/components/floating-action-buttons.tsx @@ -13,6 +13,8 @@ import { buildElaprPrgrsSttsListFromAplnRows, type ElaprDmndSaveRequestDto, elaprDmndService, + type ElaprTmpltPickerConfirmPayload, + ElaprTmpltPickerDialog, hasCurrentUserPendingApprovalRequest, useElaprDocPrgrsByDocNo, } from "@/entities/elapr"; @@ -39,10 +41,6 @@ import { type ChecklistFormState } from "../../model/view-types"; import type { TechReviewOpinionSectionHandle } from "../sections/tech-review-opinion-section"; import { ElaprApprovalDialog } from "./elapr-approval-dialog"; -import { - type ElaprTmpltPickerConfirmPayload, - ElaprTmpltPickerDialog, -} from "./elapr-tmplt-picker-dialog"; interface FloatingActionButtonsProps { data: InstlSncnMngDetResponse; @@ -516,6 +514,7 @@ function FloatingActionButtons({ setActiveDialog(null)} + docSeComCd={docSeComCd} onConfirm={handleElaprTmpltConfirm} />