Merge branch 'iipbs/as/dev'
This commit is contained in:
@@ -2,35 +2,35 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 DcRagFileInfoDto */
|
||||
export const dcRagFileInfoSchema = z.object({
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 DcRagDocumentTransferRequestDto */
|
||||
export const dcRagDocumentTransferRequestSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string(),
|
||||
taskSeComCd: z.string().optional(),
|
||||
taskSeCdNm: z.string().optional(),
|
||||
essentialFiles: z.array(dcRagFileInfoSchema).optional(),
|
||||
etcFiles: z.array(dcRagFileInfoSchema).optional(),
|
||||
callbackUrl: z.string().optional(),
|
||||
taskSeComCd: z.string().nullish(),
|
||||
taskSeCdNm: z.string().nullish(),
|
||||
essentialFiles: z.array(dcRagFileInfoSchema).nullish(),
|
||||
etcFiles: z.array(dcRagFileInfoSchema).nullish(),
|
||||
callbackUrl: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 DcRagDraftGenerateRequestDto */
|
||||
export const dcRagDraftGenerateRequestSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string(),
|
||||
pwrkCnsltnDocNo: z.string(),
|
||||
crtDmndUuid: z.string().optional(),
|
||||
essentialFiles: z.array(dcRagFileInfoSchema).optional(),
|
||||
etcFiles: z.array(dcRagFileInfoSchema).optional(),
|
||||
rqstrUuid: z.string().optional(),
|
||||
callbackUrl: z.string().optional(),
|
||||
crtDmndUuid: z.string().nullish(),
|
||||
essentialFiles: z.array(dcRagFileInfoSchema).nullish(),
|
||||
etcFiles: z.array(dcRagFileInfoSchema).nullish(),
|
||||
rqstrUuid: z.string().nullish(),
|
||||
callbackUrl: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 DcRagCostVerifyRequestDto */
|
||||
|
||||
@@ -2,68 +2,68 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 DesignCnstnMngSaveRequestDto */
|
||||
export const designCnstnMngSaveSchema = z.object({
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.BzScaleItem */
|
||||
export const designCnstnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
cnstpSeCd: z.string().nullish(),
|
||||
bscPl: z.number().nullish(),
|
||||
budgetReflct: z.number().nullish(),
|
||||
thtiDsgn: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.BizCstItem */
|
||||
export const designCnstnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
acntgSeCd: z.string().nullish(),
|
||||
bscPlanAmt: z.number().nullish(),
|
||||
bgtRfltAmt: z.number().nullish(),
|
||||
tstmDesignAmt: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const designCnstnEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnRvwOpnnSaveRequestDto */
|
||||
export const designCnstnRvwOpnnSaveSchema = z.object({
|
||||
designCnstnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
rvwOpnnSn: z.number().nullish(),
|
||||
opnrSeComCd: z.string().nullish(),
|
||||
opnrNm: z.string().nullish(),
|
||||
rvwRsltCn: z.string().nullish(),
|
||||
rvwRsltAnmlyYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 DesignCnstnApplySaveRequestDto */
|
||||
export const designCnstnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
techReViewSaveCd: z.string().nullish(),
|
||||
rvwStpCd: z.string().nullish(),
|
||||
designCnstnMng: designCnstnMngSaveSchema,
|
||||
bzScaleList: z.array(designCnstnBzScaleItemSchema).optional(),
|
||||
bizCstList: z.array(designCnstnBizCstItemSchema).optional(),
|
||||
esntlAtchFileList: z.array(designCnstnEsntlAtchFileItemSchema).optional(),
|
||||
rvwOpnnList: z.array(designCnstnRvwOpnnSaveSchema).optional(),
|
||||
bzScaleList: z.array(designCnstnBzScaleItemSchema).nullish(),
|
||||
bizCstList: z.array(designCnstnBizCstItemSchema).nullish(),
|
||||
esntlAtchFileList: z.array(designCnstnEsntlAtchFileItemSchema).nullish(),
|
||||
rvwOpnnList: z.array(designCnstnRvwOpnnSaveSchema).nullish(),
|
||||
});
|
||||
|
||||
export type DesignCnstnMngSaveRequestDto = z.infer<typeof designCnstnMngSaveSchema>;
|
||||
|
||||
@@ -41,6 +41,7 @@ export {
|
||||
validateAllRowsForSave,
|
||||
validateAplnRow,
|
||||
validateAtrzOrder,
|
||||
validateUniqueAtrzAprvr,
|
||||
validateUniqueSeqNo,
|
||||
} from "./lib/apln-validators";
|
||||
export {
|
||||
|
||||
@@ -76,3 +76,16 @@ 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;
|
||||
docPrgrsSttsComCd?: string;
|
||||
elaprCycl?: number | null;
|
||||
docPrgrsSttsComCd?: string | null;
|
||||
elaprPrgrsSttsList?: ElaprPrgrsSttsSaveRequestDto[];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
} from "@common/components";
|
||||
|
||||
import { useElaprAplnPage } from "../api/queries";
|
||||
import { validateAplnRow } from "../lib/apln-validators";
|
||||
import { validateAplnRow, validateUniqueAtrzAprvr } 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,6 +110,12 @@ function ElaprAplnPickerGrid({
|
||||
return;
|
||||
}
|
||||
|
||||
const duplicateError = validateUniqueAtrzAprvr(rows);
|
||||
if (duplicateError) {
|
||||
alert(duplicateError);
|
||||
return;
|
||||
}
|
||||
|
||||
commitEdit(row.rowKey);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getVisibleRows,
|
||||
validateAllRowsForSave,
|
||||
validateAtrzOrder,
|
||||
validateUniqueAtrzAprvr,
|
||||
validateUniqueSeqNo,
|
||||
} from "../lib/apln-validators";
|
||||
import { mapDocPrgrsToAplnRows } from "../lib/doc-prgrs-to-apln-rows";
|
||||
@@ -90,6 +91,12 @@ function ElaprDocPrgrsPickerDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
const atrzAprvrError = validateUniqueAtrzAprvr(aplnRows);
|
||||
if (atrzAprvrError) {
|
||||
alert(atrzAprvrError);
|
||||
return;
|
||||
}
|
||||
|
||||
const visibleRows = getVisibleRows(aplnRows);
|
||||
onConfirm?.({ aplnRows: visibleRows });
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
getVisibleRows,
|
||||
validateAllRowsForSave,
|
||||
validateAtrzOrder,
|
||||
validateUniqueAtrzAprvr,
|
||||
validateUniqueSeqNo,
|
||||
} from "../lib/apln-validators";
|
||||
import type { ElaprAplnRow } from "../model/elapr-apln-row-types";
|
||||
@@ -106,6 +107,12 @@ 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().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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.BzScaleItem */
|
||||
export const fnrsCnsltnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
cnstpSeCd: z.string().nullish(),
|
||||
bscPl: z.number().nullish(),
|
||||
budgetReflct: z.number().nullish(),
|
||||
thtiDsgn: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.BizCstItem */
|
||||
export const fnrsCnsltnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
acntgSeCd: z.string().nullish(),
|
||||
bscPlanAmt: z.number().nullish(),
|
||||
bgtRfltAmt: z.number().nullish(),
|
||||
tstmDesignAmt: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.CklstResultItem */
|
||||
export const fnrsCnsltnCklstResultItemSchema = z.object({
|
||||
cklstRvwSn: z.number().optional(),
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
cklstRvwSn: z.number().nullish(),
|
||||
cklstArtclSn: z.number().nullish(),
|
||||
cklstSn: z.number().nullish(),
|
||||
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().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsCnsltnSplmntDmndItem */
|
||||
export const fnrsCnsltnSplmntDmndItemSchema = z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: 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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnRvwOpnnSaveRequestDto */
|
||||
export const fnrsCnsltnRvwOpnnSaveSchema = z.object({
|
||||
fnrsCnsltnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
rvwOpnnSn: z.number().nullish(),
|
||||
opnrSeComCd: z.string().nullish(),
|
||||
opnrNm: z.string().nullish(),
|
||||
rvwRsltCn: z.string().nullish(),
|
||||
rvwRsltAnmlyYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto.FnrsSharItem */
|
||||
export const fnrsSharItemSchema = z.object({
|
||||
fnrsSharSeCd: z.string().optional(),
|
||||
exstnAmt: z.number().optional(),
|
||||
chgRqtAmt: z.number().optional(),
|
||||
ajmtAmt: z.number().optional(),
|
||||
rmrkCn: z.string().optional(),
|
||||
envoRmrkCn: z.string().optional(),
|
||||
fnrsSharSeCd: z.string().nullish(),
|
||||
exstnAmt: z.number().nullish(),
|
||||
chgRqtAmt: z.number().nullish(),
|
||||
ajmtAmt: z.number().nullish(),
|
||||
rmrkCn: z.string().nullish(),
|
||||
envoRmrkCn: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 FnrsCnsltnApplySaveRequestDto */
|
||||
export const fnrsCnsltnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
techReViewSaveCd: z.string().nullish(),
|
||||
rvwStpCd: z.string().nullish(),
|
||||
fnrsCnsltnMng: fnrsCnsltnMngSaveSchema,
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
export type FnrsCnsltnMngSaveRequestDto = z.infer<typeof fnrsCnsltnMngSaveSchema>;
|
||||
|
||||
@@ -2,12 +2,12 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 FnrsExamineRequestDto */
|
||||
export const fnrsExamineRequestSchema = z.object({
|
||||
prgrsSttsComCd: z.string().optional(),
|
||||
prgrsSttsComCd: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 EnvoActnReviewReqDto */
|
||||
export const envoActnReviewReqSchema = z.object({
|
||||
specialRqstCn: z.string().optional(),
|
||||
specialRqstCn: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type FnrsExamineRequestDto = z.infer<typeof fnrsExamineRequestSchema>;
|
||||
|
||||
@@ -2,52 +2,52 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 InstlSncnMngSaveRequestDto */
|
||||
export const instlSncnMngSaveSchema = z.object({
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.BzScaleItem (diff는 API 미전송) */
|
||||
export const instlSncnBzScaleItemSchema = z.object({
|
||||
cnstpSeCd: z.string().optional(),
|
||||
bscPl: z.number().optional(),
|
||||
budgetReflct: z.number().optional(),
|
||||
thtiDsgn: z.number().optional(),
|
||||
cnstpSeCd: z.string().nullish(),
|
||||
bscPl: z.number().nullish(),
|
||||
budgetReflct: z.number().nullish(),
|
||||
thtiDsgn: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.BizCstItem */
|
||||
export const instlSncnBizCstItemSchema = z.object({
|
||||
acntgSeCd: z.string().optional(),
|
||||
bscPlanAmt: z.number().optional(),
|
||||
bgtRfltAmt: z.number().optional(),
|
||||
tstmDesignAmt: z.number().optional(),
|
||||
acntgSeCd: z.string().nullish(),
|
||||
bscPlanAmt: z.number().nullish(),
|
||||
bgtRfltAmt: z.number().nullish(),
|
||||
tstmDesignAmt: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.CklstResultItem */
|
||||
export const instlSncnCklstResultItemSchema = z.object({
|
||||
cklstRvwSn: z.number().optional(),
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
cklstRvwSn: z.number().nullish(),
|
||||
cklstArtclSn: z.number().nullish(),
|
||||
cklstSn: z.number().nullish(),
|
||||
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().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnRvwOpnnSaveRequestDto */
|
||||
export const instlSncnRvwOpnnSaveSchema = z.object({
|
||||
instlSncnDocNo: z.string(),
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
rvwOpnnSn: z.number().nullish(),
|
||||
opnrSeComCd: z.string().nullish(),
|
||||
opnrNm: z.string().nullish(),
|
||||
rvwRsltCn: z.string().nullish(),
|
||||
rvwRsltAnmlyYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto.InstlSncnSplmntDmndItem */
|
||||
export const instlSncnSplmntDmndItemSchema = z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: 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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 InstlSncnApplySaveRequestDto */
|
||||
export const instlSncnApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
techReViewSaveCd: z.string().nullish(),
|
||||
rvwStpCd: z.string().nullish(),
|
||||
instlSncnMng: instlSncnMngSaveSchema,
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
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).optional(),
|
||||
atchFileGroupId: z.string().max(30).nullish(),
|
||||
});
|
||||
|
||||
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().optional(),
|
||||
pwrkCnsltnDocEvlCn: z.string().max(2000).optional(),
|
||||
pwrkCnsltnDocEvlScr: z.number().optional(),
|
||||
pwrkCnsltnDocEvlDt: z.string().optional(),
|
||||
pwrkCnsltnDocEvlUuid: z.string().uuid().nullish(),
|
||||
pwrkCnsltnDocEvlCn: z.string().max(2000).nullish(),
|
||||
pwrkCnsltnDocEvlScr: z.number().nullish(),
|
||||
pwrkCnsltnDocEvlDt: z.string().nullish(),
|
||||
});
|
||||
|
||||
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().optional(),
|
||||
rfltYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
actnRsltCn: z.string().nullish(),
|
||||
rfltYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 PwrkActnResultSubmitDto */
|
||||
export const pwrkActnResultSubmitSchema = z.object({
|
||||
isSubmit: z.boolean(),
|
||||
actnList: z.array(pwrkActnResultActnItemSchema).optional(),
|
||||
totActnSummary: z.string().optional(),
|
||||
muniDocNo: z.string().optional(),
|
||||
pwrkAtchFileGroupId: z.string().optional(),
|
||||
actnList: z.array(pwrkActnResultActnItemSchema).nullish(),
|
||||
totActnSummary: z.string().nullish(),
|
||||
muniDocNo: z.string().nullish(),
|
||||
pwrkAtchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
export type PwrkActnResultSubmitDto = z.infer<typeof pwrkActnResultSubmitSchema>;
|
||||
|
||||
@@ -2,50 +2,50 @@ import { z } from "zod";
|
||||
|
||||
/** 백엔드 SewerBscPlanSaveRequestDto */
|
||||
export const sewerBscPlanSaveSchema = z.object({
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanDtlSaveRequestDto */
|
||||
export const sewerBscPlanDtlSaveSchema = z.object({
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanOtlnItem */
|
||||
export const sewerBscPlanOtlnItemSchema = z.object({
|
||||
planStpNo: z.number().optional(),
|
||||
sewerPlanPpltnCnt: z.number().optional(),
|
||||
sewerPrcsPpltnCnt: z.number().optional(),
|
||||
sewerPsplyRt: z.number().optional(),
|
||||
planStpNo: z.number().nullish(),
|
||||
sewerPlanPpltnCnt: z.number().nullish(),
|
||||
sewerPrcsPpltnCnt: z.number().nullish(),
|
||||
sewerPsplyRt: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.CklstResultItem */
|
||||
export const sewerBscPlanCklstResultItemSchema = z.object({
|
||||
cklstRvwSn: z.number().optional(),
|
||||
cklstArtclSn: z.number().optional(),
|
||||
cklstSn: z.number().optional(),
|
||||
cklstRvwSn: z.number().nullish(),
|
||||
cklstArtclSn: z.number().nullish(),
|
||||
cklstSn: z.number().nullish(),
|
||||
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().optional(),
|
||||
gvexpAmt: z.number().optional(),
|
||||
lcexpAmt: z.number().optional(),
|
||||
orgprBdchgAmt: z.number().optional(),
|
||||
etcAmt: z.number().optional(),
|
||||
sumAmt: z.number().optional(),
|
||||
planStpNo: z.number().nullish(),
|
||||
gvexpAmt: z.number().nullish(),
|
||||
lcexpAmt: z.number().nullish(),
|
||||
orgprBdchgAmt: z.number().nullish(),
|
||||
etcAmt: z.number().nullish(),
|
||||
sumAmt: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanSplmntDmndItem */
|
||||
export const sewerBscPlanSplmntDmndItemSchema = z.object({
|
||||
splmntDmndSn: z.number().nullish(),
|
||||
splmntArtclComCd: 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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanRvwOpnnItem */
|
||||
export const sewerBscPlanRvwOpnnItemSchema = z.object({
|
||||
rvwOpnnSn: z.number().optional(),
|
||||
opnrSeComCd: z.string().optional(),
|
||||
opnrNm: z.string().optional(),
|
||||
rvwRsltCn: z.string().optional(),
|
||||
rvwRsltAnmlyYn: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
rvwOpnnSn: z.number().nullish(),
|
||||
opnrSeComCd: z.string().nullish(),
|
||||
opnrNm: z.string().nullish(),
|
||||
rvwRsltCn: z.string().nullish(),
|
||||
rvwRsltAnmlyYn: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.EsntlAtchFileItem */
|
||||
export const sewerBscPlanEsntlAtchFileItemSchema = z.object({
|
||||
esntlAtchFileId: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
esntlAtchFileId: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanBizCstItem */
|
||||
export const sewerBscPlanBizCstItemSchema = z.object({
|
||||
planStpNo: z.number().optional(),
|
||||
acntgSeCd: z.string().optional(),
|
||||
cstrnAmt: z.number().optional(),
|
||||
planStpNo: z.number().nullish(),
|
||||
acntgSeCd: z.string().nullish(),
|
||||
cstrnAmt: z.number().nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntDtlItem */
|
||||
export const sewerBscPlanTmplntDtlItemSchema = z.object({
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntItem */
|
||||
export const sewerBscPlanTmplntItemSchema = z.object({
|
||||
swtrmtFcltCd: z.string().optional(),
|
||||
atchFileGroupId: z.string().optional(),
|
||||
sewerBscPlanTmplntDtlList: z.array(sewerBscPlanTmplntDtlItemSchema).optional(),
|
||||
swtrmtFcltCd: z.string().nullish(),
|
||||
atchFileGroupId: z.string().nullish(),
|
||||
sewerBscPlanTmplntDtlList: z.array(sewerBscPlanTmplntDtlItemSchema).nullish(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto.SewerBscPlanTmplntTmprStrgItem */
|
||||
export const sewerBscPlanTmplntTmprStrgItemSchema = z.object({
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
/** 백엔드 SewerBscPlanApplySaveRequestDto */
|
||||
export const sewerBscPlanApplySaveSchema = z.object({
|
||||
techReViewSaveCd: z.string().optional(),
|
||||
rvwStpCd: z.string().optional(),
|
||||
techReViewSaveCd: z.string().nullish(),
|
||||
rvwStpCd: z.string().nullish(),
|
||||
sewerBscPlan: sewerBscPlanSaveSchema,
|
||||
sewerBscPlanDtl: sewerBscPlanDtlSaveSchema,
|
||||
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(),
|
||||
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(),
|
||||
});
|
||||
|
||||
export type SewerBscPlanSaveRequestDto = z.infer<typeof sewerBscPlanSaveSchema>;
|
||||
|
||||
@@ -23,8 +23,8 @@ export const designCnstnRegistSchema = z.object({
|
||||
.string()
|
||||
.min(1, "설계자 이메일은 필수입니다")
|
||||
.email("올바른 이메일 형식이 아닙니다"),
|
||||
sido: z.string().optional(),
|
||||
gugun: z.string().optional(),
|
||||
sido: z.string().nullish(),
|
||||
gugun: z.string().nullish(),
|
||||
}),
|
||||
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().optional(),
|
||||
diff: z.number().nullish(),
|
||||
}),
|
||||
),
|
||||
esntlAtchFileList: z.array(
|
||||
@@ -52,7 +52,7 @@ export function formatTaskSeComCdForApi(taskSeComCd: string[]): string {
|
||||
return taskSeComCd.join(",");
|
||||
}
|
||||
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string): string[] {
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string | null): string[] {
|
||||
if (!taskSeComCd) return [];
|
||||
return taskSeComCd.split(",").filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ function getBadgeColor(state: BadgeState) {
|
||||
}
|
||||
}
|
||||
|
||||
function formatOptional(value: string | undefined) {
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
|
||||
@@ -51,14 +51,19 @@ 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,7 +4,6 @@ import {
|
||||
type ChecklistFormState,
|
||||
type ChecklistProgress,
|
||||
type ChecklistRowKey,
|
||||
type ChecklistStepMap,
|
||||
REVIEW_STEP_CODES,
|
||||
type ReviewStepCode,
|
||||
} from "../model/view-types";
|
||||
@@ -17,15 +16,6 @@ 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)));
|
||||
}
|
||||
@@ -70,13 +60,6 @@ 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"],
|
||||
@@ -93,23 +76,6 @@ 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().optional(),
|
||||
gugun: z.string().optional(),
|
||||
sido: z.string().nullish(),
|
||||
gugun: z.string().nullish(),
|
||||
}),
|
||||
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().optional(),
|
||||
diff: z.number().nullish(),
|
||||
}),
|
||||
),
|
||||
bizCstList: z.array(
|
||||
@@ -52,8 +52,8 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
fnrsSharSeCd: z.string(),
|
||||
exstnAmt: z.number(),
|
||||
chgRqtAmt: z.number(),
|
||||
ajmtAmt: z.number().optional(),
|
||||
rmrkCn: z.string().optional(),
|
||||
ajmtAmt: z.number().nullish(),
|
||||
rmrkCn: z.string().nullish(),
|
||||
}),
|
||||
),
|
||||
cklstResultList: z.array(
|
||||
@@ -74,7 +74,7 @@ export const fnrsCnsltnRegistSchema = z.object({
|
||||
splmntDmndList: z.array(
|
||||
fnrsCnsltnSplmntDmndItemSchema.extend({
|
||||
splmntArtclCdNm: z.string().nullish(),
|
||||
splmntDmndAtchFileNm: z.string().optional(),
|
||||
splmntDmndAtchFileNm: z.string().nullish(),
|
||||
}),
|
||||
),
|
||||
privacyConsent: z.enum(["Y", "N"], {
|
||||
@@ -88,7 +88,7 @@ export function formatTaskSeComCdForApi(taskSeComCd: string[]): string {
|
||||
return taskSeComCd.join(",");
|
||||
}
|
||||
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string): string[] {
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string | null): 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).optional(),
|
||||
pwrkCnsltnDocEvlCn: z.string().max(2000).nullish(),
|
||||
});
|
||||
|
||||
export type PwrkCnsltnDocEvlFormValues = z.infer<typeof pwrkCnsltnDocEvlFormSchema>;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { CklstRvwRsltTreeDto } from "@/entities/fnrs-cnsltn";
|
||||
|
||||
import {
|
||||
PBADMS_PROGRESS_ORDER,
|
||||
REVIEW_STEP_CODES,
|
||||
@@ -28,6 +30,25 @@ 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 | undefined) {
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
|
||||
@@ -44,10 +44,14 @@ export interface ChecklistFormState {
|
||||
progress: ChecklistProgress;
|
||||
}
|
||||
|
||||
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: "환경청",
|
||||
|
||||
@@ -180,7 +180,9 @@ function FloatingActionButtons({
|
||||
userInfo?.userUnqId,
|
||||
);
|
||||
const viewOnlyPwrkCnsltnDoc =
|
||||
prgrsSttsComCd === "SL" || (prgrsSttsComCd === "RB" && dtlInstCd !== "ROLE_USER05");
|
||||
prgrsSttsComCd === "SL" ||
|
||||
(prgrsSttsComCd === "RB" && dtlInstCd !== "ROLE_USER05") ||
|
||||
(prgrsSttsComCd === "RK" && dtlInstCd === "ROLE_USER03");
|
||||
|
||||
const actionButtons = useMemo(() => {
|
||||
if (needsAtrzResolve && isRvwLoginUserLoading) return [];
|
||||
|
||||
@@ -113,7 +113,8 @@ function PwrkCnsltnDocEvlDialog({ open, pwrkCnsltnDocNo, onClose }: PwrkCnsltnDo
|
||||
<Textarea
|
||||
id="pwrkCnsltnDocEvlCn"
|
||||
rows={4}
|
||||
placeholder="의견을 입력해 주세요 (선택)"
|
||||
placeholder="의견을 입력해 주세요"
|
||||
className="!text-xl"
|
||||
{...register("pwrkCnsltnDocEvlCn")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
fnrsCnsltnMngService,
|
||||
} from "@/entities/fnrs-cnsltn";
|
||||
import { useFnrsSharSeMngTree } from "@/entities/fnrs-shar-se";
|
||||
import { findCompletedPwrkDraft, usePwrkCnsltnMngByUpDocNo } from "@/entities/pwrk-cnsltn-mng";
|
||||
import {
|
||||
AttachmentsFormSection,
|
||||
buildAcntgCostItems,
|
||||
@@ -35,7 +36,10 @@ 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,
|
||||
@@ -68,6 +72,12 @@ 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" });
|
||||
@@ -126,7 +136,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
(item) => !item.splmntCn || !item.splmntCn.trim(),
|
||||
);
|
||||
if (hasEmptySplmntCn) {
|
||||
alert("보완요청내용이 있습니다. 보완 완료내용을 입력해 주세요.");
|
||||
alert("보완 요청 내용이 있습니다.\n보완 완료 내용을 입력해 주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -174,6 +184,11 @@ 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;
|
||||
@@ -260,7 +275,10 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
initialEsntlFiles={isEditMode ? initialData?.esntlFileList : undefined}
|
||||
initialEtcFiles={isEditMode ? initialData?.etcFileList : undefined}
|
||||
/>
|
||||
<SplmntDmndFormSection readOnly={!isSplmntPrgrsStts} />
|
||||
<SplmntDmndFormSection
|
||||
initialData={initialData?.splmntDmndList}
|
||||
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">
|
||||
@@ -300,7 +318,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{fnrsCnsltnDocNo ? (
|
||||
{hasCompletedDraft ? (
|
||||
<Button
|
||||
color="orange"
|
||||
type="button"
|
||||
@@ -324,7 +342,7 @@ function FnrsFormPage({ mode, initialData, createDefaults }: FnrsFormPageProps)
|
||||
</div>
|
||||
</SectionCollapsibleGroup>
|
||||
</form>
|
||||
{fnrsCnsltnDocNo ? (
|
||||
{hasCompletedDraft && fnrsCnsltnDocNo ? (
|
||||
<PwrkCnsltnDocMngDialog
|
||||
open={isPwrkCnsltnDocMngOpen}
|
||||
fnrsCnsltnDocNo={fnrsCnsltnDocNo}
|
||||
|
||||
@@ -14,7 +14,11 @@ 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 { getVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import { resolveVisibleReviewSteps } 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(
|
||||
() => getVisibleReviewSteps(prgrsSttsComCd, userRoleCode),
|
||||
[prgrsSttsComCd, userRoleCode],
|
||||
() => resolveVisibleReviewSteps(prgrsSttsComCd, userRoleCode, cklstResultList),
|
||||
[prgrsSttsComCd, userRoleCode, cklstResultList],
|
||||
);
|
||||
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">
|
||||
{REVIEW_STEP_LABELS[stepCode]}
|
||||
{CHECKLIST_TAB_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().optional(),
|
||||
splmntDmndAtchFileNm: z.string().nullish(),
|
||||
}),
|
||||
),
|
||||
privacyConsent: z.enum(["Y", "N"], {
|
||||
@@ -82,7 +82,7 @@ export function formatTaskSeComCdForApi(taskSeComCd: string[]): string {
|
||||
return taskSeComCd.join(",");
|
||||
}
|
||||
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string): string[] {
|
||||
export function parseTaskSeComCdFromApi(taskSeComCd?: string | null): string[] {
|
||||
if (!taskSeComCd) return [];
|
||||
return taskSeComCd.split(",").filter(Boolean);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReviewStepCode } from "@/entities/instl-sncn";
|
||||
import type { CklstRvwRsltTreeDto, ReviewStepCode } from "@/entities/instl-sncn";
|
||||
|
||||
import {
|
||||
INSTL_PROGRESS_ORDER,
|
||||
@@ -27,6 +27,25 @@ 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,7 +9,10 @@ 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 | undefined) {
|
||||
function formatOptional(value: string | null | undefined) {
|
||||
return value?.trim() ? value : "-";
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,12 @@ 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 { getVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import { resolveVisibleReviewSteps } 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(
|
||||
() => getVisibleReviewSteps(prgrsSttsComCd, userRoleCode),
|
||||
[prgrsSttsComCd, userRoleCode],
|
||||
() => resolveVisibleReviewSteps(prgrsSttsComCd, userRoleCode, cklstResultList),
|
||||
[prgrsSttsComCd, userRoleCode, cklstResultList],
|
||||
);
|
||||
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">
|
||||
{REVIEW_STEP_LABELS[stepCode]}
|
||||
{CHECKLIST_TAB_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;
|
||||
lcexpAmt?: number;
|
||||
orgprBdchgAmt?: number;
|
||||
etcAmt?: number;
|
||||
sumAmt?: number;
|
||||
gvexpAmt?: number | null;
|
||||
lcexpAmt?: number | null;
|
||||
orgprBdchgAmt?: number | null;
|
||||
etcAmt?: number | null;
|
||||
sumAmt?: number | null;
|
||||
}
|
||||
|
||||
export interface FnrsCstStepRemaining {
|
||||
@@ -32,10 +32,7 @@ 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,3 +1,5 @@
|
||||
import type { CklstRvwRsltTreeDto } from "@/entities/sewer-bsc-plan";
|
||||
|
||||
import {
|
||||
PBADMS_PROGRESS_ORDER,
|
||||
REVIEW_STEP_CODES,
|
||||
@@ -28,6 +30,25 @@ 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().optional(),
|
||||
taskSeCdNm: z.string().optional(),
|
||||
sido: z.string().optional(),
|
||||
sewerBscPlanSeCdNm: z.string().nullish(),
|
||||
taskSeCdNm: z.string().nullish(),
|
||||
sido: z.string().nullish(),
|
||||
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().optional(),
|
||||
atchFileNm: z.string().nullish(),
|
||||
sewerBscPlanTmplntDtlList: z
|
||||
.array(
|
||||
sewerBscPlanTmplntDtlItemSchema.extend({
|
||||
@@ -69,7 +69,7 @@ export const sewerBscPlanRegistSchema = z.object({
|
||||
swtrmtFcltCd: z.string(),
|
||||
}),
|
||||
)
|
||||
.optional(),
|
||||
.nullish(),
|
||||
}),
|
||||
),
|
||||
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().nullable().optional(),
|
||||
picRvwCn: z.string().nullish(),
|
||||
}),
|
||||
),
|
||||
esntlAtchFileList: z.array(
|
||||
@@ -90,9 +90,9 @@ export const sewerBscPlanRegistSchema = z.object({
|
||||
splmntDmndList: z.array(
|
||||
sewerBscPlanSplmntDmndItemSchema.extend({
|
||||
splmntArtclCdNm: z.string().nullish(),
|
||||
splmntDmndAtchFileNm: z.string().optional(),
|
||||
splmntAtchFileNm: z.string().optional(),
|
||||
splmntAtchFileAtflId: z.string().optional(),
|
||||
splmntDmndAtchFileNm: z.string().nullish(),
|
||||
splmntAtchFileNm: z.string().nullish(),
|
||||
splmntAtchFileAtflId: z.string().nullish(),
|
||||
}),
|
||||
),
|
||||
privacyConsent: z.enum(["Y", "N"], {
|
||||
|
||||
@@ -42,6 +42,12 @@ 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;
|
||||
initialSidoNm?: string | null;
|
||||
/** 부모 폼 `sewerBscPlan.gugun` 표시명 */
|
||||
initialGugunNm?: string | null;
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ import {
|
||||
extractTaskCodes,
|
||||
getChecklistDisplayRows,
|
||||
} from "../../lib/checklist-utils";
|
||||
import { getVisibleReviewSteps } from "../../lib/review-step-utils";
|
||||
import { resolveVisibleReviewSteps } 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(
|
||||
() => getVisibleReviewSteps(prgrsSttsComCd, userRoleCode),
|
||||
[prgrsSttsComCd, userRoleCode],
|
||||
() => resolveVisibleReviewSteps(prgrsSttsComCd, userRoleCode, cklstResultList),
|
||||
[prgrsSttsComCd, userRoleCode, cklstResultList],
|
||||
);
|
||||
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">
|
||||
{REVIEW_STEP_LABELS[stepCode]}
|
||||
{CHECKLIST_TAB_LABELS[stepCode]}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
|
||||
@@ -214,8 +214,8 @@ function TmplntFacilityMatrix({
|
||||
interface TmplntFacilityAttachmentsProps {
|
||||
facilityIndex: number;
|
||||
facilityName?: string;
|
||||
atchFileGroupId?: string;
|
||||
atchFileNm?: string;
|
||||
atchFileGroupId?: string | null;
|
||||
atchFileNm?: string | null;
|
||||
}
|
||||
|
||||
function TmplntFacilityAttachments({
|
||||
|
||||
@@ -205,14 +205,14 @@ export interface EsntlAtchFileSaveBase {
|
||||
export interface SplmntDmndSaveBase {
|
||||
splmntDmndSn?: number | null;
|
||||
splmntArtclComCd?: string | null;
|
||||
splmntDmndCn?: string;
|
||||
splmntDmndDt?: string;
|
||||
splmntDmndAtchFileGroupId?: string;
|
||||
splmntCn?: string;
|
||||
splmntPicNm?: string;
|
||||
splmntCmptnDt?: string;
|
||||
splmntAtchFileGroupId?: string;
|
||||
cmptnYn?: string;
|
||||
splmntDmndCn?: string | null;
|
||||
splmntDmndDt?: string | null;
|
||||
splmntDmndAtchFileGroupId?: string | null;
|
||||
splmntCn?: string | null;
|
||||
splmntPicNm?: string | null;
|
||||
splmntCmptnDt?: string | null;
|
||||
splmntAtchFileGroupId?: string | null;
|
||||
cmptnYn?: string | null;
|
||||
}
|
||||
|
||||
/** 검토의견 저장 공통 필드 (문서번호 제외) */
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
interface CanEditPbadmsDocParams {
|
||||
docSeComCd: string;
|
||||
prgrsSttsComCd: string | undefined;
|
||||
rgtr: string | undefined;
|
||||
loggedInUserUnqId: string | undefined;
|
||||
prgrsSttsComCd?: string | null;
|
||||
rgtr?: string | null;
|
||||
loggedInUserUnqId?: string | null;
|
||||
}
|
||||
|
||||
function canEditPbadmsDoc({
|
||||
|
||||
@@ -17,8 +17,8 @@ export interface FnrsSharListItem {
|
||||
fnrsSharSeCd: string;
|
||||
exstnAmt: number;
|
||||
chgRqtAmt: number;
|
||||
ajmtAmt?: number;
|
||||
rmrkCn?: string;
|
||||
ajmtAmt?: number | null;
|
||||
rmrkCn?: string | null;
|
||||
}
|
||||
|
||||
export type FnrsSharTreeNode = FnrsSharSeMngTreeDto | FnrsSharBizCstTreeDto;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { 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 { control, getValues, setValue } = useFormContext<AttachmentsFormData>();
|
||||
const { getValues, setValue } = useFormContext<AttachmentsFormData>();
|
||||
const queryClient = useQueryClient();
|
||||
const [uploadedFiles, setUploadedFiles] = useState<Record<string, UploadedFileItem[]>>({});
|
||||
const [etcFiles, setEtcFiles] = useState<EtcFileItem[]>([]);
|
||||
@@ -373,89 +373,83 @@ function AttachmentsFormSection({
|
||||
return (
|
||||
<SectionCollapsible title="첨부파일">
|
||||
<div className="space-y-6">
|
||||
<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">
|
||||
조회된 필수 첨부파일 항목이 없습니다.
|
||||
<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)
|
||||
}
|
||||
/>
|
||||
</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)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<div className="px-2">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
|
||||
@@ -43,12 +43,13 @@ function AttachmentsViewSection({ esntlFileList, etcFileList }: AttachmentsViewS
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{files.map((file) => (
|
||||
<AttachmentDownloadLink
|
||||
key={file.atflId}
|
||||
atflId={file.atflId}
|
||||
fileName={file.orgnlFileNm || "다운로드"}
|
||||
className="items-center"
|
||||
/>
|
||||
<div key={file.atflId}>
|
||||
<AttachmentDownloadLink
|
||||
atflId={file.atflId}
|
||||
fileName={file.orgnlFileNm || "다운로드"}
|
||||
className="items-center"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { Field } from "@/shared/ui/field";
|
||||
@@ -31,6 +31,16 @@ 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" }) ?? [];
|
||||
@@ -42,22 +52,22 @@ function BusinessCostFormSection() {
|
||||
useEffect(() => {
|
||||
if (costItems.length === 0) return;
|
||||
|
||||
const existingMap = new Map(getValues("bizCstList").map((item) => [item.acntgSeCd, item]));
|
||||
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;
|
||||
|
||||
setValue(
|
||||
"bizCstList",
|
||||
costItems.map((item) => {
|
||||
const existing = existingMap.get(item.code);
|
||||
if (existing) return existing;
|
||||
return {
|
||||
acntgSeCd: item.code,
|
||||
bscPlanAmt: 0,
|
||||
bgtRfltAmt: 0,
|
||||
tstmDesignAmt: 0,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
acntgSeCd: item.code,
|
||||
bscPlanAmt: 0,
|
||||
bgtRfltAmt: 0,
|
||||
tstmDesignAmt: 0,
|
||||
};
|
||||
}),
|
||||
);
|
||||
if (isSameBizCstCodes(current, next)) return;
|
||||
setValue("bizCstList", next);
|
||||
}, [costItems, getValues, setValue]);
|
||||
|
||||
function handleCostChange(index: number, fieldKey: CostFieldKey, value: string) {
|
||||
@@ -119,58 +129,37 @@ function BusinessCostFormSection() {
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-right">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { 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,6 +31,16 @@ 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" }) ?? [];
|
||||
@@ -42,7 +52,8 @@ function BusinessScaleFormSection() {
|
||||
useEffect(() => {
|
||||
if (scaleItems.length === 0) return;
|
||||
|
||||
const existingMap = new Map(getValues("bzScaleList").map((item) => [item.cnstpSeCd, item]));
|
||||
const current = getValues("bzScaleList") ?? [];
|
||||
const existingMap = new Map(current.map((item) => [item.cnstpSeCd, item]));
|
||||
|
||||
const next = scaleItems.map((item) => {
|
||||
const existing = existingMap.get(item.code);
|
||||
@@ -57,6 +68,7 @@ function BusinessScaleFormSection() {
|
||||
};
|
||||
});
|
||||
|
||||
if (isSameBzScaleCodes(current, next)) return;
|
||||
setValue("bzScaleList", recalculateBzScaleTotals(next, scaleItems));
|
||||
}, [scaleItems, getValues, setValue]);
|
||||
|
||||
@@ -112,58 +124,37 @@ function BusinessScaleFormSection() {
|
||||
</div>
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-right">
|
||||
|
||||
@@ -1,18 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { 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,
|
||||
@@ -125,11 +122,21 @@ 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");
|
||||
const currentList = getValues("cklstResultList") as CklstResultListItem[];
|
||||
const newList = [...currentList];
|
||||
|
||||
tab.items
|
||||
@@ -141,7 +148,7 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
|
||||
}
|
||||
});
|
||||
|
||||
setValue("cklstResultList", newList);
|
||||
setValue("cklstResultList", newList, { shouldDirty: true });
|
||||
}
|
||||
|
||||
const hasSelectedTaskCodes = Boolean(taskSeComCdArray?.length);
|
||||
@@ -234,6 +241,8 @@ 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}>
|
||||
@@ -248,66 +257,30 @@ function ChecklistFormSection({ docSeComCd, fieldPrefix }: ChecklistFormSectionP
|
||||
{item.content}
|
||||
</TableCell>
|
||||
{showRadios && resultIdx >= 0 ? (
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
) : null}
|
||||
</TableRow>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { getDefaultFileKey, useTus } from "@/shared/lib";
|
||||
import { TextShimmer } from "@/shared/ui";
|
||||
import { SectionCollapsible } from "@/shared/ui";
|
||||
import { AttachmentFileField, 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,
|
||||
@@ -37,6 +36,7 @@ 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,6 +53,12 @@ interface FnrsSharFormData {
|
||||
|
||||
type SharFieldKey = "exstnAmt" | "chgRqtAmt" | "rmrkCn";
|
||||
|
||||
interface SharUploadedFile {
|
||||
fileKey: string;
|
||||
fileName: string;
|
||||
atflId?: string;
|
||||
}
|
||||
|
||||
interface FnrsSharFormSectionProps {
|
||||
initialSharTree?: FnrsSharBizCstTreeDto[];
|
||||
initialSharAtchFiles?: AtflMngClientResponseDto[];
|
||||
@@ -62,12 +68,30 @@ 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 [uploadedFileName, setUploadedFileName] = useState(
|
||||
() => initialSharAtchFiles?.[0]?.orgnlFileNm ?? "",
|
||||
const [uploadedFile, setUploadedFile] = useState<SharUploadedFile | null>(() =>
|
||||
toInitialSharFile(initialSharAtchFiles?.[0]),
|
||||
);
|
||||
const { mutate: downloadByAtflId } = useDownloadAttachmentByAtflId();
|
||||
|
||||
const { data: masterTree = [], isLoading: isMasterLoading } = useFnrsSharSeMngTree();
|
||||
const { data: acntgTree = [] } = useAcntgSeMngTree();
|
||||
@@ -78,9 +102,13 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
const isLoading = initialSharTree?.length ? false : isMasterLoading;
|
||||
|
||||
const { uploadFileAsync, isUploading } = useTus({
|
||||
onSuccess: (atflCd) => {
|
||||
onSuccess: (atflCd, atflId, _file, fileKey) => {
|
||||
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);
|
||||
@@ -96,6 +124,7 @@ 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]);
|
||||
|
||||
@@ -120,16 +149,32 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
applySharList(draft);
|
||||
}
|
||||
|
||||
async function handleSharFileChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const file = event.target.files?.[0];
|
||||
async function handleSharFileAdd(files: FileList | null) {
|
||||
const file = files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const fileKey = createSharUploadKey(file);
|
||||
setUploadedFileName(file.name);
|
||||
await uploadFileAsync(file, { fileKey });
|
||||
event.target.value = "";
|
||||
setUploadedFile({ fileName: file.name, fileKey });
|
||||
|
||||
try {
|
||||
await uploadFileAsync(file, { fileKey });
|
||||
} catch {
|
||||
// onError callback handles cleanup
|
||||
}
|
||||
}
|
||||
|
||||
const sharAtchFiles = uploadedFile
|
||||
? [
|
||||
{
|
||||
fileKey: uploadedFile.fileKey,
|
||||
fileName: uploadedFile.fileName,
|
||||
onDownload: uploadedFile.atflId
|
||||
? () => downloadByAtflId(uploadedFile.atflId!)
|
||||
: undefined,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SectionCollapsible title="재원분담">
|
||||
@@ -189,40 +234,26 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
{item.label}
|
||||
</TableHead>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<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>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="text-right">
|
||||
@@ -233,19 +264,12 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
<div className="text-right">{calcDiffPercent(diff, data.exstnAmt || 0)}</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<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>
|
||||
)}
|
||||
/>
|
||||
<Field>
|
||||
<Input
|
||||
value={data.rmrkCn ?? ""}
|
||||
onChange={(e) => handleSharChange(idx, "rmrkCn", e.target.value)}
|
||||
/>
|
||||
</Field>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
@@ -257,31 +281,20 @@ function FnrsSharFormSection({ initialSharTree, initialSharAtchFiles }: FnrsShar
|
||||
|
||||
<div className="mt-6 px-2 pb-2">
|
||||
<h3 className="mb-2 text-xl font-semibold">첨부파일</h3>
|
||||
<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>
|
||||
{!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>
|
||||
</SectionCollapsible>
|
||||
);
|
||||
|
||||
@@ -5,7 +5,12 @@ 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 {
|
||||
AttachmentDownloadLink,
|
||||
attachService,
|
||||
useDownloadAttachmentByAtflId,
|
||||
} from "@/entities/attach";
|
||||
import { FnrsCnsltnSplmntDmndResponseDto } from "@/entities/fnrs-cnsltn";
|
||||
import {
|
||||
Badge,
|
||||
Table,
|
||||
@@ -46,6 +51,7 @@ interface SplmntDmndFormData {
|
||||
}
|
||||
|
||||
interface SplmntDmndFormSectionProps {
|
||||
initialData?: FnrsCnsltnSplmntDmndResponseDto[];
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
@@ -65,7 +71,7 @@ function getRowKey(item: SplmntDmndFormItem, index: number) {
|
||||
return item.splmntDmndSn != null ? String(item.splmntDmndSn) : `idx-${index}`;
|
||||
}
|
||||
|
||||
function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps) {
|
||||
function SplmntDmndFormSection({ initialData, readOnly = false }: SplmntDmndFormSectionProps) {
|
||||
const formId = useId();
|
||||
const { control, setValue, getValues } = useFormContext<SplmntDmndFormData>();
|
||||
const splmntDmndList = useWatch({ control, name: "splmntDmndList" }) ?? [];
|
||||
@@ -260,6 +266,7 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps)
|
||||
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}>
|
||||
@@ -282,7 +289,15 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps)
|
||||
</TableCell>
|
||||
<TableCell className="align-top">
|
||||
{isRowReadOnly ? (
|
||||
<span className="whitespace-pre-wrap">{item.splmntCn || "-"}</span>
|
||||
<div className="space-y-2">
|
||||
<div className="whitespace-pre-wrap">{item.splmntCn || "-"}</div>
|
||||
{splmntAtchFileInfo?.atflCd ? (
|
||||
<AttachmentDownloadLink
|
||||
atflCd={splmntAtchFileInfo?.atflCd}
|
||||
fileName={splmntAtchFileInfo?.orgnlFileNm}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<Controller
|
||||
control={control}
|
||||
@@ -292,7 +307,7 @@ function SplmntDmndFormSection({ readOnly = false }: SplmntDmndFormSectionProps)
|
||||
value={field.value ?? ""}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
placeholder="보완완료 내용을 입력하세요."
|
||||
placeholder="보완 완료 내용을 입력하세요."
|
||||
className="min-h-[6rem] !text-xl"
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user