439 lines
14 KiB
TypeScript
439 lines
14 KiB
TypeScript
"use client";
|
|
import {
|
|
ContentsDiv,
|
|
ScrollArea,
|
|
SearchDiv,
|
|
SearchFormLeft,
|
|
SearchInput,
|
|
Title,
|
|
UiModal,
|
|
UiTable,
|
|
} from "@common/business_components";
|
|
import { Button } from "@common/components";
|
|
import { useWrStore } from "../../../store";
|
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@common/components";
|
|
import { useCallback, useEffect, useState } from "react";
|
|
import ListDiv from "@common/business_components/ui/list_div";
|
|
import { confirmToast, tableData } from "@/components/lib/func/commom";
|
|
import type {
|
|
customPbnst,
|
|
fcBizType,
|
|
PbnstRegArrrayType,
|
|
pubHasuType,
|
|
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
|
|
import {
|
|
pbnstRegZod,
|
|
pbnstRegArrayZod,
|
|
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
|
|
import { PbnstMngForm } from "@/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm";
|
|
import { PbnstBuseForm } from "@/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm";
|
|
import { PbnstRegModal } from "@/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal";
|
|
import toast, { Toaster } from "react-hot-toast"; // toast 이미 import 되어 있음
|
|
import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg";
|
|
import { useFieldArray, useForm, type FieldErrors } from "react-hook-form"; // FieldErrors 타입 추가
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { z } from "zod";
|
|
import { createDefaultPbnstItem } from "./utilsZod";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
export default function PublicReuseFaciltyInsert() {
|
|
const {
|
|
code,
|
|
pbnst,
|
|
pubHasuInit,
|
|
pbnstInsert,
|
|
pbnstRegClick,
|
|
pbnstRuseFcltUpdate,
|
|
pubHasuSearch,
|
|
pbnstRuseBizUpdate,
|
|
} = useWrStore((state) => state);
|
|
const { t } = useTranslation("base");
|
|
const { pubFacHeadList, pbnstHasuResCount, pbnstHasuRes, pbnstRes } = pbnst;
|
|
const [, setCurrentTab] = useState<string>("mng");
|
|
const [isRuseReg, setIsRuseReg] = useState<{
|
|
isModal: boolean;
|
|
}>({ isModal: false });
|
|
const [resetPage, isResetPage] = useState(false);
|
|
const [indexMng, setIndexMng] = useState<number>(0);
|
|
const [indexBiz, setIndexBiz] = useState<number>(0);
|
|
const [searchTxt, setSearchTxt] = useState<string>();
|
|
const [bdstTable, setbdstTable] = useState<pubHasuType[]>();
|
|
const [selectRow, setSelectRow] = useState<pubHasuType>();
|
|
|
|
const [pageNation, setPageNation] = useState<number>(1);
|
|
const form = useForm<{
|
|
items: z.infer<typeof pbnstRegZod>[];
|
|
}>({
|
|
resolver: zodResolver(z.object({ items: pbnstRegArrayZod })),
|
|
// defaultValues: { items: pbnstRegArrayZod.parse([]) },
|
|
mode: "onChange",
|
|
});
|
|
const { isDirty } = form.formState;
|
|
|
|
const {
|
|
fields: items,
|
|
append,
|
|
remove,
|
|
} = useFieldArray({
|
|
control: form.control,
|
|
name: `items`,
|
|
});
|
|
|
|
const formWatch = form.watch("items");
|
|
useEffect(() => {
|
|
if (!pbnst.isInit) {
|
|
pubHasuInit("1", "41");
|
|
}
|
|
}, [pbnst.isInit, pubHasuInit]);
|
|
|
|
const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => {
|
|
if (!isDirty) {
|
|
toast.error("변경사항이 없습니다."); // 변경사항이 없으면 메시지 표시
|
|
return;
|
|
}
|
|
const data = writeRgtrAndMdfr(valuse.items);
|
|
let bizs = [] as fcBizType[];
|
|
data.forEach((fclt) => {
|
|
bizs = [...bizs, ...fclt.fcBizs];
|
|
});
|
|
|
|
const pbnst = data.map((fclt) => {
|
|
return {
|
|
pbnstRuseFcltId: fclt.pbnstRuseFcltId,
|
|
fcltCpct: fclt.fcltCpct,
|
|
ruseCpct: fclt.ruseCpct,
|
|
splyExtraCpct: fclt.splyExtraCpct,
|
|
rgtr: fclt.rgtr,
|
|
mdfr: fclt.mdfr,
|
|
fcFiles: fclt.fcFiles,
|
|
fcOpers: fclt.fcOpers,
|
|
};
|
|
});
|
|
|
|
const handleConfirm = async () => {
|
|
Promise.allSettled([
|
|
pbnstRuseFcltUpdate(pbnst as customPbnst[]),
|
|
pbnstRuseBizUpdate(bizs),
|
|
]).then(([resFclt, resBiz]) => {
|
|
const isFcltSuccess = resFclt.status === "fulfilled";
|
|
const isBizSuccess = resBiz.status === "fulfilled";
|
|
if (isFcltSuccess && isBizSuccess) {
|
|
toast.success("하수 재이용 시설 및 사업이 수정되었습니다. ");
|
|
// 메시지 표시, 페이지 이동 등
|
|
} else {
|
|
let message = "";
|
|
// ✅ 각각 어떤 항목이 실패했는지도 로그/알림 처리
|
|
if (!isFcltSuccess) {
|
|
message = "하수재이용 시설 ";
|
|
}
|
|
if (!isBizSuccess) {
|
|
message += "하수재이용 시설 사업 ";
|
|
}
|
|
toast.error(message + "오류 입니다 ");
|
|
pubHasuInit("1", "41");
|
|
}
|
|
});
|
|
};
|
|
confirmToast(t("isUpdatetMsg"), handleConfirm);
|
|
};
|
|
|
|
// 폼 유효성 검사 실패 시 호출될 함수
|
|
const onInvalid = (
|
|
errors: FieldErrors<{ items: z.infer<typeof pbnstRegZod>[] }>
|
|
) => {
|
|
const findAllErrors = (
|
|
errorObject: any,
|
|
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 (value.message) {
|
|
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}`);
|
|
});
|
|
}
|
|
};
|
|
useEffect(() => {
|
|
console.log("form errors:", form.formState.errors);
|
|
}, [form.formState.errors]);
|
|
|
|
useEffect(() => {
|
|
console.log("formState", items);
|
|
}, [items]);
|
|
|
|
useEffect(() => {
|
|
if (code.sggCd.length && pbnstHasuRes.length > 0) {
|
|
setbdstTable(tableData(pbnstHasuRes, { code }));
|
|
}
|
|
}, [code, pbnstHasuRes]);
|
|
|
|
const cellClick = (val: number | undefined) => {
|
|
if (val === undefined) return;
|
|
const selectRow = pbnstHasuRes?.[val];
|
|
setSelectRow(pbnstHasuRes?.[val]);
|
|
if (selectRow) {
|
|
pbnstRegClick(selectRow);
|
|
} else {
|
|
console.error("선택된 행이 없습니다.");
|
|
}
|
|
};
|
|
|
|
const handleTabChange = (value: string) => {
|
|
setCurrentTab(value);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (indexMng) {
|
|
setIndexBiz(0);
|
|
}
|
|
}, [indexMng]);
|
|
|
|
useEffect(() => {
|
|
if (pbnst.pbnstRes && pbnst.pbnstRes?.length > 0) {
|
|
form.reset({ items: pbnst.pbnstRes });
|
|
} else {
|
|
form.reset({ items: [] });
|
|
}
|
|
}, [pbnst.pbnstRes]);
|
|
|
|
const appendPbnstRuseFclt = useCallback(() => {
|
|
if (pbnstRes && pbnstRes.length > 0) {
|
|
if (selectRow) {
|
|
// pbnstRes.length를 사용하여 새로운 항목의 인덱스를 결정
|
|
append(createDefaultPbnstItem(selectRow, pbnstRes.length));
|
|
setIsRuseReg({ isModal: true });
|
|
}
|
|
} else {
|
|
if (selectRow) {
|
|
// pbnstRes가 비어있으면 첫 항목이므로 인덱스 0
|
|
|
|
append(createDefaultPbnstItem(selectRow, 0));
|
|
setIsRuseReg({ isModal: true });
|
|
}
|
|
}
|
|
}, [pbnstRes, append, selectRow]);
|
|
|
|
useEffect(() => {
|
|
pubHasuInit(String(pageNation), "41");
|
|
isResetPage(false);
|
|
}, [pageNation, pubHasuInit]);
|
|
|
|
return (
|
|
<>
|
|
<ContentsDiv>
|
|
<div className="grid grid-cols-4 gap-4">
|
|
<div className="col-span-4">
|
|
<Title title={"하수재이용 시설 등록"} />
|
|
<SearchDiv>
|
|
<SearchFormLeft>
|
|
<SearchInput
|
|
className={"col-span-3 !text-3xl"}
|
|
label="시설명"
|
|
textValue={searchTxt || ""}
|
|
setText={(e) => {
|
|
setSearchTxt(e);
|
|
}}
|
|
/>
|
|
<div className="flex flex-row space-x-2 items-center">
|
|
<Button
|
|
className=""
|
|
size={"sm"}
|
|
onClick={() => {
|
|
if (searchTxt && searchTxt?.length > 0) {
|
|
pubHasuSearch(String(pageNation), "41", searchTxt);
|
|
} else {
|
|
pubHasuInit(String(pageNation), "41");
|
|
}
|
|
isResetPage(true);
|
|
}}
|
|
>
|
|
조회
|
|
</Button>
|
|
<Button
|
|
size={"sm"}
|
|
className=""
|
|
onClick={() => {
|
|
// pbnstMngData([] as customPbnst[]);
|
|
if (!selectRow) {
|
|
toast.error(
|
|
"하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요"
|
|
);
|
|
return;
|
|
} else {
|
|
appendPbnstRuseFclt();
|
|
}
|
|
}}
|
|
>
|
|
신규등록
|
|
</Button>
|
|
</div>
|
|
</SearchFormLeft>
|
|
</SearchDiv>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-8 gap-4">
|
|
<div className="col-span-5 h-full min-h-0">
|
|
<ListDiv className="min-h-0">
|
|
{/* <ScrollArea className="h-[60vh]"> */}
|
|
{" "}
|
|
{/* 높이 조정 */}
|
|
<UiTable
|
|
tableData={bdstTable || []}
|
|
headlist={pubFacHeadList}
|
|
pageSize={10}
|
|
total={pbnstHasuResCount || 20}
|
|
headName={"공공 하수처리시설 목록"}
|
|
cellClick={cellClick}
|
|
onPageChange={setPageNation}
|
|
resetPageNo={resetPage}
|
|
/>
|
|
{/* </ScrollArea> */}
|
|
</ListDiv>
|
|
</div>
|
|
<ListDiv className="col-span-3 pr-6">
|
|
{formWatch && formWatch[0]?.pbnstRuseFcltId ? (
|
|
<Tabs defaultValue="mng" onValueChange={handleTabChange}>
|
|
<div className="flex justify-between items-center">
|
|
<TabsList className="">
|
|
<TabsTrigger className="!text-xl" value="mng">
|
|
시설정보
|
|
</TabsTrigger>
|
|
<TabsTrigger className="!text-xl" value="buse">
|
|
차수별정보
|
|
</TabsTrigger>
|
|
</TabsList>
|
|
<Button
|
|
size={"sm"}
|
|
onClick={form.handleSubmit(onSubmit, onInvalid)}
|
|
className="mr-5 mt-2"
|
|
>
|
|
저장
|
|
</Button>
|
|
</div>
|
|
<ScrollArea className="h-[60vh]">
|
|
<TabsContent className="max-h-full" value="mng">
|
|
<PbnstMngForm
|
|
formType="update"
|
|
form={form}
|
|
setIndexMng={(index: number) => {
|
|
setIndexMng(index);
|
|
}}
|
|
indexMng={indexMng}
|
|
/>
|
|
</TabsContent>
|
|
<TabsContent className="max-h-full" value="buse">
|
|
<PbnstBuseForm
|
|
formType="update"
|
|
form={form}
|
|
setIndexBiz={(index: number) => {
|
|
setIndexBiz(index);
|
|
}}
|
|
indexMng={indexMng}
|
|
indexBiz={indexBiz}
|
|
/>
|
|
</TabsContent>
|
|
|
|
</ScrollArea>
|
|
</Tabs>
|
|
) : (
|
|
<>
|
|
<div className="card-header text-2xl">
|
|
등록된 재이용시설 정보가 없습니다.
|
|
</div>
|
|
<div className="!mx-[1.6rem] !p-0r flex flex-col items-center justify-center h-64 border rounded-md">
|
|
<p className="text-3xl mb-4">
|
|
신규등록 버튼을 클릭하여 재이용시설을 등록해주세요.
|
|
</p>
|
|
<Button
|
|
size={"sm"}
|
|
className=""
|
|
onClick={() => {
|
|
// pbnstMngData([] as customPbnst[]);
|
|
if (!selectRow) {
|
|
toast.error(
|
|
"하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요"
|
|
);
|
|
return;
|
|
} else {
|
|
appendPbnstRuseFclt();
|
|
}
|
|
}}
|
|
>
|
|
신규등록
|
|
</Button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</ListDiv>
|
|
<UiModal
|
|
body={
|
|
<PbnstRegModal
|
|
form={form} // form prop 전달
|
|
setPbnstInsert={(data) => {
|
|
if (data) {
|
|
pbnstInsert(data); // pbnstInsert 함수 호출
|
|
setIsRuseReg({ isModal: false });
|
|
}
|
|
}}
|
|
onInvalid={onInvalid}
|
|
></PbnstRegModal>
|
|
}
|
|
bodyClass="w-[68vw] max-w-none h-[90vh]"
|
|
open={isRuseReg.isModal}
|
|
onOpenChange={(open) => {
|
|
remove(form.getValues("items").length - 1);
|
|
setIsRuseReg({ isModal: open });
|
|
}}
|
|
/>
|
|
</div>
|
|
<Toaster position="top-center" />
|
|
</ContentsDiv>
|
|
</>
|
|
);
|
|
}
|