Merge branch 'iipbs/wr/dev' into iipbs/wr/form_fuction

This commit is contained in:
jyha00
2025-07-01 13:45:04 +09:00
130 changed files with 5632 additions and 847 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
const BASE_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || '';
const BASE_URL = 'http://con.mind-one.co.kr:11083/sewerage-service/api';
export const postData = async (url: string, data: object): Promise<any> => {
const options: RequestInit = {
@@ -1,4 +1,4 @@
import { VerticalTable } from '@/components/ui/verticalTable';
import { VerticalTable } from '../../../components/ui/verticalTable';
import React, { useState } from 'react'
import InputSection from '../InputSection';
import { handleInputChange } from '../common';
@@ -1,5 +1,5 @@
import Modal from '@/components/ui/modal';
import { VerticalTable } from '@/components/ui/verticalTable';
import Modal from '../../../components/ui/modal';
import { VerticalTable } from '../../../components/ui/verticalTable';
import { UiTable } from '@common/business_components/ui';
import { Button, TableCell } from '@common/components/ui';
import React, { useState } from 'react'
@@ -1,6 +1,6 @@
import React, { useState } from 'react'
import { type ChartConfig, MainChart, CHART_FORM } from "@common/business_components";
import Modal from '@/components/ui/modal';
import Modal from '../../../../components/ui/modal';
import { Button } from '@common/components/ui';
type ModalOpenProp = {
isModalOpen: boolean,
@@ -1,4 +1,4 @@
import { VerticalTable } from '@/components/ui/verticalTable';
import { VerticalTable } from '../../../components/ui/verticalTable';
import React from 'react'
const inputColumn = [
{ id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
@@ -1,5 +1,5 @@
// 하수맨홀
import { VerticalTable } from '@/components/ui/verticalTable';
import { VerticalTable } from '../../../components/ui/verticalTable';
import React from 'react'
const inputColumn = [
{ id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] },
@@ -1,4 +1,4 @@
import { VerticalTable } from '@/components/ui/verticalTable';
import { VerticalTable } from '../../../components/ui/verticalTable';
import React from 'react'
const inputColumn = [
{ id: "swtrmt_zone_cd", title: "처리구역", type: "input" },
+1
View File
@@ -106,6 +106,7 @@ const Map = () => {
setIsSearchModalOpen={setIsSearchModalOpen}
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={undefined}
/>
</div>
);
+201
View File
@@ -0,0 +1,201 @@
'use client'
import { getData, putData } from "../../../sn/app/components/lib/api";
import Modal from "../../../sn/app/components/ui/modal";
import { VerticalTable } from "../../../sn/app/components/ui/verticalTable";
import { useGisContext } from "@common/business_components/ui/map/Gis";
import { Button } from "@common/components/ui";
import { useEffect, useRef, useState } from "react";
import { getLayerContentComponent } from "../../../sn/app/map/components/common";
// TODO:
const FeatureClickEventPopup = ({ onClose, featureData }: { onClose: () => void, featureData: any }) => {
const { gisMap } = useGisContext() as unknown as { checkedLayerIds: React.MutableRefObject<number[]>; treeData: { lyrSn: number }[] | null; gisCore: any; gisMap: any; };
// const featureId = featureData.getId(); // 피처의 id 가져오기
const featureId = typeof featureData.getId === 'function'
? featureData.getId()
: featureData.fid;
const [layerNm, setLayerNm] = useState<string>(""); // 레이어 이름 상태
const [tableData, setTableData] = useState<any>(null); // 피처 데이터
const [mntrngsData, setMntrngsData] = useState(false); // 수량/수질 측정 정보
const [headlist, setHeadlist] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [headlist2, setHeadlist2] = useState<{ id: string; title: string; }[]>([]); // 컬럼리스트 변경상태
const [buttonChk, setButtonChk] = useState(true)
const [fetchData, setfetchData] = useState<any>(); // api 호출여부
const contentRef = useRef<HTMLDivElement>(null);
const [isModalOpen, setIsModalOpen] = useState(false); // 모달 열기 상태
const [chkMainSsc, setChkMainSsc] = useState(false); // 중점우수토실 여부
// 피쳐데이터의 현재 레이어 이름 가져오는 함수
const getSelectedLayers = () => {
let result = null;
const layers = gisMap.getLayers().getArray(); // gisMap에서 모든 레이어 가져오기
let featureLayer: { get: (key: string) => any } = { get: (key: string) => null }; // 피처가 속한 레이어를 찾기 위한 변수
layers.forEach((layer: any) => {
const source = layer.getSource();
if (source && source.hasFeature && featureData
&& typeof featureData.getId === 'function' && source.hasFeature(featureData)) {
featureLayer = layer; // 피처가 속한 레이어 찾기
}
});
// SearchModal 클릭 이벤트와 Gis 클릭 이벤트 구분하기
if (featureLayer.get('lyrNm') === null) {
if (featureData.lyrTypeNm === 'tb_il_swage_prcs_fclt') result = '하수처리시설';
else if (featureData.lyrTypeNm === 'tb_ms_swage_prcs_zone') result = '하수처리구역';
else if (featureData.lyrTypeNm === 'om_dt_untrt_swg_rain') result = '중점우수토실';
}
else result = featureLayer.get('lyrNm');
return result;
// return featureLayer.get('lyrNm'); // 레이어 이름 출력
};
useEffect(() => {
const fetchData = async () => {
if (featureId) {
const selectedLayerName = getSelectedLayers(); // 레이어 이름 가져오기
setLayerNm(selectedLayerName); // 상태 업데이트
const layerId = featureId.split('.')[0]
let data = await getData(`v1/fclts/${featureId}?lyrTypeNm=${layerId}`);
setfetchData(data)
switch (selectedLayerName) {
case '하수처리시설':
setButtonChk(false)
break;
case '하수처리구역':
setButtonChk(false)
break;
case '중점우수토실':
setButtonChk(false)
setChkMainSsc(true)
// 데이터 전처리: 합쳐진 필드 추가
data = data ? {
...data,
discharge_pipe_spec: `직경: ${data.pipe_diameter || '-'} / 높이: ${data.pipe_hg || '-'} / 길이: ${data.pipe_lt || '-'}`
} : {};
// 수량/수질측정 정보 데이터 호출
const additionalResponse = await getData(`v1/onsemiro/swgRain/${data.adst_dstr_cd}/${data.manage_no}/mntrngs`);
setMntrngsData(additionalResponse.content);
break;
case '우수토실':
setButtonChk(true)
break;
case '하수관로':
setButtonChk(true)
break;
case '오수펌프':
setButtonChk(true)
break;
case '빗물펌프':
setButtonChk(true)
break;
case '하수맨홀':
setButtonChk(true)
break;
default:
break;
}
setTableData(data);
};
}
fetchData();
}, [featureId]);
// 저장 시 모달 띄우기
const handleSaveClick = () => {
setIsModalOpen(true); // 모달 열기
};
const handleModalSave = () => {
setIsModalOpen(false); // 모달 닫기
btnClickEvent(); // 저장 이벤트 실행
};
const handleModalClose = () => {
setIsModalOpen(false); // 모달 닫기
};
const btnClickEvent = async () => {
const bodyData: Record<string, any> = {}
if (buttonChk && contentRef.current) {
const inputElements = contentRef.current.querySelectorAll('input');
const selectElements = contentRef.current.querySelectorAll('select');
// 카멜케이스 변환 함수
const toCamelCase = (str: string) =>
str.replace(/_([a-z])/g, (_, char) => char.toUpperCase());
// select 태그의 값 가져오기
selectElements.forEach((select) => {
const camelCaseId = toCamelCase(select.id);
bodyData[camelCaseId] = select.value;
});
// input 태그의 값 가져오기
inputElements.forEach((input) => {
const camelCaseId = toCamelCase(input.id);
bodyData[camelCaseId] = input.value;
});
if (layerNm === '하수관로') bodyData['lyrTypeNm'] = 'tb_ms_swage_ppl'
else if (layerNm === '우수토실') bodyData['lyrTypeNm'] = 'tb_ms_stofcbr'
else if (layerNm === '오수펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
else if (layerNm === '빗물펌프') bodyData['lyrTypeNm'] = 'tb_ms_swage_pmstn'
else if (layerNm === '하수맨홀') bodyData['lyrTypeNm'] = 'tb_ms_swage_mnhl'
bodyData['fid'] = featureId
const data = await putData(`v1/fclts/${featureId}`, bodyData);
if (data.status == 200) {
alert('저장되었습니다')
setTableData(data)
setfetchData(data)
}
}
}
type ContentComponentProps = {
featureData: any;
chkMainSsc: boolean;
additionalData: any;
};
const ContentComponent = layerNm ? getLayerContentComponent(layerNm) as React.ComponentType<ContentComponentProps> : null;
return (
<div id="popup" className="absolute top-0 right-0 w-[350px] h-[calc(100vh-10rem)] bg-white border-r border-gray-300 flex flex-col overflow-hidden transition-all duration-300 z-[102]">
<div className="card-header bg-primary flex items-center justify-between p-4">
<div className="flex mx-3 flex-col">
<h3 className="text-2xl font-medium text-white text-label">{layerNm} 정보</h3>
<div className="font-medium text-white text-label">(00구 00리 하수관로)</div>
</div>
<button
onClick={onClose}
className="text-white hover:text-gray-700 focus:outline-none"
aria-label="닫기"
>
✕
</button>
</div>
<div className="overflow-y-auto overflow-x-hidden flex-1" ref={contentRef}>
<VerticalTable
headName='LX 연계정보'
tableData={[tableData]}
headlist={headlist}
pageSize={20}
total={0}
children={undefined}
cellClick={undefined}
onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}
/>
<div className="w-full mx-5 flex items-center justify-end space-x-2">
{buttonChk ? <Button onClick={handleSaveClick} className="px-4 mr-5 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700">
저장
</Button> : null}
</div>
<Modal isModalOpen={isModalOpen} title="자체입력정보 저장" setIsModalOpen={setIsModalOpen} closeModal={handleModalClose} saveModal={handleModalSave}><div className="text-lg">정보를 저장하시겠습니까?</div></Modal>
{ContentComponent &&
<ContentComponent
featureData={fetchData}
chkMainSsc={chkMainSsc}
additionalData={mntrngsData}
/>
}
</div>
</div>
)
}
export default FeatureClickEventPopup;
+621
View File
@@ -0,0 +1,621 @@
"use client";
import {
Form,
FormField,
FormUi,
SelectBox,
} from "@common/business_components/ui";
import { Button, Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useWrStore } from "@/app/store";
import { selectClass } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import {
hdtflZod,
type customHtdflBdst,
} from "@/app/store/ruseFcltMng/fcltReg/htdflReg";
import { useEffect } from "react";
import BdstReg from "@/components/ui/form/fcltReg/bdstReg/bdstReg";
import {
formatCommaNumber,
handleCommaNumberChange,
} from "@/components/lib/func/commom";
import { formH1Class } from "@/components/lib/class/comClass";
export default function HtdflPrfmncSttnPopup({
htdflForm,
isModal,
}: {
htdflForm: customHtdflBdst;
isModal: boolean;
}) {
const { code, htdflBdstUpdate, htdflBdstReg } = useWrStore((state) => state);
const defaultValues: z.infer<typeof hdtflZod> = hdtflZod.parse({});
const form = useForm<z.infer<typeof hdtflZod>>({
resolver: zodResolver(hdtflZod),
defaultValues: defaultValues,
});
const onSubmit = (values: z.infer<typeof hdtflZod>) => {
if (isModal) {
htdflBdstReg(values, '41');
} else {
htdflBdstUpdate(values, '41');
}
};
const cleanHtdflData = (raw: any): z.infer<typeof hdtflZod> => {
return {
...raw,
frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null),
archChgPrmsnDt: raw?.archChgPrmsnDt
? new Date(raw?.archChgPrmsnDt)
: null,
chgBuilding: (raw?.chgBuilding || []).map((item: any) => ({
...item,
archChgPrmsnDt:
item?.archChgPrmsnDt === null
? new Date()
: new Date(item?.archChgPrmsnDt),
bdstChgNm: item?.bdstChgNm ?? "", // null → ""
})),
reuseBuilding: (raw?.reuseBuilding || []).map((item: any) => ({
...item,
instlCmptnDt: new Date(item?.instlCmptnDt),
htdflObj: {
...item?.htdflObj,
},
})),
};
};
useEffect(() => {
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
useEffect(() => {
if (hdtflZod) form.reset(cleanHtdflData(htdflForm));
}, [htdflForm]);
return (
<div className="!mx-[1.6rem] !p-0r">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* <div className="flex justify-between items-center mx-3"> */}
<div>
<div className="grid grid-cols-12 flex items-center card-header">
<h1 className="text-2xl font-medium text-label col-span-10">온배수 시설 정보</h1>
</div>
</div>
{isModal && htdflForm != undefined ? <BdstReg form={form} /> : <></>}
{isModal ? (
<h1 className="text-xl col-span-10 mt-4">
온배수 시설 정보{" "}
</h1>
) : (
<></>
)}
<div className="grid grid-cols-6 gap-2 mt-4">
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.etgtType"
render={({ field }) => (
<FormUi label="발전유형(코드정비후 수정)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
value={field.value ?? ""}
onChange={field.onChange}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.fclCpct" // 이름 이상함.
render={({ field }) => (
<FormUi label="설비용량">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.plwtYn"
render={({ field }) => (
<FormUi label="담수화 여부">
<SelectBox
className={""}
label={""}
selectClass={selectClass}
selectArray={code.yn}
onSelectValue={field.onChange}
value={field.value ?? code.yn[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className=" my-4">
<h1 className={formH1Class}>
온배수 시설 운영현황
</h1>
</div>
<div className="grid grid-cols-10 gap-1 mt-4">
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.orgwtWtritSpt"
render={({ field }) => (
<FormUi label="원수취수 지점(코드정비후 수정)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
value={field.value ?? 0}
onChange={(e) => {
field.onChange(Number(e.target.value));
}}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.orgwtOcrnProcs"
render={({ field }) => (
<FormUi label="원수발생 공정">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type=""
value={field.value ?? ""}
onChange={field.onChange}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.wtritEntrcTp"
render={({ field }) => (
<FormUi label="취수구 온도">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.amtgnr"
render={({ field }) => (
<FormUi label="발생량(m³/년)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.prcsProcsYn"
render={({ field }) => (
<FormUi label="처리공정">
<SelectBox
className={""}
label={""}
selectClass={selectClass}
selectArray={code.yn}
onSelectValue={field.onChange}
value={field.value ?? code.yn[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.prcsFcltCpct"
render={({ field }) => (
<FormUi label="처리시설 용량(m³/년)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.fmYn"
render={({ field }) => (
<FormUi label="유량계유무">
<SelectBox
className={""}
selectClass={selectClass}
label={""}
selectArray={code.yn}
onSelectValue={field.onChange}
value={field.value ?? code.yn[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.utztnCpct"
render={({ field }) => (
<FormUi label="이용량(m³/년)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.utztnUsg"
render={({ field }) => (
<FormUi label="이용용도">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
value={field.value ?? ""}
onChange={field.onChange}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className=" my-2">
<h1 className={formH1Class}>
요구수질
</h1>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.grssClfg"
render={({ field }) => (
<FormUi label="총대장균군">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.tn"
render={({ field }) => (
<FormUi label="총질소">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.combResdch"
render={({ field }) => (
<FormUi label="결합잔류염소">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-6 ">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.tph"
render={({ field }) => (
<FormUi label="총인">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.trbt"
render={({ field }) => (
<FormUi label="탁도">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.ph"
render={({ field }) => (
<FormUi label="수소이온농도">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.bod"
render={({ field }) => (
<FormUi label="생물학적 산소요구량">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.clIonQnt"
render={({ field }) => (
<FormUi label="염화물">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.smll"
render={({ field }) => (
<FormUi label="냄새">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.cdtnv"
render={({ field }) => (
<FormUi label="전기전도도">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.crmty"
render={({ field }) => (
<FormUi label="색도">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-6">
<FormField
control={form.control}
name="reuseBuilding.0.htdflObj.emsnExitTp"
render={({ field }) => (
<FormUi label="배출구온도">
<Input
className={'w-full !text-xl'}
placeholder="10"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
</form>
</Form>
</div>
);
}
+160
View File
@@ -0,0 +1,160 @@
"use client";
import Gis from "@common/business_components/ui/map/Gis";
import { useEffect, useRef, useState, useCallback } from "react";
import FeatureClickEventPopup from "../featureClickEventPopup";
import { mapService } from "../service";
import { MapSearchBar } from "../mapSearchBar";
import NewWindowPopup from "../newWindowPopup";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
const Map = () => {
const [treeData, setTreeData] = useState([]); // 초기값을 객체 또는 null로 설정
const [selectedFeature, setSelectedFeature] = useState(null); // 선택된 피처 상태
const gisMaps = useRef<any | null>(null);
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
const [checkedLayers, setCheckedLayers] = useState<string[]>([]);
const [checkedLayersData, setCheckedLayersData] = useState<any[]>([]);
const fetchData = async () => {
try {
const response = await mapService.getLyrStngs();
setTreeData(response); // API 응답 저장
} catch (error) {
console.error("데이터 불러오기 실패:", error);
}
};
// 팝업 닫기 핸들러를 useCallback으로 메모이제이션
const handlePopupClose = useCallback(() => {
setIsOpen(false);
setFeatureData(null);
}, []);
// 컴포넌트 마운트 시 1회 실행
useEffect(() => {
fetchData();
}, []);
// 체크된 레이어 정보를 받는 핸들러
const handleCheckedLayersChange = (checkedIds: string[], checkedNodesData: any[]) => {
setCheckedLayers(checkedIds);
setCheckedLayersData(checkedNodesData);
};
// SearchModal, GisContent 등에서 호출
const onFeatureSelect = (data: any) => {
let map = gisMaps.current;
if (!map) {
console.warn("지도가 초기화되지 않았습니다.");
return;
}
// 항상 같은 select 객체 사용
const select = GisApp.Module.select as any;
if (!select) return;
// 1. 기존 선택 초기화
select.resetSelectedFeature();
// 2. data가 OpenLayers Feature 인스턴스인지 확인
let foundFeature = null;
if (
data &&
typeof data.getGeometry === "function" &&
typeof data.setStyle === "function"
) {
// 이미 Feature 인스턴스라면 그대로 사용
foundFeature = data;
} else {
// 아니면 featureId로 맵에서 Feature 인스턴스를 찾아서 사용
const featureId = data.fid || data.ftrIdn || data.id;
map
.getLayers()
.getArray()
.forEach((layer: any) => {
const source = layer.getSource?.();
if (source?.getFeatureById) {
const feature = source.getFeatureById(String(featureId).trim());
if (feature) foundFeature = feature;
}
});
}
// 3. 피처 선택
if (foundFeature) {
let geometry = foundFeature.getGeometry();
if (geometry) {
let extent = geometry.getExtent();
map.getView().fit(extent, {
duration: 300,
padding: [50, 50, 50, 50],
maxZoom: 16,
constrainResolution: true,
nearest: true,
});
}
select.selectFeature(foundFeature, true);
} else {
console.warn("OpenLayers Feature를 찾을 수 없습니다:", data);
}
setFeatureData(data);
setIsOpen(true);
setIsSearchModalOpen(false);
};
// MapSearchBar 래퍼 컴포넌트
// const CustomMapSearchBar = (props: any) => {
// return (
// <MapSearchBar
// {...props}
// searchType="공급처 후보지 조회"
// showApplyButton={false}
// />
// );
// };
return (
<div className="bg-container">
<Gis
treeData={treeData}
isSearch={false}
gisMaps={gisMaps}
isMiniMap={true}
searchView={undefined}
// searchView={(props: any) => (
// <MapSearchBar
// searchType="공급처 후보지 조회"
// showApplyButton={false}
// checkedLayers={checkedLayers}
// checkedLayersData={checkedLayersData}
// {...props}
// />
// )
// }
detailView={(props: any) => (
<NewWindowPopup
onClose={handlePopupClose} // 메모이제이션된 함수 사용
checkedLayers={checkedLayers}
checkedLayersData={checkedLayersData}
{...props}
/>
)}
onFeatureSelect={undefined}
setIsOpen={setIsOpen}
setFeatureData={setFeatureData}
featureData={featureData}
setIsSearchModalOpen={setIsSearchModalOpen}
isOpen={isOpen}
isSearchModalOpen={isSearchModalOpen}
onCheckedLayersChange={handleCheckedLayersChange}
/>
</div>
);
};
export default Map;
+414
View File
@@ -0,0 +1,414 @@
// "use client"
// import { useWrStore } from "@/app/store";
// import SearchInputText from "@/components/ui/searchInput/searchInput";
// import type { EnvPartSelectDataType, ReturnType, SelectDataType } from "@common/business_components/ui";
// import { SelectBox } from "@common/business_components/ui";
// import { Button, Input, Label } from "@common/components/ui";
// import React, { useEffect, useMemo, useRef, useState } from "react";
// import { MapSearchFormLeft } from "./mapSearchForm";
// // 건물 검색 타입 정의
// interface BuildingInfo {
// bdstInfoPk: number;
// ctpvCd: number;
// sggCd: number;
// daddr: string;
// bdstNm: string;
// frstArchPrmsnDt: string;
// archArea: number;
// ctpvNm: string | null;
// sggNm: string | null;
// lat: number | null;
// lot: number | null;
// regDt: string;
// rgtr: string | null;
// mdfcnDt: string;
// mdfr: string | null;
// }
// type SearchBaseType = {
// searchType: string;
// selectSidoData?: SelectDataType[] | [];
// selectSigunData?: SelectDataType[] | [];
// selectRegionData?: SelectDataType[] | [];
// selectUnitWaterShedData?: SelectDataType[] | [];
// selectMiddleRegionData?: SelectDataType[] | [];
// selectEnvPartData?: EnvPartSelectDataType[] | [];
// onFeatureSelect: () => void;
// btnClass?: string;
// ref?: React.RefObject<ReturnType>;
// isAdmin?: boolean;
// leftClassName?: string;
// rightClassName?: string;
// showQueryButton?: boolean;
// showInitButton?: boolean;
// showApplyButton?: boolean;
// checkedLayers?: string[];
// checkedLayersData?: any[];
// }
// const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
// const YEARS = Array.from({ length: 100 }, (_, i) => `${2025 - i}`);
// interface SearchModalProps {
// open: boolean;
// onOpenChange: (open: boolean) => void;
// onFeatureSelect: (row: any) => void;
// }
// interface SelectedDataTypeWithSggNm {
// text: string;
// val: string;
// sggNm: string;
// }
// export function MapSearchBar({
// searchType,
// selectSidoData,
// selectSigunData,
// selectRegionData,
// selectUnitWaterShedData,
// selectMiddleRegionData,
// selectEnvPartData,
// btnClass,
// onFeatureSelect,
// isAdmin = false,
// leftClassName,
// rightClassName,
// showQueryButton = true,
// showInitButton = true,
// showApplyButton = true,
// checkedLayers = [],
// checkedLayersData = [],
// }: SearchBaseType) {
// // 로딩바
// const [isLoading, setIsLoading] = useState(false);
// // 상세 정보 조회를 위한 상태 추가
// const [popupFeature, setPopupFeature] = useState<any>(null);
// const [isChHead, setIsChHead] = useState(false);
// const ref = useRef<ReturnType>({} as ReturnType);
// // zustand store에서 건물 검색 함수와 데이터 가져오기
// const {
// code,
// bdst,
// findBdst,
// wrMap,
// buildingSearchRes,
// buildingNmSearch,
// buildingNmSearchMultiple
// } = useWrStore((state) => state);
// const [selectedBuilding, setSelectedBuilding] = useState<BuildingInfo | null>(null);
// const [buildingSearchText, setBuildingSearchText] = useState("");
// const [buildingList, setBuildingList] = useState<BuildingInfo[]>([]);
// // 전체 목록
// const [partValue, setPartValue] = useState<string>("A");
// const [sidoValue, setSidoValue] = useState<string>("0");
// const [sigunValue, setSigunValue] = useState<string>("0");
// const [regionValue, setRegionValue] = useState<string>("0");
// const [untitWaterShedValue, setUntitWaterShedValue] = useState<string>("0");
// const [middleRegionValue, setMiddleRegionValue] = useState<string>("0");
// const [envPartValue, setEnvPartValue] = useState<string>("0");
// const [resetTrigger, setResetTrigger] = useState(false);
// const [sidoText, setSidoText] = useState<string>("0");
// const [sigunText, setSigunText] = useState<string>("0");
// // 공급처 후보지 조회 필터 목록
// const [selectedSido, setSelectedSido] = useState<SelectDataType>({ text: "전체", val: '0' });
// const [selectedSigun, setSelectedSigun] = useState<SelectDataType>({ text: "전체", val: '0' });
// const [selectedFclt, setSelectedFclt] = useState<SelectedDataTypeWithSggNm>({ text: "전체", val: '0', sggNm: '전체' }); // 시설명 선택 상태
// // 전체 시설
// let allFclt: SelectedDataTypeWithSggNm[] = [{ text: "전체", val: '0', sggNm: '전체' }];
// const [selectedDong, setSelectedDong] = useState<SelectedDataTypeWithSggNm>({ text: "전체", val: '0', sggNm: '전체' }); // 시설명 선택 상태 // 행정동
// const filteredSigunData = useMemo(() => {
// if (!selectedSido || selectedSido.val === '0' || !selectedSido.val) return [{ text: "전체", val: '0' }];
// return [
// { text: "전체", val: '0' },
// ...code.sggCd.filter((item: { val: string; }) => item.val.substring(0, 2) === selectedSido.val.substring(0, 2))
// ];
// }, [selectedSido]);
// const filteredFcltData = useMemo(() => {
// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) return [{ text: "전체", val: '0', sggNm: '전체' }];
// return [
// { text: "전체", val: '0', sggNm: '전체' },
// ...allFclt.filter(item => item.sggNm === selectedSido.text)
// ];
// }, [selectedSigun]);
// const filteredDong = useMemo(() => {
// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) return [{ text: "전체", val: '0', sggNm: '전체' }];
// return [
// { text: "전체", val: '0', sggNm: '전체' },
// // ...code.dong.filter(item => item.sggNm === selectedSido.text)
// ];
// }, [selectedSigun]);
// // 조회 함수
// const queryVal = () => {
// // 46 : 하수처리시설
// // 47 : 하수재이용시설
// // 48 : 빗물이용시설
// // 49 : 중수도시설
// // 50 : 온배수시설
// console.log("조회버튼 클릭: ", checkedLayers);
// if (checkedLayers && checkedLayers.includes('48')) {
// // searchBuildings
// }
// }
// // 초기화 함수
// const initVal = () => {
// setPartValue("A");
// setSidoValue("0");
// setSigunValue("0");
// setRegionValue("0");
// setUntitWaterShedValue("0");
// setMiddleRegionValue("0");
// setEnvPartValue("0");
// setSidoText("전체");
// setSigunText("전체");
// setSelectedSido({ text: "전체", val: '0' });
// setSelectedSigun({ text: "전체", val: '0' });
// setSelectedFclt({ text: "전체", val: '0', sggNm: '전체' });
// setSelectedDong({ text: "전체", val: '0', sggNm: '전체' });
// setResetTrigger(true);
// };
// // 적용 버튼
// const applyVal = () => {
// onFeatureSelect();
// };
// useEffect(() => {
// if (resetTrigger) {
// setResetTrigger(false);
// }
// }, [resetTrigger]);
// // 시설 조회
// useEffect(() => {
// const fetchData = async () => {
// setIsLoading(true);
// if (!selectedSigun || selectedSigun.val === '0' || !selectedSigun.val) {
// allFclt = ([{ text: "전체", val: '0', sggNm: '전체' }]);
// setIsLoading(false);
// return;
// }
// try {
// const queryParamsFacility = new URLSearchParams({
// lyrTypeNm: 'tb_il_swage_prcs_fclt',
// page: '0',
// size: '100000',
// sort: 'string',
// });
// const cqlFiltersFacility: string[] = [];
// if (selectedSigun.text && selectedSigun.val !== '0') {
// cqlFiltersFacility.push(`sgg_nm ILIKE '%${selectedSigun.text}%'`);
// }
// if (cqlFiltersFacility.length > 0) {
// queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and '));
// }
// console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`);
// const facilityRes = await fetch(`${API_URL}/v1/fclts?${queryParamsFacility.toString()}`, {
// method: "GET",
// headers: { accept: "application/json" }
// }).then(res => res.json());
// const facilities = (facilityRes.data || []).map((item: any) => ({
// text: item.swtrmtFcltNm,
// val: item.fid,
// sggNm: item.sggNm
// }));
// allFclt = (facilities);
// } catch (error) {
// console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error);
// allFclt = ([{ text: "전체", val: '0', sggNm: '전체' }]);
// } finally {
// setIsLoading(false);
// }
// };
// fetchData();
// }, [selectedSigun]);
// useEffect(() => {
// if (ref && ref.current) {
// const customRef = ref.current;
// customRef.partValue = partValue;
// customRef.sidoValue = sidoValue;
// customRef.sigunValue = sigunValue;
// customRef.regionValue = regionValue;
// customRef.unitWaterShedValue = untitWaterShedValue;
// customRef.middleRegionValue = middleRegionValue;
// customRef.envPartValue = envPartValue;
// customRef.sidoText = selectedSido.text;
// customRef.sigunText = selectedSigun.text;
// }
// }, [partValue, sidoValue, sigunValue, regionValue, untitWaterShedValue, middleRegionValue, envPartValue, ref]);
// // buildingSearchText가 변경될 때 API 호출
// useEffect(() => {
// if (buildingSearchText && buildingSearchText.length > 1) {
// if (checkedLayers && checkedLayers.length > 0) {
// // 다중 레이어 검색
// // buildingNmSearchMultiple(checkedLayers, selectedSigun.val || "", buildingSearchText);
// buildingNmSearch("RNB", selectedSigun.val, buildingSearchText);
// } else {
// // 단일 레이어 검색
// // buildingNmSearch(checkedLayers[0], selectedSigun.val, buildingSearchText);
// }
// } else {
// // 검색 텍스트가 짧으면 리스트 초기화
// if (checkedLayers && checkedLayers.length > 0) {
// // buildingNmSearchMultiple(checkedLayers, selectedSigun.val || "", "");
// } else {
// buildingNmSearch("RNB", selectedSigun.val || "", "");
// }
// }
// }, [buildingSearchText]);
// return (
// <div className="absolute left-1/2 -translate-x-1/2 bg-white flex items-center justify-center p-4 rounded-md shadow-lg w-auto min-w-fit">
// <div className={leftClassName ? leftClassName : `flex items-center justify-center space-x-2 `}>
// <div className="flex gap-8 items-center">
// {searchType == "공급처 후보지 조회" && (
// <>
// <SelectBox
// className="flex items-center"
// label={'시도'}
// selectArray={code.cityCd}
// onSelectValue={(val) => {
// const selected = code.cityCd.find((item: { val: string | number; }) => (item.val === val));
// setSelectedSido(selected || { text: "전체", val: '0' });
// setSelectedSigun({ text: "전체", val: '0' });
// }}
// value={selectedSido.val}
// selectClass={'w-32'}
// />
// <SelectBox
// className="flex items-center"
// label={'시군구'}
// selectArray={filteredSigunData}
// onSelectValue={(val) => {
// const selected = filteredSigunData.find(item => (item.val === val));
// setSelectedSigun(selected || { text: "전체", val: '0' });
// setSelectedFclt({ text: "전체", val: '0', sggNm: '전체' });
// }}
// value={selectedSigun.val}
// selectClass={'w-32'}
// />
// <div className="flex items-center">
// <Label className="w-20 text-xl text-gray-600">시설명</Label>
// <div className="w-64">
// <SearchInputText
// label="시설명"
// initValue={buildingSearchText}
// setValue={(searchNm?: string) => {
// setBuildingSearchText(searchNm || "");
// }}
// setRtnData={function (rtnData: any): void {
// // 선택된 건물 데이터 처리
// // if (rtnData && rtnData.bdstInfoPk) {
// // setSelectedBuilding(rtnData);
// // setBuildingSearchText(rtnData.bdstNm || "");
// // }
// }}
// searchList={buildingSearchRes || []}
// />
// </div>
// </div>
// </>
// )}
// {searchType == "우리동네가뭄지도보기" && (
// <>
// <SelectBox
// className="flex items-center"
// label={'시도'}
// selectArray={code.cityCd}
// onSelectValue={(val) => {
// const selected = code.cityCd.find((item: { val: string | number; }) => (item.val === val));
// setSelectedSido(selected || { text: "전체", val: '0' });
// setSelectedSigun({ text: "전체", val: '0' });
// }}
// value={selectedSido.val}
// selectClass={'w-44'}
// />
// <SelectBox
// className="flex items-center"
// label={'시군구'}
// selectArray={filteredSigunData}
// onSelectValue={(val) => {
// const selected = filteredSigunData.find(item => (item.val === val));
// setSelectedSigun(selected || { text: "전체", val: '0' });
// setSelectedDong({ text: "전체", val: '0', sggNm: '전체' });
// }}
// value={selectedSigun.val}
// selectClass={'w-32'}
// />
// <SelectBox
// className="flex items-center mr-8"
// label={'행정동'}
// selectArray={filteredDong}
// onSelectValue={(val) => {
// const selected = filteredDong.find(item => (item.val === val));
// setSelectedDong(selected || { text: "전체", val: '0', sggNm: '전체' });
// }}
// value={selectedDong.val}
// selectClass={'w-32'}
// />
// </>
// )}
// {searchType == "지도보기" && (
// <div className="flex items-center gap-2 w-[800px]">
// <div className="flex min-w-0"> {/* SearchFormLeft 래퍼 */}
// <MapSearchFormLeft
// isShowPart={true}
// selectSidoData={code.cityCd}
// selectSigunData={filteredSigunData}
// selectRegionData={code.rvbsnCd}
// selectUnitWaterShedData={selectUnitWaterShedData}
// selectMiddleRegionData={selectMiddleRegionData}
// selectEnvPartData={selectEnvPartData}
// setRtnPartValue={setPartValue}
// setRtnSidoValue={setSidoValue}
// setRtnSigunValue={setSigunValue}
// setRtnRegionValue={setRegionValue}
// setRtnUnitWaterShedValue={setUntitWaterShedValue}
// setRtnMiddleRegionValue={setMiddleRegionValue}
// setRtnEnvPartValue={setEnvPartValue}
// setRtnEnvPartSidoValue={setSidoValue}
// setRtnEnvPartSigunValue={setSigunValue}
// setRtnSidoText={setSidoText}
// setRtnSigunText={setSigunText}
// resetTrigger={resetTrigger}
// isAdmin={isAdmin}
// children={undefined}
// />
// </div>
// <Label className="w-auto text-xl text-gray-600 whitespace-nowrap">시설명</Label>
// <div className="w-44">
// <Input className="mr-4" />
// </div>
// </div>
// )}
// </div>
// {/* 버튼 부분 */}
// <div className="flex items-center justify-center gap-4">
// {showQueryButton && <Button className={btnClass} onClick={queryVal}>조회</Button>}
// {showInitButton && <Button className={btnClass} onClick={initVal}>초기화</Button>}
// {showApplyButton && <Button className={btnClass} onClick={applyVal}>적용</Button>}
// </div>
// </div>
// </div>
// )
// }
+340
View File
@@ -0,0 +1,340 @@
"use client"
import React, { useEffect, useState } from 'react';
import type { SelectDataType } from '@common/business_components/ui/search_input_select';
import { SelectBox } from '@common/business_components/ui/select';
/**
* 검색 폼 왼쪽 컴포넌트 타입
*
* @param isShowPart 구분 조회 여부
* @param children 자식 컴포넌트
* @param selectSidoData 시도 데이터
* @param selectSigunData 시군구 데이터
* @param selectRegionData 유역 데이터
* @param selectUnitWaterShedData 단위유역 데이터
* @param selectMiddleRegionData 중권역 데이터
* @param selectEnvPartData 환경청 데이터
* @param selectEnvPartSidoData 환경청 시도 데이터
* @param selectEnvPartSigunData 환경청 시군구 데이터
* @param setRtnPartValue 부분 값 변경 핸들러
*/
type SearchFormLeftProps = {
isShowPart?: boolean | undefined;
children: React.ReactNode;
selectSidoData?: SelectDataType[] | [];
selectSigunData?: SelectDataType[] | [];
selectRegionData?: SelectDataType[] | [];
selectUnitWaterShedData?: SelectDataType[] | [];
selectMiddleRegionData?: SelectDataType[] | [];
selectEnvPartData?: EnvPartSelectDataType[] | [];
selectEnvPartSidoData?: SelectDataType[] | [];
selectEnvPartSigunData?: SelectDataType[] | [];
setRtnPartValue?: (value: string) => void;
setRtnSidoValue?: (value: string) => void;
setRtnSigunValue?: (value: string) => void;
setRtnRegionValue?: (value: string) => void;
setRtnUnitWaterShedValue?: (value: string) => void;
setRtnMiddleRegionValue?: (value: string) => void;
setRtnEnvPartValue?: (value: string) => void;
setRtnEnvPartSidoValue?: (value: string) => void;
setRtnEnvPartSigunValue?: (value: string) => void;
setRtnSidoText?: (value: string) => void;
setRtnSigunText?: (value: string) => void;
resetTrigger?: boolean;
isAdmin?: boolean;
}
export type EnvPartSelectDataType = SelectDataType & {
adstDliStrCd: string,
}
const selectPartData: SelectDataType[] = [
{ text: "행정별", val: 'A' },
{ text: "유역별", val: 'R' },
{ text: "환경청별", val: 'G' },
];
function MapSearchFormLeft({ isShowPart = false, children, selectSidoData = [], selectSigunData = [], selectRegionData = [], selectUnitWaterShedData = [], selectMiddleRegionData = [], selectEnvPartData = [], setRtnPartValue, setRtnSidoValue, setRtnSidoText, setRtnSigunValue, setRtnSigunText, setRtnRegionValue, setRtnUnitWaterShedValue, setRtnMiddleRegionValue, setRtnEnvPartValue, setRtnEnvPartSidoValue, setRtnEnvPartSigunValue, resetTrigger = false, isAdmin = false }: SearchFormLeftProps) {
/******************선택된 데이터******************/
const [partValue, setPartValue] = useState<string>('A');
//행정별 시도 시군구
const [sidoValue, setSidoValue] = useState<string>('0');
const [sigunValue, setSigunValue] = useState<string>('0');
const [sidoText, setSidoText] = useState<string>('');
const [sigunText, setSigunText] = useState<string>('');
//유역별
const [regionValue, setRegionValue] = useState<string>('0');
const [unitWaterShedValue, setUnitWaterShedValue] = useState<string>('0');
const [middleRegionValue, setMiddleRegionValue] = useState<string>('0');
//환경청별 시도 시군구
const [envPartValue, setEnvPartValue] = useState<string>('0');
// 환경청
const [filteredEnvPartSigunDataSelected, setFilteredEnvPartSigunDataSelected] = useState<SelectDataType[]>([]);
/****************** 셀렉트 리스트 데이터. ******************/
// 조회된 조건에 맞는 시도 시군 유역
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredUnitWaterShedData, setFilteredUnitWaterShedData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredMiddleRegionData, setFilteredMiddleRegionData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
// 조회된 조건에 맞는 환경청 시도 시군
const [filteredEnvPartData, setFilteredEnvPartData] = useState<EnvPartSelectDataType[]>([{ text: "전체", val: '0', adstDliStrCd: '' }]);
const [filteredEnvPartSidoData, setFilteredEnvPartSidoData] = useState<EnvPartSelectDataType[]>([{ text: "전체", val: '0', adstDliStrCd: '' }]);
const [filteredEnvPartSigunData, setFilteredEnvPartSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
// 시군구 세팅
const initializeSigunData = (sidoCd: string) => {
if (sidoCd === '0') {
setSigunValue('0');
setFilteredSigunData([{ text: "전체", val: '0' }]);
} else {
const newFilteredData = [
{ text: "전체", val: '0' },
...selectSigunData.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd)
];
setSigunValue('0');
setFilteredSigunData(newFilteredData);
}
}
// 단위유역 세팅
const initializeUnitWaterShedData = (regionCd: string) => {
if (regionCd === '0') {
setUnitWaterShedValue('0');
setFilteredUnitWaterShedData([{ text: "전체", val: '0' }]);
} else {
const newFilteredData = [
{ text: "전체", val: '0' },
...selectUnitWaterShedData.filter((item: SelectDataType) => item.val.substring(0, 1) === regionCd)
];
setUnitWaterShedValue('0');
setFilteredUnitWaterShedData(newFilteredData);
}
}
// 중권역 세팅
const initializeMiddleRegionData = (unitWaterShedCd: string) => {
if (unitWaterShedCd === '0') {
setMiddleRegionValue('0');
setFilteredMiddleRegionData([{ text: "전체", val: '0' }]);
} else {
const newFilteredData = [
{ text: "전체", val: '0' },
...selectMiddleRegionData.filter((item: SelectDataType) => item.val.substring(0, 2) === unitWaterShedCd)
];
setMiddleRegionValue('0');
setFilteredMiddleRegionData(newFilteredData);
}
}
// 환경청 세팅 - 시도
const initializeCityDataByEnvPartSido = (envPartCd: string) => {
const foundEnvPart = selectEnvPartData.find((item: EnvPartSelectDataType) => item.val === envPartCd);
const cityArray = foundEnvPart?.adstDliStrCd.toString().split('|').join('').split(',');
// 시군구 데이터
const envSigunData = selectSigunData.filter((item: SelectDataType) => cityArray?.includes(item.val));
// 환경청 시도 데이터
const sidoArray = [...new Set(envSigunData.map((item: SelectDataType) => item.val.substring(0, 2)))];
const envSidoData = selectSidoData.filter((item: SelectDataType) => sidoArray?.includes(item.val));
setFilteredEnvPartSigunDataSelected(envSigunData);
setFilteredEnvPartSidoData([
{ text: "전체", val: '0', adstDliStrCd: '' },
...envSidoData.map((item: SelectDataType) => ({ text: item.text, val: item.val, adstDliStrCd: '' }))
]);
}
// 환경청 세팅 - 시군구
const initializeCityDataByEnvPartSigun = (sidoCd: string) => {
const envSigunData = filteredEnvPartSigunDataSelected.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd);
setFilteredEnvPartSigunData([
{ text: "전체", val: '0' },
...envSigunData.map((item: SelectDataType) => ({ text: item.text, val: item.val }))
]);
}
const onSelectValue = (val: string, type: string) => {
let selectedSido;
let selectedSigun;
let selectedEnvPartSido;
let selectedEnvPartSigun;
switch (type) {
case 'part':
setPartValue(val);
if (setRtnPartValue) {
setRtnPartValue(val);
}
// 구분
if (val == 'R') {
initializeMiddleRegionData('0');
initializeUnitWaterShedData('0');
} else if (val == 'G') {
setFilteredEnvPartData([
{ text: "전체", val: '0', adstDliStrCd: '' },
...selectEnvPartData.filter((item: EnvPartSelectDataType) => item.val !== null && item.adstDliStrCd !== '')
]);
setEnvPartValue('0');
initializeCityDataByEnvPartSido('0');
} else {
initializeSigunData('0');
onSelectValue(selectSigunData[0]?.val || '0', 'sigun');
}
// 다른 selectbox 전체로 초기화
onSelectValue(selectSidoData[0]?.val || '0', 'sido');
onSelectValue(selectSigunData[0]?.val || '0', 'sigun');
break;
case 'sido':
setSidoValue(val);
selectedSido = selectSidoData.find(item => item.val === val);
setSidoText(selectedSido?.text || '');
if (setRtnSidoValue) {
setRtnSidoValue(val);
}
if (setRtnSidoText) {
setRtnSidoText(selectedSido?.text || '');
}
initializeSigunData(val);
onSelectValue(selectSigunData[0]?.val || '0', 'sigun');
break;
case 'sigun':
if (setRtnSigunValue) {
setRtnSigunValue(val);
}
setSigunValue(val);
selectedSigun = filteredSigunData.find(item => item.val === val);
if (setRtnSigunText) {
setRtnSigunText(selectedSigun?.text || '');
}
setSigunText(selectedSigun?.text || '');
break;
// 유역 세팅
case 'region':
setRegionValue(val);
if (setRtnRegionValue) {
setRtnRegionValue(val);
}
initializeUnitWaterShedData(val);
onSelectValue(selectUnitWaterShedData[0]?.val || '0', 'unitWaterShed');
break;
case 'unitWaterShed':
setUnitWaterShedValue(val);
if (setRtnUnitWaterShedValue) {
setRtnUnitWaterShedValue(val);
}
initializeMiddleRegionData(val);
onSelectValue(selectMiddleRegionData[0]?.val || '0', 'middleRegion');
break;
case 'middleRegion':
setMiddleRegionValue(val);
if (setRtnMiddleRegionValue) {
setRtnMiddleRegionValue(val);
}
break;
// 환경청 세팅
case 'envPart':
setEnvPartValue(val);
if (setRtnEnvPartValue) {
setRtnEnvPartValue(val);
}
initializeCityDataByEnvPartSido(val);
onSelectValue(filteredEnvPartSidoData[0]?.val || '0', 'envPartSido');
break;
case 'envPartSido':
setSidoValue(val);
selectedEnvPartSido = filteredEnvPartSidoData.find(item => item.val === val);
setSidoText(selectedEnvPartSido?.text || '');
if (setRtnEnvPartSidoValue) {
setRtnEnvPartSidoValue(val);
}
initializeCityDataByEnvPartSigun(val);
onSelectValue(filteredEnvPartSigunData[0]?.val || '0', 'envPartSigun');
break;
case 'envPartSigun':
setSigunValue(val);
selectedEnvPartSigun = filteredEnvPartSigunData.find(item => item.val === val);
setSigunText(selectedEnvPartSigun?.text || '');
if (setRtnEnvPartSigunValue) {
setRtnEnvPartSigunValue(val);
}
break;
}
}
// 리셋 함수
const resetAllValues = () => {
setPartValue('A');
setSidoValue('0');
setSigunValue('0');
setRegionValue('0');
setUnitWaterShedValue('0');
setMiddleRegionValue('0');
setEnvPartValue('0');
setFilteredSigunData([{ text: "전체", val: '0' }]);
setFilteredUnitWaterShedData([{ text: "전체", val: '0' }]);
setFilteredMiddleRegionData([{ text: "전체", val: '0' }]);
setFilteredEnvPartData([{ text: "전체", val: '0', adstDliStrCd: '' }]);
setFilteredEnvPartSidoData([{ text: "전체", val: '0', adstDliStrCd: '' }]);
setFilteredEnvPartSigunData([{ text: "전체", val: '0' }]);
if (setRtnPartValue) setRtnPartValue('A');
if (setRtnSidoValue) setRtnSidoValue('0');
if (setRtnSigunValue) setRtnSigunValue('0');
if (setRtnRegionValue) setRtnRegionValue('0');
if (setRtnUnitWaterShedValue) setRtnUnitWaterShedValue('0');
if (setRtnMiddleRegionValue) setRtnMiddleRegionValue('0');
if (setRtnEnvPartValue) setRtnEnvPartValue('0');
if (setRtnEnvPartSidoValue) setRtnEnvPartSidoValue('0');
if (setRtnEnvPartSigunValue) setRtnEnvPartSigunValue('0');
};
// 리셋 트리거 변경 감지
useEffect(() => {
if (resetTrigger) {
resetAllValues();
// 리셋 후 트리거를 false로 설정
if (setRtnPartValue) {
setRtnPartValue('0');
}
}
}, [resetTrigger]);
return (
isShowPart ? (
<div className="flex gap-5">
<SelectBox className="w-auto whitespace-nowrap" label={'구분'} selectArray={isAdmin ? selectPartData.slice(0, 1) : selectPartData} onSelectValue={(val) => onSelectValue(val.toString(), 'part')} value={partValue} selectClass={'w-32'} />
{partValue === 'R' ? (
<>
<SelectBox className="w-auto whitespace-nowrap" label={'유역'} selectArray={selectRegionData} onSelectValue={(val) => onSelectValue(val.toString(), 'region')} value={regionValue} selectClass={'w-44'} />
<SelectBox className="w-auto whitespace-nowrap" label={'단위유역'} selectArray={filteredUnitWaterShedData} onSelectValue={(val) => onSelectValue(val.toString(), 'unitWaterShed')} value={unitWaterShedValue} selectClass={'w-44'} />
<SelectBox className="w-auto whitespace-nowrap" label={'중권역'} selectArray={filteredMiddleRegionData} onSelectValue={(val) => onSelectValue(val.toString(), 'middleRegion')} value={middleRegionValue} selectClass={'w-44'} />
</>
) : partValue === 'G' ? (
<>
<SelectBox className="w-auto whitespace-nowrap" label={'환경청'} selectArray={filteredEnvPartData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPart')} value={envPartValue} selectClass={'w-44'} />
<SelectBox className="w-auto whitespace-nowrap" label={'시도'} selectArray={filteredEnvPartSidoData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPartSido')} value={sidoValue} selectClass={'w-44'} />
<SelectBox className="w-auto whitespace-nowrap" label={'시군구'} selectArray={filteredEnvPartSigunData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPartSigun')} value={sigunValue} selectClass={'w-44'} />
</>
) : (
<>
<SelectBox className="w-auto whitespace-nowrap" label={'시도'} selectArray={selectSidoData} onSelectValue={(val) => onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={'w-44'} />
<SelectBox className="w-auto whitespace-nowrap" label={'시군구'} selectArray={filteredSigunData} onSelectValue={(val) => onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={'w-44'} />
</>
)}
</div>
) : (
<div className="grid grid-cols-12 gap-5 w-11/12 m-4">
{children}
</div>
)
)
}
export { MapSearchFormLeft };
+382
View File
@@ -0,0 +1,382 @@
import { useWrStore } from "@/app/store";
import { btnClass } from "@/app/store/common/code";
import type { customPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type";
import { excelDownload } from "@/components/lib/func/commom";
import { Title, UiTable, UITab, ContentsDiv } from '@common/business_components';
import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan";
import { Button } from "@common/components/ui";
import React, { useCallback, useState, useEffect } from 'react';
import NewWindowPortal from '@/app/map/newWindowPortal';
import PbnstPrfmncSttnPopup from "@/app/map/pbnstPrfmncSttnPopup";
import { PbnstSttnModal } from "@/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal";
import type { WaterReuseFacility } from "@/app/store/ruseFcltMng/fcltSttn/pbnstSttn/type";
import type { chgBuilding, customRainBdst } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import RainPrfmncSttnPopup from "@/app/map/rainPrfmncSttnPopup";
import RswtrPrfmncsSttnPopup from "@/app/map/rswtrPrfmncSttnPopup";
import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg";
import HtdflPrfmncSttnPopup from "@/app/map/htdflPrfmncSttnPopup";
import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg";
interface NewWindowPopupProps {
featureData: any;
checkedLayers?: string[];
checkedLayersData?: any[];
onClose: () => void;
}
// 탭
const TABS = [
{ val: "0", text: "재이용시설1" },
{ val: "1", text: "재이용시설2" },
{ val: "2", text: "재이용시설n" },
];
// 데이터의 속성(key)을 사용자에게 보여줄 한글 이름으로 변환하기 위한 맵
// 실제 데이터에 맞게 이 부분을 확장해야 합니다.
const propertyNameMap: { [key: string]: string } = {
fid: '고유 ID',
fcltNm: '시설명',
opertDe: '운영일자',
addr: '주소',
// ... 필요한 만큼 속성 매핑 추가
};
const NewWindowPopup: React.FC<NewWindowPopupProps> = ({
featureData,
checkedLayers = [],
checkedLayersData = [],
onClose
}) => {
const [activeTab, setActiveTab] = useState(TABS[0]?.val ?? "0");
// swcho : useState의 true, false 값 변경하여 목록, 상태 테스트 하였음
const [showDetailView, setShowDetailView] = useState(true); // 상세정보 화면 표시 상태 추가
// onClose 함수를 useCallback으로 메모이제이션
const memoizedOnClose = useCallback(() => {
onClose();
}, [onClose]);
// 하수재이용시설 변수
const {
code,
pbnstSttn,
pbnstSttnSearch,
pbnSttnInit,
getPbnStnDetail,
pbnNmSearch,
} = useWrStore((state) => state);
const [bdstTable, setBdstTable] = useState<WaterReuseFacility[]>();
const [pageNation, setPageNation] = useState(1);
const [pbnDetaileData, setPbnDetaileData] = useState<{
isModal: boolean;
data: customPbnst;
}>({ isModal: false, data: {} as customPbnst });
// 빗물재이용시설 변수
const { rainSttn, rainSttnSearch, rainSttnInit } = useWrStore(
(state) => state
);
const { rainSttnRes } = rainSttn;
const [rainTable, setRainTable] = useState<customRainBdst[]>();
const [rainBdstChgProps, setRainBdstChgProps] = useState<{
isOpen: boolean;
chBuilding: chgBuilding[];
}>({ isOpen: false, chBuilding: [] });
const [rainDetailModal, setRainDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false });
// 중수도시설 변수
const { rswtSttn, rswtSttnSearch, rswtSttnInit } = useWrStore(
(state) => state
);
const { rswtSttnRes } = rswtSttn;
const [rswtrTable, setRswtrTable] = useState<customRswtBdst[]>();
const [rswtrDetailModal, setRswtrDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false });
const [rswtrBdstChgProps, setRswtrBdstChgProps] = useState<{
isOpen: boolean;
chBuilding: chgBuilding[];
}>({ isOpen: false, chBuilding: [] });
// 온배수시설 변수
const { htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore((state) => state);
const { htdflSttnRes } = htdflSttn;
const [htdflTable, setHtdflTable] = useState<customHtdflBdst[]>();
const [htdflDetailModal, setHtdflDetailModal] = useState<{ isOpen: boolean, data: customHtdflBdst | undefined }>({ data: undefined, isOpen: false });
const [bdstChgProps, setBdstChgProps] = useState<{ isOpen: boolean, chBuilding: chgBuilding[] }>({ isOpen: false, chBuilding: [] });
if (!featureData) {
return null;
}
// 2. RuseMngTable에 필요한 tableData를 생성합니다. (단일 행 데이터)
const tableData = [featureData];
console.log("tableData", tableData);
// 물재이용시설 cellClick 이벤트 (상세정보)
const pbnstCellClick = (index: number | undefined) => {
if (
index !== undefined &&
pbnstSttn.pbnstSttnRes &&
pbnstSttn.pbnstSttnRes[index]
) {
const pbnstRuseFcltPk = pbnstSttn.pbnstSttnRes[index].pbnstRuseFcltPk;
getPbnStnDetail(pbnstRuseFcltPk);
}
};
useEffect(() => {
setBdstTable(pbnstSttn.pbnstSttnRes);
}, [pbnstSttn.pbnstSttnRes]);
useEffect(() => {
if (pbnstSttn.pbnstDetail.pbnstRuseFcltPk) setShowDetailView(true);
}, [pbnstSttn.pbnstDetail]);
// 빗물재이용시설 cellClick 이벤트 (상세정보)
const rainCellClick = (index: number | undefined, headId: string) => {
if (
index !== undefined &&
rainTable !== undefined &&
headId !== 'chBuildingYn') {
setRainDetailModal({ isOpen: true, data: rainTable[index] })
} else if (headId === 'chBuildingYn' && index !== undefined && rainTable !== undefined) {
if (rainTable[index] && rainTable[index]?.chgBuilding[0]?.bdstChgNm !== null)
setRainBdstChgProps({ isOpen: true, chBuilding: rainTable[index].chgBuilding });
}
}
// 중수도시설 cellClick 이벤트 (상세정보)
const rswtCellClick = (index: number | undefined, headId: string) => {
if (headId !== 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) {
setRswtrDetailModal({ isOpen: true, data: rswtrTable[index] })
} else if (headId === 'chBuildingYn' && index !== undefined && rswtrTable !== undefined) {
if (rswtrTable[index] && rswtrTable[index]?.chBuildingYn === 'Y') {
setRswtrBdstChgProps({ isOpen: true, chBuilding: rswtrTable[index].chgBuilding });
}
}
}
// 온배수시설 cellClick 이벤트 (상세정보)
const htdflCellClick = (index: number | undefined, headId: string) => {
if (
headId !== "chBuildingYn" &&
index !== undefined &&
htdflTable !== undefined
) {
setHtdflDetailModal({ isOpen: true, data: htdflTable[index] });
}
};
const excelClick = useCallback(() => {
// if (codeData) pbnstSttnSearch(1, "10000", codeData);
excelDownload(
[[]],
"하수재이용시설 현황",
"sample_pbnstSttn.xlsx",
"A1:U2"
);
}, [pbnstSttnSearch]);
// 체크된 레이어에 따른 조건부 컴포넌트 렌더링 함수
const renderConditionalContent = () => {
// 특정 레이어가 체크되었는지 확인
const hasWaterTreatmentLayer = checkedLayersData.some(layer =>
layer.data?.lyrNm?.includes('하수처리') || layer.data?.lyrNm?.includes('재이용시설')
);
// swcho : true, false 값 변경하여 시설 변경하여 테스트하였음
// 재이용시설
const isPbnst = true;
// 빗물이용시설
const isRain = false;
// 중수도시설
const isRswt = false;
// 온배수시설
const isHtdfl = false;
return (
<div className="mt-4">
{/* 체크된 레이어가 없는 경우 (재이용시설 목록) */}
{isPbnst && (
<>
{showDetailView ? (
// 재이용시설 상세
<>
<div className="flex justify-between items-center mb-4">
<Title title='하수재이용시설 상세정보' />
</div>
<div className="flex justify-between items-center mb-8">
<UITab
defaultValue={TABS[0]?.val}
tabList={TABS.map((tab) => ({
value: tab.val,
label: tab.text,
onClick: () => setActiveTab(tab.val),
}))}
children={undefined}
/>
</div>
<PbnstSttnModal
data={pbnDetaileData.data || ({} as customPbnst)}
titleVisible={false}
/>
<PbnstPrfmncSttnPopup isVisible={false} />
</>
) : (
// 재이용시설 목록
<>
<div className="flex justify-between items-center">
<Title title='하수재이용시설 현황' />
<div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button>
</div>
</div>
<UiTable
headName={[]}
tableData={bdstTable || []}
children={
<HeadMakeColSpan upHeadList={pbnstSttn.pbnstSttNodeList} />
}
headlist={pbnstSttn.pbnstSttHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
pbnstCellClick(index);
}}
onPageChange={setPageNation}
/>
</>
)}
</>
)}
{/* 빗물이용시설 */}
{isRain && (
<>
{showDetailView ? (
// 빗물이용시설 상세
<>
<div className="flex justify-between items-center mb-4">
<Title title='빗물이용시설 상세정보' />
</div>
<RainPrfmncSttnPopup rainForm={rainDetailModal.data as customRainBdst} isModal={false} />
{/* TODO: 지도 점검 결과 현황 추가 할 것 */}
</>
) : (
// 빗물이용시설 목록
<>
<div className="flex justify-between items-center">
<Title title='빗물이용시설 현황' />
<div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button>
</div>
</div>
<UiTable
headName={[]}
tableData={rainTable || []}
children={
<HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} />
}
headlist={rainSttn.rainSttHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
rainCellClick(index ?? undefined, headId)
}}
onPageChange={setPageNation}
/>
</>
)}
</>
)}
{/* 중수도시설 */}
{isRswt && (
<>
{showDetailView ? (
// 재이용시설 상세
<>
<div className="flex justify-between items-center mb-4">
<Title title='중수도시설 상세정보' />
</div>
<RswtrPrfmncsSttnPopup rswtForm={rswtrDetailModal.data as customRswtBdst} isModal={false} />
{/* TODO: 지도 점검 결과 현황 추가 할 것 */}
</>
) : (
// 재이용시설 목록
<>
<div className="flex justify-between items-center">
<Title title='중수도시설 현황' />
<div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button>
</div>
</div>
<UiTable
headName={[]}
tableData={rswtrTable || []}
children={
<HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} />
}
headlist={rswtSttn.rswtSttHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
rswtCellClick(index, headId);
}}
onPageChange={setPageNation}
/>
</>
)}
</>
)}
{/* 온배수시설 */}
{isHtdfl && (
<>
{showDetailView ? (
// 재이용시설 상세
<>
<div className="flex justify-between items-center mb-4">
<Title title='온배수시설 상세정보' />
</div>
{/* <HtdflPrfmncSttnPopup htdflForm={htdflDetailModal.data as customHtdflBdst} isModal={false} /> */}
<HtdflPrfmncSttnPopup htdflForm={htdflDetailModal.data as customHtdflBdst} isModal={false} />
</>
) : (
// 재이용시설 목록
<>
<div className="flex justify-between items-center">
<Title title='온배수시설 현황' />
<div className="flex gap-2 mt-8">
<Button className={btnClass} color="primary" onClick={excelClick}>엑셀다운로드</Button>
</div>
</div>
<UiTable
headName={[]}
tableData={bdstTable || []}
children={
<HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} />
}
headlist={htdflSttn.htdflSttHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
htdflCellClick(index ?? undefined, headId);
}}
onPageChange={setPageNation}
/>
</>
)}
</>
)}
</div>
);
};
return (
<NewWindowPortal onClose={memoizedOnClose} title="시설 상세 정보">
{/* 체크된 레이어에 따른 조건부 렌더링 */}
{renderConditionalContent()}
</NewWindowPortal>
);
};
export default NewWindowPopup;
+90
View File
@@ -0,0 +1,90 @@
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom';
// 현재 문서의 스타일(CSS)을 새 창으로 복사하는 함수
const copyStyles = (sourceDoc: Document, targetDoc: Document) => {
Array.from(sourceDoc.styleSheets).forEach(styleSheet => {
try {
if (styleSheet.cssRules) { // <style> 태그로 주입된 CSS
const newStyleEl = sourceDoc.createElement('style');
Array.from(styleSheet.cssRules).forEach(cssRule => {
newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
});
targetDoc.head.appendChild(newStyleEl);
} else if (styleSheet.href) { // <link> 태그로 연결된 CSS 파일
const newLinkEl = sourceDoc.createElement('link');
newLinkEl.rel = 'stylesheet';
newLinkEl.href = styleSheet.href;
targetDoc.head.appendChild(newLinkEl);
}
} catch (e) {
console.warn('스타일시트를 복사할 수 없습니다:', e);
}
});
};
interface NewWindowPortalProps {
children: React.ReactNode;
onClose: () => void;
title?: string;
}
const NewWindowPortal: React.FC<NewWindowPortalProps> = ({ children, onClose, title = "Detail" }) => {
const [container, setContainer] = useState<HTMLElement | null>(null);
const newWindowRef = useRef<Window | null>(null);
const onCloseRef = useRef(onClose);
// onClose 함수를 ref로 저장하여 의존성 문제 해결
useEffect(() => {
onCloseRef.current = onClose;
}, [onClose]);
const handleUnload = useCallback(() => {
onCloseRef.current();
}, []);
useEffect(() => {
// 이미 창이 열려있으면 새로 열지 않음
if (newWindowRef.current && !newWindowRef.current.closed) return;
// 1. 새 창을 열고 참조를 저장합니다.
newWindowRef.current = window.open('', title, 'width=1400,height=1024,resizable=yes,scrollbars=yes');
if (newWindowRef.current) {
newWindowRef.current.document.title = title;
// 2. React 컴포넌트를 마운트할 컨테이너 div를 생성하여 새 창의 body에 추가합니다.
const div = newWindowRef.current.document.createElement('div');
newWindowRef.current.document.body.appendChild(div);
setContainer(div);
// 3. 메인 창의 스타일을 새 창으로 복사합니다. (매우 중요)
copyStyles(document, newWindowRef.current.document);
// 4. 사용자가 창을 닫을 때 부모에게 알리기 위한 이벤트 리스너를 설정합니다.
// const handleUnload = () => {
// onClose();
// };
newWindowRef.current.addEventListener('unload', handleUnload);
// 5. 컴포넌트가 사라질 때 창을 닫는 클린업 함수를 반환합니다.
return () => {
if (newWindowRef.current) {
newWindowRef.current.removeEventListener('unload', handleUnload);
if (!newWindowRef.current.closed) {
newWindowRef.current.close();
}
}
};
} else {
alert('팝업이 차단되었습니다. 팝업 차단을 해제하고 다시 시도해주세요.');
onCloseRef.current();
// onClose();
}
}, [onClose, title]);
// `createPortal`을 사용해 children을 새 창의 container div에 렌더링합니다.
return container ? createPortal(children, container) : null;
};
export default NewWindowPortal;
+596
View File
@@ -0,0 +1,596 @@
"use client";
import { useWrStore } from "@/app/store";
import { initValInUsage, initValOutUsage, subTotalObj, totalObj, type FcPrfType, type PrfType } from "@/app/store/prfmncReg/pbnstPrfmncReg/type";
import { CHART_FORM, ContentsDiv, MainChart, ScrollArea, SelectBox, UiTable, type ChartConfig, type ReturnType } from "@common/business_components";
import ListDiv from "@common/business_components/ui/list_div";
import { useEffect, useRef, useState } from "react";
import { Toaster } from "react-hot-toast";
interface PbnstPrfmncSttnPopupProps {
isVisible?: boolean;
}
export default function PbnstPrfmncSttnPopup({ isVisible = true }: PbnstPrfmncSttnPopupProps) {
const { code, tableHeadMaker, pbnstPrfmncSttn, pbnstPrfmncInit, pbnstPrfmncSearch, prfmnc } = useWrStore((state) => state);
const { monthHeadList } = prfmnc
const { pbnstPrfHeadList, pbnstPrfmncRes, prfmncRes, rawData, selectYearList } = pbnstPrfmncSttn;
const [isChHead, setIsChHead] = useState(false);
const [fromDate, setFromDate] = useState<string>(String(new Date().getFullYear()));
const [toDate, setToDate] = useState<string>(String(new Date().getFullYear()));
const [year, setYear] = useState<string>(String(new Date().getFullYear()));
const [bdstTable, setbdstTable] = useState<FcPrfType[]>();
const ref = useRef<ReturnType>({} as ReturnType);
const [swtrmtFcltNm, setSwtrmtFcltNm] = useState<string>();
const [chartConfig, setChartConfig] = useState<ChartConfig>({});
const [chartData, setChartData] = useState<dnChartForm[]>([]);
const [dnInUsage, setDnInUsage] = useState<dnChartForm[]>([]);
const [dnInChartConfig, setDnInChartConfig] = useState<ChartConfig>({});
const [dnOutUsage, setDnOutUsage] = useState<dnChartForm[]>([]);
const [dnOutChartConfig, setDnOutChartConfig] = useState<ChartConfig>({});
const [barData, setBarData] = useState<barChartForm[]>([]);
const [barChartConfig, setBarChartConfig] = useState<ChartConfig>({});
const [prfData, setPrfData] = useState<any[]>();
const [lnInChartConfig, setLnInChartConfig] = useState<ChartConfig>({});
const [lnOutChartConfig, setLnOutChartConfig] = useState<ChartConfig>({});
const [inLineData, setInLineData] = useState<{
chartData1: lineChartDataProps[];
chartData2: lineChartDataProps[];
chartData3: lineChartDataProps[];
chartData4: lineChartDataProps[];
chartData5: lineChartDataProps[];
chartData6: lineChartDataProps[];
chartData7: lineChartDataProps[];
chartData8: lineChartDataProps[];
chartData9: lineChartDataProps[];
}>();
// const [lnInChartConfig, setLnInChartConfig] = useState<ChartConfig>({});
const [outLineData, setOutLineData] = useState<{
chartData1: lineChartDataProps[];
chartData2: lineChartDataProps[];
chartData3: lineChartDataProps[];
chartData4: lineChartDataProps[];
chartData5: lineChartDataProps[];
chartData6: lineChartDataProps[];
}>();
type dnChartForm = { label: string, val: number, fill?: string };
type barChartForm = { label: string, "장내용수": number, "장외용수": number };
type lineChartDataProps = { label: string, val: number, color: string };
const prfTableData = (prfList: PrfType[], year: string) => {
const inUsageData = initValInUsage;
const outUsageData = initValOutUsage;
const inSubtotal = subTotalObj;
const outSubtotal = subTotalObj;
console.log(' prfList ', prfList)
const prfFilter = prfList?.filter((e) => e.ym.slice(0, 4) === year)
if (prfList && prfFilter.length > 0) {
prfList?.forEach((prfItem) => {
const month = Number(prfItem?.ym?.slice(4)); // "MM"
const monthKey = `${month}월`;
inUsageData.forEach((inData: any) => {
const usageKey = inData.usage;
const value = (prfItem as any)[usageKey] ?? 0;
inData[monthKey] = value;
inData.total += value;
if (inSubtotal && usageKey !== "subtotal") {
(inSubtotal as any)[monthKey] =
((inSubtotal as any)[monthKey] ?? 0) + value;
inSubtotal.total += value;
}
});
outUsageData.forEach((outData: any) => {
const usageKey = outData.usage;
const value = (prfItem as any)[usageKey] ?? 0;
outData[monthKey] = value;
outData.total += value;
if (usageKey !== "subtotal") {
(outSubtotal as any)[monthKey] =
((outSubtotal as any)[monthKey] ?? 0) + value;
outSubtotal.total += value;
}
});
});
inUsageData.push(inSubtotal);
outUsageData.push(outSubtotal);
const usageTotalObj: { [key: string]: any } = totalObj;
for (const key in usageTotalObj) {
if (["usage", "gubun", "edit"].includes(key)) continue;
const val =
(Number((inSubtotal as any)[key]) || 0) +
(Number((outSubtotal as any)[key]) || 0);
(usageTotalObj as any)[key] = val;
}
const tableData = [...inUsageData, ...outUsageData, usageTotalObj];
setPrfData(tableData);
} else {
setPrfData([]);
}
}
useEffect(() => {
let wshwtr = 0;
let coolUswtr = 0;
let cleanUswtr = 0;
let drwtUswtr = 0;
let dltnUswtr = 0;
let ithlRswtr = 0;
let ithlEtcUswtr = 0;
let cleanToilUswtr = 0;
let resn = 0;
let lndwtr = 0;
let hydUswtr = 0;
let ugwtChr = 0;
let farmUswtr = 0;
let rvrKeepUswtr = 0;
let indstUswtr = 0;
if (rawData) {
rawData.forEach((item) => {
prfTableData(item.prfList, year);
item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => {
wshwtr += element.wshwtr; //세척수
coolUswtr += element.coolUswtr; //냉각수
cleanUswtr += element.cleanUswtr; //청소수
drwtUswtr += element.drwtUswtr; //식수대
dltnUswtr += element.dltnUswtr //희석용수
ithlRswtr += element.ithlRswtr //중수도
ithlEtcUswtr += element.ithlEtcUswtr //기타
cleanToilUswtr += element.cleanToilUswtr //청소화장실용수
resn += element.resn //세척살수용수
lndwtr += element.lndwtr; //조경용수
hydUswtr += element.hydUswtr; //친수용수
ugwtChr += element.ugwtChr; //지하수충전
farmUswtr += element.farmUswtr //농업용수
rvrKeepUswtr += element.rvrKeepUswtr //하천등유지용수
indstUswtr += element.indstUswtr //공업용수
});
})
const data: dnChartForm[] = []
data?.push({ label: "세척수", val: wshwtr, fill: "red" })
data?.push({ label: "냉각수", val: coolUswtr, fill: "orange" })
data?.push({ label: "청소수", val: cleanUswtr, fill: "yellow" })
data?.push({ label: "식수대", val: drwtUswtr, fill: "green" })
data?.push({ label: "희석용수", val: dltnUswtr, fill: "skyblue" })
data?.push({ label: "중수도", val: ithlRswtr, fill: "blue" })
data?.push({ label: "기타", val: ithlEtcUswtr, fill: "purple" })
data?.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "red" })
data?.push({ label: "세척살수용수", val: resn, fill: "orange" })
data?.push({ label: "조경용수", val: lndwtr, fill: "yellow" })
data?.push({ label: "친수용수", val: hydUswtr, fill: "green" })
data?.push({ label: "지하수충전", val: ugwtChr, fill: "skyblue" })
data?.push({ label: "농업용수", val: farmUswtr, fill: "blue" })
data?.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "purple" })
data?.push({ label: "공업용수", val: indstUswtr, fill: "black" })
setChartData(data);
}
}, [rawData, year])
useEffect(() => {
const newChartConfig = chartData.reduce((config, element) => {
config[element.label] = {
label: element.label,
fill: element.fill
};
return config;
}, {} as Record<string, { label: string; fill?: string }>) satisfies ChartConfig;
setChartConfig(newChartConfig);
}, [chartData]);
// 장내용수 도넛차트
useEffect(() => {
let wshwtr = 0;
let coolUswtr = 0;
let cleanUswtr = 0;
let drwtUswtr = 0;
let dltnUswtr = 0;
let ithlRswtr = 0;
let ithlEtcUswtr = 0;
let cleanToilUswtr = 0;
let resn = 0;
if (rawData) {
rawData.forEach((item) => {
item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => {
wshwtr += element.wshwtr; //세척수
coolUswtr += element.coolUswtr; //냉각수
cleanUswtr += element.cleanUswtr; //청소수
drwtUswtr += element.drwtUswtr; //식수대
dltnUswtr += element.dltnUswtr //희석용수
ithlRswtr += element.ithlRswtr //중수도
ithlEtcUswtr += element.ithlEtcUswtr //기타
cleanToilUswtr += element.cleanToilUswtr //청소화장실용수
resn += element.resn //세척살수용수
});
})
const data: dnChartForm[] = []
data?.push({ label: "세척수", val: wshwtr, fill: "yellow" })
data?.push({ label: "냉각수", val: coolUswtr, fill: "green" })
data?.push({ label: "청소수", val: cleanUswtr, fill: "yellow" })
data?.push({ label: "식수대", val: drwtUswtr, fill: "green" })
data?.push({ label: "희석용수", val: dltnUswtr, fill: "yellow" })
data?.push({ label: "중수도", val: ithlRswtr, fill: "green" })
data?.push({ label: "기타", val: ithlEtcUswtr, fill: "yellow" })
data?.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "green" })
data?.push({ label: "세척살수용수", val: resn, fill: "yellow" })
setDnInUsage(data);
}
}, [rawData, year])
useEffect(() => {
const newChartConfig = dnInUsage.reduce((config, element) => {
config[element.label] = {
label: element.label,
fill: element.fill
};
return config;
}, {} as Record<string, { label: string; color: string; }>) satisfies ChartConfig;
setDnInChartConfig(newChartConfig);
}, [dnInUsage]);
// 장외용수 도넛차트
useEffect(() => {
let lndwtr = 0;
let hydUswtr = 0;
let ugwtChr = 0;
let farmUswtr = 0;
let rvrKeepUswtr = 0;
let indstUswtr = 0;
if (rawData) {
rawData.forEach((item) => {
item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => {
lndwtr += element.lndwtr; //조경용수
hydUswtr += element.hydUswtr; //친수용수
ugwtChr += element.ugwtChr; //지하수충전
farmUswtr += element.farmUswtr //농업용수
rvrKeepUswtr += element.rvrKeepUswtr //하천등유지용수
indstUswtr += element.indstUswtr //공업용수
});
})
const data: dnChartForm[] = []
data?.push({ label: "조경용수", val: lndwtr, fill: "yellow" })
data?.push({ label: "친수용수", val: hydUswtr, fill: "black" })
data?.push({ label: "지하수충전", val: ugwtChr, fill: "yellow" })
data?.push({ label: "농업용수", val: farmUswtr, fill: "black" })
data?.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "yellow" })
data?.push({ label: "공업용수", val: indstUswtr, fill: "black" })
setDnOutUsage(data);
}
}, [rawData, year])
useEffect(() => {
const newChartConfig = dnOutUsage.reduce((config, element) => {
config[element.label] = {
label: element.label,
fill: element.fill
};
return config;
}, {} as Record<string, { label: string; color: string; }>) satisfies ChartConfig;
setDnOutChartConfig(newChartConfig);
}, [dnOutUsage]);
//월별 장내외 용수 총 실적 막대차트
useEffect(() => {
let wshwtr = 0;
let coolUswtr = 0;
let cleanUswtr = 0;
let drwtUswtr = 0;
let dltnUswtr = 0;
let ithlRswtr = 0;
let ithlEtcUswtr = 0;
let cleanToilUswtr = 0;
let resn = 0;
let lndwtr = 0;
let hydUswtr = 0;
let ugwtChr = 0;
let farmUswtr = 0;
let rvrKeepUswtr = 0;
let indstUswtr = 0;
let inUsageSum = 0;
let outUsageSum = 0;
let inUsageArr = [];
let outUsageArr = [];
if (rawData) {
const data: barChartForm[] = []
rawData.forEach((item) => {
item.prfList
.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => {
wshwtr = element.wshwtr; //세척수
coolUswtr = element.coolUswtr; //냉각수
cleanUswtr = element.cleanUswtr; //청소수
drwtUswtr = element.drwtUswtr; //식수대
dltnUswtr = element.dltnUswtr //희석용수
ithlRswtr = element.ithlRswtr //중수도
ithlEtcUswtr = element.ithlEtcUswtr //기타
cleanToilUswtr = element.cleanToilUswtr //청소화장실용수
resn = element.resn //세척살수용수
lndwtr = element.lndwtr; //조경용수
hydUswtr = element.hydUswtr; //친수용수
ugwtChr = element.ugwtChr; //지하수충전
farmUswtr = element.farmUswtr //농업용수
rvrKeepUswtr = element.rvrKeepUswtr //하천등유지용수
indstUswtr = element.indstUswtr //공업용수
inUsageSum = wshwtr + coolUswtr + cleanUswtr + drwtUswtr + dltnUswtr + ithlRswtr + ithlEtcUswtr + cleanToilUswtr + resn;
inUsageArr.push(inUsageSum)
outUsageSum = lndwtr + hydUswtr + ugwtChr + farmUswtr + rvrKeepUswtr + indstUswtr;
outUsageArr.push(outUsageSum)
const month = Number(element.ym.slice(4, 6))
data.push({ label: `${month}월`, "장내용수": inUsageSum, "장외용수": outUsageSum })
},
);
})
setBarData(data);
}
}, [rawData, year])
useEffect(() => {
const newChartConfig = barData.reduce((config, element) => {
config[element.label] = {
label: element.label,
};
return config;
}, {} as Record<string, { label: string }>) satisfies ChartConfig;
setBarChartConfig(newChartConfig);
}, [barData]);
//월별 장내 용수 실적 라인차트
useEffect(() => {
if (rawData) {
const wshwtrData: lineChartDataProps[] = []
const coolUswtrData: lineChartDataProps[] = []
const cleanUswtrData: lineChartDataProps[] = []
const drwtUswtrData: lineChartDataProps[] = []
const dltnUswtrData: lineChartDataProps[] = []
const ithlRswtrData: lineChartDataProps[] = []
const ithlEtcUswtrData: lineChartDataProps[] = []
const cleanToilUswtrData: lineChartDataProps[] = []
const resnData: lineChartDataProps[] = []
rawData.forEach((item) => {
item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => {
const month = Number(element.ym.slice(4, 6))
wshwtrData.push({ label: `${month}월`, val: element.wshwtr, color: "red" })
coolUswtrData.push({ label: `${month}월`, val: element.coolUswtr, color: "red" })
cleanUswtrData.push({ label: `${month}월`, val: element.cleanUswtr, color: "red" })
drwtUswtrData.push({ label: `${month}월`, val: element.drwtUswtr, color: "red" })
dltnUswtrData.push({ label: `${month}월`, val: element.dltnUswtr, color: "red" })
ithlRswtrData.push({ label: `${month}월`, val: element.ithlRswtr, color: "red" })
ithlEtcUswtrData.push({ label: `${month}월`, val: element.ithlEtcUswtr, color: "red" })
cleanToilUswtrData.push({ label: `${month}월`, val: element.cleanToilUswtr, color: "red" })
resnData.push({ label: `${month}월`, val: element.resn, color: "red" })
},
);
})
const rtndata = {
chartData1: wshwtrData,
chartData2: coolUswtrData,
chartData3: cleanUswtrData,
chartData4: drwtUswtrData,
chartData5: dltnUswtrData,
chartData6: ithlRswtrData,
chartData7: ithlEtcUswtrData,
chartData8: cleanToilUswtrData,
chartData9: resnData
}
setInLineData(rtndata)
}
}, [rawData, year])
useEffect(() => {
if (inLineData?.chartData1) {
const newChartConfig = {
chartData1: {
label: '세척수',
color: "red",
},
chartData2: {
label: '냉각수',
color: "orange",
},
chartData3: {
label: '청소수',
color: "yellow",
},
chartData4: {
label: '식수대',
color: "pink",
},
chartData5: {
label: '희석용수',
color: "purple",
},
chartData6: {
label: '중수도',
color: "green",
},
chartData7: {
label: '기타',
color: "blue",
},
chartData8: {
label: '청소화장실용수',
color: "magenta",
},
chartData9: {
label: '세척살수용수',
color: "skyblue",
},
} satisfies ChartConfig;
setLnInChartConfig(newChartConfig);
}
}, [inLineData]);
//월별 장외 용수 실적 라인차트
useEffect(() => {
if (rawData) {
const lndwtrData: lineChartDataProps[] = []
const hydUswtrData: lineChartDataProps[] = []
const ugwtChrData: lineChartDataProps[] = []
const farmUswtrData: lineChartDataProps[] = []
const rvrKeepUswtrData: lineChartDataProps[] = []
const indstUswtrData: lineChartDataProps[] = []
rawData.forEach((item) => {
item.prfList.filter((e) => e.ym.slice(0, 4) === year).forEach((element) => {
const month = Number(element.ym.slice(4, 6))
lndwtrData.push({ label: `${month}월`, val: element.lndwtr, color: "red" })
hydUswtrData.push({ label: `${month}월`, val: element.hydUswtr, color: "red" })
ugwtChrData.push({ label: `${month}월`, val: element.ugwtChr, color: "red" })
farmUswtrData.push({ label: `${month}월`, val: element.farmUswtr, color: "red" })
rvrKeepUswtrData.push({ label: `${month}월`, val: element.rvrKeepUswtr, color: "red" })
indstUswtrData.push({ label: `${month}월`, val: element.indstUswtr, color: "red" })
},
);
})
setOutLineData({
chartData1: lndwtrData,
chartData2: hydUswtrData,
chartData3: ugwtChrData,
chartData4: farmUswtrData,
chartData5: rvrKeepUswtrData,
chartData6: indstUswtrData,
})
}
}, [rawData, year])
useEffect(() => {
if (outLineData?.chartData1) {
const newChartConfig = {
chartData1: {
label: '조경용수',
color: "red",
},
chartData2: {
label: '친수용수',
color: "orange",
},
chartData3: {
label: '지하수충전',
color: "yellow",
},
chartData4: {
label: '농업용수',
color: "green",
},
chartData5: {
label: '하천등유지용수',
color: "blue",
},
chartData6: {
label: '공업용수',
color: "purple",
},
} satisfies ChartConfig;
setLnOutChartConfig(newChartConfig);
}
}, [outLineData]);
useEffect(() => {
if (!pbnstPrfmncSttn.isInit) {
pbnstPrfmncInit("1", "11");
}
}, [pbnstPrfmncSttn.isInit, pbnstPrfmncInit]);
useEffect(() => {
setbdstTable(prfmncRes);
}, [prfmncRes]);
return (
<>
<ContentsDiv>
<ListDiv>
<hr />
<h3 className="mt-4 ml-2 text-2xl font-medium text-label">하수재이용실적 상세현황</h3>
<SelectBox
className="w-1/12 text-2xl mt-4 ml-2"
label="연도"
selectClass={""}
selectArray={selectYearList}
value={year}
onSelectValue={(val: string | number) => setYear(String(val))} />
<h1 className="mt-4 ml-2">장내·외 용도별 하수재이용실적 현황(계)</h1>
<div className="flex">
<div className="w-1/3">
<MainChart chartConfig={chartConfig} chartData={chartData} chartTitle={"장내·외 용수 총 실적"} chartForm={CHART_FORM.DN} />
</div>
<div className="w-1/3">
<MainChart chartConfig={dnInChartConfig} chartData={dnInUsage} chartTitle={"장내용수 실적"} chartForm={CHART_FORM.DN} />
</div>
<div className="w-1/3">
<MainChart chartConfig={dnOutChartConfig} chartData={dnOutUsage} chartTitle={"장외용수 실적"} chartForm={CHART_FORM.DN} />
</div>
</div>
<h1 className="">장내·외 월별 하수재이용실적 현황</h1>
<div className="flex">
<div className="w-1/3">
<MainChart chartConfig={barChartConfig} chartData={barData} chartTitle={"월별 장내·외 용수 총 실적"} chartForm={CHART_FORM.DOUBLEBAR} />
</div>
<div className="w-1/3">
<MainChart chartConfig={lnInChartConfig} chartData={inLineData} chartTitle={"월별 장내 용수 실적"} chartForm={CHART_FORM.LINE} />
</div>
<div className="w-1/3">
<MainChart chartConfig={lnOutChartConfig} chartData={outLineData} chartTitle={"월별 장외 용수 총 실적"} chartForm={CHART_FORM.LINE} />
</div>
</div>
{/* 하수재이용실적 현황 - 조건부 렌더링 */}
{isVisible && (
<>
<h1 className="">하수재이용실적 현황</h1>
<ListDiv className="col-span-8 pr-6 ">
<ScrollArea className="h-[60vh]">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
{"하수처리수재이용 실적(m3) 등록결과"}
</h3>
</div>
<UiTable
headName={undefined}
tableData={prfData || []}
headlist={monthHeadList}
pageSize={0}
total={0}
cellClick={undefined}
onPageChange={function (page: number): void {
throw new Error("Function not implemented.");
}}>
</UiTable>
</ScrollArea>
</ListDiv>
</>
)}
</ListDiv>
</ContentsDiv>
<Toaster position="top-center" />
</>
)
}
+421
View File
@@ -0,0 +1,421 @@
"use client";
import { useWrStore } from "@/app/store";
import {
rainZod,
selectClass,
TEST_UUID,
type customRainBdst,
} from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { DatePicker, FormUi } from "@common/business_components";
import { Form, FormField, SelectBox } from "@common/business_components/ui";
import { Button, Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm, useWatch, type UseFormReturn } from "react-hook-form";
import { z } from "zod";
import BdstReg from "@/components/ui/form/fcltReg/bdstReg/bdstReg";
import {
formatCommaNumber,
handleCommaNumberChange,
} from "@/components/lib/func/commom";
import { formH1Class, formH2Class } from "@/components/lib/class/comClass";
import { Label } from "@common/business_components/ui/form/lib/label";
export default function RainPrfmncSttnPopup({
rainForm,
isModal,
}: {
rainForm: customRainBdst;
isModal: boolean;
}) {
const { code, rainBdstReg, rainBdstUpdate, rain } = useWrStore(
(state) => state
);
const { rainFcltCd } = code;
const defaultValues: z.infer<typeof rainZod> = rainZod.parse({});
const form = useForm<z.infer<typeof rainZod>>({
resolver: zodResolver(rainZod),
defaultValues: defaultValues,
});
const [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctYn] = form.watch([
"reuseBuilding.0.grntchBdstCertYn",
"reuseBuilding.0.instlCstSprtYn",
"reuseBuilding.0.rainyObj.filtrTrtmfctYn",
]);
const cleanRainData = (raw: any): z.infer<typeof rainZod> => {
return {
...raw,
frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null),
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 → ""
rainObj: {
...item?.rainObj,
},
})),
};
};
useEffect(() => {
if (grntchBdstCertYn === "N") {
form.setValue("reuseBuilding.0.grntchBdstCertYr", "");
form.setValue("reuseBuilding.0.grntchBdstCertNo", "");
form.setValue("reuseBuilding.0.grntchBdstCertGrd", "");
} else if (instlCstSprtYn === "N") {
form.setValue("reuseBuilding.0.instlCstSprtAmt", 0);
} else if (filtrTrtmfctYn === "N") {
form.setValue("reuseBuilding.0.rainyObj.filtrTrtmfctCpct", 0);
form.setValue("reuseBuilding.0.rainyObj.filtrTrtmfctPrs", "");
}
}, [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctYn]);
// form 값이 변경될 때마다 로그 출력
useEffect(() => {
const subscription = form.watch((value) => {
});
return () => subscription.unsubscribe();
}, [form]);
useEffect(() => {
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
const onSubmit = (values: z.infer<typeof rainZod>) => {
if (isModal) {
rainBdstReg(values, "41");
} else {
rainBdstUpdate(values, "41");
}
};
useEffect(() => {
if (rainZod) form.reset(cleanRainData(rainForm));
}, [rainForm]);
return (
<div className="!mx-[1.6rem] !p-0r">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<div className="grid grid-cols-12 flex items-center card-header">
<h1 className="text-2xl font-medium text-label col-span-10">
{isModal ? "건축물 운영정보" : "빗물이용시설 정보"}
</h1>
</div>
</div>
{isModal && rainForm != undefined ? <BdstReg form={form} /> : <></>}
{isModal ? (
<h1 className="pl-3 text-xl col-span-10 mt-4">
빗물이용시설 정보{" "}
</h1>
) : (
<></>
)}
<h1 className={formH1Class}>
기본정보
</h1>
<div className="grid grid-cols-4 gap-2 mt-4">
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.instlCmptnDt"
render={({ field }) => (
<FormUi label="설치완료일">
<DatePicker
mode="single"
field={{
value:
field.value && !isNaN(new Date(field.value).getTime())
? new Date(field.value)
: new Date(), // Provide a default Date value
onChange: (date) => field.onChange(date),
}}
selectClass={"w-full"}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.instlCst"
render={({ field }) => (
<FormUi label="설치비">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className="">
<h1 className={formH1Class}>
빗물이용시설 정보
</h1>
</div>
<div>
<h2 className={formH2Class}>집수면</h2>
</div>
<div className="grid grid-cols-10 gap-4 mt-4 ">
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.rainyObj.pntfColKnd"
render={({ field }) => (
<FormUi label="종류">
<SelectBox
className={""}
selectClass={selectClass}
label={""}
selectArray={rainFcltCd}
onSelectValue={field.onChange}
value={field.value ?? rainFcltCd[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.rainyObj.pntfColArea"
render={({ field }) => (
<FormUi label="면적(m³)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
/>
</FormUi>
)}
/>
</div>
</div>
<div>
<h2 className={formH2Class}>여과 등 처리시설</h2>
</div>
<div className="grid grid-cols-12 gap-2 mt-4">
<div className="col-span-4">
<FormField
control={form.control}
name="reuseBuilding.0.rainyObj.filtrTrtmfctYn"
render={({ field }) => (
<FormUi label="유무">
<SelectBox
className={""}
selectClass={selectClass}
label={""}
selectArray={code.yn}
onSelectValue={field.onChange}
value={field.value ?? code.yn[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="reuseBuilding.0.rainyObj.filtrTrtmfctCpct"
render={({ field }) => (
<FormUi label="용량(m³/일)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={filtrTrtmfctYn !== "Y"}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="reuseBuilding.0.rainyObj.filtrTrtmfctPrs"
render={({ field }) => (
<FormUi label="처리공정">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type=""
value={field.value ?? ""}
onChange={field.onChange}
readOnly={filtrTrtmfctYn !== "Y"}
/>
</FormUi>
)}
/>
</div>
</div>
<div className=" my-2">
<h1 className={formH1Class}>
인센티브 정보
</h1>
</div>
<div>
<h2 className={formH2Class}>설치비 지원</h2>
</div>
<div className="grid grid-cols-10 gap-2 mt-4">
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.instlCstSprtYn"
render={({ field }) => (
<FormUi label="지원 여부">
<SelectBox
className={""}
selectClass={selectClass}
label={""}
selectArray={code.yn}
onSelectValue={field.onChange}
value={field.value ?? code.yn[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-5">
<FormField
control={form.control}
name="reuseBuilding.0.instlCstSprtAmt"
render={({ field }) => (
<FormUi label="지원금액(원)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={instlCstSprtYn !== "Y"}
/>
</FormUi>
)}
/>
</div>
</div>
<div>
<h2 className={formH2Class}>녹색건축물 인증</h2>
</div>
<div className="grid grid-cols-6 gap-2 mt-4">
<div className="col-span-3">
<FormField
control={form.control}
name="reuseBuilding.0.grntchBdstCertYn"
render={({ field }) => (
<FormUi label="인증여부">
<SelectBox
className={""}
selectClass={selectClass}
label={""}
selectArray={code.yn}
onSelectValue={field.onChange}
value={field.value ?? code.yn[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="reuseBuilding.0.grntchBdstCertYr"
render={({ field }) => (
<FormUi label="인증년도">
<Input
className={'w-full !text-xl'}
placeholder="2024"
type=""
value={field.value ?? ""}
onChange={field.onChange}
readOnly={grntchBdstCertYn !== "Y"}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3 ">
<FormField
control={form.control}
name="reuseBuilding.0.grntchBdstCertNo"
render={({ field }) => (
<FormUi label="인증번호">
<Input
className={'w-full !text-xl'}
placeholder="ab1234678"
type=""
value={field.value ?? ""}
onChange={field.onChange}
readOnly={grntchBdstCertYn !== "Y"}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="reuseBuilding.0.grntchBdstCertGrd"
render={({ field }) => (
<FormUi label="인증등급">
<Input
className={'w-full !text-xl'}
placeholder="A"
type=""
value={field.value ?? ""}
onChange={field.onChange}
readOnly={grntchBdstCertYn !== "Y"}
/>
</FormUi>
)}
/>
</div>
</div>
</form>
</Form>
</div>
);
}
+404
View File
@@ -0,0 +1,404 @@
"use client";
import {
DatePicker,
Form,
FormField,
FormUi,
SelectBox,
} from "@common/business_components/ui";
import { Button, Input } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useWrStore } from "@/app/store";
import { selectClass } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import {
rswtZod,
type customRswtBdst,
} from "@/app/store/ruseFcltMng/fcltReg/rswtReg";
import { useEffect } from "react";
import BdstReg from "@/components/ui/form/fcltReg/bdstReg/bdstReg";
import {
formatCommaNumber,
handleCommaNumberChange,
} from "@/components/lib/func/commom";
import { formH1Class, formH2Class } from "@/components/lib/class/comClass";
import { Label } from "@common/business_components/ui/form/lib/label";
export default function RswtrPrfmncsSttnPopup({
rswtForm,
isModal,
}: {
rswtForm: customRswtBdst;
isModal: boolean;
}) {
const { rswt, code, rswtBdstUpdate, rswtBdstReg } = useWrStore(
(state) => state
);
const defaultValues: z.infer<typeof rswtZod> = rswtZod.parse({});
const form = useForm<z.infer<typeof rswtZod>>({
resolver: zodResolver(rswtZod),
defaultValues: defaultValues,
});
const [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctProcs] = form.watch([
"reuseBuilding.0.grntchBdstCertYn",
"reuseBuilding.0.instlCstSprtYn",
"reuseBuilding.0.rswtrObj.filtrTrtmfctProcs",
]);
const onSubmit = (values: z.infer<typeof rswtZod>) => {
if (isModal) {
rswtBdstReg(values, "41");
} else {
rswtBdstUpdate(values, "41");
}
};
const cleanRswtData = (raw: any): z.infer<typeof rswtZod> => {
return {
...raw,
frstArchPrmsnDt: new Date(raw?.frstArchPrmsnDt || null),
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: {
...item?.rswtrObj,
},
})),
};
};
useEffect(() => {
console.log("form errors:", form.formState.errors);
}, [form.formState.errors]);
useEffect(() => {
console.log("filtrTrtmfctProcs ", filtrTrtmfctProcs);
if (grntchBdstCertYn === "N") {
form.setValue("reuseBuilding.0.grntchBdstCertYr", "");
form.setValue("reuseBuilding.0.grntchBdstCertNo", "");
form.setValue("reuseBuilding.0.grntchBdstCertGrd", "");
} else if (instlCstSprtYn === "N") {
form.setValue("reuseBuilding.0.instlCstSprtAmt", 0);
}
}, [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctProcs]);
useEffect(() => {
if (rswtZod) form.reset(cleanRswtData(rswtForm));
}, [rswtForm]);
return (
<div className="!mx-[1.6rem] !p-0r">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* <div className="flex justify-between items-center mx-3"> */}
<div>
<div className="grid grid-cols-12 flex items-center card-header">
<h1 className="text-2xl font-medium text-label col-span-10">
건축물 운영정보
</h1>
</div>
</div>
{isModal && rswtForm != undefined ? <BdstReg form={form} /> : <></>}
{isModal ? (
<h1 className="pl-3 text-xl col-span-10 mt-4 ">
중수도 시설 정보{" "}
</h1>
) : (
<></>
)}
<h1 className={formH1Class}>기본정보</h1>
<div className="grid grid-cols-4 gap-2 mt-4">
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.instlCmptnDt"
render={({ field }) => (
<FormUi label="설치완료일">
<DatePicker
mode="single"
field={{
value:
field.value && !isNaN(new Date(field.value).getTime())
? new Date(field.value)
: new Date(), // Provide a default Date value
onChange: (date) => field.onChange(date),
}}
selectClass={"w-full"}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.instlCst"
render={({ field }) => (
<FormUi label="설치비">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className="grid grid-cols-4 gap-2 mt-4">
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.rswtrObj.wtrUsqty"
render={({ field }) => (
<FormUi label="물사용량(m³)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-2">
<FormField
control={form.control}
name="reuseBuilding.0.rswtrObj.oblgTrgtWtrUsqty"
render={({ field }) => (
<FormUi label="의무대상 물사용량(m³)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className=" my-4">
<h1 className={formH1Class}>중수도 시설 정보</h1>
</div>
<div className="grid grid-cols-12 gap-1 mt-4">
<div className="col-span-4">
<FormField
control={form.control}
name="reuseBuilding.0.rswtrObj.filtrTrtmfctCpct"
render={({ field }) => (
<FormUi label="처리시설 용량">
<Input
className={'w-full !text-xl'}
placeholder="0"
type="number"
value={field.value ?? ""}
onChange={(e)=>{field.onChange(Number(e.target.value))} }
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="reuseBuilding.0.rswtrObj.orgwtKndCd"
render={({ field }) => (
<FormUi label="원수 종류">
<SelectBox
className={""}
label={""}
selectClass={selectClass}
selectArray={code.orgwtKndCd}
onSelectValue={field.onChange}
value={field.value ?? code.orgwtKndCd[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-4">
<FormField
control={form.control}
name="reuseBuilding.0.rswtrObj.filtrTrtmfctProcs"
render={({ field }) => (
<FormUi label="주요처리공정">
<Input
className={'w-full !text-xl'}
placeholder="주요처리공정"
type=""
value={field.value ?? ""}
onChange={field.onChange}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
<div className=" my-2">
<h1 className={formH1Class}>인센티브 정보</h1>
</div>
<div>
<h2 className={formH2Class}>설치비 지원</h2>
</div>
<div className="grid grid-cols-2 gap-2 mt-4">
<div className="col-span-1">
<FormField
control={form.control}
name="reuseBuilding.0.instlCstSprtYn"
render={({ field }) => (
<FormUi label="설치비 지원여부">
<SelectBox
className={""}
selectClass={selectClass}
label={""}
selectArray={code.yn}
onSelectValue={field.onChange}
value={field.value ?? code.yn[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-1">
<FormField
control={form.control}
name="reuseBuilding.0.instlCstSprtAmt"
render={({ field }) => (
<FormUi label="지원금액(원)">
<Input
className={'w-full !text-xl'}
placeholder="1,000,000"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={instlCstSprtYn !== "Y"}
/>
</FormUi>
)}
/>
</div>
</div>
<div>
<h2 className={formH2Class}>녹색건축물 인증</h2>
</div>
<div className="grid grid-cols-6 gap-2 mt-4">
<div className="col-span-3">
<FormField
control={form.control}
name="reuseBuilding.0.grntchBdstCertYn"
render={({ field }) => (
<FormUi label="인증여부">
<SelectBox
className={""}
selectClass={selectClass}
label={""}
selectArray={code.yn}
onSelectValue={field.onChange}
value={field.value ?? code.yn[0]?.val}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="reuseBuilding.0.grntchBdstCertYr"
render={({ field }) => (
<FormUi label="인증년도">
<Input
className={'w-full !text-xl'}
placeholder="2024"
type="text"
{...field}
value={field.value ?? ""}
onChange={field.onChange}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="reuseBuilding.0.grntchBdstCertNo"
render={({ field }) => (
<FormUi label="인증번호">
<Input
className={'w-full !text-xl'}
placeholder="ab1234678"
type=""
value={field.value ?? ""}
onChange={field.onChange}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
<div className="col-span-3">
<FormField
control={form.control}
name="reuseBuilding.0.grntchBdstCertGrd"
render={({ field }) => (
<FormUi label="인증등급">
<Input
className={'w-full !text-xl'}
placeholder="A"
type=""
value={field.value ?? ""}
onChange={field.onChange}
readOnly={true}
/>
</FormUi>
)}
/>
</div>
</div>
</form>
</Form>
</div>
);
}
+1
View File
@@ -0,0 +1 @@
export * from './map'
+47
View File
@@ -0,0 +1,47 @@
/**
* 관망도 API
*/
// const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}/v1/map`
const API_URL = "http://con.mind-one.co.kr:11083/sewerage-service/api/v1/map";
export type LyrStngProps = {
lyr_sn: string
lyr_nm: string
upper_lyr_sn: string
drawOrdr: string
flterSql: string
gobjTy: string
lyrTpNm: string
useYn: string
minViewSize: string
iconNm: string
lblAtrbNm: string
fontNm: string
fontIntnColor: string
fontExtnColor: string
fontExtnWdth: string
lnWdth: string
lnColor: string
polgnColor: string
initEprssYn: string
clickPsblYn: string
}
export const mapService = {
//레이어 설정 조회
getLyrStngs: async () => {
const response = await fetch(`${API_URL}/lyrStngs/WR_NETWORK`, {
method: 'GET',
headers: {
'Accept': 'application/json',
},
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
}
+173 -45
View File
@@ -1,30 +1,162 @@
"use client";
import { useWrStore } from "@/app/store";
import { ruseUtzType } from "@/app/store/matDmd/ruseCaseMng/headList";
import { getCity } from "@/components/lib/func/commom";
import { excelDownload, getCity } from "@/components/lib/func/commom";
import RuseCaseMngExcel from "@/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal";
import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal";
import { ContentsDiv, SearchDiv, SearchFormLeft, SearchFormRight, SearchInput, SelectBox, Title, UiTable, type SelectDataType } from "@common/business_components/ui";
import {
ContentsDiv,
filePickedBlobArray,
SearchDiv,
SearchFormLeft,
SearchFormRight,
SearchInput,
SelectBox,
Title,
UiModal,
UiTable,
type SelectDataType,
} from "@common/business_components/ui";
import ListDiv from "@common/business_components/ui/list_div";
import { Button } from "@common/components/ui";
import { useState } from "react";
export default function RuseCaseMng() {
const { code,ruseCase} = useWrStore((state) => state);
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>()
const { code, ruseCaseMng, selectRuseCaseMng } = useWrStore((state) => state);
const { ruseCaseMngList } = ruseCaseMng;
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>([]);
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
const [selectUtz, setSelectUtz] = useState<string[] | []>()
const [searchTxt, setSearchTxt] = useState<string>()
const [sidoCd, setSidoCd] = useState<string>("");
const [sigunguCd, setSigunguCd] = useState<string>("");
const [selectUtz, setSelectUtz] = useState<string[] | []>(["조경용수"]);
const [name, setName] = useState<string>("");
// 물재이용사례 조회
const searchRuseCaseMng = async () => {
const res = await selectRuseCaseMng(
sidoCd ?? "",
sigunguCd ?? "",
selectUtz[0] ?? "",
name ?? ""
);
}
// 시도 선택
const getSelCity = (val: string | number) => {
const cityValue = String(val);
const cityText = code.cityCd.find((item: { val: string; text: string }) => item.val === cityValue)?.text;
setSidoCd(cityText || "");
const data = getCity(cityValue, code.sggCd);
setSelectSgg(data);
setObjSearch({ city: cityValue, sgg: '' });
setObjSearch({ city: cityValue, sgg: "" });
};
// 시군구 선택
const getSgg = (val: string | number) => {
setObjSearch({ ...objSearch, sgg: String(val) })
const sggValue = String(val);
const sggText = selectSgg?.find((item: { val: string; text: string }) => item.val === sggValue)?.text;
setSigunguCd(sggText || "");
setObjSearch({ ...objSearch, sgg: String(val) });
};
// 엑셀 등록 모달
const [isExcelOpen, setIsExcelOpen] = useState<{
isModal: boolean;
}>({ isModal: false });
// 다운로드
const download = () => {
searchRuseCaseMng();
fetch(`/sample_ruseCaseMng.xlsx`, {}).then(async (response) => {
if (!response.ok) {
throw Error(response.statusText);
}
// 받은 파일 바인너리 처리
const blobRet = await response.arrayBuffer();
const excelData1 = ruseCaseMngList.map((item) => {
return [
item.ruseBizNm,
item.bizBgngDate,
item.bizEndDate,
item.splyFcltKnd,
item.splyFcltNm,
item.splyFcltCpct,
item.dmdFcltNm,
item.dmdFcltAddr,
item.ruseFcltCpct,
item.ruseFcltPplLen,
item.ruseFcltUsg,
item.ruseFcltSplyCpct,
item.ruseFcltPrcsMsrc,
item.grnteWqUsg,
item.grnteWqGrssClfg,
item.grnteWqCombResdch,
item.grnteWqTrbt,
item.grnteWqBod,
item.grnteWqSmll,
item.grnteWqCrmty,
item.grnteWqTn,
item.grnteWqTp,
item.grnteWqPh,
item.grnteWqClIonQnt,
item.grnteWqCndctStat,
item.grnteWqRmrk,
item.reqBizGrssCst,
item.reqBizReuseFcltCst,
item.reqBizPplCst,
item.reqBizArchCst,
item.reqBizMchnCst,
item.reqBizMtrngCst,
item.reqBizGisCst,
item.reqBizCctvCst,
item.reqBizCmcnCst,
item.reqBizWstCst,
item.reqBizDesignCst,
item.reqBizSpvsCst,
item.reqBizSbsdCst,
item.reqBizEtc,
item.prcmPlanGrssCst,
item.prcmPlanNlep,
item.prcmPlanLcep,
item.prcmPlanBrdnCst,
item.prcmPlanEcqrRt,
item.clclnGrssCst,
item.clclnNlep,
item.clclnLcep,
item.clclnBrdnCst,
item.clclnEcqrRt,
];
});
const sheetParameter = [
{
data: excelData1,
headerNum: 2,
headerRange: 'A1:AX1',
sheetName: '물재이용사례',
startCol: 'A'
}
];
// 버퍼에 있는 엑셀데이터 전달 후 json으로 변환
await filePickedBlobArray(blobRet, '시설정보및운영현황황', sheetParameter);
});
}
// 양식받기
const sampleDownLoad = () => {
excelDownload(
[[]],
"물재이용사례 관리",
"sample_ruseCaseMng.xlsx",
"A1:AX1",
2
);
}
return (
<>
<ContentsDiv>
@@ -54,47 +186,32 @@ export default function RuseCaseMng() {
selectClass={""}
selectArray={ruseUtzType}
onSelectValue={(val) => {
if(val)
setSelectUtz([String(val)])}}
if (val) setSelectUtz([String(val)]);
}}
/>
<SearchInput
className={"text-2xl"}
label="수요처명"
textValue={""}
textValue={name}
setText={(e) => {
setSearchTxt(e)
setName(e);
}}
/>
</div>
</SearchFormLeft>
<SearchFormRight
className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 ">
<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={() => { searchRuseCaseMng() }}>
조회
</Button>
<Button
size={"sm"}
onClick={(e) => { }}
>
<Button size={"sm"} onClick={() => { setIsExcelOpen({ isModal: true }); }}>
엑셀등록
</Button>
<Button
size={"sm"}
onClick={(e) => { }}
>
<Button size={"sm"} onClick={() => { download() }}>
다운로드
</Button>
<Button
size={"sm"}
onClick={(e) => { }}
>
<Button size={"sm"} onClick={() => { sampleDownLoad() }}>
양식받기
</Button>
</div>
@@ -103,24 +220,35 @@ export default function RuseCaseMng() {
</SearchDiv>
</div>
</div>
<div className="grid grid-cols-6 gap-4 ">
<div className="grid grid-cols-6 gap-4">
<div className="col-span-6">
<ListDiv className="overflow-x-auto">
<UiTable
headName={"물재이용사례 현황"}
tableData={[]}
children={undefined}
headlist={ruseCase.ruseHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
}}
onPageChange={() => { }}
></UiTable>
<div className="w-[350%]">
<UiTable
headName={"물재이용사례 현황"}
tableData={ruseCaseMngList}
children={undefined}
headlist={ruseCaseMng.ruseHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {}}
onPageChange={() => {}}
/>
</div>
</ListDiv>
</div>
</div>
</ContentsDiv>
{/* 엑셀 등록 모달 */}
<UiModal
open={isExcelOpen.isModal}
onOpenChange={() => {
setIsExcelOpen({ isModal: !isExcelOpen.isModal })
}}
bodyClass={"w-[40vw] max-w-none"}
body={<RuseCaseMngExcel />}
/>
</>
)
);
}
+12
View File
@@ -153,6 +153,18 @@ export default function Home() {
</div>
</div>
</div>
{/* 시설 현황(지도) */}
<div>
<h2 className="text-xl font-semibold mb-4">시설 현황(지도)</h2>
<div className="grid grid-cols-12 gap-4">
<div className="col-span-2">
<Button className="w-full" onClick={() => {router.push('/map/htdflSttn')}}>
시설 현황(지도)
</Button>
</div>
</div>
</div>
</div>
</>
);
+180 -92
View File
@@ -29,9 +29,9 @@ import {
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";
import toast, { Toaster } from "react-hot-toast"; // toast 이미 import 되어 있음
import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg";
import { useFieldArray, useForm } from "react-hook-form";
import { useFieldArray, useForm, type FieldErrors } from "react-hook-form"; // FieldErrors 타입 추가
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
@@ -39,8 +39,8 @@ export default function PublicReuseFaciltyInsert() {
const {
code,
pbnst,
searchFclts,
pubHasuInit,
pbnstInsert,
pbnstRegClick,
pbnstRuseFcltUpdate,
pubHasuSearch,
@@ -50,17 +50,15 @@ export default function PublicReuseFaciltyInsert() {
const [currentTab, setCurrentTab] = useState<string>("mng");
const [isRuseReg, setIsRuseReg] = useState<{
isModal: boolean;
data: pubHasuType;
}>({ isModal: false, data: {} as pubHasuType });
}>({ isModal: 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>[];
}>({
@@ -76,6 +74,8 @@ export default function PublicReuseFaciltyInsert() {
control: form.control,
name: `items`,
});
const formWatch = form.watch("items");
useEffect(() => {
if (!pbnst.isInit) {
pubHasuInit("1", "41");
@@ -88,32 +88,84 @@ export default function PublicReuseFaciltyInsert() {
data.forEach((fclt) => {
bizs = [...bizs, ...fclt.fcBizs];
});
const pbnst =data.map((fclt) => {
return({
pbnstRuseFcltPk: fclt.pbnstRuseFcltPk,
fcltCpct: fclt.fcltCpct,
ruseCpct: fclt.ruseCpct,
splyExtraCpct: fclt.splyExtraCpct,
rgtr: fclt.rgtr,
mdfr: fclt.mdfr,
fcFiles: fclt.fcFiles,
fcOpers: fclt.fcOpers,
})
})
const pbnst = data.map((fclt) => {
return {
pbnstRuseFcltPk: fclt.pbnstRuseFcltPk,
fcltCpct: fclt.fcltCpct,
ruseCpct: fclt.ruseCpct,
splyExtraCpct: fclt.splyExtraCpct,
rgtr: fclt.rgtr,
mdfr: fclt.mdfr,
fcFiles: fclt.fcFiles,
fcOpers: fclt.fcOpers,
};
});
await pbnstRuseFcltUpdate(pbnst as customPbnst[]);
await pbnstRuseBizUpdate(bizs);
};
useEffect(() => {
if (code.sggCd.length) {
if (searchFclts) {
setbdstTable(tableData(searchFclts, { code }));
} else if (code.sggCd.length > 0 && pbnstHasuRes.length > 0) {
setbdstTable(tableData(pbnstHasuRes, { code }));
}
// 폼 유효성 검사 실패 시 호출될 함수
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 (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}`);
});
}
}, [code, pbnstHasuRes, searchFclts]);
};
useEffect(() => {
if (code.sggCd.length && pbnstHasuRes.length > 0) {
setbdstTable(tableData(pbnstHasuRes, { code }));
}
}, [code, pbnstHasuRes]);
const cellClick = (val: number | undefined) => {
if (val === undefined) return;
@@ -121,6 +173,8 @@ export default function PublicReuseFaciltyInsert() {
setSelectRow(pbnstHasuRes?.[val]);
if (selectRow) {
pbnstRegClick(selectRow);
} else {
console.error("선택된 행이 없습니다.");
}
};
@@ -129,12 +183,9 @@ export default function PublicReuseFaciltyInsert() {
};
useEffect(() => {
if (pbnst.pbnstRes&& pbnst.pbnstRes?.length>0) {
console.log("pbnst.pbnstRes", pbnstRes);
if (pbnst.pbnstRes && pbnst.pbnstRes?.length > 0) {
form.reset({ items: pbnstRegArrayZod.parse(pbnst.pbnstRes) });
}else{
console.log("pbnst.pbnstRes is null", pbnstRes);
} else {
form.reset({ items: [] });
}
}, [pbnst.pbnstRes]);
@@ -150,11 +201,9 @@ export default function PublicReuseFaciltyInsert() {
const appendPbnstRuseFclt = useCallback(() => {
const selecTableRow = selectRow;
if (pbnstRes && pbnstRes.length > 0) {
const lastPbnst = pbnstRes[pbnstRes.length - 1];
if (lastPbnst && selecTableRow) {
append(
pbnstRegZod.parse({
pbnstRuseFcltPk:
@@ -170,32 +219,33 @@ export default function PublicReuseFaciltyInsert() {
] as fcBizType[],
})
);
setIsRuseReg({ ...isRuseReg, isModal: true });
setIsRuseReg({ isModal: true });
}
} else {
if (selecTableRow) {
append(
pbnstRegZod.parse({
pbnstRuseFcltPk: String(selecTableRow.bscFctCd)+'1',
ruseUtztnFcltNm: "",
regDt: null,
mdfcnDt: null,
fcBizs: [fcBizSchema.parse({pbnstRuseFcltFk:String(selecTableRow.bscFctCd)+'1',cycl:1})] as fcBizType[],
})
)
setIsRuseReg({ ...isRuseReg, isModal: true });
pbnstRegZod.parse({
pbnstRuseFcltPk: String(selecTableRow.bscFctCd) + "1",
ruseUtztnFcltNm: "",
regDt: null,
mdfcnDt: null,
fcBizs: [
fcBizSchema.parse({
pbnstRuseFcltFk: String(selecTableRow.bscFctCd) + "1",
cycl: 1,
}),
] as fcBizType[],
})
);
setIsRuseReg({ isModal: true });
}
}
}, [pbnstRegZod, pbnstRes, append, selectRow, form]);
useEffect(() => {
// console.log(" page getgetvaslue ", form.getValues());
}, [form.getValues()]);
pubHasuInit(String(pageNation), "41");
}, [pageNation, pubHasuInit]);
useEffect(() =>{
pubHasuInit(String(pageNation), "41");
},[pageNation,pubHasuInit])
return (
<>
<ContentsDiv>
@@ -206,7 +256,7 @@ useEffect(() =>{
<SearchFormLeft>
<SearchInput
className={"col-span-3 my-7 !text-3xl"}
label="건물명"
label="시설명"
textValue={searchTxt || ""}
setText={(e) => {
setSearchTxt(e);
@@ -228,7 +278,7 @@ useEffect(() =>{
size={"sm"}
className=""
onClick={() => {
// pbnstMngData([] as customPbnst[]);
// pbnstMngData([] as customPbnst[]);
if (!selectRow) {
toast.error(
"하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요"
@@ -251,6 +301,8 @@ useEffect(() =>{
<div className="col-span-4 h-full min-h-0">
<ListDiv className="min-h-0">
<ScrollArea className="h-[60vh]">
{" "}
{/* 높이 조정 */}
<UiTable
tableData={bdstTable || []}
headlist={pubFacHeadList}
@@ -266,59 +318,95 @@ useEffect(() =>{
</div>
<ListDiv className="col-span-2 pr-6 ">
<ScrollArea className="h-[60vh]">
<Tabs defaultValue="mng" onValueChange={handleTabChange}>
<TabsList className="">
<TabsTrigger className="!text-xl" value="mng">
시설정보
</TabsTrigger>
<TabsTrigger className="!text-xl" value="buse">
차수별정보
</TabsTrigger>
<Button
{formWatch && formWatch[0]?.pbnstRuseFcltPk ? (
<Tabs defaultValue="mng" onValueChange={handleTabChange}>
<TabsList className="">
<TabsTrigger className="!text-xl" value="mng">
시설정보
</TabsTrigger>
<TabsTrigger className="!text-xl" value="buse">
차수별정보
</TabsTrigger>
<Button
size={"sm"}
onClick={form.handleSubmit(onSubmit, onInvalid)}
className="!justify-end" // 저장 버튼 위치 조정
>
저장
</Button>
</TabsList>
<TabsContent className="max-h-full" value="mng">
<PbnstMngForm
formType="update"
form={form}
setIndexMng={(index: number) => {
console.log("setIndexMng", index);
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>
</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"}
onClick={form.handleSubmit(onSubmit)}
className="!justify-end"
className=""
onClick={() => {
// pbnstMngData([] as customPbnst[]);
if (!selectRow) {
toast.error(
"하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요"
);
return;
} else {
appendPbnstRuseFclt();
}
}}
>
저장
신규등록
</Button>
</TabsList>
<TabsContent className="max-h-full" value="mng">
<PbnstMngForm
formType="update"
form={form}
setIndexMng={(index: number) => {
console.log("setIndexMng", index);
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>
</Tabs>
</div>
</>
)}
</ScrollArea>
</ListDiv>
<UiModal
body={
<PbnstRegModal
form={form}
data={isRuseReg.data || ({} as customPbnst)}
form={form} // form prop 전달
setPbnstInsert={(data) => {
if (data) {
pbnstInsert(data); // pbnstInsert 함수 호출
setIsRuseReg({ isModal: false });
}
}}
></PbnstRegModal>
}
bodyClass="w-[68vw] max-w-none h-[90vh]"
bodyClass="w-[68vw] max-w-none h-[90vh]"
open={isRuseReg.isModal}
onOpenChange={(open) => {
remove(form.getValues("items").length - 1);
setIsRuseReg({ data: {} as pubHasuType, isModal: open });
setIsRuseReg({ isModal: open });
}}
/>
</div>
@@ -38,8 +38,10 @@ export default function Home() {
}>({ isOpen: false, chgBuilding: [] });
useEffect(() => {
setIsNewReg( false)
if (!rain.isInit) {
initRain('41');
}
}, [rain.isInit, initRain]);
@@ -80,7 +82,6 @@ export default function Home() {
};
useEffect(() => {
console.log("getPageNation", pageNation);
getRainData(pageNation, "41");
}, [pageNation]);
@@ -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 });
@@ -37,10 +37,7 @@ export default function RainSttn() {
const [pageNation, setPageNation] = useState(1);
const [isChHead, setIsChHead] = useState(false);
const [bdstTable, setBdstTable] = useState<customRainBdst[]>();
const [detailModal, setDetailModal] = useState<{
isOpen: boolean;
data: customRainBdst | undefined;
}>({ data: undefined, isOpen: false });
const [detailModal, setDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false })
const [bdstChgProps, setBdstChgProps] = useState<{
isOpen: boolean;
chBuilding: chgBuilding[];
@@ -49,25 +46,12 @@ export default function RainSttn() {
const codeData = getCityOrSgg(ref);
const cellClick = (index: number | undefined, headId: string) => {
if (
headId !== "chBuildingYn" &&
index !== undefined &&
bdstTable !== undefined
) {
setDetailModal({ isOpen: true, data: bdstTable[index] });
} else if (
headId === "chBuildingYn" &&
index !== undefined &&
bdstTable !== undefined
) {
if (
bdstTable[index] &&
bdstTable[index]?.chgBuilding[0]?.bdstChgNm !== null
) {
setBdstChgProps({
isOpen: true,
chBuilding: bdstTable[index].chgBuilding,
});
if (headId !== 'chBuildingYn' && index !== undefined && bdstTable !== undefined) {
setDetailModal({ isOpen: true, data: bdstTable[index] })
} else if (headId === 'chBuildingYn' && index !== undefined && bdstTable !== undefined) {
if (bdstTable[index] && bdstTable[index]?.chgBuilding[0]?.bdstChgNm !== null) {
setBdstChgProps({ isOpen: true, chBuilding: bdstTable[index].chgBuilding });
}
}
};
@@ -168,9 +152,7 @@ export default function RainSttn() {
headlist={rainSttn.rainSttHeadList}
pageSize={20}
total={100000}
cellClick={(index: number | undefined, headId: string) => {
cellClick(index ?? undefined, headId);
}}
cellClick={(index: number | undefined, headId: string) => { cellClick(index ?? undefined, headId) }}
onPageChange={setPageNation}
></UiTable>
</ListDiv>
@@ -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) => ({
+34
View File
@@ -10,6 +10,32 @@ const yn: SelectDataType[] = [
{ val: 'N', text: 'N' },
]
//처리장연계/자체+연계/자체처리
const erctUswtrPrcsMeth : SelectDataType[]= [
{ val: '처리장연계', text: '처리장연계' },
{ val: '자체+연계', text: '자체+연계' },
{ val: '자체처리', text: '자체처리' },
]
const rusePrcsMthdCd : SelectDataType[] = [
{ val: '물리학적처리', text: '물리학적처리' },
{ val: '생물학적처리', text: '생물학적처리' },
]
const ruseSeCd : SelectDataType[] = [
{ val: '신규', text: '신규' },
{ val: '증설', text: '증설' },
{ val: '관로', text: '관로' },
{ val: '감량', text: '감량' },
{ val: '개량', text: '개량' },
]
const ruseBizMthdCd: SelectDataType[] = [
{ val: '재정', text: '재정' },
{ val: '민간투자', text: '민간투자' },
{ val: '자체', text: '자체' },
]
//공통 건축물 용도
const bdstUsgCd: SelectDataType[] = [
{ val: '-1', text: "확인불가" },
@@ -174,6 +200,10 @@ export type comCodeState = {
HTB:string
}
ruseMngMtd:SelectDataType[]
ruseSeCd: SelectDataType[];
ruseBizMthdCd: SelectDataType[];
rusePrcsMthdCd: SelectDataType[];
erctUswtrPrcsMeth: SelectDataType[];
};
};
@@ -323,6 +353,10 @@ export const comCodeStateExport: comCodeState = {
dmdUszCd:dmdUszCd,
buildingType:buildingType,
ruseMngMtd:ruseMngMtd,
ruseSeCd:ruseSeCd,
ruseBizMthdCd: ruseBizMthdCd,
rusePrcsMthdCd:rusePrcsMthdCd,
erctUswtrPrcsMeth:erctUswtrPrcsMeth
},
};
+46 -3
View File
@@ -7,8 +7,50 @@ import {
import type { pubHasuType } from "../../ruseFcltMng/fcltReg/pbnstReg/type";
import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form";
import type { conCostFeeType, rateFeeType } from "./type";
export const comFcltStateExport: comFcltState = {
searchFclts: [] as pubHasuType[],
demandCompList: [],
dmdIsSucees: '',
checkDemand: {
index: 0,
dmdInfoPk: "",
dmdInfoFk: "",
bzmnNo: "",
dmdCoNm: "",
daddr: "",
telno: "",
rgtr: "",
useUsgCd: "",
ctpvCd: "",
sggCd: "",
dmdAplyPswd: "",
wntUntprc: 0,
dmndTmng: "",
dmndQty: 0,
grssClfg: 0,
tn: 0,
combResdch: 0,
tph: 0,
trbt: 0,
ph: 0,
blgcOxygDmndQnt: 0,
clIonQnt: 0,
smll: 0,
cdtnv: 0,
crmty: 0,
rmrk: "",
reqDt: ""
},
constCostRes:[],
rateFeeRes:[],
};
export type comFcltType = comFcltState & comFcltActions;
export type comFcltState = {
searchFclts: pubHasuType;
searchFclts: pubHasuType[];
demandCompList: DemandCompType[];
dmdIsSucees: string;
checkDemand: DemandCompType;
@@ -16,7 +58,7 @@ export type comFcltState = {
rateFeeRes:rateFeeType[],
};
export type comFcltActions = {
type comFcltActions = {
getSwtrmtFcltNm: (searchNm: string) => void;
getDemandCompList: (searchNm: string, cnt: number) => void;
regDemandCompany: (data: DemandCompType) => void;
@@ -25,7 +67,7 @@ export type comFcltActions = {
getRateFee: (year: string) => void;
};
export const searchActions: (set: any) => comFcltActions = (set: any) => {
export const comFcltSearchAction: (set: any) => comFcltActions = (set: any) => {
return {
getSwtrmtFcltNm: async (searchNm: string) => {
const fcltNm = await getFetch(
@@ -40,6 +82,7 @@ export const searchActions: (set: any) => comFcltActions = (set: any) => {
addr: item.addr,
fctCpc: item.fctCpc,
}));
set((state: comFcltState) => ({
...state,
searchFclts: searchFclts,
+185
View File
@@ -0,0 +1,185 @@
import { getFetch } from "@/components/lib/api";
export type wrMapType = wrMapState & wrMapActions;
export type wrMapState = {
wrMap: {
buildingSearchRes: any[]
} // 건물 검색 결과 데이터
};
type wrMapActions = {
buildingNmSearch: (
flg: string,
sigunCode: string,
searchNm: string | undefined
) => void;
buildingNmSearchMultiple: (
checkedLayers: string[],
sigunCode: string,
searchNm: string | undefined
) => void; // 여러 레이어 동시 검색 함수 추가
};
export const wrMapStateExport: wrMapState = {
wrMap: {
buildingSearchRes: []
}
};
// 레이어 타입과 플래그 매핑
const LAYER_FLAG_MAP: { [key: string]: string } = {
// '46': '???', // 하수처리시설
// '47': '???', // 하수재이용시설
'48': 'RNB', // 빗물이용시설
'49': 'RST', // 중수도시설
'50': 'HTB', // 온배수시설
};
const findBdst = async (flg: string, sggCd: string, searchTxt: string | undefined) => {
return await getFetch('wr', `/reuseFc/building/${flg}/${sggCd}` + (searchTxt ? `/${searchTxt}` : ''))
}
export const wrMapActionsExport: (set: any) => wrMapActions = (
set: any
) => {
return {
buildingNmSearch: async (
flg: string,
sigunCode: string,
searchNm: string | undefined
) => {
try {
// if (!searchNm || searchNm.length < 2) {
// set((state: wrMapState) => ({
// ...state,
// buildingSearchData: []
// }));
// return;
// }
console.log("건축물 코드, 시군구 코드, 검색어: ", flg, sigunCode, searchNm);
const response = await findBdst(flg, sigunCode, searchNm);
console.log("response: ", response);
// API 응답을 SearchInputText 형식으로 변환
const searchListFormatted = (response || []).map((building: any) => ({
searchNm: building.bdstNm,
searchPk: String(building.bdstInfoPk),
dmdCoNm: building.bdstNm,
layerType: flg,
...building
}));
// findBdst와 유사한 API 호출
// findBdst(flg, sigunCode || "", searchNm).then(() => {
// console.log("건물 검색 API 호출: ", sigunCode, searchNm, flg);
// // bdst.bdstBase를 SearchInputText 형식으로 변환
// const searchListFormatted = (state.bdst.bdstBase || []).map((building: any) => ({
// searchNm: building.bdstNm,
// searchPk: String(building.bdstInfoPk),
// dmdCoNm: building.bdstNm,
// layerType: flg,
// ...building
// }));
// set((state: any) => ({
// ...state,
// wrMap: {
// ...state.wrMap,
// buildingSearchData: searchListFormatted
// }
// }));
// });
set((state: wrMapState) => ({
...state,
wrMap: {
...state.wrMap,
buildingSearchRes: searchListFormatted
}
}));
} catch (error) {
console.error("건물 검색 중 오류:", error);
}
},
buildingNmSearchMultiple: async (
checkedLayers: string[],
sigunCode: string,
searchNm: string | undefined
) => {
try {
if (!searchNm || searchNm.length < 2) {
set((state: any) => ({
...state.wrMap,
buildingSearchData: []
}));
return;
}
// 체크된 레이어들에 대해 병렬로 API 호출
const searchPromises = checkedLayers
.filter(layer => LAYER_FLAG_MAP[layer]) // 매핑된 레이어만 필터링
.map(layer => {
const flg = LAYER_FLAG_MAP[layer];
return findBdst(flg, sigunCode, searchNm)
.then(response => {
console.log(`레이어 ${layer}(${flg}) API 응답:`, response);
const searchListFormatted = (state.bdst.bdstBase || []).map((building: any) => ({
searchNm: building.bdstNm,
searchPk: String(building.bdstInfoPk),
dmdCoNm: building.bdstNm,
layerType: flg,
...building
}));
return {
layerType: layer,
flg: flg,
data: bdstNames
};
})
.catch(error => {
console.error(`레이어 ${layer}(${flg}) 검색 중 오류:`, error);
return { layerType: layer, flg: flg, data: [] };
});
});
const results = await Promise.all(searchPromises);
console.log("다중 레이어 검색 결과:", results);
// 모든 결과를 하나의 배열로 합치기
const allSearchResults = results.reduce((acc, result) => {
const formatted = result.data.map((building: any) => ({
searchNm: building.bdstNm,
searchPk: String(building.bdstInfoPk),
dmdCoNm: building.bdstNm,
layerType: result.layerType,
flg: result.flg,
...building.originalData
}));
return [...acc, ...formatted];
}, [] as any[]);
console.log("최종 합쳐진 검색 결과:", {
totalCount: allSearchResults.length,
sampleData: allSearchResults.slice(0, 3)
});
set((state: any) => ({
...state.wrMap,
buildingSearchData: allSearchResults
}));
} catch (error) {
console.error("다중 레이어 검색 중 오류:", error);
set((state: any) => ({
...state,
buildingSearchData: []
}));
}
}
};
};
@@ -0,0 +1,109 @@
import { z } from "zod";
export const reUseCaseExcel = z.object({
file: z.any()
});
type nameMap = {
[key: string]: string;
}
export const reUseCaseNames: nameMap = {
"사업명": "ruseBizNm",
"사업시작일": "bizBgngDate",
"사업종료일": "bizEndtDate",
"공급처_시설종류": "splyFcltKnd",
"공급처명": "splyFcltNm",
"공급처_시설용량(㎥/일)": "splyFcltCpct",
"수요처명": "dmdFcltNm",
"수요처_주소": "dmdFcltAddr",
"재이용시설_시설용량(㎥/일)": "ruseFcltCpct",
"재이용시설_관로(km)": "ruseFcltPplLen",
"재이용시설_용도": "ruseFcltUsg",
"재이용시설_공급량(㎥/일)": "ruseFcltSplyCpct",
"재이용시설_처리공법": "ruseFcltPrcsMsrc",
"보증수질_용도별": "grnteWqUsg",
"보증수질_총대장균군수": "grnteWqGrssClfg",
"보증수질_결합잔류염소": "grnteWqCombResdch",
"보증수질_탁도": "grnteWqTrbt",
"보증수질_생물학적산소요구량(BOD)(mg/L)": "grnteWqBod",
"보증수질_냄새": "grnteWqSmll",
"보증수질_색도(도)": "grnteWqCrmty",
"보증수질_총질소(T-N)(mg/L)": "grnteWqTn",
"보증수질_총인(T-P)(mg/L)": "grnteWqTp",
"보증수질_수소이온농도(pH)": "grnteWqPh",
"보증수질_염화물": "grnteWqClIonQnt",
"보증수질_전기전도도": "grnteWqCndctStat",
"보증수질_비고": "grnteWqRmrk",
"소요사업비_총(백만원)": "reqBizGrssCst",
"소요사업비_토목_재이용시설(백만원)": "reqBizReuseFcltCst",
"소요사업비_토목_관로(백만원)": "reqBizPplCst",
"소요사업비_건축(백만원)": "reqBizArchCst",
"소요사업비_기계(백만원)": "reqBizMchnCst",
"소요사업비_전기.계측(백만원)": "reqBizMtrngCst",
"소요사업비_GIS DB(백만원)": "reqBizGisCst",
"소요사업비_CCTV(백만원)": "reqBizCctvCst",
"소요사업비_시공 및 준공(백만원)": "reqBizCmcnCst",
"소요사업비_폐기물(백만원)": "reqBizWstCst",
"소요사업비_설계비(백만원)": "reqBizDesignCst",
"소요사업비_감리비(백만원)": "reqBizSpvsCst",
"소요사업비_시설부대비(백만원)": "reqBizSbsdCst",
"소요사업비_기타(백만원)": "reqBizEtc",
"재원조달계획_총(백만원)": "prcmPlanGrssCst",
"재원조달계획_국비(백만원)": "prcmPlanNlep",
"재원조달계획_지방비(백만원)": "prcmPlanLcep",
"재원조달계획_원인자부담(백만원)": "prcmPlanBrdnCst",
"재원조달계획_국고비율(백만원)": "prcmPlanEcqrRt",
"사업비 정산_총(백만원)": "clclnGrssCst",
"사업비 정산_국비(백만원)": "clclnNlep",
"사업비 정산_지방비(백만원)": "clclnLcep",
"사업비 정산_원인자부담(백만원)": "clclnBrdnCst",
"사업비 정산_국고비율(백만원)": "clclnEcqrRt"
}
interface PbnstSttnItem {
rvbsn: string;
lgbsn: number;
mdmbsn?: string;
rgnNm?: string;
sido?: number;
gugun?: number;
bscFctNm?: string;
efflWqltStndrdNm?: string;
fctCpc?: number;
tel?: string;
ruseUtztnFcltNm?: string;
fcltCpct?: number;
splyExtraCpct?: number;
ruseCpct?: number;
usageTotal?: number;
inUsage?: number;
outUsage?: number;
}
export const excelPbnstSttnParsing = (data: PbnstSttnItem[]): string[][] => {
const excelExportData: string[][] = [];
data.forEach((item) => {
excelExportData.push([
item.rvbsn,
String(item.lgbsn),
item.mdmbsn ?? "",
item.rgnNm ?? "",
String(item.sido ?? ""),
String(item.gugun ?? ""),
String(item.bscFctNm ?? ""),
String(item.efflWqltStndrdNm ?? ""),
String(item.fctCpc ?? ""),
String(item.tel ?? ""),
String(item.ruseUtztnFcltNm ?? ""),
String(item.fcltCpct ?? ""),
String(item.splyExtraCpct ?? ""),
String(item.ruseCpct ?? ""),
String(item.usageTotal ?? ""),
String(item.inUsage ?? ""),
String(item.outUsage ?? ""),
])
})
return excelExportData;
};
@@ -4,15 +4,22 @@ import type { TableUpperProps } from "../../common/type/header";
//★☆★☆ 여기서부터 수정하기 ★☆★☆
export const ruseCaseHeadListExport: TableUpperProps[] = [
{
id: "a",
id: "ruseBizNm",
title: "사업명",
visible: true,
row: 0,
rowSpan: 2,
},
{
id: "b",
title: "사업기간",
id: "bizBgngDate",
title: "사업시작일",
visible: true,
row: 0,
rowSpan: 2,
},
{
id: "bizEndDate",
title: "사업종료일",
visible: true,
row: 0,
rowSpan: 2,
@@ -25,19 +32,19 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
colSpan: 3,
},
{
id: "d",
id: "splyFcltKnd",
title: "시설종류",
visible: true,
row: 1,
},
{
id: "e",
id: "splyFcltNm",
title: "공급처명",
visible: true,
row: 1,
},
{
id: "f",
id: "splyFcltCpct",
title: "시설용량(m³/일)",
visible: true,
row: 1,
@@ -50,13 +57,13 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
colSpan: 2,
},
{
id: "h",
id: "dmdFcltNm",
title: "수요처명",
visible: true,
row: 1,
},
{
id: "i",
id: "dmdFcltAddr",
title: "주소",
visible: true,
row: 1,
@@ -69,31 +76,31 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
colSpan: 5,
},
{
id: "q2",
id: "ruseFcltCpct",
title: "시설용량(m³/일)",
visible: true,
row: 1,
},
{
id: "q3",
id: "ruseFcltPplLen",
title: "관로(km)",
visible: true,
row: 1,
},
{
id: "q4",
id: "ruseFcltUsg",
title: "용도",
visible: true,
row: 1,
},
{
id: "q5",
id: "ruseFcltSplyCpct",
title: "공급량(m³/일)",
visible: true,
row: 1,
},
{
id: "q6",
id: "ruseFcltPrcsMsrc",
title: "처리공법",
visible: true,
row: 1,
@@ -106,79 +113,79 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
colSpan: 13,
},
{
id: "k1",
id: "grnteWqUsg",
title: "용도별",
visible: true,
row: 1,
},
{
id: "k2",
id: "grnteWqGrssClfg",
title: "총대장균군수​(개/100ml)",
visible: true,
row: 1,
},
{
id: "k3",
id: "grnteWqCombResdch",
title: "결합잔류염소(mg/L)",
visible: true,
row: 1,
},
{
id: "k4",
id: "grnteWqTrbt",
title: "탁도(NTU)",
visible: true,
row: 1,
},
{
id: "k5",
id: "grnteWqBod",
title: "생물학적​산소요구량(BOD)(mg/L)​",
visible: true,
row: 1,
},
{
id: "k6",
id: "grnteWqSmll",
title: "냄새​",
visible: true,
row: 1,
},
{
id: "k7",
id: "grnteWqCrmty",
title: "색도​",
visible: true,
row: 1,
},
{
id: "k8",
id: "grnteWqTn",
title: "총질소(T-N)(mg/L)​",
visible: true,
row: 1,
},
{
id: "k9",
id: "grnteWqTp",
title: "총인(T-P)(mg/L)",
visible: true,
row: 1,
},
{
id: "k10",
id: "grnteWqPh",
title: "수소이온농도(PH)​",
visible: true,
row: 1,
},
{
id: "k11",
title: "수소이온농도(PH)​",
id: "grnteWqClIonQnt",
title: "염화물",
visible: true,
row: 1,
},
{
id: "k12",
title: "전기전도도​(㎲/cm",
id: "grnteWqCndctStat",
title: "전기전도도​(㎲/cm)",
visible: true,
row: 1,
},
{
id: "k13",
id: "grnteWqRmrk",
title: "비고​",
visible: true,
row: 1,
@@ -191,85 +198,85 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
colSpan: 14,
},
{
id: "h1",
id: "reqBizGrssCst",
title: "총",
visible: true,
row: 1,
},
{
id: "h2",
title: "토목재이용시설",
id: "reqBizReuseFcltCst",
title: "토목재이용시설",
visible: true,
row: 1,
},
{
id: "h3",
id: "reqBizPplCst",
title: "토목관로",
visible: true,
row: 1,
},
{
id: "h4",
id: "reqBizArchCst",
title: "건축",
visible: true,
row: 1,
},
{
id: "h5",
id: "reqBizMchnCst",
title: "기계",
visible: true,
row: 1,
},
{
id: "h6",
id: "reqBizMtrngCst",
title: "전기・계측",
visible: true,
row: 1,
},
{
id: "h7",
id: "reqBizGisCst",
title: "GIS・DB",
visible: true,
row: 1,
},
{
id: "h8",
id: "reqBizCctvCst",
title: "CCTV",
visible: true,
row: 1,
},
{
id: "h9",
id: "reqBizCmcnCst",
title: "시공및준공",
visible: true,
row: 1,
},
{
id: "h10",
id: "reqBizWstCst",
title: "페기물",
visible: true,
row: 1,
},
{
id: "h11",
title: "설계비",
id: "reqBizDesignCst",
title: "설계비",
visible: true,
row: 1,
},
{
id: "h12",
id: "reqBizSpvsCst",
title: "감리비",
visible: true,
row: 1,
},
{
id: "h13",
id: "reqBizSbsdCst",
title: "시설부대비",
visible: true,
row: 1,
},
{
id: "h14",
id: "reqBizEtc",
title: "기타",
visible: true,
row: 1,
@@ -279,32 +286,38 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
title: "자원조달계획(백만원)",
visible: true,
row: 0,
colSpan: 4,
colSpan: 5,
},
{
id: "i1",
id: "prcmPlanGrssCst",
title: "총",
visible: true,
row: 1,
},
{
id: "i2",
id: "prcmPlanNlep",
title: "국비",
visible: true,
row: 1,
},
{
id: "i3",
id: "prcmPlanLcep",
title: "지방비",
visible: true,
row: 1,
},
{
id: "i4",
id: "prcmPlanBrdnCst",
title: "원인자부담",
visible: true,
row: 1,
},
{
id: "prcmPlanEcqrRt",
title: "국고비율",
visible: true,
row: 1,
},
{
id: "j",
title: "사업비정산(백만원)",
@@ -313,31 +326,31 @@ export const ruseCaseHeadListExport: TableUpperProps[] = [
colSpan: 5,
},
{
id: "j1",
id: "clclnGrssCst",
title: "총",
visible: true,
row: 1,
},
{
id: "j2",
id: "clclnNlep",
title: "국비",
visible: true,
row: 1,
},
{
id: "j3",
id: "clclnLcep",
title: "지방비",
visible: true,
row: 1,
},
{
id: "j4",
id: "clclnBrdnCst",
title: "원인자부담",
visible: true,
row: 1,
},
{
id: "j5",
id: "clclnEcqrRt",
title: "국고비율",
visible: true,
row: 1,
+101 -8
View File
@@ -1,24 +1,117 @@
import { fetchType, getFetch, postFectch } from "@/components/lib/api";
import type { TableUpperProps } from "../../common/type/header";
import { ruseCaseHeadListExport } from "./headList";
import { ToastFormUi } from "@/components/ui/toast/toastFromUi";
export type ruseCaseState = {
ruseCase: {
ruseHeadList:TableUpperProps[];
};
ruseCaseMng: {
ruseHeadList:TableUpperProps[];
ruseCaseMngList:ruseCaseMngExcelProps[];
};
type ruseCaseActions = {
};
export interface ruseCaseMngExcelProps {
ruseBizNm: string,
bizBgngDate: string,
bizEndDate: string,
splyFcltKnd: string,
splyFcltNm: string,
splyFcltCpct: number | 0 |'',
dmdFcltNm: string,
dmdFcltAddr: string,
ruseFcltCpct: number | 0 |'',
ruseFcltPplLen: string,
ruseFcltUsg: string,
ruseFcltSplyCpct: number | 0 |'',
ruseFcltPrcsMsrc: string,
grnteWqUsg: string,
grnteWqGrssClfg: number | 0 |'',
grnteWqCombResdch: number | 0 |'',
grnteWqTrbt: string,
grnteWqBod: string,
grnteWqSmll: string,
grnteWqCrmty: string,
grnteWqTn: string,
grnteWqTp: string,
grnteWqPh: string,
grnteWqClIonQnt: string,
grnteWqCndctStat: string,
grnteWqRmrk: string,
reqBizReuseFcltCst: number | 0 |'',
reqBizPplCst: number | 0 |'',
reqBizArchCst: number | 0 |'',
reqBizMchnCst: number | 0 |'',
reqBizMtrngCst: number | 0 |'',
reqBizGisCst: number | 0 |'',
reqBizCctvCst: number | 0 |'',
reqBizCmcnCst: number | 0 |'',
reqBizWstCst: number | 0 |'',
reqBizDesignCst: number | 0 |'',
reqBizSpvsCst: number | 0 |'',
reqBizSbsdCst: number | 0 |'',
reqBizEtc: string,
reqBizGrssCst: number | 0 |'',
prcmPlanNlep: number | 0 |'',
prcmPlanLcep: number | 0 |'',
prcmPlanBrdnCst: number | 0 |'',
prcmPlanEcqrRt: number | 0 |'',
prcmPlanGrssCst: number | 0 |'',
clclnNlep: number | 0 |'',
clclnLcep: number | 0 |'',
clclnBrdnCst: number | 0 |'',
clclnEcqrRt: number | 0 |'',
clclnGrssCst: number | 0 |'',
}
type ruseCaseActions = {
selectRuseCaseMng: (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => Promise<void>;
insertRuseCaseMng: (data: any) => Promise<void>;
};
export const fetchSearchRuseCaseMng = async (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => {
const tailUrl = ((sigunguCd !== '전체' && sigunguCd !=='') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd? `/${usageCd}` : '/0') + (name? `/${name}` : '');
const resp = await getFetch(fetchType.WR,`/reuseFc/pubFcRuseCase${tailUrl}`);
return resp;
};
export const fetchInsertRuseCaseMng = async (data: any) => {
const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR);
return response.data;
};
export type ruseCaseType = ruseCaseState & ruseCaseActions;
export const ruseCaseStateExport: ruseCaseState = {
ruseCase: {
ruseHeadList:ruseCaseHeadListExport,
ruseCaseMng: {
ruseHeadList:ruseCaseHeadListExport,
ruseCaseMngList:[],
},
};
export const ruseCaseActionsExport: (set: any) => ruseCaseActions = (
export const ruseCaseActionsExport: (set: any) => ruseCaseActions = (
set: any
) => {
return {
selectRuseCaseMng: async (sidoCd: string, sigunguCd: string, usageCd : string, name: string) => {
const res = await fetchSearchRuseCaseMng(sidoCd, sigunguCd, usageCd, name);
console.log('res : ', res);
set((state: ruseCaseState) => ({
ruseCaseMng: {
...state.ruseCaseMng,
ruseCaseMngList: res,
},
}));
},
insertRuseCaseMng: async (data: any) => {
const response = await fetchInsertRuseCaseMng(data);
if(response.status === 200){
ToastFormUi({
body: "잠재수요처 등록이 완료되었습니다. ",
title: "잠재수요처 등록",
});
}
},
};
};
@@ -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({
@@ -77,7 +77,6 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = (
prfmncHasuInit: async (page: string, sggOrCity: string) => {
try {
const prfmncRes = await hasuListFetch(page, sggOrCity);
console.log("prfmncRes", prfmncRes);
return set((state: pbnstPrfmncState) => ({
...state,
prfmnc: {
@@ -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("등록 오류입니다.");
}
}
},
};
};
@@ -31,8 +31,8 @@ export const pbnstMngUpHeadListExport: TableUpperProps[] = [
export const pplInstlsHeadListExport: TableUpperProps[] = [
{ id: "choose", title: "선택", type: "checkbox" },
{ id: "ppMqlt", title: "관재질", type: "select", selectData: "pplQulity" },
{ id: "pdmt", title: "관경", type: "select", selectData: "pplLenght" },
{ id: "cpct", title: "연장", edit: true, type: "number" },
{ id: "pdmt", title: "관경(mm)", type: "select", selectData: "pplLenght" },
{ id: "cpct", title: "연장(m)", edit: true, type: "number" },
];
export const pbnstDemandHeadListExport: TableUpperProps[] = [
@@ -69,20 +69,21 @@ export type formDetailType = {
id: string;
lable: string;
type?: string;
selectData?: string; // select box일 경우
option?:string
};
export const wasteWaterFcltINfo: formDetailType[] = [
{ id: "wwtEmsnFcltPrmsnYn", lable: "폐수배출물신고 허가여부", type: "selectYn" },
{ id: "tmsInstlYn", lable: "Tms설치여부", type: "selectYn" },
{ id: "pbnstDrngInflwExclYn", lable: "공공하수도유입제외 허가여부", type: "selectYn" },
{ id: "wwtEmsnFcltPrmsnYn", lable: "폐수배출물신고 허가여부", type: "select" , selectData: "yn"},
{ id: "tmsInstlYn", lable: "Tms설치여부", type: "select" , selectData: "yn" },
{ id: "pbnstDrngInflwExclYn", lable: "공공하수도유입제외 허가여부", type: "select" , selectData: "yn" },
];
export const concentratedWaterInfo: formDetailType[] = [
{ id: "erctWtrOcrnCpct", lable: "눙축수 발생량(m³/일)", type: "number" },
{ id: "erctUswtrLinkSpt", lable: "눙축수연계지점" },
{ id: "erctUswtrFcltCpct", lable: "시설용량(m³/일)", type: "number" },
{ id: "erctUswtrPrcsMeth", lable: "눙축수처리방식" },
{ id: "erctUswtrPrcsMeth", lable: "눙축수처리방식", type : "select", selectData: "erctUswtrPrcsMeth" },
{ id: "erctUswtrPrcsMsrcCd", lable: "눙축수처리공법" },
];
@@ -90,8 +91,8 @@ export const ruseFcltinfo: formDetailType[] = [
{ id: "rprcsFcltCpct", lable: "시설용량(m³/일)", type: "number" },
{ id: "majorPrcsMsrcCd", lable: "주처리공법" },
{ id: "addr", lable: "위치(주소)" },
{ id: "rprcsMthdCd", lable: "재처리방식" },
{ id: "inoutYn", lable: "위치(처리장 내외)" ,type: "selectYn" }, // 머가 내? 외?,
{ id: "rprcsMthdCd", lable: "재처리방식", type: "select", selectData: "rusePrcsMthdCd" },
{ id: "inoutYn", lable: "위치(처리장 내외)" ,type: "select" , selectData: "yn"}, // 머가 내? 외?,
{ id: "mainSplyPumpCpct", lable: "메인공급펌프 용량", type: "number" },
];
@@ -100,12 +101,12 @@ export const businessMoneyInfo: formDetailType[] = [
{ id: "lcep", lable: "지방비", type: "number" },
{ id: "cusrAlotm", lable: "민간투자비", type: "number" },
{ id: "etc", lable: "기타" },
{ id: "totalMoney", lable: "합계", type: "number" ,option:"readOnly" },
{ id: "totalMoney", lable: "합계", type: "number" ,option:"disabled" },
];
export const businessSttnInfo: formDetailType[] = [
{ id: "seCd", lable: "구분" },
{ id: "bizMthdCd", lable: "사업방식" },
{ id: "seCd", lable: "구분", type: "select", selectData: "ruseSeCd" },
{ id: "bizMthdCd", lable: "사업방식", type: "select", selectData: "ruseBizMthdCd" },
{ id: "bizCmcnDay", lable: "사업준공일", type: "date" },
{ id: "instlAprvSncnDt", lable: "설치승인인가일", type: "date" },
];
@@ -118,15 +119,15 @@ export const ruseFcltIntakeList: formDetailType[] = [
];
export const ruseWaterQulityList: formDetailType[] = [
{ id: "bod", lable: "BOD(mg/L)", type: "number" },
{ id: "grssClfg", lable: "총대장균군수(개/mg)", type: "number" },
{ id: "combResdch", lable: "결합잔류염소(mh/L)", type: "number" },
{ id: "trbt", lable: "탁도(NTU)", type: "number" },
{ id: "smll", lable: "냄새", type: "number" },
{ id: "crmty", lable: "색도", type: "number" },
{ id: "tn", lable: "총질소(mg/L)", type: "number" },
{ id: "tph", lable: "총인(mg/L)", type: "number" },
{ id: "ph", lable: "수소이온농도", type: "number" },
{ id: "clIonQnt", lable: "염화물(mg/L)", type: "number" },
{ id: "cdtnv", lable: "전기전도도(μs/cm)​", type: "number" },
{ id: "bod", lable: "BOD(mg/L)", type: "text" },
{ id: "grssClfg", lable: "총대장균군수(개/mg)", type: "text" },
{ id: "combResdch", lable: "결합잔류염소(mh/L)", type: "text" },
{ id: "trbt", lable: "탁도(NTU)", type: "text" },
{ id: "smll", lable: "냄새", type: "text" },
{ id: "crmty", lable: "색도", type: "text" },
{ id: "tn", lable: "총질소(mg/L)", type: "text" },
{ id: "tph", lable: "총인(mg/L)", type: "text" },
{ id: "ph", lable: "수소이온농도", type: "text" },
{ id: "clIonQnt", lable: "염화물(mg/L)", type: "text" },
{ id: "cdtnv", lable: "전기전도도(μs/cm)​", type: "text" },
];
@@ -52,6 +52,7 @@ export type pubFacState = {
fcOrgwtsHeadList: TableUpperProps[];
pbnstMngRegData: customPbnst[];
pbnsUpdateIndex: pbnstInsertIndex;
fcOrgwtsNmList:pubHasuType[];
dmdComList: any[];
};
};
@@ -70,6 +71,7 @@ type pubFacActions = {
pbnstRuseFcltUpdate: (ruseRes: customPbnst[]) => void;
pbnstRuseBizUpdate: (ruseRes: fcBizType[]) => void;
pubHasuSearch :(page: string, sggOrCity: string , searchTxt:string) => void
getFcOrgwts: (fcltPkList: string[], size:number) => void;
};
export type pubFacType = pubFacState & pubFacActions;
@@ -90,6 +92,7 @@ export const pubFacStateExport: pubFacState = {
pbnstBizCycle: [{ text: "없음", val: "1" }],
pbnstMngRegData: [],
pbnsUpdateIndex: {} as pbnstInsertIndex,
fcOrgwtsNmList:[],
dmdComList: [],
},
};
@@ -102,12 +105,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 +178,7 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
try {
const pbnstHasuRes = await hasuListFetch(page, sggOrCity);
return set((state: pubFacState) => ({
...state,
pbnst: {
@@ -399,6 +401,25 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => {
} catch (e) {
console.log(e);
}
},getFcOrgwts : async (fcltPkList: string[] ,size:number) => {
let url = `/prcsFclt/ruseInfo/1/${size}/A/0/0/1/SW`;
const fcOrgwtsNmList = await postFectch(
url,
fcltPkList,
fetchType.COMMON
);
return set((state: pubFacState) => ({
...state,
pbnst: {
...state.pbnst,
fcOrgwtsNmList:fcOrgwtsNmList.data??[]
},
}));
}
};
};
@@ -14,10 +14,10 @@ export type selectLabel = {
val: string;
};
export type pbnstInsertIndex ={
mng:number,
biz:number
}
export type pbnstInsertIndex = {
mng: number;
biz: number;
};
export type publicReuseFacilityType = {
sido: string;
@@ -45,20 +45,20 @@ export type ruseFcltType = {
};
export type PbnstDemandComType = {
dmdInfoPk : string | '';
bzmnNo : string | '';
dmdCoNm : string | '';
daddr : string | '';
telno : string | '';
rgtr : string | '';
useUsgCd : string | '';
ctpvCd : string | '';
sggCd : string | '';
dmdAplyPswd: string | '';
dmdInfoPk: string | "";
bzmnNo: string | "";
dmdCoNm: string | "";
daddr: string | "";
telno: string | "";
rgtr: string | "";
useUsgCd: string | "";
ctpvCd: string | "";
sggCd: string | "";
dmdAplyPswd: string | "";
splyCpct: number | 0;
splyFmYn:string|'Y'
// dmdInfoFk:string|''
}
splyFmYn: string | "Y";
// dmdInfoFk:string|''
};
const pubHasuZod = z.object({
choose: z.boolean().default(false),
@@ -68,23 +68,27 @@ const pubHasuZod = z.object({
bscFctNm: z.string().default(""),
efflWqltStndrdNm: z.string().default(""),
fctCpc: z.number().default(0),
addr:z.string().nullish()
addr: z.string().nullish(),
});
export type pubHasuType = z.infer<typeof pubHasuZod>;
const zDate = z.preprocess((v) => (v ? new Date(v as string) : undefined), z.date()).nullish() ;
const zDate = z
.preprocess((v) => (v ? new Date(v as string) : undefined), z.date())
.nullish();
const zDateNullish = zDate.nullish();
const zDateDefaultNow = zDate.default(new Date());
const zNum = z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number());
const zNum = z.preprocess(
(v) => (v !== null && v !== undefined ? Number(v) : v),
z.number()
);
const zNumNullish = zNum.nullish();
const zNumDefaultZero = zNum.default(0).nullish();
const pplInstlSchema = z.object({
ruseFcltBizFk: zNumNullish,
choose: z.boolean().default(false),
pdmt: z.string().default(''),
pdmt: z.string().default(""),
ppMqlt: z.string().default(""),
cpct: zNumDefaultZero,
});
@@ -115,22 +119,23 @@ export const fcOperSchema = z.object({
});
const fcOrgwtSchema = z.object({
pbnstRuseFcltBizFk:zNumNullish,
pbnstRuseFcltBizFk: zNumNullish,
swtrmtFcltFk: z.string().nullish(),
orgwtWtritQnt: zNum,
tmsYn: z.enum(["Y", "N"]).default("N"),
dschgFmYn: z.enum(["Y", "N"]).default("N"),
dtlPstnExpln: z.string().default(''),
dtlPstnExpln: z.string().default(""),
regDt: zDateNullish,
rgtr: z.string().default(''),
rgtr: z.string().default(""),
mdfcnDt: zDateNullish,
mdfr: z.string().default(''),
mdfr: z.string().default(""),
choose: z.boolean().default(false),
ruseUtztnFcltNm:z.string().nullish()
});
const dmdColist = z.object({
dmdInfoPk: z.string().default(''),
dmdCoNm: z.string().default(''),
dmdInfoPk: z.string().default(""),
dmdCoNm: z.string().default(""),
daddr: z.string(),
useUsgCd: z.string().nullish(),
splyBgngDay: zDateDefaultNow,
@@ -148,18 +153,18 @@ export const fcBizSchema = z.object({
pbnstRuseFcltFk: z.string().nullish(),
superkey: z.string().default("").nullish(),
cycl: zNum.default(1),
seCd: z.string().default(""),
seCd: z.string().max(10, "구분은 10자를 초과할 수 없습니다.").default(""),
bizMthdCd: z.string().default(""),
instlAprvSncnDt: zDateDefaultNow,
bizCmcnDay: zDateDefaultNow,
gvsbs: zNumDefaultZero,
lcep: zNumDefaultZero,
cusrAlotm: zNumDefaultZero,
etc: z.string().default(''),
etc: z.string().default(""),
totalMoney: zNumNullish.default(0),
rprcsFcltCpct: zNumDefaultZero,
majorPrcsMsrcCd: z.string().default(""),
addr: z.string().default(''),
addr: z.string().default(""),
rprcsMthdCd: z.string().default(""),
inoutYn: z.enum(["Y", "N"]).default("Y"),
mainSplyPumpCpct: zNumNullish,
@@ -171,17 +176,29 @@ export const fcBizSchema = z.object({
wwtEmsnFcltPrmsnYn: z.string().default("Y"),
tmsInstlYn: z.string().default("Y"),
pbnstDrngInflwExclYn: z.string().default("Y"),
bod: zNumDefaultZero,
grssClfg: zNumDefaultZero,
combResdch: zNumDefaultZero,
trbt: zNumDefaultZero,
clIonQnt: zNumDefaultZero,
smll: zNumDefaultZero,
crmty: zNumDefaultZero,
tph: zNumDefaultZero,
tn: zNumDefaultZero,
ph: zNumDefaultZero,
cdtnv: zNumDefaultZero,
// bod: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "BOD 값은 5자리 미만이어야 합니다.")).nullish(),
// grssClfg: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "총대장균군 값은 5자리 미만이어야 합니다.")).nullish(),
// combResdch: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "결합잔류염소 값은 5자리 미만이어야 합니다.")).nullish(),
// trbt: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "탁도 값은 5자리 미만이어야 합니다.")).nullish(),
// clIonQnt: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "염소이온량 값은 5자리 미만이어야 합니다.")).nullish(),
// smll: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "냄새 값은 5자리 미만이어야 합니다.")).nullish(),
// crmty: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "색도 값은 5자리 미만이어야 합니다.")).nullish(),
// tph: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "총인 값은 5자리 미만이어야 합니다.")).nullish(),
// tn: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "총질소 값은 5자리 미만이어야 합니다.")).nullish(),
// ph: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "수소이온농도 값은 5자리 미만이어야 합니다.")).nullish(),
// cdtnv: z.preprocess((v) => (v !== null && v !== undefined ? Number(v) : v), z.number().max(9999, "전기전도도 값은 5자리 미만이어야 합니다.")).nullish(),
bod: z.string().nullish(),
grssClfg: z.string().nullish(),
combResdch: z.string().nullish(),
trbt: z.string().nullish(),
clIonQnt: z.string().nullish(),
smll: z.string().nullish(),
crmty: z.string().nullish(),
tph: z.string().nullish(),
tn: z.string().nullish(),
ph: z.string().nullish(),
cdtnv: z.string().nullish(),
designCst: zNumNullish,
cstrnSpvsCst: zNumNullish,
fcltSbsdCst: zNumNullish,
@@ -217,7 +234,7 @@ export const fcBizSchema = z.object({
});
export const pbnstRegZod = z.object({
pbnstRuseFcltPk: z.string().default(''),
pbnstRuseFcltPk: z.string().default(""),
swtrmtFcltFk: z.string().default(""),
ruseUtztnFcltNm: z.string().default(""),
fcltCpct: zNumDefaultZero,
@@ -232,13 +249,12 @@ export const pbnstRegZod = z.object({
fcOpers: z.array(fcOperSchema).default([]).nullish(),
});
export const pbnstRegArrayZod = z.array(pbnstRegZod)
export const pbnstRegArrayZod = z.array(pbnstRegZod);
export type PbnstRegArrrayType = z.infer<typeof pbnstRegArrayZod>
export type PbnstRegArrrayType = z.infer<typeof pbnstRegArrayZod>;
export type fcBizType = z.infer<typeof fcBizSchema>;
export type fcOrgwtType = z.infer<typeof fcOrgwtSchema>;
export type ppInstlType = z.infer<typeof pplInstlSchema>;
export type operType = z.infer<typeof fcOperSchema>;
export type fcFileType = z.infer<typeof fcFileSchema>;
@@ -2,76 +2,29 @@ import type { TableUpperProps } from "@/app/store/common/type/header";
import { getFetch, postFectch, putFectch } from "@/components/lib/api";
import { z } from "zod";
import { makeMdfr, makeRstr } from "../bdstReg";
const validationMessages = {
required: (field: string) => `${field}을(를) 입력해주세요.`,
requiredSelect: (field: string) => `${field}을(를) 선택해주세요.`,
yearFormat: '4자리 연도만 입력하세요',
};
import toast from "react-hot-toast";
import { baseReuseBuildingSchema, chgBuildingSchema, validationMessages } from "./type";
export const TEST_UUID= '2d4b151d-1828-41e0-91d9-4cd28ea1cc7e'
const rainyObjSchema = z.object({
bdstReuseFcltFk: z.number().nullish(),
pntfColKnd: z.string().default(""),
pntfColArea: z.number().default(0),
pntfColKnd: z.string().min(1, { message: validationMessages.requiredSelect("종류") }),
pntfColArea: z.number().positive({ message: validationMessages.required("면적(m³)") }),
dtfcltCpct: z.number().default(0),
filtrTrtmfctYn: z.string().default("Y").nullish(),
filtrTrtmfctYn: z.string().default("Y"),
filtrTrtmfctCpct: z.number().nullish().default(0),
filtrTrtmfctPrs: z.string().default(""),
annRnwtQty: z.number().default(0),
oilqMeasMthd: z.string().default("").nullish(),
rgtr:z.string().nullish(),
mdfr:z.string().nullish(),
}).refine((data) => data.pntfColKnd.length > 0, {
message: validationMessages.requiredSelect("종류"),
path: ["pntfColKnd"],
}).refine((data) => data.pntfColArea > 0, {
message: validationMessages.required("면적(m³)"),
path: ["pntfColArea"],
});
const reuseBuildingSchema = z.object({
bdstRuseFcltPk: z.number().nullish(),
bdstInfoFk:z.number().nullish(),
instlCmptnDt: z.coerce.date().default(() => new Date()),
instlCst: z.number().nullish(),
operMngCd: z.string().optional().default(""),
anlOperCst: z.number().default(0),
instlCstSprtYn: z.string().default("Y"),
instlCstSprtAmt: z.number().nullish(),
grntchBdstCertYn: z.string().default("Y"),
grntchBdstCertYr: z.string().default("")
.refine((year)=> year.length===0 || /^\d{4}$/.test(year),{
message: validationMessages.yearFormat
}),
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("Y"),
rgtr:z.string().nullish(),
mdfr:z.string().nullish(),
rainyObj: rainyObjSchema.default({})
}).refine((data) => data.bdstUsgCd > 0, {
message: validationMessages.requiredSelect("건축물용도"),
path: ["bdstUsgCd"],
});
const chgBuildingSchema = z.object({
bdstInfoFk: z.number().nullish(),
archChgPrmsnDt: z.coerce.date({required_error: validationMessages.required("허가일")}),
archChgArea: z.string().default("-"),
bdstChgNm: z.string().default(""),
rgtr:z.string().nullish(),
mdfr:z.string().nullish(),
}).refine(data => data.bdstChgNm.length > 0, {
message: validationMessages.requiredSelect("구분"),
path: ["bdstChgNm"]
});
const rainBuildingSchema = baseReuseBuildingSchema.extend({
rainyObj: rainyObjSchema,
})
export const rainZod = z.object({
bdstInfoPk: z.number().optional(),
@@ -85,7 +38,7 @@ export const rainZod = z.object({
archChgArea: z.number().default(0),
rgtr:z.string().nullish(),
mdfr:z.string().nullish(),
reuseBuilding: z.array(reuseBuildingSchema).default([]),
reuseBuilding: z.array(rainBuildingSchema).default([]),
chgBuilding: z.array(chgBuildingSchema).default([])
})
@@ -219,15 +172,18 @@ export const rainFacActionsExport: (set: any) => rainFacSActions = (
bdstInfoFk: chPK,
})
);
const chch= postFectch("/reuseFc/chgBuilding", makeCh);
const chch= postFectch("/reuseFc/chgBuilding", makeCh);
return chch;
}).then((chch: any) => {
if(chch.ok) {
alert("등록이 완료되었습니다.");
console.log('chch ', chch)
if(chch.status ===200) {
toast.success("등록이 완료되었습니다.");
}
})
}else{
alert("등록이 완료되었습니다.");
toast.success("등록이 완료되었습니다.");
}
}
const rainRes = await initFetch(code);
@@ -0,0 +1,45 @@
import { z } from "zod";
export const validationMessages = {
required: (field: string) => `${field}을(를) 입력해주세요.`,
requiredSelect: (field: string) => `${field}을(를) 선택해주세요.`,
yearFormat: '4자리 연도만 입력하세요',
};
export const baseReuseBuildingSchema = z.object({
bdstRuseFcltPk: z.number().nullish(),
bdstInfoFk: z.number().nullish(),
instlCmptnDt: z.coerce.date().default(() => new Date()),
instlCst: z.number().nullish(),
operMngCd: z.string().optional().default(""),
anlOperCst: z.number().default(0),
instlCstSprtYn: z.string().default("Y"),
instlCstSprtAmt: z.number().nullish(),
grntchBdstCertYn: z.string().default("Y"),
grntchBdstCertYr: z.string().default("").refine(
(year) => year.length === 0 || /^\d{4}$/.test(year),
{
message: validationMessages.yearFormat,
}
),
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().min(1, { message: validationMessages.requiredSelect("건축물용도") }).default(0),
lawTrgtYn: z.string().default("Y"),
rgtr: z.string().nullish(),
mdfr: z.string().nullish(),
});
export const chgBuildingSchema = z.object({
bdstInfoFk: z.number().nullish(),
archChgPrmsnDt: z.coerce.date({required_error: validationMessages.required("허가일")}),
archChgArea: z.string().default("-"),
bdstChgNm: z.string().min(1, { message: validationMessages.requiredSelect("구분") }),
rgtr:z.string().nullish(),
mdfr:z.string().nullish(),
});
@@ -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>;
+7 -5
View File
@@ -86,7 +86,7 @@ import {
import { rswtChkStateExport, rswtChkSttnActionsExport, type rswtChkSttnState, type rswtChkSttnType } from "./prfmncSttn/chkgdnSttn/rswtChkgdnSttn";
import { pbnstSttnActionsExport, pbnstSttnStateExport, type pbnstSttnState, type pbnstSttnType } from "./ruseFcltMng/fcltSttn/pbnstSttn";
import { coSttnActionsExport, coSttnStateExport, type coSttnState, type coSttnType } from "./matDmd/coSttn"; // 옮겨야함
import { searchActions, type comFcltActions, type comFcltState } from "./common/searchCode";
import { comFcltStateExport, comFcltSearchAction, type comFcltState, type comFcltType } from "./common/searchCode";
import { pbnstPrfmncActionsExport, pbnstPrfmncStateExport, type pbnstPrfmncState, type pbnstPrfmncType } from "./prfmncReg/pbnstPrfmncReg";
import { coRvwActionsExport, coRvwStateExport, type coRvwState, type coRvwType } from "./matDmd/coMatRvw";
import { ruseHstryActionsExport, ruseHstryStateExport, type ruseHstryState, type ruseHstryType } from "./matDmd/ruseRvwHstry";
@@ -113,7 +113,6 @@ export type WrStore = comCodeType &
rainChkSttnType&
rswtChkSttnType&
pbnstSttnType&
comFcltActions&comFcltState&
coSttnType& // 옮겨야함
ldrmntType& // 옮겨야함
pbnstPrfmncType&
@@ -124,7 +123,8 @@ export type WrStore = comCodeType &
ruseCaseType&
rainStatType&
rswtStatType&
matStatType
matStatType&
comFcltType
;
export const StoreZus = (
@@ -141,6 +141,7 @@ export const StoreZus = (
rswtSttnState &
htdflSttnState &
rainChkSttnState&
comFcltState&
rswtChkSttnState &
pbnstSttnState&
coSttnState& // 옮겨야함
@@ -180,7 +181,8 @@ export const StoreZus = (
...ruseCaseStateExport,
...rainStatExport,
...rswtStatExport,
...matStatExport
...matStatExport,
...comFcltStateExport
}
) => {
return createStore<WrStore>()((set) => {
@@ -192,7 +194,7 @@ export const StoreZus = (
getEnvPartCd(set);
return {
...initState,
...searchActions(set),
...comFcltSearchAction(set),
...pubFacActionsExport(set),
...rainFacActionsExport(set),
...rswtFacActionsExport(set),
@@ -171,7 +171,7 @@ export default function BdstReg({ form }: { form: any }) {
<FormUi label="위치">
<Input
className={selectClass}
placeholder="1,000,000"
placeholder=""
type="search"
{...field}
/>
@@ -193,7 +193,7 @@ export default function BdstReg({ form }: { form: any }) {
onSelectValue={(val) => {
field.onChange(Number(val));
}}
value={field.value}
value={field.value??code.bdstUsgCd[0]?.val}
/>
</FormUi>
)}
@@ -208,7 +208,7 @@ export default function BdstReg({ form }: { form: any }) {
<DatePicker
mode={"single"}
field={{
value: field.value || new Date(),
value: field.value?? new Date(),
onChange: (date: Date | undefined) => field.onChange(date),
}}
selectClass={'w-full'}
@@ -226,7 +226,7 @@ export default function BdstReg({ form }: { form: any }) {
<Input
className={selectClass}
placeholder="1,000,000"
type="number"
type="text"
onChange={(e) => {
handleCommaNumberChange(e, field.onChange);
}}
@@ -250,7 +250,7 @@ export default function BdstReg({ form }: { form: any }) {
onSelectValue={(val) => {
field.onChange(val);
}}
value={field.value}
value={field.value??code.yn[0]?.val }
/>
</FormUi>
)}
@@ -55,18 +55,15 @@ export function PbnstBuseForm({
code,
pbnst,
demandCompList,
getpbnstInsertIndex,
appendRuseFclt,
getDemandCompList,
getFcOrgwts,
regDemandCompany,
} = useWrStore((state) => state);
const { pbnstBizCycle, pbnstRes } = pbnst;
const { fcOrgwtsNmList } = pbnst;
const { fields: fcBizs, append: appendBiz } = useFieldArray({
control: form.control,
name: `items.${indexMng}.fcBizs`,
});
const [bizCycle, setBizCycle] = useState("1");
const [ppiSelectList, setPpiSelectList] =
useState<[{ name: string; val: SelectDataType[] }]>();
const [ppiYn, setPpiYn] = useState("Y");
@@ -79,25 +76,72 @@ export function PbnstBuseForm({
>([{ text: "없음", val: 0 }]);
const [isFcOrgwts, setIsFcOrgwts] = useState<boolean>(false);
const [sumCpct, setSumCpct] = useState<number>(0);
const [getDmdList, setGetFmdList] = useState<number>(0);
const [isDmdModal, setIsDmdModal] = useState<{
isMoal: boolean;
data: DemandCompType;
}>({ isMoal: false, data: {} as DemandCompType });
useEffect(() => {
const total =
const etc= isNaN(Number(form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.etc`)))
let total =
(form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.gvsbs`) ?? 0) +
(form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.lcep`) ?? 0) +
(form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.cusrAlotm`) ?? 0);
(form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.cusrAlotm`) ?? 0)
if( !etc ) {
total+=Number(form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.etc`))
}
form.setValue(`items.${indexMng}.fcBizs.${indexBiz}.totalMoney`, total);
}, [
form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.gvsbs`),
form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.lcep`),
form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.cusrAlotm`),
form.getValues(`items.${indexMng}.fcBizs.${indexBiz}.etc`)
]);
const fcOrgwts = form.watch( `items.${indexMng}.fcBizs.${indexBiz}.fcOrgwts`)
useEffect(() =>{
console.log('fcOrgwts ', fcOrgwts)
if(fcOrgwts&&fcOrgwts?.length>0){
const swtrmtFcltFkList = fcOrgwts.map((item) => item.swtrmtFcltFk);
console.log('swtrmtFcltFkList ', swtrmtFcltFkList)
if(swtrmtFcltFkList.length > 0){
getFcOrgwts(swtrmtFcltFkList.filter((item): item is string => item != null), fcOrgwts?.length)
}
}
},[fcOrgwts])
useEffect(() =>{
if (fcOrgwtsNmList && fcOrgwtsNmList.length > 0) {
console.log('fcOrgwtsNmList ', fcOrgwtsNmList)
console.log('fcOrgwts ', fcOrgwts)
fcOrgwts?.forEach((item, index) => {
const orgwtNm = fcOrgwtsNmList.find(
(orgwt) => orgwt.bscFctCd === item.swtrmtFcltFk
)?.bscFctNm;
console.log('orgwtNm ', orgwtNm)
if (orgwtNm) {
form.setValue(
`items.${indexMng}.fcBizs.${indexBiz}.fcOrgwts.${index}.ruseUtztnFcltNm`,
orgwtNm
);
}
});
}
},[fcOrgwtsNmList,fcOrgwts])
// fcBizs 데이터가 변경될 때마다 차수 선택 목록을 업데이트합니다.
useEffect(() => {
const cycleLength = fcBizs.length;
@@ -250,23 +294,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 (
@@ -499,7 +526,7 @@ export function PbnstBuseForm({
) || []
).map((field, index) => (
<div
key={`fcDemands-${indexMng}-${indexBiz}`} // 이게 핵심!
key={`fcDemands-${indexMng}-${indexBiz}=${index}`} // 이게 핵심!
className="grid grid-cols-12 gap-4 mt-3"
>
<div key={index + "keySon1"} className="col-span-4">
@@ -664,6 +691,7 @@ export function PbnstBuseForm({
)}
/>
</div>
<br></br>
</div>
))}
</div>
@@ -674,7 +702,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={() => {
@@ -29,6 +29,10 @@ import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg";
import { Label } from "@common/business_components/ui/form/lib/label";
import React from "react";
import RuseMngTableForm from "@/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm";
import {
handleCommaNumberChange,
formatCommaNumber,
} from "@/components/lib/func/commom";
export type FilesType = {
atflCd: string;
file: File;
@@ -46,13 +50,9 @@ export function PbnstMngForm({
indexMng: number;
setIndexMng: (index: number) => void;
}) {
const {
code,
pbnst,
pbnstInsert,
fileDelete,
fileDownLoad,
} = useWrStore((state) => state);
const { code, pbnst, pbnstInsert, fileDelete, fileDownLoad } = useWrStore(
(state) => state
);
const { pbnstRes } = pbnst;
const [regFileList, setRegFileList] = useState<fcFileType[] | null>(null);
@@ -215,16 +215,14 @@ export function PbnstMngForm({
useEffect(() => {
if (pbnst.pbnstRes && pbnst.pbnstRes?.length >= 0) {
const fcltNmList = pbnst.pbnstRes?.map((item, index) => ({
text: item.ruseUtztnFcltNm,
val: String(index),
}));
console.log(' fcltNmList ', fcltNmList)
console.log(" fcltNmList ", fcltNmList);
if (fcltNmList) setFcltNm(fcltNmList);
}else{
} else {
setFcltNm(null);
}
}, [pbnst.pbnstRes]);
@@ -253,16 +251,14 @@ export function PbnstMngForm({
}
}, [code]);
useEffect(() =>{
// console.log(' fcltNm ', watchedItem?.ruseUtztnFcltNm)
},[watchedItem])
useEffect(() => {
// console.log(' fcltNm ', watchedItem?.ruseUtztnFcltNm)
}, [watchedItem]);
useEffect(() => {
console.log(" form indexMng ", indexMng);
}, [indexMng]);
}, [indexMng]);
return (
<div className=" !mx-[1.6rem] !p-0r">
<div>
@@ -297,8 +293,6 @@ export function PbnstMngForm({
render={({ field }) => (
<FormUi label="">
<Input
className="!text-2xl"
placeholder=""
type="text"
@@ -379,11 +373,12 @@ export function PbnstMngForm({
<Input
key={`items.${indexMng}.fcltCpct`}
placeholder=""
type="number"
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
if (formType !== "sttn")
handleCommaNumberChange(e, field.onChange);
}}
value={field.value ?? 0}
value={formatCommaNumber(field.value)}
readOnly={formType === "sttn" ? true : false}
/>
</FormUi>
@@ -401,11 +396,12 @@ export function PbnstMngForm({
<Input
key={`items.${indexMng}.ruseCpct`}
placeholder=""
type="number"
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
if (formType !== "sttn")
handleCommaNumberChange(e, field.onChange);
}}
value={field.value ?? 0}
value={formatCommaNumber(field.value)}
readOnly={formType === "sttn" ? true : false}
/>
</FormUi>
@@ -422,11 +418,12 @@ export function PbnstMngForm({
<Input
key={`items.${indexMng}.splyExtraCpct`}
placeholder=""
type="number"
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
if (formType !== "sttn")
handleCommaNumberChange(e, field.onChange);
}}
value={field.value ?? 0}
value={formatCommaNumber(field.value)}
readOnly={formType === "sttn" ? true : false}
/>
</FormUi>
@@ -2,17 +2,14 @@
import { useWrStore } from "@/app/store";
import type { formDetailType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/headList";
import { formH1Class } from "@/components/lib/class/comClass";
import { safeDate } from "@/components/lib/func/commom";
import { formatCommaNumber, handleCommaNumberChange, safeDate } from "@/components/lib/func/commom";
import {
DatePicker,
FormField,
FormUi,
SearchInputSelect,
SelectBox,
type SelectDataType,
} from "@common/business_components/ui";
import { Input } from "@common/components/ui";
import { useState } from "react";
export default function MngRegFormDetail({
formList,
@@ -55,9 +52,7 @@ export default function MngRegFormDetail({
name={fieldId}
render={({ field }) => (
<FormUi label={formHead.lable}>
{formHead.type === "select" ? (
<Input placeholder="" type="text" {...field} />
) : formHead.type === "date" ? (
{formHead.type === "date" ? (
<DatePicker
mode={"single"}
className={"rounded-lg !w-full"}
@@ -67,35 +62,54 @@ export default function MngRegFormDetail({
field.onChange(date),
}}
selectClass={"w-full"}
disabled={formHead.option === "disabled" ? true : false}
/>
) : formHead.type === "number" ? (
<Input
placeholder=""
type="number"
value={field.value ?? 0}
type="text"
onChange={(e) => {
field.onChange(Number(e.target.value));
handleCommaNumberChange(
e,
field.onChange
);
}}
readOnly={formHead.option === "readOnly"?true:false}
/>
) : formHead.type === "selectYn" ? (
<SelectBox
label={""}
selectClass={""}
className="w-full"
selectArray={code.yn}
onSelectValue={(val: string | number) =>
field.onChange(val)
}
value={field.value ?? "Y"}
value={formatCommaNumber(field.value)}
readOnly={formHead.option === "readOnly" ? true : false}
disabled={formHead.option === "disabled" ? true : false}
/>
) : formHead.type === "select" ? (
(() => {
const potentialOptions = formHead.selectData
? code[formHead.selectData as keyof typeof code]
: [];
return (
<SelectBox
label={""}
selectClass={""}
className="w-full"
selectArray={
Array.isArray(potentialOptions)
? potentialOptions
: []
}
onSelectValue={field.onChange}
value={field.value.trim()}
readOnly={formHead.option === "readOnly"}
/>
);
})()
) : (
<Input
placeholder=""
type="text"
value={field.value ?? ""}
onChange={field.onChange}
readOnly={formHead.option === "readOnly"?true:false}
readOnly={formHead.option === "readOnly" ? true : false}
disabled={formHead.option === "disabled" ? true : false}
/>
)}
</FormUi>
@@ -46,30 +46,40 @@ export default function RainRegForm({
]);
const cleanRainData = (raw: any): z.infer<typeof rainZod> => {
const buildings =
raw?.reuseBuilding && raw.reuseBuilding.length > 0
? raw.reuseBuilding
: [{}];
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) => ({
reuseBuilding: buildings.map((item: any) => ({
...item,
instlCmptnDt: new Date(item?.instlCmptnDt),
instlCmptnDt: item?.instlCmptnDt
? new Date(item.instlCmptnDt)
: new Date(), // item?.instlCmptnDt가 유효하지 않으면 현재 날짜로 초기화
operMngCd: item?.operMngCd ?? "", // null → ""
grntchBdstCertYr:
typeof item?.grntchBdstCertYr === "string"
? item?.grntchBdstCertYr
: "", // null → ""
// Ensure enum fields are null if empty string to trigger Zod default
instlCstSprtYn: item?.instlCstSprtYn ?? "Y", // null → "Y"
grntchBdstCertYn:item?.grntchBdstCertYn??"Y",
rainyObj: {
...item?.rainyObj,
filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn === "" ? null : item?.rainyObj?.filtrTrtmfctYn,
filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn ?? "Y",
},
})),
};
};
useEffect(() => {
if (grntchBdstCertYn === "N") {
form.setValue("reuseBuilding.0.grntchBdstCertYr", "");
form.setValue("reuseBuilding.0.grntchBdstCertNo", "");
@@ -82,6 +92,9 @@ export default function RainRegForm({
}
}, [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctYn]);
// form 값이 변경될 때마다 로그 출력
useEffect(() => {
const subscription = form.watch((value) => {
@@ -146,9 +159,8 @@ export default function RainRegForm({
value:
field.value && !isNaN(new Date(field.value).getTime())
? new Date(field.value)
: new Date(), // Provide a default Date value
onChange: (date) => field.onChange(date),
: new Date(),
onChange: (date: Date | undefined) => field.onChange(date),
}}
selectClass={"w-full"}
readOnly={readonly}
@@ -245,7 +257,7 @@ export default function RainRegForm({
label={""}
selectArray={code.yn}
onSelectValue={readonly ? () => { } : field.onChange}
value={field.value || code.yn[0]?.val}
value={field.value ?? code.yn[0]?.val}
readOnly={readonly}
/>
</FormUi>
@@ -266,7 +278,8 @@ export default function RainRegForm({
if (!readonly) handleCommaNumberChange(e, field.onChange);
}}
value={formatCommaNumber(field.value)}
readOnly={filtrTrtmfctYn !== "Y"}
readOnly={readonly }
disabled={filtrTrtmfctYn !== "Y"}
/>
</FormUi>
)}
@@ -284,7 +297,8 @@ export default function RainRegForm({
type="text"
value={field.value ?? ""}
onChange={field.onChange}
readOnly={filtrTrtmfctYn !== "Y"}
readOnly={readonly }
disabled={filtrTrtmfctYn !== "Y"}
/>
</FormUi>
)}
@@ -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);
@@ -0,0 +1,172 @@
import { coRegExcel } from "@/app/store/matDmd/coSttn/headList";
import type { ruseCaseMngExcelProps } from "@/app/store/matDmd/ruseCaseMng/index";
import { reUseCaseNames } from "@/app/store/matDmd/ruseCaseMng/excel";
import { filePicked, FileUpload, FormUi, Title } from "@common/business_components/ui";
import { Form, FormField } from "@common/business_components/ui/form";
import { Button } from "@common/components/ui";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import { useForm } from "react-hook-form";
import type { z } from "zod";
import { useWrStore } from "@/app/store";
export default function RuseCaseMngExcel() {
const { insertRuseCaseMng } = useWrStore((state) => state);
const form = useForm<z.infer<typeof coRegExcel>>({
resolver: zodResolver(coRegExcel),
});
const [file, setFiles] = useState<File | undefined>();
const onSubmit = () => {
if (file) {
filePicked(file, 0).then((res: unknown) => {
try {
const newArray: ruseCaseMngExcelProps[] = []
const dataArray = Array.isArray(res) ? res : [];
dataArray.forEach((item: Record<string, unknown>) => {
const object: ruseCaseMngExcelProps = {
ruseBizNm: '',
bizBgngDate: '',
bizEndDate: '',
splyFcltKnd: '',
splyFcltNm: '',
splyFcltCpct: 0,
dmdFcltNm: '',
dmdFcltAddr: '',
ruseFcltCpct: 0,
ruseFcltPplLen: '',
ruseFcltUsg: '',
ruseFcltSplyCpct: 0,
ruseFcltPrcsMsrc: '',
grnteWqUsg: '',
grnteWqGrssClfg: 0,
grnteWqCombResdch: 0,
grnteWqTrbt: '',
grnteWqBod: '',
grnteWqSmll: '',
grnteWqCrmty: '',
grnteWqTn: '',
grnteWqTp: '',
grnteWqPh: '',
grnteWqClIonQnt: '',
grnteWqCndctStat: '',
grnteWqRmrk: '',
reqBizReuseFcltCst: 0,
reqBizPplCst: 0,
reqBizArchCst: 0,
reqBizMchnCst: 0,
reqBizMtrngCst: 0,
reqBizGisCst: 0,
reqBizCctvCst: 0,
reqBizCmcnCst: 0,
reqBizWstCst: 0,
reqBizDesignCst: 0,
reqBizSpvsCst: 0,
reqBizSbsdCst: 0,
reqBizEtc: '',
reqBizGrssCst: 0,
prcmPlanNlep: 0,
prcmPlanLcep: 0,
prcmPlanBrdnCst: 0,
prcmPlanEcqrRt: 0,
prcmPlanGrssCst: 0,
clclnNlep: 0,
clclnLcep: 0,
clclnBrdnCst: 0,
clclnEcqrRt: 0,
clclnGrssCst: 0,
};
Object.keys(reUseCaseNames).forEach((key: string) => {
if (item && reUseCaseNames[key] !== undefined) {
const mappedKey = reUseCaseNames[key];
if (mappedKey && object.hasOwnProperty(mappedKey)) {
let value;
if(value === "-"){
value = '';
}
if(key === "사업시작일" || key === "사업종료일"){
if(item[key]?.toString().split(".")[1]?.length === 1){
value = `${item[key]?.toString().split(".")[0] || ""}${item[key]?.toString().split(".")[1]?.length === 1 ? "0" : ""}${item[key]?.toString().split(".")[1] || ""}`
}else{
value = `${item[key]?.toString().split(".")[0] || ""}${item[key]?.toString().split(".")[1] || ""}`
}
}else if(key === "재이용시설_관로(km)" || key?.toString().includes("보증수질_")){
value = item[key]?.toString().trim() || '';
}else{
if(item[key]?.toString().includes("%") && Number(item[key]?.toString().replaceAll("%", ""))){
value = item[key]?.toString().replaceAll("%", "");
}else{
value = item[key];
}
}
if(value === null || value?.toString().trim() === "-" || value?.toString().trim() === ""){
if (typeof object[mappedKey as keyof ruseCaseMngExcelProps] === 'number') {
(object as unknown as Record<string, unknown>)[mappedKey] = '';
} else {
(object as unknown as Record<string, unknown>)[mappedKey] = '';
}
}else if (typeof object[mappedKey as keyof ruseCaseMngExcelProps] === 'number' && Number(value?.toString().replaceAll("%", "").replaceAll(",", ""))) {
(object as unknown as Record<string, unknown>)[mappedKey] = Number(value?.toString().replaceAll("%", "").replaceAll(",", ""));
}else{
(object as unknown as Record<string, unknown>)[mappedKey] = value?.toString().trim() || '';
}
}
}
});
newArray.push(object);
});
console.log("newArray : ", newArray);
insertRuseCaseMng(newArray);
} catch (e) {
console.log(e)
}
});
};
}
return (
<>
<div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div>
<Title title={"물재이용 사례 등록"}></Title>
</div>
<div className="flex justify-between">
<h3 className="text-2xl font-bold text-gray-900 mt-1 ml-7">
{"물재이용 사례 파일"}
</h3>
<Button className="" type="submit" size={"sm"}>
저장
</Button>
</div>
<div>
<FormField
control={form.control}
name="file"
render={() => (
<FormUi label="">
<FileUpload
files={file ? [file] : []}
setFiles={(files: File[] | null) => {
console.log("files", files)
if (files && files?.length === 1) setFiles(files[0]);
}}
fileTypes=""
maxFiles={1}
></FileUpload>
</FormUi>
)}
/>
</div>
</form>
</Form>
</div>
</>
);
}
@@ -14,7 +14,6 @@ const {bdst,rain} = useWrStore((state) => {
return state;
});
const { bdstChgHeadList } = bdst;
console.log("chgBuilding", chgBuilding);
return (
@@ -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}
@@ -28,7 +28,7 @@ import { useEffect, useState } from "react";
import { set, useForm } from "react-hook-form";
import { z } from "zod";
export function PbnstSttnModal({ data }: { data: customPbnst }) {
export function PbnstSttnModal({ data, titleVisible }: { data: customPbnst; titleVisible?: boolean }) {
const { code, pbnstSttn, prfmnc } = useWrStore((state) => state);
const [fcltTab, setFcltTab] = useState<string>("fclt");
const [ruseTab, setRuseTab] = useState<string>("inoutTotal");
@@ -237,10 +237,10 @@ export function PbnstSttnModal({ data }: { data: customPbnst }) {
}, [pbnstSttn.pbnstSttnRes, data]);
useEffect(()=>{
useEffect(() => {
if(data){
form.reset({items:[data]})
if (data) {
form.reset({ items: [data] })
}
},[data, form])
@@ -252,7 +252,11 @@ export function PbnstSttnModal({ data }: { data: customPbnst }) {
return (
<>
<div>
<Title title={"하수재이용 시설 상세정보"} />
{titleVisible && (
<>
<Title title={"하수재이용 시설 상세정보"} />
</>
)}
<ScrollArea className={"h-[70vh]"}>
<PbnstMngForm
form={form}
+12 -1
View File
@@ -5,6 +5,8 @@ import { CS_URL } from "./components/lib/config";
const CLAIM_NAME = "Authorization";
const AUTH_USER_ID = "token-id";
const serverApiUrl = CS_URL;
const GEOSERVER_URL = process.env.NEXT_PUBLIC_GEOSERVER_URL || "";
export function middleware(req: NextRequest) {
const pathname = req.nextUrl.pathname;
console.log(`[Middleware] Request URL: ${req.url}`);
@@ -44,9 +46,18 @@ export function middleware(req: NextRequest) {
});
}
if (pathname.startsWith("/geoserver")) {
const queryParams = searchParams.toString();
const newUrl = `${GEOSERVER_URL}${pathname.replace("/geoserver", "")}${
queryParams ? `?${queryParams}` : ""
}`;
console.log(`[Middleware] Rewriting from '/geoserver' path to: ${newUrl}`);
return NextResponse.rewrite(new URL(newUrl));
}
return NextResponse.next();
}
export const config = {
matcher: ["/server/:path*", "/attach-service/:path*"],
matcher: ["/server/:path*", "/attach-service/:path*", "/geoserver/:path*"],
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 961 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 970 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1016 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 782 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1009 B

Some files were not shown because too many files have changed in this diff Show More