Files
frontend/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx
T

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>
</>
);
}