Merge commit '7e879ef7ad761cfac5a401e7d20b0b57033a67a4' into iipbs/wr/dev
iipbs_wr / build (push) Waiting to run

This commit is contained in:
leedj312
2025-06-30 14:49:23 +09:00
18 changed files with 514 additions and 365 deletions
@@ -1,7 +1,6 @@
"use client";
import {
ContentsDiv,
DateMonthPicker,
ScrollArea,
SearchDiv,
SearchFormLeft,
@@ -40,7 +39,6 @@ export default function PublicReuseFaciltyInsert() {
const {
code,
pbnst,
searchFclts,
pubHasuInit,
pbnstInsert,
pbnstRegClick,
@@ -60,7 +58,7 @@ export default function PublicReuseFaciltyInsert() {
const [bdstTable, setbdstTable] = useState<pubHasuType[]>();
const [selectRow, setSelectRow] = useState<pubHasuType>();
const [pageNation, setPageNation] = useState<number>(1);
const [testMonth, setTestMonth] = useState<string>("202406");
const form = useForm<{
items: z.infer<typeof pbnstRegZod>[];
}>({
@@ -165,17 +163,16 @@ export default function PublicReuseFaciltyInsert() {
console.log("bdstTable ", bdstTable);
}, [bdstTable]);
useEffect(() => {
if (code.sggCd.length &&pbnstHasuRes.length > 0) {
if (code.sggCd.length && pbnstHasuRes.length > 0) {
setbdstTable(tableData(pbnstHasuRes, { code }));
// if (searchFclts) {
// if (searchFclts) { // 왜 오류가 발생할까?
// setbdstTable(tableData(searchFclts, { code }));
// } else if (code.sggCd.length > 0 && pbnstHasuRes.length > 0) {
// }
}
}, [code, pbnstHasuRes, searchFclts]);
}, [code, pbnstHasuRes]);
const cellClick = (val: number | undefined) => {
if (val === undefined) return;
@@ -192,10 +189,8 @@ export default function PublicReuseFaciltyInsert() {
useEffect(() => {
if (pbnst.pbnstRes && pbnst.pbnstRes?.length > 0) {
console.log("pbnst.pbnstRes", pbnstRes);
form.reset({ items: pbnstRegArrayZod.parse(pbnst.pbnstRes) });
} else {
console.log("pbnst.pbnstRes is null", pbnstRes);
form.reset({ items: [] });
}
}, [pbnst.pbnstRes]);
@@ -252,10 +247,6 @@ export default function PublicReuseFaciltyInsert() {
}
}, [pbnstRegZod, pbnstRes, append, selectRow, form]);
useEffect(() => {
// console.log(" page getgetvaslue ", form.getValues());
}, [form.getValues()]);
useEffect(() => {
pubHasuInit(String(pageNation), "41");
}, [pageNation, pubHasuInit]);
@@ -305,7 +296,6 @@ export default function PublicReuseFaciltyInsert() {
>
신규등록
</Button>
</div>
</SearchFormLeft>
</SearchDiv>
@@ -42,8 +42,8 @@ export default function Home() {
}
}, [rswt.isInit, initRswt]);
useEffect(() => {
if (code.sggCd.length > 0) {
const rswtData = tableData(rswtRes, { code }).map(
if (code.sggCd.length > 0 && rswtRes?.length > 0) {
const rswtData = tableData(rswtRes, { code })?.map(
(item: customRswtBdst) => {
return {
...item,
@@ -151,7 +151,7 @@ export default function Home() {
</div>
<UiModal
body={<BdstReg moType="RST"></BdstReg>}
body={<BdstReg moType="RST" setIsModal={setIsNewReg}></BdstReg>}
bodyClass="w-[68vw] max-w-none h-[90vh]"
open={issNewReg}
onOpenChange={(open) => {
@@ -162,7 +162,7 @@ export default function Home() {
bodyClass={""}
open={bdstChgProps.isOpen}
body={
<BdstChgModal chgBuilding={bdstChgProps.chgBuilding}></BdstChgModal>
<BdstChgModal chgBuilding={bdstChgProps.chgBuilding} ></BdstChgModal>
}
onOpenChange={(open) => {
setBdstChgProps({ ...bdstChgProps, isOpen: open });
@@ -65,7 +65,7 @@ export default function RswtSttn() {
}, [rswtSttn.isInit, rswtSttnInit]);
useEffect(() => {
if (rswtSttnRes.length > 0 && code.cityCd.length > 0) {
if (rswtSttnRes?.length > 0 && code.cityCd.length > 0) {
const data = tableData(rswtSttnRes, { code });
const chData = data
.map((item: customRswtBdst) => ({
@@ -82,6 +82,7 @@ export const comFcltSearchAction: (set: any) => comFcltActions = (set: any) => {
addr: item.addr,
fctCpc: item.fctCpc,
}));
set((state: comFcltState) => ({
...state,
searchFclts: searchFclts,
@@ -17,7 +17,7 @@ export const chkgdRegExcel = z.object({
},
{ message: "현재 년도보다 높은 수를 입력할 수 없습니다." }
),
file: z.instanceof(File).optional(),
file: z.instanceof(File).nullish(),
});
export const guideDetail = z.object({
@@ -1,10 +1,63 @@
import { getFetch, postFectch, putFectch } from "@/components/lib/api";
import { upHeadList, type chgBuilding} from "../rainReg";
import { upHeadList, type chgBuilding } from "../rainReg";
import { z } from "zod";
import { makeMdfr, makeRstr } from "../bdstReg";
import {
baseReuseBuildingSchema,
chgBuildingSchema,
validationMessages,
} from "../rainReg/type"; // Import baseReuseBuildingSchema, chgBuildingSchema, validationMessages
import type { TableUpperProps } from "@/app/store/common/type/header";
const hdtflObjSchema = z.object({
htdflFcltPk: z.number().nullish(),
bdstRuseFcltFk: z.number().nullish(),
etgtType: z
.string()
.min(1, { message: validationMessages.required("에너지원 종류") }),
fclCpct: z
.number()
.positive({ message: validationMessages.required("시설 용량") }),
plwtYn: z.enum(["Y", "N"]).default("Y"),
orgwtWtritSpt: z
.number()
.positive({ message: validationMessages.required("원수 취수량") }),
orgwtOcrnProcs: z
.string()
.min(1, { message: validationMessages.required("원수 발생 공정") }),
wtritEntrcTp: z
.number()
.max(100, { message: "온도는 100이상이 될 수 없습니다." })
.default(0), // Changed max to 100 based on message
amtgnr: z.number().default(0),
prcsProcsYn: z.enum(["Y", "N"]).default("Y"),
prcsFcltCpct: z
.number()
.positive({ message: validationMessages.required("처리시설 용량") }),
fmYn: z.string().default(""),
utztnCpct: z
.number()
.positive({ message: validationMessages.required("이용 용량") }),
utztnUsg: z
.string()
.min(1, { message: validationMessages.required("이용 용도") }),
grssClfg: z.number().default(0), // Measurements, 0 is fine as default
combResdch: z.number().default(0), // Measurements, 0 is fine as default
trbt: z.number().default(0), // Measurements, 0 is fine as default
bod: z.number().default(0), // Measurements, 0 is fine as default
smll: z.number().default(0), // Measurements, 0 is fine as default
crmty: z.number().default(0), // Measurements, 0 is fine as default
tn: z.number().default(0), // Measurements, 0 is fine as default
tph: z.number().default(0), // Measurements, 0 is fine as default
ph: z.number().default(0), // Measurements, 0 is fine as default
clIonQnt: z.number().default(0), // Measurements, 0 is fine as default
cdtnv: z.number().default(0), // Measurements, 0 is fine as default
emsnExitTp: z.number().default(0), // Measurements, 0 is fine as default
rgtr: z.string().nullish().default(null),
mdfr: z.string().nullish().default(null),
});
export const hdtflZod = z.object({
bdstInfoPk: z.number().nullish(),
ctpvCd: z.number().default(0),
@@ -14,85 +67,25 @@ export const hdtflZod = z.object({
daddr: z.string().default(" "),
bdstNm: z.string().default(" "),
frstArchPrmsnDt: z.date().default(() => new Date()),
archChgPrmsnDt: z.date().default(() => new Date()).nullish() ,
archChgPrmsnDt: z
.date()
.default(() => new Date())
.nullish(),
archArea: z.number().default(0),
archChgArea: z.number().nullish().default(0),
rgtr:z.string().nullish().default(null),
mdfr:z.string().nullish().default(null),
rgtr: z.string().nullish().default(null),
mdfr: z.string().nullish().default(null),
reuseBuilding: z
.array(
z.object({
bdstRuseFcltPk: z.number().nullish(),
bdstInfoFk: z.number().nullish(),
instlCmptnDt: z.coerce.date().default(() => new Date()),
instlCst: z.number().nullish().default(0),
operMngCd: z.string().nullish().default(""),
anlOperCst: z.number().default(0),
instlCstSprtYn: z.enum(["Y", "N"]).nullish(),
instlCstSprtAmt: z.number().nullish().default(0),
grntchBdstCertNo: z.string().nullish().default(""),
grntchBdstCertGrd: z.string().nullish().default(""),
ruseFcltFlg: z.string().nullish().default(""),
bdstUsgCd: z.number().default(0),
lawTrgtYn: z.enum(["Y", "N"]).nullish(),
rgtr:z.string().nullish().default(null),
mdfr:z.string().nullish().default(null),
// htdflObj 매핑
htdflObj: z
.object({
htdflFcltPk: z.number().nullish(),
bdstRuseFcltFk: z.number().nullish(),
etgtType: z.string().default(""),
fclCpct: z.number().default(0),
plwtYn: z.enum(["Y", "N"]),
orgwtWtritSpt: z.number().default(0),
orgwtOcrnProcs: z.string().default(""),
wtritEntrcTp: z.number().max(4, "온도는 100이상이 될 수 없습니다.")
.default(0),
amtgnr: z.number().default(0),
prcsProcsYn: z.enum(["Y", "N"]),
prcsFcltCpct: z.number().default(0),
fmYn: z.string().default(""),
utztnCpct: z.number().default(0),
utztnUsg: z.string().default(""),
grssClfg: z.number().default(0),
combResdch: z.number().default(0),
trbt: z.number().default(0),
bod: z.number().default(0),
smll: z.number().default(0),
crmty: z.number().default(0),
tn: z.number().default(0),
tph: z.number().default(0),
ph: z.number().default(0),
clIonQnt: z.number().default(0),
cdtnv: z.number().default(0),
emsnExitTp: z.number().default(0),
rgtr:z.string().nullish().default(null),
mdfr:z.string().nullish().default(null),
})
.default({
plwtYn: "N",
prcsProcsYn: "N",
}),
})
)
.default([]),
chgBuilding: z
.array(
z.object({
bdstInfoFk: z.number().nullish(),
archChgPrmsnDt: z.coerce.date() ,
archChgArea: z.string().default("-").nullish(),
bdstChgNm: z.string().default(""),
rgtr:z.string().nullish().default(null),
mdfr:z.string().nullish().default(null),
baseReuseBuildingSchema.extend({
htdflObj: hdtflObjSchema,
})
)
.default([]),
chgBuilding: z.array(chgBuildingSchema).default([]), // Use imported chgBuildingSchema
});
export type customHtdflBdst = z.infer<typeof hdtflZod>
export type customHtdflBdst = z.infer<typeof hdtflZod>;
export type htdflFacState = {
htdfl: {
@@ -102,11 +95,10 @@ export type htdflFacState = {
};
};
type htdflFacSActions = {
initHtdfl: (code: string ) => void;
htdflBdstUpdate:(data: customHtdflBdst, code:string|number) => void;
htdflBdstReg:(data: customHtdflBdst, code:string|number) => void;
getHtdflBdstData:(pageNation:number, code:string|number) =>void;
initHtdfl: (code: string) => void;
htdflBdstUpdate: (data: customHtdflBdst, code: string | number) => void;
htdflBdstReg: (data: customHtdflBdst, code: string | number) => void;
getHtdflBdstData: (pageNation: number, code: string | number) => void;
};
const htdflRes: customHtdflBdst[] = [];
@@ -121,25 +113,22 @@ export const htdflFacStateExport: htdflFacState = {
},
};
const initFetch = async (code: string | number) => {
const rswtRes = await getFetch("wr", `/facilityList/1/20/${code}`);
const initFetch = async (code:string|number) => {
const rswtRes = await getFetch(
"wr",
`/facilityList/1/20/${code}`)
return rswtRes;
};
export const htdflFacActionsExport: (set: any) => htdflFacSActions = (
set: any
) => {
return {
getHtdflBdstData: async (pageNation: number, code: string | number) => {
getHtdflBdstData: async (pageNation: number, code: string | number) => {
try {
const rswtRes = await getFetch(
"wr",
`/drain/facilityList/${pageNation}/20/${code}`
);
console.log(pageNation)
console.log(pageNation);
// 여기서 상태 업데이트
return set((state: htdflFacState) => ({
...state,
@@ -154,8 +143,7 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = (
}
},
initHtdfl: async (code: string ) => {
initHtdfl: async (code: string) => {
try {
const htdflRes = await initFetch(code);
set((state: htdflFacState) => {
@@ -172,9 +160,9 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = (
console.log(error);
}
},
htdflBdstUpdate: async (data: customHtdflBdst, code:string|number) => {
htdflBdstUpdate: async (data: customHtdflBdst, code: string | number) => {
try {
const chData=await makeMdfr(data)
const chData = await makeMdfr(data);
const res = await putFectch("/drain/facility", data);
if (res.ok) {
alert(" 수정이 완료되었습니다.");
@@ -196,18 +184,17 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = (
);
}
},
htdflBdstReg: async (data: customHtdflBdst , code:string|number) => {
htdflBdstReg: async (data: customHtdflBdst, code: string | number) => {
try {
const chData=await makeRstr(data)
const chData = await makeRstr(data);
const res = await postFectch("/drain/facility", data);
if (res.ok) {
if(data.chgBuilding.length > 0){
if (data.chgBuilding.length > 0) {
await res
.json()
.then((resData: any) => {
const chPK = resData?.data?.bdstInfoPk;
const makeCh = resData.data.chgBuilding.map(
(item: chgBuilding) => ({
...item,
@@ -222,9 +209,8 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = (
alert("등록이 완료되었습니다.");
}
});
}else{
} else {
alert("등록이 완료되었습니다.");
}
}
const htdflRes = await initFetch(code);
@@ -238,7 +224,7 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = (
}));
} catch (errorReg) {
alert("등록 오류입니다.");
}
}
},
};
};
@@ -102,12 +102,10 @@ const checkWrRegList = async () => {
const hasuListFetch = async (page: string, sggOrCity: string, searchTxt?:string) => {
const checkWrRegListRes = await checkWrRegList();
console.log(' checkWrRegListRes ', checkWrRegListRes)
let url = `/prcsFclt/ruseInfo/${page ? page : 1}/10/A/${sggOrCity? sggOrCity : "11"}/0/1/SW`;
const data = checkWrRegListRes.filter((item:string) => item !== "")
console.log(' checkWrRegListRes ' )
if(searchTxt){
url= url +`/${searchTxt}`
@@ -177,6 +175,7 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
try {
const pbnstHasuRes = await hasuListFetch(page, sggOrCity);
return set((state: pubFacState) => ({
...state,
pbnst: {
@@ -22,9 +22,6 @@ const rainyObjSchema = z.object({
});
const rainBuildingSchema = baseReuseBuildingSchema.extend({
rainyObj: rainyObjSchema,
})
@@ -1,10 +1,32 @@
import { getFetch, postFectch, putFectch } from "@/components/lib/api";
import { upHeadList, type chgBuilding } from "../rainReg";
import { upHeadList, type chgBuilding } from "../rainReg";
import type { TableUpperProps } from "@/app/store/common/type/header";
import { z } from "zod";
import { baseReuseBuildingSchema, chgBuildingSchema, validationMessages } from "../rainReg/type";
import { makeMdfr, makeRstr } from "../bdstReg";
const rswtrObjSchema = z.object({
bdstRuseFcltFk: z.number().nullish(),
wtrUsqty: z.number({ required_error: validationMessages.required("물사용량(m³)") }).positive({ message: validationMessages.required("물사용량(m³)") }),
oblgTrgtWtrUsqty: z.number().nullish().default(0),
orgwtKndCd: z.string({ required_error: validationMessages.requiredSelect("원수 종류") }).min(1, { message: validationMessages.requiredSelect("원수 종류") }),
filtrTrtmfctCpct: z.number({ required_error: validationMessages.required("처리시설 용량") }).positive({ message: validationMessages.required("처리시설 용량") }),
filtrTrtmfctProcs: z.string({ required_error: validationMessages.required("주요처리공정") }).min(1, { message: validationMessages.required("주요처리공정") }),
filtrTrtmfctUsgCd: z.string().default("").nullish(),
rswtrUsqty: z.number().default(0),
altUswtrUtztnYn: z.enum(["Y", "N"]).nullish(), //안쓰는값
altUswtrKndCd: z.string().default("").nullish(), //안쓰는값
altUswtrUtztnQnt: z.number().default(0), //안쓰는값
altUswtrIdntyMthdCd: z.string().default("").nullish(), //안쓰는값
rgtr: z.string().nullish().default(null),
mdfr: z.string().nullish().default(null),
});
const rswtBuildingSchema = baseReuseBuildingSchema.extend({
rswtrObj: rswtrObjSchema,
})
export const rswtZod = z.object({
bdstInfoPk: z.number().nullish(),
ctpvCd: z.number().default(0),
@@ -21,73 +43,8 @@ export const rswtZod = z.object({
bdstUsgText: z.string().default(""),
cityText: z.string().default(""),
sggText: z.string().default(""),
reuseBuilding: z
.array(
z.object({
bdstRuseFcltPk: z.number().nullish(),
bdstInfoFk: z.number().nullish(),
instlCmptnDt: z.coerce.date().default(() => new Date()),
instlCst: z.number().default(0),
operMngCd: z.string().nullish().default(""),
anlOperCst: z.number().default(0),
instlCstSprtYn: z.enum(["Y", "N"]),
instlCstSprtAmt: z.number().default(0),
grntchBdstCertYn: z.enum(["Y", "N"]),
grntchBdstCertYr: z
.string()
.default("")
.refine((year) => year.length === 0 || /^\d{4}$/.test(year), {
message: "4자리 연도만 입력하세요",
}),
grntchBdstCertNo: z.string().default(""),
grntchBdstCertGrd: z.string().default(""),
crgRdctYn: z.string().default("N").nullish(), // 안쓰는 값
crgRdctBss: z.string().default("").nullish(), /// 안쓰는 값
crgRdctAmt: z.number().default(0), // 안쓰는 값
ruseFcltFlg: z.string().default(""),
bdstUsgCd: z.number().default(0),
lawTrgtYn: z.string().default(""),
rgtr: z.string().nullish().default(null),
mdfr: z.string().nullish().default(null),
rswtrObj: z
.object({
bdstRuseFcltFk: z.number().nullish(),
wtrUsqty: z.number().default(0),
oblgTrgtWtrUsqty: z.number().nullish().default(0),
orgwtKndCd: z.string().default(""),
filtrTrtmfctCpct: z.number().default(0),
filtrTrtmfctProcs: z.string().default(""),
filtrTrtmfctUsgCd: z.string().default("").nullish(),
rswtrUsqty: z.number().default(0),
altUswtrUtztnYn: z.enum(["Y", "N"]).nullish(), //안쓰는값
altUswtrKndCd: z.string().default("").nullish(), //안쓰는값
altUswtrUtztnQnt: z.number().default(0), //안쓰는값
altUswtrIdntyMthdCd: z.string().default("").nullish(), //안쓰는값
rgtr: z.string().nullish().default(null),
mdfr: z.string().nullish().default(null),
})
.default({}),
})
)
.default([]),
chgBuilding: z
.array(
z.object({
bdstInfoFk: z.number().nullish(),
archChgPrmsnDt: z.coerce.date(),
archChgArea: z.string().default("-"),
bdstChgNm: z.string().default(""),
rgtr: z.string().nullish().default(null),
mdfr: z.string().nullish().default(null),
})
)
.default([]),
reuseBuilding: z.array(rswtBuildingSchema).default([]),
chgBuilding: z.array(chgBuildingSchema).default([]),
});
export type customRswtBdst = z.infer<typeof rswtZod>;
@@ -247,23 +247,6 @@ export function PbnstBuseForm({
setSumCpct(sum);
}, [form.formState, indexMng, indexBiz]);
// // 차수별 정보(fcBizs)가 하나도 없을 경우 플레이스홀더 UI를 보여줍니다.
// if (fcBizs.length === 0) {
// return (
// <div className="!mx-[1.6rem] !p-0r flex flex-col items-center justify-center h-64 text-gray-500 border rounded-md">
// <p className="text-lg">등록된 차수별 정보가 없습니다.</p>
// <p className="text-sm mb-4">아래 버튼을 눌러 신규 차수 정보를 등록해주세요.</p>
// <Button
// onClick={addRuseFcltObj}
// type="button"
// size={commonButonSize}
// className="mt-2"
// >
// 신규 차수 등록
// </Button>
// </div>
// );
// }
const pbnstBiz = form.watch(`items.${indexMng}.fcBizs`);
return (
@@ -672,7 +655,7 @@ export function PbnstBuseForm({
<UiModal
open={isFcOrgwts}
onOpenChange={(open: boolean) => setIsFcOrgwts(open)}
bodyClass={"w-[50vw] max-w-none h-[90vh]"}
bodyClass={"w-[70vw] max-w-none max-h-[90vh] overflow-y-auto"}
body={
<FcOrgwtsRegModal
closeModal={() => {
@@ -28,10 +28,12 @@ import BdstReg from "../bdstReg/bdstReg";
export default function RswtrRegForm({
rswtForm,
isModal,
setIsModal,
readonly = false
}: {
rswtForm: customRswtBdst;
isModal: boolean;
setIsModal?: (isModal: boolean) => void;
readonly?: boolean;
}) {
const { rswt, code, rswtBdstUpdate, rswtBdstReg } = useWrStore(
@@ -54,6 +56,7 @@ export default function RswtrRegForm({
const onSubmit = (values: z.infer<typeof rswtZod>) => {
if (isModal) {
rswtBdstReg(values, "41");
if(setIsModal) setIsModal(false)
} else {
rswtBdstUpdate(values, "41");
}
@@ -61,28 +64,29 @@ export default function RswtrRegForm({
const cleanRswtData = (raw: any): z.infer<typeof rswtZod> => {
return {
...raw,
frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null),
frstArchPrmsnDt: raw?.frstArchPrmsnDt
? new Date(raw.frstArchPrmsnDt)
: new Date(), // raw?.frstArchPrmsnDt가 유효하지 않으면 현재 날짜로 초기화
archChgPrmsnDt: raw?.archChgPrmsnDt
? new Date(raw?.archChgPrmsnDt)
: null,
reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({
...item,
instlCmptnDt: new Date(item?.instlCmptnDt),
operMngCd: item?.operMngCd ?? "", // null → ""
grntchBdstCertYr:
typeof item?.grntchBdstCertYr === "string"
? item?.grntchBdstCertYr
: "", // null → ""
rswtrObj: {
instlCmptnDt: item?.instlCmptnDt
? new Date(item.instlCmptnDt)
: new Date(), // item?.instlCmptnDt가 유효하지 않으면 현재 날짜로 초기화
operMngCd: item?.operMngCd ?? "", // null → ""
instlCstSprtYn: item?.instlCstSprtYn ?? "Y", // null → "Y"
grntchBdstCertYn:item?.grntchBdstCertYn??"Y",
rswtrObj: {
...item?.rswtrObj,
filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn ?? "Y",
},
})),
};
};
useEffect(()=>{
console.log("rswtForm:", rswtForm)
}, [rswtForm])
useEffect(() => {
console.log("form errors:", form.formState.errors);
@@ -29,9 +29,10 @@ export type objSearchType = {
interface BdstRegProps<T extends MoType> {
moType: T;
setIsModal?: (open: boolean) => void;
}
export function BdstReg<T extends MoType>({ moType }: BdstRegProps<T>) {
export function BdstReg<T extends MoType>({ moType,setIsModal }: BdstRegProps<T>) {
const { rain, code, bdst,findBdst } = useWrStore((state) => {
return state;
});
@@ -143,7 +144,7 @@ export function BdstReg<T extends MoType>({ moType }: BdstRegProps<T>) {
{ moType === 'RNB'?
<RainRegForm rainForm={rainBdstForm as customRainBdst} isModal={true} />
:moType==='RST'
? <RswtrRegForm rswtForm={rainBdstForm as customRswtBdst} isModal={true}></RswtrRegForm>
? <RswtrRegForm rswtForm={rainBdstForm as customRswtBdst} isModal={true} setIsModal={(open) =>{setIsModal}}></RswtrRegForm>
:<HtdflRegForm htdflForm={rainBdstForm as unknown as customHtdflBdst} isModal={true}></HtdflRegForm>
}
@@ -1,6 +1,10 @@
"use client";
import { useWrStore } from "@/app/store";
import type { pubHasuType, fcBizType, fcOrgwtType,} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import type {
pubHasuType,
fcBizType,
fcOrgwtType,
} from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import type { TableUpperProps } from "@/app/store/common/type/header";
import { tableData } from "@/components/lib/func/commom";
import RuseMngTable from "@/components/ui/table/pbnstReg/ruseMngTable/ruseMngTable";
@@ -11,138 +15,161 @@ import { Input } from "@common/components/ui";
import { ArrowLeft, ArrowRight } from "lucide-react";
import { useEffect, useRef, useState } from "react";
export default function FcOrgwtsRegModal({ onRightSet, closeModal }: { onRightSet?: (rightData:fcOrgwtType[]) => void,closeModal?:(val:boolean) =>void }) {
const { code, pbnst, getSwtrmtFcltNm, searchFclts } = useWrStore((state) => state);
export default function FcOrgwtsRegModal({
onRightSet,
closeModal,
}: {
onRightSet?: (rightData: fcOrgwtType[]) => void;
closeModal?: (val: boolean) => void;
}) {
const { code, pbnst, getSwtrmtFcltNm, searchFclts } = useWrStore(
(state) => state
);
const { fcOrgwtsHeadList, pbnstHasuRes } = pbnst;
const [bdstTable, setbdstTable] = useState<pubHasuType[]>();
const rightRef = useRef<pubHasuType[] | null>([] as pubHasuType[]);
const leftRef = useRef<pubHasuType[] | null>([] as pubHasuType[]);
const [rightData, setRightData] = useState<pubHasuType[]>();
const [inputVal, setInputVal] = useState<string>("");
useEffect(()=>{
if(Array.isArray(searchFclts)){
useEffect(() => {
if (Array.isArray(searchFclts)) {
const filteredData = searchFclts.filter((item) => {
if(rightData){
if(rightData.find(rightItem =>rightItem.bscFctCd===item.bscFctCd))
if (rightData) {
if (
rightData.find((rightItem) => rightItem.bscFctCd === item.bscFctCd)
)
return false;
}
return true
})
setbdstTable(Array.isArray(filteredData) ? [...filteredData] : [])
return true;
});
setbdstTable(Array.isArray(filteredData) ? [...filteredData] : []);
}
}, [searchFclts])
}, [searchFclts]);
useEffect(() => {
if (code.sggCd.length > 0 && pbnstHasuRes.length > 0) {
setbdstTable(tableData(pbnstHasuRes, { code }));
console.log('bdstTable ', bdstTable)
console.log("bdstTable ", bdstTable);
}
}, [code, pbnstHasuRes]);
useEffect(() => {
console.log('rightData ', rightData)
if(onRightSet)
onRightSet(
(rightData as pubHasuType[])?.map(item => ({
swtrmtFcltFk: item.bscFctCd, // 임시값, 실제 데이터에 맞게 수정
orgwtWtritQnt: Number(item.fctCpc), // 임시값, 실제 데이터에 맞게 수정
tmsYn: "N", // 임시값, 실제 데이터에 맞게 수정
dschgFmYn: "N", // 임시값, 실제 데이터에 맞게 수정
dtlPstnExpln: item.addr, // 임시값, 실제 데이터에 맞게 수정
choose: false, // 임시값, 실제 데이터에 맞게 수정
// 필요한 경우 다른 속성들도 추가
})) as unknown as fcOrgwtType[]
);
}, [rightData])
console.log("rightData ", rightData);
if (onRightSet)
onRightSet(
(rightData as pubHasuType[])?.map((item) => ({
swtrmtFcltFk: item.bscFctCd, // 임시값, 실제 데이터에 맞게 수정
orgwtWtritQnt: Number(item.fctCpc), // 임시값, 실제 데이터에 맞게 수정
tmsYn: "N", // 임시값, 실제 데이터에 맞게 수정
dschgFmYn: "N", // 임시값, 실제 데이터에 맞게 수정
dtlPstnExpln: item.addr, // 임시값, 실제 데이터에 맞게 수정
choose: false, // 임시값, 실제 데이터에 맞게 수정
// 필요한 경우 다른 속성들도 추가
})) as unknown as fcOrgwtType[]
);
}, [rightData]);
return (
<>
<div className="flex flex-col h-full">
<div className="flex items-center justify-between">
<Title title={"취수하수처리장 추가"} />
<Button className="m-2" size={"sm"}
onClick={() => {closeModal && closeModal(false) }}>저장</Button>
</div>
<div className="flex flex-col h-[80vh]">
<div className="shrink-0 p-4 border-b z-10 bg-white">
<div className="flex items-center">
<Title title="취수하수처리장 추가" />
{/* <Button size="sm" onClick={() => closeModal?.(false)}>저장</Button> */}
<div className="flex items-center gap-2 mt-8">
<div className="flex flex-row flex-grow gap-4 mt-4">
<div className="w-3/7">
<div className="flex items-center justify-between">
<Label className="w-full text-2xl text-gray-600 text-left">공공하수처리시설 목록</Label>
<div className="flex items-center">
<Label className="w-full text-xl text-gray-600 text-right">시설명</Label>
<Input className="w-1/2 !text-xl ml-2" type="text" value={inputVal} onChange={(e) => {setInputVal(e.target.value)}} />
<Button className="m-2" size={"sm"} onClick={() => getSwtrmtFcltNm(inputVal)}>조회</Button>
</div>
</div>
<div className="border-t border-gray-300 my-4"></div>
<RuseMngTable
tableData={bdstTable || []}
headList={fcOrgwtsHeadList}
upHeadList={[]} // TODO: Replace with actual upHeadList data if available
ref={leftRef}
/>
</div>
<div className="flex flex-col items-center justify-center w-1/7 gap-2">
<ArrowRight
type="button"
onClick={() => {
if (leftRef && typeof leftRef === "object" && "current" in leftRef) {
const checked = leftRef.current?.filter((item) => ((item as unknown as pubHasuType)?.choose))
const unchecked = leftRef.current?.filter((item) => !(item as unknown as pubHasuType)?.choose)
if (checked && checked.length > 0) {
checked.forEach(item=>(item as unknown as pubHasuType).choose = false)
setRightData([...(checked as unknown as pubHasuType[]),
...((rightRef.current ?? []) as unknown as pubHasuType[])
])
}
if (unchecked && unchecked.length > 0) {
setbdstTable([...(unchecked as unknown as pubHasuType[])])
}else{
setbdstTable([])
}
}
}
}
className="w-12 h-12 text-gray-500 cursor-pointer"
/>
<ArrowLeft
type="button"
onClick={() => {
if (rightRef && typeof rightRef === "object" && "current" in rightRef) {
const checked = rightRef.current?.filter((item) => ((item as unknown as pubHasuType)?.choose))
const unchecked = rightRef.current?.filter((item) => !(item as unknown as pubHasuType)?.choose)
if (checked && checked.length > 0) {
checked.forEach(item=>(item as unknown as pubHasuType).choose = false)
setbdstTable([
...(checked as unknown as pubHasuType[]),
...((leftRef.current ?? []) as unknown as pubHasuType[])
])
}
if (unchecked && unchecked.length > 0) {
setRightData([...(unchecked as unknown as pubHasuType[])])
}else{
setRightData([])
}
}
}}
className="w-12 h-12 text-gray-500 cursor-pointer mt-5" />
</div>
{/* Right Table container: Takes 2/5 of the width */}
<div className="w-3/7">
<Label className="w-full text-2xl text-gray-600 text-right">취수하수처리장 목록</Label>
<div className="border-t border-gray-300 my-4"></div>
<RuseMngTable
tableData={rightData || []}
headList={fcOrgwtsHeadList}
upHeadList={[]} // TODO: Replace with actual upHeadList data if available
ref={rightRef}
/>
<Label className="text-xl text-gray-600">시설명</Label>
<Input
className="w-48 text-xl"
type="text"
value={inputVal}
onChange={(e) => setInputVal(e.target.value)}
/>
<Button size="sm" onClick={() => getSwtrmtFcltNm(inputVal)}>
조회
</Button>
</div>
</div>
</div>
</>
{/* <div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Label className="text-xl text-gray-600">시설명</Label>
<Input
className="w-48 text-xl"
type="text"
value={inputVal}
onChange={(e) => setInputVal(e.target.value)}
/>
<Button size="sm" onClick={() => getSwtrmtFcltNm(inputVal)}>조회</Button>
</div>
</div>
</div> */}
{/* 테이블 영역 */}
<div className="flex flex-row gap-4 flex-grow p-4 overflow-x-auto">
{/* 왼쪽 테이블 */}
<div className="w-5/12 min-w-[400px]">
<Label className="text-2xl text-gray-600 mb-2 block text-right">
공공하수처리시설 목록
</Label>
<RuseMngTable
tableData={bdstTable || []}
headList={fcOrgwtsHeadList}
upHeadList={[]}
ref={leftRef}
/>
</div>
{/* 중간 이동 버튼 */}
<div className="flex flex-col justify-center items-center gap-4 w-[200px] min-w-[120px]">
<ArrowRight
onClick={() => {
const checked = leftRef.current?.filter((item) => item.choose);
const unchecked = leftRef.current?.filter((item) => !item.choose);
if (checked?.length) {
checked.forEach((item) => (item.choose = false));
setRightData([
...(checked as pubHasuType[]),
...(rightRef.current ?? []),
]);
}
setbdstTable(unchecked ?? []);
}}
className="w-8 h-8 text-gray-500 cursor-pointer"
/>
<ArrowLeft
onClick={() => {
const checked = rightRef.current?.filter((item) => item.choose);
const unchecked = rightRef.current?.filter(
(item) => !item.choose
);
if (checked?.length) {
checked.forEach((item) => (item.choose = false));
setbdstTable([
...(checked as pubHasuType[]),
...(leftRef.current ?? []),
]);
}
setRightData(unchecked ?? []);
}}
className="w-8 h-8 text-gray-500 cursor-pointer"
/>
</div>
{/* 오른쪽 테이블 */}
<div className="w-5/12 min-w-[400px]">
<Label className="text-2xl text-gray-600 mb-2 block text-right">
취수하수처리장 목록
</Label>
<RuseMngTable
tableData={rightData || []}
headList={fcOrgwtsHeadList}
upHeadList={[]}
ref={rightRef}
/>
</div>
</div>
</div>
);
}
@@ -20,13 +20,13 @@ import { useFieldArray, type UseFormReturn } from "react-hook-form";
import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg";
export function PbnstRegModal({
data,
form,
setPbnstInsert
}: {
data: pubHasuType;
form: UseFormReturn<{ items: PbnstRegArrrayType }>;
setPbnstInsert: (data: customPbnst) => void
}) {
const { pbnst, pbnstInsert } = useWrStore((state) => {
const { pbnstInsert } = useWrStore((state) => {
return state;
});
const [pbnstBdstForm, setPbnstBdstForm] = useState<customPbnst>(
@@ -35,40 +35,30 @@ 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]){
pbnstInsert(data[0])
}
};
if (data && data[0]) {
// pbnstInsert(data[0]);
setPbnstInsert(data[0])
}
};
const handleTabChange = (value: string) => {
setCurrentTab(value);
};
useEffect(() => {
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
};
useEffect(() => {
if (data) {
const firstForm = form.getValues("items");
setIndexMng(firstForm.length - 1) }
}, [data, form]);
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
useEffect(() => {
console.log(' form.getValues() ' ,form.getValues())
}, [form.getValues()]);
const firstForm = form.getValues("items");
if (firstForm) {
setIndexMng(firstForm.length - 1);
}
}, [form]);
return (
<>
<h1 className="text-2xl font-medium text-label pl-8">
@@ -86,7 +76,7 @@ export function PbnstRegModal({
차수별정보
</TabsTrigger>
<Button
onClick={ form.handleSubmit(onSubmit)}
onClick={form.handleSubmit(onSubmit)}
size={"sm"}
className="!justify-end"
>
@@ -104,7 +94,7 @@ export function PbnstRegModal({
</TabsContent>
<TabsContent className="max-h-full" value="buse">
<PbnstBuseForm
form={form}
form={form}
indexMng={indexMng}
indexBiz={indexBiz}
setIndexBiz={setIndexBiz}
@@ -0,0 +1,82 @@
'use client';
import { cn } from "../../lib/utils";
import {
Popover,
PopoverTrigger,
PopoverContent,
Button,
} from "@common/components/ui";
import { ko } from "date-fns/locale";
import { format } from "date-fns";
import { useState } from "react";
import { MonthPicker } from "./mothPicker";
import { CalendarIcon } from "lucide-react";
type MonthPickerProps = {
field: { value: string; onChange: (date: string) => void }; // yyyyMM 문자열
selectClass?: string;
readOnly?: boolean;
};
// yyyyMM → Date 객체
const parseYYYYMM = (value: string) => {
const year = Number(value.substring(0, 4));
const month = Number(value.substring(4, 6)) - 1;
return new Date(year, month);
};
// Date 객체 → yyyyMM 문자열
const formatDateToYYYYMM = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
return `${year}${month}`;
};
export const DateMonthPicker = ({
field,
selectClass,
readOnly = false,
}: MonthPickerProps) => {
const [open, setOpen] = useState(false);
const parsedDate = field?.value ? parseYYYYMM(field.value) : new Date();
const handleSelect = (
date: Date | undefined,
shouldClosePopover?: boolean
) => {
if (!readOnly && date) {
field.onChange(formatDateToYYYYMM(date));
if (shouldClosePopover) setOpen(false);
}
};
return (
<Popover open={open && !readOnly} onOpenChange={readOnly ? undefined : setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
color="white"
disabled={readOnly}
className={cn(
selectClass ?? "w-[240px] justify-between pl-3 text-left font-normal",
!field?.value && "text-muted-foreground",
readOnly && "opacity-60 cursor-not-allowed"
)}
>
{parsedDate
?format(parsedDate, "yyyy년 M월", { locale: ko })
: "월을 선택하세요"}
<CalendarIcon className="ml-2 h-4 w-4" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<MonthPicker
currentMonth={parsedDate}
onMonthChange={handleSelect}
/>
</PopoverContent>
</Popover>
);
};
@@ -0,0 +1,125 @@
import { cn } from "../../lib/utils";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components/ui";
import {
eachMonthOfInterval,
endOfYear,
format,
isEqual,
isFuture,
parse,
startOfMonth,
startOfToday,
} from "date-fns";
import { ko } from "date-fns/locale";
function getStartOfCurrentMonth() {
return startOfMonth(startOfToday());
}
export type MonthPickerProps = {
currentMonth: Date;
onMonthChange: (
newMonth: Date | undefined,
shouldClosePopover?: boolean
) => void;
};
export function MonthPicker({ currentMonth, onMonthChange }: MonthPickerProps) {
const today = startOfToday();
const currentYear = today.getFullYear();
const yearFromProp = format(currentMonth, "yyyy");
const selectedYear = parseInt(yearFromProp);
// 현재 월의 시작 날짜
const fullYearStart = parse(yearFromProp, "yyyy", new Date());
const yearEnd =
selectedYear === currentYear ? today : endOfYear(fullYearStart);
const yearRange = Array.from({ length: 21 }, (_, i) => currentYear - 20 + i);
const months = eachMonthOfInterval({ start: fullYearStart, end: yearEnd });
const getMonthButtonClass = (month: Date) =>
cn(
"inline-flex h-9 w-16 items-center justify-center rounded-md p-0 text-sm font-normal ring-offset-white transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-selected:opacity-100 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:focus-visible:ring-slate-800",
isEqual(month, currentMonth) &&
"bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900",
!isEqual(month, currentMonth) &&
isEqual(month, getStartOfCurrentMonth()) &&
"bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50"
);
return (
<div className="p-3">
<div className="flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
<div className="space-y-4">
{/* 년도 셀렉트 */}
<div className="flex justify-center pt-1">
<Select
value={yearFromProp}
onValueChange={(selectedYearStr) => {
const selectedYearNum = parseInt(selectedYearStr);
const newDate = new Date(
selectedYearNum,
currentMonth.getMonth(),
1
);
onMonthChange(newDate, false);
}}
>
<SelectTrigger className="w-[120px] h-7">
<SelectValue placeholder="년도" />
</SelectTrigger>
<SelectContent>
{yearRange.map((year) => (
<SelectItem key={year} value={String(year)}>
{year}년
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{/* 월 그리드 */}
<div
className="grid w-full grid-cols-3 gap-2"
role="grid"
aria-labelledby="month-picker"
>
{months.map((month) => (
<div
key={month.toString()}
className="relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-slate-100 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md dark:[&:has([aria-selected])]:bg-slate-800"
role="presentation"
>
<button
name="month"
className={getMonthButtonClass(month)}
disabled={isFuture(month)}
role="gridcell"
tabIndex={-1}
type="button"
onClick={() => onMonthChange(month, true)}
>
<time dateTime={format(month, "yyyy-MM-dd")}>
{format(month, 'MMM', { locale: ko })}
</time>
</button>
</div>
))}
</div>
</div>
</div>
</div>
);
}
@@ -1,4 +1,5 @@
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from "@common/components";
import { cn } from "../../lib/utils";
type ModalType = {
bodyClass: string;
@@ -25,15 +26,20 @@ export function UiModal({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className={bodyClass}>
{noHeaderYn === 'N' && (
<DialogContent className={bodyClass}>
{noHeaderYn === 'N' && (
<DialogHeader>
<DialogDescription>
{headerTitle}
</DialogDescription>
</DialogHeader>
)}
{(open && body)}
{(open && body) ?
<div> {body}</div>:<></>
}
{noFooterYn === 'N' && (
<DialogFooter className="sm:justify-start">
{footerContent}
@@ -20,4 +20,5 @@ export * from "./table";
export * from "./table_div";
export * from "./title";
export * from "./sdmInsertForm";
export * from "./popover_ui"
export * from "./popover_ui";
export * from "./date_month_picker";