결함대장 수정

This commit is contained in:
leedj312
2025-07-04 22:26:55 +09:00
parent 76a511a5e0
commit db74c07df8
9 changed files with 282 additions and 96 deletions
+21 -12
View File
@@ -9,6 +9,7 @@ import {
} from "@/app/store/matDmd/coMatRvw/type";
import { formH1Class, formTableName } from "@/components/lib/class/comClass";
import {
formatPhoneNumber,
getCity,
getdmdMatFlag,
tableData,
@@ -33,7 +34,7 @@ import {
import ListDiv from "@common/business_components/ui/list_div";
import { Button, Input, Label } from "@common/components/ui";
import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import { useForm, useWatch } from "react-hook-form";
import toast, { Toaster } from "react-hot-toast";
import { z } from "zod";
import moment from "moment";
@@ -68,6 +69,7 @@ export default function CoMatRvw() {
const [selectData, setSelectData] = useState<DmdCoReqType>(
{} as DmdCoReqType
);
const [selectionVersion, setSelectionVersion] = useState(0);
const getSearch = (
startYear: number,
@@ -91,39 +93,46 @@ export default function CoMatRvw() {
const form = useForm<z.infer<typeof DmdCoReqSchema>>({
resolver: zodResolver(DmdCoReqSchema),
shouldUnregister: true
});
const TableCellClick = (index: number) => {
console.log(coMatRvwRes);
if (coMatRvwRes && coMatRvwRes[index]) {
setSelectData(coMatRvwRes[index]);
setSelectionVersion((v) => v + 1); // 행을 클릭할 때마다 버전을 1씩 증가
}
};
const useUsgCdWatch = useWatch({ control: form.control, name: "useUsgCd" });
useEffect(() => {
if (form?.getValues("useUsgCd")) {
console.log(' useUsgCdWatch page ' ,useUsgCdWatch)
if (useUsgCdWatch) {
form.setValue(
"demandCst.usgPrcsProcs",
usageProcData?.find(
(item) => item.usage === (form?.getValues("useUsgCd") as string)
(item) => item.usage === useUsgCdWatch
)?.process ?? ""
);
}
}, [form.watch("useUsgCd")]);
}, [useUsgCdWatch, form,selectionVersion]);
useEffect(() => {
if (coMatRvwRes.length > 0) {
const data = coMatRvwRes?.map((item, index) => {
let flag = "";
flag = getdmdMatFlag(item?.demandMatching?.splyFcltFlg ??"");
flag = getdmdMatFlag(item?.demandMatching?.splyFcltFlg ||undefined);
return {
...item,
ctpvCd: item.demandCo?.ctpvCd,
sggCd: item.demandCo?.sggCd,
dmdCoNm: item.demandCo?.dmdCoNm,
daddr: item.demandCo?.daddr,
telno: item.demandCo?.telno,
regDt: moment(item?.demandCo?.regDt).format("YYYY-MM-DD"),
telno: formatPhoneNumber(item?.demandCo?.telno||''),
dmndTmng: item?.dmndTmng,
splyFcltFlg: flag,
};
});
@@ -151,8 +160,8 @@ export default function CoMatRvw() {
useEffect(() => {
if (selectData) {
console.log(selectData);
form.reset(selectData);
setSelectionVersion((v) => v + 1); // 강제로 바뀌었다고 알리기
}
}, [selectData, form]);
return (
@@ -269,7 +278,7 @@ export default function CoMatRvw() {
></UiTable>
</ListDiv>
<hr />
<ScrollArea className="h-[49vh]">
{/* <ScrollArea className="h-[49vh]"> */}
<ListDiv>
<h3 className="mt-4 ml-2 text-2xl font-medium text-label">
수요신청 상세현황
@@ -660,9 +669,9 @@ export default function CoMatRvw() {
</div>
</div>
</Form>
<ConCostForm form={form} type={"mat"} />
<ConCostForm form={form} type={"mat"} selectionVersion={selectionVersion} />
</ListDiv>
</ScrollArea>
{/* </ScrollArea> */}
</div>
</div>
</ContentsDiv>
@@ -35,6 +35,10 @@ import { useFieldArray, useForm, type FieldErrors } from "react-hook-form"; // F
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createDefaultPbnstItem } from "./utilsZod";
export default function PublicReuseFaciltyInsert() {
const {
code,
@@ -100,7 +104,7 @@ export default function PublicReuseFaciltyInsert() {
fcOpers: fclt.fcOpers,
};
});
await pbnstRuseFcltUpdate(pbnst as customPbnst[]);
await pbnstRuseFcltUpdate(pbnst as customPbnst[])
await pbnstRuseBizUpdate(bizs);
};
@@ -115,21 +119,43 @@ export default function PublicReuseFaciltyInsert() {
path: (string | number)[] = []
): { message: string; path: (string | number)[] }[] => {
if (!errorObject) return [];
return Object.keys(errorObject).flatMap((key) => {
const value = errorObject[key];
const currentKey = !isNaN(parseInt(key)) ? parseInt(key) : key;
const newPath = [...path, currentKey];
if (value && typeof value === "object") {
if (typeof value.message === "string") {
return [{ message: value.message, path: newPath }]; // 에러 메시지와 경로 반환
}
return findAllErrors(value, newPath);
// Zod에서 배열 에러는 바로 배열로 전달됨 (예: items: [ {}, {...}, ... ])
if (Array.isArray(errorObject)) {
return errorObject.flatMap((entry, index) =>
findAllErrors(entry, [...path, index])
);
}
if (typeof errorObject === "object") {
const entries: { message: string; path: (string | number)[] }[] = [];
if (typeof errorObject.message === "string") {
entries.push({ message: errorObject.message, path });
}
return [];
});
if (Array.isArray(errorObject._errors)) {
for (const msg of errorObject._errors) {
entries.push({ message: msg, path });
}
}
const keys = Object.keys(errorObject).filter(
(k) => k !== "message" && k !== "_errors"
);
for (const key of keys) {
entries.push(...findAllErrors(errorObject[key], [...path, key]));
}
return entries;
}
return [];
};
const errorList = findAllErrors(errors);
console.log(' errors ', errors)
if (errorList.length > 0) {
errorList.forEach((errorInfo) => {
const { message, path } = errorInfo;
@@ -160,6 +186,9 @@ export default function PublicReuseFaciltyInsert() {
});
}
};
useEffect(() => {
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
useEffect(() => {
if (code.sggCd.length && pbnstHasuRes.length > 0) {
@@ -184,6 +213,7 @@ export default function PublicReuseFaciltyInsert() {
useEffect(() => {
if (pbnst.pbnstRes && pbnst.pbnstRes?.length > 0) {
form.reset({ items: pbnstRegArrayZod.parse(pbnst.pbnstRes) });
} else {
form.reset({ items: [] });
@@ -204,21 +234,23 @@ export default function PublicReuseFaciltyInsert() {
if (pbnstRes && pbnstRes.length > 0) {
const lastPbnst = pbnstRes[pbnstRes.length - 1];
if (lastPbnst && selecTableRow) {
append(
pbnstRegZod.parse({
pbnstRuseFcltPk:
String(selecTableRow.bscFctCd) + String(pbnstRes.length + 1),
ruseUtztnFcltNm: "",
fcBizs: [
fcBizSchema.parse({
pbnstRuseFcltFk:
String(lastPbnst.pbnstRuseFcltPk) +
String(pbnstRes.length + 1),
cycl: 1,
}),
] as fcBizType[],
})
);
// append(
// pbnstRegZod.parse({
// pbnstRuseFcltPk:
// String(selecTableRow.bscFctCd) + String(pbnstRes.length + 1),
// ruseUtztnFcltNm: null,
// fcBizs: [
// fcBizSchema.parse({
// pbnstRuseFcltFk:
// String(lastPbnst.pbnstRuseFcltPk) +
// String(pbnstRes.length + 1),
// cycl: 1,
// }),
// ] as fcBizType[],
// })
// );
append(createDefaultPbnstItem(selecTableRow, pbnstRes.length));
setIsRuseReg({ isModal: true });
}
} else {
@@ -396,7 +428,7 @@ export default function PublicReuseFaciltyInsert() {
form={form} // form prop 전달
setPbnstInsert={(data) => {
if (data) {
pbnstInsert(data); // pbnstInsert 함수 호출
// pbnstInsert(data); // pbnstInsert 함수 호출
setIsRuseReg({ isModal: false });
}
}}
@@ -0,0 +1,38 @@
import type { fcBizType, pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
export function createDefaultPbnstItem(
selecTableRow: pubHasuType,
index: number
) {
const pk = String(selecTableRow.bscFctCd) + String(index + 1);
return {
pbnstRuseFcltPk: pk,
swtrmtFcltFk: "",
ruseUtztnFcltNm: "", // 필수 입력이므로 빈 문자열로
fcltCpct: 0,
ruseCpct: 0,
splyExtraCpct: 0,
regDt: null,
rgtr: "test",
mdfcnDt: null,
mdfr: "test",
fcFiles: [],
fcBizs: [
createDefaultBiz(pk, 1), // 첫 차수
],
fcOpers: [],
};
}
export function createDefaultBiz(
pbnstRuseFcltFk: string,
cycl: number
): fcBizType {
return {
pbnstRuseFcltFk,
cycl,
// 필요한 값 있으면 여기에 추가
} as fcBizType;
}
@@ -37,6 +37,7 @@ import {
type PrfType,
} from "@/app/store/prfmncReg/pbnstPrfmncReg/type";
import { excelToPbnPrfData } from "@/app/store/prfmncReg/pbnstPrfmncReg/excel";
import data from "@common/components/ui/icon-finder/data";
export default function prfmncPrfmncReg() {
const { code, prfmnc, prfmncHasuInit, prfmnRegData } = useWrStore(
@@ -78,9 +79,6 @@ export default function prfmncPrfmncReg() {
}
}, [inputData, searchYear, pbnstRuseFcltPk, prfmncHasuInit, pageNation]);
const cellClick = useCallback(
(index: number | undefined, headId: string) => {
if (index !== undefined) {
@@ -88,16 +86,22 @@ export default function prfmncPrfmncReg() {
isModal: false,
data: prfmncRes[index] || ({} as FcPrfType),
});
const inUsageData = structuredClone(initValInUsage).map(row => ({
const inUsageData = structuredClone(initValInUsage).map((row) => ({
...row,
구분: "장내용수",
}));
const outUsageData = structuredClone(initValOutUsage).map(row => ({
const outUsageData = structuredClone(initValOutUsage).map((row) => ({
...row,
구분: "장외용수",
}));
const inSubtotal = { ...structuredClone(subTotalObj), 구분: "장내용수" };
const outSubtotal = { ...structuredClone(subTotalObj), 구분: "장외용수" };
const inSubtotal = {
...structuredClone(subTotalObj),
구분: "장내용수",
};
const outSubtotal = {
...structuredClone(subTotalObj),
구분: "장외용수",
};
if (prfmncRes[index]?.prfList) {
prfmncRes[index].prfList?.forEach((prfItem) => {
const month = Number(prfItem?.ym?.slice(4)); // "MM"
@@ -133,7 +137,11 @@ export default function prfmncPrfmncReg() {
});
inUsageData.push(inSubtotal);
outUsageData.push(outSubtotal);
const usageTotalObj: { [key: string]: any } = { ...structuredClone(totalObj), 구분: "총합계", 용수명: "" };
const usageTotalObj: { [key: string]: any } = {
...structuredClone(totalObj),
구분: "총합계",
용수명: "",
};
for (const key in usageTotalObj) {
if (["usage", "gubun", "edit"].includes(key)) continue;
const val =
@@ -156,7 +164,7 @@ export default function prfmncPrfmncReg() {
useEffect(() => {
if (!prfmnc.isInit) {
prfmncHasuInit('1', "41");
prfmncHasuInit("1", "41");
}
}, [prfmnc.isInit, prfmncHasuInit]);
useEffect(() => {
@@ -203,7 +211,7 @@ export default function prfmncPrfmncReg() {
<SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 ">
<div className="space-y">
<div className="flex flex-row space-x-2">
<Button size={"sm"} onClick={(e) => { }}>
<Button size={"sm"} onClick={(e) => {}}>
조회
</Button>
<Button
@@ -218,7 +226,7 @@ export default function prfmncPrfmncReg() {
size={"sm"}
onClick={() => {
if (cellNum === undefined) {
toast.error("하수처리재이용시설을 선택해주세요.")
toast.error("하수처리재이용시설을 선택해주세요.");
return;
} else {
setIsExcelOPen({ ...isExcelOpen, isModal: true });
@@ -279,7 +287,14 @@ export default function prfmncPrfmncReg() {
</ContentsDiv>
<UiModal
bodyClass={"w-[30vw] max-w-none"}
body={<PbnPrfExcelModal data={isExcelOpen.data}></PbnPrfExcelModal>}
body={
<PbnPrfExcelModal
data={isExcelOpen.data}
setIsExcelOPen={(open) => {
setIsExcelOPen({ ...isExcelOpen, isModal: open })
}}
></PbnPrfExcelModal>
}
open={isExcelOpen.isModal}
onOpenChange={() => {
setIsExcelOPen({ isModal: false, data: {} as FcPrfType });
@@ -29,12 +29,12 @@ export const coRvwHeadListExport: CommonTableProps[]= [
visible: true,
},
{
id: "regDt", //바꾸기
id: "dmndTmng", //바꾸기
title: "신청일자",
visible: true,
},
{
id: "state", //바꾸기
id: "splyFcltFlg", //바꾸기
title: "진행상태",
visible: true,
},
+22 -11
View File
@@ -165,18 +165,29 @@ export const yearOptions: { val: string; text: string }[] = Array.from(
})
);
export const getdmdMatFlag = (flag: string|undefined) => {
let flagMat = "";
export const getdmdMatFlag = (flag: string | undefined | null) => {
const cleanFlag = (flag ?? "").trim();
if(flag === undefined || flag === null) {
flagMat = "신청";
}else if(flag.trim() === "Sub") {
flagMat = "검토";
}else if(flag.trim() === "matched") {
flagMat = "매칭완료";
}else {
flagMat = "신청";
console.log('cleanFlag ', cleanFlag)
switch (cleanFlag) {
case "Sub":
return "검토";
case "matched":
return "매칭완료";
default:
return "신청";
}
return flagMat;
};
export const formatPhoneNumber=(tel:string) => {
const onlyNums = tel.replace(/[^0-9]/g, '');
if (onlyNums.length === 10) {
return onlyNums.replace(/(\d{2})(\d{4})(\d{4})/, '$1-$2-$3');
} else if (onlyNums.length === 11) {
return onlyNums.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3');
} else {
return tel; // 유효하지 않으면 빈 문자열
}
}
@@ -31,9 +31,11 @@ import toast from "react-hot-toast";
export default function ConCostForm({
form,
type,
selectionVersion,
}: {
form: UseFormReturn<DmdCoReqType>;
type: string;
selectionVersion: number;
}) {
const {
constCostRes,
@@ -70,7 +72,14 @@ export default function ConCostForm({
const dmndQtyWatch = form.watch("dmndQty");
const totalConstCost = form.watch("totalConstCost");
const cstrnCst = form.watch("demandCst.cstrnCst");
const useUsgCdWatch = form.watch("useUsgCd");
useEffect(() =>{
console.log('usgPrcsProcsWatch ', usgPrcsProcsWatch)
console.log('useUsgCdWatch ' , useUsgCdWatch)
},[usgPrcsProcsWatch, useUsgCdWatch, selectionVersion])
const calculateConstCost = useCallback(() => {
const usgPrcsProcs = usgPrcsProcsWatch;
const dmndQtyYear = dmndQtyWatch * 365;
@@ -255,7 +264,7 @@ export default function ConCostForm({
placeholder=""
type="text"
onChange={field.onChange}
value={form.getValues("demandCst.usgPrcsProcs") ?? ""}
value={field.value ?? ""}
className="mt-2"
/>
</FormUi>
@@ -18,12 +18,13 @@ import { Button, Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import type { z } from "zod";
export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) {
export default function PbnPrfExcelModal({ data ,setIsExcelOPen }: { data: FcPrfType ,setIsExcelOPen:(open:boolean )=>void }) {
const { prfmnRegData } = useWrStore((state) => state);
const [files, setFiles] = useState<File[] | null>(null);
const [file, setFile] = useState<File | null>(null);
const [excelData, setExcelData] = useState<PrfType[]>();
const form = useForm<z.infer<typeof pbnPrfRegExcelZod>>({
resolver: zodResolver(pbnPrfRegExcelZod),
@@ -31,10 +32,18 @@ export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) {
ym: String(new Date().getFullYear()),
},
});
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.target.files?.[0];
if (selectedFile) {
setFile(selectedFile);
}
};
const onSubmit = (values: z.infer<typeof pbnPrfRegExcelZod>) => {
if (excelData) {
prfmnRegData(excelData as unknown as PrfType);
setIsExcelOPen(false)
}
@@ -46,31 +55,24 @@ export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) {
form.setValue("pbnstRuseFcltNm", data.ruseUtztnFcltNm);
}, [data]);
useEffect(() => {
console.log("excelData", excelData);
}, [excelData]);
useEffect(() => {
if (files) {
console.log("이런")
const file = files[0];
if (file) {
const isFile = file
if (form.getValues("ym").length < 1) {
ToastFormUi({
body: "파일업로드 이전에 등록년도를 작성해주세요 ",
title: "지도점검 등록",
});
setFiles(null);
toast.error("파일업로드 이전에 등록년도를 작성해주세요 " );
setFile(null);
return;
}
if (file) {
filePicked(file, 0).then((res) => {
if (isFile) {
filePicked(isFile, 0).then((res) => {
if (res !== undefined)
setExcelData(excelToPbnPrfData(res as unknown as JSON[], form.getValues("ym"), form.getValues('pbnstRuseFcltFk')) as PrfType[])
});
}
}
}, [files]);
}, [file]);
return (
<>
@@ -144,14 +146,25 @@ export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) {
name="file"
render={({ field }) => (
<FormUi label="점검결과 파일">
<FileUpload
{/* <FileUpload
files={files || []}
setFiles={(files: File[] | null) => {
if (files) setFiles(files);
}}
fileTypes="xlsx"
maxFiles={1}
></FileUpload>
></FileUpload> */}
<div className="space-y-4 p-4">
<Input
type="file"
onChange={handleFileChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel"
/>
</div>
</FormUi>
)}
/>
@@ -1,6 +1,7 @@
"use client";
import { useWrStore } from "@/app/store";
import {
pbnstRegZod,
type customPbnst,
type PbnstRegArrrayType,
type pubHasuType,
@@ -16,17 +17,23 @@ import {
} from "@common/components/ui";
import { useEffect, useState } from "react";
import { PbnstBuseForm } from "@/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm";
import { useFieldArray, type UseFormReturn } from "react-hook-form";
import {
useFieldArray,
type FieldErrors,
type UseFormReturn,
} from "react-hook-form";
import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg";
import toast from "react-hot-toast";
import type { z } from "zod";
export function PbnstRegModal({
form,
setPbnstInsert
setPbnstInsert,
}: {
form: UseFormReturn<{ items: PbnstRegArrrayType }>;
setPbnstInsert: (data: customPbnst) => void
setPbnstInsert: (data: customPbnst) => void;
}) {
const { pbnstInsert } = useWrStore((state) => {
const { pbnstInsert } = useWrStore((state) => {
return state;
});
const [pbnstBdstForm, setPbnstBdstForm] = useState<customPbnst>(
@@ -35,13 +42,65 @@ export function PbnstRegModal({
const [currentTab, setCurrentTab] = useState<string>("mng");
const [indexMng, setIndexMng] = useState<number>(0);
const [indexBiz, setIndexBiz] = useState<number>(0);
const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => {
const item = valuse.items[indexMng];
const data = item ? writeRgtrAndMdfr([item]) : [];
const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => {
const item = valuse.items[indexMng];
const data = item ? writeRgtrAndMdfr([item]) : [];
if (data && data[0]) {
setPbnstInsert(data[0]);
}
};
const onInvalid = (
errors: FieldErrors<{ items: z.infer<typeof pbnstRegZod>[] }>
) => {
// 재귀 함수를 사용해 중첩된 에러 객체에서 모든 에러 메시지와 경로를 찾습니다.
const findAllErrors = (
errorObject: any,
path: (string | number)[] = []
): { message: string; path: (string | number)[] }[] => {
if (!errorObject) return [];
if (data && data[0]) {
// pbnstInsert(data[0]);
setPbnstInsert(data[0])
return Object.keys(errorObject).flatMap((key) => {
const value = errorObject[key];
const currentKey = !isNaN(parseInt(key)) ? parseInt(key) : key;
const newPath = [...path, currentKey];
if (value && typeof value === "object") {
if (typeof value.message === "string") {
return [{ message: value.message, path: newPath }]; // 에러 메시지와 경로 반환
}
return findAllErrors(value, newPath);
}
return [];
});
};
const errorList = findAllErrors(errors);
if (errorList.length > 0) {
errorList.forEach((errorInfo) => {
const { message, path } = errorInfo;
let prefix = "";
const itemsIndex = path.indexOf("items");
if (itemsIndex > -1 && path.length > itemsIndex + 1) {
const itemIdx = path[itemsIndex + 1] as number;
const facilityName = form.getValues(
`items.${itemIdx}.ruseUtztnFcltNm`
);
if (facilityName) {
prefix += `[${facilityName}] `;
}
const fcBizsIndex = path.indexOf("fcBizs");
if (fcBizsIndex > -1 && path.length > fcBizsIndex + 1) {
const bizIdx = path[fcBizsIndex + 1] as number;
const cycle = form.getValues(
`items.${itemIdx}.fcBizs.${bizIdx}.cycl`
);
if (cycle) {
prefix += `[${cycle}차수] `;
}
}
}
toast.error(`${prefix}${message}`);
});
}
};
@@ -76,7 +135,7 @@ export function PbnstRegModal({
차수별정보
</TabsTrigger>
<Button
onClick={form.handleSubmit(onSubmit)}
onClick={form.handleSubmit(onSubmit, onInvalid)}
size={"sm"}
className="!justify-end"
>