Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eddb1b24d6 | ||
|
|
cf2cc11167 | ||
|
|
9e71f529e7 | ||
|
|
36b11d6b00 | ||
|
|
79f70d8b4b | ||
|
|
7911cff96a | ||
|
|
fe501965bf | ||
|
|
fb82cadc82 | ||
|
|
f5b6effeb3 | ||
|
|
63c2901ae3 | ||
|
|
0356aef7b2 | ||
|
|
f709dc99f6 | ||
|
|
728a66bac2 | ||
|
|
c850b9ce1e | ||
|
|
791bc20e8e | ||
|
|
fed1383ced | ||
|
|
91ff80a255 | ||
|
|
5e43d69d8e | ||
|
|
7f10e20a41 | ||
|
|
9971e512c8 | ||
|
|
0c77ed9bba | ||
|
|
e759c96ade | ||
|
|
f602b75268 | ||
|
|
11d0378060 | ||
|
|
8ed4f959cb | ||
|
|
e5f7778861 | ||
|
|
2d086a05b2 | ||
|
|
6ef4603a7c | ||
|
|
8c614b84a7 | ||
|
|
7d1a501ad8 | ||
|
|
48589274c8 | ||
|
|
83f7676db4 | ||
|
|
c8b30810f6 | ||
|
|
f71fa7d97a | ||
|
|
4c7c4c00ab | ||
|
|
ae341d66ba | ||
|
|
5fcc184a88 | ||
|
|
f6d3f89b5b | ||
|
|
106e09a7a2 | ||
|
|
d1b4600392 | ||
|
|
4204dfa624 | ||
|
|
d7fd5be068 | ||
|
|
735bb397ec | ||
|
|
8b5a2b713c | ||
|
|
4ff80dd257 | ||
|
|
9bb1069d26 | ||
|
|
f5331d4c50 | ||
|
|
f32fe0ec5b | ||
|
|
7b506320b4 | ||
|
|
d45cd79422 | ||
|
|
7cbb58ff32 | ||
|
|
4f50593eac | ||
|
|
8818dd21c6 | ||
|
|
b73b83a49f | ||
|
|
549debf8bf |
+2
-1
@@ -1,6 +1,7 @@
|
||||
# turbo
|
||||
.turbo/
|
||||
.vscode/**/*
|
||||
**/.vscode/
|
||||
*.code-workspace
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
Vendored
-88
@@ -1,88 +0,0 @@
|
||||
{
|
||||
// IntelliSense를 사용하여 가능한 특성에 대해 알아보세요.
|
||||
// 기존 특성에 대한 설명을 보려면 가리킵니다.
|
||||
// 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요.
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "dev:as 디버그",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev:as"
|
||||
],
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal",
|
||||
"sourceMaps": true,
|
||||
"env": {
|
||||
"NODE_OPTIONS": "--inspect"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "dev:cs 디버그",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev:cs"
|
||||
],
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal",
|
||||
"sourceMaps": true,
|
||||
"env": {
|
||||
"NODE_OPTIONS": "--inspect"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "dev:cs 서버 연결 (attach)",
|
||||
"port": 9229,
|
||||
"restart": true,
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"resolveSourceMapLocations": [
|
||||
"${workspaceFolder}/apps/cs/**",
|
||||
"!**/node_modules/**"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "dev:cs 클라이언트 디버그 (Chrome)",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}/apps/cs",
|
||||
"sourceMaps": true
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "dev:st 디버그",
|
||||
"runtimeExecutable": "pnpm",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev:st"
|
||||
],
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"console": "integratedTerminal",
|
||||
"sourceMaps": true,
|
||||
"env": {
|
||||
"NODE_OPTIONS": "--inspect"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
-5
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"chat.tools.terminal.autoApprove": {
|
||||
"pnpm": true
|
||||
}
|
||||
}
|
||||
@@ -2,35 +2,35 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 DcRagFileInfoDto */
|
||||
export const dcRagFileInfoSchema = z.object({
|
||||
fileId: z.string().nullish(),
|
||||
fileGroupId: z.string().nullish(),
|
||||
fileName: z.string().nullish(),
|
||||
physicalPath: z.string().nullish(),
|
||||
downloadUrl: z.string().nullish(),
|
||||
fileSize: z.number().nullish(),
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
esntlAtchFileNm: z.string().nullish(),
|
||||
fileId: z.string().optional(),
|
||||
fileGroupId: z.string().optional(),
|
||||
fileName: z.string().optional(),
|
||||
physicalPath: z.string().optional(),
|
||||
downloadUrl: z.string().optional(),
|
||||
fileSize: z.number().optional(),
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
esntlAtchFileNm: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DcRagDocumentTransferRequestDto */
|
||||
export const dcRagDocumentTransferRequestSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string(),
|
||||
taskSeComCd: z.string().nullish(),
|
||||
taskSeCdNm: z.string().nullish(),
|
||||
essentialFiles: z.array(dcRagFileInfoSchema).nullish(),
|
||||
etcFiles: z.array(dcRagFileInfoSchema).nullish(),
|
||||
callbackUrl: z.string().nullish(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
taskSeCdNm: z.string().optional(),
|
||||
essentialFiles: z.array(dcRagFileInfoSchema).optional(),
|
||||
etcFiles: z.array(dcRagFileInfoSchema).optional(),
|
||||
callbackUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DcRagDraftGenerateRequestDto */
|
||||
export const dcRagDraftGenerateRequestSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string(),
|
||||
pwrkCnsltnDocNo: z.string(),
|
||||
crtDmndUuid: z.string().nullish(),
|
||||
essentialFiles: z.array(dcRagFileInfoSchema).nullish(),
|
||||
etcFiles: z.array(dcRagFileInfoSchema).nullish(),
|
||||
rqstrUuid: z.string().nullish(),
|
||||
callbackUrl: z.string().nullish(),
|
||||
crtDmndUuid: z.string().optional(),
|
||||
essentialFiles: z.array(dcRagFileInfoSchema).optional(),
|
||||
etcFiles: z.array(dcRagFileInfoSchema).optional(),
|
||||
rqstrUuid: z.string().optional(),
|
||||
callbackUrl: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DcRagCostVerifyRequestDto */
|
||||
|
||||
@@ -2,68 +2,68 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 DesignCnstnMngSaveRequestDto */
|
||||
export const designCnstnMngSaveSchema = z.object({
|
||||
designCnstnDocNo: z.string().nullish(),
|
||||
admdstCd: z.string().nullish(),
|
||||
envoSeComCd: z.string().nullish(),
|
||||
taskSeComCd: z.string().nullish(),
|
||||
bizNm: z.string().nullish(),
|
||||
bizBgngYr: z.string().nullish(),
|
||||
bizEndYr: z.string().nullish(),
|
||||
bizCn: z.string().nullish(),
|
||||
aplcntNm: z.string().nullish(),
|
||||
aplcntMblTelno: z.string().nullish(),
|
||||
aplcntOgdpNm: z.string().nullish(),
|
||||
aplcntEmlAddr: z.string().nullish(),
|
||||
dsgnrNm: z.string().nullish(),
|
||||
dsgnrTelno: z.string().nullish(),
|
||||
dsgnrOgdpNm: z.string().nullish(),
|
||||
dsgnrEmlAddr: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
prgrsSttsComCd: z.string().nullish(),
|
||||
designCnstnDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
bizNm: z.string().optional(),
|
||||
bizBgngYr: z.string().optional(),
|
||||
bizEndYr: z.string().optional(),
|
||||
bizCn: z.string().optional(),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
dsgnrNm: z.string().optional(),
|
||||
dsgnrTelno: z.string().optional(),
|
||||
dsgnrOgdpNm: z.string().optional(),
|
||||
dsgnrEmlAddr: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.BzScaleItem */
|
||||
export const designCnstnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().nullish(),
|
||||
bscPl: z.number().nullish(),
|
||||
budgetReflct: z.number().nullish(),
|
||||
thtiDsgn: z.number().nullish(),
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.BizCstItem */
|
||||
export const designCnstnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().nullish(),
|
||||
bscPlanAmt: z.number().nullish(),
|
||||
bgtRfltAmt: z.number().nullish(),
|
||||
tstmDesignAmt: z.number().nullish(),
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const designCnstnEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnRvwOpnnSaveRequestDto */
|
||||
export const designCnstnRvwOpnnSaveSchema = z.object({
|
||||
designCnstnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().nullish(),
|
||||
opnrSeComCd: z.string().nullish(),
|
||||
opnrNm: z.string().nullish(),
|
||||
rvwRsltCn: z.string().nullish(),
|
||||
rvwRsltAnmlyYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto */
|
||||
export const designCnstnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().nullish(),
|
||||
rvwStpCd: z.string().nullish(),
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
designCnstnMng: designCnstnMngSaveSchema,
|
||||
bzScaleList: z.array(designCnstnBzScaleItemSchema).nullish(),
|
||||
bizCstList: z.array(designCnstnBizCstItemSchema).nullish(),
|
||||
esntlAtchFileList: z.array(designCnstnEsntlAtchFileItemSchema).nullish(),
|
||||
rvwOpnnList: z.array(designCnstnRvwOpnnSaveSchema).nullish(),
|
||||
bzScaleList: z.array(designCnstnBzScaleItemSchema).optional(),
|
||||
bizCstList: z.array(designCnstnBizCstItemSchema).optional(),
|
||||
esntlAtchFileList: z.array(designCnstnEsntlAtchFileItemSchema).optional(),
|
||||
rvwOpnnList: z.array(designCnstnRvwOpnnSaveSchema).optional(),
|
||||
});
|
||||
|
||||
export type DesignCnstnMngSaveRequestDto = z.infer<typeof designCnstnMngSaveSchema>;
|
||||
|
||||
@@ -41,7 +41,6 @@ export {
|
||||
validateAllRowsForSave,
|
||||
validateAplnRow,
|
||||
validateAtrzOrder,
|
||||
validateUniqueAtrzAprvr,
|
||||
validateUniqueSeqNo,
|
||||
} from "./lib/apln-validators";
|
||||
export {
|
||||
|
||||
@@ -76,16 +76,3 @@ export function validateUniqueSeqNo(rows: ElaprAplnRow[]): string | null {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function validateUniqueAtrzAprvr(rows: ElaprAplnRow[]): string | null {
|
||||
const pairSet = new Set<string>();
|
||||
for (const row of getVisibleRows(rows)) {
|
||||
const atrz = row.atrzSeComCd?.trim();
|
||||
const aprvr = row.aprvrUuid?.trim();
|
||||
if (!atrz || !aprvr) continue;
|
||||
const key = `${atrz}::${aprvr}`;
|
||||
if (pairSet.has(key)) return "동일한 결재구분과 결재자가 중복됩니다.";
|
||||
pairSet.add(key);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ export interface ElaprDmndSaveRequestDto {
|
||||
elaprDocNo: string;
|
||||
dmndTtl: string;
|
||||
dmndCn: string;
|
||||
elaprCycl?: number | null;
|
||||
docPrgrsSttsComCd?: string | null;
|
||||
elaprCycl?: number;
|
||||
docPrgrsSttsComCd?: string;
|
||||
elaprPrgrsSttsList?: ElaprPrgrsSttsSaveRequestDto[];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@common/components";
|
||||
|
||||
import { useElaprAplnPage } from "../api/queries";
|
||||
import { validateAplnRow, validateUniqueAtrzAprvr } from "../lib/apln-validators";
|
||||
import { validateAplnRow } from "../lib/apln-validators";
|
||||
import { useAplnRows } from "../lib/use-apln-rows";
|
||||
import { useDndSensors } from "../lib/use-dnd-sensors";
|
||||
import type { ElaprApln } from "../model/elapr-apln-interface";
|
||||
@@ -110,12 +110,6 @@ function ElaprAplnPickerGrid({
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicateError = validateUniqueAtrzAprvr(rows);
|
||||
if (duplicateError) {
|
||||
alert(duplicateError);
|
||||
return;
|
||||
}
|
||||
|
||||
commitEdit(row.rowKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
getVisibleRows,
|
||||
validateAllRowsForSave,
|
||||
validateAtrzOrder,
|
||||
validateUniqueAtrzAprvr,
|
||||
validateUniqueSeqNo,
|
||||
} from "../lib/apln-validators";
|
||||
import { mapDocPrgrsToAplnRows } from "../lib/doc-prgrs-to-apln-rows";
|
||||
@@ -91,12 +90,6 @@ function ElaprDocPrgrsPickerDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
const atrzAprvrError = validateUniqueAtrzAprvr(aplnRows);
|
||||
if (atrzAprvrError) {
|
||||
alert(atrzAprvrError);
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleRows = getVisibleRows(aplnRows);
|
||||
onConfirm?.({ aplnRows: visibleRows });
|
||||
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
getVisibleRows,
|
||||
validateAllRowsForSave,
|
||||
validateAtrzOrder,
|
||||
validateUniqueAtrzAprvr,
|
||||
validateUniqueSeqNo,
|
||||
} from "../lib/apln-validators";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
@@ -107,12 +106,6 @@ function ElaprTmpltPickerDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
const atrzAprvrError = validateUniqueAtrzAprvr(aplnRows);
|
||||
if (atrzAprvrError) {
|
||||
alert(atrzAprvrError);
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleRows = getVisibleRows(aplnRows);
|
||||
|
||||
const payload: ElaprTmpltPickerConfirmPayload = {
|
||||
|
||||
@@ -2,52 +2,52 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 FnrsCnsltnMngSaveRequestDto */
|
||||
export const fnrsCnsltnMngSaveSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string().nullish(),
|
||||
admdstCd: z.string().nullish(),
|
||||
envoSeComCd: z.string().nullish(),
|
||||
taskSeComCd: z.string().nullish(),
|
||||
cfmtnBgtBizNo: z.string().nullish(),
|
||||
bgtAplyYr: z.string().nullish(),
|
||||
bizNm: z.string().nullish(),
|
||||
bizBgngYr: z.string().nullish(),
|
||||
bizEndYr: z.string().nullish(),
|
||||
bizCn: z.string().nullish(),
|
||||
aplcntNm: z.string().nullish(),
|
||||
aplcntMblTelno: z.string().nullish(),
|
||||
aplcntOgdpNm: z.string().nullish(),
|
||||
aplcntEmlAddr: z.string().nullish(),
|
||||
dsgnrNm: z.string().nullish(),
|
||||
dsgnrTelno: z.string().nullish(),
|
||||
dsgnrOgdpNm: z.string().nullish(),
|
||||
dsgnrEmlAddr: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
sharAtchFileGroupId: z.string().nullish(),
|
||||
prgrsSttsComCd: z.string().nullish(),
|
||||
rtlztnFcltSeComCd: z.string().nullish(),
|
||||
rtlztnUsgSeComCd: z.string().nullish(),
|
||||
fnrsCnsltnDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
cfmtnBgtBizNo: z.string().optional(),
|
||||
bgtAplyYr: z.string().optional(),
|
||||
bizNm: z.string().optional(),
|
||||
bizBgngYr: z.string().optional(),
|
||||
bizEndYr: z.string().optional(),
|
||||
bizCn: z.string().optional(),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
dsgnrNm: z.string().optional(),
|
||||
dsgnrTelno: z.string().optional(),
|
||||
dsgnrOgdpNm: z.string().optional(),
|
||||
dsgnrEmlAddr: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
sharAtchFileGroupId: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
rtlztnFcltSeComCd: z.string().optional(),
|
||||
rtlztnUsgSeComCd: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.BzScaleItem */
|
||||
export const fnrsCnsltnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().nullish(),
|
||||
bscPl: z.number().nullish(),
|
||||
budgetReflct: z.number().nullish(),
|
||||
thtiDsgn: z.number().nullish(),
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.BizCstItem */
|
||||
export const fnrsCnsltnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().nullish(),
|
||||
bscPlanAmt: z.number().nullish(),
|
||||
bgtRfltAmt: z.number().nullish(),
|
||||
tstmDesignAmt: z.number().nullish(),
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.CklstResultItem */
|
||||
export const fnrsCnsltnCklstResultItemSchema = z.object({
|
||||
cklstRvwSn: z.number().nullish(),
|
||||
cklstArtclSn: z.number().nullish(),
|
||||
cklstSn: z.number().nullish(),
|
||||
cklstRvwSn: z.number().optional(),
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
rvwStpComCd: z.string().nullish(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
@@ -55,58 +55,58 @@ export const fnrsCnsltnCklstResultItemSchema = z.object({
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const fnrsCnsltnEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsCnsltnSplmntDmndItem */
|
||||
export const fnrsCnsltnSplmntDmndItemSchema = z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: z.string().nullish(),
|
||||
splmntDmndCn: z.string().nullish(),
|
||||
splmntDmndDt: z.string().nullish(),
|
||||
splmntDmndAtchFileGroupId: z.string().nullish(),
|
||||
splmntCn: z.string().nullish(),
|
||||
splmntPicNm: z.string().nullish(),
|
||||
splmntCmptnDt: z.string().nullish(),
|
||||
splmntAtchFileGroupId: z.string().nullish(),
|
||||
cmptnYn: z.string().nullish(),
|
||||
splmntDmndCn: z.string().optional(),
|
||||
splmntDmndDt: z.string().optional(),
|
||||
splmntDmndAtchFileGroupId: z.string().optional(),
|
||||
splmntCn: z.string().optional(),
|
||||
splmntPicNm: z.string().optional(),
|
||||
splmntCmptnDt: z.string().optional(),
|
||||
splmntAtchFileGroupId: z.string().optional(),
|
||||
cmptnYn: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnRvwOpnnSaveRequestDto */
|
||||
export const fnrsCnsltnRvwOpnnSaveSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().nullish(),
|
||||
opnrSeComCd: z.string().nullish(),
|
||||
opnrNm: z.string().nullish(),
|
||||
rvwRsltCn: z.string().nullish(),
|
||||
rvwRsltAnmlyYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsSharItem */
|
||||
export const fnrsSharItemSchema = z.object({
|
||||
fnrsSharSeCd: z.string().nullish(),
|
||||
exstnAmt: z.number().nullish(),
|
||||
chgRqtAmt: z.number().nullish(),
|
||||
ajmtAmt: z.number().nullish(),
|
||||
rmrkCn: z.string().nullish(),
|
||||
envoRmrkCn: z.string().nullish(),
|
||||
fnrsSharSeCd: z.string().optional(),
|
||||
exstnAmt: z.number().optional(),
|
||||
chgRqtAmt: z.number().optional(),
|
||||
ajmtAmt: z.number().optional(),
|
||||
rmrkCn: z.string().optional(),
|
||||
envoRmrkCn: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto */
|
||||
export const fnrsCnsltnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().nullish(),
|
||||
rvwStpCd: z.string().nullish(),
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
fnrsCnsltnMng: fnrsCnsltnMngSaveSchema,
|
||||
bzScaleList: z.array(fnrsCnsltnBzScaleItemSchema).nullish(),
|
||||
bizCstList: z.array(fnrsCnsltnBizCstItemSchema).nullish(),
|
||||
cklstResultList: z.array(fnrsCnsltnCklstResultItemSchema).nullish(),
|
||||
cklstResultMap: z.record(z.array(fnrsCnsltnCklstResultItemSchema)).nullish(),
|
||||
esntlAtchFileList: z.array(fnrsCnsltnEsntlAtchFileItemSchema).nullish(),
|
||||
rvwOpnnList: z.array(fnrsCnsltnRvwOpnnSaveSchema).nullish(),
|
||||
splmntDmndList: z.array(fnrsCnsltnSplmntDmndItemSchema).nullish(),
|
||||
fnrsSharList: z.array(fnrsSharItemSchema).nullish(),
|
||||
bzScaleList: z.array(fnrsCnsltnBzScaleItemSchema).optional(),
|
||||
bizCstList: z.array(fnrsCnsltnBizCstItemSchema).optional(),
|
||||
cklstResultList: z.array(fnrsCnsltnCklstResultItemSchema).optional(),
|
||||
cklstResultMap: z.record(z.array(fnrsCnsltnCklstResultItemSchema)).optional(),
|
||||
esntlAtchFileList: z.array(fnrsCnsltnEsntlAtchFileItemSchema).optional(),
|
||||
rvwOpnnList: z.array(fnrsCnsltnRvwOpnnSaveSchema).optional(),
|
||||
splmntDmndList: z.array(fnrsCnsltnSplmntDmndItemSchema).optional(),
|
||||
fnrsSharList: z.array(fnrsSharItemSchema).optional(),
|
||||
});
|
||||
|
||||
export type FnrsCnsltnMngSaveRequestDto = z.infer<typeof fnrsCnsltnMngSaveSchema>;
|
||||
|
||||
@@ -2,12 +2,12 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 FnrsExamineRequestDto */
|
||||
export const fnrsExamineRequestSchema = z.object({
|
||||
prgrsSttsComCd: z.string().nullish(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 EnvoActnReviewReqDto */
|
||||
export const envoActnReviewReqSchema = z.object({
|
||||
specialRqstCn: z.string().nullish(),
|
||||
specialRqstCn: z.string().optional(),
|
||||
});
|
||||
|
||||
export type FnrsExamineRequestDto = z.infer<typeof fnrsExamineRequestSchema>;
|
||||
|
||||
@@ -2,52 +2,52 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 InstlSncnMngSaveRequestDto */
|
||||
export const instlSncnMngSaveSchema = z.object({
|
||||
instlSncnDocNo: z.string().nullish(),
|
||||
admdstCd: z.string().nullish(),
|
||||
envoSeComCd: z.string().nullish(),
|
||||
sncnSeComCd: z.string().nullish(),
|
||||
taskSeComCd: z.string().nullish(),
|
||||
bgtBizCd: z.string().nullish(),
|
||||
bgtAplyYr: z.string().nullish(),
|
||||
bizNm: z.string().nullish(),
|
||||
bizBgngYr: z.string().nullish(),
|
||||
bizEndYr: z.string().nullish(),
|
||||
bizCn: z.string().nullish(),
|
||||
aplcntNm: z.string().nullish(),
|
||||
aplcntMblTelno: z.string().nullish(),
|
||||
aplcntOgdpNm: z.string().nullish(),
|
||||
aplcntEmlAddr: z.string().nullish(),
|
||||
dsgnrNm: z.string().nullish(),
|
||||
dsgnrTelno: z.string().nullish(),
|
||||
dsgnrOgdpNm: z.string().nullish(),
|
||||
dsgnrEmlAddr: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
prgrsSttsComCd: z.string().nullish(),
|
||||
rtlztnFcltSeComCd: z.string().nullish(),
|
||||
rtlztnUsgSeComCd: z.string().nullish(),
|
||||
instlSncnDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
sncnSeComCd: z.string().optional(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
bgtBizCd: z.string().optional(),
|
||||
bgtAplyYr: z.string().optional(),
|
||||
bizNm: z.string().optional(),
|
||||
bizBgngYr: z.string().optional(),
|
||||
bizEndYr: z.string().optional(),
|
||||
bizCn: z.string().optional(),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
dsgnrNm: z.string().optional(),
|
||||
dsgnrTelno: z.string().optional(),
|
||||
dsgnrOgdpNm: z.string().optional(),
|
||||
dsgnrEmlAddr: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
rtlztnFcltSeComCd: z.string().optional(),
|
||||
rtlztnUsgSeComCd: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.BzScaleItem (diff는 API 미전송) */
|
||||
export const instlSncnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().nullish(),
|
||||
bscPl: z.number().nullish(),
|
||||
budgetReflct: z.number().nullish(),
|
||||
thtiDsgn: z.number().nullish(),
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.BizCstItem */
|
||||
export const instlSncnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().nullish(),
|
||||
bscPlanAmt: z.number().nullish(),
|
||||
bgtRfltAmt: z.number().nullish(),
|
||||
tstmDesignAmt: z.number().nullish(),
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.CklstResultItem */
|
||||
export const instlSncnCklstResultItemSchema = z.object({
|
||||
cklstRvwSn: z.number().nullish(),
|
||||
cklstArtclSn: z.number().nullish(),
|
||||
cklstSn: z.number().nullish(),
|
||||
cklstRvwSn: z.number().optional(),
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
rvwStpComCd: z.string().nullish(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
@@ -55,47 +55,47 @@ export const instlSncnCklstResultItemSchema = z.object({
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const instlSncnEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnRvwOpnnSaveRequestDto */
|
||||
export const instlSncnRvwOpnnSaveSchema = z.object({
|
||||
instlSncnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().nullish(),
|
||||
opnrSeComCd: z.string().nullish(),
|
||||
opnrNm: z.string().nullish(),
|
||||
rvwRsltCn: z.string().nullish(),
|
||||
rvwRsltAnmlyYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.InstlSncnSplmntDmndItem */
|
||||
export const instlSncnSplmntDmndItemSchema = z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: z.string().nullish(),
|
||||
splmntDmndCn: z.string().nullish(),
|
||||
splmntDmndDt: z.string().nullish(),
|
||||
splmntDmndAtchFileGroupId: z.string().nullish(),
|
||||
splmntCn: z.string().nullish(),
|
||||
splmntPicNm: z.string().nullish(),
|
||||
splmntCmptnDt: z.string().nullish(),
|
||||
splmntAtchFileGroupId: z.string().nullish(),
|
||||
cmptnYn: z.string().nullish(),
|
||||
splmntDmndCn: z.string().optional(),
|
||||
splmntDmndDt: z.string().optional(),
|
||||
splmntDmndAtchFileGroupId: z.string().optional(),
|
||||
splmntCn: z.string().optional(),
|
||||
splmntPicNm: z.string().optional(),
|
||||
splmntCmptnDt: z.string().optional(),
|
||||
splmntAtchFileGroupId: z.string().optional(),
|
||||
cmptnYn: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto */
|
||||
export const instlSncnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().nullish(),
|
||||
rvwStpCd: z.string().nullish(),
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
instlSncnMng: instlSncnMngSaveSchema,
|
||||
bzScaleList: z.array(instlSncnBzScaleItemSchema).nullish(),
|
||||
bizCstList: z.array(instlSncnBizCstItemSchema).nullish(),
|
||||
cklstResultList: z.array(instlSncnCklstResultItemSchema).nullish(),
|
||||
cklstResultMap: z.record(z.array(instlSncnCklstResultItemSchema)).nullish(),
|
||||
esntlAtchFileList: z.array(instlSncnEsntlAtchFileItemSchema).nullish(),
|
||||
rvwOpnnList: z.array(instlSncnRvwOpnnSaveSchema).nullish(),
|
||||
splmntDmndList: z.array(instlSncnSplmntDmndItemSchema).nullish(),
|
||||
bzScaleList: z.array(instlSncnBzScaleItemSchema).optional(),
|
||||
bizCstList: z.array(instlSncnBizCstItemSchema).optional(),
|
||||
cklstResultList: z.array(instlSncnCklstResultItemSchema).optional(),
|
||||
cklstResultMap: z.record(z.array(instlSncnCklstResultItemSchema)).optional(),
|
||||
esntlAtchFileList: z.array(instlSncnEsntlAtchFileItemSchema).optional(),
|
||||
rvwOpnnList: z.array(instlSncnRvwOpnnSaveSchema).optional(),
|
||||
splmntDmndList: z.array(instlSncnSplmntDmndItemSchema).optional(),
|
||||
});
|
||||
|
||||
export type InstlSncnMngSaveRequestDto = z.infer<typeof instlSncnMngSaveSchema>;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { z } from "zod";
|
||||
export const pwrkCnsltnDocCrtRsltSaveRequestSchema = z.object({
|
||||
crtDmndUuid: z.string().uuid(),
|
||||
pwrkCnsltnDocNo: z.string().min(1).max(100),
|
||||
atchFileGroupId: z.string().max(30).nullish(),
|
||||
atchFileGroupId: z.string().max(30).optional(),
|
||||
});
|
||||
|
||||
export type PwrkCnsltnDocCrtRsltSaveRequestDto = z.infer<
|
||||
|
||||
+4
-4
@@ -3,10 +3,10 @@ import { z } from "zod";
|
||||
/** 백엔드 PwrkCnsltnDocEvlSaveRequestDto */
|
||||
export const pwrkCnsltnDocEvlSaveRequestSchema = z.object({
|
||||
pwrkCnsltnDocNo: z.string().min(1),
|
||||
pwrkCnsltnDocEvlUuid: z.string().uuid().nullish(),
|
||||
pwrkCnsltnDocEvlCn: z.string().max(2000).nullish(),
|
||||
pwrkCnsltnDocEvlScr: z.number().nullish(),
|
||||
pwrkCnsltnDocEvlDt: z.string().nullish(),
|
||||
pwrkCnsltnDocEvlUuid: z.string().uuid().optional(),
|
||||
pwrkCnsltnDocEvlCn: z.string().max(2000).optional(),
|
||||
pwrkCnsltnDocEvlScr: z.number().optional(),
|
||||
pwrkCnsltnDocEvlDt: z.string().optional(),
|
||||
});
|
||||
|
||||
export type PwrkCnsltnDocEvlSaveRequestDto = z.infer<typeof pwrkCnsltnDocEvlSaveRequestSchema>;
|
||||
|
||||
@@ -3,18 +3,18 @@ import { z } from "zod";
|
||||
/** 백엔드 PwrkActnResultSubmitDto.ActnItemDto */
|
||||
export const pwrkActnResultActnItemSchema = z.object({
|
||||
rvwOpnnSn: z.number(),
|
||||
actnRsltCn: z.string().nullish(),
|
||||
rfltYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
actnRsltCn: z.string().optional(),
|
||||
rfltYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 PwrkActnResultSubmitDto */
|
||||
export const pwrkActnResultSubmitSchema = z.object({
|
||||
isSubmit: z.boolean(),
|
||||
actnList: z.array(pwrkActnResultActnItemSchema).nullish(),
|
||||
totActnSummary: z.string().nullish(),
|
||||
muniDocNo: z.string().nullish(),
|
||||
pwrkAtchFileGroupId: z.string().nullish(),
|
||||
actnList: z.array(pwrkActnResultActnItemSchema).optional(),
|
||||
totActnSummary: z.string().optional(),
|
||||
muniDocNo: z.string().optional(),
|
||||
pwrkAtchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
export type PwrkActnResultSubmitDto = z.infer<typeof pwrkActnResultSubmitSchema>;
|
||||
|
||||
@@ -2,50 +2,50 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 SewerBscPlanSaveRequestDto */
|
||||
export const sewerBscPlanSaveSchema = z.object({
|
||||
sewerBscPlanDocNo: z.string().nullish(),
|
||||
admdstCd: z.string().nullish(),
|
||||
envoSeComCd: z.string().nullish(),
|
||||
sewerBscPlanSeComCd: z.string().nullish(),
|
||||
sewerBscPlanNm: z.string().nullish(),
|
||||
taskSeComCd: z.string().nullish(),
|
||||
prgrsSttsComCd: z.string().nullish(),
|
||||
sewerBscPlanDocNo: z.string().optional(),
|
||||
admdstCd: z.string().optional(),
|
||||
envoSeComCd: z.string().optional(),
|
||||
sewerBscPlanSeComCd: z.string().optional(),
|
||||
sewerBscPlanNm: z.string().optional(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanDtlSaveRequestDto */
|
||||
export const sewerBscPlanDtlSaveSchema = z.object({
|
||||
sewerBscPlanDocNo: z.string().nullish(),
|
||||
chgCycl: z.number().nullish(),
|
||||
aplcntNm: z.string().nullish(),
|
||||
aplcntMblTelno: z.string().nullish(),
|
||||
aplcntOgdpNm: z.string().nullish(),
|
||||
aplcntEmlAddr: z.string().nullish(),
|
||||
dsgnrNm: z.string().nullish(),
|
||||
dsgnrTelno: z.string().nullish(),
|
||||
dsgnrOgdpNm: z.string().nullish(),
|
||||
dsgnrEmlAddr: z.string().nullish(),
|
||||
planYr: z.string().nullish(),
|
||||
crtrYr: z.string().nullish(),
|
||||
bizOtlnCn: z.string().nullish(),
|
||||
frlvlGoalYr: z.string().nullish(),
|
||||
sclvlGoalYr: z.string().nullish(),
|
||||
thlvlGoalYr: z.string().nullish(),
|
||||
folvlGoalYr: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
sewerBscPlanDocNo: z.string().optional(),
|
||||
chgCycl: z.number().optional(),
|
||||
aplcntNm: z.string().optional(),
|
||||
aplcntMblTelno: z.string().optional(),
|
||||
aplcntOgdpNm: z.string().optional(),
|
||||
aplcntEmlAddr: z.string().optional(),
|
||||
dsgnrNm: z.string().optional(),
|
||||
dsgnrTelno: z.string().optional(),
|
||||
dsgnrOgdpNm: z.string().optional(),
|
||||
dsgnrEmlAddr: z.string().optional(),
|
||||
planYr: z.string().optional(),
|
||||
crtrYr: z.string().optional(),
|
||||
bizOtlnCn: z.string().optional(),
|
||||
frlvlGoalYr: z.string().optional(),
|
||||
sclvlGoalYr: z.string().optional(),
|
||||
thlvlGoalYr: z.string().optional(),
|
||||
folvlGoalYr: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanOtlnItem */
|
||||
export const sewerBscPlanOtlnItemSchema = z.object({
|
||||
planStpNo: z.number().nullish(),
|
||||
sewerPlanPpltnCnt: z.number().nullish(),
|
||||
sewerPrcsPpltnCnt: z.number().nullish(),
|
||||
sewerPsplyRt: z.number().nullish(),
|
||||
planStpNo: z.number().optional(),
|
||||
sewerPlanPpltnCnt: z.number().optional(),
|
||||
sewerPrcsPpltnCnt: z.number().optional(),
|
||||
sewerPsplyRt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.CklstResultItem */
|
||||
export const sewerBscPlanCklstResultItemSchema = z.object({
|
||||
cklstRvwSn: z.number().nullish(),
|
||||
cklstArtclSn: z.number().nullish(),
|
||||
cklstSn: z.number().nullish(),
|
||||
cklstRvwSn: z.number().optional(),
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
rvwStpComCd: z.string(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
@@ -53,109 +53,109 @@ export const sewerBscPlanCklstResultItemSchema = z.object({
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanFnrsCstItem */
|
||||
export const sewerBscPlanFnrsCstItemSchema = z.object({
|
||||
planStpNo: z.number().nullish(),
|
||||
gvexpAmt: z.number().nullish(),
|
||||
lcexpAmt: z.number().nullish(),
|
||||
orgprBdchgAmt: z.number().nullish(),
|
||||
etcAmt: z.number().nullish(),
|
||||
sumAmt: z.number().nullish(),
|
||||
planStpNo: z.number().optional(),
|
||||
gvexpAmt: z.number().optional(),
|
||||
lcexpAmt: z.number().optional(),
|
||||
orgprBdchgAmt: z.number().optional(),
|
||||
etcAmt: z.number().optional(),
|
||||
sumAmt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanSplmntDmndItem */
|
||||
export const sewerBscPlanSplmntDmndItemSchema = z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: z.string().nullish(),
|
||||
splmntDmndCn: z.string().nullish(),
|
||||
splmntDmndDt: z.string().nullish(),
|
||||
splmntDmndAtchFileGroupId: z.string().nullish(),
|
||||
splmntCn: z.string().nullish(),
|
||||
splmntPicNm: z.string().nullish(),
|
||||
splmntCmptnDt: z.string().nullish(),
|
||||
splmntAtchFileGroupId: z.string().nullish(),
|
||||
cmptnYn: z.string().nullish(),
|
||||
splmntDmndCn: z.string().optional(),
|
||||
splmntDmndDt: z.string().optional(),
|
||||
splmntDmndAtchFileGroupId: z.string().optional(),
|
||||
splmntCn: z.string().optional(),
|
||||
splmntPicNm: z.string().optional(),
|
||||
splmntCmptnDt: z.string().optional(),
|
||||
splmntAtchFileGroupId: z.string().optional(),
|
||||
cmptnYn: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanRvwOpnnItem */
|
||||
export const sewerBscPlanRvwOpnnItemSchema = z.object({
|
||||
rvwOpnnSn: z.number().nullish(),
|
||||
opnrSeComCd: z.string().nullish(),
|
||||
opnrNm: z.string().nullish(),
|
||||
rvwRsltCn: z.string().nullish(),
|
||||
rvwRsltAnmlyYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const sewerBscPlanEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanBizCstItem */
|
||||
export const sewerBscPlanBizCstItemSchema = z.object({
|
||||
planStpNo: z.number().nullish(),
|
||||
acntgSeCd: z.string().nullish(),
|
||||
cstrnAmt: z.number().nullish(),
|
||||
planStpNo: z.number().optional(),
|
||||
acntgSeCd: z.string().optional(),
|
||||
cstrnAmt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntDtlItem */
|
||||
export const sewerBscPlanTmplntDtlItemSchema = z.object({
|
||||
planStpNo: z.number().nullish(),
|
||||
swtrmtFcltCd: z.string().nullish(),
|
||||
prcsZoneArea: z.number().nullish(),
|
||||
swtrmtPpltnCnt: z.number().nullish(),
|
||||
planSwqty: z.number().nullish(),
|
||||
fcltCpct: z.number().nullish(),
|
||||
sprsysDrtwPplLen: z.number().nullish(),
|
||||
sprsysExlnPplLen: z.number().nullish(),
|
||||
cbsysPplLen: z.number().nullish(),
|
||||
icptgPplLen: z.number().nullish(),
|
||||
drgCnt: z.number().nullish(),
|
||||
drtwRlyPumpCnt: z.number().nullish(),
|
||||
drtwPumpCnt: z.number().nullish(),
|
||||
exlnPumpCnt: z.number().nullish(),
|
||||
planStpNo: z.number().optional(),
|
||||
swtrmtFcltCd: z.string().optional(),
|
||||
prcsZoneArea: z.number().optional(),
|
||||
swtrmtPpltnCnt: z.number().optional(),
|
||||
planSwqty: z.number().optional(),
|
||||
fcltCpct: z.number().optional(),
|
||||
sprsysDrtwPplLen: z.number().optional(),
|
||||
sprsysExlnPplLen: z.number().optional(),
|
||||
cbsysPplLen: z.number().optional(),
|
||||
icptgPplLen: z.number().optional(),
|
||||
drgCnt: z.number().optional(),
|
||||
drtwRlyPumpCnt: z.number().optional(),
|
||||
drtwPumpCnt: z.number().optional(),
|
||||
exlnPumpCnt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntItem */
|
||||
export const sewerBscPlanTmplntItemSchema = z.object({
|
||||
swtrmtFcltCd: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
sewerBscPlanTmplntDtlList: z.array(sewerBscPlanTmplntDtlItemSchema).nullish(),
|
||||
swtrmtFcltCd: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
sewerBscPlanTmplntDtlList: z.array(sewerBscPlanTmplntDtlItemSchema).optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntTmprStrgItem */
|
||||
export const sewerBscPlanTmplntTmprStrgItemSchema = z.object({
|
||||
dtStrgDt: z.string().nullish(),
|
||||
swtrmtFcltCd: z.string().nullish(),
|
||||
prcsZoneArea: z.number().nullish(),
|
||||
swtrmtPpltnCnt: z.number().nullish(),
|
||||
planSwqty: z.number().nullish(),
|
||||
fcltCpct: z.number().nullish(),
|
||||
sprsysDrtwPplLen: z.number().nullish(),
|
||||
sprsysExlnPplLen: z.number().nullish(),
|
||||
cbsysPplLen: z.number().nullish(),
|
||||
icptgPplLen: z.number().nullish(),
|
||||
drgCnt: z.number().nullish(),
|
||||
drtwRlyPumpCnt: z.number().nullish(),
|
||||
drtwPumpCnt: z.number().nullish(),
|
||||
exlnPumpCnt: z.number().nullish(),
|
||||
dtStrgDt: z.string().optional(),
|
||||
swtrmtFcltCd: z.string().optional(),
|
||||
prcsZoneArea: z.number().optional(),
|
||||
swtrmtPpltnCnt: z.number().optional(),
|
||||
planSwqty: z.number().optional(),
|
||||
fcltCpct: z.number().optional(),
|
||||
sprsysDrtwPplLen: z.number().optional(),
|
||||
sprsysExlnPplLen: z.number().optional(),
|
||||
cbsysPplLen: z.number().optional(),
|
||||
icptgPplLen: z.number().optional(),
|
||||
drgCnt: z.number().optional(),
|
||||
drtwRlyPumpCnt: z.number().optional(),
|
||||
drtwPumpCnt: z.number().optional(),
|
||||
exlnPumpCnt: z.number().optional(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto */
|
||||
export const sewerBscPlanApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().nullish(),
|
||||
rvwStpCd: z.string().nullish(),
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
sewerBscPlan: sewerBscPlanSaveSchema,
|
||||
sewerBscPlanDtl: sewerBscPlanDtlSaveSchema,
|
||||
sewerBscPlanOtlnList: z.array(sewerBscPlanOtlnItemSchema).nullish(),
|
||||
cklstResultList: z.array(sewerBscPlanCklstResultItemSchema).nullish(),
|
||||
fnrsCstList: z.array(sewerBscPlanFnrsCstItemSchema).nullish(),
|
||||
splmntDmndList: z.array(sewerBscPlanSplmntDmndItemSchema).nullish(),
|
||||
rvwOpnnList: z.array(sewerBscPlanRvwOpnnItemSchema).nullish(),
|
||||
esntlAtchFileList: z.array(sewerBscPlanEsntlAtchFileItemSchema).nullish(),
|
||||
bizCstList: z.array(sewerBscPlanBizCstItemSchema).nullish(),
|
||||
sewerBscPlanTmplntList: z.array(sewerBscPlanTmplntItemSchema).nullish(),
|
||||
tmplntTmprStrgList: z.array(sewerBscPlanTmplntTmprStrgItemSchema).nullish(),
|
||||
sewerBscPlanOtlnList: z.array(sewerBscPlanOtlnItemSchema).optional(),
|
||||
cklstResultList: z.array(sewerBscPlanCklstResultItemSchema).optional(),
|
||||
fnrsCstList: z.array(sewerBscPlanFnrsCstItemSchema).optional(),
|
||||
splmntDmndList: z.array(sewerBscPlanSplmntDmndItemSchema).optional(),
|
||||
rvwOpnnList: z.array(sewerBscPlanRvwOpnnItemSchema).optional(),
|
||||
esntlAtchFileList: z.array(sewerBscPlanEsntlAtchFileItemSchema).optional(),
|
||||
bizCstList: z.array(sewerBscPlanBizCstItemSchema).optional(),
|
||||
sewerBscPlanTmplntList: z.array(sewerBscPlanTmplntItemSchema).optional(),
|
||||
tmplntTmprStrgList: z.array(sewerBscPlanTmplntTmprStrgItemSchema).optional(),
|
||||
});
|
||||
|
||||
export type SewerBscPlanSaveRequestDto = z.infer<typeof sewerBscPlanSaveSchema>;
|
||||
|
||||
@@ -23,8 +23,8 @@ export const designCnstnRegistSchema = z.object({
|
||||
.string()
|
||||
.min(1, "설계자 이메일은 필수입니다")
|
||||
.email("올바른 이메일 형식이 아닙니다"),
|
||||
sido: z.string().nullish(),
|
||||
gugun: z.string().nullish(),
|
||||
sido: z.string().optional(),
|
||||
gugun: z.string().optional(),
|
||||
}),
|
||||
bzScaleList: z.array(
|
||||
designCnstnBzScaleItemSchema.extend({
|
||||
@@ -32,7 +32,7 @@ export const designCnstnRegistSchema = z.object({
|
||||
bscPl: z.number(),
|
||||
budgetReflct: z.number(),
|
||||
thtiDsgn: z.number(),
|
||||
diff: z.number().nullish(),
|
||||
diff: z.number().optional(),
|
||||
}),
|
||||
),
|
||||
esntlAtchFileList: z.array(
|
||||
@@ -52,7 +52,7 @@ export function formatTaskSeComCdForApi(taskSeComCd: string[]): string {
|
||||
return taskSeComCd.join(",");
|
||||
}
|
||||
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string | null): string[] {
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string): string[] {
|
||||
if (!taskSeComCd) return [];
|
||||
return taskSeComCd.split(",").filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ function getBadgeColor(state: BadgeState) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
function formatOptional(value: string | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
|
||||
@@ -51,19 +51,14 @@ const ROLE_STATUS_BUTTON_ACTIONS: Record<string, Record<string, FnrsCnsltnViewBu
|
||||
LE: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply, FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstTechRvw],
|
||||
TP: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwOpnnRply,
|
||||
],
|
||||
RB: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
RA: [
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng,
|
||||
FNRS_CNSLTN_VIEW_BUTTON_ACTION.rqstActnRvw,
|
||||
],
|
||||
AV: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
EV: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
RK: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
RR: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng, FNRS_CNSLTN_VIEW_BUTTON_ACTION.rvwRply],
|
||||
EP: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
TA: [FNRS_CNSLTN_VIEW_BUTTON_ACTION.pwrkCnsltnDocMng],
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
type ChecklistFormState,
|
||||
type ChecklistProgress,
|
||||
type ChecklistRowKey,
|
||||
type ChecklistStepMap,
|
||||
REVIEW_STEP_CODES,
|
||||
type ReviewStepCode,
|
||||
} from "../model/view-types";
|
||||
@@ -16,6 +17,15 @@ export function buildChecklistRowKey(
|
||||
return `${row.taskSeComCd}_${row.cklstSn}_${row.cklstArtclSn}_${row.cklstRvwSn}_${step}`;
|
||||
}
|
||||
|
||||
export function groupByReviewStep(rows: CklstRvwRsltTreeDto[]): ChecklistStepMap {
|
||||
const base: ChecklistStepMap = { LOCAL: [], ENV: [], KECO: [] };
|
||||
rows.forEach((row) => {
|
||||
const step = (row.rvwStpComCd ?? "LOCAL") as ReviewStepCode;
|
||||
if (REVIEW_STEP_CODES.includes(step)) base[step].push(row);
|
||||
});
|
||||
return base;
|
||||
}
|
||||
|
||||
export function extractTaskCodes(rows: CklstRvwRsltTreeDto[]): string[] {
|
||||
return Array.from(new Set(rows.map((row) => row.taskSeComCd).filter(Boolean)));
|
||||
}
|
||||
@@ -60,6 +70,13 @@ export function computeChecklistProgress(
|
||||
return { total, completed, percent };
|
||||
}
|
||||
|
||||
export function filterChecklistRowsByReviewStep(
|
||||
rows: CklstRvwRsltTreeDto[],
|
||||
reviewStep: ReviewStepCode,
|
||||
): CklstRvwRsltTreeDto[] {
|
||||
return rows.filter((row) => row.rvwStpComCd === reviewStep);
|
||||
}
|
||||
|
||||
export function computeReviewStepChecklistProgress(
|
||||
rows: CklstRvwRsltTreeDto[],
|
||||
results: ChecklistFormState["results"],
|
||||
@@ -76,6 +93,23 @@ export function computeReviewStepChecklistProgress(
|
||||
return { total, completed, percent };
|
||||
}
|
||||
|
||||
export function buildChecklistFormState(rows: CklstRvwRsltTreeDto[]): ChecklistFormState {
|
||||
const results: ChecklistFormState["results"] = {};
|
||||
const comments: ChecklistFormState["comments"] = {};
|
||||
rows.forEach((row) => {
|
||||
const key = buildChecklistRowKey(row);
|
||||
if (row.rvwRsltAnmlyYn === "Y" || row.rvwRsltAnmlyYn === "N") {
|
||||
results[key] = row.rvwRsltAnmlyYn;
|
||||
}
|
||||
if (row.picRvwCn) comments[key] = row.picRvwCn;
|
||||
});
|
||||
return {
|
||||
results,
|
||||
comments,
|
||||
progress: computeChecklistProgress(rows, results),
|
||||
};
|
||||
}
|
||||
|
||||
export function mergeChecklistFormStateFromServer(
|
||||
prev: ChecklistFormState,
|
||||
allRows: CklstRvwRsltTreeDto[],
|
||||
|
||||
@@ -27,8 +27,8 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
.string()
|
||||
.min(1, "설계자 이메일은 필수입니다")
|
||||
.email("올바른 이메일 형식이 아닙니다"),
|
||||
sido: z.string().nullish(),
|
||||
gugun: z.string().nullish(),
|
||||
sido: z.string().optional(),
|
||||
gugun: z.string().optional(),
|
||||
}),
|
||||
bzScaleList: z.array(
|
||||
fnrsCnsltnBzScaleItemSchema.extend({
|
||||
@@ -36,7 +36,7 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
bscPl: z.number(),
|
||||
budgetReflct: z.number(),
|
||||
thtiDsgn: z.number(),
|
||||
diff: z.number().nullish(),
|
||||
diff: z.number().optional(),
|
||||
}),
|
||||
),
|
||||
bizCstList: z.array(
|
||||
@@ -52,8 +52,8 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
fnrsSharSeCd: z.string(),
|
||||
exstnAmt: z.number(),
|
||||
chgRqtAmt: z.number(),
|
||||
ajmtAmt: z.number().nullish(),
|
||||
rmrkCn: z.string().nullish(),
|
||||
ajmtAmt: z.number().optional(),
|
||||
rmrkCn: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
cklstResultList: z.array(
|
||||
@@ -74,7 +74,7 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
splmntDmndList: z.array(
|
||||
fnrsCnsltnSplmntDmndItemSchema.extend({
|
||||
splmntArtclCdNm: z.string().nullish(),
|
||||
splmntDmndAtchFileNm: z.string().nullish(),
|
||||
splmntDmndAtchFileNm: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
privacyConsent: z.enum(["Y", "N"], {
|
||||
@@ -88,7 +88,7 @@ export function formatTaskSeComCdForApi(taskSeComCd: string[]): string {
|
||||
return taskSeComCd.join(",");
|
||||
}
|
||||
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string | null): string[] {
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string): string[] {
|
||||
if (!taskSeComCd) return [];
|
||||
return taskSeComCd.split(",").filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const pwrkCnsltnDocEvlFormSchema = z.object({
|
||||
required_error: "만족도를 선택해 주세요.",
|
||||
invalid_type_error: "만족도를 선택해 주세요.",
|
||||
}),
|
||||
pwrkCnsltnDocEvlCn: z.string().max(2000).nullish(),
|
||||
pwrkCnsltnDocEvlCn: z.string().max(2000).optional(),
|
||||
});
|
||||
|
||||
export type PwrkCnsltnDocEvlFormValues = z.infer<typeof pwrkCnsltnDocEvlFormSchema>;
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { CklstRvwRsltTreeDto } from "@/entities/fnrs-cnsltn";
|
||||
|
||||
import {
|
||||
PBADMS_PROGRESS_ORDER,
|
||||
REVIEW_STEP_CODES,
|
||||
@@ -30,25 +28,6 @@ export function getVisibleReviewSteps(
|
||||
return REVIEW_STEP_CODES.slice(0, count);
|
||||
}
|
||||
|
||||
export function resolveVisibleReviewSteps(
|
||||
prgrsSttsComCd: string,
|
||||
userRoleCode: string | undefined,
|
||||
cklstResultList: CklstRvwRsltTreeDto[],
|
||||
): ReviewStepCode[] {
|
||||
const byProgressAndRole = getVisibleReviewSteps(prgrsSttsComCd, userRoleCode);
|
||||
const currentStep = byProgressAndRole.at(-1);
|
||||
const stepsWithData = new Set<ReviewStepCode>();
|
||||
|
||||
for (const row of cklstResultList) {
|
||||
const step = row.rvwStpComCd as ReviewStepCode | undefined;
|
||||
if (step && REVIEW_STEP_CODES.includes(step)) stepsWithData.add(step);
|
||||
}
|
||||
|
||||
return REVIEW_STEP_CODES.filter(
|
||||
(step) => stepsWithData.has(step) || step === currentStep,
|
||||
);
|
||||
}
|
||||
|
||||
export function getEditableReviewStep(
|
||||
dtlInstCd?: string,
|
||||
prgrsSttsComCd?: string,
|
||||
|
||||
@@ -113,7 +113,7 @@ function getBadgeColor(state: BadgeState) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
function formatOptional(value: string | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
|
||||
@@ -44,13 +44,9 @@ export interface ChecklistFormState {
|
||||
progress: ChecklistProgress;
|
||||
}
|
||||
|
||||
export const REVIEW_STEP_CODES: ReviewStepCode[] = ["LOCAL", "ENV", "KECO"];
|
||||
export type ChecklistStepMap = Record<ReviewStepCode, CklstRvwRsltTreeDto[]>;
|
||||
|
||||
export const CHECKLIST_TAB_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "자가체크",
|
||||
ENV: "기술검토",
|
||||
KECO: "환경청검토",
|
||||
};
|
||||
export const REVIEW_STEP_CODES: ReviewStepCode[] = ["LOCAL", "ENV", "KECO"];
|
||||
|
||||
export const REVIEW_STEP_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "지자체",
|
||||
|
||||
@@ -180,9 +180,7 @@ function FloatingActionButtons({
|
||||
userInfo?.userUnqId,
|
||||
);
|
||||
const viewOnlyPwrkCnsltnDoc =
|
||||
prgrsSttsComCd === "SL" ||
|
||||
(prgrsSttsComCd === "RB" && dtlInstCd !== "ROLE_USER05") ||
|
||||
(prgrsSttsComCd === "RK" && dtlInstCd === "ROLE_USER03");
|
||||
prgrsSttsComCd === "SL" || (prgrsSttsComCd === "RB" && dtlInstCd !== "ROLE_USER05");
|
||||
|
||||
const actionButtons = useMemo(() => {
|
||||
if (needsAtrzResolve && isRvwLoginUserLoading) return [];
|
||||
|
||||
@@ -113,8 +113,7 @@ function PwrkCnsltnDocEvlDialog({ open, pwrkCnsltnDocNo, onClose }: PwrkCnsltnDo
|
||||
<Textarea
|
||||
id="pwrkCnsltnDocEvlCn"
|
||||
rows={4}
|
||||
placeholder="의견을 입력해 주세요"
|
||||
className="!text-xl"
|
||||
placeholder="의견을 입력해 주세요 (선택)"
|
||||
{...register("pwrkCnsltnDocEvlCn")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
fnrsCnsltnMngService,
|
||||
} from "@/entities/fnrs-cnsltn";
|
||||
import { useFnrsSharSeMngTree } from "@/entities/fnrs-shar-se";
|
||||
import { findCompletedPwrkDraft, usePwrkCnsltnMngByUpDocNo } from "@/entities/pwrk-cnsltn-mng";
|
||||
import {
|
||||
AttachmentsFormSection,
|
||||
buildAcntgCostItems,
|
||||
@@ -36,10 +35,7 @@ import {
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import {
|
||||
type FnrsCnsltnRegistFormData,
|
||||
fnrsCnsltnRegistSchema,
|
||||
} from "../lib/fnrs-cnsltn-regist-schema";
|
||||
import { type FnrsCnsltnRegistFormData, fnrsCnsltnRegistSchema } from "../lib/fnrs-cnsltn-regist-schema";
|
||||
import {
|
||||
buildFormPayload,
|
||||
getCreateDefaultValues,
|
||||
@@ -72,12 +68,6 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
const isSplmntPrgrsStts = initialData?.detDto?.prgrsSttsComCd === "SL";
|
||||
const fnrsCnsltnDocNo = initialData?.detDto?.fnrsCnsltnDocNo;
|
||||
|
||||
const { data: pwrkCnsltnData } = usePwrkCnsltnMngByUpDocNo(
|
||||
fnrsCnsltnDocNo,
|
||||
isSplmntPrgrsStts && Boolean(fnrsCnsltnDocNo),
|
||||
);
|
||||
const hasCompletedDraft = Boolean(findCompletedPwrkDraft(pwrkCnsltnData));
|
||||
|
||||
const { data: masterSharTree = [] } = useFnrsSharSeMngTree();
|
||||
const { data: acntgTree = [] } = useAcntgSeMngTree();
|
||||
const { data: essentialFiles = [] } = useEsntlAtchFileMngTree({ docSeComCd: "FNRS" });
|
||||
@@ -136,7 +126,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
(item) => !item.splmntCn || !item.splmntCn.trim(),
|
||||
);
|
||||
if (hasEmptySplmntCn) {
|
||||
alert("보완 요청 내용이 있습니다.\n보완 완료 내용을 입력해 주세요.");
|
||||
alert("보완요청내용이 있습니다. 보완 완료내용을 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -184,11 +174,6 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
async function onApply(data: FnrsCnsltnRegistFormData) {
|
||||
if (isSubmitting) return;
|
||||
|
||||
if (data.privacyConsent !== "Y") {
|
||||
alert("개인정보 수집/이용에 동의해야 신청이 가능합니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isFormChecklistComplete(data.cklstResultList)) {
|
||||
alert("모든 체크리스트 항목을 체크해야 신청이 가능합니다.");
|
||||
return;
|
||||
@@ -275,10 +260,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
initialEsntlFiles={isEditMode ? initialData?.esntlFileList : undefined}
|
||||
initialEtcFiles={isEditMode ? initialData?.etcFileList : undefined}
|
||||
/>
|
||||
<SplmntDmndFormSection
|
||||
initialData={initialData?.splmntDmndList}
|
||||
readOnly={!isSplmntPrgrsStts}
|
||||
/>
|
||||
<SplmntDmndFormSection readOnly={!isSplmntPrgrsStts} />
|
||||
|
||||
<div className="sticky bottom-0 z-40 !-mb-2 border-t bg-white pb-8 pt-6 shadow-[0_-4px_12px_rgba(0,0,0,0.06)]">
|
||||
<div className="mx-4 flex flex-wrap justify-center gap-3 sm:gap-4">
|
||||
@@ -318,7 +300,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{hasCompletedDraft ? (
|
||||
{fnrsCnsltnDocNo ? (
|
||||
<Button
|
||||
color="orange"
|
||||
type="button"
|
||||
@@ -342,7 +324,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
{hasCompletedDraft && fnrsCnsltnDocNo ? (
|
||||
{fnrsCnsltnDocNo ? (
|
||||
<PwrkCnsltnDocMngDialog
|
||||
open={isPwrkCnsltnDocMngOpen}
|
||||
fnrsCnsltnDocNo={fnrsCnsltnDocNo}
|
||||
|
||||
@@ -14,11 +14,7 @@ import {
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { Skeleton } from "@common/components";
|
||||
|
||||
import {
|
||||
getAiPendingMessage,
|
||||
isAiPendingStatus,
|
||||
isRagIndexingInProgress,
|
||||
} from "../lib/ai-pending-utils";
|
||||
import { getAiPendingMessage, isAiPendingStatus, isRagIndexingInProgress } from "../lib/ai-pending-utils";
|
||||
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";
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
extractTaskCodes,
|
||||
getChecklistDisplayRows,
|
||||
} from "../../lib/checklist-utils";
|
||||
import { resolveVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import { getVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import type { ReviewStepCode } from "../../model/view-types";
|
||||
import {
|
||||
CHECKLIST_TAB_LABELS,
|
||||
type ChecklistFormState,
|
||||
REVIEW_STEP_LABELS,
|
||||
ROLE_TO_REVIEW_STEP,
|
||||
TASK_CODE_LABELS,
|
||||
} from "../../model/view-types";
|
||||
@@ -37,8 +37,8 @@ function ChecklistSection({
|
||||
setFormState,
|
||||
}: ChecklistSectionProps) {
|
||||
const visibleReviewSteps = useMemo(
|
||||
() => resolveVisibleReviewSteps(prgrsSttsComCd, userRoleCode, cklstResultList),
|
||||
[prgrsSttsComCd, userRoleCode, cklstResultList],
|
||||
() => getVisibleReviewSteps(prgrsSttsComCd, userRoleCode),
|
||||
[prgrsSttsComCd, userRoleCode],
|
||||
);
|
||||
const taskCodes = useMemo(() => extractTaskCodes(cklstResultList), [cklstResultList]);
|
||||
|
||||
@@ -87,7 +87,7 @@ function ChecklistSection({
|
||||
<TabsList>
|
||||
{visibleReviewSteps.map((stepCode) => (
|
||||
<TabsTrigger key={stepCode} value={stepCode} className="!px-6 !py-3 text-xl">
|
||||
{CHECKLIST_TAB_LABELS[stepCode]}
|
||||
{REVIEW_STEP_LABELS[stepCode]}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
@@ -68,7 +68,7 @@ export const instlSncnRegistSchema = z.object({
|
||||
splmntDmndList: z.array(
|
||||
instlSncnSplmntDmndItemSchema.extend({
|
||||
splmntArtclCdNm: z.string().nullish(),
|
||||
splmntDmndAtchFileNm: z.string().nullish(),
|
||||
splmntDmndAtchFileNm: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
privacyConsent: z.enum(["Y", "N"], {
|
||||
@@ -82,7 +82,7 @@ export function formatTaskSeComCdForApi(taskSeComCd: string[]): string {
|
||||
return taskSeComCd.join(",");
|
||||
}
|
||||
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string | null): string[] {
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string): string[] {
|
||||
if (!taskSeComCd) return [];
|
||||
return taskSeComCd.split(",").filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CklstRvwRsltTreeDto, ReviewStepCode } from "@/entities/instl-sncn";
|
||||
import type { ReviewStepCode } from "@/entities/instl-sncn";
|
||||
|
||||
import {
|
||||
INSTL_PROGRESS_ORDER,
|
||||
@@ -27,25 +27,6 @@ export function getVisibleReviewSteps(
|
||||
return REVIEW_STEP_CODES.slice(0, count);
|
||||
}
|
||||
|
||||
export function resolveVisibleReviewSteps(
|
||||
prgrsSttsComCd: string,
|
||||
userRoleCode: string | undefined,
|
||||
cklstResultList: CklstRvwRsltTreeDto[],
|
||||
): ReviewStepCode[] {
|
||||
const byProgressAndRole = getVisibleReviewSteps(prgrsSttsComCd, userRoleCode);
|
||||
const currentStep = byProgressAndRole.at(-1);
|
||||
const stepsWithData = new Set<ReviewStepCode>();
|
||||
|
||||
for (const row of cklstResultList) {
|
||||
const step = row.rvwStpComCd as ReviewStepCode | undefined;
|
||||
if (step && REVIEW_STEP_CODES.includes(step)) stepsWithData.add(step);
|
||||
}
|
||||
|
||||
return REVIEW_STEP_CODES.filter(
|
||||
(step) => stepsWithData.has(step) || step === currentStep,
|
||||
);
|
||||
}
|
||||
|
||||
export function getEditableReviewStep(
|
||||
dtlInstCd?: string,
|
||||
prgrsSttsComCd?: string,
|
||||
|
||||
@@ -9,10 +9,7 @@ import type {
|
||||
ReviewStepCode,
|
||||
} from "@/entities/instl-sncn";
|
||||
import { instlSncnApplySaveSchema } from "@/entities/instl-sncn";
|
||||
import {
|
||||
type SplmntDmndDialogConfirmPayload,
|
||||
toEsntlAtchFileSaveList,
|
||||
} from "@/widgets/pbadms-sprt";
|
||||
import { type SplmntDmndDialogConfirmPayload, toEsntlAtchFileSaveList } from "@/widgets/pbadms-sprt";
|
||||
|
||||
import type { ChecklistFormState } from "../model/view-types";
|
||||
|
||||
|
||||
@@ -114,7 +114,7 @@ function getBadgeColor(state: BadgeState) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
function formatOptional(value: string | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
|
||||
@@ -48,12 +48,6 @@ export type ChecklistStepMap = Record<ReviewStepCode, CklstRvwRsltTreeDto[]>;
|
||||
|
||||
export const REVIEW_STEP_CODES: ReviewStepCode[] = ["LOCAL", "ENV", "KECO"];
|
||||
|
||||
export const CHECKLIST_TAB_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "자가체크",
|
||||
ENV: "기술검토",
|
||||
KECO: "환경청검토",
|
||||
};
|
||||
|
||||
export const REVIEW_STEP_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "지자체",
|
||||
ENV: "환경청",
|
||||
|
||||
@@ -11,10 +11,10 @@ import {
|
||||
extractTaskCodes,
|
||||
getChecklistDisplayRows,
|
||||
} from "../../lib/checklist-utils";
|
||||
import { resolveVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import { getVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import {
|
||||
CHECKLIST_TAB_LABELS,
|
||||
type ChecklistFormState,
|
||||
REVIEW_STEP_LABELS,
|
||||
ROLE_TO_REVIEW_STEP,
|
||||
TASK_CODE_LABELS,
|
||||
} from "../../model/view-types";
|
||||
@@ -36,8 +36,8 @@ function ChecklistSection({
|
||||
setFormState,
|
||||
}: ChecklistSectionProps) {
|
||||
const visibleReviewSteps = useMemo(
|
||||
() => resolveVisibleReviewSteps(prgrsSttsComCd, userRoleCode, cklstResultList),
|
||||
[prgrsSttsComCd, userRoleCode, cklstResultList],
|
||||
() => getVisibleReviewSteps(prgrsSttsComCd, userRoleCode),
|
||||
[prgrsSttsComCd, userRoleCode],
|
||||
);
|
||||
const taskCodes = useMemo(() => extractTaskCodes(cklstResultList), [cklstResultList]);
|
||||
|
||||
@@ -86,7 +86,7 @@ function ChecklistSection({
|
||||
<TabsList>
|
||||
{visibleReviewSteps.map((stepCode) => (
|
||||
<TabsTrigger key={stepCode} value={stepCode} className="!px-6 !py-3 text-xl">
|
||||
{CHECKLIST_TAB_LABELS[stepCode]}
|
||||
{REVIEW_STEP_LABELS[stepCode]}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { type CostItemDef, roundAmount } from "@/widgets/pbadms-sprt";
|
||||
|
||||
import { type BizCstMatrixItem, getAmt, PLAN_STEPS } from "./biz-cst-matrix-utils";
|
||||
import { type BizCstMatrixItem,getAmt, PLAN_STEPS } from "./biz-cst-matrix-utils";
|
||||
|
||||
export interface FnrsCstItem {
|
||||
planStpNo: number;
|
||||
gvexpAmt?: number | null;
|
||||
lcexpAmt?: number | null;
|
||||
orgprBdchgAmt?: number | null;
|
||||
etcAmt?: number | null;
|
||||
sumAmt?: number | null;
|
||||
gvexpAmt?: number;
|
||||
lcexpAmt?: number;
|
||||
orgprBdchgAmt?: number;
|
||||
etcAmt?: number;
|
||||
sumAmt?: number;
|
||||
}
|
||||
|
||||
export interface FnrsCstStepRemaining {
|
||||
@@ -32,7 +32,10 @@ function getBizCstStepTotal(
|
||||
|
||||
function sumFnrsCstLeaf(item: FnrsCstItem): number {
|
||||
return roundAmount(
|
||||
(item.gvexpAmt ?? 0) + (item.lcexpAmt ?? 0) + (item.orgprBdchgAmt ?? 0) + (item.etcAmt ?? 0),
|
||||
(item.gvexpAmt ?? 0) +
|
||||
(item.lcexpAmt ?? 0) +
|
||||
(item.orgprBdchgAmt ?? 0) +
|
||||
(item.etcAmt ?? 0),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import type { CklstRvwRsltTreeDto } from "@/entities/sewer-bsc-plan";
|
||||
|
||||
import {
|
||||
PBADMS_PROGRESS_ORDER,
|
||||
REVIEW_STEP_CODES,
|
||||
@@ -30,25 +28,6 @@ export function getVisibleReviewSteps(
|
||||
return REVIEW_STEP_CODES.slice(0, count);
|
||||
}
|
||||
|
||||
export function resolveVisibleReviewSteps(
|
||||
prgrsSttsComCd: string,
|
||||
userRoleCode: string | undefined,
|
||||
cklstResultList: CklstRvwRsltTreeDto[],
|
||||
): ReviewStepCode[] {
|
||||
const byProgressAndRole = getVisibleReviewSteps(prgrsSttsComCd, userRoleCode);
|
||||
const currentStep = byProgressAndRole.at(-1);
|
||||
const stepsWithData = new Set<ReviewStepCode>();
|
||||
|
||||
for (const row of cklstResultList) {
|
||||
const step = row.rvwStpComCd as ReviewStepCode | undefined;
|
||||
if (step && REVIEW_STEP_CODES.includes(step)) stepsWithData.add(step);
|
||||
}
|
||||
|
||||
return REVIEW_STEP_CODES.filter(
|
||||
(step) => stepsWithData.has(step) || step === currentStep,
|
||||
);
|
||||
}
|
||||
|
||||
export function getEditableReviewStep(
|
||||
dtlInstCd?: string,
|
||||
prgrsSttsComCd?: string,
|
||||
|
||||
@@ -16,9 +16,9 @@ import {
|
||||
export const sewerBscPlanRegistSchema = z.object({
|
||||
sewerBscPlan: sewerBscPlanSaveSchema.extend({
|
||||
sewerBscPlanNm: z.string().min(1, "기본계획명은 필수입니다"),
|
||||
sewerBscPlanSeCdNm: z.string().nullish(),
|
||||
taskSeCdNm: z.string().nullish(),
|
||||
sido: z.string().nullish(),
|
||||
sewerBscPlanSeCdNm: z.string().optional(),
|
||||
taskSeCdNm: z.string().optional(),
|
||||
sido: z.string().optional(),
|
||||
gugun: z.string().nullish(),
|
||||
}),
|
||||
sewerBscPlanDtl: sewerBscPlanDtlSaveSchema.extend({
|
||||
@@ -61,7 +61,7 @@ export const sewerBscPlanRegistSchema = z.object({
|
||||
sewerBscPlanTmplntItemSchema.extend({
|
||||
swtrmtFcltCd: z.string(),
|
||||
swtrmtFcltNm: z.string().nullish(),
|
||||
atchFileNm: z.string().nullish(),
|
||||
atchFileNm: z.string().optional(),
|
||||
sewerBscPlanTmplntDtlList: z
|
||||
.array(
|
||||
sewerBscPlanTmplntDtlItemSchema.extend({
|
||||
@@ -69,7 +69,7 @@ export const sewerBscPlanRegistSchema = z.object({
|
||||
swtrmtFcltCd: z.string(),
|
||||
}),
|
||||
)
|
||||
.nullish(),
|
||||
.optional(),
|
||||
}),
|
||||
),
|
||||
cklstResultList: z.array(
|
||||
@@ -78,7 +78,7 @@ export const sewerBscPlanRegistSchema = z.object({
|
||||
cklstArtclSn: z.number(),
|
||||
cklstRvwSn: z.number(),
|
||||
selected: z.enum(["Y", "N"]).nullish(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
picRvwCn: z.string().nullable().optional(),
|
||||
}),
|
||||
),
|
||||
esntlAtchFileList: z.array(
|
||||
@@ -90,9 +90,9 @@ export const sewerBscPlanRegistSchema = z.object({
|
||||
splmntDmndList: z.array(
|
||||
sewerBscPlanSplmntDmndItemSchema.extend({
|
||||
splmntArtclCdNm: z.string().nullish(),
|
||||
splmntDmndAtchFileNm: z.string().nullish(),
|
||||
splmntAtchFileNm: z.string().nullish(),
|
||||
splmntAtchFileAtflId: z.string().nullish(),
|
||||
splmntDmndAtchFileNm: z.string().optional(),
|
||||
splmntAtchFileNm: z.string().optional(),
|
||||
splmntAtchFileAtflId: z.string().optional(),
|
||||
}),
|
||||
),
|
||||
privacyConsent: z.enum(["Y", "N"], {
|
||||
|
||||
@@ -42,12 +42,6 @@ export type ChecklistStepMap = Record<ReviewStepCode, CklstRvwRsltTreeDto[]>;
|
||||
|
||||
export const REVIEW_STEP_CODES: ReviewStepCode[] = ["LOCAL", "ENV", "KECO"];
|
||||
|
||||
export const CHECKLIST_TAB_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "자가체크",
|
||||
ENV: "기술검토",
|
||||
KECO: "환경청검토",
|
||||
};
|
||||
|
||||
export const REVIEW_STEP_LABELS: Record<ReviewStepCode, string> = {
|
||||
LOCAL: "지자체",
|
||||
ENV: "환경청",
|
||||
|
||||
@@ -31,7 +31,7 @@ interface PreviousPlanLookupDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
/** 부모 폼 `sewerBscPlan.sido` 표시명 */
|
||||
initialSidoNm?: string | null;
|
||||
initialSidoNm?: string;
|
||||
/** 부모 폼 `sewerBscPlan.gugun` 표시명 */
|
||||
initialGugunNm?: string | null;
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
extractTaskCodes,
|
||||
getChecklistDisplayRows,
|
||||
} from "../../lib/checklist-utils";
|
||||
import { resolveVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import { getVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import type { ReviewStepCode } from "../../model/view-types";
|
||||
import {
|
||||
CHECKLIST_TAB_LABELS,
|
||||
type ChecklistFormState,
|
||||
REVIEW_STEP_LABELS,
|
||||
ROLE_TO_REVIEW_STEP,
|
||||
TASK_CODE_LABELS,
|
||||
} from "../../model/view-types";
|
||||
@@ -37,8 +37,8 @@ function ChecklistSection({
|
||||
setFormState,
|
||||
}: ChecklistSectionProps) {
|
||||
const visibleReviewSteps = useMemo(
|
||||
() => resolveVisibleReviewSteps(prgrsSttsComCd, userRoleCode, cklstResultList),
|
||||
[prgrsSttsComCd, userRoleCode, cklstResultList],
|
||||
() => getVisibleReviewSteps(prgrsSttsComCd, userRoleCode),
|
||||
[prgrsSttsComCd, userRoleCode],
|
||||
);
|
||||
const taskCodes = useMemo(() => extractTaskCodes(cklstResultList), [cklstResultList]);
|
||||
|
||||
@@ -87,7 +87,7 @@ function ChecklistSection({
|
||||
<TabsList>
|
||||
{visibleReviewSteps.map((stepCode) => (
|
||||
<TabsTrigger key={stepCode} value={stepCode} className="!px-6 !py-3 text-xl">
|
||||
{CHECKLIST_TAB_LABELS[stepCode]}
|
||||
{REVIEW_STEP_LABELS[stepCode]}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
@@ -214,8 +214,8 @@ function TmplntFacilityMatrix({
|
||||
interface TmplntFacilityAttachmentsProps {
|
||||
facilityIndex: number;
|
||||
facilityName?: string;
|
||||
atchFileGroupId?: string | null;
|
||||
atchFileNm?: string | null;
|
||||
atchFileGroupId?: string;
|
||||
atchFileNm?: string;
|
||||
}
|
||||
|
||||
function TmplntFacilityAttachments({
|
||||
|
||||
@@ -205,14 +205,14 @@ export interface EsntlAtchFileSaveBase {
|
||||
export interface SplmntDmndSaveBase {
|
||||
splmntDmndSn?: number | null;
|
||||
splmntArtclComCd?: string | null;
|
||||
splmntDmndCn?: string | null;
|
||||
splmntDmndDt?: string | null;
|
||||
splmntDmndAtchFileGroupId?: string | null;
|
||||
splmntCn?: string | null;
|
||||
splmntPicNm?: string | null;
|
||||
splmntCmptnDt?: string | null;
|
||||
splmntAtchFileGroupId?: string | null;
|
||||
cmptnYn?: string | null;
|
||||
splmntDmndCn?: string;
|
||||
splmntDmndDt?: string;
|
||||
splmntDmndAtchFileGroupId?: string;
|
||||
splmntCn?: string;
|
||||
splmntPicNm?: string;
|
||||
splmntCmptnDt?: string;
|
||||
splmntAtchFileGroupId?: string;
|
||||
cmptnYn?: string;
|
||||
}
|
||||
|
||||
/** 검토의견 저장 공통 필드 (문서번호 제외) */
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
interface CanEditPbadmsDocParams {
|
||||
docSeComCd: string;
|
||||
prgrsSttsComCd?: string | null;
|
||||
rgtr?: string | null;
|
||||
loggedInUserUnqId?: string | null;
|
||||
prgrsSttsComCd: string | undefined;
|
||||
rgtr: string | undefined;
|
||||
loggedInUserUnqId: string | undefined;
|
||||
}
|
||||
|
||||
function canEditPbadmsDoc({
|
||||
|
||||
@@ -17,8 +17,8 @@ export interface FnrsSharListItem {
|
||||
fnrsSharSeCd: string;
|
||||
exstnAmt: number;
|
||||
chgRqtAmt: number;
|
||||
ajmtAmt?: number | null;
|
||||
rmrkCn?: string | null;
|
||||
ajmtAmt?: number;
|
||||
rmrkCn?: string;
|
||||
}
|
||||
|
||||
export type FnrsSharTreeNode = FnrsSharSeMngTreeDto | FnrsSharBizCstTreeDto;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Download } from "lucide-react";
|
||||
@@ -78,7 +78,7 @@ function AttachmentsFormSection({
|
||||
initialEsntlFiles,
|
||||
initialEtcFiles,
|
||||
}: AttachmentsFormSectionProps) {
|
||||
const { getValues, setValue } = useFormContext<AttachmentsFormData>();
|
||||
const { control, getValues, setValue } = useFormContext<AttachmentsFormData>();
|
||||
const queryClient = useQueryClient();
|
||||
const [uploadedFiles, setUploadedFiles] = useState<Record<string, UploadedFileItem[]>>({});
|
||||
const [etcFiles, setEtcFiles] = useState<EtcFileItem[]>([]);
|
||||
@@ -373,83 +373,89 @@ function AttachmentsFormSection({
|
||||
return (
|
||||
<SectionCollapsible title="첨부파일">
|
||||
<div className="space-y-6">
|
||||
<div className="px-2">
|
||||
<h3 className="mb-4 text-xl font-semibold">필수 첨부파일</h3>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="44%" />
|
||||
<col width="12%" />
|
||||
<col width="44%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[30rem]" align="left">
|
||||
파일명
|
||||
</TableHead>
|
||||
<TableHead>파일 확장자</TableHead>
|
||||
<TableHead>파일첨부</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{essentialFiles.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="py-12 text-center text-muted-foreground">
|
||||
조회된 필수 첨부파일 항목이 없습니다.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
essentialFiles.map((file) => {
|
||||
const atflCd = file.atchFileGroupId ?? "";
|
||||
|
||||
return (
|
||||
<TableRow key={file.esntlAtchFileId}>
|
||||
<TableHead
|
||||
className="group break-keep font-medium"
|
||||
data-required={file.esntlAtchYn === "Y"}
|
||||
align="left"
|
||||
>
|
||||
<div className="inline-flex w-[calc(100%-1rem)] items-center justify-between gap-10">
|
||||
{file.esntlAtchFileNm}
|
||||
{atflCd ? (
|
||||
<Button
|
||||
size="sm"
|
||||
color="white"
|
||||
onClick={() => downloadTemplate(atflCd)}
|
||||
>
|
||||
<Download className="!size-5 shrink-0" />
|
||||
양식내려받기
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableCell align="center">{file.esntlAtchFileRmrk}</TableCell>
|
||||
<TableCell className="space-y-2">
|
||||
<AttachmentFileField
|
||||
files={(uploadedFiles[file.esntlAtchFileId] ?? []).map((item) => ({
|
||||
fileKey: item.fileKey,
|
||||
fileName: item.file?.name ?? item.fileName,
|
||||
onDownload: item.atflId
|
||||
? () => downloadByAtflId(item.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={`${file.esntlAtchFileId}-file-input`}
|
||||
isUploadingByKey={isUploading}
|
||||
accept={toFileAccept(file.esntlAtchFileRmrk) || undefined}
|
||||
onAdd={(fileList) =>
|
||||
void handleEssentialFileUpload(file.esntlAtchFileId, fileList)
|
||||
}
|
||||
onRemove={(index) =>
|
||||
void removeEssentialFile(file.esntlAtchFileId, index)
|
||||
}
|
||||
/>
|
||||
<Controller
|
||||
name="esntlAtchFileList"
|
||||
control={control}
|
||||
render={() => (
|
||||
<div className="px-2">
|
||||
<h3 className="mb-4 text-xl font-semibold">필수 첨부파일</h3>
|
||||
<Table variant="secondary" className="table-fixed">
|
||||
<colgroup>
|
||||
<col width="44%" />
|
||||
<col width="12%" />
|
||||
<col width="44%" />
|
||||
</colgroup>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[30rem]" align="left">
|
||||
파일명
|
||||
</TableHead>
|
||||
<TableHead>파일 확장자</TableHead>
|
||||
<TableHead>파일첨부</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{essentialFiles.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={4} className="py-12 text-center text-muted-foreground">
|
||||
조회된 필수 첨부파일 항목이 없습니다.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
essentialFiles.map((file) => {
|
||||
const atflCd = file.atchFileGroupId ?? "";
|
||||
|
||||
return (
|
||||
<TableRow key={file.esntlAtchFileId}>
|
||||
<TableHead
|
||||
className="group break-keep font-medium"
|
||||
data-required={file.esntlAtchYn === "Y"}
|
||||
align="left"
|
||||
>
|
||||
<div className="inline-flex w-[calc(100%-1rem)] items-center justify-between gap-10">
|
||||
{file.esntlAtchFileNm}
|
||||
{atflCd ? (
|
||||
<Button
|
||||
size="sm"
|
||||
color="white"
|
||||
onClick={() => downloadTemplate(atflCd)}
|
||||
>
|
||||
<Download className="!size-5 shrink-0" />
|
||||
양식내려받기
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableCell align="center">{file.esntlAtchFileRmrk}</TableCell>
|
||||
<TableCell className="space-y-2">
|
||||
<AttachmentFileField
|
||||
files={(uploadedFiles[file.esntlAtchFileId] ?? []).map((item) => ({
|
||||
fileKey: item.fileKey,
|
||||
fileName: item.file?.name ?? item.fileName,
|
||||
onDownload: item.atflId
|
||||
? () => downloadByAtflId(item.atflId!)
|
||||
: undefined,
|
||||
}))}
|
||||
inputId={`${file.esntlAtchFileId}-file-input`}
|
||||
isUploadingByKey={isUploading}
|
||||
accept={toFileAccept(file.esntlAtchFileRmrk) || undefined}
|
||||
onAdd={(fileList) =>
|
||||
void handleEssentialFileUpload(file.esntlAtchFileId, fileList)
|
||||
}
|
||||
onRemove={(index) =>
|
||||
void removeEssentialFile(file.esntlAtchFileId, index)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className="px-2">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
|
||||
@@ -43,13 +43,12 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{files.map((file) => (
|
||||
<div key={file.atflId}>
|
||||
<AttachmentDownloadLink
|
||||
atflId={file.atflId}
|
||||
fileName={file.orgnlFileNm || "다운로드"}
|
||||
className="items-center"
|
||||
/>
|
||||
</div>
|
||||
<AttachmentDownloadLink
|
||||
key={file.atflId}
|
||||
atflId={file.atflId}
|
||||
fileName={file.orgnlFileNm || "다운로드"}
|
||||
className="items-center"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field } from "@/shared/ui/field";
|
||||
@@ -31,16 +31,6 @@ interface BusinessCostFormData {
|
||||
|
||||
type CostFieldKey = "bscPlanAmt" | "bgtRfltAmt" | "tstmDesignAmt";
|
||||
|
||||
function isSameBizCstCodes(
|
||||
current: BusinessCostFormData["bizCstList"],
|
||||
next: BusinessCostFormData["bizCstList"],
|
||||
) {
|
||||
return (
|
||||
current.length === next.length &&
|
||||
current.every((item, index) => item.acntgSeCd === next[index]?.acntgSeCd)
|
||||
);
|
||||
}
|
||||
|
||||
function BusinessCostFormSection() {
|
||||
const { control, setValue, getValues } = useFormContext<BusinessCostFormData>();
|
||||
const bizCstList = useWatch({ control, name: "bizCstList" }) ?? [];
|
||||
@@ -52,22 +42,22 @@ function BusinessCostFormSection() {
|
||||
useEffect(() => {
|
||||
if (costItems.length === 0) return;
|
||||
|
||||
const current = getValues("bizCstList") ?? [];
|
||||
const existingMap = new Map(current.map((item) => [item.acntgSeCd, item]));
|
||||
const next = costItems.map((item) => {
|
||||
const existing = existingMap.get(item.code);
|
||||
if (existing) return existing;
|
||||
const existingMap = new Map(getValues("bizCstList").map((item) => [item.acntgSeCd, item]));
|
||||
|
||||
return {
|
||||
acntgSeCd: item.code,
|
||||
bscPlanAmt: 0,
|
||||
bgtRfltAmt: 0,
|
||||
tstmDesignAmt: 0,
|
||||
};
|
||||
});
|
||||
setValue(
|
||||
"bizCstList",
|
||||
costItems.map((item) => {
|
||||
const existing = existingMap.get(item.code);
|
||||
if (existing) return existing;
|
||||
|
||||
if (isSameBizCstCodes(current, next)) return;
|
||||
setValue("bizCstList", next);
|
||||
return {
|
||||
acntgSeCd: item.code,
|
||||
bscPlanAmt: 0,
|
||||
bgtRfltAmt: 0,
|
||||
tstmDesignAmt: 0,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}, [costItems, getValues, setValue]);
|
||||
|
||||
function handleCostChange(index: number, fieldKey: CostFieldKey, value: string) {
|
||||
@@ -129,37 +119,58 @@ function BusinessCostFormSection() {
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
type="number"
|
||||
value={data.bgtRfltAmt ?? ""}
|
||||
onChange={(e) => handleCostChange(idx, "bgtRfltAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`bizCstList.${idx}.bgtRfltAmt`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value || ""}
|
||||
onChange={(e) => handleCostChange(idx, "bgtRfltAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
type="number"
|
||||
value={data.bscPlanAmt ?? ""}
|
||||
onChange={(e) => handleCostChange(idx, "bscPlanAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`bizCstList.${idx}.bscPlanAmt`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value || ""}
|
||||
onChange={(e) => handleCostChange(idx, "bscPlanAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
type="number"
|
||||
value={data.tstmDesignAmt ?? ""}
|
||||
onChange={(e) => handleCostChange(idx, "tstmDesignAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`bizCstList.${idx}.tstmDesignAmt`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value || ""}
|
||||
onChange={(e) => handleCostChange(idx, "tstmDesignAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-right">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field } from "@/shared/ui/field";
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
TableRow,
|
||||
} from "@common/components";
|
||||
|
||||
import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils";
|
||||
import { buildCnstpScaleItems, recalculateBzScaleTotals } from "../lib/business-scale-form-utils";
|
||||
|
||||
interface BusinessScaleFormData {
|
||||
bzScaleList: Array<{
|
||||
@@ -31,16 +31,6 @@ interface BusinessScaleFormData {
|
||||
|
||||
type ScaleFieldKey = "bscPl" | "budgetReflct" | "thtiDsgn";
|
||||
|
||||
function isSameBzScaleCodes(
|
||||
current: BusinessScaleFormData["bzScaleList"],
|
||||
next: BusinessScaleFormData["bzScaleList"],
|
||||
) {
|
||||
return (
|
||||
current.length === next.length &&
|
||||
current.every((item, index) => item.cnstpSeCd === next[index]?.cnstpSeCd)
|
||||
);
|
||||
}
|
||||
|
||||
function BusinessScaleFormSection() {
|
||||
const { control, setValue, getValues } = useFormContext<BusinessScaleFormData>();
|
||||
const bzScaleList = useWatch({ control, name: "bzScaleList" }) ?? [];
|
||||
@@ -52,8 +42,7 @@ function BusinessScaleFormSection() {
|
||||
useEffect(() => {
|
||||
if (scaleItems.length === 0) return;
|
||||
|
||||
const current = getValues("bzScaleList") ?? [];
|
||||
const existingMap = new Map(current.map((item) => [item.cnstpSeCd, item]));
|
||||
const existingMap = new Map(getValues("bzScaleList").map((item) => [item.cnstpSeCd, item]));
|
||||
|
||||
const next = scaleItems.map((item) => {
|
||||
const existing = existingMap.get(item.code);
|
||||
@@ -68,7 +57,6 @@ function BusinessScaleFormSection() {
|
||||
};
|
||||
});
|
||||
|
||||
if (isSameBzScaleCodes(current, next)) return;
|
||||
setValue("bzScaleList", recalculateBzScaleTotals(next, scaleItems));
|
||||
}, [scaleItems, getValues, setValue]);
|
||||
|
||||
@@ -124,37 +112,58 @@ function BusinessScaleFormSection() {
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
type="number"
|
||||
value={data.bscPl ?? ""}
|
||||
onChange={(e) => handleScaleChange(idx, "bscPl", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`bzScaleList.${idx}.bscPl`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value || ""}
|
||||
onChange={(e) => handleScaleChange(idx, "bscPl", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
type="number"
|
||||
value={data.budgetReflct ?? ""}
|
||||
onChange={(e) => handleScaleChange(idx, "budgetReflct", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`bzScaleList.${idx}.budgetReflct`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value || ""}
|
||||
onChange={(e) => handleScaleChange(idx, "budgetReflct", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
type="number"
|
||||
value={data.thtiDsgn ?? ""}
|
||||
onChange={(e) => handleScaleChange(idx, "thtiDsgn", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`bzScaleList.${idx}.thtiDsgn`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value || ""}
|
||||
onChange={(e) => handleScaleChange(idx, "thtiDsgn", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={`text-right ${item.readonly ? "bg-muted" : ""}`}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-right">
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
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";
|
||||
import {
|
||||
Button,
|
||||
RadioGroup,
|
||||
RadioGroupItem,
|
||||
Skeleton,
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -122,21 +125,11 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
|
||||
}
|
||||
}, [checklistTabs, activeTab]);
|
||||
|
||||
function handleSelectChange(resultIdx: number, value: "Y" | "N") {
|
||||
const currentList = getValues("cklstResultList") as CklstResultListItem[];
|
||||
const row = currentList[resultIdx];
|
||||
if (!row) return;
|
||||
|
||||
const newList = [...currentList];
|
||||
newList[resultIdx] = { ...row, selected: value };
|
||||
setValue("cklstResultList", newList, { shouldDirty: true });
|
||||
}
|
||||
|
||||
function handleSelectAll(tabCode: string, value: "Y" | "N") {
|
||||
const tab = checklistTabs.find((t) => t.code === tabCode);
|
||||
if (!tab) return;
|
||||
|
||||
const currentList = getValues("cklstResultList") as CklstResultListItem[];
|
||||
const currentList = getValues("cklstResultList");
|
||||
const newList = [...currentList];
|
||||
|
||||
tab.items
|
||||
@@ -148,7 +141,7 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
|
||||
}
|
||||
});
|
||||
|
||||
setValue("cklstResultList", newList, { shouldDirty: true });
|
||||
setValue("cklstResultList", newList);
|
||||
}
|
||||
|
||||
const hasSelectedTaskCodes = Boolean(taskSeComCdArray?.length);
|
||||
@@ -241,8 +234,6 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
|
||||
{tab.items.map((item) => {
|
||||
const resultIdx = cklstRvwSnIndexMap.get(item.cklstRvwSn) ?? -1;
|
||||
const showRadios = item.bchckUseYn === "Y";
|
||||
const selected = cklstResultList[resultIdx]?.selected;
|
||||
const radioName = `check-${item.cklstRvwSn}`;
|
||||
|
||||
return (
|
||||
<TableRow key={item.cklstRvwSn}>
|
||||
@@ -257,30 +248,66 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
|
||||
{item.content}
|
||||
</TableCell>
|
||||
{showRadios && resultIdx >= 0 ? (
|
||||
<>
|
||||
<TableCell className="text-center">
|
||||
<input
|
||||
type="radio"
|
||||
name={radioName}
|
||||
value="Y"
|
||||
checked={selected === "Y"}
|
||||
onChange={() => handleSelectChange(resultIdx, "Y")}
|
||||
aria-label="예"
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<input
|
||||
type="radio"
|
||||
name={radioName}
|
||||
value="N"
|
||||
checked={selected === "N"}
|
||||
onChange={() => handleSelectChange(resultIdx, "N")}
|
||||
aria-label="아니오"
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</TableCell>
|
||||
</>
|
||||
<Controller
|
||||
name={`cklstResultList.${resultIdx}.selected`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<>
|
||||
<TableCell className="text-center">
|
||||
<RadioGroup
|
||||
name={field.name}
|
||||
value={field.value === "Y" ? "Y" : undefined}
|
||||
onValueChange={field.onChange}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<FieldLabel
|
||||
htmlFor={`check-${item.cklstRvwSn}-Y`}
|
||||
className="!w-auto justify-center border-none"
|
||||
>
|
||||
<Field
|
||||
orientation="horizontal"
|
||||
data-invalid={fieldState.invalid}
|
||||
className="w-auto"
|
||||
>
|
||||
<RadioGroupItem
|
||||
value="Y"
|
||||
id={`check-${item.cklstRvwSn}-Y`}
|
||||
aria-invalid={fieldState.invalid}
|
||||
aria-label="예"
|
||||
/>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</RadioGroup>
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<RadioGroup
|
||||
name={field.name}
|
||||
value={field.value === "N" ? "N" : undefined}
|
||||
onValueChange={field.onChange}
|
||||
className="flex justify-center"
|
||||
>
|
||||
<FieldLabel
|
||||
htmlFor={`check-${item.cklstRvwSn}-N`}
|
||||
className="!w-auto justify-center border-none"
|
||||
>
|
||||
<Field
|
||||
orientation="horizontal"
|
||||
data-invalid={fieldState.invalid}
|
||||
className="w-auto"
|
||||
>
|
||||
<RadioGroupItem
|
||||
value="N"
|
||||
id={`check-${item.cklstRvwSn}-N`}
|
||||
aria-invalid={fieldState.invalid}
|
||||
aria-label="아니오"
|
||||
/>
|
||||
</Field>
|
||||
</FieldLabel>
|
||||
</RadioGroup>
|
||||
</TableCell>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
) : null}
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field } from "@/shared/ui/field";
|
||||
import { useAcntgSeMngTree } from "@/entities/acntg";
|
||||
import { useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import type { AtflMngClientResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import type { FnrsSharBizCstTreeDto } from "@/entities/fnrs-shar-se";
|
||||
import { useFnrsSharSeMngTree } from "@/entities/fnrs-shar-se";
|
||||
import {
|
||||
Button,
|
||||
cn,
|
||||
Input,
|
||||
Skeleton,
|
||||
@@ -36,7 +37,6 @@ import { calcDiffPercent } from "../lib/format-utils";
|
||||
import { BizCstTotalDisplayTable } from "./biz-cst-total-display-table";
|
||||
|
||||
const SHAR_FILE_KEY_PREFIX = "fnrs-shar::";
|
||||
const SHAR_FILE_ACCEPT = ".hwp,.hwpx";
|
||||
|
||||
interface FnrsSharFormData {
|
||||
fnrsSharList: FnrsSharListItem[];
|
||||
@@ -53,12 +53,6 @@ interface FnrsSharFormData {
|
||||
|
||||
type SharFieldKey = "exstnAmt" | "chgRqtAmt" | "rmrkCn";
|
||||
|
||||
interface SharUploadedFile {
|
||||
fileKey: string;
|
||||
fileName: string;
|
||||
atflId?: string;
|
||||
}
|
||||
|
||||
interface FnrsSharFormSectionProps {
|
||||
initialSharTree?: FnrsSharBizCstTreeDto[];
|
||||
initialSharAtchFiles?: AtflMngClientResponseDto[];
|
||||
@@ -68,30 +62,12 @@ function createSharUploadKey(file: File) {
|
||||
return `${SHAR_FILE_KEY_PREFIX}${getDefaultFileKey(file)}`;
|
||||
}
|
||||
|
||||
function toInitialSharFile(file?: AtflMngClientResponseDto): SharUploadedFile | null {
|
||||
if (!file?.orgnlFileNm) return null;
|
||||
|
||||
return {
|
||||
fileKey: file.atflId ? `server::${file.atflId}` : `${SHAR_FILE_KEY_PREFIX}${file.atflCd}`,
|
||||
fileName: file.orgnlFileNm,
|
||||
atflId: file.atflId,
|
||||
};
|
||||
}
|
||||
|
||||
function isSameFnrsSharCodes(current: FnrsSharListItem[], next: FnrsSharListItem[]) {
|
||||
return (
|
||||
current.length === next.length &&
|
||||
current.every((item, index) => item.fnrsSharSeCd === next[index]?.fnrsSharSeCd)
|
||||
);
|
||||
}
|
||||
|
||||
function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsSharFormSectionProps) {
|
||||
const { control, setValue, getValues } = useFormContext<FnrsSharFormData>();
|
||||
const fnrsSharList = useWatch({ control, name: "fnrsSharList" }) ?? [];
|
||||
const [uploadedFile, setUploadedFile] = useState<SharUploadedFile | null>(() =>
|
||||
toInitialSharFile(initialSharAtchFiles?.[0]),
|
||||
const [uploadedFileName, setUploadedFileName] = useState(
|
||||
() => initialSharAtchFiles?.[0]?.orgnlFileNm ?? "",
|
||||
);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const { data: masterTree = [], isLoading: isMasterLoading } = useFnrsSharSeMngTree();
|
||||
const { data: acntgTree = [] } = useAcntgSeMngTree();
|
||||
@@ -102,13 +78,9 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
const isLoading = initialSharTree?.length ? false : isMasterLoading;
|
||||
|
||||
const { uploadFileAsync, isUploading } = useTus({
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
onSuccess: (atflCd) => {
|
||||
if (!atflCd) return;
|
||||
setValue("fnrsCnsltnMng.sharAtchFileGroupId", atflCd, { shouldDirty: true });
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? { ...prev, atflId } : prev));
|
||||
},
|
||||
onError: (_error, _file, fileKey) => {
|
||||
setUploadedFile((prev) => (prev?.fileKey === fileKey ? null : prev));
|
||||
},
|
||||
});
|
||||
const isSharUploading = Object.values(isUploading).some(Boolean);
|
||||
@@ -124,7 +96,6 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
: mergeFnrsSharTreeWithForm(sharTree, []);
|
||||
|
||||
const withTotals = recalculateFnrsSharTotals(merged, sharItems);
|
||||
if (isSameFnrsSharCodes(existing, withTotals)) return;
|
||||
setValue("fnrsSharList", withTotals);
|
||||
}, [sharItems, sharTree, initialSharTree, getValues, setValue]);
|
||||
|
||||
@@ -149,32 +120,16 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
applySharList(draft);
|
||||
}
|
||||
|
||||
async function handleSharFileAdd(files: FileList | null) {
|
||||
const file = files?.[0];
|
||||
async function handleSharFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const file = event.target.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const fileKey = createSharUploadKey(file);
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, { fileKey });
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
setUploadedFileName(file.name);
|
||||
await uploadFileAsync(file, { fileKey });
|
||||
event.target.value = "";
|
||||
}
|
||||
|
||||
const sharAtchFiles = uploadedFile
|
||||
? [
|
||||
{
|
||||
fileKey: uploadedFile.fileKey,
|
||||
fileName: uploadedFile.fileName,
|
||||
onDownload: uploadedFile.atflId
|
||||
? () => downloadByAtflId(uploadedFile.atflId!)
|
||||
: undefined,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SectionCollapsible title="재원분담">
|
||||
@@ -234,26 +189,40 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
{item.label}
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
type="number"
|
||||
value={data.exstnAmt ?? ""}
|
||||
onChange={(e) => handleSharChange(idx, "exstnAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
className={cn("text-right", item.readonly && "bg-muted")}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`fnrsSharList.${idx}.exstnAmt`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) => handleSharChange(idx, "exstnAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={cn("text-right", item.readonly && "bg-muted")}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
type="number"
|
||||
value={data.chgRqtAmt ?? ""}
|
||||
onChange={(e) => handleSharChange(idx, "chgRqtAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
className={cn("text-right", item.readonly && "bg-muted")}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`fnrsSharList.${idx}.chgRqtAmt`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
type="number"
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) => handleSharChange(idx, "chgRqtAmt", e.target.value)}
|
||||
readOnly={item.readonly}
|
||||
aria-invalid={fieldState.invalid}
|
||||
className={cn("text-right", item.readonly && "bg-muted")}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-right">
|
||||
@@ -264,12 +233,19 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
<div className="text-right">{calcDiffPercent(diff, data.exstnAmt || 0)}</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Field>
|
||||
<Input
|
||||
value={data.rmrkCn ?? ""}
|
||||
onChange={(e) => handleSharChange(idx, "rmrkCn", e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
<Controller
|
||||
name={`fnrsSharList.${idx}.rmrkCn`}
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Field data-invalid={fieldState.invalid}>
|
||||
<Input
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) => handleSharChange(idx, "rmrkCn", e.target.value)}
|
||||
aria-invalid={fieldState.invalid}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
@@ -281,20 +257,31 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
|
||||
<div className="mt-6 px-2 pb-2">
|
||||
<h3 className="mb-2 text-xl font-semibold">첨부파일</h3>
|
||||
{!uploadedFile ? (
|
||||
<p className="mb-2 px-1 text-xl text-muted-foreground">
|
||||
총사업비 신청서(.hwp) 파일을 등록해주세요.
|
||||
</p>
|
||||
) : null}
|
||||
<AttachmentFileField
|
||||
files={sharAtchFiles}
|
||||
inputId="fnrs-shar-atch-file-input"
|
||||
isUploadingByKey={isUploading}
|
||||
accept={SHAR_FILE_ACCEPT}
|
||||
multiple={false}
|
||||
addDisabled={isSharUploading}
|
||||
onAdd={(fileList) => void handleSharFileAdd(fileList)}
|
||||
/>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{isSharUploading && uploadedFileName ? (
|
||||
<TextShimmer className="max-w-lg text-xl" active>
|
||||
{uploadedFileName}
|
||||
</TextShimmer>
|
||||
) : (
|
||||
<Input
|
||||
value={uploadedFileName}
|
||||
readOnly
|
||||
placeholder="총사업비 신청서(.hwp) 파일을 등록해주세요."
|
||||
className="max-w-lg rounded-sm"
|
||||
/>
|
||||
)}
|
||||
<Button type="button" color="white" disabled={isSharUploading} asChild>
|
||||
<label className="cursor-pointer">
|
||||
첨부파일
|
||||
<input
|
||||
type="file"
|
||||
accept=".hwp,.hwpx"
|
||||
className="hidden"
|
||||
onChange={handleSharFileChange}
|
||||
/>
|
||||
</label>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SectionCollapsible>
|
||||
);
|
||||
|
||||
@@ -5,12 +5,7 @@ import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { dayjs, getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { AttachmentFileField, SectionCollapsible } from "@/shared/ui";
|
||||
import {
|
||||
AttachmentDownloadLink,
|
||||
attachService,
|
||||
useDownloadAttachmentByAtflId,
|
||||
} from "@/entities/attach";
|
||||
import { FnrsCnsltnSplmntDmndResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import { AttachmentDownloadLink, attachService, useDownloadAttachmentByAtflId } from "@/entities/attach";
|
||||
import {
|
||||
Badge,
|
||||
Table,
|
||||
@@ -51,7 +46,6 @@ interface SplmntDmndFormData {
|
||||
}
|
||||
|
||||
interface SplmntDmndFormSectionProps {
|
||||
initialData?: FnrsCnsltnSplmntDmndResponseDto[];
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
@@ -71,7 +65,7 @@ function getRowKey(item: SplmntDmndFormItem, index: number) {
|
||||
return item.splmntDmndSn != null ? String(item.splmntDmndSn) : `idx-${index}`;
|
||||
}
|
||||
|
||||
function SplmntDmndFormSection({ initialData, readOnly = false }: SplmntDmndFormSectionProps) {
|
||||
function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps) {
|
||||
const formId = useId();
|
||||
const { control, setValue, getValues } = useFormContext<SplmntDmndFormData>();
|
||||
const splmntDmndList = useWatch({ control, name: "splmntDmndList" }) ?? [];
|
||||
@@ -266,7 +260,6 @@ function SplmntDmndFormSection({ initialData, readOnly = false }: SplmntDmndForm
|
||||
const isRowUploading = rowFiles.some((file) => isUploading[file.fileKey] ?? false);
|
||||
const fileInputId = `${formId}-${rowKey}-file`;
|
||||
const isRowReadOnly = readOnly || item.cmptnYn === "Y";
|
||||
const splmntAtchFileInfo = initialData?.[index]?.splmntAtchFileInfo;
|
||||
|
||||
return (
|
||||
<TableRow key={rowKey}>
|
||||
@@ -289,15 +282,7 @@ function SplmntDmndFormSection({ initialData, readOnly = false }: SplmntDmndForm
|
||||
</TableCell>
|
||||
<TableCell className="align-top">
|
||||
{isRowReadOnly ? (
|
||||
<div className="space-y-2">
|
||||
<div className="whitespace-pre-wrap">{item.splmntCn || "-"}</div>
|
||||
{splmntAtchFileInfo?.atflCd ? (
|
||||
<AttachmentDownloadLink
|
||||
atflCd={splmntAtchFileInfo?.atflCd}
|
||||
fileName={splmntAtchFileInfo?.orgnlFileNm}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
<span className="whitespace-pre-wrap">{item.splmntCn || "-"}</span>
|
||||
) : (
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -307,7 +292,7 @@ function SplmntDmndFormSection({ initialData, readOnly = false }: SplmntDmndForm
|
||||
value={field.value ?? ""}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
placeholder="보완 완료 내용을 입력하세요."
|
||||
placeholder="보완완료 내용을 입력하세요."
|
||||
className="min-h-[6rem] !text-xl"
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { client } from "@/shared/api";
|
||||
import { create } from "zustand";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PplLengthContent, SewerageService } from "../model/sn-interface";
|
||||
import type { BasinStofcbrCountContent, PplLengthContent, SewerageService } from "../model/sn-interface";
|
||||
import type { OldPplChartData, mapPplOldType, mapPplOldItem } from "../model/sn-interface";
|
||||
|
||||
export type SnState = {
|
||||
@@ -35,26 +35,7 @@ const initialUsePplChartData: OldPplChartData = {
|
||||
};
|
||||
const initialOldPplChartData: OldPplChartData = {
|
||||
labels: ["금강", "한강", "낙동강", "영산강"],
|
||||
datasets: [
|
||||
{
|
||||
label: "20-25년",
|
||||
data: [1140, 3181, 3100, 1662],
|
||||
backgroundColor: "#3B82F6",
|
||||
borderRadius: 4
|
||||
},
|
||||
{
|
||||
label: "25-30년",
|
||||
data: [1391, 3615, 1814, 1411],
|
||||
backgroundColor: "#EF4444",
|
||||
borderRadius: 4
|
||||
},
|
||||
{
|
||||
label: "30년이상",
|
||||
data: [2014, 10303, 9831, 2000],
|
||||
backgroundColor: "#F87171",
|
||||
borderRadius: 4
|
||||
}
|
||||
]
|
||||
datasets: []
|
||||
};
|
||||
type OldPplStoreState = {
|
||||
chartData: OldPplChartData;
|
||||
@@ -70,23 +51,122 @@ export const useUsePplStore = create<OldPplStoreState>((set) => ({
|
||||
}));
|
||||
|
||||
export const initailMapOldPplData: mapPplOldType = {
|
||||
colTitle: { totalLen: "총길이", pplLen2025: "20~25년", pplLen2530: "25~30년", pplLen30: "30년 이상" }, //헤더 영역
|
||||
colTitle: { stofcbrCount: "우수토실개소수", totalLen: "하수관로총연장" },
|
||||
data: [
|
||||
{ name: "금강", totalLen: 4545, pplLen2025: 1140, pplLen2530: 1391, pplLen30: 2014 },
|
||||
{ name: "한강", totalLen: 17099, pplLen2025: 3181, pplLen2530: 3615, pplLen30: 10303 },
|
||||
{ name: "낙동강", totalLen: 14745, pplLen2025: 3100, pplLen2530: 1814, pplLen30: 9831 },
|
||||
{ name: "영산강", totalLen: 5073, pplLen2025: 1662, pplLen2530: 1411, pplLen30: 2000 },
|
||||
{ name: "금강", totalLen: 0, stofcbrCount: 0 },
|
||||
{ name: "한강", totalLen: 0, stofcbrCount: 0 },
|
||||
{ name: "낙동강", totalLen: 0, stofcbrCount: 0 },
|
||||
{ name: "영산강", totalLen: 0, stofcbrCount: 0 },
|
||||
]
|
||||
};
|
||||
type mapPplOldState = {
|
||||
mapData: mapPplOldItem[];
|
||||
setMapData: (mapData: mapPplOldItem[]) => void;
|
||||
setStofcbrCounts: (counts: BasinStofcbrCountContent[]) => void;
|
||||
};
|
||||
export const useMapOldPplStore = create<mapPplOldState>((set) => ({
|
||||
mapData: initailMapOldPplData.data,
|
||||
setMapData: (newMapData) => set({ mapData: newMapData }),
|
||||
setMapData: (newMapData) => set((state) => ({
|
||||
mapData: newMapData.map((item) => ({
|
||||
...item,
|
||||
stofcbrCount: state.mapData.find((current) => current.name === item.name)?.stofcbrCount ?? item.stofcbrCount,
|
||||
})),
|
||||
})),
|
||||
setStofcbrCounts: (counts) => set((state) => ({
|
||||
mapData: state.mapData.map((item) => ({
|
||||
...item,
|
||||
stofcbrCount: counts.find((count) => count.zoneNm === item.name)?.stofcbrCount ?? item.stofcbrCount,
|
||||
})),
|
||||
})),
|
||||
}));
|
||||
|
||||
const DEFAULT_BASIN_ORDER = ["금강", "한강", "낙동강", "영산강"];
|
||||
const YEAR_GROUP_START_COLOR = "#FACC15";
|
||||
const YEAR_GROUP_END_COLOR = "#F87171";
|
||||
|
||||
/**
|
||||
* 시작색과 종료색 사이를 RGB 기준으로 보간해 구간 수만큼 색상을 생성합니다.
|
||||
* 구간이 하나면 시작색을, 둘 이상이면 첫 구간은 파란색·마지막 구간은 빨간색으로 고정합니다.
|
||||
*/
|
||||
function buildYearGroupColors(count: number): string[] {
|
||||
if (count <= 0) return [];
|
||||
if (count === 1) return [YEAR_GROUP_START_COLOR];
|
||||
|
||||
const parseHex = (color: string) => [
|
||||
Number.parseInt(color.slice(1, 3), 16),
|
||||
Number.parseInt(color.slice(3, 5), 16),
|
||||
Number.parseInt(color.slice(5, 7), 16),
|
||||
];
|
||||
const toHex = (value: number) => Math.round(value).toString(16).padStart(2, "0");
|
||||
const start = parseHex(YEAR_GROUP_START_COLOR);
|
||||
const end = parseHex(YEAR_GROUP_END_COLOR);
|
||||
|
||||
return Array.from({ length: count }, (_, index) => {
|
||||
const ratio = index / (count - 1);
|
||||
return `#${start.map((channel, channelIndex) => {
|
||||
const endChannel = end[channelIndex] ?? channel;
|
||||
return toHex(channel + (endChannel - channel) * ratio);
|
||||
}).join("")}`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 별도 정렬순서가 없는 MV 계약에서 구간명의 첫 숫자를 기준으로 표시순서를 결정합니다.
|
||||
* 숫자가 없는 비정형 구간은 숫자 구간 뒤에 한글 오름차순으로 배치합니다.
|
||||
*/
|
||||
function compareYearGroups(left: string, right: string): number {
|
||||
const leftNumber = left.match(/\d+/)?.[0];
|
||||
const rightNumber = right.match(/\d+/)?.[0];
|
||||
|
||||
if (leftNumber !== undefined && rightNumber !== undefined) {
|
||||
const numberDifference = Number(leftNumber) - Number(rightNumber);
|
||||
if (numberDifference !== 0) return numberDifference;
|
||||
} else if (leftNumber !== undefined) {
|
||||
return -1;
|
||||
} else if (rightNumber !== undefined) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return left.localeCompare(right, "ko");
|
||||
}
|
||||
|
||||
/**
|
||||
* MV 응답의 yearGroup 목록으로 노후관로 차트 데이터를 동적으로 생성합니다.
|
||||
* 구간명이나 구간 개수가 바뀌어도 고정 매핑 없이 동일한 로직을 사용합니다.
|
||||
*/
|
||||
export function buildOldPplChartData(items: PplLengthContent[]): OldPplChartData {
|
||||
const responseZones = [...new Set(items.map((item) => item.zoneNm).filter(Boolean))];
|
||||
const labels = [
|
||||
...DEFAULT_BASIN_ORDER,
|
||||
...responseZones.filter((zone) => !DEFAULT_BASIN_ORDER.includes(zone)).sort((left, right) => left.localeCompare(right, "ko")),
|
||||
];
|
||||
const yearGroups = [
|
||||
...new Set(
|
||||
items
|
||||
.map((item) => item.yearGroup?.trim())
|
||||
.filter((yearGroup): yearGroup is string => Boolean(yearGroup)),
|
||||
),
|
||||
].sort(compareYearGroups);
|
||||
const colors = buildYearGroupColors(yearGroups.length);
|
||||
|
||||
const lengthByZoneAndGroup = new Map<string, number>();
|
||||
items.forEach((item) => {
|
||||
const yearGroup = item.yearGroup?.trim();
|
||||
if (!item.zoneNm || !yearGroup) return;
|
||||
lengthByZoneAndGroup.set(`${item.zoneNm}\u0000${yearGroup}`, Number(item.sumPrlgVl) || 0);
|
||||
});
|
||||
|
||||
return {
|
||||
labels,
|
||||
datasets: yearGroups.map((yearGroup, index) => ({
|
||||
label: yearGroup,
|
||||
data: labels.map((zone) => lengthByZoneAndGroup.get(`${zone}\u0000${yearGroup}`) ?? 0),
|
||||
backgroundColor: colors[index] ?? YEAR_GROUP_START_COLOR,
|
||||
borderRadius: 4,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* OldPplChartData를 지도 팝업 데이터 형태인 mapPplOldItem[]으로 변환합니다.
|
||||
* @param chartData 차트 데이터
|
||||
@@ -96,22 +176,16 @@ export function transformChartDataToMapData(chartData: OldPplChartData): mapPplO
|
||||
const mapData: mapPplOldItem[] = [];
|
||||
const { labels, datasets } = chartData;
|
||||
|
||||
const pplLen2025Data = datasets.find(d => d.label === '20-25년')?.data || [];
|
||||
const pplLen2530Data = datasets.find(d => d.label === '25-30년')?.data || [];
|
||||
const pplLen30Data = datasets.find(d => d.label === '30년이상')?.data || [];
|
||||
|
||||
labels.forEach((label, index) => {
|
||||
const pplLen2025 = pplLen2025Data[index] || 0;
|
||||
const pplLen2530 = pplLen2530Data[index] || 0;
|
||||
const pplLen30 = pplLen30Data[index] || 0;
|
||||
const totalLen = pplLen2025 + pplLen2530 + pplLen30;
|
||||
const totalLen = datasets.reduce((sum, dataset) => {
|
||||
const value = dataset.data[index];
|
||||
return sum + (typeof value === "number" ? value : 0);
|
||||
}, 0);
|
||||
|
||||
mapData.push({
|
||||
name: label,
|
||||
totalLen,
|
||||
pplLen2025,
|
||||
pplLen2530,
|
||||
pplLen30,
|
||||
stofcbrCount: 0,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -171,7 +245,7 @@ export class SewerageApiService implements SewerageService {
|
||||
}));
|
||||
return list;
|
||||
}
|
||||
// 유역별 노후도별 관로 길이(km) MV조회 : yearGroup - 20년~25년미만,25년~30년미만,30년이상
|
||||
// 유역별 노후도별 관로 길이(km) MV조회: yearGroup은 MV가 제공하는 동적 구간명
|
||||
public async getMvOldPplLength(): Promise<PplLengthContent[]> {
|
||||
const response = await client.get<{
|
||||
PplLengthList: PplLengthContent[];
|
||||
@@ -184,6 +258,18 @@ export class SewerageApiService implements SewerageService {
|
||||
return list;
|
||||
}
|
||||
|
||||
// CM 대시보드 유역별 우수토실 개소수 조회
|
||||
public async getBasinStofcbrCount(): Promise<BasinStofcbrCountContent[]> {
|
||||
const response = await client.get<{
|
||||
basinStofcbrCountList: BasinStofcbrCountContent[];
|
||||
}>(ENDPOINT.SEWERAGE_SERVICE.BASIN_STOFCBR_COUNT);
|
||||
|
||||
return (response.data.basinStofcbrCountList ?? []).map((item) => ({
|
||||
...item,
|
||||
stofcbrCount: Number(item.stofcbrCount),
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const SewerageApi = new SewerageApiService();
|
||||
|
||||
@@ -12,7 +12,7 @@ import { svcInfo } from "../model/map-data";
|
||||
import type { mapOperFcltType, mapReuseMapType} from "../model/fo-interface";
|
||||
import type { mapPplOldType } from "../model/sn-interface";
|
||||
import { useMapOperFcltStore, OperationApi, useMapReuseMapStore } from "../api/fo-service";
|
||||
import { useMapOldPplStore } from "../api/sn-service";
|
||||
import { SewerageApi, useMapOldPplStore } from "../api/sn-service";
|
||||
import {Button} from "@common/components";
|
||||
|
||||
const SVG_URL = "/images/region_map.svg"; // public 폴더에 region_map.svg 위치 필요
|
||||
@@ -31,12 +31,12 @@ const mapOperFcltData: mapOperFcltType = {
|
||||
]
|
||||
};
|
||||
const mapPplOldData: mapPplOldType = {
|
||||
colTitle: {totalLen: "총길이",pplLen2025: "20~25년",pplLen2530: "25~30년",pplLen30 : "30년 이상" }, //헤더 영역
|
||||
colTitle: {stofcbrCount: "우수토실개소수", totalLen: "하수관로총연장" },
|
||||
data: [
|
||||
{name: "금강", totalLen: 4545, pplLen2025: 1140, pplLen2530: 1391, pplLen30 : 2014},
|
||||
{name: "한강", totalLen: 17099, pplLen2025: 3181, pplLen2530: 3615, pplLen30 : 10303},
|
||||
{name: "낙동강", totalLen: 14745, pplLen2025: 3100, pplLen2530: 1814, pplLen30 : 9831},
|
||||
{name: "영산강", totalLen: 5073, pplLen2025: 1662, pplLen2530: 1411, pplLen30 : 2000},
|
||||
{name: "금강", totalLen: 0, stofcbrCount: 0},
|
||||
{name: "한강", totalLen: 0, stofcbrCount: 0},
|
||||
{name: "낙동강", totalLen: 0, stofcbrCount: 0},
|
||||
{name: "영산강", totalLen: 0, stofcbrCount: 0},
|
||||
]
|
||||
};
|
||||
const mapReuseMapData: mapReuseMapType = {
|
||||
@@ -62,17 +62,18 @@ export function KoreaBasinMap() {
|
||||
const { selectedBasin, setSelectedBasin } = useStore(basinStore);
|
||||
const selectedBasinKey = basinKeyMap[selectedBasin];
|
||||
const {mapOperFclt} = useMapOperFcltStore();
|
||||
const {mapData: mapPplOld} = useMapOldPplStore();
|
||||
const {mapData: mapPplOld, setStofcbrCounts} = useMapOldPplStore();
|
||||
const {setMapData: setMapReuseMapData} = useMapReuseMapStore();
|
||||
mapOperFcltData.data = mapOperFclt;
|
||||
mapPplOldData.data = mapPplOld;
|
||||
// mapReuseMapData.data = mapReuseMap;
|
||||
let standardYear = '2024';
|
||||
const [standardYear, setStandardYear] = useState('2024');
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
standardYear = await OperationApi.getStandardYear();
|
||||
const mapReuseMapRslt = await OperationApi.getReuseMap(standardYear);
|
||||
const fetchedStandardYear = await OperationApi.getStandardYear();
|
||||
setStandardYear(fetchedStandardYear);
|
||||
const mapReuseMapRslt = await OperationApi.getReuseMap(fetchedStandardYear);
|
||||
setMapReuseMapData(mapReuseMapRslt);
|
||||
mapReuseMapData.data = mapReuseMapRslt;
|
||||
} catch (e) {
|
||||
@@ -80,7 +81,13 @@ export function KoreaBasinMap() {
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
}, []);
|
||||
}, [setMapReuseMapData]);
|
||||
|
||||
useEffect(() => {
|
||||
SewerageApi.getBasinStofcbrCount()
|
||||
.then(setStofcbrCounts)
|
||||
.catch((e) => console.error("유역별 우수토실 개소수 조회 실패", e));
|
||||
}, [setStofcbrCounts]);
|
||||
// 유역정보
|
||||
const showFor = useCallback((g: SVGGElement) => {
|
||||
const activeService = services.find(s => s.active);
|
||||
@@ -104,24 +111,16 @@ export function KoreaBasinMap() {
|
||||
});
|
||||
} else if (activeService.svcId === 'sn') { // 하수관로
|
||||
const item = mapPplOldData.data.find(d => d.name === basinName);
|
||||
newPopupContent.title = `${standardYear}년도 ${basinName}유역 노후하수관로 현황`;
|
||||
newPopupContent.title = `${standardYear}년도 ${basinName}유역 하수관로 현황`;
|
||||
const colTitles = mapPplOldData.colTitle;
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.stofcbrCount,
|
||||
data: item ? `${fmt(item.stofcbrCount)}개소` : '-',
|
||||
});
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.totalLen,
|
||||
data: item ? `${fmt(item.totalLen)}km` : '-',
|
||||
});
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.pplLen2025,
|
||||
data: item ? `${fmt(item.pplLen2025)}km` : '-',
|
||||
});
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.pplLen2530,
|
||||
data: item ? `${fmt(item.pplLen2530)}km` : '-',
|
||||
});
|
||||
newPopupContent.items.push({
|
||||
header: colTitles.pplLen30,
|
||||
data: item ? `${fmt(item.pplLen30)}km` : '-',
|
||||
});
|
||||
} else if (activeService.svcId === 'wr') { // 물재이용
|
||||
const item = mapReuseMapData.data.find(d => d.name === basinName);
|
||||
newPopupContent.title = `${standardYear}년도 ${basinName}유역 물재이용 현황`;
|
||||
@@ -160,7 +159,7 @@ export function KoreaBasinMap() {
|
||||
);
|
||||
popupRef.current.style.left = x + "px";
|
||||
}
|
||||
}, [services, mapOperFclt]);
|
||||
}, [services, mapOperFclt, mapPplOld, standardYear]);
|
||||
|
||||
// selectedBasinKey prop이 변경될 때 지도를 업데이트하는 useEffect
|
||||
useEffect(() => {
|
||||
@@ -410,8 +409,14 @@ export function KoreaBasinMap() {
|
||||
};
|
||||
// 스타일은 필요에 따라 tailwind, css 등으로 커스터마이즈하세요.
|
||||
return (
|
||||
<div className="col-span-3 !p-0 relative z-10" style={{ height: "765px", background: "rgba(255, 255, 255, 0.6)" }}>
|
||||
<div className="card card-border" style={{ height: "100%" }}>
|
||||
<div
|
||||
className="absolute inset-0 z-10 col-span-3 h-full !p-0"
|
||||
style={{ background: "rgba(255, 255, 255, 0.6)" }}
|
||||
>
|
||||
<div
|
||||
className="card card-border"
|
||||
style={{ height: "calc(100% - 1.2rem)" }}
|
||||
>
|
||||
<div className="card-header text-foreground">
|
||||
<h3 className="text-2xl font-medium">유역별 정보</h3>
|
||||
|
||||
|
||||
@@ -6,11 +6,17 @@ export interface PplLengthContent {
|
||||
yearGroup?: string;
|
||||
}
|
||||
|
||||
export interface BasinStofcbrCountContent {
|
||||
zoneNm: string;
|
||||
stofcbrCount: number;
|
||||
}
|
||||
|
||||
export interface SewerageService {
|
||||
getPplLength(ymdFrom: string, ymdTo: string, useCd: string): Promise<PplLengthContent[]>;
|
||||
getPplUseLength(ymdFrom: string, ymdTo: string, useCd: string): Promise<PplLengthContent[]>;
|
||||
getMvUsgPplLength(): Promise<PplLengthContent[]>;
|
||||
getMvOldPplLength(): Promise<PplLengthContent[]>;
|
||||
getBasinStofcbrCount(): Promise<BasinStofcbrCountContent[]>;
|
||||
}
|
||||
|
||||
export interface ChartDataSet {
|
||||
@@ -37,17 +43,13 @@ type SnOldPplLength = {
|
||||
export type mapPplOldItem = {
|
||||
name: string;
|
||||
totalLen: number;
|
||||
pplLen2025: number;
|
||||
pplLen2530: number;
|
||||
pplLen30: number;
|
||||
stofcbrCount: number;
|
||||
};
|
||||
|
||||
export type mapPplOldType = {
|
||||
colTitle: {
|
||||
totalLen: string; // 총길이
|
||||
pplLen2025: string; // 20~25년
|
||||
pplLen2530: string; // 25~30년
|
||||
pplLen30: string; // 30년 이상
|
||||
stofcbrCount: string; // 우수토실개소수
|
||||
totalLen: string; // 총연장
|
||||
};
|
||||
data: mapPplOldItem[];
|
||||
};
|
||||
@@ -82,4 +84,4 @@ export function buildSnOldPplLength(
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ function DashboardPage() {
|
||||
<div className="p-2 sm:p-4 md:p-6">
|
||||
<div className="flex flex-col gap-1">
|
||||
{/* 첫번째 라인 - 좌측 6개 패널(2x3) + 우측 지도/사용자 정보 */}
|
||||
<div className="flex flex-col gap-1 lg:flex-row">
|
||||
<div className="flex flex-col gap-1 lg:flex-row lg:items-stretch">
|
||||
{/* 좌측: 드래그 가능한 6개 패널 (2x3 그리드, 화면의 50%) */}
|
||||
<div className="w-full lg:w-1/2">
|
||||
{isMounted ? (
|
||||
@@ -173,9 +173,9 @@ function DashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* 우측: 고정된 지도와 사용자 정보 (화면의 50%) */}
|
||||
<div className="flex w-full gap-1 lg:w-1/2">
|
||||
<div className="flex w-full flex-col items-stretch gap-1 lg:w-1/2 lg:flex-row">
|
||||
{/* 지도영역 - 고정 (드래그 불가) */}
|
||||
<div className="flex-1">
|
||||
<div className="relative min-h-[765px] w-full self-stretch lg:min-h-0 lg:flex-1">
|
||||
<KoreaBasinMap />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
} from "chart.js";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import { Button } from "@common/components";
|
||||
import type { PplLengthContent, ChartDataSet, OldPplChartData } from "../model/sn-interface";
|
||||
import { SewerageApi, useOldPplStore,useMapOldPplStore, transformChartDataToMapData } from "../api/sn-service";
|
||||
import type { PplLengthContent } from "../model/sn-interface";
|
||||
import { buildOldPplChartData, SewerageApi, useOldPplStore,useMapOldPplStore, transformChartDataToMapData } from "../api/sn-service";
|
||||
import { useDragHandle } from "./sortable-panel";
|
||||
|
||||
|
||||
@@ -41,45 +41,7 @@ export default function SnOldPpl() {
|
||||
useEffect(() => {
|
||||
if (oldPplLenData.length === 0) return;
|
||||
|
||||
// 유역 목록 추출 (중복 제거)
|
||||
const allZones = [...new Set(oldPplLenData.map(d => d.zoneNm))];
|
||||
|
||||
// yearGroup 매핑: API 응답값 -> 차트 label
|
||||
const yearGroupMap: { [key: string]: { label: string; color: string } } = {
|
||||
"20년~25년미만": { label: "20-25년", color: "#3B82F6" },
|
||||
"25년~30년미만": { label: "25-30년", color: "#EF4444" },
|
||||
"30년이상": { label: "30년이상", color: "#F87171" },
|
||||
};
|
||||
|
||||
// 유역별, 노후년도별 데이터 구조화
|
||||
const dataByZone: { [zone: string]: { [yearGroup: string]: number } } = {};
|
||||
allZones.forEach(zone => {
|
||||
dataByZone[zone] = {};
|
||||
});
|
||||
|
||||
oldPplLenData.forEach(item => {
|
||||
let zoneData = dataByZone[item.zoneNm];
|
||||
if (!zoneData) {
|
||||
zoneData = {};
|
||||
dataByZone[item.zoneNm] = zoneData;
|
||||
}
|
||||
if (item.yearGroup) {
|
||||
zoneData[item.yearGroup] = item.sumPrlgVl;
|
||||
}
|
||||
});
|
||||
|
||||
// 데이터셋 생성
|
||||
const newDatasets: ChartDataSet[] = Object.entries(yearGroupMap).map(([yearGroup, { label, color }]) => ({
|
||||
label,
|
||||
data: allZones.map(zone => dataByZone[zone]?.[yearGroup] ?? 0),
|
||||
backgroundColor: color,
|
||||
borderRadius: 4,
|
||||
}));
|
||||
|
||||
const newChartData: OldPplChartData = {
|
||||
labels: allZones,
|
||||
datasets: newDatasets,
|
||||
};
|
||||
const newChartData = buildOldPplChartData(oldPplLenData);
|
||||
|
||||
setChartData(newChartData);
|
||||
|
||||
@@ -110,9 +72,7 @@ export default function SnOldPpl() {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
min: 0,
|
||||
max: 10000,
|
||||
ticks: {
|
||||
stepSize: 2000,
|
||||
callback: function (value) {
|
||||
return Number(value).toLocaleString() + 'km';
|
||||
}
|
||||
@@ -192,4 +152,4 @@ export default function SnOldPpl() {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ export const ENDPOINT = {
|
||||
OLD_PPL_COUNTS: "/sewerage-service/api/v1/ms/oldPplCounts",
|
||||
MV_USG_PPL_LENGTH: "/sewerage-service/api/v1/ms/mvUsgPplLength",
|
||||
MV_OLD_PPL_LENGTH: "/sewerage-service/api/v1/ms/mvOldPplLength",
|
||||
BASIN_STOFCBR_COUNT: "/sewerage-service/api/v1/ms/basinStofcbrCount",
|
||||
},
|
||||
OPERATION_SERVICE: {
|
||||
MAIN_YEAR: "/operation-service/api/v1/dashBoard/main/year",
|
||||
|
||||
@@ -276,9 +276,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) {
|
||||
)}
|
||||
</div>
|
||||
</PostAction>
|
||||
{bbsInfo?.cmntUseYn === "Y" && (
|
||||
<PostComment bbsId={parseInt(bbsId)} pstNo={parseInt(pstNo)} />
|
||||
)}
|
||||
<PostComment bbsId={parseInt(bbsId)} pstNo={parseInt(pstNo)} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
## Project
|
||||
This repository contains a Next.js web application.
|
||||
|
||||
## Portable paths
|
||||
|
||||
Resolve paths from this `apps/dm/AGENTS.md` file, not from a developer-specific drive or the current shell directory.
|
||||
|
||||
- `FE_APP_ROOT`: directory containing this file (`apps/dm`)
|
||||
- `FE_REPO_ROOT`: `FE_APP_ROOT/../..` (`frontend`)
|
||||
- `WORKSPACE_ROOT`: `FE_REPO_ROOT/..`
|
||||
- `COMMON_ROOT_AGENTS`: `WORKSPACE_ROOT/AGENTS.md`
|
||||
- `BE_REPO_ROOT`: `WORKSPACE_ROOT/IIPBS` (Backend repository root)
|
||||
- `BE_SERVICE_ROOT`: `BE_REPO_ROOT/sewerage-service`
|
||||
|
||||
Use an explicitly supplied repository path when the repositories are installed under different parent directories. Verify a calculated path exists before using it.
|
||||
|
||||
Main technologies:
|
||||
- Next.js
|
||||
- TypeScript
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
## Project
|
||||
This repository contains a Next.js web application.
|
||||
|
||||
## Portable paths
|
||||
|
||||
Resolve paths from this `apps/fo/AGENTS.md` file, not from a developer-specific drive or the current shell directory.
|
||||
|
||||
- `FE_APP_ROOT`: directory containing this file (`apps/fo`)
|
||||
- `FE_REPO_ROOT`: `FE_APP_ROOT/../..` (`frontend`)
|
||||
- `WORKSPACE_ROOT`: `FE_REPO_ROOT/..`
|
||||
- `COMMON_ROOT_AGENTS`: `WORKSPACE_ROOT/AGENTS.md`
|
||||
- `BE_REPO_ROOT`: `WORKSPACE_ROOT/IIPBS` (Backend repository root)
|
||||
- `BE_SERVICE_ROOT`: `BE_REPO_ROOT/sewerage-service`
|
||||
|
||||
Use an explicitly supplied repository path when the repositories are installed under different parent directories. Verify a calculated path exists before using it.
|
||||
|
||||
Main technologies:
|
||||
- Next.js
|
||||
- TypeScript
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
|
||||
#NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr/openapi/Gettile.do?apiKey=04trYP9_xwLAfALjwZ-B8g&Service=WMTS&Request=GetTile&Version=1.0.0&Format=image/png&layer=korean_map&style=korean&tilematrixset=EPSG:5179&TileMatrix={z}&TileCol={x}&TileRow={y}
|
||||
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr
|
||||
NEXT_PUBLIC_NGII_PARAM = 04trYP9_xwLAfALjwZ-B8g&service=WMTS&request=GetTile&version=1.0.0&format=image/png&layer=korean_map&style=korean&tilematrixset=korean&tilematrix={z}&tilecol={x}&tilerow={y}
|
||||
SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
SERVER_API_URL = https://apigateway.onasys.co.kr
|
||||
SEWERAGE_API_URL = http://localhost:8883
|
||||
# SEWERAGE_API_URL = http://con.mind-one.co.kr:11083
|
||||
DC_URL = https://apigateway.apps.rsonesoft.com
|
||||
|
||||
@@ -6,7 +6,7 @@ NEXT_PUBLIC_ACCESS_TOKEN = access-token
|
||||
NEXT_PUBLIC_REFRESH_TOKEN = refresh-token
|
||||
NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11302
|
||||
NEXT_PUBLIC_SUB_DOMAIN = http://con.mind-one.co.kr:13000
|
||||
SERVER_API_URL = http://dev.onasys.co.kr:8000
|
||||
SERVER_API_URL = https://apigateway.onasys.co.kr
|
||||
NEXT_PUBLIC_GEOSERVER_URL = https://hasudo.geo.rsonesoft.com/geoserver
|
||||
NEXT_PUBLIC_LX_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver
|
||||
NEXT_PUBLIC_NGII_URL = https://map.ngii.go.kr
|
||||
|
||||
+38
-3
@@ -43,14 +43,46 @@
|
||||
|
||||
## 공통 시작 기준
|
||||
|
||||
Codex 작업 루트는 가능하면 `../../../../../`로 잡고, 공통 시작 기준은 `AGENTS.md`를 따른다.
|
||||
개발자마다 드라이브와 상위 작업 폴더 위치가 다르므로 특정 드라이브·사용자 디렉터리의 절대경로를 기준으로 사용하지 않는다.
|
||||
|
||||
경로 별칭은 `apps/sn/AGENTS.md`가 있는 디렉터리(`FE_APP_ROOT`)를 기준으로 다음과 같이 해석한다. 상대경로는 현재 셸의 실행 위치가 아니라 이 파일의 위치를 기준으로 계산한다.
|
||||
|
||||
- `FE_APP_ROOT`: 이 파일이 있는 `apps/sn` 디렉터리
|
||||
- `FE_REPO_ROOT`: `FE_APP_ROOT/../..`
|
||||
- `WORKSPACE_ROOT`: `FE_APP_ROOT/../../..`
|
||||
- `COMMON_ROOT_AGENTS`: `WORKSPACE_ROOT/AGENTS.md`
|
||||
- `BE_REPO_ROOT`: `WORKSPACE_ROOT/IIPBS` (Backend Git 저장소 루트)
|
||||
- `BE_SERVICE_ROOT`: `BE_REPO_ROOT/sewerage-service` (하수관로 백엔드 서비스 모듈)
|
||||
- `COMMON_DOCS_ROOT`: `BE_REPO_ROOT/docs`
|
||||
- `FE_APP_DOCS_ROOT`: `FE_APP_ROOT/docs`
|
||||
|
||||
경로 결정은 아래 우선순위를 따른다.
|
||||
|
||||
1. 작업 환경이나 사용자가 `WORKSPACE_ROOT`, `COMMON_DOCS_ROOT`, `BE_REPO_ROOT`, `BE_SERVICE_ROOT` 등의 유효한 경로를 명시한 경우 그 값을 우선한다. 저장소를 서로 다른 상위 폴더에 배치한 개발자도 이 방식으로 사용할 수 있다.
|
||||
2. 명시값이 없으면 위의 `FE_APP_ROOT` 기준 상대경로로 계산한다. 드라이브 문자나 사용자별 상위 디렉터리 이름이 달라도 같은 저장소 배치 구조를 사용할 수 있다.
|
||||
3. 계산한 경로는 사용 전에 대상 파일이나 디렉터리의 존재 여부를 확인한다. 없거나 후보가 여러 개인 경우 특정 절대경로를 추측하지 말고 현재 저장소에서 확인 가능한 작업만 진행한 뒤 필요한 경로를 사용자에게 확인한다.
|
||||
4. 작업 문서와 결과 보고에는 특별한 사유가 없으면 개발자별 절대경로 대신 위 경로 별칭이나 저장소 기준 상대경로를 기록한다.
|
||||
|
||||
PowerShell에서 현재 프론트엔드 저장소를 기준으로 확인할 때는 다음처럼 동적으로 계산할 수 있다. 각 단계는 앞 단계에서 찾은 저장소 위치를 기반으로 하므로 개발자별 설치 경로에 의존하지 않는다.
|
||||
|
||||
```powershell
|
||||
$feRepoRoot = (git rev-parse --show-toplevel).Trim()
|
||||
$feAppRoot = Join-Path $feRepoRoot 'apps/sn'
|
||||
$workspaceRoot = Split-Path $feRepoRoot -Parent
|
||||
$commonRootAgents = Join-Path $workspaceRoot 'AGENTS.md'
|
||||
$beRepoRoot = Join-Path $workspaceRoot 'IIPBS'
|
||||
$beServiceRoot = Join-Path $beRepoRoot 'sewerage-service'
|
||||
$commonDocsRoot = Join-Path $beRepoRoot 'docs'
|
||||
```
|
||||
|
||||
공통 시작 기준은 `COMMON_ROOT_AGENTS`를 따른다.
|
||||
|
||||
이 파일은 프론트엔드 `sn` 앱 세부 기준이다.
|
||||
|
||||
백엔드(`sewerage-service`)와 함께 수정하거나 API/응답/DB/인증/권한 연동 영향이 있는 작업은 아래 파일을 함께 확인한다.
|
||||
|
||||
- 공통 시작 기준: `AGENTS.md`
|
||||
- 백엔드 세부 기준: `BE_REPO_ROOT/AGENTS.md`
|
||||
- 백엔드 세부 기준: `BE_SERVICE_ROOT/AGENTS.md`
|
||||
- 프론트엔드 `sn` 앱 기준: `FE_APP_ROOT/AGENTS.md`
|
||||
|
||||
---
|
||||
@@ -134,6 +166,9 @@ Codex 작업 루트는 가능하면 `../../../../../`로 잡고, 공통 시작
|
||||
|
||||
## 작업 원칙
|
||||
|
||||
- `sn` 프론트엔드 엑셀 다운로드 기능을 신규 개발하거나 수정할 때는 `src/shared/util/download-excel-workbook.ts` 재사용을 먼저 검토한다. 세부 기준은 [컴포넌트정책의 공통 유틸 재사용 기준](docs/기준문서/004-컴포넌트정책.md#공통-유틸-재사용-기준)을 따른다. 이 코드 경로는 `FE_APP_ROOT` 기준이다.
|
||||
- 일반 목록·모든 팝업은 공통 AGENTS.md의 서버 페이징·지도·Excel 계약을 따른다. 지도 전체 표출에 현재 그리드 페이지 배열을 재사용하지 않는다. Excel은 적용조건 전체를 사용하고 대량 원시 자료는 서버 생성으로 분리한다. 화면 페이징/페이징 없음 예외는 사유와 검증 범위를 메뉴 > 화면명별로 기록한다.
|
||||
|
||||
1. 모든 변경은 원본 요구사항정의서와 화면설계서, 2차 요구사항, 기능 명세 또는 승인된 작업 문서를 근거로 한다.
|
||||
2. `apps/sn`과 `packages/common/business_components` 범위를 넘는 수정은 하지 않는다.
|
||||
3. 공통 패키지 수정은 `sn` 요구사항을 충족하기 위한 최소 범위로 제한한다.
|
||||
@@ -228,7 +263,7 @@ VSCode나 별도 터미널에서 `sn` Next 개발 서버가 실행 중이면 `ne
|
||||
|
||||
### 프론트엔드 실행 프로세스 관리
|
||||
|
||||
- 모든 세션·역할·문서/검증 섹션에 공통 D:/workspace/AGENTS.md의 테스트 임시 파일·결과 파일 정리 기준을 적용한다. 일회성 검증 스크립트·캡처·Network/서버 로그·원시 결과 파일은 최종 보고서에 요약 후 삭제한다. 정식 회귀 테스트·최종 산출물·사용자 원본/복구 백업은 유지한다. 사용자 dev server가 쓰는 .next·공유 캐시·node_modules는 임시 검증물이라는 이유로 포괄 삭제하지 않는다.
|
||||
- 모든 세션·역할·문서/검증 섹션에 `COMMON_ROOT_AGENTS`의 테스트 파일 정리 기준을 적용한다. 에이전트가 테스트를 위해 만든 파일은 정식 회귀 테스트·재사용 가능한 소스·fixture·mock·임시 라우트를 포함해 위치와 관계없이 결과를 요약한 뒤 작업 종료 전에 삭제한다(2026-09-23 사용자 지시). 전용 빈 폴더와 끊어진 실행 명령도 정리한다. 명시 보존 요청 파일·최종 산출물·사용자 원본/복구 백업은 유지한다. 기존 타 작업자 테스트 자산은 삭제 권한을 먼저 확인한다. 사용자 dev server의 .next·공유 캐시·node_modules는 포괄 삭제하지 않는다.
|
||||
|
||||
- Codex 에이전트가 작업 또는 검증을 위해 직접 기동한 `next dev`, `dev:sn`, Turborepo dev와 기타 프론트엔드 장시간 실행 프로세스는 작업 종료 전에 반드시 종료한다.
|
||||
- 셸, 백그라운드, 숨김 창 등 VSCode 밖에서 에이전트가 기동한 프로세스도 종료 대상이다.
|
||||
|
||||
@@ -19,7 +19,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
||||
import { NoFooterLayout } from "@common/components";
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { useMenuStore } from "@/shared/lib";
|
||||
import useStore from '../store/useStore';
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import { authService } from "@/entities/auth";
|
||||
|
||||
export default function MapLayout({
|
||||
|
||||
+2
-2
@@ -2,13 +2,13 @@
|
||||
"use client";
|
||||
|
||||
import * as XLSX from "xlsx";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import {
|
||||
getAgedPipeStatusByCtpvValidationMessage,
|
||||
normalizeAgedPipeStatusByCtpvFilters,
|
||||
type AgedPipeStatusByCtpvFilters,
|
||||
type AgedPipeStatusByCtpvResponse,
|
||||
} from "./agedPipeStatusByCtpvUtils";
|
||||
} from "./aged-pipe-status-by-ctpv-utils";
|
||||
|
||||
/** 적용조건의 시도별 노후관로현황 전체를 조회해 연도 그룹 Excel로 생성한다. */
|
||||
export const downloadAgedPipeStatusByCtpvExcel = async (
|
||||
+8
-8
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import { getSnRowNumber } from "@/shared/lib/row-number";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button, Input } from "@common/components";
|
||||
import {
|
||||
getStandardRowNumber,
|
||||
StandardDataTable,
|
||||
StandardPopupLayout,
|
||||
} from "@common/business_components";
|
||||
@@ -18,10 +18,10 @@ import {
|
||||
type ColumnDef,
|
||||
type PaginationState,
|
||||
} from "@tanstack/react-table";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions";
|
||||
import {
|
||||
AGED_PIPE_STATUS_BY_CTPV_DEFAULT_ELAPSED_YEARS,
|
||||
buildAgedPipeStatusYearOptions,
|
||||
@@ -32,9 +32,9 @@ import {
|
||||
type AgedPipeStatusByCtpvFilters,
|
||||
type AgedPipeStatusByCtpvResponse,
|
||||
type AgedPipeStatusByCtpvRow,
|
||||
} from "./agedPipeStatusByCtpvUtils";
|
||||
import { downloadAgedPipeStatusByCtpvExcel } from "./agedPipeStatusByCtpvExport";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
} from "./aged-pipe-status-by-ctpv-utils";
|
||||
import { downloadAgedPipeStatusByCtpvExcel } from "./aged-pipe-status-by-ctpv-export";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
|
||||
// CS 목록과 동일하게 한 페이지에는 최대 10개 시도만 표시한다.
|
||||
const AGED_PIPE_STATUS_PAGE_SIZE = 10;
|
||||
@@ -71,7 +71,7 @@ export default function AgedPipeStatusByCtpvPopupPage() {
|
||||
id: "rowNumber",
|
||||
header: "순번",
|
||||
cell: ({ row }) =>
|
||||
getStandardRowNumber({
|
||||
getSnRowNumber({
|
||||
total: rows.length,
|
||||
rowIndex: row.index,
|
||||
}),
|
||||
+3
-2
@@ -66,7 +66,8 @@ export const normalizeAgedPipeStatusByCtpvFilters = (
|
||||
filters: Partial<AgedPipeStatusByCtpvFilters>,
|
||||
): AgedPipeStatusByCtpvFilters => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
let startYear = Math.trunc(Number(filters.startYear ?? currentYear - 2));
|
||||
// 최초 진입·초기화는 당해연도 1년 조회로 시작하며 직접 선택한 범위는 유지한다.
|
||||
let startYear = Math.trunc(Number(filters.startYear ?? currentYear));
|
||||
let endYear = Math.trunc(Number(filters.endYear ?? currentYear));
|
||||
let elapsedYears = Math.trunc(
|
||||
Number(
|
||||
@@ -74,7 +75,7 @@ export const normalizeAgedPipeStatusByCtpvFilters = (
|
||||
),
|
||||
);
|
||||
|
||||
if (!Number.isFinite(startYear)) startYear = currentYear - 2;
|
||||
if (!Number.isFinite(startYear)) startYear = currentYear;
|
||||
if (!Number.isFinite(endYear)) endYear = currentYear;
|
||||
if (
|
||||
!Number.isFinite(elapsedYears) ||
|
||||
+3
-7
@@ -1,13 +1,9 @@
|
||||
/** 노후관로 목록의 엑셀 다운로드 데이터와 서식을 생성한다. */
|
||||
"use client";
|
||||
|
||||
import { postBlobData } from "@/components/lib/api";
|
||||
import { fileDownload } from "@/components/lib/utils/fileDownload";
|
||||
import {
|
||||
AGED_PIPE_EXCEL_FIELDS,
|
||||
normalizeAgedPipeFilters,
|
||||
type AgedPipeFilters,
|
||||
} from "./agedPipeUtils";
|
||||
import { postBlobData } from "@/shared/api/file-api";
|
||||
import { fileDownload } from "@/components/lib/utils/file-download";
|
||||
import { AGED_PIPE_EXCEL_FIELDS, normalizeAgedPipeFilters, type AgedPipeFilters } from "@/entities/facility/lib/aged-pipe-utils";
|
||||
|
||||
type AgedPipeExportProgress = {
|
||||
fileName?: string;
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { validateElapsedYears, type AgedPipeFilters } from "./agedPipeUtils";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
import { validateElapsedYears, type AgedPipeFilters } from "@/entities/facility/lib/aged-pipe-utils";
|
||||
import LayerSettingCard from "../layer-setting/layer-setting-card";
|
||||
|
||||
type MaybePromise<T = void> = T | Promise<T>;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/** 노후관로의 지도와 목록이 사용하는 DB 레이어 설정 계약이다. */
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import {
|
||||
buildAgedPipeCqlFilters,
|
||||
isAgedPipeLayerNode,
|
||||
type AgedPipeFilters,
|
||||
} from "@/entities/facility/lib/aged-pipe-utils";
|
||||
|
||||
/** DB 응답을 그대로 보존하며 최초 표출 전에 필터만 해석한다. */
|
||||
export const prepareAgedPipeLayerRows = (
|
||||
rows: any[],
|
||||
filters: AgedPipeFilters,
|
||||
): any[] =>
|
||||
rows.map((row) => {
|
||||
const next = { ...row };
|
||||
if (isAgedPipeLayerNode(row)) {
|
||||
next.agedPipeFilterTemplate = row.flterSql;
|
||||
try {
|
||||
validateAgedPipeLayer(row);
|
||||
next.flterSql = buildAgedPipeCqlFilters(filters, row.flterSql).join(
|
||||
" AND ",
|
||||
);
|
||||
} catch (error) {
|
||||
// 노후관로 DB 미전환이 다른 시설물 레이어까지 중단시키지 않도록 이 행만 차단한다.
|
||||
next.agedPipeConfigError =
|
||||
error instanceof Error ? error.message : "노후관로 레이어 설정 오류";
|
||||
next.actlLyrYn = "N";
|
||||
next.flterSql = "EXCLUDE";
|
||||
}
|
||||
}
|
||||
if (Array.isArray(row.children))
|
||||
next.children = prepareAgedPipeLayerRows(row.children, filters);
|
||||
return next;
|
||||
});
|
||||
|
||||
/** 환경마다 달라질 수 있는 일련번호 대신 DB 업무명을 통해 설정 행을 찾는다. */
|
||||
export const findAgedPipeLayer = (rows: any[]): any | undefined => {
|
||||
for (const row of rows) {
|
||||
if (isAgedPipeLayerNode(row)) return row;
|
||||
const child = Array.isArray(row.children)
|
||||
? findAgedPipeLayer(row.children)
|
||||
: undefined;
|
||||
if (child) return child;
|
||||
}
|
||||
};
|
||||
|
||||
/** 예전 제어용 행을 실제 WFS 레이어처럼 요청하지 않도록 전환 상태를 확인한다. */
|
||||
export const validateAgedPipeLayer = (row: any) => {
|
||||
if (row?.agedPipeConfigError) throw new Error(row.agedPipeConfigError);
|
||||
if (
|
||||
!row ||
|
||||
row.actlLyrYn !== "Y" ||
|
||||
row.srvcNm !== "wfs" ||
|
||||
!row.lyrTyNm ||
|
||||
row.lyrTyNm === "tb_old_pipe"
|
||||
) {
|
||||
throw new Error(
|
||||
"노후관로 DB 레이어 설정을 실제 WFS 레이어와 sql_filt 템플릿으로 갱신해 주세요.",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/** 독립 목록 팝업도 부모창의 하드코딩 없이 동일 설정 API를 조회한다. */
|
||||
export const fetchAgedPipeLayer = async () => {
|
||||
const rows = await getRawJson(
|
||||
"/sewerage-service/api/v1/map/lyrStngs/SN_NETWORK",
|
||||
);
|
||||
const row = findAgedPipeLayer(Array.isArray(rows) ? rows : []);
|
||||
validateAgedPipeLayer(row);
|
||||
return row;
|
||||
};
|
||||
+79
-39
@@ -3,36 +3,40 @@
|
||||
* 노후관로를 검색하고 목록·엑셀 다운로드·지도 위치 확인 기능을 제공한다.
|
||||
*/
|
||||
"use client";
|
||||
import { useFacilityCodeCatalog } from "@/entities/facility/lib/use-facility-code-catalog";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import useStore from "@/store/useStore";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import {
|
||||
AGED_PIPE_DEFAULT_FILTERS,
|
||||
AGED_PIPE_LAYER_TYPE,
|
||||
AGED_PIPE_LIST_HEADLIST,
|
||||
AGED_PIPE_PAGE_SIZE,
|
||||
buildAgedPipeCqlFilters,
|
||||
getAgedPipeManagementName,
|
||||
normalizeAgedPipeFilters,
|
||||
toDisplayPipeRow,
|
||||
validateElapsedYears,
|
||||
type AgedPipeFilters,
|
||||
type AgedPipeRow,
|
||||
} from "./agedPipeUtils";
|
||||
import { downloadAgedPipeExcel } from "./agedPipeExport";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
type AgedPipeManagementAgency,
|
||||
} from "@/entities/facility/lib/aged-pipe-utils";
|
||||
import { fetchAgedPipeLayer } from "./aged-pipe-layer";
|
||||
import { downloadAgedPipeExcel } from "./aged-pipe-export";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
|
||||
const POPUP_ROW_SELECT_MESSAGE = "SN_AGED_PIPE_ROW_SELECT";
|
||||
const POPUP_MINIMIZE_MESSAGE = "SN_AGED_PIPE_POPUP_MINIMIZE";
|
||||
const POPUP_FILTER_SYNC_MESSAGE = "SN_AGED_PIPE_FILTER_SYNC";
|
||||
|
||||
export default function AgedPipePopupPage() {
|
||||
const facilityCodes = useFacilityCodeCatalog();
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const searchParams = useSearchParams();
|
||||
const { ctpv, sgg, fetchCtpv, fetchSgg } = useStore();
|
||||
@@ -53,6 +57,18 @@ export default function AgedPipePopupPage() {
|
||||
const [appliedFilters, setAppliedFilters] =
|
||||
useState<AgedPipeFilters>(initialFilters);
|
||||
const [rows, setRows] = useState<AgedPipeRow[]>([]);
|
||||
// 관리기관명 조회가 늦게 완료되어도 기존 목록을 재조회하지 않고 표시만 갱신한다.
|
||||
const [managementAgencies, setManagementAgencies] = useState<
|
||||
AgedPipeManagementAgency[]
|
||||
>([]);
|
||||
const displayRows = useMemo(
|
||||
() =>
|
||||
rows.map((row) => ({
|
||||
...row,
|
||||
mngInstNm: getAgedPipeManagementName(row, managementAgencies),
|
||||
})),
|
||||
[rows, managementAgencies],
|
||||
);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
@@ -73,15 +89,34 @@ export default function AgedPipePopupPage() {
|
||||
if (sgg.length === 0) fetchSgg();
|
||||
}, [ctpv.length, fetchCtpv, fetchSgg, sgg.length]);
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
// 기존 GIS 관리 화면과 같은 기관 목록을 사용하고, 실패 시 관로 조회는 유지한다.
|
||||
getData("v1/fcltPpl/mngInsts")
|
||||
.then((response) => {
|
||||
if (active)
|
||||
setManagementAgencies(Array.isArray(response) ? response : []);
|
||||
})
|
||||
.catch(() => {
|
||||
if (active)
|
||||
alert("관리기관명을 불러오지 못해 관리기관 코드로 표시합니다.");
|
||||
});
|
||||
return () => {
|
||||
active = false;
|
||||
};
|
||||
}, [alert]);
|
||||
|
||||
const fetchRows = useCallback(
|
||||
async (page: number, nextFilters: AgedPipeFilters, size: number) => {
|
||||
const normalized = normalizeAgedPipeFilters(nextFilters);
|
||||
// 독립 팝업에서도 현재 DB의 원천 레이어와 필터 템플릿을 사용한다.
|
||||
const layer = await fetchAgedPipeLayer();
|
||||
const queryParams = new URLSearchParams({
|
||||
lyrTypeNm: AGED_PIPE_LAYER_TYPE,
|
||||
lyrTypeNm: layer.lyrTyNm,
|
||||
page: String(page - 1),
|
||||
size: String(size),
|
||||
});
|
||||
const cqlFilters = buildAgedPipeCqlFilters(normalized);
|
||||
const cqlFilters = buildAgedPipeCqlFilters(normalized, layer.flterSql);
|
||||
if (cqlFilters.length > 0) {
|
||||
queryParams.append("cqlFiter", cqlFilters.join(" and "));
|
||||
}
|
||||
@@ -92,12 +127,12 @@ export default function AgedPipePopupPage() {
|
||||
const total = Number(response?.count ?? 0);
|
||||
const rows = responseRows.map(
|
||||
(item: Record<string, unknown>, index: number) =>
|
||||
toDisplayPipeRow(item, index, page, ctpv, sgg),
|
||||
toDisplayPipeRow(item, index, page, ctpv, sgg, facilityCodes),
|
||||
);
|
||||
|
||||
return { rows, total };
|
||||
},
|
||||
[ctpv, sgg],
|
||||
[ctpv, sgg, facilityCodes],
|
||||
);
|
||||
|
||||
const loadRows = useCallback(
|
||||
@@ -115,13 +150,18 @@ export default function AgedPipePopupPage() {
|
||||
setAppliedFilters(normalizeAgedPipeFilters(nextFilters));
|
||||
} catch (error) {
|
||||
console.error("노후관로 목록 조회 중 오류:", error);
|
||||
alert(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "노후관로 목록 조회 중 오류가 발생했습니다.",
|
||||
);
|
||||
setRows([]);
|
||||
setTotal(0);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[fetchRows, filters],
|
||||
[alert, fetchRows, filters],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -247,31 +287,6 @@ export default function AgedPipePopupPage() {
|
||||
) : null}
|
||||
|
||||
<PopupSearchPanel contentClassName="flex flex-wrap items-end gap-x-5 gap-y-3">
|
||||
<label className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap text-[1.3rem] text-label">
|
||||
경과년도
|
||||
</span>
|
||||
<Input
|
||||
className="w-[12rem]"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
autoComplete="off"
|
||||
value={String(filters.elapsedYears ?? "")}
|
||||
onChange={(event) => {
|
||||
const digits = event.target.value.replace(/[^0-9]/g, "");
|
||||
setFilters({
|
||||
...filters,
|
||||
elapsedYears:
|
||||
digits === "" ? ("" as unknown as number) : Number(digits),
|
||||
});
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") void handleSearch(1);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap text-[1.3rem] text-label">
|
||||
시도
|
||||
@@ -316,6 +331,31 @@ export default function AgedPipePopupPage() {
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="flex items-center gap-2">
|
||||
<span className="whitespace-nowrap text-[1.3rem] text-label">
|
||||
경과년도
|
||||
</span>
|
||||
<Input
|
||||
className="w-[12rem]"
|
||||
type="text"
|
||||
inputMode="numeric"
|
||||
pattern="[0-9]*"
|
||||
autoComplete="off"
|
||||
value={String(filters.elapsedYears ?? "")}
|
||||
onChange={(event) => {
|
||||
const digits = event.target.value.replace(/[^0-9]/g, "");
|
||||
setFilters({
|
||||
...filters,
|
||||
elapsedYears:
|
||||
digits === "" ? ("" as unknown as number) : Number(digits),
|
||||
});
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") void handleSearch(1);
|
||||
}}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
@@ -341,7 +381,7 @@ export default function AgedPipePopupPage() {
|
||||
</PopupSearchPanel>
|
||||
|
||||
<MapPopupDataTable
|
||||
rows={rows as Array<AgedPipeRow & Record<string, unknown>>}
|
||||
rows={displayRows}
|
||||
headlist={AGED_PIPE_LIST_HEADLIST}
|
||||
title="노후관로 목록"
|
||||
total={total}
|
||||
+4
-35
@@ -10,26 +10,22 @@ import {
|
||||
ChartColumnBig,
|
||||
ChevronUp,
|
||||
PanelsTopLeft,
|
||||
PencilRuler,
|
||||
Search,
|
||||
Settings2,
|
||||
TriangleAlert,
|
||||
Waves,
|
||||
} from "lucide-react";
|
||||
|
||||
import { createPredictionAnalysisTimeRange } from "@/features/fldngPredcPrst/model/analysisTimeRange";
|
||||
import { createPredictionAnalysisTimeRange } from "@/features/fldng-predc-prst/model/analysis-time-range";
|
||||
import {
|
||||
windowPopupDefinitions,
|
||||
type WindowPopupKey,
|
||||
} from "@/features/windowPopup/model/popupDefinitions";
|
||||
} from "@/features/window-popup/model/popup-definitions";
|
||||
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
|
||||
type Props = {
|
||||
onOpenPopup: (popupKey: WindowPopupKey) => Window | null;
|
||||
onOpenInfoSearch: () => void;
|
||||
onToggleGisEdit?: () => void;
|
||||
isGisEditActive?: boolean;
|
||||
isInfoSearchActive?: boolean;
|
||||
initialActiveKey?: WindowPopupKey | null;
|
||||
};
|
||||
@@ -89,16 +85,6 @@ const ANALYSIS_POPUP_ITEMS: Array<{
|
||||
label: windowPopupDefinitions.agedPipeStatusByCtpvPopup.title,
|
||||
icon: ChartColumnBig,
|
||||
},
|
||||
{
|
||||
key: "gisEditPopup",
|
||||
label: windowPopupDefinitions.gisEditPopup.title,
|
||||
icon: PencilRuler,
|
||||
},
|
||||
{
|
||||
key: "gisManagementPopup",
|
||||
label: windowPopupDefinitions.gisManagementPopup.title,
|
||||
icon: Settings2,
|
||||
},
|
||||
{
|
||||
key: "flowRiskPopup",
|
||||
label: windowPopupDefinitions.flowRiskPopup.title,
|
||||
@@ -119,8 +105,6 @@ const ANALYSIS_POPUP_ITEMS: Array<{
|
||||
export default function AnalysisPopupMenu({
|
||||
onOpenPopup,
|
||||
onOpenInfoSearch,
|
||||
onToggleGisEdit,
|
||||
isGisEditActive = false,
|
||||
isInfoSearchActive = false,
|
||||
initialActiveKey = null,
|
||||
}: Props) {
|
||||
@@ -155,12 +139,6 @@ export default function AnalysisPopupMenu({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isGisEditActive && activeKey === "gisEditPopup") {
|
||||
setActiveKey(null);
|
||||
}
|
||||
}, [activeKey, isGisEditActive]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInfoSearchActive && activeKey === "infoSearch") {
|
||||
setActiveKey(null);
|
||||
@@ -214,10 +192,7 @@ export default function AnalysisPopupMenu({
|
||||
<div className="flex flex-col gap-[3px] p-[6px]">
|
||||
{ANALYSIS_POPUP_ITEMS.map((item) => {
|
||||
const Icon = item.icon;
|
||||
const isActive =
|
||||
item.key === "gisEditPopup"
|
||||
? isGisEditActive
|
||||
: activeKey === item.key;
|
||||
const isActive = activeKey === item.key;
|
||||
const label =
|
||||
item.key === "fldngPredcPrst"
|
||||
? `침수예측현황(${seriousPredictionCount}건)`
|
||||
@@ -233,12 +208,6 @@ export default function AnalysisPopupMenu({
|
||||
: "border-transparent bg-background text-foreground hover:border-border hover:bg-accent hover:text-primary"
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (item.key === "gisEditPopup" && onToggleGisEdit) {
|
||||
setActiveKey(item.key);
|
||||
onToggleGisEdit();
|
||||
return;
|
||||
}
|
||||
|
||||
setActiveKey(item.key);
|
||||
|
||||
if (item.key === "infoSearch") {
|
||||
@@ -1,72 +0,0 @@
|
||||
/** 선택한 지도 레이어 종류에 맞는 시설 상세 컴포넌트를 반환한다. */
|
||||
import MsSwagePpl from "../popupComponent/MsSwagePpl"; // 하수관로
|
||||
import sewagePump from "../popupComponent/sewagePump"; // 하수펌프
|
||||
import smh from "../popupComponent/smh";
|
||||
import Ssc from "../popupComponent/Ssc";
|
||||
import StormWaterPump from "../popupComponent/stormWaterPump";
|
||||
import SwtrmtFclt from "../popupComponent/SwtrmtFclt";
|
||||
import SwtrmtPrcsZoneAnalysis from "../popupComponent/SwtrmtPrcsZoneAnalysis";
|
||||
|
||||
type LayerComponentProps = {
|
||||
featureData: any;
|
||||
isMainSsc?: boolean;
|
||||
isDrtwSewagePump?: boolean;
|
||||
rainMeasureData?: any;
|
||||
onSaveClick?: () => void;
|
||||
onFormDataChange?: (data: any) => void; // 추가
|
||||
chmbrXcnts: string;
|
||||
chmbrYdnts: string;
|
||||
gisObject?: Record<string, any>; // GIS 객체 추가
|
||||
rawFeatureData?: any;
|
||||
surveyResultContent?: React.ReactNode;
|
||||
};
|
||||
type LayerContentMap = {
|
||||
[key: string]: {
|
||||
component?: React.ComponentType<any>;
|
||||
props?: any;
|
||||
};
|
||||
};
|
||||
// 레이어 이름과 컴포넌트를 매핑
|
||||
// 레이어 선택시 변경되는 팝업 컨텐츠 동적으로 변경하도록 작업
|
||||
const layerContentMap: LayerContentMap = {
|
||||
"하수처리시설": { component: SwtrmtFclt, }, // 디지털 자료관 연계정보가 없으니 일단 주석 처리
|
||||
"하수처리구역": { component: SwtrmtPrcsZoneAnalysis },
|
||||
"우수토실": { component: Ssc, },
|
||||
"중점우수토실": { component: Ssc, }, // 중점우수토실 여부 설정
|
||||
"하수관로": { component: MsSwagePpl, },
|
||||
"하수맨홀": { component: smh, },
|
||||
"하수펌프장": { component: sewagePump },
|
||||
// "빗물펌프": { component: StormWaterPump },
|
||||
// "오수펌프": { component: sewagePump },
|
||||
};
|
||||
|
||||
// 동적 컴포넌트 반환 함수
|
||||
export const getLayerContentComponent = (layerNm: string): React.FC<LayerComponentProps> | null => {
|
||||
const component = layerContentMap[layerNm];
|
||||
if (!component || !component.component) {
|
||||
// component가 없으면 null 반환
|
||||
return () => null;
|
||||
}
|
||||
return component.component as React.FC<LayerComponentProps>
|
||||
};
|
||||
// 사용자 입력 필드 (보수 및 갱신, 청소 및 준설, 점검 및 진단)
|
||||
export const handleInputChange = (section: string,
|
||||
index: number,
|
||||
field: string,
|
||||
value: string,
|
||||
dataMap: {
|
||||
[key: string]: {
|
||||
data: { year: string; type: string, bizInfoNm: string, bizType: string }[];
|
||||
setData: React.Dispatch<React.SetStateAction<{ year: string; type: string, bizInfoNm: string, bizType: string }[]>>;
|
||||
};
|
||||
}
|
||||
) => {
|
||||
const target = dataMap[section];
|
||||
if (!target) return;
|
||||
|
||||
const updated = [...target.data];
|
||||
if (updated[index]) {
|
||||
updated[index][field as keyof typeof updated[number]] = value;
|
||||
}
|
||||
target.setData(updated);
|
||||
};
|
||||
@@ -1,924 +0,0 @@
|
||||
/** 지도 시설 상세 및 목록에서 사용하는 컬럼·코드 정의를 제공한다. */
|
||||
// 하수관로
|
||||
export const MsSwagePplCol = [
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
{ id: 'mngInstNm', title: '관리기관' },
|
||||
{ id: 'inst_mng_no', title: '기관관리번호' },
|
||||
{ id: 'flowDirectionBasis', title: '유하방향 근거' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
{ id: 'elapsedYears', title: '경과년도' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
{ id: 'pdmt_vl', title: '관경(mm)' },
|
||||
{ id: 'prlg_vl', title: '연장(m)' },
|
||||
{ id: 'usg_cd', title: '용도' },
|
||||
{ id: 'mqlt_cd', title: '재질' },
|
||||
{ id: 'avg_grdn', title: '평균구배(m/m)' },
|
||||
{ id: 'drtw_mltv_area', title: '오수배수면적(m²)' },
|
||||
{ id: 'excln_mltv_area', title: '우수배수면적(m²)' },
|
||||
{ id: 'tmpt_dpth', title: '시점깊이(m)' },
|
||||
{ id: 'tmpt_mhl_mng_no', title: '시점맨홀관리번호' },
|
||||
{ id: 'scend_dpth', title: '종점깊이(m)' },
|
||||
{ id: 'scend_mhl_mng_no', title: '종점맨홀관리번호' },
|
||||
{ id: 'trfln_cnt', title: '차선수' },
|
||||
{ id: 'vrtc_len', title: '세로길이(m)' },
|
||||
{ id: 'wdth_len', title: '가로길이(m)' },
|
||||
// { id: 'rnyssn_fvlc', title: '우기 시 유속(m/s)' },
|
||||
// { id: 'clar_fvlc', title: '건기 시 유속(m/s)' },
|
||||
// { id: 'mng_inst_cd', title: '관리기관코드' },
|
||||
// { id: 'pbadms_emd_cd', title: '행정읍면동코드' },
|
||||
// { id: 'fclty_shp_cd', title: '시설물형태코드' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'ftre_smbol_cd', title: '지형지물부호코드' },
|
||||
// { id: 'lyrTypeNm', title: '레이어타입명' },
|
||||
]
|
||||
// 하수관로 searchModal 기본정보tab
|
||||
export const MsSwagePplCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
{ id: 'fcltMngNo', title: '시설\n관리번호' },
|
||||
{ id: 'instMngNo', title: '기관\n관리번호' },
|
||||
{ id: 'flowDirectionBasis', title: '유하방향\n근거' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
{ id: "swtrmt_zone_nm", title: "처리구역" },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'mapshtNo', title: '도엽번호' },
|
||||
{ id: 'usgCd', title: '용도' },
|
||||
{ id: 'pdmtVl', title: '관경\n(mm)' },
|
||||
{ id: 'prlgVl', title: '연장\n(m)' },
|
||||
// { id: 'avgGrdn', title: '평균구배\n(m/m)' },
|
||||
{ id: 'tmptDpth', title: '시점깊이\n(m)' },
|
||||
{ id: 'tmptMhlMngNo', title: '시점맨홀\n관리번호' },
|
||||
{ id: 'scendDpth', title: '종점깊이\n(m)' },
|
||||
{ id: 'scendMhlMngNo', title: '종점맨홀\n관리번호' },
|
||||
{ id: 'vrtcLen', title: '세로길이\n(m)' },
|
||||
{ id: 'wdthLen', title: '가로길이\n(m)' },
|
||||
{ id: 'fcltyShpCd', title: '시설물형태코드' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'mqltCd', title: '재질코드' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
// { id: 'lyrTypeNm', title: '레이어타입명' },
|
||||
]
|
||||
export const MsSwagePplExcelCol = {
|
||||
'fcltMngNo': '시설 관리번호',
|
||||
'instMngNo': '기관 관리번호',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'swtrmt_zone_nm': '처리구역',
|
||||
'instlYmd': '설치일자',
|
||||
'usgCd': '용도',
|
||||
'pdmtVl': '관경(mm)',
|
||||
'prlgVl': '연장(m)',
|
||||
'tmptDpth': '시점 깊이(m)',
|
||||
'tmptMhlMngNo': '시점맨홀 관리번호',
|
||||
'scendDpth': '종점 깊이(m)',
|
||||
'scendMhlMngNo': '종점맨홀 관리번호',
|
||||
'vrtcLen': '세로길이(m)',
|
||||
'wdthLen': '가로길이(m)',
|
||||
'fcltyShpCd': '시설물형태코드',
|
||||
'mqltCd': '재질코드',
|
||||
};
|
||||
// 하수관로 용도 코드표
|
||||
export const MS_SWAGE_PPL_USE_CODES = [
|
||||
{ val: "SBA000", text: "미분류" },
|
||||
{ val: "SBA001", text: "합류관" },
|
||||
{ val: "SBA002", text: "차집관" },
|
||||
{ val: "SBA003", text: "우수관" },
|
||||
{ val: "SBA004", text: "오수관" },
|
||||
{ val: "SBA999", text: "기타" },
|
||||
];
|
||||
// 하수관로 재질 코드표
|
||||
export const MS_SWAGE_PPL_CODES = [
|
||||
{ val: "MOP000", text: "미분류" },
|
||||
{ val: "MOP001", text: "닥타일주철관(DCIP)" },
|
||||
{ val: "MOP002", text: "강관(SP)" },
|
||||
{ val: "MOP003", text: "아연도강관(GS)" },
|
||||
{ val: "MOP004", text: "염화비닐관(PVC)" },
|
||||
{ val: "MOP005", text: "폴리에틸렌관(PE)" },
|
||||
{ val: "MOP006", text: "스테인레스관(SSP)" },
|
||||
{ val: "MOP007", text: "동관(COP)" },
|
||||
{ val: "MOP008", text: "P.S.콘크리트관(PC)" },
|
||||
{ val: "MOP009", text: "석면시멘트관(AS)" },
|
||||
{ val: "MOP010", text: "회주철관(CIP)" },
|
||||
{ val: "MOP011", text: "연관(LP)" },
|
||||
{ val: "MOP012", text: "흉관(HP)" },
|
||||
{ val: "MOP013", text: "철근콘크리트관(RC)" },
|
||||
{ val: "MOP014", text: "강관(ST)" },
|
||||
{ val: "MOP015", text: "주철관(CI)" },
|
||||
{ val: "MOP016", text: "화학제품류관(PV)" },
|
||||
{ val: "MOP017", text: "도관(VC)" },
|
||||
{ val: "MOP018", text: "석축(SE)" },
|
||||
{ val: "MOP019", text: "도복장 강관(SP)" },
|
||||
{ val: "MOP020", text: "흉관(EP)" },
|
||||
{ val: "MOP021", text: "아연도강관(GP)" },
|
||||
{ val: "MOP022", text: "청동관(BR)" },
|
||||
{ val: "MOP023", text: "고무몰딩(RM)" },
|
||||
{ val: "MOP024", text: "주강(CS)" },
|
||||
{ val: "MOP025", text: "VR관" },
|
||||
{ val: "MOP026", text: "HDPE관" },
|
||||
{ val: "MOP027", text: "시멘트라이닝주철관(C)" },
|
||||
{ val: "MOP028", text: "수도용 PE관(PE)" },
|
||||
{ val: "MOP029", text: "석면시멘트관(AC)" },
|
||||
{ val: "MOP030", text: "폴리에틸렌분체라이닝강관(PEP)" },
|
||||
{ val: "MOP031", text: "에폭시코팅강관(EPS)" },
|
||||
{ val: "MOP032", text: "HI-3P" },
|
||||
{ val: "MOP033", text: "철근콘크리트박스(RCB)" },
|
||||
{ val: "MOP034", text: "콘크리트중선함(CD)" },
|
||||
{ val: "MOP035", text: "후랙시불전선함(FP)" },
|
||||
{ val: "MOP036", text: "전선함(FD)" },
|
||||
{ val: "MOP038", text: "이중보온관" },
|
||||
{ val: "MOP039", text: "폴리에틸렌피복강관(PLP)" },
|
||||
{ val: "MOP041", text: "FRP" },
|
||||
{ val: "MOP042", text: "강재" },
|
||||
{ val: "MOP999", text: "기타" },
|
||||
{ val: "MOP800", text: "2중벽PE관" },
|
||||
{ val: "MOP801", text: "3중벽PE관" },
|
||||
{ val: "MOP802", text: "가스배관용 강관" },
|
||||
{ val: "MOP803", text: "강관(피복 Double, 용접접합)" },
|
||||
{ val: "MOP804", text: "강관(피복 Single, 용접접합)" },
|
||||
{ val: "MOP805", text: "강관(CP)" },
|
||||
{ val: "MOP806", text: "강철" },
|
||||
{ val: "MOP807", text: "강화플라스틱복합관" },
|
||||
{ val: "MOP808", text: "경질염화비닐관(HI-VP)" },
|
||||
{ val: "MOP809", text: "고강성PVC이중벽관" },
|
||||
{ val: "MOP810", text: "고광도PVC이중벽관" },
|
||||
{ val: "MOP811", text: "고밀도폴리에틸렌관(THP)" },
|
||||
{ val: "MOP812", text: "고밀도폴리에틸렌파상형이중벽관" },
|
||||
{ val: "MOP813", text: "공장제 박스관거" },
|
||||
{ val: "MOP814", text: "관로없음(알케이블)" },
|
||||
{ val: "MOP815", text: "나선형강관" },
|
||||
{ val: "MOP816", text: "내충격수도관" },
|
||||
{ val: "MOP817", text: "내충격용하수도용경질염화비닐관(HI-VG)" },
|
||||
{ val: "MOP818", text: "내충격HI-VG2관" },
|
||||
{ val: "MOP819", text: "내충격PVC하수관" },
|
||||
{ val: "MOP820", text: "녹석(돌산호과 일종)" },
|
||||
{ val: "MOP821", text: "동관(구리관, 용접)" },
|
||||
{ val: "MOP822", text: "둥근잔돌" },
|
||||
{ val: "MOP823", text: "무근콘크리트" },
|
||||
{ val: "MOP824", text: "백관" },
|
||||
{ val: "MOP825", text: "복합철판파형관" },
|
||||
{ val: "MOP826", text: "분체라이닝관" },
|
||||
{ val: "MOP827", text: "석축박스" },
|
||||
{ val: "MOP828", text: "석축철콘" },
|
||||
{ val: "MOP829", text: "수도용경질염화비관" },
|
||||
{ val: "MOP830", text: "수도용경질염화비닐이음관" },
|
||||
{ val: "MOP831", text: "수도용엑포시수지분체내외면코팅강관" },
|
||||
{ val: "MOP832", text: "수지파형강관(PCS)" },
|
||||
{ val: "MOP833", text: "수지CSP" },
|
||||
{ val: "MOP834", text: "스테인레스 주름관" },
|
||||
{ val: "MOP835", text: "스테인레스관(STS)" },
|
||||
{ val: "MOP836", text: "스테인레스스틸파이프관" },
|
||||
{ val: "MOP837", text: "아스콘" },
|
||||
{ val: "MOP838", text: "아스팔트" },
|
||||
{ val: "MOP839", text: "아스팔트도복장강관(ASP)" },
|
||||
{ val: "MOP840", text: "아연" },
|
||||
{ val: "MOP841", text: "아연도금(백관)" },
|
||||
{ val: "MOP842", text: "알루미늄" },
|
||||
{ val: "MOP843", text: "알루미늄합금" },
|
||||
{ val: "MOP844", text: "압력배관용 탄소강관" },
|
||||
{ val: "MOP845", text: "양철관" },
|
||||
{ val: "MOP846", text: "양철주름관(CSP)" },
|
||||
{ val: "MOP847", text: "연합인슈강판" },
|
||||
{ val: "MOP848", text: "와사관(GSP)" },
|
||||
{ val: "MOP849", text: "원심력철근콘크리트" },
|
||||
{ val: "MOP850", text: "유리섬유관(GRP)" },
|
||||
{ val: "MOP851", text: "유리섬유복합관(GRP)" },
|
||||
{ val: "MOP852", text: "이음매없는동및동합금관" },
|
||||
{ val: "MOP853", text: "일반배관용스테인리스강관" },
|
||||
{ val: "MOP854", text: "전압 콘크리트관" },
|
||||
{ val: "MOP855", text: "주름관" },
|
||||
{ val: "MOP856", text: "주름주철파형강관" },
|
||||
{ val: "MOP857", text: "지중 저압케이블 입상관" },
|
||||
{ val: "MOP858", text: "진동및전압철근콘크리트관" },
|
||||
{ val: "MOP859", text: "철근콘크리트" },
|
||||
{ val: "MOP860", text: "철근콘크리트 블록" },
|
||||
{ val: "MOP861", text: "철근콘크리트조립흙막이" },
|
||||
{ val: "MOP862", text: "철판매입관" },
|
||||
{ val: "MOP863", text: "청동" },
|
||||
{ val: "MOP864", text: "코아식프리스트레스트콘크리트관" },
|
||||
{ val: "MOP865", text: "콘크리트" },
|
||||
{ val: "MOP866", text: "콘크리트 폴륨관" },
|
||||
{ val: "MOP867", text: "콘크리트블록" },
|
||||
{ val: "MOP868", text: "탄소강" },
|
||||
{ val: "MOP869", text: "탄소강(카본스틸)" },
|
||||
{ val: "MOP870", text: "탄소주강" },
|
||||
{ val: "MOP871", text: "트러후" },
|
||||
{ val: "MOP872", text: "파선형강관(RCF)" },
|
||||
{ val: "MOP873", text: "파형강관(CSP)" },
|
||||
{ val: "MOP874", text: "파형관" },
|
||||
{ val: "MOP875", text: "파형플라스틱관" },
|
||||
{ val: "MOP876", text: "폴리머관" },
|
||||
{ val: "MOP877", text: "폴리에스테르수지콘크리트관" },
|
||||
{ val: "MOP878", text: "폴리에틸렌 전선관" },
|
||||
{ val: "MOP879", text: "폴리에틸렌 피복강관" },
|
||||
{ val: "MOP880", text: "폴리에틸렌관 1호관" },
|
||||
{ val: "MOP881", text: "폴리에틸렌관 2호관" },
|
||||
{ val: "MOP882", text: "폴리에틸렌관(2중벽)" },
|
||||
{ val: "MOP883", text: "폴리에틸렌관(3중벽)" },
|
||||
{ val: "MOP884", text: "폴리에틸렌분체라이닝" },
|
||||
{ val: "MOP885", text: "폴리에틸렌이중벽하수관" },
|
||||
{ val: "MOP886", text: "폴리탄산에스테르" },
|
||||
{ val: "MOP887", text: "프리스트레스트콘크리트실린더관" },
|
||||
{ val: "MOP888", text: "플라스틱" },
|
||||
{ val: "MOP889", text: "플라스틱주름관" },
|
||||
{ val: "MOP890", text: "하수도용고강성경질염화비닐이중벽주름관" },
|
||||
{ val: "MOP891", text: "현장타설관" },
|
||||
{ val: "MOP892", text: "현장타설철근콘크리트관" },
|
||||
{ val: "MOP893", text: "흑관" },
|
||||
{ val: "MOP894", text: "흙" },
|
||||
{ val: "MOP895", text: "AP" },
|
||||
{ val: "MOP896", text: "API관" },
|
||||
{ val: "MOP897", text: "ASP" },
|
||||
{ val: "MOP898", text: "CMCIP" },
|
||||
{ val: "MOP899", text: "CSP" },
|
||||
{ val: "MOP900", text: "DRP" },
|
||||
{ val: "MOP901", text: "DSP" },
|
||||
{ val: "MOP902", text: "DTC관" },
|
||||
{ val: "MOP903", text: "ELP" },
|
||||
{ val: "MOP904", text: "EPP" },
|
||||
{ val: "MOP905", text: "FC관" },
|
||||
{ val: "MOP906", text: "H" },
|
||||
{ val: "MOP907", text: "H6" },
|
||||
{ val: "MOP908", text: "HCP" },
|
||||
{ val: "MOP909", text: "HI_2P관" },
|
||||
{ val: "MOP910", text: "HI-KP" },
|
||||
{ val: "MOP911", text: "HL" },
|
||||
{ val: "MOP912", text: "IP" },
|
||||
{ val: "MOP913", text: "KCGP" },
|
||||
{ val: "MOP914", text: "KP관" },
|
||||
{ val: "MOP915", text: "P2S관" },
|
||||
{ val: "MOP916", text: "PC박스(PCB)" },
|
||||
{ val: "MOP917", text: "PCS" },
|
||||
{ val: "MOP918", text: "PDP" },
|
||||
{ val: "MOP919", text: "PE이중벽관" },
|
||||
{ val: "MOP920", text: "PE적측벽1종관" },
|
||||
{ val: "MOP921", text: "PFP관" },
|
||||
{ val: "MOP922", text: "PG관" },
|
||||
{ val: "MOP923", text: "PIP관(이중보온관)" },
|
||||
{ val: "MOP924", text: "PLCSP" },
|
||||
{ val: "MOP925", text: "PS" },
|
||||
{ val: "MOP926", text: "PUB" },
|
||||
{ val: "MOP927", text: "PVB" },
|
||||
{ val: "MOP928", text: "PVC이중벽관(PVC2)" },
|
||||
{ val: "MOP929", text: "PVC-DC" },
|
||||
{ val: "MOP930", text: "PVC-DV" },
|
||||
{ val: "MOP931", text: "SBK" },
|
||||
{ val: "MOP932", text: "SCP" },
|
||||
{ val: "MOP933", text: "SEB" },
|
||||
{ val: "MOP934", text: "SIS관(이중보온관)" },
|
||||
{ val: "MOP935", text: "SMC" },
|
||||
{ val: "MOP936", text: "STW" },
|
||||
{ val: "MOP937", text: "SUS" },
|
||||
{ val: "MOP938", text: "THP나선관" },
|
||||
{ val: "MOP939", text: "U관" },
|
||||
{ val: "MOP940", text: "VG2" },
|
||||
{ val: "MOP941", text: "WSP(와사피)" },
|
||||
{ val: "MOP942", text: "Steel Grating" },
|
||||
{ val: "MOP943", text: "모래" },
|
||||
{ val: "MOP944", text: "인조석" },
|
||||
{ val: "MOP945", text: "SPOL" },
|
||||
{ val: "MOP946", text: "PCF관(강판보강 PE 나선형관)" },
|
||||
{ val: "MOP947", text: "PVC3(PVC3중벽관)" },
|
||||
{ val: "MOP948", text: "PE다중벽관" },
|
||||
{ val: "MOP977", text: "전주용입상관" },
|
||||
{ val: "MOP978", text: "MD관" },
|
||||
];
|
||||
// 하수관로 'fcltyShpCd' '시설물형태코드'
|
||||
export const MS_SWAGE_PPL_SHAPE = [
|
||||
{ val: "FOR000 ", text: "미분류" },
|
||||
{ val: "FOR001 ", text: "원형" },
|
||||
{ val: "FOR002 ", text: "원추형" },
|
||||
{ val: "FOR003 ", text: "원통형" },
|
||||
{ val: "FOR004 ", text: "구형" },
|
||||
{ val: "FOR005 ", text: "타원형" },
|
||||
{ val: "FOR006 ", text: "난원" },
|
||||
{ val: "FOR007 ", text: "반원형" },
|
||||
|
||||
{ val: "FOR010 ", text: "피라미드형" },
|
||||
{ val: "FOR011 ", text: "사면체형" },
|
||||
{ val: "FOR012 ", text: "기둥,축" },
|
||||
{ val: "FOR013 ", text: "돛대,활대처럼 둥글고 긴 원재" },
|
||||
{ val: "FOR014 ", text: "아치형" },
|
||||
{ val: "FOR015 ", text: "다중아치형" },
|
||||
{ val: "FOR016 ", text: "돔형" },
|
||||
{ val: "FOR017 ", text: "고형체형" },
|
||||
|
||||
{ val: "FOR020 ", text: "사각형" },
|
||||
{ val: "FOR021 ", text: "평방형" },
|
||||
{ val: "FOR022 ", text: "다이아몬드형" },
|
||||
{ val: "FOR023 ", text: "사다리꼴" },
|
||||
{ val: "FOR024 ", text: "각형" },
|
||||
{ val: "FOR025 ", text: "마제형관" },
|
||||
{ val: "FOR026 ", text: "태형" },
|
||||
|
||||
{ val: "FOR030 ", text: "A형" },
|
||||
{ val: "FOR031 ", text: "H형" },
|
||||
{ val: "FOR032 ", text: "I형" },
|
||||
{ val: "FOR033 ", text: "Y형" },
|
||||
{ val: "FOR034 ", text: "T형" },
|
||||
|
||||
{ val: "FOR050 ", text: "불규칙한 형태" },
|
||||
|
||||
{ val: "FOR900 ", text: "계란형" },
|
||||
{ val: "FOR901 ", text: "구(사각)형" },
|
||||
{ val: "FOR902 ", text: "높이조절철개형" },
|
||||
{ val: "FOR903 ", text: "박스형(RCB)" },
|
||||
{ val: "FOR904 ", text: "반월개거" },
|
||||
{ val: "FOR905 ", text: "반월개거(측구)" },
|
||||
{ val: "FOR906 ", text: "벽돌조" },
|
||||
{ val: "FOR907 ", text: "사각철판" },
|
||||
{ val: "FOR908 ", text: "석축개거" },
|
||||
{ val: "FOR909 ", text: "석축개거(측구)" },
|
||||
{ val: "FOR910 ", text: "승수로" },
|
||||
{ val: "FOR911 ", text: "옹벽개거" },
|
||||
{ val: "FOR912 ", text: "옹벽개거(측구)" },
|
||||
{ val: "FOR913 ", text: "이단" },
|
||||
{ val: "FOR914 ", text: "이중침전형" },
|
||||
{ val: "FOR915 ", text: "자연개거" },
|
||||
{ val: "FOR916 ", text: "자연개거(측구)" },
|
||||
{ val: "FOR917 ", text: "조립식" },
|
||||
{ val: "FOR918 ", text: "지상관" },
|
||||
{ val: "FOR919 ", text: "지상단구" },
|
||||
{ val: "FOR920 ", text: "지상쌍구" },
|
||||
{ val: "FOR921 ", text: "지하단구" },
|
||||
{ val: "FOR922 ", text: "지하쌍구" },
|
||||
{ val: "FOR923 ", text: "직사각형" },
|
||||
{ val: "FOR924 ", text: "타설식" },
|
||||
{ val: "FOR925 ", text: "토류형" },
|
||||
{ val: "FOR926 ", text: "흄관원형" },
|
||||
{ val: "FOR927 ", text: "L형측구" },
|
||||
{ val: "FOR928 ", text: "LO형측구" },
|
||||
{ val: "FOR929 ", text: "LU형측구" },
|
||||
{ val: "FOR930 ", text: "PVC이중벽관" },
|
||||
{ val: "FOR931 ", text: "RCB 맨홀" },
|
||||
{ val: "FOR932 ", text: "U형" },
|
||||
{ val: "FOR933 ", text: "U형측구(미복개)" },
|
||||
{ val: "FOR934 ", text: "U형측구(복개)" },
|
||||
|
||||
{ val: "FOR999 ", text: "기타" }
|
||||
];
|
||||
// 하수처리시설
|
||||
export const SwtrmtFcltCol = [
|
||||
{ id: 'swtrmt_fclt_nm', title: '하수처리시설명' },
|
||||
{ id: 'fclt_cpct', title: '시설용량(㎥/d)' },
|
||||
{ id: 'rgn_expln', title: '지역설명' },
|
||||
{ id: 'cmcn_ymd', title: '준공일자' },
|
||||
{ id: 'oprtng_strt_ymd', title: '가동개시일자' },
|
||||
{ id: 'clsg_ymd', title: '폐쇄일자' },
|
||||
// { id: 'prcs_msrc_cd', title: '처리공법코드' },
|
||||
// { id: 'lae_cd', title: '지자체코드' },
|
||||
{ id: 'lae_nm', title: '지자체명' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
// { id: 'sarea_cd', title: '권역코드' },
|
||||
{ id: 'sarea_nm', title: '권역명' },
|
||||
{ id: 'lrge_sarea_nm', title: '대권역명' },
|
||||
{ id: 'rvr_nm', title: '하천명' },
|
||||
{ id: 'addr', title: '주소' },
|
||||
{ id: 'dtl_addr', title: '상세주소' },
|
||||
{ id: 'rdnm_addr', title: '도로명주소' },
|
||||
{ id: 'dtl_rdnm_addr', title: '상세도로명주소' },
|
||||
{ id: 'dschg_wtst_nm', title: '방류수계명' },
|
||||
{ id: 'plln_area', title: '부지면적(m²)' },
|
||||
{ id: 'mng_rprtv_bzenty_nm', title: '관리대행업제명' },
|
||||
{ id: 'swtrmt_ppltn_cnt', title: '하수처리인구수(인)' },
|
||||
{ id: 'prcs_zone_area_sum', title: '처리구역면적합계(m²)' },
|
||||
{ id: 'xcrd', title: 'X좌표' },
|
||||
{ id: 'ycrd', title: 'Y좌표' },
|
||||
// { id: 'oprtngSttsCd', title: '가동상태코드' },
|
||||
{ id: 'rprs_msrc_cn', title: '대표공법내용' },
|
||||
// { id: 'stty_crtr_bod', title: '법정기준생물화학적산소요구량(mg/L)' },
|
||||
// { id: 'stty_crtr_toc', title: '법정기준TOC((mg/L))' },
|
||||
// { id: 'stty_crtr_ss', title: '법정기준SS(mg/L)' },
|
||||
// { id: 'stty_crtr_tn', title: '법정기준총질소(mg/L)' },
|
||||
// { id: 'stty_crtr_tph', title: '법정기준총인(mg/L)' },
|
||||
// { id: 'stty_crtr_clbcl_cnt', title: '법정기준대장균수(군수/100mL)' },
|
||||
{ id: 'bsn_nm', title: '유역명' },
|
||||
// { id: 'obsrvnSptCd', title: '관측지점코드' },
|
||||
];
|
||||
export const SwtrmtFcltCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리\n시설명' },
|
||||
{ id: 'fcltCpct', title: '시설\n용량\n(㎥/d)' },
|
||||
{ id: 'rgnExpln', title: '지역설명' },
|
||||
// { id: 'cmcnYmd', title: '준공일자' },
|
||||
{ id: 'oprtngStrtYmd', title: '가동개시일자' },
|
||||
// { id: 'clsgYmd', title: '폐쇄일자' },
|
||||
// { id: 'prcsMsrcCd', title: '처리공법코드' },
|
||||
// { id: 'laeCd', title: '지자체코드' },
|
||||
{ id: 'laeNm', title: '지자체명' },
|
||||
// { id: 'ctpvNm', title: '시도명' },
|
||||
// { id: 'sggNm', title: '시군구명' },
|
||||
// { id: 'sareaCd', title: '권역코드' },
|
||||
{ id: 'sareaNm', title: '권역명' },
|
||||
// { id: 'lrgeSareaNm', title: '대권역명' },
|
||||
{ id: 'rvrNm', title: '하천명' },
|
||||
// { id: 'addr', title: '주소' },
|
||||
// { id: 'dtlAddr', title: '상세주소' },
|
||||
// { id: 'rdnmAddr', title: '도로명주소' },
|
||||
// { id: 'dtlRdnmAddr', title: '상세도로명주소' },
|
||||
{ id: 'dschgWtstNm', title: '방류수계명' },
|
||||
// { id: 'pllnArea', title: '부지면적(m²)' },
|
||||
// { id: 'mngRprtvBzentyNm', title: '관리대행업체명' },
|
||||
{ id: 'swtrmtPpltnCnt', title: '하수\n처리\n인구수\n(인)' },
|
||||
// { id: 'prcsZoneAreaSum', title: '처리구역면적합계(m²)' },
|
||||
// { id: 'xcrd', title: 'X좌표' },
|
||||
// { id: 'ycrd', title: 'Y좌표' },
|
||||
// { id: 'oprtngSttsCd', title: '가동상태코드' },
|
||||
{ id: 'rprsMsrcCn', title: '대표\n공법\n내용' },
|
||||
// { id: 'sttyCrtrBod', title: '법정기준생물화학적산소요구량(mg/L)' },
|
||||
// { id: 'sttyCrtrToc', title: '법정기준TOC(mg/L)' },
|
||||
// { id: 'sttyCrtrSs', title: '법정기준SS(mg/L)' },
|
||||
// { id: 'sttyCrtrTn', title: '법정기준총질소(mg/L)' },
|
||||
// { id: 'sttyCrtrTph', title: '법정기준총인(mg/L)' },
|
||||
// { id: 'sttyCrtrClbclCnt', title: '법정기준대장균수(군수/100mL)' },
|
||||
// { id: 'bsnNm', title: '유역명' },
|
||||
// { id: 'obsrvnSptCd', title: '관측지점코드' },
|
||||
];
|
||||
export const SwtrmtFcltExcelCol = {
|
||||
'swtrmtFcltNm': '하수처리시설명',
|
||||
'fcltCpct': '시설 용량(㎥/d)',
|
||||
'rgnExpln': '지역설명',
|
||||
'oprtngStrtYmd': '가동개시 일자',
|
||||
'laeNm': '지자체명',
|
||||
'sareaNm': '권역명',
|
||||
'rvrNm': '하천명',
|
||||
'dschgWtstNm': '방류수계명',
|
||||
'swtrmtPpltnCnt': '하수 처리 인구수(인)',
|
||||
'rprsMsrcCn': '대표 공법 내용'
|
||||
};
|
||||
|
||||
// 하수처리구역
|
||||
export const SwtrmtPrcsZoneCol = [
|
||||
// { id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
|
||||
{ id: 'swtrmt_zone_nm', title: '하수처리구역명' },
|
||||
// { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' },
|
||||
// { id: 'ctpv_cd', title: '시도코드' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
// { id: 'sgg_cd', title: '시군구코드' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
{ id: 'swtrmt_fclt_nm', title: '하수처리시설명' },
|
||||
];
|
||||
export const SwtrmtPrcsZoneCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'swtrmtZoneSn', title: '하수처리구역 일련번호' },
|
||||
{ id: 'swtrmtZoneNm', title: '하수처리구역명' },
|
||||
// { id: 'swtrmt_fclt_cd', title: '하수처리시설코드' },
|
||||
// { id: 'ctpv_cd', title: '시도코드' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
// { id: 'sgg_cd', title: '시군구코드' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
{ id: 'swtrmtFcltNm', title: '하수처리시설명' },
|
||||
];
|
||||
export const SwtrmtPrcsZoneExcelCol = {
|
||||
'swtrmtZoneNm': '하수처리구역명',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'swtrmtFcltNm': '하수처리시설명'
|
||||
};
|
||||
|
||||
// 침수흔적도
|
||||
export const FldngWtlvMapCol = [
|
||||
{ id: 'fldng_yr', title: '침수년도' },
|
||||
{ id: 'fldng_dstr_nm', title: '침수재해명' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
{ id: 'fldng_bgng_dtm', title: '침수시작일시' },
|
||||
{ id: 'fldng_end_dtm', title: '침수종료일시' },
|
||||
{ id: 'fldng_hr', title: '침수시간' },
|
||||
{ id: 'fldng_dstrct_nm', title: '침수지구명' },
|
||||
{ id: 'fldng_cs_dtl_cd', title: '침수원인상세코드' },
|
||||
{ id: 'dtl_ocrn_cs_cn', title: '침수발생원인' },
|
||||
{ id: 'avg_fldng_wtlv', title: '평균침수위' },
|
||||
{ id: 'fldng_area', title: '침수면적' },
|
||||
{ id: 'exmn_yr', title: '조사년도' },
|
||||
{ id: 'dam_amt', title: '피해금액' },
|
||||
{ id: 'rain_bgng_tm', title: '강우시작일시' },
|
||||
{ id: 'rain_end_tm', title: '강우종료일시' },
|
||||
{ id: 'rain_tot', title: '총강우량(mm)' },
|
||||
{ id: 'rain_str', title: '강우강도(mm/hr)' },
|
||||
];
|
||||
export const FldngWtlvMapCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
{ id: 'fldngYr', title: '침수년도' },
|
||||
{ id: 'fldngDstrNm', title: '침수재해명' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
{ id: 'fldngBgngDtm', title: '침수시작일시' },
|
||||
{ id: 'fldngEndDtm', title: '침수종료일시' },
|
||||
{ id: 'fldngHr', title: '침수시간' },
|
||||
{ id: 'fldngDstrctNm', title: '침수지구명' },
|
||||
{ id: 'fldngCsDtlCd', title: '침수원인상세코드' },
|
||||
{ id: 'avgFldngWtlv', title: '평균침수위' },
|
||||
{ id: 'fldngArea', title: '침수면적' },
|
||||
{ id: 'exmnYr', title: '조사년도' },
|
||||
{ id: 'damAmt', title: '피해금액' },
|
||||
{ id: 'rainBgngTm', title: '강우시작일시' },
|
||||
{ id: 'rainEndTm', title: '강우종료일시' },
|
||||
{ id: 'rainTot', title: '총강우량(mm)' },
|
||||
{ id: 'rainStr', title: '강우강도(mm/hr)' },
|
||||
];
|
||||
export const FldngDpthMapCol = [
|
||||
{ id: 'fldng_yr', title: '침수년도' },
|
||||
{ id: 'ctpv_nm', title: '시도명' },
|
||||
{ id: 'sgg_nm', title: '시군구명' },
|
||||
{ id: 'fldng_dpth', title: '침수깊이(m)' },
|
||||
{ id: 'fldng_area', title: '침수면적(㎡)' },
|
||||
// { id: 'shape_area', title: '형상면적(㎡)' },
|
||||
// { id: 'shape_len', title: '형상길이' },
|
||||
];
|
||||
export const FldngWtlvMapExcelCol = {
|
||||
'fldngYr': '침수년도',
|
||||
'fldngDstrNm': '침수재해명',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'bgngYmd': '침수시작일자',
|
||||
'fldngBgngHm': '침수시작시분',
|
||||
'endYmd': '침수종료일자',
|
||||
'fldngEndHm': '침수종료시분',
|
||||
'fldngHr': '침수시간',
|
||||
'fldngDstrctNm': '침수지구명',
|
||||
'fldngCsDtlCd': '침수원인상세코드',
|
||||
'avgFldngWtlv': '평균침수위',
|
||||
'fldngArea': '침수면적',
|
||||
'exmnYr': '조사년도',
|
||||
'damAmt': '피해금액'
|
||||
};
|
||||
|
||||
// 하수맨홀
|
||||
export const MsSwageMhlCol = [
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftre_smbol_cd', title: '지형지물부호코드' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadms_emd_cd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mng_inst_cd', title: '관리기관코드' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
{ id: 'last_drdgn_ymd', title: '최종준설일자' },
|
||||
// { id: 'usg_cd', title: '용도코드' },
|
||||
// { id: 'fclty_shp_cd', title: '시설물형태코드' },
|
||||
// { id: 'mhl_knd_cd', title: '맨홀종류코드' },
|
||||
{ id: 'swage_mhL_diam_vl', title: '하수맨홀구경(mm)' },
|
||||
{ id: 'mqlt_cd', title: '재질' },
|
||||
{ id: 'wdth_len', title: '가로길이(m)' },
|
||||
{ id: 'vrtc_len', title: '세로길이(m)' },
|
||||
// { id: 'ang_dir', title: '표고' },
|
||||
{ id: 'altd_vl', title: '지표고(EL.m)' },
|
||||
{ id: 'dpthl_vl', title: '맨홀저고(EL.m)' },
|
||||
// { id: 'inst_mng_no', title: '기관관리번호' }
|
||||
];
|
||||
export const MsSwageMhlCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fcltMngNo', title: '시설관리번호' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
// { id: 'mapshtNo', title: '도엽번호' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'lastDrdgnYmd', title: '최종준설일자' },
|
||||
{ id: 'usgCd', title: '용도' },
|
||||
// { id: 'fcltyShpCd', title: '시설물형태코드' },
|
||||
// { id: 'mhlKndCd', title: '맨홀종류코드' },
|
||||
{ id: 'swageMhlDiamVl', title: '하수맨홀구경(mm)' },
|
||||
// { id: 'mqltCd', title: '재질코드' },
|
||||
{ id: 'wdthLen', title: '가로길이(m)' },
|
||||
{ id: 'vrtcLen', title: '세로길이(m)' },
|
||||
// { id: 'angDir', title: '표고' },
|
||||
{ id: 'altdVl', title: '지표고(EL.m)' },
|
||||
{ id: 'dpthlVl', title: '맨홀저고(EL.m)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' }
|
||||
];
|
||||
export const MsSwageMhlExcelCol = {
|
||||
'fcltMngNo': '시설관리번호',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'instlYmd': '설치일자',
|
||||
'usgCd': '용도',
|
||||
'swageMhlDiamVl': '하수맨홀구경(mm)',
|
||||
'wdthLen': '가로길이(m)',
|
||||
'vrtcLen': '세로길이(m)',
|
||||
'altdVl': '지표고(EL.m)',
|
||||
'dpthlVl': '맨홀저고(EL.m)'
|
||||
};
|
||||
// 하수맨홀 뚜껑재질 코드표
|
||||
export const MS_SWAGE_MHL_CODES = [
|
||||
{ val: "SBC000", text: "미분류" },
|
||||
{ val: "SBC001", text: "주철" },
|
||||
{ val: "SBC002", text: "철판" },
|
||||
{ val: "SBC003", text: "콘크리트" },
|
||||
{ val: "SBC004", text: "그레이팅" },
|
||||
{ val: "SBC091", text: "칼라" },
|
||||
{ val: "SBC092", text: "S.G" },
|
||||
{ val: "SBC999", text: "기타" },
|
||||
{ val: "SBC900", text: "각주철" },
|
||||
{ val: "SBC901", text: "강재" },
|
||||
{ val: "SBC902", text: "나무" },
|
||||
{ val: "SBC903", text: "나무,널판지,합판" },
|
||||
{ val: "SBC904", text: "대리석" },
|
||||
{ val: "SBC905", text: "뚜껑없음" },
|
||||
{ val: "SBC906", text: "보도용콘크리트" },
|
||||
{ val: "SBC907", text: "스틸(ST)" },
|
||||
{ val: "SBC908", text: "신형 CON" },
|
||||
{ val: "SBC909", text: "알루미늄" },
|
||||
{ val: "SBC910", text: "염화비닐(PE)" },
|
||||
{ val: "SBC911", text: "인조석" },
|
||||
{ val: "SBC912", text: "인조석(주철)" },
|
||||
{ val: "SBC913", text: "인조CONC" },
|
||||
{ val: "SBC914", text: "철, 철근" },
|
||||
{ val: "SBC915", text: "칼라콘크리트" },
|
||||
{ val: "SBC916", text: "플라스틱" },
|
||||
{ val: "SBC917", text: "PVC" },
|
||||
{ val: "SBC918", text: "SP" },
|
||||
{ val: "SBC919", text: "SS" },
|
||||
{ val: "SBC920", text: "닥타일주철" },
|
||||
{ val: "SBC921", text: "철근G" },
|
||||
{ val: "SBC922", text: "폴리에틸렌(PE)" },
|
||||
{ val: "SBC923", text: "회주철" }
|
||||
];
|
||||
// 하수맨홀 용도 코드표
|
||||
export const MS_SWAGE_MHL_USE_CODES = [
|
||||
{ val: "SMU000 ", text: "미분류" },
|
||||
{ val: "SMU001 ", text: "우수" },
|
||||
{ val: "SMU002 ", text: "오수" },
|
||||
{ val: "SMU003 ", text: "집수" },
|
||||
{ val: "SMU004 ", text: "합류" },
|
||||
{ val: "SMU005 ", text: "차집" },
|
||||
{ val: "SMU999 ", text: "기타" },
|
||||
{ val: "SMU900 ", text: "1호" },
|
||||
{ val: "SMU901 ", text: "2호" },
|
||||
{ val: "SMU902 ", text: "3호" },
|
||||
{ val: "SMU903 ", text: "4호" },
|
||||
{ val: "SMU904 ", text: "5호" },
|
||||
{ val: "SMU905 ", text: "6호" },
|
||||
{ val: "SMU906 ", text: "7호" },
|
||||
{ val: "SMU907 ", text: "8호" },
|
||||
{ val: "SMU908 ", text: "구거용맨홀" },
|
||||
{ val: "SMU909 ", text: "부관부착맨홀" },
|
||||
{ val: "SMU910 ", text: "조립식맨홀" },
|
||||
{ val: "SMU911 ", text: "특1호" },
|
||||
{ val: "SMU912 ", text: "특2호" },
|
||||
{ val: "SMU913 ", text: "특3호" },
|
||||
{ val: "SMU914 ", text: "특4호" },
|
||||
{ val: "SMU915 ", text: "특5호" },
|
||||
{ val: "SMU916 ", text: "하수맨홀용도" },
|
||||
{ val: "SMU917 ", text: "현장타설관거용" }
|
||||
];
|
||||
|
||||
// 우수펌프장
|
||||
export const ExclnSewagePumpCol = [
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
{ id: 'swage_pmstn_nm', title: '하수펌프장명' },
|
||||
{ id: 'plln_area', title: '부지면적(m²)' },
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'daily_prcs_cpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'max_impnd_cpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altd_vl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlv_vl', title: '수위(m)' },
|
||||
{ id: 'excln_wpmp_ablt_vl', title: '우수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
export const ExclnSewagePumpCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fcltMngNo', title: '시설관리번호' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
// { id: 'mapshtNo', title: '도엽번호' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'swagePmstnNm', title: '하수펌프장명' },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'pllnArea', title: '부지면적(m²)' },
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'dailyPrcsCpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'maxImpndCpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altdVl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlvVl', title: '수위(m)' },
|
||||
{ id: 'exclnWpmpAbltVl', title: '우수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
export const ExclnSewagePumpExcelCol = {
|
||||
'fcltMngNo': '시설관리번호',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'swagePmstnNm': '하수펌프장명',
|
||||
'instlYmd': '설치일자',
|
||||
'dailyPrcsCpct': '일일처리용량(㎥)',
|
||||
'maxImpndCpct': '최대저수용량(㎥)',
|
||||
'altdVl': '표고(EL.m)',
|
||||
'wtlvVl': '수위(m)',
|
||||
'exclnWpmpAbltVl': '우수양수능력(m²/d)'
|
||||
};
|
||||
// 오수펌프장
|
||||
export const DrtwSewagePumpCol = [
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
{ id: 'swage_pmstn_nm', title: '하수펌프장명' },
|
||||
{ id: 'plln_area', title: '부지면적(m²)' },
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'daily_prcs_cpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'max_impnd_cpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altd_vl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlv_vl', title: '수위(m)' },
|
||||
{ id: 'excln_wpmp_ablt_vl', title: '오수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
export const DrtwSewagePumpCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'fid', title: 'FID' },
|
||||
// { id: 'geom', title: 'geometry' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fcltMngNo', title: '시설관리번호' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
// { id: 'mapshtNo', title: '도엽번호' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'swagePmstnNm', title: '하수펌프장명' },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'pllnArea', title: '부지면적(m²)' },
|
||||
// { id: 'opngSttsCd', title: '개통상태코드' },
|
||||
// { id: 'usgCd', title: '용도코드' },
|
||||
{ id: 'dailyPrcsCpct', title: '일일처리용량(㎥)' },
|
||||
{ id: 'maxImpndCpct', title: '최대저수용량(㎥)' },
|
||||
{ id: 'altdVl', title: '표고(EL.m)' },
|
||||
{ id: 'wtlvVl', title: '수위(m)' },
|
||||
{ id: 'exclnWpmpAbltVl', title: '오수양수능력(m²/d)' },
|
||||
// { id: 'instMngNo', title: '기관관리번호' },
|
||||
]
|
||||
// 펌프장 용도 코드표
|
||||
export const DRTW_SEWAGE_PUMP_USE_CODES = [
|
||||
{ val: "SBE000", text: "미분류" },
|
||||
{ val: "SBE001", text: "빗물펌프장" },
|
||||
{ val: "SBE002", text: "오수펌프장" },
|
||||
{ val: "SBE003", text: "처리장내펌프장" },
|
||||
{ val: "SBE900", text: "오수배제" },
|
||||
{ val: "SBE901", text: "우수배제" },
|
||||
{ val: "SBE999", text: "기타" },
|
||||
];
|
||||
|
||||
// 우수토실
|
||||
export const stofcbrCol = [
|
||||
// { id: 'ftre_smbol_cd', title: '지형지물부호코드' },
|
||||
{ id: 'fclt_mng_no', title: '시설관리번호' },
|
||||
// { id: 'pbadms_emd_cd', title: '행정읍면동코드' },
|
||||
{ id: 'mapsht_no', title: '도엽번호' },
|
||||
// { id: 'mng_inst_cd', title: '관리기관코드' },
|
||||
{ id: 'instl_ymd', title: '설치일자' },
|
||||
// { id: 'fclty_shp_cd', title: '시설물형태코드' },
|
||||
{ id: 'worflw_wall_hgt', title: '월류벽높이(m)' },
|
||||
// { id: 'mhl_exst_yn_cd', title: '맨홀존재여부코드' },
|
||||
// { id: 'swage_mhl_ftre_smbol_cd', title: '하수맨홀지형지물부호코드' },
|
||||
{ id: 'swage_mhl_mng_no', title: '하수맨홀관리번호' },
|
||||
{ id: 'drtn_angl', title: '방향각도(°)' },
|
||||
]
|
||||
export const stofcbrCol2 = [
|
||||
{ id: 'rowNumber', title: '순번' },
|
||||
// { id: 'ftreSmbolCd', title: '지형지물부호코드' },
|
||||
{ id: 'fcltMngNo', title: '시설관리번호' },
|
||||
// { id: 'pbadmsEmdCd', title: '행정읍면동코드' },
|
||||
{ id: 'mapshtNo', title: '도엽번호' },
|
||||
{ id: 'ctpvNm', title: '시도' },
|
||||
{ id: 'sggNm', title: '시군구' },
|
||||
// { id: 'mngInstCd', title: '관리기관코드' },
|
||||
{ id: 'instlYmd', title: '설치일자' },
|
||||
// { id: 'fcltyShpCd', title: '시설물형태코드' },
|
||||
{ id: 'worflwWallHgt', title: '월류벽높이(m)' },
|
||||
// { id: 'mhlExstYnCd', title: '맨홀존재여부코드' },
|
||||
// { id: 'swageMhlFtreSmbolCd', title: '하수맨홀지형지물부호코드' },
|
||||
{ id: 'swageMhlMngNo', title: '하수맨홀관리번호' },
|
||||
{ id: 'drtnAngl', title: '방향각도(°)' },
|
||||
]
|
||||
export const stofcbrExcelCol = {
|
||||
'fcltMngNo': '시설관리번호',
|
||||
'mapshtNo': '도엽번호',
|
||||
'ctpvNm': '시도',
|
||||
'sggNm': '시군구',
|
||||
'instlYmd': '설치일자',
|
||||
'worflwWallHgt': '월류벽높이(m)',
|
||||
'swageMhlMngNo': '하수맨홀관리번호',
|
||||
'drtnAngl': '방향각도(°)'
|
||||
};
|
||||
|
||||
// 중점우수토실 측정지점
|
||||
export const untrtSwgRainCol1 = [
|
||||
{ id: 'manage_no', title: '관리번호' },
|
||||
{ id: 'swr_ppl_eqp_nm', title: '처리구역' },
|
||||
{ id: 'swr_ppl_trt_nm', title: '처리분구' },
|
||||
{ id: 'addr', title: '인근주소' },
|
||||
{ id: 'chmbr_xcnts', title: '토실 위도' },
|
||||
{ id: 'chmbr_ydnts', title: '토실 경도' }
|
||||
];
|
||||
|
||||
// 중점우수토실 측정지점 시설현황
|
||||
export const untrtSwgRainCol2 = [
|
||||
{ id: 'mesure_spot_se', title: '구분' }, // 유량계측정방식 기타
|
||||
{ id: 'discharge_pipe_spec', title: '방류관로 규격(m)' },
|
||||
{ id: 'swr_ppl_eqp_ar', title: '배수구역 면적(m²)' },
|
||||
{ id: 'swr_trt_popl', title: '하수처리 인구(인)' },
|
||||
{ id: 'screen_instl_at', title: '스크린설치 여부' },
|
||||
{ id: 'weir_hg', title: '위어 높이(m)' },
|
||||
{ id: 'weir_lt', title: '위어 길이(m)' }
|
||||
];
|
||||
|
||||
// 중점우수토실 강우정보 (id값 틀릴 수 있음)
|
||||
export const untrtSwgRainCol3 = [
|
||||
{ id: 'rainBeginDt', title: '강우시작일시' },
|
||||
{ id: 'rainEndDt', title: '강우종료일시' },
|
||||
{ id: 'precdngDry', title: '선행건기(h)' },
|
||||
{ id: 'rainInfo', title: '강우정보' },
|
||||
{ id: 'rainFall', title: '누적강우량(mm)' },
|
||||
{ id: 'cntncTime', title: '지속시간(h)' },
|
||||
{ id: 'rainIn', title: '강우강도(mm/h)' }
|
||||
];
|
||||
|
||||
// 중점우수토실 측정정보 (id값 틀릴 수 있음)
|
||||
export const untrtSwgRainCol4 = [
|
||||
{ id: 'mesureBeginDt', title: '측정시작일시' },
|
||||
{ id: 'mesureEndDt', title: '측정종료일시' },
|
||||
{ id: 'mesureIntrvl', title: '측정간격(분)' },
|
||||
{ id: 'mesureMhrlsInstlLc', title: '측정기기 설치위치' },
|
||||
{ id: 'flwmtrCombined', title: '유량계 종류 및 모델명' }, // 합쳐진 컬럼
|
||||
{ id: 'qltwtrBeginDt', title: '채수시작일시' },
|
||||
{ id: 'qltwtrEndDt', title: '채수종료일시' },
|
||||
{ id: 'qltwtrCo', title: '채수횟수' }
|
||||
];
|
||||
|
||||
// 중점우수토실 수량/수질측정 정보
|
||||
export const untrtSwgRainCol5 = [
|
||||
{ id: 'odr', title: '회차' },
|
||||
{ id: 'mesureDt', title: '측정일자' },
|
||||
{ id: 'mesureQy', title: '수량(m³)' },
|
||||
{ id: 'mesureBod', title: 'BOD(mg/L)' },
|
||||
{ id: 'mesureToc', title: 'TOC(mg/L)' },
|
||||
{ id: 'mesureLoad', title: '오염부하량(kg)' }
|
||||
];
|
||||
|
||||
// 🎯 날짜 필드들 (yyyymmdd -> yyyy년 mm월 dd일)
|
||||
export const dateFields = [
|
||||
'instl_ymd', 'last_drdgn_ymd', 'cmcn_ymd', 'oprtng_strt_ymd', 'clsg_ymd',
|
||||
'mesure_begin_dt', 'mesure_end_dt', 'qltwtr_begin_dt', 'qltwtr_end_dt',
|
||||
'rain_begin_dt', 'rain_end_dt', 'mesureDt',
|
||||
// 🎯 camelCase 버전 추가
|
||||
'instlYmd', 'lastDrdgnYmd', 'cmcnYmd', 'oprtngStrtYmd', 'clsgYmd',
|
||||
'mesureBeginDt', 'mesureEndDt', 'qltwtrBeginDt', 'qltwtrEndDt',
|
||||
'rainBeginDt', 'rainEndDt',
|
||||
'flfmtBgngYmd', 'flfmtEndYmd', 'flfmtYr',
|
||||
'bgng_ymd', 'end_ymd', 'bgngYmd', 'endYmd'
|
||||
];
|
||||
|
||||
// 🎯 연월 필드들 (yyyymm -> yyyy년 m월)
|
||||
export const yearMonthFields = ['aplcnYm',];
|
||||
|
||||
// 🎯 날짜시간 필드들 (yyyy-mm-dd hh:mm:ss -> yyyy년 m월 d일 hh:mm)
|
||||
export const dateTimeFields = ['regDt',];
|
||||
|
||||
// 🎯 숫자 필드들 (천 단위 구분자)
|
||||
export const numberFields = [
|
||||
'pdmt', 'prlg', 'avg_grdn', 'drtw_mltv_area', 'excln_mltv_area', 'tmpt_dpth', 'scend_dpth', 'trfln_cnt', 'vrtc_len', 'wdth_len',
|
||||
'fclt_cpct', 'plln_area', 'prcs_zone_area_sum', 'swtrmt_ppltn_cnt', 'stty_crtr_bod', 'stty_crtr_toc', 'stty_crtr_ss', 'stty_crtr_tn', 'stty_crtr_tph', 'stty_crtr_clbcl_cnt',
|
||||
'swage_mh_diam', 'wdth_len', 'vrtc_len', 'altd_vl', 'dpthlVl',
|
||||
'plln_area', 'daily_prcs_cpct', 'max_impnd_cpct', 'altd_vl', 'wtlv_vl', 'excln_wpmp_ablt',
|
||||
'worflw_wall_hgt',
|
||||
'swr_ppl_eqp_ar', 'swr_trt_popl', 'weir_hg', 'weir_lt',
|
||||
'precdng_dry', 'rain_fall', 'cntnc_time', 'rain_in',
|
||||
'mesure_intrvl', 'qltwtr_co', 'mesureQy', 'mesureBod', 'mesureToc', 'mesureLoad',
|
||||
// 🎯 camelCase 버전 추가
|
||||
'avgGrdn', 'drtwMltvArea', 'exclnMltvArea', 'tmptDpth', 'scendDpth', 'trflnCnt', 'vrtcLen', 'wdthLen',
|
||||
'fcltCpct', 'pllnArea', 'prcsZoneAreaSum', 'swtrmtPpltnCnt', 'sttyCltrBod', 'sttyCltrToc', 'sttyCltrSs', 'sttyCltrTn', 'sttyCltrTph', 'sttyCltrClbclCnt',
|
||||
'swageMhDiam', 'altdVl', 'dpthlVl',
|
||||
'dailyPrcsCpct', 'maxImpndCpct', 'wtlvVl', 'exclnWpmpAblt',
|
||||
'worflwWallHgt',
|
||||
'swrPplEqpAr', 'swrTrtPopl', 'weirHg', 'weirLt',
|
||||
'precdngDry', 'rainFall', 'cntncTime', 'rainIn',
|
||||
'mesureIntrvl', 'qltwtrCo', 'rnysnFvlc', 'clarFvlc',
|
||||
'bizAmt', 'count',
|
||||
'chmbr_xcnts', 'chmbr_ydnts',
|
||||
'fldng_hr', 'avg_fldng_wtlv', 'fldng_area', 'dam_amt',
|
||||
'fldngHr', 'avgFldngWtlv', 'fldngArea', 'damAmt'
|
||||
];
|
||||
+5
-2
@@ -4,8 +4,8 @@
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmnMapTypes";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmn-map-types";
|
||||
import LayerSettingCard from "../layer-setting/layer-setting-card";
|
||||
|
||||
type Props = {
|
||||
jobType: ExmnMapJobType;
|
||||
@@ -14,6 +14,7 @@ type Props = {
|
||||
enabled?: boolean;
|
||||
onEnabledChange?: (enabled: boolean) => void;
|
||||
onClose?: () => void;
|
||||
statusMessage?: string;
|
||||
};
|
||||
|
||||
const GRADE_OPTIONS = ["", "1", "2", "3", "4", "5"];
|
||||
@@ -26,6 +27,7 @@ export default function ExmnLayerSettingPanel({
|
||||
enabled = true,
|
||||
onEnabledChange = () => {},
|
||||
onClose = () => {},
|
||||
statusMessage = "",
|
||||
}: Props) {
|
||||
const updateFilter = (key: keyof ExmnMapFilters, value: string) => {
|
||||
onFiltersChange({
|
||||
@@ -46,6 +48,7 @@ export default function ExmnLayerSettingPanel({
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="space-y-[6px]">
|
||||
{statusMessage ? <p role="status" className="text-[1.1rem] text-muted-foreground">{statusMessage}</p> : null}
|
||||
<label className="flex min-h-[24px] flex-col items-stretch gap-1">
|
||||
<span className={`flex items-center gap-1 ${labelClass}`}>
|
||||
<span
|
||||
+9
-4
@@ -1,11 +1,13 @@
|
||||
/** 관로조사 지도 데이터 조회 API와 응답 변환 함수를 제공한다. */
|
||||
import { getRawJson } from "../../../../components/lib/api";
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmnMapTypes";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import type { ExmnMapFilters, ExmnMapJobType } from "./exmn-map-types";
|
||||
|
||||
type ExmnLayerScope = {
|
||||
sggCd?: string;
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
exmnBizId?: string;
|
||||
trtdstId?: string;
|
||||
};
|
||||
|
||||
/** 구버전 응답이 하류맨홀을 포함해도 업로드 관로조사의 상류맨홀 피처만 지도에 전달한다. */
|
||||
@@ -36,16 +38,19 @@ export const retainUpstreamManholeFeatures = (response: any) => {
|
||||
export const getExmnMapLayer = async (
|
||||
jobType: ExmnMapJobType,
|
||||
filters: ExmnMapFilters,
|
||||
scope: ExmnLayerScope,
|
||||
scope: ExmnLayerScope = {},
|
||||
signal?: AbortSignal,
|
||||
) => {
|
||||
const query = new URLSearchParams({ jobType });
|
||||
if (scope.sggCd?.trim()) query.set("sggCd", scope.sggCd.trim());
|
||||
if (scope.ctpvNm?.trim()) query.set("ctpvNm", scope.ctpvNm.trim());
|
||||
if (scope.sggNm?.trim()) query.set("sggNm", scope.sggNm.trim());
|
||||
if (scope.exmnBizId?.trim()) query.set("exmnBizId", scope.exmnBizId.trim());
|
||||
if (scope.trtdstId?.trim()) query.set("trtdstId", scope.trtdstId.trim());
|
||||
if (filters.bizCycl) query.set("bizCycl", filters.bizCycl);
|
||||
if (filters.structuralGrade) query.set("structuralGrade", filters.structuralGrade);
|
||||
if (filters.operationalGrade) query.set("operationalGrade", filters.operationalGrade);
|
||||
|
||||
const response = await getRawJson(`/sewerage-service/api/v1/exmnMap/layers?${query.toString()}`);
|
||||
const response = await getRawJson(`/sewerage-service/api/v1/exmnMap/layers?${query.toString()}`, { signal });
|
||||
return jobType === "MNHL" ? retainUpstreamManholeFeatures(response) : response;
|
||||
};
|
||||
+3
-1
@@ -21,6 +21,7 @@ export type ExmnMapFilters = {
|
||||
|
||||
export type ExmnMapEntryContext = {
|
||||
exmnBizId: string;
|
||||
trtdstId: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
bizCycl: string;
|
||||
@@ -126,7 +127,7 @@ const normalizeControlNode = (
|
||||
...node,
|
||||
lyrSn: nodeId,
|
||||
id: nodeId,
|
||||
// DB에 저장된 과거 명칭과 무관하게 조사관리 표준 화면 명칭으로 표시한다.
|
||||
// DB에 저장된 과거 명칭과 무관하게 조사사업 표준 화면 명칭으로 표시한다.
|
||||
lyrNm: fallbackName,
|
||||
upperLyrSn: upperId,
|
||||
upId: upperId,
|
||||
@@ -291,6 +292,7 @@ export const buildExmnMapEntryContext = (
|
||||
const rawJobType = String(searchParams.get("exmnJobType") ?? "").toUpperCase();
|
||||
return {
|
||||
exmnBizId: String(searchParams.get("exmnBizId") ?? "").trim(),
|
||||
trtdstId: String(searchParams.get("trtdstId") ?? "").trim(),
|
||||
ctpvNm: String(searchParams.get("ctpvNm") ?? "").trim(),
|
||||
sggNm: String(searchParams.get("sggNm") ?? "").trim(),
|
||||
bizCycl: String(searchParams.get("bizCycl") ?? "").replace(/[^0-9]/g, ""),
|
||||
@@ -0,0 +1,266 @@
|
||||
"use client";
|
||||
import { beginMapNavigation } from "@/features/map-search/lib/map-navigation";
|
||||
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type RefObject,
|
||||
} from "react";
|
||||
import GeoJSON from "ol/format/GeoJSON";
|
||||
import VectorLayer from "ol/layer/Vector";
|
||||
import VectorSource from "ol/source/Vector";
|
||||
import { getExmnMapLayer } from "./exmn-map-api";
|
||||
import {
|
||||
EMPTY_EXMN_MAP_FILTERS,
|
||||
EXMN_RESULT_PPL_LAYER_ID,
|
||||
EXMN_RESULT_MNHL_LAYER_ID,
|
||||
type ExmnMapEntryContext,
|
||||
type ExmnMapFilters,
|
||||
type ExmnMapJobType,
|
||||
} from "./exmn-map-types";
|
||||
|
||||
type Options = {
|
||||
gisApp: any;
|
||||
mapRef: RefObject<any>;
|
||||
ready: boolean;
|
||||
treeData: any[];
|
||||
entry: ExmnMapEntryContext;
|
||||
pipeChecked: boolean;
|
||||
manholeChecked: boolean;
|
||||
pipeEnabled: boolean;
|
||||
manholeEnabled: boolean;
|
||||
pipeFilters: ExmnMapFilters;
|
||||
manholeFilters: ExmnMapFilters;
|
||||
pipeStyle: any;
|
||||
manholeStyle: any;
|
||||
};
|
||||
|
||||
type LayerState = {
|
||||
layer?: VectorLayer<any>;
|
||||
controller?: AbortController;
|
||||
sequence: number;
|
||||
};
|
||||
|
||||
/** 전체 조사 레이어와 상세현황 위치 이동을 분리하며 유형별 요청을 취소/무효화한다. */
|
||||
export const useExmnResultMap = (options: Options) => {
|
||||
const {
|
||||
gisApp,
|
||||
mapRef,
|
||||
ready,
|
||||
treeData,
|
||||
entry,
|
||||
pipeChecked,
|
||||
manholeChecked,
|
||||
pipeEnabled,
|
||||
manholeEnabled,
|
||||
pipeFilters,
|
||||
manholeFilters,
|
||||
pipeStyle,
|
||||
manholeStyle,
|
||||
} = options;
|
||||
// 지도 객체/요청 수명은 React 렌더링과 분리하고 관로·맨홀을 독립적으로 관리한다.
|
||||
const layers = useRef<Record<ExmnMapJobType, LayerState>>({
|
||||
PPL: { sequence: 0 },
|
||||
MNHL: { sequence: 0 },
|
||||
});
|
||||
const fittedContext = useRef("");
|
||||
const [messages, setMessages] = useState<Record<ExmnMapJobType, string>>({
|
||||
PPL: "",
|
||||
MNHL: "",
|
||||
});
|
||||
|
||||
/** 체크 해제/언마운트 시 늦게 도착한 응답도 다시 표시되지 않게 한다. */
|
||||
const removeLayer = useCallback(
|
||||
(job: ExmnMapJobType) => {
|
||||
const state = layers.current[job];
|
||||
state.sequence++;
|
||||
state.controller?.abort();
|
||||
state.controller = undefined;
|
||||
if (state.layer) {
|
||||
gisApp.Module.select?.removeLayer?.(state.layer);
|
||||
mapRef.current?.removeLayer?.(state.layer);
|
||||
}
|
||||
state.layer = undefined;
|
||||
},
|
||||
[gisApp, mapRef],
|
||||
);
|
||||
|
||||
/** 일반 진입은 전체, 조사관리 지도보기는 선택 사업·처리분구만 조회해 겹친 타 사업 선택을 방지한다. */
|
||||
const loadLayer = useCallback(
|
||||
async (job: ExmnMapJobType, filters: ExmnMapFilters) => {
|
||||
const map = mapRef.current;
|
||||
if (!map) return;
|
||||
const navigation = entry.jobType === job && entry.exmnBizId && entry.trtdstId
|
||||
? beginMapNavigation(map) : null;
|
||||
removeLayer(job);
|
||||
const state = layers.current[job];
|
||||
const sequence = state.sequence;
|
||||
const controller = new AbortController();
|
||||
state.controller = controller;
|
||||
const active = () =>
|
||||
state.sequence === sequence && !controller.signal.aborted;
|
||||
setMessages((current) => ({
|
||||
...current,
|
||||
[job]: "조사결과를 불러오는 중입니다.",
|
||||
}));
|
||||
try {
|
||||
const response = await getExmnMapLayer(
|
||||
job,
|
||||
filters,
|
||||
entry.exmnBizId && entry.trtdstId
|
||||
? { exmnBizId: entry.exmnBizId, trtdstId: entry.trtdstId }
|
||||
: {},
|
||||
controller.signal,
|
||||
);
|
||||
if (!active()) return;
|
||||
const data = response?.data ?? response;
|
||||
const features = new GeoJSON().readFeatures(data, {
|
||||
dataProjection: "EPSG:5179",
|
||||
featureProjection: gisApp.TO_PROJ,
|
||||
});
|
||||
const source = new VectorSource({ features });
|
||||
const layer = new VectorLayer({
|
||||
source,
|
||||
zIndex: job === "PPL" ? 9988 : 9989,
|
||||
style: job === "PPL" ? pipeStyle : manholeStyle,
|
||||
});
|
||||
layer.setProperties({
|
||||
id:
|
||||
job === "PPL"
|
||||
? EXMN_RESULT_PPL_LAYER_ID
|
||||
: EXMN_RESULT_MNHL_LAYER_ID,
|
||||
lyrNm: job === "PPL" ? "관로조사" : "맨홀조사",
|
||||
lyrTyNm: job === "PPL" ? "tb_ms_swage_ppl" : "tb_ms_swage_mhl",
|
||||
clickPsblYn: "Y",
|
||||
chcYn: "Y",
|
||||
});
|
||||
state.layer = layer;
|
||||
map.addLayer(layer);
|
||||
gisApp.Module.select?.addLayer?.(layer);
|
||||
const missing = Number(data?.missingGeometryCount ?? 0);
|
||||
const diagnostics = data?.diagnostics ?? {};
|
||||
const reasons = [
|
||||
["지역 확인 불가", diagnostics.regionUnresolvedCount],
|
||||
["시설번호 없음", diagnostics.identifierMissingCount],
|
||||
["도형 없음", diagnostics.geometryNotFoundCount],
|
||||
["도형 중복", diagnostics.ambiguousGeometryCount],
|
||||
]
|
||||
.filter(([, count]) => Number(count) > 0)
|
||||
.map(([label, count]) => `${label} ${count}건`)
|
||||
.join(", ");
|
||||
const summary = features.length
|
||||
? `${features.length.toLocaleString()}개 표시`
|
||||
: "표시할 조사결과가 없습니다.";
|
||||
const status = missing
|
||||
? `${summary} · 공간정보 미연결 ${missing}건${reasons ? ` (${reasons})` : ""}`
|
||||
: summary;
|
||||
setMessages((current) => ({ ...current, [job]: status }));
|
||||
|
||||
if (entry.jobType !== job || !entry.exmnBizId || !entry.trtdstId)
|
||||
return;
|
||||
// 위치 조회 실패가 정상적으로 조회된 기본 레이어를 지우지 않도록 별도 처리한다.
|
||||
try {
|
||||
const selectedResponse = await getExmnMapLayer(
|
||||
job,
|
||||
filters,
|
||||
{ exmnBizId: entry.exmnBizId, trtdstId: entry.trtdstId },
|
||||
controller.signal,
|
||||
);
|
||||
if (!active()) return;
|
||||
const selectedData = selectedResponse?.data ?? selectedResponse;
|
||||
const selectedFeatures = new GeoJSON().readFeatures(selectedData, {
|
||||
dataProjection: "EPSG:5179",
|
||||
featureProjection: gisApp.TO_PROJ,
|
||||
});
|
||||
if (!selectedFeatures.length) {
|
||||
setMessages((current) => ({
|
||||
...current,
|
||||
[job]: `${status} · 선택한 처리분구에 현재 조건으로 표시할 공간정보가 없습니다.`,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
const selectedSource = new VectorSource({
|
||||
features: selectedFeatures,
|
||||
});
|
||||
// 별도 강조선을 추가하지 않고 기존 조사 레이어 색상으로 표시한다.
|
||||
const contextKey = `${entry.exmnBizId}:${entry.trtdstId}:${job}`;
|
||||
const extent = selectedSource.getExtent();
|
||||
if (
|
||||
fittedContext.current !== contextKey &&
|
||||
extent.every(Number.isFinite) && navigation?.commit(extent)
|
||||
) {
|
||||
map
|
||||
.getView()
|
||||
.fit(extent, {
|
||||
duration: 300,
|
||||
padding: [80, 420, 80, 80],
|
||||
maxZoom: 18,
|
||||
constrainResolution: true,
|
||||
nearest: true,
|
||||
});
|
||||
fittedContext.current = contextKey;
|
||||
}
|
||||
} catch (error) {
|
||||
if (active())
|
||||
setMessages((current) => ({
|
||||
...current,
|
||||
[job]: `${status} · 선택 영역 조회 실패: ${error instanceof Error ? error.message : "다시 조회해 주세요."}`,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
if (active())
|
||||
setMessages((current) => ({
|
||||
...current,
|
||||
[job]: `조회 실패: ${error instanceof Error ? error.message : "다시 조회해 주세요."}`,
|
||||
}));
|
||||
}
|
||||
},
|
||||
[
|
||||
entry.exmnBizId,
|
||||
entry.trtdstId,
|
||||
entry.jobType,
|
||||
gisApp,
|
||||
mapRef,
|
||||
pipeStyle,
|
||||
manholeStyle,
|
||||
removeLayer,
|
||||
],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && pipeChecked)
|
||||
void loadLayer("PPL", pipeEnabled ? pipeFilters : EMPTY_EXMN_MAP_FILTERS);
|
||||
else removeLayer("PPL");
|
||||
return () => removeLayer("PPL");
|
||||
}, [
|
||||
ready,
|
||||
pipeChecked,
|
||||
pipeEnabled,
|
||||
pipeFilters,
|
||||
treeData,
|
||||
loadLayer,
|
||||
removeLayer,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && manholeChecked)
|
||||
void loadLayer(
|
||||
"MNHL",
|
||||
manholeEnabled ? manholeFilters : EMPTY_EXMN_MAP_FILTERS,
|
||||
);
|
||||
else removeLayer("MNHL");
|
||||
return () => removeLayer("MNHL");
|
||||
}, [
|
||||
ready,
|
||||
manholeChecked,
|
||||
manholeEnabled,
|
||||
manholeFilters,
|
||||
treeData,
|
||||
loadLayer,
|
||||
removeLayer,
|
||||
]);
|
||||
|
||||
return { removeLayer, messages };
|
||||
};
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/** 침수흔적 강우분석과 연결된 심선·위선 목록 조회를 담당한다. */
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
|
||||
import type { FldngHstryDetailCacheValue } from "./fldngHstryTypes";
|
||||
import type { FldngHstryDetailCacheValue } from "@/entities/flood-history/model/flood-history-types";
|
||||
|
||||
const normalizeFldngHstryRainAnalysisPayload = (data: any) => {
|
||||
if (!data || typeof data !== "object") return {};
|
||||
+2
-2
@@ -5,8 +5,8 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import type { FldngHstryItem } from "./fldngHstryTypes";
|
||||
import { formatFldngHstryArea, formatFldngHstryDepth } from "./fldngHstryUtils";
|
||||
import type { FldngHstryItem } from "@/entities/flood-history/model/flood-history-types";
|
||||
import { formatFldngHstryArea, formatFldngHstryDepth } from "@/entities/flood-history/lib/flood-history-utils";
|
||||
|
||||
type Props = {
|
||||
detail: FldngHstryItem;
|
||||
+11
-7
@@ -3,6 +3,7 @@
|
||||
* 우측 정보 화면의 심선·위선 목록, 선택 상태와 화면 생명주기 캐시를 관리한다.
|
||||
*/
|
||||
"use client";
|
||||
import { mapSearchNavigationOptions } from "@/features/map-search/lib/map-navigation";
|
||||
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import type { RefObject } from "react";
|
||||
@@ -10,12 +11,8 @@ import GisApp from "@common/business_components/ui/map/lib/gis.core";
|
||||
import type Feature from "ol/Feature.js";
|
||||
import VectorSource from "ol/source/Vector.js";
|
||||
|
||||
import { loadFldngHstryDetail } from "./fldngHstryClient";
|
||||
import {
|
||||
FLDNG_HSTRY_DEPTH_LAYER_TYPE,
|
||||
FLDNG_HSTRY_LAYER_TYPE,
|
||||
type FldngHstryDetailCacheValue,
|
||||
} from "./fldngHstryTypes";
|
||||
import { loadFldngHstryDetail } from "./fldng-hstry-client";
|
||||
import { FLDNG_HSTRY_DEPTH_LAYER_TYPE, FLDNG_HSTRY_LAYER_TYPE, type FldngHstryDetailCacheValue } from "@/entities/flood-history/model/flood-history-types";
|
||||
|
||||
type FldngHstryGisLayer = {
|
||||
searchLayer?: { getSource?: () => VectorSource | null } | null;
|
||||
@@ -27,6 +24,7 @@ type FldngHstryGisLayer = {
|
||||
showSearchResultsFromGeoJSON: (
|
||||
layerTypeName: string,
|
||||
featureId: string,
|
||||
options?: { beforeFocus: (extent: unknown) => boolean },
|
||||
) => void | Promise<void>;
|
||||
};
|
||||
|
||||
@@ -131,11 +129,14 @@ const displayCachedFldngHstryFeatures = (
|
||||
gisLayer: FldngHstryGisLayer,
|
||||
layerTypeName: string,
|
||||
features: Feature[],
|
||||
beforeFocus: (extent: unknown) => boolean,
|
||||
) => {
|
||||
const matched = Object.values(GisApp.layerCode ?? {}).find(
|
||||
(item: any) => item?.lyrTyNm === layerTypeName,
|
||||
) as any;
|
||||
if (!matched || features.length === 0) return;
|
||||
// 캐시를 재사용해도 최신 선택의 유효 도형일 때만 검색조건을 초기화한다.
|
||||
if (!beforeFocus(features[0]?.getGeometry()?.getExtent())) return;
|
||||
|
||||
gisLayer.clearSearchLayer(matched);
|
||||
const layer = gisLayer.ensureSearchLayer(matched);
|
||||
@@ -152,17 +153,20 @@ const showFldngHstryFeature = async (
|
||||
featureId: string,
|
||||
) => {
|
||||
const cacheKey = `${layerTypeName}:${featureId}`;
|
||||
/** 캐시 대기와 신규 WFS 조회가 동일한 이동 성공/취소 계약을 사용한다. */
|
||||
const navigationOptions = mapSearchNavigationOptions((GisApp.Module.core as any).map);
|
||||
const cachedRequest = cache.get(cacheKey);
|
||||
if (cachedRequest) {
|
||||
displayCachedFldngHstryFeatures(
|
||||
gisLayer,
|
||||
layerTypeName,
|
||||
await cachedRequest,
|
||||
navigationOptions.beforeFocus,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await gisLayer.showSearchResultsFromGeoJSON(layerTypeName, featureId);
|
||||
await gisLayer.showSearchResultsFromGeoJSON(layerTypeName, featureId, navigationOptions);
|
||||
const source = gisLayer.searchLayer?.getSource?.();
|
||||
if (!source) return;
|
||||
|
||||
+6
-9
@@ -5,12 +5,9 @@
|
||||
"use client";
|
||||
|
||||
import { SelectBox } from "@common/business_components/ui";
|
||||
import {
|
||||
FLDNG_HSTRY_MIN_YEAR,
|
||||
type FldngHstryYearRange,
|
||||
} from "./fldngHstryTypes";
|
||||
import { normalizeFldngHstryYearRange } from "./fldngHstryUtils";
|
||||
import LayerSettingCard from "../layerSetting/LayerSettingCard";
|
||||
import { FLDNG_HSTRY_MIN_YEAR, FLDNG_HSTRY_MAX_YEAR, type FldngHstryYearRange } from "@/entities/flood-history/model/flood-history-types";
|
||||
import { normalizeFldngHstryYearRange } from "@/entities/flood-history/lib/flood-history-utils";
|
||||
import LayerSettingCard from "../layer-setting/layer-setting-card";
|
||||
|
||||
type Props = {
|
||||
yearRange: FldngHstryYearRange;
|
||||
@@ -29,10 +26,10 @@ export default function FldngHstryLayerSettingPanel({
|
||||
onClose,
|
||||
}: Props) {
|
||||
const normalizedRange = normalizeFldngHstryYearRange(yearRange);
|
||||
const currentYear = new Date().getFullYear();
|
||||
// 제공 자료의 최대 연도부터 최소 연도까지 선택 목록을 구성한다.
|
||||
const yearOptions = Array.from(
|
||||
{ length: currentYear - FLDNG_HSTRY_MIN_YEAR + 1 },
|
||||
(_, index) => String(currentYear - index),
|
||||
{ length: FLDNG_HSTRY_MAX_YEAR - FLDNG_HSTRY_MIN_YEAR + 1 },
|
||||
(_, index) => String(FLDNG_HSTRY_MAX_YEAR - index),
|
||||
);
|
||||
const startYearOptions = yearOptions
|
||||
.filter((year) => Number(year) <= Number(normalizedRange.endYear))
|
||||
+24
-63
@@ -7,34 +7,17 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button, Input } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
import { requestGridExcel } from "@/shared/api/grid-export";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/window-popup/model/types";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { fetchAllPages } from "@/shared/lib/fetch-all-pages";
|
||||
import useStore from "@/store/useStore";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import {
|
||||
FLDNG_HSTRY_LAYER_TYPE,
|
||||
FLDNG_HSTRY_DEFAULT_FILTERS,
|
||||
FLDNG_HSTRY_DOWNLOAD_BATCH_SIZE,
|
||||
FLDNG_HSTRY_DOWNLOAD_HEADERS,
|
||||
FLDNG_HSTRY_LIST_HEADLIST,
|
||||
FLDNG_HSTRY_PAGE_SIZE,
|
||||
FLDNG_HSTRY_ROW_SELECT_MESSAGE,
|
||||
type FldngHstryApiItem,
|
||||
type FldngHstryFilters,
|
||||
type FldngHstryTableRow,
|
||||
} from "./fldngHstryTypes";
|
||||
import {
|
||||
attachFldngHstryGeometry,
|
||||
buildFldngHstryListQueryString,
|
||||
buildFldngHstryYearOptions,
|
||||
normalizeFldngHstryFilters,
|
||||
toFldngHstryTableRows,
|
||||
} from "./fldngHstryUtils";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupFooterNote from "@/features/window-popup/ui/popup-footer-note";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import { FLDNG_HSTRY_LAYER_TYPE, FLDNG_HSTRY_DEFAULT_FILTERS, FLDNG_HSTRY_LIST_HEADLIST, FLDNG_HSTRY_PAGE_SIZE, FLDNG_HSTRY_ROW_SELECT_MESSAGE, type FldngHstryApiItem, type FldngHstryFilters, type FldngHstryTableRow } from "@/entities/flood-history/model/flood-history-types";
|
||||
import { attachFldngHstryGeometry, buildFldngHstryListQueryString, buildFldngHstryYearOptions, normalizeFldngHstryFilters, toFldngHstryTableRows } from "@/entities/flood-history/lib/flood-history-utils";
|
||||
|
||||
const unwrapPayload = <T,>(response: any): T =>
|
||||
(response?.data ?? response) as T;
|
||||
@@ -58,26 +41,6 @@ const resolveFldngHstryMapFeatureFid = (row: FldngHstryTableRow) => {
|
||||
return "";
|
||||
};
|
||||
|
||||
const buildDownloadRows = (rows: FldngHstryTableRow[]) =>
|
||||
rows.map((row) => ({
|
||||
rowNo: row.rowNo,
|
||||
floodYearDisplay: row.floodYearDisplay,
|
||||
ctpvNm: row.ctpvNm,
|
||||
sggNm: row.sggNm,
|
||||
disasterName: row.disasterName,
|
||||
rainBgngTmDisplay: row.rainBgngTmDisplay,
|
||||
rainEndTmDisplay: row.rainEndTmDisplay,
|
||||
rainTotDisplay: row.rainTotDisplay,
|
||||
rainStrDisplay: row.rainStrDisplay,
|
||||
floodHourDisplay: row.floodHourDisplay,
|
||||
districtName: row.districtName,
|
||||
causeDetailCode: row.causeDetailCode,
|
||||
causeDetailName: row.causeDetailName,
|
||||
avgFloodDepthDisplay: row.avgFloodDepthDisplay,
|
||||
floodAreaDisplay: row.floodAreaDisplay,
|
||||
exmnYearDisplay: row.exmnYearDisplay,
|
||||
damageAmountDisplay: row.damageAmountDisplay,
|
||||
}));
|
||||
|
||||
const resolveRegionNames = (
|
||||
row: FldngHstryTableRow,
|
||||
@@ -265,19 +228,13 @@ export default function FldngHstryPopupPage() {
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await fetchAllPages({
|
||||
batchSize: FLDNG_HSTRY_DOWNLOAD_BATCH_SIZE,
|
||||
fetchPage: (page, size) => fetchRows(page + 1, appliedFilters, size),
|
||||
getItems: (page) => page.rows,
|
||||
getTotal: (page) => page.total,
|
||||
});
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: buildDownloadRows(response.items) as unknown as JSON[],
|
||||
fileName: `침수발생이력_${new Date().toISOString().slice(0, 10)}.xlsx`,
|
||||
sheetNameArray: ["침수발생이력"],
|
||||
headerRange: "A1:Q1",
|
||||
headerMapping: FLDNG_HSTRY_DOWNLOAD_HEADERS,
|
||||
});
|
||||
const params = new URLSearchParams(buildFldngHstryListQueryString(appliedFilters, 1, FLDNG_HSTRY_PAGE_SIZE));
|
||||
params.delete("page"); params.delete("size");
|
||||
await requestGridExcel("v1/spatialGridExports/floodHistories", {
|
||||
history: Object.fromEntries(params),
|
||||
ctpvNames: Object.fromEntries(ctpv.map((item) => [String(item.val).trim(), item.text])),
|
||||
sggNames: Object.fromEntries(sgg.map((item) => [String(item.val).trim(), item.text])),
|
||||
}, `침수발생이력_${new Date().toISOString().slice(0, 10)}.xlsx`);
|
||||
} catch (error) {
|
||||
console.error("침수발생이력 다운로드 중 오류:", error);
|
||||
alert(
|
||||
@@ -465,9 +422,13 @@ export default function FldngHstryPopupPage() {
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="rounded-sm bg-secondary px-4 py-3 text-[1.2rem] text-muted-foreground">
|
||||
행을 클릭하면 메인 지도에서 해당 침수흔적을 표시합니다.
|
||||
</div>
|
||||
{/* 기존 행 클릭 안내에 인수테스트 요구(자료 원천 표시) 출처 문구를 글머리표로 더한다. */}
|
||||
<PopupFooterNote
|
||||
items={[
|
||||
"행을 클릭하면 메인 지도에서 해당 침수흔적을 표시합니다.",
|
||||
"출처 : 행정안전부 재난보험과(2002년~2025년)",
|
||||
]}
|
||||
/>
|
||||
</StandardPopupLayout>
|
||||
);
|
||||
}
|
||||
+23
-14
@@ -7,12 +7,13 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@common/components";
|
||||
import { StandardPopupLayout } from "@common/business_components";
|
||||
import { getData } from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/windowPopup/model/types";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import useStore from "@/store/useStore";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import { WINDOW_POPUP_MESSAGE_TYPE } from "@/features/window-popup/model/types";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupFooterNote from "@/features/window-popup/ui/popup-footer-note";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import {
|
||||
FLOOD_DNGR_MAP_DEFAULT_FILTERS,
|
||||
FLOOD_DNGR_MAP_DEPTH_OPTIONS,
|
||||
@@ -25,7 +26,7 @@ import {
|
||||
type FloodDngrMapExtent,
|
||||
type FloodDngrMapFilters,
|
||||
type FloodDngrMapTableRow,
|
||||
} from "./floodDngrMapTypes";
|
||||
} from "./flood-dngr-map-types";
|
||||
|
||||
const FLOOD_DNGR_MAP_REQUEST_TIMEOUT_MS = 10_000;
|
||||
const FLOOD_DNGR_MAP_LOADING_WATCHDOG_MS = 12_000;
|
||||
@@ -226,7 +227,12 @@ export default function FloodDngrMapPopupPage() {
|
||||
const response = await getFloodDngrMapJson(
|
||||
`/sewerage-service/api/v1/fldng/floodDngrMap/group/extent?${params.toString()}`,
|
||||
);
|
||||
const payload = response as FloodDngrMapExtent;
|
||||
const payload = response as FloodDngrMapExtent | null | undefined;
|
||||
// 성공 응답의 null·생략된 data는 위치 부재이다. 좌표 접근과 지도 메시지를 중단한다.
|
||||
if (payload == null) {
|
||||
setErrorMessage("위치 정보가 없습니다");
|
||||
return;
|
||||
}
|
||||
const extent = [
|
||||
payload?.minX,
|
||||
payload?.minY,
|
||||
@@ -237,7 +243,7 @@ export default function FloodDngrMapPopupPage() {
|
||||
extent.length !== 4 ||
|
||||
extent.some((value) => !Number.isFinite(value))
|
||||
) {
|
||||
throw new Error("해당 도시침수지도 피처의 위치 정보가 없습니다.");
|
||||
throw new Error("도시침수지도 위치 응답의 좌표가 올바르지 않습니다.");
|
||||
}
|
||||
window.opener?.postMessage(
|
||||
{
|
||||
@@ -253,11 +259,9 @@ export default function FloodDngrMapPopupPage() {
|
||||
} catch (error) {
|
||||
console.error("도시침수지도 위치 이동 중 오류:", error);
|
||||
const message =
|
||||
error instanceof Error && error.message.includes("404")
|
||||
? "해당 도시침수지도 데이터에 위치 정보가 없어 이동할 수 없습니다."
|
||||
: error instanceof Error
|
||||
? error.message
|
||||
: "도시침수지도 위치 이동 중 오류가 발생했습니다.";
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "도시침수지도 위치 이동 중 오류가 발생했습니다.";
|
||||
setErrorMessage(message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -395,6 +399,11 @@ export default function FloodDngrMapPopupPage() {
|
||||
onRowClick={(row) => void handleRowClick(row)}
|
||||
rowId={(row, index) => `${row.sggCd}-${row.sbmrgFrqExpln}-${index}`}
|
||||
/>
|
||||
|
||||
{/* 인수테스트 요구: 팝업 자료의 원천을 목록 아래에 표시한다. */}
|
||||
<PopupFooterNote
|
||||
items={["출처 : 한강홍수통제소, 행정구역별 도시침수지도(2025.09)"]}
|
||||
/>
|
||||
</StandardPopupLayout>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -159,7 +159,7 @@ export const toFloodDngrMapTableRows = (
|
||||
): FloodDngrMapTableRow[] =>
|
||||
items.map((item, index) => ({
|
||||
...item,
|
||||
rowNo: total - page * pageSize - index,
|
||||
rowNo: page * pageSize + index + 1,
|
||||
ctpvNm: resolveAdministrativeName(item.ctpvCd, ctpvOptions, 2),
|
||||
sggNm: resolveAdministrativeName(item.sggCd, sggOptions, 5),
|
||||
sbmrgDpthlDisplay: toDepthRangeDisplay(
|
||||
+61
-22
@@ -4,15 +4,19 @@
|
||||
*/
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState, useRef } from "react";
|
||||
import { useFacilityCodeCatalog } from "@/entities/facility/lib/use-facility-code-catalog";
|
||||
|
||||
import { Button } from "@common/components/ui";
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { VerticalTable } from "@/components/ui/verticalTable";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { VerticalTable } from "@/components/ui/vertical-table";
|
||||
|
||||
import type { GisEditChange, GisEditPipeBasicInfo } from "./gisEditTypes";
|
||||
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES } from "../common/table";
|
||||
import type { GisEditChange, GisEditPipeBasicInfo } from "./gis-edit-types";
|
||||
import {
|
||||
MS_SWAGE_PPL_CODES,
|
||||
MS_SWAGE_PPL_USE_CODES,
|
||||
} from "@/entities/facility/model/facility-code-fallbacks";
|
||||
|
||||
type Props = {
|
||||
change: GisEditChange;
|
||||
@@ -206,20 +210,25 @@ const normalizeBasicFormValues = (values: Record<string, any>) => {
|
||||
return nextValues;
|
||||
};
|
||||
|
||||
const buildBaseFeatureValues = (featureData: any) => {
|
||||
const buildBaseFeatureValues = (
|
||||
featureData: any,
|
||||
catalog?: ReturnType<typeof useFacilityCodeCatalog>,
|
||||
) => {
|
||||
const resolveUsageCode = (value: unknown) => {
|
||||
const raw = String(value ?? "").trim();
|
||||
const matched = MS_SWAGE_PPL_USE_CODES.find(
|
||||
(item) => item.val === raw || item.text === raw,
|
||||
);
|
||||
const matched = [
|
||||
...(catalog?.MS_SWAGE_PPL_USE_CODES ?? []),
|
||||
...MS_SWAGE_PPL_USE_CODES,
|
||||
].find((item) => item.val === raw || item.text === raw);
|
||||
return matched?.val.trim() ?? raw;
|
||||
};
|
||||
|
||||
const resolveMaterialCode = (value: unknown) => {
|
||||
const raw = String(value ?? "").trim();
|
||||
const matched = MS_SWAGE_PPL_CODES.find(
|
||||
(item) => item.val === raw || item.text === raw,
|
||||
);
|
||||
const matched = [
|
||||
...(catalog?.MS_SWAGE_PPL_CODES ?? []),
|
||||
...MS_SWAGE_PPL_CODES,
|
||||
].find((item) => item.val === raw || item.text === raw);
|
||||
return matched?.val.trim() ?? raw;
|
||||
};
|
||||
|
||||
@@ -243,13 +252,12 @@ const buildBaseFeatureValues = (featureData: any) => {
|
||||
}, {});
|
||||
};
|
||||
|
||||
const getPipeUsageLabel = (value: unknown) => {
|
||||
const getPipeUsageLabel = (value: unknown, codes = MS_SWAGE_PPL_USE_CODES) => {
|
||||
const raw = String(value ?? "").trim();
|
||||
if (!raw) return "";
|
||||
return (
|
||||
MS_SWAGE_PPL_USE_CODES.find(
|
||||
(item) => item.val.trim() === raw || item.text === raw,
|
||||
)?.text ?? raw
|
||||
codes.find((item) => item.val.trim() === raw || item.text === raw)?.text ??
|
||||
raw
|
||||
);
|
||||
};
|
||||
|
||||
@@ -269,10 +277,17 @@ export default function GisEditPipeSessionPopup({
|
||||
onClose,
|
||||
onSave,
|
||||
}: Props) {
|
||||
const catalog = useFacilityCodeCatalog();
|
||||
// CMS 명칭 갱신은 입력 중인 초안을 초기화할 이유가 아니다.
|
||||
const initializedSource = useRef<{
|
||||
featureData: any;
|
||||
changeId: string;
|
||||
isNewPipe: boolean;
|
||||
} | null>(null);
|
||||
const isNewPipe = change.editType === "ADD";
|
||||
const baseFeatureValues = useMemo(
|
||||
() => buildBaseFeatureValues(featureData),
|
||||
[featureData],
|
||||
() => buildBaseFeatureValues(featureData, catalog),
|
||||
[featureData, catalog],
|
||||
);
|
||||
const [basicInfoTableData, setBasicInfoTableData] = useState<
|
||||
Record<string, any>
|
||||
@@ -322,12 +337,36 @@ export default function GisEditPipeSessionPopup({
|
||||
PIPE_BASIC_INFO_FIELDS.map((field) =>
|
||||
field.id === "mngInstCd"
|
||||
? { ...field, options: mngInstSelectOptions }
|
||||
: field,
|
||||
: field.id === "usgCd" || field.id === "mqltCd"
|
||||
? {
|
||||
...field,
|
||||
options: [
|
||||
EMPTY_SELECT_OPTION,
|
||||
...(field.id === "usgCd"
|
||||
? catalog.MS_SWAGE_PPL_USE_CODES
|
||||
: catalog.MS_SWAGE_PPL_CODES
|
||||
).map((item) => ({ value: item.val, label: item.text })),
|
||||
],
|
||||
}
|
||||
: field,
|
||||
),
|
||||
[mngInstSelectOptions],
|
||||
[mngInstSelectOptions, catalog],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const previous = initializedSource.current;
|
||||
if (
|
||||
previous &&
|
||||
previous.featureData === featureData &&
|
||||
previous.changeId === change.changeId &&
|
||||
previous.isNewPipe === isNewPipe
|
||||
)
|
||||
return;
|
||||
initializedSource.current = {
|
||||
featureData,
|
||||
changeId: change.changeId,
|
||||
isNewPipe,
|
||||
};
|
||||
const initialBasicValues: Record<string, any> = {
|
||||
...baseFeatureValues,
|
||||
...normalizeBasicFormValues(change.basicInfo ?? {}),
|
||||
@@ -335,7 +374,7 @@ export default function GisEditPipeSessionPopup({
|
||||
|
||||
setBasicInfoTableData(initialBasicValues);
|
||||
setInputInfoTableData(buildInitialInputDraft(change));
|
||||
}, [baseFeatureValues, change.changeId, isNewPipe]);
|
||||
}, [baseFeatureValues, featureData, change.changeId, isNewPipe]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isNewPipe) return;
|
||||
@@ -429,7 +468,7 @@ export default function GisEditPipeSessionPopup({
|
||||
<h3 className="whitespace-nowrap text-2xl font-medium text-primary-foreground">
|
||||
하수관로
|
||||
{basicInfoTableData?.usgCd
|
||||
? `(${getPipeUsageLabel(basicInfoTableData.usgCd)})`
|
||||
? `(${getPipeUsageLabel(basicInfoTableData.usgCd, catalog.MS_SWAGE_PPL_USE_CODES)})`
|
||||
: ""}{" "}
|
||||
정보 <br />
|
||||
관리번호 : {displayManagementNo || "신규"}
|
||||
+2
-2
@@ -6,8 +6,8 @@
|
||||
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import type { GisEditSessionSnapshot, GisEditTool } from "./gisEditTypes";
|
||||
import { useGisEditOperationLabels } from "./useGisEditCommonCodes";
|
||||
import type { GisEditSessionSnapshot, GisEditTool } from "./gis-edit-types";
|
||||
import { useGisEditOperationLabels } from "./use-gis-edit-common-codes";
|
||||
|
||||
type Props = GisEditSessionSnapshot & {
|
||||
onRemoveFocusedChange: () => void;
|
||||
+2
-2
@@ -7,8 +7,8 @@
|
||||
import { Check, Move, PenTool, Plus, Trash2, X } from "lucide-react";
|
||||
import { Button } from "@common/components/ui";
|
||||
|
||||
import type { GisEditTool } from "./gisEditTypes";
|
||||
import { useGisEditOperationLabels } from "./useGisEditCommonCodes";
|
||||
import type { GisEditTool } from "./gis-edit-types";
|
||||
import { useGisEditOperationLabels } from "./use-gis-edit-common-codes";
|
||||
|
||||
type Props = {
|
||||
isEditMode: boolean;
|
||||
+1
-1
@@ -1,2 +1,2 @@
|
||||
/** GIS 편집 모델은 기능이 소유하고 기존 화면 import 경로는 유지한다. */
|
||||
export * from "@/features/gisEdit/model/types";
|
||||
export * from "@/features/gis-edit/model/types";
|
||||
+2
-2
@@ -3,9 +3,9 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { getCommonCodes } from "@/components/lib/common/apiHelpers";
|
||||
import { getCommonCodes } from "@/entities/code/api/common-code-api";
|
||||
|
||||
import type { GisEditTool } from "./gisEditTypes";
|
||||
import type { GisEditTool } from "./gis-edit-types";
|
||||
|
||||
const GIS_EDIT_OPERATION_UP_CD = "GISEDITOPRT";
|
||||
|
||||
+4
-4
@@ -22,19 +22,19 @@ import {
|
||||
getFeatureProperties,
|
||||
normalizePipeGeometry,
|
||||
createChangeId,
|
||||
} from "@/features/gisEdit";
|
||||
export { EMPTY_GIS_EDIT_PIPE_BASIC_INFO } from "@/features/gisEdit";
|
||||
} from "@/features/gis-edit";
|
||||
export { EMPTY_GIS_EDIT_PIPE_BASIC_INFO } from "@/features/gis-edit";
|
||||
|
||||
import type {
|
||||
GisEditChange,
|
||||
GisEditPipeBasicInfo,
|
||||
GisEditSavePayload,
|
||||
GisEditTool,
|
||||
} from "./gisEditTypes";
|
||||
} from "./gis-edit-types";
|
||||
|
||||
export const useGisEdit = (treeData: any[]) => {
|
||||
const editableLayers = useMemo(() => collectEditableLayers(treeData), [treeData]);
|
||||
// GIS 편집 모드 on/off는 우측 분석 메뉴의 GIS 편집 버튼과 직접 연결된다.
|
||||
// GIS 편집 모드 on/off는 지도 우측 GIS 편집 아이콘과 직접 연결된다.
|
||||
const [isEditMode, setIsEditMode] = useState(false);
|
||||
// 현재 선택 레이어를 기준으로 허용 도구와 저장 payload의 layerId를 결정한다.
|
||||
const [selectedLayerId, setSelectedLayerId] = useState("tb_ms_swage_ppl");
|
||||
+14
-18
@@ -12,26 +12,22 @@ import {
|
||||
} from "@common/business_components";
|
||||
import { Button, Input } from "@common/components";
|
||||
|
||||
import {
|
||||
getBlobDataWithHeaders,
|
||||
getData,
|
||||
postMutiPart,
|
||||
postRawJson,
|
||||
} from "@/components/lib/api";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import { getBlobDataWithHeaders, postMutiPart } from "@/shared/api/file-api";
|
||||
import { getData, postRawJson } from "@/shared/api/sewerage-api";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useUserStore } from "@/shared/lib/use-user-store";
|
||||
import GisUploadPreviewMap from "./GisUploadPreviewMap";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import { useGisManagementCommonCodes } from "./useGisManagementCommonCodes";
|
||||
import type { UploadFacilityType, MngInstOption, UploadHistoryItem, GisUploadResponse, ValidationErrorItem, ViewMode } from "@/features/gisManagement/model/upload-types";
|
||||
import { getLocalizedValidationErrorMessage, getFileExtension, formatDateTime, toScopeKey, buildManagementLabel, getErrorMessage } from "@/features/gisManagement/lib/upload-display";
|
||||
import { createGisDownloadClient, saveBlob } from "@/features/gisManagement/api/gis-download";
|
||||
import { postRefreshMessageToOpener } from "@/features/gisManagement/lib/popup-refresh";
|
||||
import GisUploadPreviewMap from "./gis-upload-preview-map";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import { useGisManagementCommonCodes } from "./use-gis-management-common-codes";
|
||||
import type { UploadFacilityType, MngInstOption, UploadHistoryItem, GisUploadResponse, ValidationErrorItem, ViewMode } from "@/features/gis-management/model/upload-types";
|
||||
import { getLocalizedValidationErrorMessage, getFileExtension, formatDateTime, toScopeKey, buildManagementLabel, getErrorMessage } from "@/features/gis-management/lib/upload-display";
|
||||
import { createGisDownloadClient, saveBlob } from "@/features/gis-management/api/gis-download";
|
||||
import { postRefreshMessageToOpener } from "@/features/gis-management/lib/popup-refresh";
|
||||
|
||||
import { createGisUploadRetryGuard, getGisPartialSuccessNotice } from "@/features/gisManagement";
|
||||
import { createGisUploadRetryGuard, getGisPartialSuccessNotice } from "@/features/gis-management";
|
||||
|
||||
// 기존 전송 경로를 유지하며 다운로드 작업의 비동기 처리는 기능 모듈에 위임한다.
|
||||
const { downloadGeoPackage, startDownloadJob, waitForDownloadJob } =
|
||||
@@ -57,7 +53,7 @@ const GIS_UPLOAD_HISTORY_HEADLIST: UiHeadProps[] = [
|
||||
];
|
||||
|
||||
|
||||
/** 오류 원본 행번호와 공통 표의 역순 순번을 별도 열로 표시한다. */
|
||||
/** 오류 원본 행번호와 공통 표의 오름차순 표시 순번을 별도 열로 표시한다. */
|
||||
const GIS_VALIDATION_ERROR_HEADLIST: UiHeadProps[] = [
|
||||
{ id: "no", title: "순번", widthPercent: 8 },
|
||||
{ id: "sourceRowNo", title: "행번호", widthPercent: 10 },
|
||||
+1
-1
@@ -20,7 +20,7 @@ import { get as getProjection } from "ol/proj";
|
||||
import { register } from "ol/proj/proj4";
|
||||
import proj4 from "proj4";
|
||||
|
||||
import { getRawJson } from "@/components/lib/api";
|
||||
import { getRawJson } from "@/shared/api/sewerage-api";
|
||||
|
||||
type GisUploadPreviewMapProps = {
|
||||
uploadId: string;
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { getCommonCodes } from "@/components/lib/common/apiHelpers";
|
||||
import { getCommonCodes } from "@/entities/code/api/common-code-api";
|
||||
|
||||
type CodeLabelMap = Record<string, string>;
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { PencilRuler, Settings2 } from "lucide-react";
|
||||
|
||||
type Props = {
|
||||
isGisEditActive: boolean;
|
||||
onToggleGisEdit: () => void;
|
||||
onOpenGisManagement: () => Window | null;
|
||||
};
|
||||
|
||||
/** 기존 지도 도구와 같은 크기로 GIS 버튼의 기본·활성·키보드 포커스를 표시한다. */
|
||||
const getButtonClass = (isActive: boolean) =>
|
||||
`flex h-[40px] w-[40px] items-center justify-center p-0 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary ${
|
||||
isActive
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "bg-background text-label hover:bg-primary hover:text-primary-foreground"
|
||||
}`;
|
||||
|
||||
/** 분석도구 메뉴와 독립적으로 GIS 편집 모드와 관리 팝업의 진입 버튼을 제공한다. */
|
||||
export default function GisToolButtons({
|
||||
isGisEditActive,
|
||||
onToggleGisEdit,
|
||||
onOpenGisManagement,
|
||||
}: Props) {
|
||||
// 관리 팝업이 닫히면 버튼의 활성 표시도 해제한다.
|
||||
const [isGisManagementActive, setIsGisManagementActive] = useState(false);
|
||||
// 렌더링과 무관한 감지 타이머를 보관해 재클릭·화면 이탈 시 중복 실행을 방지한다.
|
||||
const closeWatcherRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => () => {
|
||||
if (closeWatcherRef.current !== null) {
|
||||
window.clearInterval(closeWatcherRef.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
/** 기존 팝업 실행 함수를 호출하고 차단·닫힘 상태를 버튼에 반영한다. */
|
||||
const handleOpenGisManagement = () => {
|
||||
if (closeWatcherRef.current !== null) {
|
||||
window.clearInterval(closeWatcherRef.current);
|
||||
closeWatcherRef.current = null;
|
||||
}
|
||||
const popup = onOpenGisManagement();
|
||||
setIsGisManagementActive(Boolean(popup && !popup.closed));
|
||||
if (!popup || popup.closed) return;
|
||||
|
||||
closeWatcherRef.current = window.setInterval(() => {
|
||||
if (!popup.closed) return;
|
||||
if (closeWatcherRef.current !== null) {
|
||||
window.clearInterval(closeWatcherRef.current);
|
||||
closeWatcherRef.current = null;
|
||||
}
|
||||
setIsGisManagementActive(false);
|
||||
}, 300);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
role="group"
|
||||
aria-label="GIS 도구"
|
||||
className="absolute right-[16px] top-[222.67px] z-[45] flex w-[40px] flex-col overflow-hidden rounded-[3px] bg-background shadow-[0_1px_2px_rgba(60,64,67,0.3),0_1px_3px_1px_rgba(60,64,67,0.15)]"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
title="GIS 편집"
|
||||
aria-label="GIS 편집"
|
||||
aria-pressed={isGisEditActive}
|
||||
onClick={onToggleGisEdit}
|
||||
className={`${getButtonClass(isGisEditActive)} border-b border-[#eeeeee]`}
|
||||
>
|
||||
<PencilRuler className="h-[20px] w-[20px]" aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
title="GIS 관리"
|
||||
aria-label="GIS 관리"
|
||||
aria-haspopup="dialog"
|
||||
className={getButtonClass(isGisManagementActive)}
|
||||
onClick={handleOpenGisManagement}
|
||||
>
|
||||
<Settings2 className="h-[20px] w-[20px]" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+16
-35
@@ -11,26 +11,25 @@ import {
|
||||
type UiHeadProps,
|
||||
type UiTableSort,
|
||||
} from "@common/business_components";
|
||||
import { downloadExcelWithKoreanHeaders } from "@/shared/lib/download-excel";
|
||||
import { fetchAllPages } from "@/shared/lib/fetch-all-pages";
|
||||
import { downloadAnalysisExcel } from "@/shared/lib/download-analysis-excel";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import { getData } from "@/components/lib/api";
|
||||
import { getData } from "@/shared/api/sewerage-api";
|
||||
import { appendApiSearchParams } from "@/shared/util/util";
|
||||
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpvSggPrcsFcltSelectBox";
|
||||
import { formatDisplayData } from "@/components/lib/utils/formatDisplayValue";
|
||||
import useStore from "@/store/useStore";
|
||||
import { windowPopupDefinitions } from "@/features/windowPopup/model/popupDefinitions";
|
||||
import MapPopupDataTable from "@/features/windowPopup/ui/MapPopupDataTable";
|
||||
import PopupSearchPanel from "@/features/windowPopup/ui/PopupSearchPanel";
|
||||
import LoadingOverlay from "@/components/ui/LoadingBar";
|
||||
import { CtpvSggPrcsFcltSelectBox } from "@/components/ui/ctpv-sgg-prcs-fclt-select-box";
|
||||
import { formatDisplayData } from "@/components/lib/utils/format-display-value";
|
||||
import useStore from "@/entities/reference-data/model/reference-data-store";
|
||||
import { windowPopupDefinitions } from "@/features/window-popup/model/popup-definitions";
|
||||
import MapPopupDataTable from "@/features/window-popup/ui/map-popup-data-table";
|
||||
import PopupSearchPanel from "@/features/window-popup/ui/popup-search-panel";
|
||||
import LoadingOverlay from "@/components/ui/loading-bar";
|
||||
|
||||
import {
|
||||
INFTWT_RESULT_ROW_SELECT_MESSAGE,
|
||||
INFTWT_RESULT_ROW_SELECT_STORAGE_KEY,
|
||||
type InftwtResultRow,
|
||||
} from "./inftwtResultTypes";
|
||||
} from "./inftwt-result-types";
|
||||
|
||||
const INFTWT_RESULT_PAGE_SIZE = 10;
|
||||
|
||||
@@ -78,6 +77,8 @@ const DEFAULT_FILTERS: InftwtResultFilters = {
|
||||
};
|
||||
|
||||
const postRowSelectMessage = (row: InftwtResultRow) => {
|
||||
// 같은 클릭의 message/storage 전달을 식별한다.
|
||||
const requestId = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
if (typeof window !== "undefined") {
|
||||
try {
|
||||
window.localStorage.setItem(
|
||||
@@ -85,6 +86,7 @@ const postRowSelectMessage = (row: InftwtResultRow) => {
|
||||
JSON.stringify({
|
||||
row,
|
||||
timestamp: Date.now(),
|
||||
requestId,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -97,6 +99,7 @@ const postRowSelectMessage = (row: InftwtResultRow) => {
|
||||
{
|
||||
type: INFTWT_RESULT_ROW_SELECT_MESSAGE,
|
||||
row,
|
||||
requestId,
|
||||
},
|
||||
window.location.origin,
|
||||
);
|
||||
@@ -160,7 +163,7 @@ export default function InftwtResultPopupPage() {
|
||||
return {
|
||||
...(formattedItem as Record<string, unknown>),
|
||||
rowNumber: Math.max(
|
||||
totalElements - ((page - 1) * INFTWT_RESULT_PAGE_SIZE + index),
|
||||
(page - 1) * INFTWT_RESULT_PAGE_SIZE + index + 1,
|
||||
0,
|
||||
),
|
||||
ctpvNm: ctpvOption ? ctpvOption.text : String(item?.ctpvCd ?? "-"),
|
||||
@@ -241,29 +244,7 @@ export default function InftwtResultPopupPage() {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const response = await fetchAllPages<any, Record<string, unknown>>({
|
||||
fetchPage: (page, size) =>
|
||||
getData(
|
||||
`v1/inftwtAnlsRslt/ppl/${selectedRow.inftwtAnlsSn}?page=${page}&size=${size}`,
|
||||
),
|
||||
getItems: (page) =>
|
||||
Array.isArray(page?.content) ? page.content : [],
|
||||
getTotal: (page) => Number(page?.totalElements ?? 0),
|
||||
});
|
||||
const excelRows = response.items;
|
||||
|
||||
await downloadExcelWithKoreanHeaders({
|
||||
data: excelRows as unknown as JSON[],
|
||||
fileName: `침입수분석결과_${selectedRow.swtrmtFcltNm}_${selectedRow.aplcnYm}_${selectedRow.inftwtAnlsSn}`,
|
||||
sheetNameArray: ["침입수 분석결과"],
|
||||
headerRange: "A1:D1",
|
||||
headerMapping: {
|
||||
inftwtAnlsSn: "침입수분석\n분석번호",
|
||||
pplMngNo: "관로\n관리번호",
|
||||
anlsTm: "분석시각",
|
||||
predcOilq: "예측유량\n(m³/h)",
|
||||
},
|
||||
});
|
||||
await downloadAnalysisExcel("inftwt", selectedRow.inftwtAnlsSn, `침입수분석결과_${selectedRow.swtrmtFcltNm}_${selectedRow.aplcnYm}_${selectedRow.inftwtAnlsSn}`);
|
||||
} catch (error) {
|
||||
console.error("침입수 분석결과 다운로드 중 오류:", error);
|
||||
alert("침입수 분석결과 다운로드 중 오류가 발생했습니다.");
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user