From bcf5d3caec0988ec48d2b13153dd060f14bf4a65 Mon Sep 17 00:00:00 2001 From: kiminchan Date: Thu, 30 Jul 2026 16:53:30 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A1=B0=EC=82=AC=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=ED=8C=9D=EC=97=85=20UI=EC=99=80=20=EA=B4=80=EB=A1=9C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EB=93=B1=EA=B8=89=20=ED=91=9C=EC=8B=9C=20?= =?UTF-8?q?=EB=B3=B4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/ctpvSggPrcsFcltSelectBox.tsx | 7 +- .../001-fe-SFR-010-004~007-조사사업-작업카드.md | 7 ++ .../exmn/lib/exmn-business-validation.ts | 2 +- .../exmn/ui/ExmnBusinessRegisterPopupPage.tsx | 26 ++++--- .../features/exmn/ui/ExmnRegistrationPage.tsx | 2 +- .../exmn/ui/ExmnResultRegisterPopupPage.tsx | 58 ++++++++------- .../exmn/ui/ExmnSurveyDetailPopupPage.tsx | 70 +++++++------------ 7 files changed, 88 insertions(+), 84 deletions(-) diff --git a/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx b/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx index 238cf83ee..ee48a048f 100644 --- a/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx +++ b/apps/sn/app/components/ui/ctpvSggPrcsFcltSelectBox.tsx @@ -39,6 +39,8 @@ interface CtpvSggPrcsFcltSelectBoxProps { setIsLoading?: (isLoading: boolean) => void; /** 좁은 조회·입력 행에서 시도·시군구 선택 폭과 간격을 줄여 표시한다. */ compact?: boolean; + /** 화면별 배경에 맞춰 시도·시군구 선택값의 글자색 등을 직접 지정한다. */ + selectValueClassName?: string; } const defaultOption: ApiSelectOption = { val: '0', text: '전체' }; @@ -62,6 +64,7 @@ export function CtpvSggPrcsFcltSelectBox({ defaultSwtrmtFcltNm, setIsLoading, compact = false, + selectValueClassName = "", }: CtpvSggPrcsFcltSelectBoxProps) { const [prcsFcltOptions, setPrcsFcltOptions] = useState([]); @@ -138,7 +141,7 @@ export function CtpvSggPrcsFcltSelectBox({ 시군구 opt.val.startsWith(sidoPrefix))]} value={sigungu} diff --git a/apps/sn/docs/작업계획/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-작업카드.md b/apps/sn/docs/작업계획/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-작업카드.md index a734dac40..0a78d2738 100644 --- a/apps/sn/docs/작업계획/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-작업카드.md +++ b/apps/sn/docs/작업계획/SFR-010-004~SFR-010-007-조사사업/001-fe-SFR-010-004~007-조사사업-작업카드.md @@ -280,3 +280,10 @@ - `ExmnSurveyDetailPopupPage`의 데이터행과 정렬값은 `drainageTypeName || drainageType`, `pipeTypeName || pipeType`으로 구성한다. - 미등록 코드도 빈칸으로 숨기지 않고 코드 원문을 표시한다. - 완료 게이트는 관로·맨홀 전체·구조·운영 상세 화면에서 코드가 한글 명칭으로 표시되고, 명칭 정렬과 서버 XLSX가 동일한 값을 사용하는 것이다. + +## 22. 2026-07-30 관로 상세보기 식별자·등급 열 보정 + +- 상세보기 조회 조건의 `관리번호`, `관리번호 입력` 문구를 `관로번호`, `관로번호 입력`으로 통일한다. +- 관로 전체 상세표의 고정 열에 `구조적 내부결함`, `운영적 내부결함` 등급을 다시 포함한다. +- 화면은 상세 API의 `structuralGrade`, `operationalGrade`를 사용하며 프론트에서 등급 산정 공식을 중복 구현하지 않는다. +- 관로·맨홀의 데이터행, 소계·합계와 클라이언트 상세 Excel에 두 등급 열을 동일하게 구성한다. diff --git a/apps/sn/src/features/exmn/lib/exmn-business-validation.ts b/apps/sn/src/features/exmn/lib/exmn-business-validation.ts index 442d5485c..fee3a4a76 100644 --- a/apps/sn/src/features/exmn/lib/exmn-business-validation.ts +++ b/apps/sn/src/features/exmn/lib/exmn-business-validation.ts @@ -8,7 +8,7 @@ export const EXMN_MAX_FILE_SIZE = 200 * 1024 * 1024; /** 조사사업 파일별 화면 표시명과 허용 확장자다. */ export const EXMN_FILE_RULES: Record = { - EXMN_EXCEL: { label: "조사 결과", extensions: ["xlsx", "xlsm"] }, + EXMN_EXCEL: { label: "조사 결과", extensions: ["xlsx"] }, PPL_EXMN_PDF: { label: "CCTV 조사 결과(관로)", extensions: ["pdf"] }, MNHL_EXMN_PDF: { label: "CCTV 조사 결과(맨홀)", extensions: ["pdf"] }, BIZ_REPORT: { label: "사업보고서", extensions: ["pdf"] }, diff --git a/apps/sn/src/features/exmn/ui/ExmnBusinessRegisterPopupPage.tsx b/apps/sn/src/features/exmn/ui/ExmnBusinessRegisterPopupPage.tsx index 63a2374f6..b722efb90 100644 --- a/apps/sn/src/features/exmn/ui/ExmnBusinessRegisterPopupPage.tsx +++ b/apps/sn/src/features/exmn/ui/ExmnBusinessRegisterPopupPage.tsx @@ -27,6 +27,10 @@ import type { TrtdstSettingDto } from "../model/trtdst-types"; const DOWNLOAD_BUTTON_CLASS = "rounded-sm px-4 py-2 text-sm font-medium !text-white disabled:!text-white"; +const FORM_LABEL_CLASS = + "shrink-0 whitespace-nowrap text-xl font-medium text-gray-600"; +const FORM_INPUT_CLASS = + "h-[3.2rem] text-base text-label"; type Notice = { type: "success" | "error" | "info"; text: string } | null; @@ -334,7 +338,7 @@ export function ExmnBusinessRegisterPopupPage({ diff --git a/apps/sn/src/features/exmn/ui/ExmnResultRegisterPopupPage.tsx b/apps/sn/src/features/exmn/ui/ExmnResultRegisterPopupPage.tsx index da45702ab..6383c618f 100644 --- a/apps/sn/src/features/exmn/ui/ExmnResultRegisterPopupPage.tsx +++ b/apps/sn/src/features/exmn/ui/ExmnResultRegisterPopupPage.tsx @@ -26,8 +26,12 @@ import type { ExmnImportValidationResult, } from "../model/exmn-business-types"; -const ACTION_BUTTON_CLASS = - "rounded-sm bg-blue-600 px-3 py-1.5 text-sm font-medium !text-white hover:bg-blue-700 disabled:!text-white"; +const POPUP_BUTTON_SIZE_CLASS = + "h-10 w-[9.6rem] rounded-sm px-4 !text-[1.2rem] font-medium"; +const POPUP_ACTION_BUTTON_CLASS = + `${POPUP_BUTTON_SIZE_CLASS} !text-white disabled:!text-white`; +const FIND_BUTTON_CLASS = + "inline-flex h-10 cursor-pointer items-center justify-center rounded-sm bg-blue-600 px-3 !text-[1.2rem] font-medium !text-white hover:bg-blue-700"; const FILE_TYPES: ExmnFileType[] = [ "EXMN_EXCEL", @@ -375,8 +379,8 @@ export function ExmnResultRegisterPopupPage({ + ) : -} @@ -507,8 +511,8 @@ export function ExmnResultRegisterPopupPage({ {visibleValidationTypes.length > 0 && (
-

