조사관리 팝업 UI와 관로 상세 등급 표시 보정

This commit is contained in:
kiminchan
2026-07-30 16:53:30 +09:00
parent 474676af17
commit bcf5d3caec
7 changed files with 88 additions and 84 deletions
@@ -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<ApiSelectOption[]>([]);
@@ -138,7 +141,7 @@ export function CtpvSggPrcsFcltSelectBox({
<Label className={`${compact ? "mr-1.5 text-base" : "mr-2 text-xl"} whitespace-nowrap font-medium text-gray-600`}>시도</Label>
<SelectBox
className={compact ? "mr-4 w-[180px]" : "mr-8 w-48"}
selectClass={`${compact ? "w-[180px]" : ""} rounded-lg bg-white`}
selectClass={`${compact ? "w-[180px]" : ""} rounded-lg bg-white ${selectValueClassName}`}
label=""
selectArray={[defaultOption, ...ctpv]}
value={sido}
@@ -152,7 +155,7 @@ export function CtpvSggPrcsFcltSelectBox({
<Label className={`${compact ? "mr-1.5 text-base" : "mr-2 text-xl"} whitespace-nowrap font-medium text-gray-600`}>시군구</Label>
<SelectBox
className={compact ? "w-36" : "w-48"}
selectClass={`${compact ? "w-36" : "w-48"} truncate rounded-lg bg-white`}
selectClass={`${compact ? "w-36" : "w-48"} truncate rounded-lg bg-white ${selectValueClassName}`}
label=""
selectArray={[defaultOption, ...sgg.filter(opt => opt.val.startsWith(sidoPrefix))]}
value={sigungu}
@@ -280,3 +280,10 @@
- `ExmnSurveyDetailPopupPage`의 데이터행과 정렬값은 `drainageTypeName || drainageType`, `pipeTypeName || pipeType`으로 구성한다.
- 미등록 코드도 빈칸으로 숨기지 않고 코드 원문을 표시한다.
- 완료 게이트는 관로·맨홀 전체·구조·운영 상세 화면에서 코드가 한글 명칭으로 표시되고, 명칭 정렬과 서버 XLSX가 동일한 값을 사용하는 것이다.
## 22. 2026-07-30 관로 상세보기 식별자·등급 열 보정
- 상세보기 조회 조건의 `관리번호`, `관리번호 입력` 문구를 `관로번호`, `관로번호 입력`으로 통일한다.
- 관로 전체 상세표의 고정 열에 `구조적 내부결함`, `운영적 내부결함` 등급을 다시 포함한다.
- 화면은 상세 API의 `structuralGrade`, `operationalGrade`를 사용하며 프론트에서 등급 산정 공식을 중복 구현하지 않는다.
- 관로·맨홀의 데이터행, 소계·합계와 클라이언트 상세 Excel에 두 등급 열을 동일하게 구성한다.
@@ -8,7 +8,7 @@ export const EXMN_MAX_FILE_SIZE = 200 * 1024 * 1024;
/** 조사사업 파일별 화면 표시명과 허용 확장자다. */
export const EXMN_FILE_RULES: Record<ExmnFileType, { label: string; extensions: string[] }> = {
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"] },
@@ -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({
<Button
type="button"
color="white"
size="xs"
size="sm"
className="sn-popup-header-button"
disabled={isLoading || formLocked}
onClick={() => void handleSave()}
@@ -362,11 +366,12 @@ export function ExmnBusinessRegisterPopupPage({
<div className="space-y-4 p-4">
<div className="flex flex-nowrap items-center gap-5">
<div className="flex items-center gap-2">
<Label className="shrink-0 whitespace-nowrap text-base">
<Label className={FORM_LABEL_CLASS}>
<span className="text-red-500">* </span>사업차수
</Label>
<Input
style={{ width: "3.5rem", flex: "none" }}
className={FORM_INPUT_CLASS}
style={{ width: "6rem", flex: "none" }}
type="text"
inputMode="numeric"
maxLength={3}
@@ -379,17 +384,17 @@ export function ExmnBusinessRegisterPopupPage({
</div>
<SelectBox
label="사업년도"
labelClass="shrink-0 whitespace-nowrap text-base"
labelClass={FORM_LABEL_CLASS}
className="flex items-center gap-2"
selectClass="w-[6.5rem] bg-white"
selectClass="h-[3.2rem] w-28 rounded-lg bg-white text-label"
selectArray={getStartYearArray({ placeholder: "선택" })}
value={bizYr}
readOnly={formLocked}
onSelectValue={(value) => setBizYr(String(value))}
/>
<CtpvSggPrcsFcltSelectBox
compact
className="!mr-0 !justify-start"
selectValueClassName="text-label"
sido={ctpvCd}
sigungu={sggCd}
onSidoChange={(nextCtpvCd) => {
@@ -409,8 +414,9 @@ export function ExmnBusinessRegisterPopupPage({
}}
/>
<div className="flex items-center gap-2">
<Label className="shrink-0 whitespace-nowrap text-base"><span className="text-red-500">* </span>정밀조사 연장</Label>
<Label className={FORM_LABEL_CLASS}><span className="text-red-500">* </span>정밀조사 연장</Label>
<Input
className={FORM_INPUT_CLASS}
style={{ width: "6rem", flex: "none" }}
type="text"
inputMode="decimal"
@@ -419,12 +425,12 @@ export function ExmnBusinessRegisterPopupPage({
disabled={formLocked}
onChange={(event) => setPrcisExmnPrlg(event.target.value)}
/>
<span className="text-sm text-gray-500">km</span>
<span className="text-base font-medium text-gray-600">km</span>
</div>
</div>
<div className="flex items-center gap-3">
<Label className="w-20 shrink-0 text-base"><span className="text-red-500">* </span>사업명</Label>
<Input style={{ maxWidth: "480px" }} value={bizNm} maxLength={200} disabled={formLocked} onChange={(event) => setBizNm(event.target.value)} />
<Label className={`w-20 ${FORM_LABEL_CLASS}`}><span className="text-red-500">* </span>사업명</Label>
<Input className={FORM_INPUT_CLASS} style={{ maxWidth: "480px" }} value={bizNm} maxLength={200} disabled={formLocked} onChange={(event) => setBizNm(event.target.value)} />
</div>
</div>
</section>
@@ -403,7 +403,7 @@ export function ExmnRegistrationPage() {
/>
<div className="mr-8 flex items-center gap-2">
<Label className="whitespace-nowrap text-xl font-medium text-gray-600">사업명</Label>
<Input className="h-[3.2rem] w-52 text-base" value={draftFilters.bizNm} onChange={(event) => setDraftFilters((current) => ({ ...current, bizNm: event.target.value }))} onKeyDown={(event) => { if (event.key === "Enter") void handleSearch(); }} />
<Input className="h-[3.2rem] w-[32rem] text-base" value={draftFilters.bizNm} onChange={(event) => setDraftFilters((current) => ({ ...current, bizNm: event.target.value }))} onKeyDown={(event) => { if (event.key === "Enter") void handleSearch(); }} />
</div>
<div className="ml-auto flex shrink-0 items-center gap-2">
<Button className={ACTION_BUTTON_CLASS} size="sm" disabled={isLoading} onClick={() => void handleSearch()}>조회</Button>
@@ -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({
<Button
type="button"
color="white"
size="xs"
className="sn-popup-header-button"
size="sm"
className="sn-popup-header-button h-10 w-auto px-3 !text-[1.2rem]"
disabled={
isLoading
|| !excelValidation?.valid
@@ -392,7 +396,7 @@ export function ExmnResultRegisterPopupPage({
<section className="flex min-h-0 flex-1 flex-col gap-2 overflow-x-hidden overflow-y-auto px-4 pb-3 pt-3">
{notice && notice.type !== "success" && (
<div role="alert" className={`whitespace-pre-line rounded border px-4 py-3 text-sm ${
<div role="alert" className={`whitespace-pre-line rounded border px-4 py-3 text-[1.3rem] ${
notice.type === "error"
? "border-red-200 bg-red-50 text-red-700"
: notice.type === "success"
@@ -407,30 +411,30 @@ export function ExmnResultRegisterPopupPage({
>
<div className="flex min-w-0 flex-wrap items-center gap-x-7 gap-y-2">
<div className="flex shrink-0 items-center gap-2">
<span className="whitespace-nowrap text-[1.05rem] font-medium text-label">사업차수</span>
<span className="whitespace-nowrap text-[1.05rem] font-semibold text-foreground">
<span className="whitespace-nowrap text-xl font-medium text-gray-600">사업차수</span>
<span className="whitespace-nowrap text-[1.3rem] font-semibold text-black">
{detail?.bizCycl ?? businessSummary.bizCycl ?? "-"}차
</span>
</div>
<div className="flex shrink-0 items-center gap-2">
<span className="whitespace-nowrap text-[1.05rem] font-medium text-label">사업년도</span>
<span className="whitespace-nowrap text-[1.05rem] font-semibold text-foreground">
<span className="whitespace-nowrap text-xl font-medium text-gray-600">사업년도</span>
<span className="whitespace-nowrap text-[1.3rem] font-semibold text-black">
{detail?.bizYr ?? businessSummary.bizYr ?? "-"}
</span>
</div>
<div className="flex shrink-0 items-center gap-2">
<span className="whitespace-nowrap text-[1.05rem] font-medium text-label">행정구역</span>
<span className="whitespace-nowrap text-xl font-medium text-gray-600">행정구역</span>
<span
className="max-w-[22rem] truncate text-[1.05rem] font-semibold text-foreground"
className="max-w-[22rem] truncate text-[1.3rem] font-semibold text-black"
title={`${detail?.ctpvNm ?? businessSummary.ctpvNm} ${detail?.sggNm ?? businessSummary.sggNm}`}
>
{detail?.ctpvNm ?? businessSummary.ctpvNm} {detail?.sggNm ?? businessSummary.sggNm}
</span>
</div>
<div className="flex min-w-[24rem] flex-1 items-center gap-2">
<span className="shrink-0 whitespace-nowrap text-[1.05rem] font-medium text-label">사업명</span>
<span className="shrink-0 whitespace-nowrap text-xl font-medium text-gray-600">사업명</span>
<span
className="min-w-0 truncate text-[1.05rem] font-semibold text-foreground"
className="min-w-0 truncate text-[1.3rem] font-semibold text-black"
title={detail?.bizNm ?? businessSummary.bizNm}
>
{detail?.bizNm ?? businessSummary.bizNm ?? "-"}
@@ -441,7 +445,7 @@ export function ExmnResultRegisterPopupPage({
<section className="overflow-hidden border border-border">
<div
className="grid items-center gap-3 bg-[#eaf3fb] px-3 py-2.5 text-center text-sm font-semibold text-title"
className="grid items-center gap-3 bg-[#eaf3fb] px-3 py-2.5 text-center text-[1.3rem] font-medium text-label"
style={{ gridTemplateColumns: "180px minmax(0, 1fr) 80px 112px" }}
>
<span>파일 구분</span><span>선택 파일</span><span>찾기</span><span>데이터 검토</span>
@@ -455,15 +459,15 @@ export function ExmnResultRegisterPopupPage({
return (
<div
key={fileType}
className="grid items-center gap-3 border-t border-border px-3 py-3 text-sm"
className="grid items-center gap-3 border-t border-border px-3 py-3 text-[1.3rem] text-label"
style={{ gridTemplateColumns: "180px minmax(0, 1fr) 80px 112px" }}
>
<label htmlFor={inputId} className="font-medium text-gray-700">
{(fileType === "PPL_EXMN_PDF" || fileType === "MNHL_EXMN_PDF") && <span className="text-red-500">* </span>}
<label htmlFor={inputId} className="font-medium text-gray-600">
{(fileType === "EXMN_EXCEL" || fileType === "PPL_EXMN_PDF" || fileType === "MNHL_EXMN_PDF") && <span className="text-red-500">* </span>}
{rule.label}
</label>
<div
className={`flex min-w-0 items-center gap-2 border px-3 py-1.5 ${
className={`flex min-w-0 items-center gap-2 rounded-md border px-3 py-1.5 ${
validationStatus === "success"
? "border-emerald-300 bg-emerald-50 font-semibold text-emerald-700"
: validationStatus === "error"
@@ -492,11 +496,11 @@ export function ExmnResultRegisterPopupPage({
disabled={isLoading}
onChange={(event) => handleFileChange(fileType, event.target.files)}
/>
<label htmlFor={inputId} className="inline-flex cursor-pointer rounded-sm bg-blue-600 px-3 py-1.5 font-medium text-white hover:bg-blue-700">찾기</label>
<label htmlFor={inputId} className={FIND_BUTTON_CLASS}>찾기</label>
</div>
<div className="text-center">
{canValidate ? (
<Button className={ACTION_BUTTON_CLASS} size="sm" disabled={isLoading} onClick={() => void handleValidate(fileType)}>데이터 검토</Button>
<Button color="green" className={POPUP_ACTION_BUTTON_CLASS} size="sm" disabled={isLoading} onClick={() => void handleValidate(fileType)}>데이터 검토</Button>
) : <span className="text-gray-400">-</span>}
</div>
</div>
@@ -507,8 +511,8 @@ export function ExmnResultRegisterPopupPage({
{visibleValidationTypes.length > 0 && (
<section className="overflow-hidden border border-border" aria-label="데이터 검토 내역">
<div className="flex items-center justify-between bg-[#eaf3fb] px-3 py-2.5">
<h2 className="text-sm font-semibold text-title">데이터 검토 내역</h2>
<span className="text-xs text-label">파일별로 실제 확인한 항목입니다.</span>
<h2 className="text-[1.3rem] font-medium text-label">데이터 검토 내역</h2>
<span className="text-base text-label">파일별로 실제 확인한 항목입니다.</span>
</div>
{visibleValidationTypes.map((fileType) => {
const status = fileValidationStatuses[fileType]!;
@@ -523,10 +527,10 @@ export function ExmnResultRegisterPopupPage({
return (
<div
key={fileType}
className="grid items-start gap-3 border-t border-border px-3 py-2.5 text-sm"
className="grid items-start gap-3 border-t border-border px-3 py-2.5 text-[1.3rem] text-label"
style={{ gridTemplateColumns: "180px 88px minmax(0, 1fr)" }}
>
<span className="font-medium text-gray-700">
<span className="font-medium text-gray-600">
{EXMN_FILE_RULES[fileType].label}
</span>
<span className={`inline-flex w-fit rounded-full px-2.5 py-1 text-xs font-semibold ${
@@ -538,9 +542,9 @@ export function ExmnResultRegisterPopupPage({
</span>
<div className="min-w-0">
{status === "success" && countText && (
<p className="mb-1 font-medium text-foreground">{countText}</p>
<p className="mb-1 font-medium text-label">{countText}</p>
)}
<p className="leading-5 text-gray-600">
<p className="leading-5 text-label">
{guide?.checkedItems.join(" · ")}
</p>
{guide?.pendingNote && (
@@ -560,7 +564,7 @@ export function ExmnResultRegisterPopupPage({
)}
{uploadStatus && (
<p className="text-right text-sm text-blue-700">{uploadStatus}</p>
<p className="text-right text-[1.3rem] text-blue-700">{uploadStatus}</p>
)}
</section>
</main>
@@ -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({
</label>
<label className="sn-popup-field" style={{ width: "300px" }}>
<div className="sn-popup-field-label">관리번호</div>
<div className="sn-popup-field-label">관로번호</div>
<Input
className="sn-popup-input !text-[1.1rem]"
value={detailNumberKeyword}
placeholder="관리번호 입력"
placeholder="관로번호 입력"
onChange={(event) => setDetailNumberKeyword(event.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter") handleSearch();
@@ -901,7 +885,7 @@ export function ExmnSurveyDetailPopupPage({
<div className="flex shrink-0 items-center gap-7 whitespace-nowrap text-[1.05rem] text-label">
{isUnitMode ? (
<>
<span><strong className="mr-2 text-foreground">관리번호</strong>{appliedDetailNumberKeyword || "전체"}</span>
<span><strong className="mr-2 text-foreground">관로번호</strong>{appliedDetailNumberKeyword || "전체"}</span>
<span><strong className="mr-2 text-foreground">단위구간</strong>{unitSectionCount.toLocaleString("ko-KR")}개</span>
</>
) : (
@@ -929,7 +913,7 @@ export function ExmnSurveyDetailPopupPage({
rows={unitRows}
emptyMessage={
appliedDetailNumberKeyword
? "입력한 관리번호에 해당하는 본단위구간이 없습니다."
? "입력한 관로번호에 해당하는 본단위구간이 없습니다."
: "조회된 본단위구간이 없습니다."
}
/>