diff --git a/apps/as/src/entities/elapr/lib/approval-utils.ts b/apps/as/src/entities/elapr/lib/approval-utils.ts index 00d7d7b2a..831c06949 100644 --- a/apps/as/src/entities/elapr/lib/approval-utils.ts +++ b/apps/as/src/entities/elapr/lib/approval-utils.ts @@ -6,11 +6,26 @@ export interface ApproverStatusConfig { } export const APPROVER_STATUS_MAP: Record = { - ATRZ_PRGRS_001: { label: "대기", badgeClass: "bg-gray-100 text-gray-700" }, - ATRZ_PRGRS_002: { label: "요청", badgeClass: "bg-yellow-100 text-yellow-700" }, - ATRZ_PRGRS_003: { label: "승인", badgeClass: "bg-green-100 text-green-700" }, - ATRZ_PRGRS_004: { label: "반려", badgeClass: "bg-red-100 text-red-700" }, - ATRZ_PRGRS_999: { label: "결재안함", badgeClass: "bg-blue-100 text-blue-700" }, + ATRZ_PRGRS_001: { + label: "대기", + badgeClass: "bg-gray-100 text-gray-700", + }, + ATRZ_PRGRS_002: { + label: "결재요청", + badgeClass: "bg-yellow-100 text-yellow-700", + }, + ATRZ_PRGRS_003: { + label: "결재승인", + badgeClass: "bg-green-100 text-green-700", + }, + ATRZ_PRGRS_004: { + label: "반려", + badgeClass: "bg-red-100 text-red-700", + }, + ATRZ_PRGRS_999: { + label: "결재안함", + badgeClass: "bg-blue-100 text-blue-700", + }, }; export function sortByAtrzSeqNo( @@ -19,10 +34,19 @@ export function sortByAtrzSeqNo( return list.slice().sort((a, b) => Number(a.atrzSeqNo ?? 0) - Number(b.atrzSeqNo ?? 0)); } -export function getApproverStatus(status: string | undefined): ApproverStatusConfig { +export function getApproverStatus( + atrzPrgrsSttsComCd?: string, + atrzSeComCd?: string, +): ApproverStatusConfig { + if (atrzPrgrsSttsComCd === "ATRZ_PRGRS_003" && atrzSeComCd === "ATRZ01") + return { + label: "기술검토 접수", + badgeClass: "bg-green-100 text-green-700", + }; + return ( - APPROVER_STATUS_MAP[status ?? ""] ?? { - label: status || "-", + APPROVER_STATUS_MAP[atrzPrgrsSttsComCd ?? ""] ?? { + label: "-", badgeClass: "bg-gray-100 text-gray-700", } ); diff --git a/apps/as/src/entities/elapr/ui/elapr-approval-dialog.tsx b/apps/as/src/entities/elapr/ui/elapr-approval-dialog.tsx index c4cb3956d..ad9abfa3e 100644 --- a/apps/as/src/entities/elapr/ui/elapr-approval-dialog.tsx +++ b/apps/as/src/entities/elapr/ui/elapr-approval-dialog.tsx @@ -314,8 +314,8 @@ function ElaprApprovalDialog({ - - + + diff --git a/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-status.tsx b/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-status.tsx index 731e39a07..4be4cf0c3 100644 --- a/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-status.tsx +++ b/apps/as/src/entities/elapr/ui/elapr-doc-prgrs-status.tsx @@ -4,7 +4,9 @@ import { Fragment } from "react"; import { ChevronRight } from "lucide-react"; +import { dayjs, useUserStore } from "@/shared/lib"; import type { ElaprDocPrgrsClientResponseDto } from "@/shared/model"; +import { SectionCollapsible } from "@/shared/ui"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/shared/ui/tooltip"; import { cn, Skeleton } from "@common/components"; @@ -18,7 +20,7 @@ interface ElaprDocPrgrsStatusProps { } function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) { - const status = getApproverStatus(item.atrzPrgrsSttsComCd); + const status = getApproverStatus(item.atrzPrgrsSttsComCd, item.atrzSeComCd); const isRejected = item.atrzPrgrsSttsComCd === REJECT_STATUS_CD; const rjctRsn = item.rjctRsn?.trim(); const metaParts = [item.ognzNm, item.jbpsSeNm].filter(Boolean); @@ -26,7 +28,7 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) { const statusBadge = ( +
{isRejected && rjctRsn ? ( {statusBadge} @@ -49,11 +51,16 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) { )}
- [{item.atrzSeComNm || "-"}] - {item.aprvrNm || item.aprvrUuid || "-"} +

+ [{item.atrzSeComNm || "-"}]{" "} + {item.aprvrNm || item.aprvrUuid || "-"} +

{metaParts.length > 0 && ( - {metaParts.join(" · ")} + {metaParts.join(" · ")} + )} + {item.atrzDt && ( + {dayjs(item.atrzDt).format("YYYY-MM-DD")} )}
@@ -61,34 +68,45 @@ function ElaprDocPrgrsStep({ item }: { item: ElaprDocPrgrsClientResponseDto }) { } function ElaprDocPrgrsStatus({ elaprDocNo }: ElaprDocPrgrsStatusProps) { + const userInfo = useUserStore((state) => state.userInfo); const { data, isLoading } = useElaprDocPrgrsByDocNo(elaprDocNo); const steps = sortByAtrzSeqNo(data ?? []); + if (userInfo?.dtlInstCd !== "ROLE_USER01") { + return null; + } + if (isLoading) { return ( -
- - - -
+ +
+ + + +
+
); } if (steps.length === 0) { - return
결재 정보가 없습니다.
; + return null; } return ( -
- {steps.map((item, index) => ( - -
- {index > 0 && } -
- -
- ))} -
+ +
+ {steps.map((item, index) => ( + + {index > 0 && ( +
+ {} +
+ )} + +
+ ))} +
+
); } diff --git a/apps/as/src/entities/elapr/ui/sortable-prgrs-stts-row.tsx b/apps/as/src/entities/elapr/ui/sortable-prgrs-stts-row.tsx index 601d272f2..3c2bb457a 100644 --- a/apps/as/src/entities/elapr/ui/sortable-prgrs-stts-row.tsx +++ b/apps/as/src/entities/elapr/ui/sortable-prgrs-stts-row.tsx @@ -73,14 +73,14 @@ function SortablePrgrsSttsRow({ const isDcarbable = isPrgrsSttsRowDcarbable(row); const rowDisabled = isLocked && !isEditing; const atrzSeLabel = row.atrzSeComNm || findCodeLabel(atrzSeCodes, row.atrzSeComCd); - const status = getApproverStatus(row.atrzPrgrsSttsComCd); + const status = getApproverStatus(row.atrzPrgrsSttsComCd, row.atrzSeComCd); const isRejectedRow = row.atrzPrgrsSttsComCd === "ATRZ_PRGRS_004"; const rjctRsn = row.rjctRsn?.trim(); const statusBadge = ( (); const MAIN_STEP_LABELS = { - save: "저장", - apply: "신청완료", - review: "설계자문 검토", - reply: "설계검토 회신", + apply: "설계자문 신청", + techReview: "기술검토", + reply: "검토결과 회신", } as const; -const REVIEW_STATUS_CODES = new Set(["CS", "CR"]); +export const PROGRESS_STATUS_COLUMNS = [ + { label: "설계자문 신청 (시군)", sttsComCd: "ER" }, + { label: "기술검토", sttsComCd: "TE" }, + { label: "기술검토 회신", sttsComCd: "CP" }, +]; -type BadgeState = "completed" | "current" | "pending" | "action"; +type BadgeState = "completed" | "current" | "pending"; interface ProgressBadge { label: string; state: BadgeState; } -function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] { - const reviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.review; - const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply; - +function buildProgressBadges(prgrsSttsComCd: string): ProgressBadge[] { if (prgrsSttsComCd === "CV") { return [ - { label: MAIN_STEP_LABELS.save, state: "current" }, { label: MAIN_STEP_LABELS.apply, state: "pending" }, - { label: MAIN_STEP_LABELS.review, state: "pending" }, + { label: MAIN_STEP_LABELS.techReview, state: "pending" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; } - if (prgrsSttsComCd === "CT") { + if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "CS" || prgrsSttsComCd === "SL") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, - { label: MAIN_STEP_LABELS.apply, state: "current" }, - { label: MAIN_STEP_LABELS.review, state: "pending" }, - { label: MAIN_STEP_LABELS.reply, state: "pending" }, - ]; - } - - if (REVIEW_STATUS_CODES.has(prgrsSttsComCd)) { - return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: reviewLabel, state: "action" }, + { label: MAIN_STEP_LABELS.techReview, state: "current" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; } - if (prgrsSttsComCd === "CP" || prgrsSttsComCd === "EP") { + if (prgrsSttsComCd === "CP") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: MAIN_STEP_LABELS.review, state: "completed" }, - { label: replyLabel, state: "action" }, + { label: MAIN_STEP_LABELS.techReview, state: "completed" }, + { label: MAIN_STEP_LABELS.reply, state: "completed" }, ]; } - return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }]; + return [ + { label: MAIN_STEP_LABELS.apply, state: "pending" }, + { label: MAIN_STEP_LABELS.techReview, state: "pending" }, + { label: MAIN_STEP_LABELS.reply, state: "pending" }, + ]; } function getBadgeColor(state: BadgeState) { @@ -77,8 +69,6 @@ function getBadgeColor(state: BadgeState) { return "blue"; case "pending": return "lightGray"; - case "action": - return "orange"; } } @@ -88,11 +78,10 @@ function formatOptional(value: string | undefined) { interface ProgressStepBadgeProps { prgrsSttsComCd: string; - prgrsSttsCdNm: string; } -function ProgressStepBadge({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepBadgeProps) { - const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm); +function ProgressStepBadge({ prgrsSttsComCd }: ProgressStepBadgeProps) { + const badges = buildProgressBadges(prgrsSttsComCd); return (
@@ -142,12 +131,7 @@ export function createDesignCnstnColumns() { id: "progress", header: "진행상황", meta: { align: "center" }, - cell: ({ row }) => ( - - ), + cell: ({ row }) => , }), columnHelper.accessor("regDt", { header: "신청일", diff --git a/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx b/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx index b9bc230d9..e1a0ced7e 100644 --- a/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx +++ b/apps/as/src/pages/design-cnstn/ui/design-form-page.tsx @@ -22,6 +22,7 @@ import { ContactInfoFormSection, isFormEsntlAtchComplete, PrivacyConsentSection, + ProgressStatusSection, } from "@/widgets/pbadms-sprt"; import { Button } from "@common/components"; @@ -31,6 +32,7 @@ import { formatTaskSeComCdForApi, } from "../lib/design-cnstn-regist-schema"; import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper"; +import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { BasicInfoFormSection } from "./sections/basic-info-form-section"; @@ -174,6 +176,13 @@ function DesignFormPage({ id, mode, initialData, createDefaults }: DesignFormPag return (
+ {initialData?.detDto?.designCnstnDocNo && ( + + )} diff --git a/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx b/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx index aefd42317..cc4315025 100644 --- a/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx +++ b/apps/as/src/pages/design-cnstn/ui/design-view-page.tsx @@ -6,11 +6,12 @@ import { useUserStore } from "@/shared/lib"; import { useDesignCnstnMng } from "@/entities/design-cnstn"; import { ElaprDocPrgrsStatus } from "@/entities/elapr"; import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { ProgressStatusSection } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; import { getEditableReviewStep } from "../lib/review-step-utils"; +import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { FloatingActionButtons } from "./components/floating-action-buttons"; import { AttachmentsSection } from "./sections/attachments-section"; @@ -65,11 +66,14 @@ function DesignViewPage({ id }: DesignViewPageProps) { return
상세 정보를 찾을 수 없습니다.
; } - const detDto = data.detDto; - const bizSclList = data.bizSclList ?? []; - const esntlFileList = data.esntlFileList ?? []; - const etcFileList = data.etcFileList ?? []; - const rvwList = data.rvwList ?? []; + const { + detDto, + bizSclList = [], + esntlFileList = [], + etcFileList = [], + rvwList = [], + sttsChgHstryList = [], + } = data; const editableReviewStep = getEditableReviewStep( userInfo?.dtlInstCd, @@ -78,11 +82,14 @@ function DesignViewPage({ id }: DesignViewPageProps) { return (
+ - - - + (); const MAIN_STEP_LABELS = { - save: "저장", - apply: "신청완료", + apply: "재원협의 신청", + envoReview: "환경청 검토", techReview: "기술검토", reply: "검토결과 회신", } as const; -const TECH_REVIEW_STATUS_CODES = new Set(["LE", "ER", "EE", "TE", "TS", "TR", "TP"]); +export const PROGRESS_STATUS_COLUMNS = [ + { label: "재원협의 신청 (시군)", sttsComCd: "ST" }, + { label: "재원협의 신청 (시도)", sttsComCd: "LE" }, + { label: "환경청 검토", sttsComCd: "ER" }, + { label: "기술검토", sttsComCd: "TE" }, + { label: "기술검토 회신", sttsComCd: "TP" }, + { label: "환경청 검토 결과 회신", sttsComCd: "EP" }, +]; -type BadgeState = "completed" | "current" | "pending" | "action"; +type BadgeState = "completed" | "current" | "pending"; interface ProgressBadge { label: string; state: BadgeState; } -function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] { - const techReviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.techReview; - const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply; - +function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] { if (prgrsSttsComCd === "SV") { return [ - { label: MAIN_STEP_LABELS.save, state: "current" }, { label: MAIN_STEP_LABELS.apply, state: "pending" }, + { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, { label: MAIN_STEP_LABELS.techReview, state: "pending" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; @@ -41,41 +45,61 @@ function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): Pro if (prgrsSttsComCd === "ST") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "current" }, + { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, { label: MAIN_STEP_LABELS.techReview, state: "pending" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; } - if (TECH_REVIEW_STATUS_CODES.has(prgrsSttsComCd)) { + if (prgrsSttsComCd === "LE") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: techReviewLabel, state: "action" }, + { label: MAIN_STEP_LABELS.envoReview, state: "current" }, + { label: MAIN_STEP_LABELS.techReview, state: "pending" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; } + if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") { + return [ + { label: MAIN_STEP_LABELS.apply, state: "completed" }, + { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, + { label: MAIN_STEP_LABELS.techReview, state: "current" }, + { label: MAIN_STEP_LABELS.reply, state: "pending" }, + ]; + } + + if (prgrsSttsComCd === "TP") { + return [ + { label: MAIN_STEP_LABELS.apply, state: "completed" }, + { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, + { + label: MAIN_STEP_LABELS.techReview, + state: techRplyHstryYn === "Y" ? "completed" : "pending", + }, + { label: MAIN_STEP_LABELS.reply, state: "current" }, + ]; + } + if (prgrsSttsComCd === "EP") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: MAIN_STEP_LABELS.techReview, state: "completed" }, - { label: replyLabel, state: "action" }, - ]; - } - - if (prgrsSttsComCd === "TA" || prgrsSttsComCd === "TB") { - return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, - { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: MAIN_STEP_LABELS.techReview, state: "completed" }, + { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, + { + label: MAIN_STEP_LABELS.techReview, + state: techRplyHstryYn === "Y" ? "completed" : "pending", + }, { label: MAIN_STEP_LABELS.reply, state: "completed" }, ]; } - return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }]; + return [ + { label: MAIN_STEP_LABELS.apply, state: "pending" }, + { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, + { label: MAIN_STEP_LABELS.techReview, state: "pending" }, + { label: MAIN_STEP_LABELS.reply, state: "pending" }, + ]; } function getBadgeColor(state: BadgeState) { @@ -86,8 +110,6 @@ function getBadgeColor(state: BadgeState) { return "blue"; case "pending": return "lightGray"; - case "action": - return "orange"; } } @@ -97,11 +119,11 @@ function formatOptional(value: string | undefined) { interface ProgressStepBadgeProps { prgrsSttsComCd: string; - prgrsSttsCdNm: string; + techRplyHstryYn: "Y" | "N"; } -function ProgressStepBadge({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepBadgeProps) { - const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm); +function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) { + const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn); return (
@@ -154,7 +176,7 @@ export function createFnrsCnsltnColumns() { cell: ({ row }) => ( ), }), diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx index 5a4222553..e189e28f6 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-form-page.tsx @@ -30,6 +30,7 @@ import { isFormChecklistComplete, isFormEsntlAtchComplete, PrivacyConsentSection, + ProgressStatusSection, recalculateFnrsSharTotals, SplmntDmndFormSection, toFnrsSharSaveList, @@ -43,6 +44,7 @@ import { formatTaskSeComCdForApi, } from "../lib/fnrs-cnsltn-regist-schema"; import { getCreateDefaultValues, mapDetailToFormData } from "../lib/form-data-mapper"; +import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { BasicInfoFormSection } from "./sections/basic-info-form-section"; interface FnrsFormPageProps { @@ -270,6 +272,13 @@ function FnrsFormPage({ id, mode, initialData, createDefaults }: FnrsFormPagePro return ( + {initialData?.detDto?.fnrsCnsltnDocNo && ( + + )} diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx index 3ec2522d1..6fa957d44 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/fnrs-view-page.tsx @@ -6,12 +6,13 @@ import { useUserStore } from "@/shared/lib"; import { ElaprDocPrgrsStatus } from "@/entities/elapr"; import { useFnrsCnsltnMng } from "@/entities/fnrs-cnsltn"; import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; -import { SectionCollapsible, SupplementDemandSection } from "@/widgets/pbadms-sprt"; +import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; import { getEditableReviewStep } from "../lib/review-step-utils"; import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state"; +import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { FloatingActionButtons } from "./components/floating-action-buttons"; import { AttachmentsSection } from "./sections/attachments-section"; @@ -71,15 +72,18 @@ function FnrsViewPage({ id }: FnrsViewPageProps) { return
상세 정보를 찾을 수 없습니다.
; } - const detDto = data.detDto; - const bizSclList = data.bizSclList ?? []; - const bizCstList = data.bizCstList ?? []; - const cklstResultList = data.cklstResultList ?? []; - const esntlFileList = data.esntlFileList ?? []; - const etcFileList = data.etcFileList ?? []; - const rvwList = data.rvwList ?? []; - const splmntDmndList = data.splmntDmndList ?? []; - const fnrsSharList = data.fnrsSharList ?? []; + const { + detDto, + bizSclList = [], + bizCstList = [], + cklstResultList = [], + esntlFileList = [], + etcFileList = [], + rvwList = [], + splmntDmndList = [], + fnrsSharList = [], + sttsChgHstryList = [], + } = data; const editableReviewStep = getEditableReviewStep( userInfo?.dtlInstCd, @@ -88,11 +92,14 @@ function FnrsViewPage({ id }: FnrsViewPageProps) { return (
+ - - - + diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/attachments-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/attachments-section.tsx index 0ac529c18..b55b87c47 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/attachments-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/attachments-section.tsx @@ -1,12 +1,12 @@ "use client"; import { formatFileSize } from "@/shared/lib/file-utils"; +import { SectionCollapsible } from "@/shared/ui"; import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; import type { AtflMngClientResponseDto, EsntlAtchFileMngTreeResponseDto, } from "@/entities/fnrs-cnsltn"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface AttachmentsSectionProps { diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/basic-info-form-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/basic-info-form-section.tsx index 751312ac6..759b1f0df 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/basic-info-form-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/basic-info-form-section.tsx @@ -3,10 +3,10 @@ import { useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; +import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldError, FieldGroup, FieldLabel, FieldSet } from "@/shared/ui/field"; import { findCodeLabel, useCodeList } from "@/entities/code"; import { type BnsApplyData, BnsPickerDialog } from "@/widgets/bns-picker"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Button, Checkbox, diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/basic-info-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/basic-info-section.tsx index 939af68fd..48f14eceb 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/basic-info-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/basic-info-section.tsx @@ -1,7 +1,7 @@ "use client"; +import { SectionCollapsible } from "@/shared/ui"; import type { FnrsCnsltnMngResponseDto } from "@/entities/fnrs-cnsltn"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface BasicInfoSectionProps { diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/business-cost-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/business-cost-section.tsx index 285fe8bed..d880811b6 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/business-cost-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/business-cost-section.tsx @@ -2,9 +2,9 @@ import { useMemo } from "react"; +import { SectionCollapsible } from "@/shared/ui"; import type { BizCstTreeDto } from "@/entities/fnrs-cnsltn"; import { calcDiffPercent, fmtNumber, indentPx } from "@/widgets/pbadms-sprt"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; import { sortBizCstListByTreePath } from "../../lib/business-cost-utils"; diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/business-scale-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/business-scale-section.tsx index f8849cf8f..c1f5a28af 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/business-scale-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/business-scale-section.tsx @@ -1,8 +1,8 @@ "use client"; +import { SectionCollapsible } from "@/shared/ui"; import type { BizSclTreeDto } from "@/entities/fnrs-cnsltn"; import { fmtNumber, indentPx } from "@/widgets/pbadms-sprt"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface BusinessScaleSectionProps { diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/checklist-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/checklist-section.tsx index 09aa48052..e50f5a6b8 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/checklist-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/checklist-section.tsx @@ -2,8 +2,8 @@ import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from "react"; +import { SectionCollapsible } from "@/shared/ui"; import type { CklstRvwRsltTreeDto } from "@/entities/fnrs-cnsltn"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/contact-info-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/contact-info-section.tsx index 7443460e8..f5273fb33 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/contact-info-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/contact-info-section.tsx @@ -1,7 +1,7 @@ "use client"; +import { SectionCollapsible } from "@/shared/ui"; import type { FnrsCnsltnMngResponseDto } from "@/entities/fnrs-cnsltn"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface ContactInfoSectionProps { diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/fnrs-shar-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/fnrs-shar-section.tsx index 198261c41..ad83c53e4 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/fnrs-shar-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/fnrs-shar-section.tsx @@ -2,8 +2,9 @@ import { useMemo } from "react"; +import { SectionCollapsible } from "@/shared/ui"; import type { FnrsSharBizCstTreeDto } from "@/entities/fnrs-shar-se"; -import { fmtNumber, indentPx, SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { fmtNumber, indentPx } from "@/widgets/pbadms-sprt"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface FnrsSharSectionProps { diff --git a/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx index f8d416af1..bdbd4e622 100644 --- a/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/fnrs-cnsltn/ui/sections/tech-review-opinion-section.tsx @@ -7,10 +7,10 @@ import { Paperclip } from "lucide-react"; import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; +import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; import { attachService } from "@/entities/attach"; import type { FnrsCnsltnRvwOpnnResponseDto } from "@/entities/fnrs-cnsltn"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Button, RadioGroup, diff --git a/apps/as/src/pages/instl-sncn/model/columns.tsx b/apps/as/src/pages/instl-sncn/model/columns.tsx index 772e20df0..2dcca5c58 100644 --- a/apps/as/src/pages/instl-sncn/model/columns.tsx +++ b/apps/as/src/pages/instl-sncn/model/columns.tsx @@ -11,29 +11,34 @@ import { Badge } from "@common/components"; const columnHelper = createColumnHelper(); const MAIN_STEP_LABELS = { - save: "저장", - apply: "신청완료", + apply: "설치인가 신청", + envoReview: "환경청 검토", techReview: "기술검토", reply: "검토결과 회신", } as const; -const TECH_REVIEW_STATUS_CODES = new Set(["LE", "ER", "EE", "TE", "TS", "TR", "TP"]); +export const PROGRESS_STATUS_COLUMNS = [ + { label: "설치인가 신청 (시군)", sttsComCd: "ST" }, + { label: "설치인가 신청 (시도)", sttsComCd: "LE" }, + { label: "환경청 검토", sttsComCd: "ER" }, + { label: "기술검토", sttsComCd: "TE" }, + { label: "기술검토 회신", sttsComCd: "TP" }, + { label: "검토결과 회신", sttsComCd: "EP" }, + { label: "설치인가 승인", sttsComCd: "TA" }, +]; -type BadgeState = "completed" | "current" | "pending" | "action"; +type BadgeState = "completed" | "current" | "pending"; interface ProgressBadge { label: string; state: BadgeState; } -function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] { - const techReviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.techReview; - const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply; - +function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] { if (prgrsSttsComCd === "SV") { return [ - { label: MAIN_STEP_LABELS.save, state: "current" }, { label: MAIN_STEP_LABELS.apply, state: "pending" }, + { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, { label: MAIN_STEP_LABELS.techReview, state: "pending" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; @@ -41,41 +46,61 @@ function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): Pro if (prgrsSttsComCd === "ST") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "current" }, + { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, { label: MAIN_STEP_LABELS.techReview, state: "pending" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; } - if (TECH_REVIEW_STATUS_CODES.has(prgrsSttsComCd)) { + if (prgrsSttsComCd === "LE") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: techReviewLabel, state: "action" }, + { label: MAIN_STEP_LABELS.envoReview, state: "current" }, + { label: MAIN_STEP_LABELS.techReview, state: "pending" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; } - if (prgrsSttsComCd === "EP") { + if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: MAIN_STEP_LABELS.techReview, state: "completed" }, - { label: replyLabel, state: "action" }, + { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, + { label: MAIN_STEP_LABELS.techReview, state: "current" }, + { label: MAIN_STEP_LABELS.reply, state: "pending" }, + ]; + } + + if (prgrsSttsComCd === "TP" || prgrsSttsComCd === "EP") { + return [ + { label: MAIN_STEP_LABELS.apply, state: "completed" }, + { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, + { + label: MAIN_STEP_LABELS.techReview, + state: techRplyHstryYn === "Y" ? "completed" : "pending", + }, + { label: MAIN_STEP_LABELS.reply, state: "current" }, ]; } if (prgrsSttsComCd === "TA" || prgrsSttsComCd === "TB") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: MAIN_STEP_LABELS.techReview, state: "completed" }, + { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, + { + label: MAIN_STEP_LABELS.techReview, + state: techRplyHstryYn === "Y" ? "completed" : "pending", + }, { label: MAIN_STEP_LABELS.reply, state: "completed" }, ]; } - return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }]; + return [ + { label: MAIN_STEP_LABELS.apply, state: "pending" }, + { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, + { label: MAIN_STEP_LABELS.techReview, state: "pending" }, + { label: MAIN_STEP_LABELS.reply, state: "pending" }, + ]; } function getBadgeColor(state: BadgeState) { @@ -86,8 +111,6 @@ function getBadgeColor(state: BadgeState) { return "blue"; case "pending": return "lightGray"; - case "action": - return "orange"; } } @@ -97,11 +120,11 @@ function formatOptional(value: string | undefined) { interface ProgressStepBadgeProps { prgrsSttsComCd: string; - prgrsSttsCdNm: string; + techRplyHstryYn: "Y" | "N"; } -function ProgressStepBadge({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepBadgeProps) { - const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm); +function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) { + const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn); return (
@@ -154,7 +177,7 @@ export function createInstlSncnColumns() { cell: ({ row }) => ( ), }), diff --git a/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx b/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx index bc248e3b2..c2bd574b9 100644 --- a/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx +++ b/apps/as/src/pages/instl-sncn/ui/instl-form-page.tsx @@ -25,6 +25,7 @@ import { isFormChecklistComplete, isFormEsntlAtchComplete, PrivacyConsentSection, + ProgressStatusSection, SplmntDmndFormSection, } from "@/widgets/pbadms-sprt"; import { Button } from "@common/components"; @@ -35,6 +36,7 @@ import { type InstlSncnRegistFormData, instlSncnRegistSchema, } from "../lib/instl-sncn-regist-schema"; +import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { BasicInfoFormSection } from "./sections/basic-info-form-section"; interface InstlFormPageProps { @@ -228,6 +230,13 @@ function InstlFormPage({ id, mode, initialData, createDefaults }: InstlFormPageP return ( + {initialData?.detDto.instlSncnDocNo && ( + + )} diff --git a/apps/as/src/pages/instl-sncn/ui/instl-view-page.tsx b/apps/as/src/pages/instl-sncn/ui/instl-view-page.tsx index 094072f1a..d8f637f59 100644 --- a/apps/as/src/pages/instl-sncn/ui/instl-view-page.tsx +++ b/apps/as/src/pages/instl-sncn/ui/instl-view-page.tsx @@ -6,12 +6,13 @@ import { useUserStore } from "@/shared/lib"; import { ElaprDocPrgrsStatus } from "@/entities/elapr"; import { useInstlSncnDetail } from "@/entities/instl-sncn"; import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; -import { SectionCollapsible, SupplementDemandSection } from "@/widgets/pbadms-sprt"; +import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; import { getEditableReviewStep } from "../lib/review-step-utils"; import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state"; +import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { FloatingActionButtons } from "./components/floating-action-buttons"; import { AttachmentsSection } from "./sections/attachments-section"; @@ -79,6 +80,7 @@ function InstlViewPage({ id }: InstlViewPageProps) { etcFileList, rvwList = [], splmntDmndList = [], + sttsChgHstryList = [], } = data; const editableReviewStep = getEditableReviewStep( @@ -88,11 +90,14 @@ function InstlViewPage({ id }: InstlViewPageProps) { return (
+ - - - + (); const MAIN_STEP_LABELS = { - save: "저장", - apply: "신청완료", + apply: "기본계획 신청", envoReview: "환경청 검토", techReview: "기술검토", reply: "검토결과 회신", - supplement: "보완요청", } as const; -const TECH_REVIEW_STATUS_CODES = new Set(["ER", "TE", "TP"]); +export const PROGRESS_STATUS_COLUMNS = [ + { label: "기본계획 신청 (시군)", sttsComCd: "LE" }, + { label: "환경청 검토", sttsComCd: "ER" }, + { label: "기술검토", sttsComCd: "TE" }, + { label: "기술검토 회신", sttsComCd: "TP" }, + { label: "횐경청 검토 결과 회신", sttsComCd: "EP" }, +]; -type BadgeState = "completed" | "current" | "pending" | "action"; +type BadgeState = "completed" | "current" | "pending"; interface ProgressBadge { label: string; state: BadgeState; } -function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): ProgressBadge[] { - const techReviewLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.techReview; - const replyLabel = prgrsSttsCdNm || MAIN_STEP_LABELS.reply; - +function buildProgressBadges(prgrsSttsComCd: string, techRplyHstryYn: "Y" | "N"): ProgressBadge[] { if (prgrsSttsComCd === "SV") { return [ - { label: MAIN_STEP_LABELS.save, state: "current" }, { label: MAIN_STEP_LABELS.apply, state: "pending" }, { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, { label: MAIN_STEP_LABELS.techReview, state: "pending" }, @@ -42,57 +42,63 @@ function buildProgressBadges(prgrsSttsComCd: string, prgrsSttsCdNm: string): Pro ]; } + if (prgrsSttsComCd === "ST") { + return [ + { label: MAIN_STEP_LABELS.apply, state: "current" }, + { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, + { label: MAIN_STEP_LABELS.techReview, state: "pending" }, + { label: MAIN_STEP_LABELS.reply, state: "pending" }, + ]; + } + if (prgrsSttsComCd === "LE") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: MAIN_STEP_LABELS.envoReview, state: "action" }, + { label: MAIN_STEP_LABELS.envoReview, state: "current" }, { label: MAIN_STEP_LABELS.techReview, state: "pending" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; } - if (prgrsSttsComCd === "SL") { + if (prgrsSttsComCd === "ER" || prgrsSttsComCd === "TE" || prgrsSttsComCd === "SL") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, - { label: MAIN_STEP_LABELS.supplement, state: "action" }, + { label: MAIN_STEP_LABELS.techReview, state: "current" }, { label: MAIN_STEP_LABELS.reply, state: "pending" }, ]; } - if (TECH_REVIEW_STATUS_CODES.has(prgrsSttsComCd)) { + if (prgrsSttsComCd === "TP") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, - { label: techReviewLabel, state: "action" }, - { label: MAIN_STEP_LABELS.reply, state: "pending" }, - ]; - } - - if (prgrsSttsComCd === "TA") { - return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, - { label: MAIN_STEP_LABELS.apply, state: "completed" }, - { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, - { label: MAIN_STEP_LABELS.techReview, state: "completed" }, - { label: MAIN_STEP_LABELS.reply, state: "completed" }, + { + label: MAIN_STEP_LABELS.techReview, + state: techRplyHstryYn === "Y" ? "completed" : "pending", + }, + { label: MAIN_STEP_LABELS.reply, state: "current" }, ]; } if (prgrsSttsComCd === "EP") { return [ - { label: MAIN_STEP_LABELS.save, state: "completed" }, { label: MAIN_STEP_LABELS.apply, state: "completed" }, { label: MAIN_STEP_LABELS.envoReview, state: "completed" }, - { label: MAIN_STEP_LABELS.techReview, state: "completed" }, - { label: replyLabel, state: "completed" }, + { + label: MAIN_STEP_LABELS.techReview, + state: techRplyHstryYn === "Y" ? "completed" : "pending", + }, + { label: MAIN_STEP_LABELS.reply, state: "completed" }, ]; } - return [{ label: prgrsSttsCdNm || prgrsSttsComCd || "-", state: "current" }]; + return [ + { label: MAIN_STEP_LABELS.apply, state: "pending" }, + { label: MAIN_STEP_LABELS.envoReview, state: "pending" }, + { label: MAIN_STEP_LABELS.techReview, state: "pending" }, + { label: MAIN_STEP_LABELS.reply, state: "pending" }, + ]; } function getBadgeColor(state: BadgeState) { @@ -103,8 +109,6 @@ function getBadgeColor(state: BadgeState) { return "blue"; case "pending": return "lightGray"; - case "action": - return "orange"; } } @@ -114,11 +118,11 @@ function formatOptional(value: string | undefined) { interface ProgressStepBadgeProps { prgrsSttsComCd: string; - prgrsSttsCdNm: string; + techRplyHstryYn: "Y" | "N"; } -function ProgressStepBadge({ prgrsSttsComCd, prgrsSttsCdNm }: ProgressStepBadgeProps) { - const badges = buildProgressBadges(prgrsSttsComCd, prgrsSttsCdNm); +function ProgressStepBadge({ prgrsSttsComCd, techRplyHstryYn }: ProgressStepBadgeProps) { + const badges = buildProgressBadges(prgrsSttsComCd, techRplyHstryYn); return (
@@ -177,7 +181,7 @@ export function createSewerBscPlanColumns() { cell: ({ row }) => ( ), }), diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/previous-plan-lookup-dialog.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/previous-plan-lookup-dialog.tsx index 162efec34..0d0d6f4a8 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/previous-plan-lookup-dialog.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/previous-plan-lookup-dialog.tsx @@ -3,13 +3,13 @@ import { useEffect, useMemo, useState } from "react"; import { useAlertStore } from "@/shared/lib"; +import { SectionCollapsible } from "@/shared/ui"; import { type CmCdAdstDstrComboResponseDto, useFindGugunCombo, useFindSidoCombo, } from "@/entities/adstdstr"; import { useLvNowDmgMtncGnsvy, useNowDmgMtncGnsvy } from "@/entities/dc"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Button, Dialog, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/attachments-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/attachments-section.tsx index f35cab5ff..e379d5097 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/attachments-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/attachments-section.tsx @@ -3,12 +3,12 @@ import { Paperclip } from "lucide-react"; import { formatFileSize } from "@/shared/lib/file-utils"; +import { SectionCollapsible } from "@/shared/ui"; import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; import type { AtflMngClientResponseDto, EsntlAtchFileMngTreeResponseDto, } from "@/entities/sewer-bsc-plan"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface AttachmentsSectionProps { diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/basic-info-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/basic-info-form-section.tsx index 1af5a02b1..36544c2f4 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/basic-info-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/basic-info-form-section.tsx @@ -3,8 +3,8 @@ import { Controller, useFormContext } from "react-hook-form"; import { useUserStore } from "@/shared/lib"; +import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldError } from "@/shared/ui/field"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Input, Select, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/basic-info-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/basic-info-section.tsx index 3cfd3e4a9..3272c12b6 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/basic-info-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/basic-info-section.tsx @@ -1,10 +1,10 @@ "use client"; +import { SectionCollapsible } from "@/shared/ui"; import type { SewerBscPlanDtlResponseDto, SewerBscPlanResponseDto, } from "@/entities/sewer-bsc-plan"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface BasicInfoSectionProps { diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-form-section.tsx index f6cba8863..efbdfc462 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-form-section.tsx @@ -3,9 +3,10 @@ import { useEffect, useMemo } from "react"; import { useFormContext, useWatch } from "react-hook-form"; +import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useAcntgSeMngTree } from "@/entities/acntg"; -import { buildAcntgCostItems, SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { buildAcntgCostItems } from "@/widgets/pbadms-sprt"; import { Input, Skeleton, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-section.tsx index ae224238d..4b1ecba97 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/biz-cst-matrix-section.tsx @@ -2,12 +2,13 @@ import { useMemo } from "react"; +import { SectionCollapsible } from "@/shared/ui"; import { useAcntgSeMngTree } from "@/entities/acntg"; import type { SewerBscPlanBizCstResponseDto, SewerBscPlanDtlResponseDto, } from "@/entities/sewer-bsc-plan"; -import { buildAcntgCostItems, fmtNumber, SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { buildAcntgCostItems, fmtNumber } from "@/widgets/pbadms-sprt"; import { Skeleton, Table, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/checklist-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/checklist-section.tsx index 9bb85272b..14123d871 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/checklist-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/checklist-section.tsx @@ -2,8 +2,8 @@ import { type Dispatch, type SetStateAction, useEffect, useMemo, useState } from "react"; +import { SectionCollapsible } from "@/shared/ui"; import type { CklstRvwRsltTreeDto } from "@/entities/sewer-bsc-plan"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components"; import { diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/contact-info-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/contact-info-section.tsx index 4abd171c1..11a99df12 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/contact-info-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/contact-info-section.tsx @@ -1,7 +1,7 @@ "use client"; +import { SectionCollapsible } from "@/shared/ui"; import type { SewerBscPlanDtlResponseDto } from "@/entities/sewer-bsc-plan"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface ContactInfoSectionProps { diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-form-section.tsx index 2082822d2..c27e2d34b 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-form-section.tsx @@ -3,9 +3,10 @@ import { useMemo } from "react"; import { useFormContext, useWatch } from "react-hook-form"; +import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useAcntgSeMngTree } from "@/entities/acntg"; -import { buildAcntgCostItems, SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { buildAcntgCostItems } from "@/widgets/pbadms-sprt"; import { cn, Input, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-section.tsx index 5ec099fda..92ae6851a 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/fnrs-cst-section.tsx @@ -2,12 +2,13 @@ import { useMemo } from "react"; +import { SectionCollapsible } from "@/shared/ui"; import { useAcntgSeMngTree } from "@/entities/acntg"; import type { SewerBscPlanDtlResponseDto, SewerBscPlanFnrsCstResponseDto, } from "@/entities/sewer-bsc-plan"; -import { buildAcntgCostItems, fmtNumber, SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { buildAcntgCostItems, fmtNumber } from "@/widgets/pbadms-sprt"; import { Skeleton, Table, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx index 97674abbc..c6dcdfc2f 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-form-section.tsx @@ -4,8 +4,8 @@ import { useState } from "react"; import { useFormContext, useWatch } from "react-hook-form"; import dynamic from "next/dynamic"; +import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Button, Input, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-section.tsx index 87f91fac9..9bb57b943 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/outline-section.tsx @@ -1,10 +1,11 @@ "use client"; +import { SectionCollapsible } from "@/shared/ui"; import type { SewerBscPlanDtlResponseDto, SewerBscPlanOtlnResponseDto, } from "@/entities/sewer-bsc-plan"; -import { fmtNumber, SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { fmtNumber } from "@/widgets/pbadms-sprt"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; interface OutlineSectionProps { diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx index cf8135717..1781f183e 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tech-review-opinion-section.tsx @@ -7,10 +7,10 @@ import { Paperclip } from "lucide-react"; import { PROXY_HOST } from "@/shared/config"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; +import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel, FieldSet } from "@/shared/ui/field"; import { attachService } from "@/entities/attach"; import type { SewerBscPlanRvwOpnnResponseDto } from "@/entities/sewer-bsc-plan"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Button, RadioGroup, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-form-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-form-section.tsx index dfd49b8bd..89b25aee9 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-form-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-form-section.tsx @@ -7,11 +7,11 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; +import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { attachService } from "@/entities/attach"; import { useFacilityList } from "@/entities/bns-if"; import { useNowDmgMtncGnsvy } from "@/entities/dc"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Button, Checkbox, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx index 372f6cb60..7f87254d3 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sections/tmplnt-section.tsx @@ -4,6 +4,7 @@ import { useMemo, useState } from "react"; import { Paperclip } from "lucide-react"; +import { SectionCollapsible } from "@/shared/ui"; import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; import { useFacilityList } from "@/entities/bns-if"; import { useNowDmgMtncGnsvy } from "@/entities/dc"; @@ -12,7 +13,7 @@ import type { SewerBscPlanResponseDto, SewerBscPlanTmplntResponseDto, } from "@/entities/sewer-bsc-plan"; -import { fmtNumber, SectionCollapsible } from "@/widgets/pbadms-sprt"; +import { fmtNumber } from "@/widgets/pbadms-sprt"; import { Table, TableBody, diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-form-page.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-form-page.tsx index 03c605ebe..ca8f2c9d0 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-form-page.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-form-page.tsx @@ -25,6 +25,7 @@ import { isFormChecklistComplete, isFormEsntlAtchComplete, PrivacyConsentSection, + ProgressStatusSection, SplmntDmndFormSection, } from "@/widgets/pbadms-sprt"; import { Button } from "@common/components"; @@ -39,6 +40,7 @@ import { type SewerBscPlanRegistFormData, sewerBscPlanRegistSchema, } from "../lib/sewer-bsc-plan-regist-schema"; +import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { BasicInfoFormSection } from "./sections/basic-info-form-section"; import { BizCstMatrixFormSection } from "./sections/biz-cst-matrix-form-section"; @@ -242,6 +244,13 @@ function SewerBscPlanFormPage({ return ( + {initialData?.detDto?.sewerBscPlanDocNo && ( + + )} diff --git a/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-view-page.tsx b/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-view-page.tsx index 293d31bd8..e04483925 100644 --- a/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-view-page.tsx +++ b/apps/as/src/pages/sewer-bsc-plan/ui/sewer-bsc-plan-view-page.tsx @@ -6,12 +6,13 @@ import { useUserStore } from "@/shared/lib"; import { ElaprDocPrgrsStatus } from "@/entities/elapr"; import { useRvwMnpwMngLoginUserList } from "@/entities/rvw-mnpw"; import { useSewerBscPlanDetails } from "@/entities/sewer-bsc-plan"; -import { SectionCollapsible, SupplementDemandSection } from "@/widgets/pbadms-sprt"; +import { ProgressStatusSection, SupplementDemandSection } from "@/widgets/pbadms-sprt"; import { Skeleton } from "@common/components"; import { needsRoleUser01AtrzResolve } from "../lib/button-config"; import { getEditableReviewStep } from "../lib/review-step-utils"; import { EMPTY_CKLST_RESULT_LIST, useChecklistFormState } from "../lib/use-checklist-form-state"; +import { PROGRESS_STATUS_COLUMNS } from "../model/columns"; import { FloatingActionButtons } from "./components/floating-action-buttons"; import { AttachmentsSection } from "./sections/attachments-section"; @@ -74,17 +75,20 @@ function SewerBscPlanViewPage({ id, propChgCycl }: SewerBscPlanViewPageProps) { return
상세 정보를 찾을 수 없습니다.
; } - const detDto = data.detDto; - const activeDtlDto = data.activeDtlDto; - const otlnList = data.otlnList ?? []; - const bizCstList = data.bizCstList ?? []; - const fnrsCstList = data.fnrsCstList ?? []; - const tmplntList = data.tmplntList ?? []; - const cklstResultRows = data.cklstResultRows ?? []; - const esntlFileList = data.esntlFileList ?? []; - const etcFileList = data.etcFileList ?? []; - const rvwOpnnList = data.rvwOpnnList ?? []; - const splmntDmndList = data.splmntDmndList ?? []; + const { + detDto, + activeDtlDto, + otlnList = [], + bizCstList = [], + fnrsCstList = [], + tmplntList = [], + cklstResultRows = [], + esntlFileList = [], + etcFileList = [], + rvwOpnnList = [], + splmntDmndList = [], + sttsChgHstryList = [], + } = data; const editableReviewStep = getEditableReviewStep( userInfo?.dtlInstCd, @@ -93,11 +97,14 @@ function SewerBscPlanViewPage({ id, propChgCycl }: SewerBscPlanViewPageProps) { return (
+ - - - + diff --git a/apps/as/src/shared/model/index.ts b/apps/as/src/shared/model/index.ts index bdf339145..75c66834d 100644 --- a/apps/as/src/shared/model/index.ts +++ b/apps/as/src/shared/model/index.ts @@ -38,6 +38,7 @@ export type { CklstResultSaveBase, CklstRvwRsltTreeDto, DocPrgrsUpdateRequestDto, + DocSttsChgHstryResponseDto, ElaprDocPrgrsClientResponseDto, EsntlAtchFileMngTreeResponseDto, EsntlAtchFileSaveBase, diff --git a/apps/as/src/shared/model/pbadms-sprt.ts b/apps/as/src/shared/model/pbadms-sprt.ts index 67b0b831e..272e6d110 100644 --- a/apps/as/src/shared/model/pbadms-sprt.ts +++ b/apps/as/src/shared/model/pbadms-sprt.ts @@ -155,6 +155,22 @@ export interface ElaprDocPrgrsClientResponseDto { dcarbYn?: string; } +/** 문서 상태 변경 이력 응답 DTO */ +export interface DocSttsChgHstryResponseDto { + chgHstrySn: number; + docSeComCd: string; + docSeComNm: string; + docNo: string; + chgBfrSttsComCd: string; + chgBfrSttsComNm: string; + chgAftrSttsComCd: string; + chgAftrSttsComNm: string; + rmrk: string; + rgtrUuid: string; + rgtrNm: string; + regDt: string; +} + /** 사업규모 저장 공통 필드 */ export interface BzScaleSaveBase { cnstpSeCd?: string; @@ -244,4 +260,5 @@ export interface PbadmsMngDetResponseBase - - - - {children} - - ); -} - -export { SectionCollapsible }; +"use client"; + +import type { ReactNode } from "react"; + +import { ChevronDown } from "lucide-react"; + +import { cn, Collapsible, CollapsibleContent, CollapsibleTrigger } from "@common/components"; + +interface SectionCollapsibleProps { + title: ReactNode; + defaultOpen?: boolean; + children: ReactNode; +} + +function SectionCollapsible({ title, defaultOpen = true, children }: SectionCollapsibleProps) { + return ( + + + + + {children} + + ); +} + +export { SectionCollapsible }; diff --git a/apps/as/src/widgets/bns-picker/ui/bns-picker-dialog.tsx b/apps/as/src/widgets/bns-picker/ui/bns-picker-dialog.tsx index a259c8aca..4153aec1a 100644 --- a/apps/as/src/widgets/bns-picker/ui/bns-picker-dialog.tsx +++ b/apps/as/src/widgets/bns-picker/ui/bns-picker-dialog.tsx @@ -12,12 +12,12 @@ import { import { useDataTable } from "@/shared/lib"; import type { PageResponse } from "@/shared/model"; +import { SectionCollapsible } from "@/shared/ui"; import { NoData } from "@/shared/ui/no-data"; import type { CmCdAdstDstrComboResponseDto } from "@/entities/adstdstr"; import { useFindGugunCombo, useFindSidoCombo } from "@/entities/adstdstr"; import type { BnsDetailResponseDto, BnsListResponseDto, BnsSearchDto } from "@/entities/bns-if"; import { buildBnsSearchDto, useBnsDetail, useBnsList } from "@/entities/bns-if"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Button, Dialog, diff --git a/apps/as/src/widgets/cnfm/ui/attachments.tsx b/apps/as/src/widgets/cnfm/ui/attachments.tsx index dcd68209e..4a585d231 100644 --- a/apps/as/src/widgets/cnfm/ui/attachments.tsx +++ b/apps/as/src/widgets/cnfm/ui/attachments.tsx @@ -1,4 +1,5 @@ import React from "react"; + import { formatFileSize } from "@/shared/lib/file-utils"; type EtcFile = { diff --git a/apps/as/src/widgets/pbadms-sprt/index.ts b/apps/as/src/widgets/pbadms-sprt/index.ts index 8f2c44b95..4746b9097 100644 --- a/apps/as/src/widgets/pbadms-sprt/index.ts +++ b/apps/as/src/widgets/pbadms-sprt/index.ts @@ -39,7 +39,7 @@ export { type PbadmsTaskApplySelection, } from "./ui/pbadms-task-apply-dialog"; export { PrivacyConsentSection } from "./ui/privacy-consent-section"; -export { SectionCollapsible } from "./ui/section-collapsible"; +export { ProgressStatusSection } from "./ui/progress-status-section"; export { SplmntDmndDialog, type SplmntDmndDialogConfirmItem, diff --git a/apps/as/src/widgets/pbadms-sprt/lib/progress-status-utils.ts b/apps/as/src/widgets/pbadms-sprt/lib/progress-status-utils.ts new file mode 100644 index 000000000..2a936c6ad --- /dev/null +++ b/apps/as/src/widgets/pbadms-sprt/lib/progress-status-utils.ts @@ -0,0 +1,59 @@ +import type { + DocSttsChgHstryResponseDto, + ElaprDocPrgrsClientResponseDto, +} from "@/shared/model/pbadms-sprt"; + +interface ProgressStatusItem { + rgtrNm: string; + regDt: string; +} + +const TECH_REVIEW_STTS_COM_CD = "TE"; +const TECH_REVIEW_ATRZ_SE_COM_CD = "ATRZ02"; +const TECH_REVIEW_ATRZ_PRGRS_STTS_COM_CD = "ATRZ_PRGRS_003"; + +function findProgressStatusItem( + sttsChgHstryList: DocSttsChgHstryResponseDto[], + sttsComCd: string, +): ProgressStatusItem[] { + return sttsChgHstryList + .filter((item) => item.chgAftrSttsComCd === sttsComCd) + .map((item) => ({ + rgtrNm: item.rgtrNm, + regDt: item.regDt, + })); +} + +function findTechReviewProgressItem( + elaprDocPrgrsList: ElaprDocPrgrsClientResponseDto[], +): ProgressStatusItem[] { + return elaprDocPrgrsList + .filter( + (item) => + item.atrzSeComCd === TECH_REVIEW_ATRZ_SE_COM_CD && + item.atrzPrgrsSttsComCd === TECH_REVIEW_ATRZ_PRGRS_STTS_COM_CD, + ) + .map((item) => ({ + rgtrNm: item.aprvrNm ?? "", + regDt: item.atrzDt ?? "", + })); +} + +function resolveProgressStatusItem( + sttsComCd: string, + sttsChgHstryList: DocSttsChgHstryResponseDto[], + elaprDocPrgrsList: ElaprDocPrgrsClientResponseDto[], +): ProgressStatusItem[] { + if (sttsComCd === TECH_REVIEW_STTS_COM_CD) { + return findTechReviewProgressItem(elaprDocPrgrsList); + } + + return findProgressStatusItem(sttsChgHstryList, sttsComCd); +} + +export { + findProgressStatusItem, + findTechReviewProgressItem, + resolveProgressStatusItem, + TECH_REVIEW_STTS_COM_CD, +}; diff --git a/apps/as/src/widgets/pbadms-sprt/ui/attachments-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/attachments-form-section.tsx index 03d812211..b4bce6b8a 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/attachments-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/attachments-form-section.tsx @@ -9,6 +9,7 @@ import { ENDPOINT } from "@/shared/config"; import { useTus } from "@/shared/lib"; import type { AtflMngClientResponseDto, EsntlAtchFileMngTreeResponseDto } from "@/shared/model"; import { TextShimmer } from "@/shared/ui"; +import { SectionCollapsible } from "@/shared/ui"; import { attachService } from "@/entities/attach"; import { useEsntlAtchFileMngTree } from "@/entities/esntl"; import { @@ -29,9 +30,7 @@ import { type FieldPrefix, isEtcFileKey, toFileAccept, -} from "../lib/attachments-form-utils"; - -import { SectionCollapsible } from "./section-collapsible"; +} from "../lib/attachments-form-utils"; interface AttachmentsFormData { esntlAtchFileList: Array<{ diff --git a/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx index 51d7a82a1..969a4613b 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/business-cost-form-section.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo } from "react"; import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useAcntgSeMngTree } from "@/entities/acntg"; import { @@ -17,9 +18,7 @@ import { } from "@common/components"; import { buildAcntgCostItems, recalculateBizCstTotals } from "../lib/business-cost-form-utils"; -import { calcDiffPercent } from "../lib/format-utils"; - -import { SectionCollapsible } from "./section-collapsible"; +import { calcDiffPercent } from "../lib/format-utils"; interface BusinessCostFormData { bizCstList: Array<{ diff --git a/apps/as/src/widgets/pbadms-sprt/ui/business-scale-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/business-scale-form-section.tsx index 63698dd08..2bee5e104 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/business-scale-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/business-scale-form-section.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo } from "react"; import { Controller, useFormContext, useWatch } from "react-hook-form"; +import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useCnstpSeMngTree } from "@/entities/cnstp"; import { @@ -16,9 +17,7 @@ import { TableRow, } from "@common/components"; -import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils"; - -import { SectionCollapsible } from "./section-collapsible"; +import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils"; interface BusinessScaleFormData { bzScaleList: Array<{ diff --git a/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx index 36fb793ae..4b9866877 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/checklist-form-section.tsx @@ -5,6 +5,7 @@ import { Controller, useFormContext, useWatch } from "react-hook-form"; import { useQuery } from "@tanstack/react-query"; +import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldLabel } from "@/shared/ui/field"; import { cklstMngService, type CklstTreeNode } from "@/entities/cklst"; import { useCodeList } from "@/entities/code"; @@ -29,9 +30,7 @@ import { buildChecklistTabs, normalizeTaskSeComCdArray, syncCklstResultListFromTree, -} from "../lib/checklist-form-utils"; - -import { SectionCollapsible } from "./section-collapsible"; +} from "../lib/checklist-form-utils"; type FieldPrefix = "fnrsCnsltnMng" | "instlSncnMng" | "designCnstnMng" | "sewerBscPlan"; diff --git a/apps/as/src/widgets/pbadms-sprt/ui/contact-info-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/contact-info-form-section.tsx index c3dc02fcc..0f5b659a0 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/contact-info-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/contact-info-form-section.tsx @@ -3,8 +3,8 @@ import { Controller, useFormContext } from "react-hook-form"; import { useUserStore } from "@/shared/lib"; +import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldError } from "@/shared/ui/field"; -import { SectionCollapsible } from "@/widgets/pbadms-sprt"; import { Input, Table, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; type FieldPrefix = diff --git a/apps/as/src/widgets/pbadms-sprt/ui/fnrs-shar-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/fnrs-shar-form-section.tsx index 5365db9c9..0ab921ef9 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/fnrs-shar-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/fnrs-shar-form-section.tsx @@ -5,6 +5,7 @@ import { Controller, useFormContext, useWatch } from "react-hook-form"; import { getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; +import { SectionCollapsible } from "@/shared/ui"; import { Field } from "@/shared/ui/field"; import { useAcntgSeMngTree } from "@/entities/acntg"; import type { AtflMngClientResponseDto } from "@/entities/fnrs-cnsltn"; @@ -34,7 +35,6 @@ import { import { calcDiffPercent } from "../lib/format-utils"; import { BizCstTotalDisplayTable } from "./biz-cst-total-display-table"; -import { SectionCollapsible } from "./section-collapsible"; const SHAR_FILE_KEY_PREFIX = "fnrs-shar::"; diff --git a/apps/as/src/widgets/pbadms-sprt/ui/privacy-consent-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/privacy-consent-section.tsx index d51d9aeef..eefd96163 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/privacy-consent-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/privacy-consent-section.tsx @@ -2,6 +2,7 @@ import { Controller, useFormContext } from "react-hook-form"; +import { SectionCollapsible } from "@/shared/ui"; import { Field, FieldError, FieldLabel, FieldSet } from "@/shared/ui/field"; import { RadioGroup, @@ -11,9 +12,7 @@ import { TableHead, TableHeader, TableRow, -} from "@common/components"; - -import { SectionCollapsible } from "./section-collapsible"; +} from "@common/components"; interface PrivacyConsentFormData { privacyConsent: "Y" | "N"; diff --git a/apps/as/src/widgets/pbadms-sprt/ui/progress-status-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/progress-status-section.tsx new file mode 100644 index 000000000..4ce4d8a0a --- /dev/null +++ b/apps/as/src/widgets/pbadms-sprt/ui/progress-status-section.tsx @@ -0,0 +1,88 @@ +"use client"; + +import dayjs from "dayjs"; + +import { DocSttsChgHstryResponseDto } from "@/shared/model/pbadms-sprt"; +import { SectionCollapsible } from "@/shared/ui"; +import { useElaprDocPrgrsByDocNo } from "@/entities/elapr"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@common/components"; + +import { resolveProgressStatusItem } from "../lib/progress-status-utils"; + +interface ProgressStatusColumn { + label: string; + sttsComCd: string; +} + +interface ProgressStatusItem { + rgtrNm: string; + regDt: string; +} + +interface ProgressStatusSectionProps { + columns: ProgressStatusColumn[]; + sttsChgHstryList: DocSttsChgHstryResponseDto[]; + elaprDocNo: string; +} + +interface ProgressStatusCellProps { + items: ProgressStatusItem[]; +} + +function ProgressStatusCell({ items }: ProgressStatusCellProps) { + if (items.length === 0) return "-"; + + return ( +
+ {items.map((item, index) => ( +
+ {item.rgtrNm || "-"} + {item.regDt && ( +

+ {dayjs(item.regDt).format("YYYY-MM-DD")} +

+ )} +
+ ))} +
+ ); +} + +function ProgressStatusSection({ + columns, + sttsChgHstryList, + elaprDocNo, +}: ProgressStatusSectionProps) { + const { data: elaprDocPrgrsList = [] } = useElaprDocPrgrsByDocNo(elaprDocNo); + + return ( + + + + + {columns.map((column) => ( + {column.label} + ))} + + + + + {columns.map((column) => ( + + + + ))} + + +
+
+ ); +} + +export { ProgressStatusSection }; diff --git a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx index 6a8d94670..a8efff36d 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/splmnt-dmnd-form-section.tsx @@ -7,6 +7,7 @@ import { Paperclip } from "lucide-react"; import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib"; import { TextShimmer } from "@/shared/ui"; +import { SectionCollapsible } from "@/shared/ui"; import { attachService, useDownloadAttachmentByAtflCd } from "@/entities/attach"; import { Badge, @@ -20,8 +21,6 @@ import { Textarea, } from "@common/components"; -import { SectionCollapsible } from "./section-collapsible"; - const SPLMNT_CMPTN_FILE_KEY_PREFIX = "splmnt-cmptn::"; interface UploadedSplmntFile { diff --git a/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx b/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx index 8c7d6a8c7..1da222fdf 100644 --- a/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx +++ b/apps/as/src/widgets/pbadms-sprt/ui/supplement-demand-section.tsx @@ -4,6 +4,7 @@ import { Paperclip } from "lucide-react"; import { dayjs } from "@/shared/lib"; import type { SplmntDmndResponseExtension, SplmntDmndSaveBase } from "@/shared/model"; +import { SectionCollapsible } from "@/shared/ui"; import { useDownloadAttachmentByAtflCd } from "@/entities/attach"; import { Badge, @@ -15,8 +16,6 @@ import { TableRow, } from "@common/components"; -import { SectionCollapsible } from "./section-collapsible"; - type SplmntDmndItem = SplmntDmndSaveBase & SplmntDmndResponseExtension; interface SupplementDemandSectionProps { diff --git a/apps/st/src/components/css/DataGrid.css b/apps/st/src/components/css/DataGrid.css index 2f3519372..f4a2020f0 100644 --- a/apps/st/src/components/css/DataGrid.css +++ b/apps/st/src/components/css/DataGrid.css @@ -1,5 +1,11 @@ @tailwind components; +/* RealGrid 기본은 white-space: nowrap → max-width만 주면 잘림. 너비 초과 시에만 줄바꿈 */ +.rg-tooltip { + max-width: 60rem; + white-space: pre-line; +} + @layer components { .rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell { @apply !bg-white; diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index aba00d672..896659c00 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -86,6 +86,40 @@ function mergeGridRowsWithSourceMetadata( }); } +/** row.verificationResult 필드별 오류 배열이 하나라도 있으면 true */ +function rowHasVerificationError( + row: Record, +): boolean { + const verification = ( + row as { verificationResult?: Record } + ).verificationResult; + if (!verification || typeof verification !== "object") return false; + for (const value of Object.values(verification)) { + if (Array.isArray(value) && value.length > 0) return true; + } + return false; +} + +function rowsHaveVerificationErrors( + rows: Record[], +): boolean { + return rows.some(rowHasVerificationError); +} + +function sortRowsVerifyErrorsFirst( + rows: Record[], +): Record[] { + return rows + .map((row, index) => ({ row, index })) + .sort((a, b) => { + const aErr = rowHasVerificationError(a.row) ? 0 : 1; + const bErr = rowHasVerificationError(b.row) ? 0 : 1; + if (aErr !== bErr) return aErr - bErr; + return a.index - b.index; + }) + .map(({ row }) => row); +} + function applyCellStyleCallbacksToColumns( gridView: unknown, columns: ColumnConfig[], @@ -804,6 +838,12 @@ const DataGrid = ({ const emptyCellHoverRafRef = useRef(null); const [emptyCellHoverTooltip, setEmptyCellHoverTooltip] = useState(null); + /** 검증 오류 행 우선보기 (오류 있는 데이터일 때만 UI 노출) */ + const [preferVerifyErrors, setPreferVerifyErrors] = useState(false); + const verifyPriorityBaselineRef = useRef< + Record[] | null + >(null); + const skipVerifyPriorityResetRef = useRef(false); columnsRef.current = columns; rowDataRef.current = rowData; cellStyleCallbackRef.current = cellStyleCallback; @@ -814,6 +854,90 @@ const DataGrid = ({ onCellEditedRef.current = onCellEdited; onDataCellClickRef.current = onDataCellClick; + const hasVerificationErrors = rowsHaveVerificationErrors(rowData); + const showVerifyPriorityToggle = hasVerificationErrors; + + // 조회 등으로 rowData가 교체되면 우선보기 해제 (자체 setRowData는 스킵) + useEffect(() => { + if (skipVerifyPriorityResetRef.current) { + skipVerifyPriorityResetRef.current = false; + return; + } + verifyPriorityBaselineRef.current = null; + setPreferVerifyErrors(false); + }, [rowData]); + + // 오류가 없어지면 체크 해제 + 원본 순서 복원 + useEffect(() => { + if (hasVerificationErrors || !preferVerifyErrors) return; + const baseline = verifyPriorityBaselineRef.current; + verifyPriorityBaselineRef.current = null; + setPreferVerifyErrors(false); + if (baseline) { + skipVerifyPriorityResetRef.current = true; + setRowData(baseline); + } + }, [hasVerificationErrors, preferVerifyErrors, setRowData]); + + const collectCurrentRowsWithMetadata = () => { + const fieldNames = columns + .map((c) => c.fieldName) + .filter(Boolean) as string[]; + const gv = gridRef.current?.gridView as + | { + getDataSource?: () => { + getRowCount(): number; + getValue(r: number, f: string): unknown; + }; + commit?: (force?: boolean) => boolean; + } + | undefined; + if (!gv?.getDataSource) { + return rowDataRef.current.map((r) => ({ ...r })); + } + try { + gv.commit?.(); + } catch { + /* ignore */ + } + const ds = gv.getDataSource(); + if (!ds) return rowDataRef.current.map((r) => ({ ...r })); + const cnt = ds.getRowCount(); + const rows: Record[] = []; + for (let i = 0; i < cnt; i++) { + const r: Record = {}; + for (const f of fieldNames) + r[f] = ds.getValue(i, f) as string | number; + rows.push(r); + } + return mergeGridRowsWithSourceMetadata( + rows, + rowDataRef.current, + fieldNames, + ); + }; + + const handlePreferVerifyErrorsChange = (checked: boolean) => { + if (checked) { + const current = collectCurrentRowsWithMetadata(); + if (!rowsHaveVerificationErrors(current)) return; + verifyPriorityBaselineRef.current = current.map((r) => ({ ...r })); + sortDirRef.current = {}; + originalRowsRef.current = {}; + skipVerifyPriorityResetRef.current = true; + setPreferVerifyErrors(true); + setRowData(sortRowsVerifyErrorsFirst(current)); + return; + } + const baseline = verifyPriorityBaselineRef.current; + verifyPriorityBaselineRef.current = null; + skipVerifyPriorityResetRef.current = true; + setPreferVerifyErrors(false); + if (baseline) { + setRowData(baseline); + } + }; + // rowData 변경 시 편집 중인 셀을 commit한 뒤 수동으로 setRows (RealGridReact에 rows prop을 넘기지 않음) const rowDataMountedRef = useRef(false); useEffect(() => { @@ -2086,60 +2210,79 @@ useEffect(() => { onChange={handleFileChange} />
- {statusInfo && ( -
- - 진행상황:{" "} - {statusInfo.laeSbmsnSttsCd === null || - statusInfo.laeSbmsnSttsCd === "0" ? ( - "미작성" - ) : statusInfo.laeSbmsnSttsCd === "1" ? ( - 작성중 - ) : statusInfo.laeSbmsnSttsCd === "2" ? ( - 제출 - ) : statusInfo.laeSbmsnSttsCd === "3" ? ( - 승인 - ) : statusInfo.laeSbmsnSttsCd === "4" ? ( - - 반려 - - - +
+ {statusInfo && ( +
+ + 진행상황:{" "} + {statusInfo.laeSbmsnSttsCd === null || + statusInfo.laeSbmsnSttsCd === "0" ? ( + "미작성" + ) : statusInfo.laeSbmsnSttsCd === "1" ? ( + 작성중 + ) : statusInfo.laeSbmsnSttsCd === "2" ? ( + 제출 + ) : statusInfo.laeSbmsnSttsCd === "3" ? ( + 승인 + ) : statusInfo.laeSbmsnSttsCd === "4" ? ( + + 반려 + + + + - - ) : ( - 검증중 - )} - - - 반려사유:{" "} - {statusInfo.rjctOpnnCn - ? statusInfo.rjctOpnnCn - : "-"} - - - 제출일:{" "} - {statusInfo.laeSbmsnYmd - ? statusInfo.laeSbmsnYmd.length === 8 && - /^\d{8}$/.test(statusInfo.laeSbmsnYmd) - ? `${statusInfo.laeSbmsnYmd.slice(0, 4)}-${statusInfo.laeSbmsnYmd.slice(4, 6)}-${statusInfo.laeSbmsnYmd.slice(6, 8)}` - : statusInfo.laeSbmsnYmd - : "미제출"} - - - 담당자:{" "} - {statusInfo.laePicNm ? statusInfo.laePicNm : "-"} - - - 연락처:{" "} - {statusInfo.laePicTelNo - ? statusInfo.laePicTelNo - : "-"} - -
- )} + ) : ( + 검증중 + )} + + + 반려사유:{" "} + {statusInfo.rjctOpnnCn + ? statusInfo.rjctOpnnCn + : "-"} + + + 제출일:{" "} + {statusInfo.laeSbmsnYmd + ? statusInfo.laeSbmsnYmd.length === 8 && + /^\d{8}$/.test(statusInfo.laeSbmsnYmd) + ? `${statusInfo.laeSbmsnYmd.slice(0, 4)}-${statusInfo.laeSbmsnYmd.slice(4, 6)}-${statusInfo.laeSbmsnYmd.slice(6, 8)}` + : statusInfo.laeSbmsnYmd + : "미제출"} + + + 담당자:{" "} + {statusInfo.laePicNm ? statusInfo.laePicNm : "-"} + + + 연락처:{" "} + {statusInfo.laePicTelNo + ? statusInfo.laePicTelNo + : "-"} + +
+ )} + {showVerifyPriorityToggle && ( + + )} +
{isExcelImport && ( +
+ +
+ 조건연산자는 규칙 전체에서 1개만 선택할 + 수 있습니다. 조건연산자를 기준으로 + 앞쪽은 기준 대상, 뒤쪽은 비교대상으로 + 구분됩니다. + {conditionBoundary === -1 && ( + + {" "} + 현재 조건연산자가 선택되지 않아 + 전체가 비교대상과 동일하게 + 처리됩니다. + + )} +
-
+ +
-
-
-
- : + ) + ) : (
- } + )}
{ setShowFomulaDialog(open); @@ -369,4 +636,4 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => { ); }; -export default DetailComponent; \ No newline at end of file +export default DetailComponent; diff --git a/apps/st/src/components/statistics/verification/generalVerification/FormulaComponents.tsx b/apps/st/src/components/statistics/verification/generalVerification/FormulaComponents.tsx index d1e7f7940..90dcedc06 100644 --- a/apps/st/src/components/statistics/verification/generalVerification/FormulaComponents.tsx +++ b/apps/st/src/components/statistics/verification/generalVerification/FormulaComponents.tsx @@ -1,41 +1,83 @@ -import { Button, Input } from "@common/components";; -import OperatorsComponents from "./OperatorsComponents"; +import { Button, Input } from "@common/components"; + import type { VerifyDetailResponseDto } from "@/entities/statistics/model/general-verification-interface"; +import OperatorsComponents from "./OperatorsComponents"; + +type ClauseZone = "" | "zone-condition" | "zone-result"; + interface FormulaComponentsProps { data?: VerifyDetailResponseDto | null; index?: number; + zone?: ClauseZone; + groupBy?: boolean; + onGroupByChange?: (checked: boolean) => void; onDelete?: () => void; onOperatorChange?: (value: string) => void; - handleFomulaDialog?: (show: boolean, data?: VerifyDetailResponseDto | null, index?: number) => void; + handleFomulaDialog?: ( + show: boolean, + data?: VerifyDetailResponseDto | null, + index?: number, + ) => void; } -const FormulaComponents = ({ data, index, onDelete, onOperatorChange, handleFomulaDialog }: FormulaComponentsProps) => { +const zoneClassName: Record = { + "": "bg-[#f9fafb]", + "zone-condition": "bg-[#eef4ff]", + "zone-result": "bg-[#fff4ea]", +}; + +const FormulaComponents = ({ + data, + index, + zone = "", + groupBy = false, + onGroupByChange, + onDelete, + onOperatorChange, + handleFomulaDialog, +}: FormulaComponentsProps) => { const cmprOpert = data?.cmprOpert; return ( -
+
handleFomulaDialog?.(true, data, index)} readOnly /> - +
+ +
+
); }; -export default FormulaComponents; \ No newline at end of file +export default FormulaComponents; diff --git a/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx b/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx index 84c24aec4..9775a7b73 100644 --- a/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx +++ b/apps/st/src/components/statistics/verification/generalVerification/RegistDialog.tsx @@ -384,9 +384,8 @@ const RegistDialog = memo( - 공통 검증 - 도단위 검증 - 시단위 검증 + 총계 검증 + 행 검증 diff --git a/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts b/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts index 7dbda56e5..9d2067de0 100644 --- a/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts +++ b/apps/st/src/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactColumn.ts @@ -50,9 +50,9 @@ export const PublicSewageFactColumn = { { name: 'prcsQnt', fieldName: 'prcsQnt', header: { text: '방류량(500m³/일 이상/미만)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, // 방류량 - (m³/일) - { name: 'prcsPhysCpct', fieldName: 'prcsPhysCpct', header: { text: '물리적' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, - { name: 'prcsBoilCpct', fieldName: 'prcsBoilCpct', header: { text: '생물학적' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, - { name: 'prcsAltdCpct', fieldName: 'prcsAltdCpct', header: { text: '고도' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, + { name: 'prcsPhysCpct', fieldName: 'prcsPhysCpct', header: { text: '물리적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'prcsBoilCpct', fieldName: 'prcsBoilCpct', header: { text: '생물학적' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, + { name: 'prcsAltdCpct', fieldName: 'prcsAltdCpct', header: { text: '고도' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' }, // 수질 { name: 'inflwBodVl', fieldName: 'inflwBodVl', header: { text: '유입BOD' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' }, diff --git a/apps/st/src/components/util/grid/columnGuides.ts b/apps/st/src/components/util/grid/columnGuides.ts new file mode 100644 index 000000000..d230bb948 --- /dev/null +++ b/apps/st/src/components/util/grid/columnGuides.ts @@ -0,0 +1,28 @@ +import { statisticsApi } from "@/entities/statistics/api/statistics-service"; + +/** column-guides API 응답 → fieldName(engColNm) → guide 텍스트 */ +export function mapColumnGuideListToRecord( + data: { engColNm?: string; columnGuide?: string }[], +): Record { + const next: Record = {}; + for (const item of data) { + const key = item.engColNm?.trim(); + const guide = item.columnGuide?.trim(); + if (key && guide) next[key] = guide; + } + return next; +} + +export async function fetchColumnGuidesRecord( + statsCd: string, +): Promise> { + try { + const res = await statisticsApi.getColumnGuides(statsCd); + if (res.status !== "success" || !Array.isArray(res.data)) { + return {}; + } + return mapColumnGuideListToRecord(res.data); + } catch { + return {}; + } +} diff --git a/apps/st/src/components/util/grid/computedFieldCallbacks.ts b/apps/st/src/components/util/grid/computedFieldCallbacks.ts index 0506bee8a..231234e86 100644 --- a/apps/st/src/components/util/grid/computedFieldCallbacks.ts +++ b/apps/st/src/components/util/grid/computedFieldCallbacks.ts @@ -14,6 +14,16 @@ function truncateToFirstDecimal(value: number): number { return Math.floor(value * 10) / 10; } +/** + * IEEE 754 가산 잡음 제거. + * 통계 표시 자릿수(대개 0~3)보다 충분히 큰 자리로 반올림해 + * 112.229999… → 112.23 처럼 만들어 RealGrid `#,##0.000` 표시 오차를 막는다. + */ +function sanitizeFloatNoise(value: number): number { + if (!Number.isFinite(value)) return 0; + return Math.round(value * 1e6) / 1e6; +} + /** 합계용 valueCallback 생성 */ export function createSumValueCallback(fieldNames: string[]): DataFieldValueCallback { return (_prod, _dataRow, _fieldName, fieldNamesArr, values) => { @@ -22,7 +32,7 @@ export function createSumValueCallback(fieldNames: string[]): DataFieldValueCall const idx = fieldNamesArr.indexOf(f); if (idx >= 0) sum += toNum(values[idx]); } - return sum; + return sanitizeFloatNoise(sum); }; } diff --git a/apps/st/src/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface.ts b/apps/st/src/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface.ts index d58f3c41e..c13243912 100644 --- a/apps/st/src/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface.ts +++ b/apps/st/src/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface.ts @@ -121,7 +121,7 @@ export interface SewerageSupplyRateSaveRequest { sggNm: string; sewerBzmnCd: string; userId: string; - sewerBzmnFrgnrWholPpltnCnt?: number; + sewerBzmnArea?: number; saveList?: SewerageSupplyRateSaveItem[]; submitList?: SewerageSupplyRateSaveItem[]; laePicNm?: string;