데이터 검토 내역

- 파일별로 실제 확인한 항목입니다. +

데이터 검토 내역

+ 파일별로 실제 확인한 항목입니다.
{visibleValidationTypes.map((fileType) => { const status = fileValidationStatuses[fileType]!; @@ -523,10 +527,10 @@ export function ExmnResultRegisterPopupPage({ return (
- + {EXMN_FILE_RULES[fileType].label}
{status === "success" && countText && ( -

{countText}

+

{countText}

)} -

+

{guide?.checkedItems.join(" · ")}

{guide?.pendingNote && ( @@ -560,7 +564,7 @@ export function ExmnResultRegisterPopupPage({ )} {uploadStatus && ( -

{uploadStatus}

+

{uploadStatus}

)}
diff --git a/apps/sn/src/features/exmn/ui/ExmnSurveyDetailPopupPage.tsx b/apps/sn/src/features/exmn/ui/ExmnSurveyDetailPopupPage.tsx index cf6d79bbd..a1ca3c7c9 100644 --- a/apps/sn/src/features/exmn/ui/ExmnSurveyDetailPopupPage.tsx +++ b/apps/sn/src/features/exmn/ui/ExmnSurveyDetailPopupPage.tsx @@ -68,7 +68,6 @@ const FIXED_COLUMNS = [ const REPORT_COLUMN_LEFT = 0; const PIPE_COLUMN_LEFT = FIXED_COLUMNS[0].width; const STICKY_COLUMN_COUNT = 2; -const PPL_FIXED_COLUMN_COUNT = 9; const STICKY_DIVIDER_SHADOW = "2px 0 0 #b8c5d1"; const SEVERITY_COLUMN_WIDTH = 52; type ExmnDetailFixedColumnId = (typeof FIXED_COLUMNS)[number]["id"]; @@ -185,13 +184,8 @@ export function ExmnSurveyDetailPopupPage({ ); const selectedTrtdstNm = selectedDistrict?.trtdstNm ?? (selectedTrtdstId === trtdstId ? trtdstNm : ""); - /** 관로는 등급을 본단위구간에서 표시하고, 맨홀 전체표만 구조·운영 등급 열을 추가한다. */ - const fixedColumns = useMemo( - () => jobType === "PPL" - ? FIXED_COLUMNS.slice(0, PPL_FIXED_COLUMN_COUNT) - : FIXED_COLUMNS, - [jobType], - ); + /** 관로·맨홀 모두 확정 계산된 구조·운영 내부결함 등급을 공통 고정 열로 표시한다. */ + const fixedColumns = FIXED_COLUMNS; const summaryLabelColumnCount = 7; const fixedInfoScrollThreshold = useMemo( () => fixedColumns @@ -238,7 +232,7 @@ export function ExmnSurveyDetailPopupPage({ row.pipeNo.toLocaleLowerCase("ko-KR").includes(normalizedKeyword), ); }, [appliedDetailNumberKeyword, scopedRows]); - /** 본단위구간 최초 진입은 전체를 표시하고, 관리번호가 있으면 부분 일치로 좁힌다. */ + /** 본단위구간 최초 진입은 전체를 표시하고, 관로번호가 있으면 부분 일치로 좁힌다. */ const unitRows = useMemo(() => { if (!isUnitMode) return []; const normalizedKeyword = appliedDetailNumberKeyword.trim().toLocaleLowerCase("ko-KR"); @@ -401,7 +395,7 @@ export function ExmnSurveyDetailPopupPage({ setNotice(null); }; - /** 모드를 바꾸면 이전 표의 관리번호·정렬·안내 상태를 초기화한다. */ + /** 모드를 바꾸면 이전 표의 관로번호·정렬·안내 상태를 초기화한다. */ const handleDetailModeChange = (nextMode: ExmnPplDetailMode) => { if (nextMode === detailMode) return; setDetailMode(nextMode); @@ -456,16 +450,12 @@ export function ExmnSurveyDetailPopupPage({ [rows], ); - /** 고정 열의 합산값이며 구조·운영 등급 합계는 맨홀 전체표에만 표시한다. */ + /** 고정 열의 합산값이며 관로·맨홀 모두 구조·운영 등급 합계를 표시한다. */ const fixedSummaryValues = [ { key: "extensionM", value: formatNumber(totals.extensionM) }, { key: "drivingDistanceM", value: formatNumber(totals.drivingDistanceM) }, - ...(jobType === "MNHL" - ? [ - { key: "structuralGrade", value: formatNumber(totals.structuralGrade) }, - { key: "operationalGrade", value: formatNumber(totals.operationalGrade) }, - ] - : []), + { key: "structuralGrade", value: formatNumber(totals.structuralGrade) }, + { key: "operationalGrade", value: formatNumber(totals.operationalGrade) }, ]; /** 현재 보이는 구조·운영 결함 열만 기준으로 대·중·소 소계와 항목별 합계를 계산한다. */ @@ -584,12 +574,10 @@ export function ExmnSurveyDetailPopupPage({ extensionM: "연장(m)", drivingDistanceM: "주행거리(m)", }; - if (jobType === "MNHL") { - Object.assign(detailHeaderMapping, { - structuralGrade: "구조적 내부결함", - operationalGrade: "운영적 내부결함", - }); - } + Object.assign(detailHeaderMapping, { + structuralGrade: "구조적 내부결함", + operationalGrade: "운영적 내부결함", + }); Object.assign(detailHeaderMapping, { anomalyL: "이상항목 합계-대", anomalyM: "이상항목 합계-중", @@ -615,12 +603,10 @@ export function ExmnSurveyDetailPopupPage({ extensionM: row.extensionM, drivingDistanceM: row.drivingDistanceM, }; - if (jobType === "MNHL") { - Object.assign(result, { - structuralGrade: row.structuralGrade, - operationalGrade: row.operationalGrade, - }); - } + Object.assign(result, { + structuralGrade: row.structuralGrade, + operationalGrade: row.operationalGrade, + }); Object.assign(result, { anomalyL: anomalyTotals.L, anomalyM: anomalyTotals.M, @@ -656,16 +642,14 @@ export function ExmnSurveyDetailPopupPage({ extensionM: totals.extensionM, drivingDistanceM: totals.drivingDistanceM, }; - if (jobType === "MNHL") { - Object.assign(subtotalRow, { - structuralGrade: totals.structuralGrade, - operationalGrade: totals.operationalGrade, - }); - Object.assign(totalRow, { - structuralGrade: totals.structuralGrade, - operationalGrade: totals.operationalGrade, - }); - } + Object.assign(subtotalRow, { + structuralGrade: totals.structuralGrade, + operationalGrade: totals.operationalGrade, + }); + Object.assign(totalRow, { + structuralGrade: totals.structuralGrade, + operationalGrade: totals.operationalGrade, + }); Object.assign(subtotalRow, { anomalyL: defectSummary.anomalySubtotals.L, anomalyM: defectSummary.anomalySubtotals.M, @@ -799,11 +783,11 @@ export function ExmnSurveyDetailPopupPage({