diff --git a/apps/cs/app/(with-base-layout)/loginlog/page.tsx b/apps/cs/app/(with-base-layout)/loginlog/page.tsx new file mode 100644 index 000000000..960f03e05 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/loginlog/page.tsx @@ -0,0 +1,2 @@ +import { LoginlogPage } from "@/pages/loginlog"; +export default LoginlogPage; diff --git a/apps/cs/src/pages/loginlog/index.ts b/apps/cs/src/pages/loginlog/index.ts new file mode 100644 index 000000000..4e5e70ea8 --- /dev/null +++ b/apps/cs/src/pages/loginlog/index.ts @@ -0,0 +1 @@ +export { LoginlogPage } from "./ui/loginlog-page"; diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/aplcnYms.ts b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/aplcnYms.ts index 26d597abf..9e7f3fe50 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/aplcnYms.ts +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/aplcnYms.ts @@ -71,8 +71,8 @@ export const getDcTree = async () => { return result; }; -const getOptionsAplcnYm = (yyyymms: string[]): { text: string; val: string }[] => { - let yyyymms1 = yyyymms.map(dateStr => { +const getOptionsAplcnYm = (yyyymms: string[]): { text: string; val: string, hidden?: boolean }[] => { + const yyyymms1 = yyyymms.map(dateStr => { const year = dateStr.slice(0, 4); const month = dateStr.slice(4, 6); return { @@ -80,7 +80,7 @@ const getOptionsAplcnYm = (yyyymms: string[]): { text: string; val: string }[] = val: dateStr }; }); - yyyymms1.unshift({ val: '0', text: '선택' }); + yyyymms1.unshift({ val: '0', text: '선택', hidden: true }); return yyyymms1; }; const selectAplcnYms = async(gubun:string) => { // 상수사용월 @@ -109,12 +109,12 @@ export const useAplcnYms = (gubun:string) => { return data; }; -export const getOptionsInftwtAnlsInfo = (anlsArr: TInftwtAnls[]): { text: string; val: number }[] => { +export const getOptionsInftwtAnlsInfo = (anlsArr: TInftwtAnls[]): { text: string; val: number, hidden?: boolean }[] => { const options = anlsArr.map(anls => ({ text: anls.anlsInfo, val: anls.inftwtAnlsSn, })); - options.unshift({ val: 0, text: '선택' }); + options.unshift({ val: 0, text: '선택', hidden: true }); return options; }; @@ -208,12 +208,12 @@ export const selectPplRdiiQty = async(rainInfwtAnlsSn:number, pplMngNo:string) = } }; -export const getOptionsRdiiAnlsInfo = (anlsArr: TRdiiAnls[]): { text: string; val: number }[] => { +export const getOptionsRdiiAnlsInfo = (anlsArr: TRdiiAnls[]): { text: string; val: number, hidden?: boolean }[] => { const options = anlsArr.map(anls => ({ text: anls.anlsInfo, val: anls.rainInfwtAnlsSn, })); - options.unshift({ val: 0, text: '선택' }); + options.unshift({ val: 0, text: '선택', hidden: true }); return options; }; diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx index 12963ef9c..fe08f34f0 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/infilChart.tsx @@ -11,25 +11,25 @@ import { useInfilChartModalStore } from '../chartModalStore'; // 모달 상태 type ModalOpenProp = { pplMngNo?: string, // 관로관리번호 } -type DataType = {label: string; val: number}; +type DataType = { label: string; val: number }; const InfilChart = ({ pplMngNo }: ModalOpenProp) => { const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useInfilChartModalStore(); // 모달 상태 관리 스토어 const [popupOpen, setPopupOpen] = React.useState(false); // 조회조건팝업 const buttonRef = React.useRef(null); // 버튼 참조 const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 - const [aplcnYm, setAplcnYm] = useState(""); + const [selectedAplcnYm, setSelectedAplcnYm] = useState(""); const [inftwtAnlss, setInftwtAnlss] = useState([]); - const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState([{ val: 0, text: '선택' }]); // 침입수분석정보 options + const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState([{ val: 0, text: '선택', hidden: true }]); // 침입수분석정보 options const [inftwtAnls, setInftwtAnls] = useState(null); // 침입수분석정보 - const [inftwtAnlsSn, setInftwtAnlsSn] = useState(0); // 침입수분석 정보 선택시 사용 + const [selectedInftwtAnls, setSelectedInftwtAnls] = useState(0); // 침입수분석 정보 선택시 사용 const [chartData, setChartData] = useState<{ chartData1: DataType[]; chartData2: DataType[] }>({ chartData1: [], chartData2: [], }); const [inftwtQty, setInftwtQty] = useState(0); // 침입수량 const aplcnYms = fn.useAplcnYms('inftwtAnlsRslt'); // 상수사용월 데이터 가져오기 - - pplMngNo = "L"+pplMngNo; // inp파일에 하수관로와 맨홀의 번호가 같으면 분석 오류가 발행하여 + + pplMngNo = "L" + pplMngNo; // inp파일에 하수관로와 맨홀의 번호가 같으면 분석 오류가 발행하여 const openPopup = () => { if (buttonRef.current) { const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산 @@ -50,60 +50,73 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { useEffect(() => { if (isModalOpen === false) { setPopupOpen(true); // 모달이 닫힐 때 조회조건팝업은 열어두기 - setAplcnYm(""); // 상수사용월 초기화 + setSelectedAplcnYm(""); // 상수사용월 초기화 setInftwtAnlss([]); // 침입수분석 초기화 - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); // 침입수분석정보 초기화 + setInftwtAnlsInfos([{ val: 0, text: '선택', hidden: true }]); // 침입수분석정보 초기화 setInftwtAnls(null); // 침입수분석정보 초기화 - setInftwtAnlsSn(0); // 침입수분석 일련번호 초기화 - setChartData({chartData1: [], chartData2: [],}); // 차트 데이터 초기화 + setSelectedInftwtAnls(0); // 침입수분석 일련번호 초기화 + setChartData({ chartData1: [], chartData2: [], }); // 차트 데이터 초기화 setInftwtQty(0); // 침입수량 초기화 } }, [isModalOpen]); - // 상수사용월 변경시 분석 정보 리스트 가져오기 - useEffect(() => { - // aplcnYm이 비어있거나 "0"이면 API 호출하지 않음 - if (!aplcnYm || aplcnYm === "0" || aplcnYm === "") { - setInftwtAnlsSn(0); // 초기화 - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); + // 상수사용월 변경 핸들러 + const handleAplcnYmChange = async (val: string | number) => { + const selectedValue = String(val); + console.log("선택된 상수사용월:", selectedValue); + + setSelectedAplcnYm(selectedValue); + + // 즉시 관련 상태 초기화 + setInftwtAnls(null); + + // 빈 값이면 초기화만 하고 종료 + if (!selectedValue || selectedValue === "0" || selectedValue === "") { + setInftwtAnlsInfos([{ val: 0, text: '선택', hidden: true }]); return; } - const dcData = fn.getDcTree(); // 자료관 데이터 가져오기 - fn.selectInftwtAnls(aplcnYm) - .then((data) => { - setInftwtAnlss(data ?? []); - setInftwtAnlsInfos(fn.getOptionsInftwtAnlsInfo(data)); - setInftwtAnls(null); // 오수전환율 이후 초기화 - setInftwtAnlsSn(0); // 초기화 - }) - .catch((error) => { - console.error("selectInftwtAnls 에러:", error); - setInftwtAnlss([]); - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); - }); - - }, [aplcnYm]); - // 값 표시 - useEffect(() => { + // API 호출 + try { + const dcData = fn.getDcTree(); + const data = await fn.selectInftwtAnls(selectedValue); + setInftwtAnlss(data ?? []); + setInftwtAnlsInfos(fn.getOptionsInftwtAnlsInfo(data)); + } catch (error) { + console.error("selectInftwtAnls 에러:", error); + setInftwtAnlss([]); + setInftwtAnlsInfos([{ val: 0, text: '선택', hidden: true }]); + } + setSelectedInftwtAnls(0); + }; + + // 분석정보 변경 핸들러 + const handleInftwtAnlsSnChange = (val: string | number) => { + const selectedValue = Number(val); + setSelectedInftwtAnls(selectedValue); + // 선택된 침입수분석 정보에 대한 처리 로직 - if (inftwtAnlsSn === 0) return; - const selectedAnls = inftwtAnlss.find(anls => anls.inftwtAnlsSn === inftwtAnlsSn); + if (selectedValue === 0) { + setInftwtAnls(null); + return; + } + + const selectedAnls = inftwtAnlss.find(anls => anls.inftwtAnlsSn === selectedValue); if (selectedAnls) { setInftwtAnls(selectedAnls); } - }, [inftwtAnlsSn]); + }; const closePopup = () => { // 팝업 닫기 - - fn.selectInftwtAnlsRslt(inftwtAnlsSn, pplMngNo).then((data) => {// 침입수분석 결과 조회 + + fn.selectInftwtAnlsRslt(selectedInftwtAnls, pplMngNo).then((data) => {// 침입수분석 결과 조회 const rsltData = fn.getChartDataInftwtAnlsRslt(data ?? []); setChartData(prev => ({ ...prev, chartData1: rsltData // 침입수분석결과 })); }); - fn.selectOilq(inftwtAnlsSn, pplMngNo).then((data) => {// 유량 조회 + fn.selectOilq(selectedInftwtAnls, pplMngNo).then((data) => {// 유량 조회 const rsltData = fn.getChartDataOilq(data ?? []); setChartData(prev => ({ ...prev, @@ -116,7 +129,7 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { chartData2: [] // 유량 데이터 초기화 })); }); - fn.selectPplInftwtQty(inftwtAnlsSn, pplMngNo).then((data) => {// 침입수량 조회 + fn.selectPplInftwtQty(selectedInftwtAnls, pplMngNo).then((data) => {// 침입수량 조회 if (data > 0) { setInftwtQty(data); } @@ -138,7 +151,7 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { 상수사용량 적용연월
- {(aplcnYm)? moment(aplcnYm, "YYYYMM").format("YYYY-MM"):""} + {(selectedAplcnYm) ? moment(selectedAplcnYm, "YYYYMM").format("YYYY-MM") : ""}
@@ -175,10 +188,8 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { selectClass="rounded-lg bg-white" label='' selectArray={aplcnYms} - value={aplcnYm} - onSelectValue={(val) => {console.log("선택된 상수사용월:", val); - setAplcnYm(String(val)); - }} + value={selectedAplcnYm} + onSelectValue={handleAplcnYmChange} />
@@ -188,10 +199,8 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { selectClass="rounded-lg bg-white" label='' selectArray={inftwtAnlsInfos} - value={inftwtAnlsSn} - onSelectValue={(val) => { - setInftwtAnlsSn(Number(val)); - }} + value={selectedInftwtAnls} + onSelectValue={handleInftwtAnlsSnChange} />
@@ -256,7 +265,7 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { onOpenChange={setIsModalOpen} headerTitle="" bodyClass={` - h-[50vh] + min-h-[50vh] w-[50vw] max-w-none max-h-none @@ -264,7 +273,7 @@ const InfilChart = ({ pplMngNo }: ModalOpenProp) => { z-[50] `} body={renderBody()} - noFooterYn = 'Y' + noFooterYn='Y' /> ) } diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx index 22f0d8876..33b65ace0 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx @@ -106,7 +106,9 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { }); fn.selectPplRdiiQty(rainInfwtAnlsSn, pplMngNo).then((data) => {// 강우유입수량 조회 setRdiiQty(Math.round(data.rdiih*24)); // cmd: m³/d 단위로 변환 - setTotalVolume(Math.round(data.rdiih * data.sustaintime)); // 총체적 m³/mm + if (data.raintotal && data.rainTotal !== 0) { + setTotalVolume(Math.round(data.rdiih * data.sustaintime / data.raintotal)); // 총체적 m³/mm + } }); setPopupOpen(false); }; @@ -248,7 +250,7 @@ const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { onOpenChange={setIsModalOpen} headerTitle="" bodyClass={` - h-[50vh] + min-h-[50vh] w-[50vw] max-w-none max-h-none diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index dd3ce64eb..36f72a5da 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -129,6 +129,7 @@ const Map = () => { setIsSearchModalOpen={setIsSearchModalOpen} isOpen={isOpen} isSearchModalOpen={isSearchModalOpen} + onCheckedLayersChange={undefined} />
); diff --git a/apps/sn/app/(with-base-layout)/fileValidation.tsx b/apps/sn/app/(with-base-layout)/fileValidation.tsx new file mode 100644 index 000000000..c0711a7d2 --- /dev/null +++ b/apps/sn/app/(with-base-layout)/fileValidation.tsx @@ -0,0 +1,48 @@ +/** + * 파일 확장자 검증 유틸리티 + */ + +// 파일 확장자 검증 함수 +export const validateFileExtension = (file: File, allowedExtensions: string[]): boolean => { + const fileName = file.name.toLowerCase(); + return allowedExtensions.some(ext => fileName.endsWith(ext.toLowerCase())); +}; + +// 파일 확장자 검증 결과 타입 +export interface FileValidationResult { + isValid: boolean; + errorMessage?: string; +} + +// 파일 검증 설정 타입 +export interface FileValidationConfig { + file: File; + allowedExtensions: string[]; + fileDescription: string; +} + +/** + * 여러 파일의 확장자를 한 번에 검증하는 함수 + * @param validationConfigs 검증할 파일들의 설정 배열 + * @returns 검증 결과. 실패 시 첫 번째 에러 메시지 반환 + */ +export const validateMultipleFiles = (validationConfigs: FileValidationConfig[]): FileValidationResult => { + for (const config of validationConfigs) { + if (!validateFileExtension(config.file, config.allowedExtensions)) { + const allowedExtStr = config.allowedExtensions.join(', '); + return { + isValid: false, + errorMessage: `${config.fileDescription} 파일은 ${allowedExtStr} 확장자만 업로드 가능합니다.` + }; + } + } + + return { isValid: true }; +}; + +// 자주 사용되는 확장자 상수 +export const FILE_EXTENSIONS = { + INP: ['.inp'], + EXCEL: ['.xls', '.xlsx', '.xlsm', '.xltx'], + ALL_OFFICE: ['.xls', '.xlsx', '.xlsm', '.xltx', '.doc', '.docx', '.ppt', '.pptx'] +}; \ No newline at end of file diff --git a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx index edee9b4ce..0e7a5c331 100644 --- a/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/irsetting/page.tsx @@ -6,11 +6,13 @@ import { DateMonthPicker, filePicked } from '@common/business_components/ui' import ListDiv from '@common/business_components/ui/list_div' import { Input } from '@common/components/ui' import { zodResolver } from '@hookform/resolvers/zod' -import { useState, useEffect } from 'react' +import { useState } from 'react' import { useForm } from 'react-hook-form' import { z } from 'zod' import { excelToFlowRateData, excelToMatchingResultData, excelToTimePatternData } from '@/components/lib/utils' import BaseLayout from '../BaseLayout' +import { validateMultipleFiles, FILE_EXTENSIONS, type FileValidationConfig } from '../../fileValidation'; +import { validateBasicSelectionsWithAlert } from '../../selectionValidation'; const formSchema = z.object({ managenm: z.string(), @@ -148,6 +150,17 @@ const IrSetting = () => { const handleAnalysisClick = async () => { setIsLoadingResult(true); // 로딩 상태 시작 + // 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장) + if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, { + requireEnv: true, + requireSido: true, + requireSigungu: true, + requireTreatmentPlant: true + })) { + setIsLoadingResult(false); + return; + } + console.log(uploadedFilesData); const file1 = uploadedFilesData[0]; // 오수관망도 const file2 = uploadedFilesData[1]; // 수용가-맨홀 매칭결과 @@ -167,6 +180,42 @@ const IrSetting = () => { return; } + // 파일 확장자 검증 (분리된 유틸리티 사용) + const validationConfigs: FileValidationConfig[] = [ + { + file: file1.file, + allowedExtensions: FILE_EXTENSIONS.INP, + fileDescription: '오수관망도' + }, + { + file: file2.file, + allowedExtensions: FILE_EXTENSIONS.EXCEL, + fileDescription: '수용가-맨홀 매칭결과' + }, + { + file: file3.file, + allowedExtensions: FILE_EXTENSIONS.EXCEL, + fileDescription: '오수량 시간패턴' + } + ]; + + // 유량자료 파일이 있는 경우 검증 목록에 추가 + if (file4?.file) { + validationConfigs.push({ + file: file4.file, + allowedExtensions: FILE_EXTENSIONS.EXCEL, + fileDescription: '유량자료' + }); + } + + // 파일 확장자 검증 실행 + const validationResult = validateMultipleFiles(validationConfigs); + if (!validationResult.isValid) { + setIsLoadingResult(false); + alert(validationResult.errorMessage); + return; + } + // 오수전환율 값 0 ~ 1 사이인지 검증 if (input1 === '' || input1 === null || input1 === undefined) { setIsLoadingResult(false); @@ -394,21 +443,6 @@ const IrSetting = () => { ]} - {/*
- - handleFileSelection(3, file, cd, id)} - showButton={false} - showPreview={true} - accept={".xls, .xlsx, .xlsm, .xltx"} - fileIdx={3} /> - -
- -
-
*/} ) diff --git a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx index f2ed4cf35..970c6d1a9 100644 --- a/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx +++ b/apps/sn/app/(with-base-layout)/ii/rdii/page.tsx @@ -13,6 +13,8 @@ import { useFieldArray, useForm } from "react-hook-form" import { z } from "zod" import { excelToMatchingResultData, excelToRainData } from '@/components/lib/utils' import { EditableFormTable, type EditableTableHeadItem } from '../../../components/ui/EditableFormTable' +import { validateMultipleFiles, FILE_EXTENSIONS, type FileValidationConfig } from '../../fileValidation'; +import { validateBasicSelectionsWithAlert } from '../../selectionValidation'; // zod 스키마 정의 @@ -167,7 +169,18 @@ export default function RTKForm() { // 침입수 분석 실행 버튼 클릭 핸들러 const handleAnalysisClick = async () => { setIsLoadingResult(true); // 로딩 상태 시작 - + + // 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장) + if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, { + requireEnv: true, + requireSido: true, + requireSigungu: true, + requireTreatmentPlant: true + })) { + setIsLoadingResult(false); + return; + } + const formData = form.getValues(); console.log(uploadedFilesData); const file1 = uploadedFilesData[0]; // 오수관망도 @@ -185,6 +198,33 @@ export default function RTKForm() { return; } + // 파일 확장자 검증 (분리된 유틸리티 사용) + const validationConfigs: FileValidationConfig[] = [ + { + file: file1.file, + allowedExtensions: FILE_EXTENSIONS.INP, + fileDescription: '오수관망도' + }, + { + file: file2.file, + allowedExtensions: FILE_EXTENSIONS.EXCEL, + fileDescription: '수용가-맨홀 매칭결과' + }, + { + file: file3.file, + allowedExtensions: FILE_EXTENSIONS.EXCEL, + fileDescription: '오수량 시간패턴' + } + ]; + + // 파일 확장자 검증 실행 + const validationResult = validateMultipleFiles(validationConfigs); + if (!validationResult.isValid) { + setIsLoadingResult(false); + alert(validationResult.errorMessage); + return; + } + // 오수전환율 값 0 ~ 1 사이인지 검증 const rateValue = form.getValues().rate; if (rateValue === '' || rateValue === null || rateValue === undefined) { @@ -193,7 +233,7 @@ export default function RTKForm() { alert('오수전환율을 입력해주세요.'); return; } - + const numericInput1 = parseFloat(rateValue); if (isNaN(numericInput1) || numericInput1 < 0 || numericInput1 > 1) { setIsLoadingResult(false); @@ -359,7 +399,7 @@ export default function RTKForm() { selectedSwageTreatmentPlant={select4} onSwageTreatmentPlantChange={setSelect4} selectedSwageProcessingZone={select5} - onSwageProcessingZoneChange={setSelect4} + onSwageProcessingZoneChange={setSelect5} isLoading={isLoadingResult} onAnalysis={handleAnalysisClick} > diff --git a/apps/sn/app/(with-base-layout)/selectionValidation.tsx b/apps/sn/app/(with-base-layout)/selectionValidation.tsx new file mode 100644 index 000000000..8cc506d0e --- /dev/null +++ b/apps/sn/app/(with-base-layout)/selectionValidation.tsx @@ -0,0 +1,161 @@ +/** + * 선택 항목 검증 유틸리티 + */ + +export interface ApiSelectOption { + text: string; + val: string; +} + +// 선택 항목 검증 설정 타입 +export interface SelectionValidationConfig { + value: string | number | ApiSelectOption; + description: string; + required: boolean; +} + +// 선택 항목 검증 결과 타입 +export interface SelectionValidationResult { + isValid: boolean; + errorMessage?: string; +} + +/** + * 단일 선택 항목이 유효한지 검증하는 함수 + * @param value 검증할 값 + * @param isRequired 필수 선택 여부 + * @returns 유효한지 여부 + */ +export const isValidSelection = ( + value: string | number | ApiSelectOption, + isRequired: boolean = true +): boolean => { + if (!isRequired) return true; + + if (typeof value === 'object' && value !== null) { + // ApiSelectOption 객체인 경우 + return value.val !== '0' && value.val !== '' && value.val !== null && value.val !== undefined; + } + + // 문자열 또는 숫자인 경우 + return value !== '0' && value !== 0 && value !== '' && value !== null && value !== undefined; +}; + +/** + * 여러 선택 항목을 한 번에 검증하는 함수 + * @param validationConfigs 검증할 선택 항목들의 설정 배열 + * @returns 검증 결과. 실패 시 첫 번째 에러 메시지 반환 + */ +export const validateMultipleSelections = ( + validationConfigs: SelectionValidationConfig[] +): SelectionValidationResult => { + for (const config of validationConfigs) { + if (!isValidSelection(config.value, config.required)) { + return { + isValid: false, + errorMessage: `${config.description}을(를) 선택해주세요.` + }; + } + } + + return { isValid: true }; +}; + +/** + * 여러 선택 항목을 한 번에 검증하고 alert 표시하는 함수 + * @param validationConfigs 검증할 선택 항목들의 설정 배열 + * @returns 검증 결과 + */ +export const validateMultipleSelectionsWithAlert = ( + validationConfigs: SelectionValidationConfig[] +): boolean => { + const result = validateMultipleSelections(validationConfigs); + if (!result.isValid && result.errorMessage) { + alert(result.errorMessage); + return false; + } + return true; +}; + +/** + * 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장) + * @param select1 환경청코드 + * @param select2 시도코드 + * @param select3 시군구 객체 + * @param select4 하수처리장코드 + * @param options 각 항목의 필수 여부 설정 + * @returns 검증 결과 + */ +export const validateBasicSelections = ( + select1: string, + select2: string, + select3: ApiSelectOption, + select4: string | number, + options: { + requireEnv?: boolean; + requireSido?: boolean; + requireSigungu?: boolean; + requireTreatmentPlant?: boolean; + } = {} +): SelectionValidationResult => { + const { + requireEnv = true, + requireSido = true, + requireSigungu = true, + requireTreatmentPlant = true + } = options; + + const validationConfigs: SelectionValidationConfig[] = [ + { + value: select1, + description: '환경청', + required: requireEnv + }, + { + value: select2, + description: '시도', + required: requireSido + }, + { + value: select3, + description: '시군구', + required: requireSigungu + }, + { + value: select4, + description: '하수처리장', + required: requireTreatmentPlant + } + ]; + + return validateMultipleSelections(validationConfigs); +}; + +/** + * 기본 선택 항목들 검증 및 alert 표시 + * @param select1 환경청코드 + * @param select2 시도코드 + * @param select3 시군구 객체 + * @param select4 하수처리장코드 + * @param options 각 항목의 필수 여부 설정 + * @returns 검증 결과 + */ +export const validateBasicSelectionsWithAlert = ( + select1: string, + select2: string, + select3: ApiSelectOption, + select4: string | number, + options: { + requireEnv?: boolean; + requireSido?: boolean; + requireSigungu?: boolean; + requireTreatmentPlant?: boolean; + } = {} +): boolean => { + const result = validateBasicSelections(select1, select2, select3, select4, options); + if (!result.isValid && result.errorMessage) { + alert(result.errorMessage); + return false; + } + return true; +}; \ No newline at end of file diff --git a/apps/sn/app/components/lib/api/index.ts b/apps/sn/app/components/lib/api/index.ts index 11267a573..e4a750ff3 100644 --- a/apps/sn/app/components/lib/api/index.ts +++ b/apps/sn/app/components/lib/api/index.ts @@ -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 => { const options: RequestInit = { diff --git a/apps/sn/app/components/lib/utils/date.ts b/apps/sn/app/components/lib/utils/date.ts index fe717301f..9c9106ec2 100644 --- a/apps/sn/app/components/lib/utils/date.ts +++ b/apps/sn/app/components/lib/utils/date.ts @@ -12,6 +12,38 @@ export const yearOptions = Array.from({ length: 10 }, (_, i) => { return { text: `${year}년`, val: year.toString() }; }); +// 현재 날짜를 기준으로 현재 분기까지의 분기 옵션 생성 +export const getQuarterOptions = (selectedYear?: string): { text: string; val: string }[] => { + const currentDate = new Date(); + const currentYear = currentDate.getFullYear().toString(); + const currentMonth = currentDate.getMonth() + 1; // 0부터 시작하므로 +1 + + // 현재 년도인 경우에만 현재 날짜까지의 분기 표시 + if (selectedYear === currentYear) { + // 현재 월을 기준으로 현재 분기 계산 + const currentQuarter = Math.ceil(currentMonth / 3); + + // 현재 분기까지의 분기 옵션 생성 + const quarterOptions = []; + for (let i = 1; i <= currentQuarter; i++) { + quarterOptions.push({ + text: `${i}분기`, + val: i.toString() + }); + } + + return quarterOptions; + } else { + // 이전 년도인 경우 모든 분기 표시 + return [ + { text: "1분기", val: "1" }, + { text: "2분기", val: "2" }, + { text: "3분기", val: "3" }, + { text: "4분기", val: "4" } + ]; + } +}; + // ["202506", "202505", "202502", "202405"] 을 SelectBox의 옵션으로 변환 export const convertToTextValArray = (yyyymms: string[]): { text: string; val: string }[] => { return yyyymms.map(dateStr => { diff --git a/apps/sn/app/components/ui/operFileUploadForm.tsx b/apps/sn/app/components/ui/operFileUploadForm.tsx index 42d824e1b..3c37673d2 100644 --- a/apps/sn/app/components/ui/operFileUploadForm.tsx +++ b/apps/sn/app/components/ui/operFileUploadForm.tsx @@ -1,17 +1,30 @@ "use client" import { SelectBox } from '@common/business_components/ui'; -import React, { useState } from 'react' +import React, { useState, useEffect } from 'react' import FileUploadForm from './fileUploadForm'; -import { yearOptions } from '@/components/lib/utils'; +import { yearOptions, getQuarterOptions } from '@/components/lib/utils'; const operFileUploadForm = ({ title, onSubmit }: { title: string, onSubmit: (files: string, year: string, quarter: string, code: string, id: string) => void }) => { const [atflCd, setAtflCd] = useState(null); const [atflId, setAtflId] = useState(null); - - const quater = [{ text: '1분기', val: '1' }, { text: '2분기', val: '2' }, { text: '3분기', val: '3' }, { text: '4분기', val: '4' }] const [selectedYear, setSelectedYear] = useState(yearOptions[0]?.val || ''); - const [selectedQuarter, setSelectedQuarter] = useState(quater[0]?.val || ''); + const [quaterOptions, setQuaterOptions] = useState(() => getQuarterOptions(selectedYear)); + const [selectedQuarter, setSelectedQuarter] = useState(quaterOptions[0]?.val || ''); + + // 년도가 변경될 때 분기 옵션 업데이트 + useEffect(() => { + const updatedQuarterOptions = getQuarterOptions(selectedYear); + setQuaterOptions(updatedQuarterOptions); + // 분기 옵션이 변경되면 첫 번째 옵션으로 자동 선택 + setSelectedQuarter(updatedQuarterOptions[0]?.val || ''); + }, [selectedYear]); + + // 년도 변경 핸들러 + const handleYearChange = (value: string | number) => { + const yearValue = String(value); + setSelectedYear(yearValue); + }; return (
@@ -26,13 +39,16 @@ const operFileUploadForm = ({ title, onSubmit }: { title: string, onSubmit: (fil selectClass="rounded-lg bg-white" label="년도" selectArray={yearOptions} - onSelectValue={(value: string | number) => setSelectedYear(String(value))} - /> + setSelectedQuarter(String(value))} + value={selectedQuarter} /> { diff --git a/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx b/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx new file mode 100644 index 000000000..1fc0cef80 --- /dev/null +++ b/apps/sn/app/map/components/popupComponent/MsSwagePpl.tsx @@ -0,0 +1,87 @@ +import { VerticalTable } from '../../../components/ui/verticalTable'; +import React, { useState } from 'react' +import InputSection from '../InputSection'; +import { handleInputChange } from '../common'; +import { Button } from '@common/components/ui'; +import InfilChart from './chart/infilChart'; +// 하수관로 +// TODO: RDII 버튼인가 그거 팝업 두개 구현 필요 +const inputColumn = [ + { id: "title", title: "설치주제", type: "select", options: ["BTL", "재정사업"] }, + { id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] }, + { id: "basu", title: "배수구역", type: "input" }, + { id: "mltv_dtdv_nm", title: "배수분구", type: "input" }, + { id: "wltjs", title: "지선/간선", type: "select", options: ["지선", "간선"] }, + { id: "flow", title: "흐름방식", type: "select", options: ["자연유하", "압송"] }, + { id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] }, +]; +const SwtrmtFclt = ({ featureData }: { featureData: any }) => { + console.log("featureData", featureData); + const [isModalOpen, setIsModalOpen] = React.useState(false); + const [btnTxt, setBtnTxt] = useState(""); + const openModal = (e: React.MouseEvent) => { + e.currentTarget.textContent && setBtnTxt(e.currentTarget.textContent) + setIsModalOpen(true); + }; + const closeModal = () => { + setIsModalOpen(false); + }; + const [repairData, setRepairData] = useState([ + { year: "2025", type: "개대체" }, + { year: "2025", type: "보수" }, + ]); + const [cleaningData, setCleaningData] = useState([ + { year: "2025", type: "청소" }, + { year: "2025", type: "준설" }, + ]); + const [inspectionData, setInspectionData] = useState([ + { year: "2025", type: "관로기술진단" }, + { year: "2025", type: "관로기술진단" }, + ]); + const dataMap = { + repair: { data: repairData, setData: setRepairData }, + cleaning: { data: cleaningData, setData: setCleaningData }, + inspection: { data: inspectionData, setData: setInspectionData }, + }; + const keysToExtract = [ + "swtrmt_zone_cd", + "mltv_dtdv_nm", + "fid", + "lyrTypeNm", + "reg_dt", + "mdfcn_dt", + "rgtr", + "mdfr", + ]; + // const filteredData = Object.entries(featureData) + // .filter(([key]) => keysToExtract.includes(key)) // 필요한 키만 필터링 + // .map(([key, value]) => ({ key, value: value ?? "N/A" })); // 테이블 데이터 형식으로 변환 + + return ( +
+ + handleInputChange(section, index, field, value, dataMap)} /> + handleInputChange(section, index, field, value, dataMap)} /> + handleInputChange(section, index, field, value, dataMap)} /> +
+ + +
+ +
+ ) +} + +export default SwtrmtFclt \ No newline at end of file diff --git a/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx b/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx new file mode 100644 index 000000000..0d1c3ef2e --- /dev/null +++ b/apps/sn/app/map/components/popupComponent/SwtrmtFclt.tsx @@ -0,0 +1,49 @@ +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' + +// 하수처리시설 +const headList = [ + { id: "title", title: "하수도정비 기본계획보고서" }, + { id: "cad", title: "하수도대장 평면도" },] +const modalHeadList = [ + { id: "no", title: "번호" }, + { id: "year", title: "승인연도" }, + { id: "cad", title: '평면도'}, + { id: "fileNm", title: "파일명" }, + { id: "date", title: "입력시간" }, +] + +const SwtrmtFclt = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalTitle, setModalTitle] = useState(''); + const openModal = (title:string) => { + setIsModalOpen(true); + setModalTitle(title) + }; + const closeModal = () => { + setIsModalOpen(false); + }; + return ( +
+ openModal('하수도정비 기본계획보고서')}>보고서, + cad: , + }} headName='디지털 자료관 연계정보' tableData={[]} headlist={headList} pageSize={20} total={0} cellClick={undefined} isEditable={true} onPageChange={function (page: number): void { + throw new Error("Function not implemented."); + }}> + +
+ +
+
+
+ ) +} + +export default SwtrmtFclt \ No newline at end of file diff --git a/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx b/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx new file mode 100644 index 000000000..06c5b5fec --- /dev/null +++ b/apps/sn/app/map/components/popupComponent/chart/infilChart.tsx @@ -0,0 +1,140 @@ +import React, { useState } from 'react' +import { type ChartConfig, MainChart, CHART_FORM } from "@common/business_components"; +import Modal from '../../../../components/ui/modal'; +import { Button } from '@common/components/ui'; +type ModalOpenProp = { + isModalOpen: boolean, + setIsModalOpen: (open: boolean) => void, + closeModal: () => void, + btnTxt?: string, +} +const InfilChart = ({ btnTxt, isModalOpen, setIsModalOpen, closeModal }: ModalOpenProp) => { + const [popupOpen, setPopupOpen] = React.useState(false); + const buttonRef = React.useRef(null); // 버튼 참조 + const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 + const openPopup = () => { + if (buttonRef.current) { + const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산 + setPopupPosition({ + top: rect.bottom + window.scrollY, // 버튼 아래쪽 + left: rect.left + window.scrollX, // 버튼 왼쪽 + }); + } + setPopupOpen(true); // 팝업 열기 + }; + console.log("btnTxt", btnTxt); + const closePopup = () => {setPopupOpen(false);}; // 팝업 닫기 + const chartForm = CHART_FORM.LINE; + const chartData = { + chartData1: [ + { label: "1", val: 20 }, + { label: "3", val: 40 }, + { label: "5", val: 60 }, + ], + chartData2: [ + { label: "1", val: 15 }, + { label: "3", val: 35 }, + { label: "5", val: 55 }, + ], + } + const chartConfig = Object.values(chartData).flat().reduce((config, element) => { + config[element.label] = { + label: element.label, + color: "hsl(var(--chart-1))", + }; + return config; + }, {} as Record) satisfies ChartConfig; + const getCurrentDate = () => { + const today = new Date(); + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, "0"); // 월은 0부터 시작하므로 +1 + return `${year}-${month}`; + }; + return ( + + {/* 분석 입력현황 조회 */} +
+
+ + + {/* 침입수량 분석결과 */} +
+ 침입수량 분석결과 +
+ 120 + m³/d +
+
+
+ {/* 차트 */} +
+ +
+
+ {popupOpen && ( +
+
분석 입력현황
+
+
상수사용량 정보:
+
+ +
+
오수 전환율:
+
+ +
+
수용가-맨홀 매칭:
+
+ +
+
관망도 자료:
+
+ +
+ {btnTxt === 'RDII' ? <>
강우 자료:
+
+ +
+
RTX 계수 출처:
+
+ +
+ : null} +
+
+ + +
+
+ )} +
+ ) +} + +export default InfilChart \ No newline at end of file diff --git a/apps/sn/app/map/components/popupComponent/sewagePump.tsx b/apps/sn/app/map/components/popupComponent/sewagePump.tsx new file mode 100644 index 000000000..79103a40e --- /dev/null +++ b/apps/sn/app/map/components/popupComponent/sewagePump.tsx @@ -0,0 +1,18 @@ +import { VerticalTable } from '../../../components/ui/verticalTable'; +import React from 'react' +const inputColumn = [ + { id: "swtrmt_zone_cd", title: "처리구역", type: "select", options: ["구역1", "구역2"] }, + { id: "mltv_dtdv_nm", title: "배수분구", type: "input" }, + { id: "freq", title: "설계빈도", type: "input" }, +] +// 오수펌프 +const sewagePump = ({ featureData }: { featureData: any }) => { + return ( +
+
+ ) +} + +export default sewagePump diff --git a/apps/sn/app/map/components/popupComponent/smh.tsx b/apps/sn/app/map/components/popupComponent/smh.tsx new file mode 100644 index 000000000..39920232b --- /dev/null +++ b/apps/sn/app/map/components/popupComponent/smh.tsx @@ -0,0 +1,21 @@ +// 하수맨홀 +import { VerticalTable } from '../../../components/ui/verticalTable'; +import React from 'react' +const inputColumn = [ + { id: "guyeok", title: "처리구역", type: "select", options: ["구역1", "구역2"] }, + { id: "basu", title: "배수구역", type: "input" }, + { id: "bungu", title: "배수분구", type: "input" }, + { id: "place", title: "매설위치", type: "select", options: ["도로", "하상"] }, + {id:'spec', title:'규격', type:'input'}, +]; +const Smh = () => { + return ( +
+ +
+ ) +} + +export default Smh; \ No newline at end of file diff --git a/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx b/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx new file mode 100644 index 000000000..172022f31 --- /dev/null +++ b/apps/sn/app/map/components/popupComponent/stormWaterPump.tsx @@ -0,0 +1,19 @@ +import { VerticalTable } from '../../../components/ui/verticalTable'; +import React from 'react' +const inputColumn = [ + { id: "swtrmt_zone_cd", title: "처리구역", type: "input" }, + { id: "mltv_dtdv_nm", title: "배수분구", type: "input" }, +] +// 빗물펌프 +const StormWaterPump = ({ featureData }: { featureData: Record }) => { + const validKey = inputColumn.map((item) => item.id); + const filteredData = featureData && Object.entries(featureData).filter(([key]) => validKey.includes(key)).reduce((obj: Record, [key, value]) => {obj[key] = value; return obj;}, {}); + return ( +
+
+ ) +} + +export default StormWaterPump \ No newline at end of file diff --git a/apps/wr/.env.development b/apps/wr/.env.development new file mode 100644 index 000000000..dccfb1453 --- /dev/null +++ b/apps/wr/.env.development @@ -0,0 +1,12 @@ +NEXT_PUBLIC_SITE_ID = WR +NEXT_PUBLIC_PORT = 11301 +NEXT_PUBLIC_PROXY_HOST = http://con.mind-one.co.kr:11301 +NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +NEXT_PUBLIC_TEMP_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함 +NEXT_PUBLIC_CLAIM_NAME = Authorization +NEXT_PUBLIC_AUTH_USER_ID = token-id +NEXT_PUBLIC_ACCESS_TOKEN = access-token +NEXT_PUBLIC_REFRESH_TOKEN = refresh-token + +NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API diff --git a/apps/wr/.env.local b/apps/wr/.env.local new file mode 100644 index 000000000..858181f32 --- /dev/null +++ b/apps/wr/.env.local @@ -0,0 +1,14 @@ +NEXT_PUBLIC_SITE_ID = WR +NEXT_PUBLIC_PORT = 3001 +NEXT_PUBLIC_PROXY_HOST = http://localhost:3001 +NEXT_PUBLIC_SERVER_API_URL = http://dev.onasys.co.kr:8000 +NEXT_PUBLIC_GEOSERVER_URL = http://rsone.iptime.org:10010/geoserver +NEXT_PUBLIC_TEMP_GEOSERVER_URL = http://con.mind-one.co.kr:11306/geoserver #TODO 운영에서 바꿔야 함 +NEXT_PUBLIC_CLAIM_NAME = Authorization +NEXT_PUBLIC_AUTH_USER_ID = token-id +NEXT_PUBLIC_ACCESS_TOKEN = access-token +NEXT_PUBLIC_REFRESH_TOKEN = refresh-token + +NEXT_PUBLIC_SEWERAGE_API_URL=http://con.mind-one.co.kr:11083/sewerage-service/api # 개발 서버 API +#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.128:8883/sewerage-service/api # 최용성 +#NEXT_PUBLIC_SEWERAGE_API_URL=http://192.168.1.66:8883/sewerage-service/api # 박이정 diff --git a/apps/wr/.gitignore b/apps/wr/.gitignore index 5ef6a5207..8c5b2ee3a 100644 --- a/apps/wr/.gitignore +++ b/apps/wr/.gitignore @@ -31,7 +31,7 @@ yarn-error.log* .pnpm-debug.log* # env files (can opt-in for committing if needed) -.env* +#.env* # vercel .vercel diff --git a/apps/wr/app/map/featureClickEventPopup.tsx b/apps/wr/app/map/featureClickEventPopup.tsx new file mode 100644 index 000000000..8048d0997 --- /dev/null +++ b/apps/wr/app/map/featureClickEventPopup.tsx @@ -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; 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(""); // 레이어 이름 상태 + const [tableData, setTableData] = useState(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(); // api 호출여부 + const contentRef = useRef(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 = {} + 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 : null; + return ( + + ) +} +export default FeatureClickEventPopup; \ No newline at end of file diff --git a/apps/wr/app/map/htdflPrfmncSttnPopup.tsx b/apps/wr/app/map/htdflPrfmncSttnPopup.tsx new file mode 100644 index 000000000..8d1bec486 --- /dev/null +++ b/apps/wr/app/map/htdflPrfmncSttnPopup.tsx @@ -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 = hdtflZod.parse({}); + + const form = useForm>({ + resolver: zodResolver(hdtflZod), + defaultValues: defaultValues, + }); + + const onSubmit = (values: z.infer) => { + if (isModal) { + htdflBdstReg(values, '41'); + } else { + htdflBdstUpdate(values, '41'); + } + }; + + + const cleanHtdflData = (raw: any): z.infer => { + 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 ( +
+
+ + {/*
*/} +
+
+

온배수 시설 정보

+
+
+ + {isModal && htdflForm != undefined ? : <>} + {isModal ? ( +

+ 온배수 시설 정보{" "} +

+ ) : ( + <> + )} + +
+
+ ( + + + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+
+

+ 온배수 시설 운영현황 +

+
+ +
+
+ ( + + { + field.onChange(Number(e.target.value)); + }} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ ( + + + + )} + /> +
+
+ +
+

+ 요구수질 +

+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ +
+
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+ +
+ ( + + { + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={true} + /> + + )} + /> +
+
+ + +
+ ); +} diff --git a/apps/wr/app/map/htdflSttn/page.tsx b/apps/wr/app/map/htdflSttn/page.tsx new file mode 100644 index 000000000..7b804b7cd --- /dev/null +++ b/apps/wr/app/map/htdflSttn/page.tsx @@ -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(null); + const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 + const [isSearchModalOpen, setIsSearchModalOpen] = useState(true); // SearchModal 열림 상태 + const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터 + const [checkedLayers, setCheckedLayers] = useState([]); + const [checkedLayersData, setCheckedLayersData] = useState([]); + + 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 ( + // + // ); + // }; + + return ( +
+ ( + // + // ) + // } + detailView={(props: any) => ( + + )} + onFeatureSelect={undefined} + setIsOpen={setIsOpen} + setFeatureData={setFeatureData} + featureData={featureData} + setIsSearchModalOpen={setIsSearchModalOpen} + isOpen={isOpen} + isSearchModalOpen={isSearchModalOpen} + onCheckedLayersChange={handleCheckedLayersChange} + /> +
+ ); +}; + +export default Map; diff --git a/apps/wr/app/map/mapSearchBar.tsx b/apps/wr/app/map/mapSearchBar.tsx new file mode 100644 index 000000000..b24b5d83a --- /dev/null +++ b/apps/wr/app/map/mapSearchBar.tsx @@ -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; +// 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(null); +// const [isChHead, setIsChHead] = useState(false); +// const ref = useRef({} as ReturnType); + +// // zustand store에서 건물 검색 함수와 데이터 가져오기 +// const { +// code, +// bdst, +// findBdst, +// wrMap, +// buildingSearchRes, +// buildingNmSearch, +// buildingNmSearchMultiple +// } = useWrStore((state) => state); + +// const [selectedBuilding, setSelectedBuilding] = useState(null); +// const [buildingSearchText, setBuildingSearchText] = useState(""); +// const [buildingList, setBuildingList] = useState([]); + +// // 전체 목록 +// const [partValue, setPartValue] = useState("A"); +// const [sidoValue, setSidoValue] = useState("0"); +// const [sigunValue, setSigunValue] = useState("0"); +// const [regionValue, setRegionValue] = useState("0"); +// const [untitWaterShedValue, setUntitWaterShedValue] = useState("0"); +// const [middleRegionValue, setMiddleRegionValue] = useState("0"); +// const [envPartValue, setEnvPartValue] = useState("0"); +// const [resetTrigger, setResetTrigger] = useState(false); +// const [sidoText, setSidoText] = useState("0"); +// const [sigunText, setSigunText] = useState("0"); +// // 공급처 후보지 조회 필터 목록 +// const [selectedSido, setSelectedSido] = useState({ text: "전체", val: '0' }); +// const [selectedSigun, setSelectedSigun] = useState({ text: "전체", val: '0' }); +// const [selectedFclt, setSelectedFclt] = useState({ text: "전체", val: '0', sggNm: '전체' }); // 시설명 선택 상태 +// // 전체 시설 +// let allFclt: SelectedDataTypeWithSggNm[] = [{ text: "전체", val: '0', sggNm: '전체' }]; +// const [selectedDong, setSelectedDong] = useState({ 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 ( +//
+//
+//
+// {searchType == "공급처 후보지 조회" && ( +// <> +// { +// 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'} +// /> +// { +// const selected = filteredSigunData.find(item => (item.val === val)); +// setSelectedSigun(selected || { text: "전체", val: '0' }); +// setSelectedFclt({ text: "전체", val: '0', sggNm: '전체' }); +// }} +// value={selectedSigun.val} +// selectClass={'w-32'} +// /> +//
+// +//
+// { +// setBuildingSearchText(searchNm || ""); +// }} +// setRtnData={function (rtnData: any): void { +// // 선택된 건물 데이터 처리 +// // if (rtnData && rtnData.bdstInfoPk) { +// // setSelectedBuilding(rtnData); +// // setBuildingSearchText(rtnData.bdstNm || ""); +// // } +// }} +// searchList={buildingSearchRes || []} +// /> +//
+//
+// +// )} +// {searchType == "우리동네가뭄지도보기" && ( +// <> +// { +// 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'} +// /> +// { +// const selected = filteredSigunData.find(item => (item.val === val)); +// setSelectedSigun(selected || { text: "전체", val: '0' }); +// setSelectedDong({ text: "전체", val: '0', sggNm: '전체' }); +// }} +// value={selectedSigun.val} +// selectClass={'w-32'} +// /> +// { +// const selected = filteredDong.find(item => (item.val === val)); +// setSelectedDong(selected || { text: "전체", val: '0', sggNm: '전체' }); +// }} +// value={selectedDong.val} +// selectClass={'w-32'} +// /> +// +// )} +// {searchType == "지도보기" && ( +//
+//
{/* SearchFormLeft 래퍼 */} +// +//
+// +//
+// +//
+//
+// )} +//
+// {/* 버튼 부분 */} +//
+// {showQueryButton && } +// {showInitButton && } +// {showApplyButton && } +//
+//
+//
+// ) +// } diff --git a/apps/wr/app/map/mapSearchForm.tsx b/apps/wr/app/map/mapSearchForm.tsx new file mode 100644 index 000000000..70927996b --- /dev/null +++ b/apps/wr/app/map/mapSearchForm.tsx @@ -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('A'); + //행정별 시도 시군구 + const [sidoValue, setSidoValue] = useState('0'); + const [sigunValue, setSigunValue] = useState('0'); + const [sidoText, setSidoText] = useState(''); + const [sigunText, setSigunText] = useState(''); + //유역별 + const [regionValue, setRegionValue] = useState('0'); + const [unitWaterShedValue, setUnitWaterShedValue] = useState('0'); + const [middleRegionValue, setMiddleRegionValue] = useState('0'); + //환경청별 시도 시군구 + const [envPartValue, setEnvPartValue] = useState('0'); + // 환경청 + const [filteredEnvPartSigunDataSelected, setFilteredEnvPartSigunDataSelected] = useState([]); + + /****************** 셀렉트 리스트 데이터. ******************/ + // 조회된 조건에 맞는 시도 시군 유역 + const [filteredSigunData, setFilteredSigunData] = useState([{ text: "전체", val: '0' }]); + const [filteredUnitWaterShedData, setFilteredUnitWaterShedData] = useState([{ text: "전체", val: '0' }]); + const [filteredMiddleRegionData, setFilteredMiddleRegionData] = useState([{ text: "전체", val: '0' }]); + // 조회된 조건에 맞는 환경청 시도 시군 + const [filteredEnvPartData, setFilteredEnvPartData] = useState([{ text: "전체", val: '0', adstDliStrCd: '' }]); + const [filteredEnvPartSidoData, setFilteredEnvPartSidoData] = useState([{ text: "전체", val: '0', adstDliStrCd: '' }]); + const [filteredEnvPartSigunData, setFilteredEnvPartSigunData] = useState([{ 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 ? ( +
+ onSelectValue(val.toString(), 'part')} value={partValue} selectClass={'w-32'} /> + {partValue === 'R' ? ( + <> + onSelectValue(val.toString(), 'region')} value={regionValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'unitWaterShed')} value={unitWaterShedValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'middleRegion')} value={middleRegionValue} selectClass={'w-44'} /> + + ) : partValue === 'G' ? ( + <> + onSelectValue(val.toString(), 'envPart')} value={envPartValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'envPartSido')} value={sidoValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'envPartSigun')} value={sigunValue} selectClass={'w-44'} /> + + ) : ( + <> + onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={'w-44'} /> + onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={'w-44'} /> + + )} +
+ ) : ( +
+ {children} +
+ ) + ) +} + +export { MapSearchFormLeft }; diff --git a/apps/wr/app/map/newWindowPopup.tsx b/apps/wr/app/map/newWindowPopup.tsx new file mode 100644 index 000000000..ebfcac1ca --- /dev/null +++ b/apps/wr/app/map/newWindowPopup.tsx @@ -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 = ({ + 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(); + 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(); + 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(); + 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(); + 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 ( +
+ {/* 체크된 레이어가 없는 경우 (재이용시설 목록) */} + {isPbnst && ( + <> + {showDetailView ? ( + // 재이용시설 상세 + <> +
+ + </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; \ No newline at end of file diff --git a/apps/wr/app/map/newWindowPortal.tsx b/apps/wr/app/map/newWindowPortal.tsx new file mode 100644 index 000000000..49949e0f4 --- /dev/null +++ b/apps/wr/app/map/newWindowPortal.tsx @@ -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; \ No newline at end of file diff --git a/apps/wr/app/map/pbnstPrfmncSttnPopup.tsx b/apps/wr/app/map/pbnstPrfmncSttnPopup.tsx new file mode 100644 index 000000000..f6d870092 --- /dev/null +++ b/apps/wr/app/map/pbnstPrfmncSttnPopup.tsx @@ -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" /> + </> + ) +} diff --git a/apps/wr/app/map/rainPrfmncSttnPopup.tsx b/apps/wr/app/map/rainPrfmncSttnPopup.tsx new file mode 100644 index 000000000..18cdac8e6 --- /dev/null +++ b/apps/wr/app/map/rainPrfmncSttnPopup.tsx @@ -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> + ); +} diff --git a/apps/wr/app/map/rswtrPrfmncSttnPopup.tsx b/apps/wr/app/map/rswtrPrfmncSttnPopup.tsx new file mode 100644 index 000000000..e7f94a3b5 --- /dev/null +++ b/apps/wr/app/map/rswtrPrfmncSttnPopup.tsx @@ -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> + ); +} diff --git a/apps/wr/app/map/service/index.ts b/apps/wr/app/map/service/index.ts new file mode 100644 index 000000000..c6bfcff72 --- /dev/null +++ b/apps/wr/app/map/service/index.ts @@ -0,0 +1 @@ +export * from './map' diff --git a/apps/wr/app/map/service/map.ts b/apps/wr/app/map/service/map.ts new file mode 100644 index 000000000..5002afef7 --- /dev/null +++ b/apps/wr/app/map/service/map.ts @@ -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(); + } +} diff --git a/apps/wr/app/matMng/coMat/coMatRvw/page.tsx b/apps/wr/app/matMng/coMat/coMatRvw/page.tsx index c1e601320..15bff0b32 100644 --- a/apps/wr/app/matMng/coMat/coMatRvw/page.tsx +++ b/apps/wr/app/matMng/coMat/coMatRvw/page.tsx @@ -9,6 +9,7 @@ import { } from "@/app/store/matDmd/coMatRvw/type"; import { formH1Class, formTableName } from "@/components/lib/class/comClass"; import { + formatPhoneNumber, getCity, getdmdMatFlag, tableData, @@ -33,7 +34,7 @@ import { import ListDiv from "@common/business_components/ui/list_div"; import { Button, Input, Label } from "@common/components/ui"; import { useEffect, useRef, useState } from "react"; -import { useForm } from "react-hook-form"; +import { useForm, useWatch } from "react-hook-form"; import toast, { Toaster } from "react-hot-toast"; import { z } from "zod"; import moment from "moment"; @@ -45,7 +46,7 @@ import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/b export default function CoMatRvw() { const { code, coRvw, coMatRvwInit, coMatRvwSearch, coMatRvwInqua } = useWrStore((state) => state); - const { coMatRvwRes } = coRvw; + const { coMatRvwRes,coMatRvwCount } = coRvw; //init, sub, match const initRef = useRef<boolean>(false); const { coRvwHeadList } = coRvw; @@ -68,6 +69,7 @@ export default function CoMatRvw() { const [selectData, setSelectData] = useState<DmdCoReqType>( {} as DmdCoReqType ); + const [selectionVersion, setSelectionVersion] = useState(0); const getSearch = ( startYear: number, @@ -91,39 +93,45 @@ export default function CoMatRvw() { const form = useForm<z.infer<typeof DmdCoReqSchema>>({ resolver: zodResolver(DmdCoReqSchema), + shouldUnregister: false, + // defaultValues: DmdCoReqSchema.parse({}) }); + const TableCellClick = (index: number) => { - console.log(coMatRvwRes); if (coMatRvwRes && coMatRvwRes[index]) { - setSelectData(coMatRvwRes[index]); + setSelectionVersion((v) => v + 1); // 행을 클릭할 때마다 버전을 1씩 증가 } }; + const useUsgCdWatch = useWatch({ control: form.control, name: "useUsgCd" }); useEffect(() => { - if (form?.getValues("useUsgCd")) { + + if (useUsgCdWatch) { form.setValue( "demandCst.usgPrcsProcs", usageProcData?.find( - (item) => item.usage === (form?.getValues("useUsgCd") as string) + (item) => item.usage === useUsgCdWatch )?.process ?? "" ); } - }, [form.watch("useUsgCd")]); + }, [useUsgCdWatch, form,selectionVersion]); useEffect(() => { - if (coMatRvwRes.length > 0) { + if (coMatRvwRes&&coMatRvwRes.length > 0) { const data = coMatRvwRes?.map((item, index) => { let flag = ""; - flag = getdmdMatFlag(item?.demandMatching?.splyFcltFlg ??""); + + flag = getdmdMatFlag(item?.demandMatching?.splyFcltFlg ||undefined); + return { ...item, ctpvCd: item.demandCo?.ctpvCd, sggCd: item.demandCo?.sggCd, dmdCoNm: item.demandCo?.dmdCoNm, daddr: item.demandCo?.daddr, - telno: item.demandCo?.telno, - regDt: moment(item?.demandCo?.regDt).format("YYYY-MM-DD"), + telno: formatPhoneNumber(item?.demandCo?.telno||''), + dmndTmng: item?.dmndTmng, splyFcltFlg: flag, }; }); @@ -151,14 +159,23 @@ export default function CoMatRvw() { useEffect(() => { if (selectData) { - console.log(selectData); - form.reset(selectData); + + try { + DmdCoReqSchema.parse(selectData); + } catch (e) { + if (e instanceof z.ZodError) { + console.log(e.flatten()); + } + } + form.reset( selectData); + setSelectionVersion((v) => v + 1); // 강제로 바뀌었다고 알리기 } }, [selectData, form]); return ( <> <ContentsDiv> <div className="grid grid-cols-4 gap-4"> + <div className="col-span-4"> <Title title={"수요매칭검토"} /> <SearchDiv> @@ -181,7 +198,7 @@ export default function CoMatRvw() { <div className="col-span-7 w-full"> <div className="flex flex-row items-center"> <Label className="text-xl text-gray-600 mx-6"> - 하수 재이용년도 + 수요신청년도 </Label> <SelectBox className="h-full w-32 text-2xl" @@ -259,7 +276,7 @@ export default function CoMatRvw() { tableData={dmdMatData || []} headlist={coRvwHeadList} pageSize={20} - total={100000} + total={coMatRvwCount} cellClick={(index: number | undefined, headId: string) => { if (index !== undefined && index >= 0) { TableCellClick(index); @@ -269,7 +286,7 @@ export default function CoMatRvw() { ></UiTable> </ListDiv> <hr /> - <ScrollArea className="h-[49vh]"> + {/* <ScrollArea className="h-[49vh]"> */} <ListDiv> <h3 className="mt-4 ml-2 text-2xl font-medium text-label"> 수요신청 상세현황 @@ -279,7 +296,7 @@ export default function CoMatRvw() { <div className="col-span-3 mt-4"> <FormField control={form.control} - name="demandCo.regDt" + name="dmndTmng" render={({ field }) => ( <FormUi label="요구시기"> <Input @@ -660,9 +677,9 @@ export default function CoMatRvw() { </div> </div> </Form> - <ConCostForm form={form} type={"mat"} /> + <ConCostForm form={form} type={"mat"} selectionVersion={selectionVersion} /> </ListDiv> - </ScrollArea> + {/* </ScrollArea> */} </div> </div> </ContentsDiv> diff --git a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx index e8146b37a..a81a85e19 100644 --- a/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx +++ b/apps/wr/app/matMng/coMat/ruseCaseMng/page.tsx @@ -1,30 +1,172 @@ "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 { ToastFormUi } from "@/components/ui/toast/toastFromUi"; +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"; +import { useEffect, useState } from "react"; +import { toast, Toaster } from "react-hot-toast"; export default function RuseCaseMng() { - const { code,ruseCase} = useWrStore((state) => state); - const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>() + const { code, ruseCaseMng, selectRuseCaseMng } = useWrStore((state) => state); + const { ruseCaseMngList, status, count } = 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 (pageNo: number, pageSize: number) => { + const res = await selectRuseCaseMng( + pageNo, + pageSize, + 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(1, 10000);; + + 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 + ); + } + + useEffect(() => { + if(status === 200){ + setIsExcelOpen({ isModal: false }); + } + }, [status]); + return ( <> <ContentsDiv> @@ -54,47 +196,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(1, 20) }}> 조회 </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 +230,37 @@ 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={count} + cellClick={(index: number | undefined, headId: string) => {}} + onPageChange={(pageNo: number) => { + searchRuseCaseMng(pageNo, 10); + }} + /> + </div> </ListDiv> </div> </div> </ContentsDiv> + <Toaster position="top-center" /> + {/* 엑셀 등록 모달 */} + <UiModal + open={isExcelOpen.isModal} + onOpenChange={() => { + setIsExcelOpen({ isModal: !isExcelOpen.isModal }) + }} + bodyClass={"w-[40vw] max-w-none"} + body={<RuseCaseMngExcel />} + /> </> - ) + ); } \ No newline at end of file diff --git a/apps/wr/app/matMng/coMat/ruseRvwHstry/page.tsx b/apps/wr/app/matMng/coMat/ruseRvwHstry/page.tsx index 2d339b869..d0a6bcb93 100644 --- a/apps/wr/app/matMng/coMat/ruseRvwHstry/page.tsx +++ b/apps/wr/app/matMng/coMat/ruseRvwHstry/page.tsx @@ -7,6 +7,7 @@ import { type RvwHistTableType, } from "@/app/store/matDmd/coMatRvw/type"; import { + formatPhoneNumber, getCity, getdmdMatFlag, yearOptions, @@ -43,7 +44,7 @@ export default function RuseRvwHstry() { ruseRvwInqua, ruseRvwComplete, } = useWrStore((state) => state); - const { rvwHistRes, ruseHistHeadList } = ruseHist; + const { rvwHistRes, ruseHistHeadList,rvwHistCount } = ruseHist; const [pageNation, setPageNatiion] = useState<number>(1); const [dmdCoNm, setDmdCoNm] = useState<string>(""); @@ -69,6 +70,36 @@ export default function RuseRvwHstry() { setObjSearch({ city: cityValue, sgg: "0" }); }; + useEffect(() =>{ + getButtonSearch() + },[pageNation]) + + const getButtonSearch=()=>{ + if (fromDate && toDate) { + const fromNum = Number(fromDate); + const toNum = Number(toDate); + if (fromNum > toNum) { + toast.error( + "시작년도가 종료년도 이후로 설정할 수 없습니다." + ); + } else { + let con: string | number = ""; + if (String(objSearch.city).length > 0) { + con = String(objSearch.city); + } + if (String(objSearch.sgg).length > 0 && objSearch.sgg !== "0") { + con = String(objSearch.sgg); + } + getSearch( + fromNum, + toNum, + con, + dmdCoNm, + ruseUtztnFcltNm + ); + } + } + } const getSgg = (val: string | number) => { console.log(val); setObjSearch({ ...objSearch, sgg: String(val) }); @@ -163,23 +194,29 @@ export default function RuseRvwHstry() { const data = rvwHistRes?.map((item, index) => { let flag = ""; flag = getdmdMatFlag(item.demandMatching?.splyFcltFlg ?? ""); - + const formatDate = (dateStr: string | null | undefined) => { + // null, undefined, 빈 문자열인 경우 '없음'을 반환합니다. + if (!dateStr) return "없음"; + // "YYYYMMDD" 형식으로 날짜를 파싱합니다. + const d = moment(dateStr, "YYYYMMDD"); + // 파싱된 날짜가 유효한 경우에만 포맷하고, 그렇지 않으면 '없음'을 반환합니다. + return d.isValid() ? d.format("YYYY-MM-DD") : "없음"; + }; + return { ...item, ctpvCd: item.demandCo?.ctpvCd, sggCd: item.demandCo?.sggCd, dmdCoNm: item.demandCo?.dmdCoNm, daddr: item.demandCo?.daddr, - telno: item.demandCo?.telno, - regDt: item?.demandCo?.regDt - ? moment(item?.demandCo?.regDt).format("YYYY-MM-DD") - : "없음", - rvwCmptnYmd: item?.rvwCmptnYmd - ? moment(item?.rvwCmptnYmd).format("YYYY-MM-DD") - : "없음", - mtchnCmptnYmd: item?.mtchnCmptnYmd - ? moment(item?.mtchnCmptnYmd).format("YYYY-MM-DD") + telno: formatPhoneNumber(item.demandCo?.telno ||''), + dmndTmng: item?.dmndTmng + ? moment(item?.dmndTmng).format("YYYY-MM-DD") : "없음", + + rvwCmptnYmd: formatDate(item.demandMatching?.rvwCmptnYmd), + mtchnCmptnYmd: formatDate(item.demandMatching?.mtchnCmptnYmd), + ruseUtztnFcltNm: item.ruseUtztnFcltNm, dmndQty: item.dmndQty, splyFcltFlg: flag, @@ -272,30 +309,7 @@ export default function RuseRvwHstry() { <Button size={"sm"} onClick={(e) => { - if (fromDate && toDate) { - const fromNum = Number(fromDate); - const toNum = Number(toDate); - if (fromNum > toNum) { - toast.error( - "시작년도가 종료년도 이후로 설정할 수 없습니다." - ); - } else { - let con: string | number = ""; - if (String(objSearch.city).length > 0) { - con = String(objSearch.city); - } - if (String(objSearch.sgg).length > 0 && objSearch.sgg !== "0") { - con = String(objSearch.sgg); - } - getSearch( - fromNum, - toNum, - con, - dmdCoNm, - ruseUtztnFcltNm - ); - } - } + getButtonSearch() }} > 조회 @@ -318,17 +332,23 @@ export default function RuseRvwHstry() { tableData={rvwHistData || []} headlist={ruseHistHeadList} pageSize={20} - total={100000} + total={rvwHistCount} cellClick={(index: number | undefined, headId: string) => { TableCellClick(index); }} - onPageChange={setPageNatiion} + onPageChange={ + (page)=>{ + setPageNatiion(page) + + } + + } ></UiTable> - <hr /> + {/* <hr /> <h3 className="mt-4 ml-2 text-2xl font-medium text-label"> 수요신청 상세현황 </h3> - <ConCostForm form={form} type={"hist"} /> + <ConCostForm form={form} type={"hist"} selectionVersion={0} /> */} </ListDiv> </div> </div> diff --git a/apps/wr/app/matMng/coMng/coSttn/page.tsx b/apps/wr/app/matMng/coMng/coSttn/page.tsx index 163195756..e828f4226 100644 --- a/apps/wr/app/matMng/coMng/coSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/coSttn/page.tsx @@ -5,6 +5,7 @@ import { btnClass } from "@/app/store/common/code"; import { fetchType, getFetch } from "@/components/lib/api"; import { excelDownload, + formatPhoneNumber, getCity, tableData } from "@/components/lib/func/commom"; @@ -26,6 +27,7 @@ import ListDiv from "@common/business_components/ui/list_div"; import SdmInsertForm from "@common/business_components/ui/sdmInsertForm"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import { Button } from "@common/components/ui"; +import moment from "moment"; import { useCallback, useEffect, useState } from "react"; import toast, { Toaster } from "react-hot-toast"; @@ -59,17 +61,29 @@ export default function CoSttn() { const [seartchTxt, setSearchTxt] = useState<string>(); const [upadateData, setUpdateData] = useState<DemandCompType>(); - const getSelCity = (val: string | number) => { - const cityValue = String(val); // Ensure val is treated as a string - const data = getCity(cityValue, code.sggCd); - setSelectSgg(data); - setObjSearch({ city: cityValue, sgg: "" }); - }; + const getSelCity = useCallback((val: string | number) => { + const cityValue = String(val); + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); + // 시/도를 변경하면 시/군/구는 '전체'("0")로 초기화합니다. + setObjSearch({ city: cityValue, sgg: "0" }); + }, [code.sggCd]); - const getSgg = (val: string | number) => { - setObjSearch({ ...objSearch, sgg: String(val) }); - }; + const getSgg = useCallback((val: string | number) => { + setObjSearch((prev) => ({ ...prev, sgg: String(val) })); + }, []); + useEffect(() => { + // code.cityCd 데이터가 로드되었고, 아직 시/도 선택이 안된 초기 상태일 때 실행 + if (code.cityCd && code.cityCd.length > 0 && !objSearch.city) { + const firstCityVal = code.cityCd[0]?.val; + if (firstCityVal) { + // 첫 번째 시/도 값으로 상태를 초기화합니다. + getSelCity(firstCityVal); + } + } + }, [code.cityCd, objSearch.city, getSelCity]); + useEffect(() => { if (!coSttn.isInit) { coSttnInit("1", "13", "0"); @@ -77,15 +91,21 @@ export default function CoSttn() { }, [coSttn.isInit, coSttnInit]); useEffect(() => { - if (coSttnRes?.length > 0 && code.cityCd.length > 0) { - const data = tableData(coSttnRes, { code }); - const chData = data.map((item: any) => ({ - ...item, - })); - // .filter((item: any) => item !== undefined); - setBdstTable(chData); - } else if (coSttnRes.length == 0) { - setBdstTable([]); + if(coSttnRes){ + + if (coSttnRes?.length > 0 && code.cityCd.length > 0) { + const data = tableData(coSttnRes, { code }); + const chData = data.map((item: any) => ({ + ...item, + telno:formatPhoneNumber(item.telno||undefined), + regDt: moment(item.regDt).format("YYYY-MM-DD") + + })); + // .filter((item: any) => item !== undefined); + setBdstTable(chData); + } else if (coSttnRes?.length == 0) { + setBdstTable([]); + } } }, [coSttnRes, code]); @@ -116,8 +136,11 @@ export default function CoSttn() { const searchClick = () => { const con = (objSearch.sgg && objSearch.sgg !== "0") ? objSearch.sgg : objSearch.city; + if (con) { if (seartchTxt && seartchTxt?.length > 0) { + + console.log(' seartchTxt ', seartchTxt) coSttnTxtSearch("1", "13", String(con), seartchTxt ?? ""); } else { coSttnSearch("1", "13", String(con)); @@ -139,13 +162,15 @@ export default function CoSttn() { selectClass={""} selectArray={code.cityCd || []} onSelectValue={getSelCity} + value={objSearch.city} /> <SelectBox className="col-span-2 w-1/6 h-full text-2xl" label="시군구" selectClass={""} - selectArray={selectSgg?selectSgg : [{text:"전체",val:"0"}]} + selectArray={selectSgg || []} onSelectValue={getSgg} + value={objSearch.sgg} /> <SearchInput className={"!text-3xl"} @@ -159,7 +184,7 @@ export default function CoSttn() { </SearchFormLeft> <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> <div className="flex flex-row space-x-2"> - <Button size={"sm"} onClick={searchClick}> + <Button className="mt-2" size={"sm"} onClick={searchClick}> 조회 </Button> <Button @@ -179,7 +204,7 @@ export default function CoSttn() { onClick={() => { /* 저장 로직 */ if (!upadateData) { - <ToastFormUi body={"수요처 변경을 원하는 행을 선택해주세요"} title={"수요처관리"}></ToastFormUi>; + toast.error("수요처 변경을 원하는 행을 선택해주세요") }else{ setIsDmdModal({ ...isDmdModal, @@ -187,7 +212,7 @@ export default function CoSttn() { } }} > - 저장 + 수정 </Button> <Button size={"sm"} diff --git a/apps/wr/app/matMng/coMng/drgtSttn/page.tsx b/apps/wr/app/matMng/coMng/drgtSttn/page.tsx index 77bbe4a5a..55dbeb454 100644 --- a/apps/wr/app/matMng/coMng/drgtSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/drgtSttn/page.tsx @@ -1,42 +1,126 @@ "use client"; import { useWrStore } from "@/app/store"; import { btnClass } from "@/app/store/common/code"; -import type { customCoSttnBdst } from "@/app/store/matDmd/coSttn"; +import type { customDrgt } from "@/app/store/matDmd/drgtSttn"; import { drgtHeadListExport } from "@/app/store/matDmd/drgtSttn/headList"; -import { excelDownload, getCity } from "@/components/lib/func/commom"; +import { getCity } from "@/components/lib/func/commom"; +import DrgtDetailModal from "@/components/ui/modal/coMat/drgtDetailModal"; import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal"; -import { ContentsDiv, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, Title, UiTable, type SelectDataType } from "@common/business_components/ui"; +import { ContentsDiv, DateMonthPicker, downloadExcel, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, Title, UiModal, UiTable, type SelectDataType } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; -import { Button, DatePicker, Label } from "@common/components/ui"; -import { useCallback, useState } from "react"; +import { Button, Label, ScrollArea } from "@common/components/ui"; +import { useEffect, useState } from "react"; +import { toast, Toaster } from "react-hot-toast"; export default function DrgtSttn() { - const { code, } = useWrStore((state) => state); - const [fromDate, setFromDate] = useState<Date>(new Date()); - const [toDate, setToDate] = useState<Date>(new Date()); + const { code, drgt, drgtSearch, drgtModalSearch, drgtInit } = useWrStore((state) => state); + const { drgtRes, drgtMoRes } = drgt; + const [fromDate, setFromDate] = useState<string>(); + const [toDate, setToDate] = useState<string>(); const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>(); const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" }); + const [tableData, setTableData] = useState<customDrgt[]>(); + const [selectedSgg, setSelectedSgg] = useState<string>(''); + const [excelData, setExcelData] = useState<any[]>(); + const [drgtModal, setDrgtModal] = useState<{ + isOpen: boolean; + data: customDrgt | undefined; + }>({ data: undefined, isOpen: false }); + + const cellClick = (index: number | undefined, headId: string) => { + if (fromDate && toDate) { + drgtModalSearch(fromDate, toDate, drgtRes[index].sggNm); + } else { + toast.error("조회기간을 선택해주세요."); + } + } + + const excelClick = () => { + downloadExcel({ + data: excelData ?? [], + fileName: "우리동네가뭄 현황", + sheetNameArray: ["Sheet1"], + headerRange: "A1:G1" + }) + } + + useEffect(() => { + if (!drgt.isInit) { + drgtInit(); + } + }, [drgt.isInit, drgtInit]); + + useEffect(() => { + let resData = Array.isArray(drgtRes) ? drgtRes : [drgtRes]; + resData = resData.map((item) => { + if (item.sggNm === '세종시') { + return { + ...item, + ctpvCd: '세종특별자치시', + sggNm: '' + } + } + + return { + ...item, + ctpvCd: code.cityCd.find(ct => (ct.text == item.sggNm || (code.sggCd.find(sgg => sgg.text == item.sggNm)?.val.substring(0, 2) == ct.val)))?.text, + sggNm: code.sggCd.find(sgg => sgg.text == item.sggNm)?.text + } + }) + const data = Array.isArray(resData) + ? resData.map((item: customDrgt) => { + return { + "시도": item.ctpvCd, + "시군구": item.sggNm, + "정상(횟수)": item.normal, + "관심(횟수)": item.attention, + "주의(횟수)": item.caution, + "관심+주의(횟수)": item.warning, + "가뭄순위": item.ranking, + } + }) : []; + setExcelData(data) + setTableData(resData) + }, [drgtRes]) + + useEffect(() => { + if (drgtMoRes && drgtMoRes.length > 0) { + setDrgtModal({ isOpen: true, data: drgtMoRes }); + } + }, [drgtMoRes]); const getSelCity = (val: string | number) => { - const cityValue = String(val); // Ensure val is treated as a string - const data = getCity(cityValue, code.sggCd); - setSelectSgg(data); - setObjSearch({ city: cityValue, sgg: '' }); + const cityValue = String(val); + + if (cityValue === "0") { + setSelectSgg([]); + setSelectedSgg(''); + setObjSearch({ city: cityValue, sgg: '' }); + } else { + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); + + const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); + setSelectedSgg(firstSggVal); + setObjSearch({ city: cityValue, sgg: firstSggVal }); + } }; const getSgg = (val: string | number) => { - console.log(val); - setObjSearch({ ...objSearch, sgg: String(val) }) + setSelectedSgg(String(val)); + setObjSearch({ ...objSearch, sgg: String(val) }); }; - const excelClick = useCallback(() => { - excelDownload( - [[]], - "우리동네가뭄", - "sample_drgtSttn.xlsx", - "A1:H1" - ); - }, []); + useEffect(() => { + if (fromDate === undefined && toDate === undefined) { + const today = new Date(); + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, '0'); + const formatted = `${year}${month}`; + setFromDate(formatted); + setToDate(formatted); + } + }, [fromDate, toDate]) return ( <> <ContentsDiv> @@ -54,23 +138,48 @@ export default function DrgtSttn() { onSelectValue={getSelCity} /> <SelectBox - className="col-span-2 w-1/6 h-full text-2xl" + className="col-span-2 w-1/6 h-full text-2xl ml-8" label="시군구" selectClass={""} selectArray={selectSgg || []} onSelectValue={getSgg} + value={selectedSgg} /> <div className="col-span-5 w-auto"> <div className="flex flex-row items-center"> - <Label className="text-xl text-gray-600">조회기간</Label> + <Label className="text-xl text-gray-600 ml-8">조회기간</Label> <div className="flex flex-row w-1/4"> - <DatePicker mode={'single'} - selected={fromDate} - onSelect={(date: Date | undefined) => { if (date) setFromDate(date) }} /> - <span>~</span> - <DatePicker mode={'single'} - selected={toDate} - onSelect={(date: Date | undefined) => { if (date) setToDate(date) }} /> + <DateMonthPicker + field={{ + value: fromDate, + onChange: (date: string) => { + const year = date.slice(0, 4); + let month = date.slice(4); + if (month.length < 2) { + month = month.padStart(2, "0"); + } + const formatted = `${year}${month}`; + setFromDate(formatted); + } + }} + selectClass="ml-4 text-2xl" + /> + <span className="ml-2 text-center">~</span> + <DateMonthPicker + field={{ + value: toDate, + onChange: (date: string) => { + const year = date.slice(0, 4); + let month = date.slice(4); + if (month.length < 2) { + month = month.padStart(2, "0"); + } + const formatted = `${year}${month}`; + setToDate(formatted); + } + }} + selectClass="ml-4" + /> </div> </div> </div> @@ -78,20 +187,42 @@ export default function DrgtSttn() { </SearchFormLeft> <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> - <div className="flex flex-row space-x-2"> + <div className="flex flex-row space-x-2 items-center"> <Button size={"sm"} - onClick={(e) => { }} + onClick={(e) => { + let sidoText = ''; + let sggText = []; + + if (objSearch?.city.toString().length > 0) { + sidoText = code.cityCd.find((item) => item.val === objSearch?.city)?.text ?? ''; + } + if (objSearch?.sgg.toString().length > 1) { + sggText = code.sggCd.find((item) => item.val === objSearch?.sgg)?.text ?? ''; + } else { + sggText = code.sggCd.filter((item) => item.val.substring(0, 2) === objSearch?.city).map((item) => item.text); + + } + if (sidoText === '전체') { sidoText = '' } + + if (sidoText === '세종특별자치시') { + sidoText = '세종시'; + sggText = sidoText; + } + + const searchArr = []; + if (sidoText) searchArr.push(sidoText); + if (Array.isArray(sggText)) { + searchArr.push(...sggText); + } else if (sggText) { + searchArr.push(sggText); + } + + drgtSearch(fromDate ?? "", toDate ?? "", searchArr) + }} > 조회 </Button> - <Button - size={"sm"} - className={btnClass} - onClick={() => { }} - > - 상세보기 - </Button> <Button size={"sm"} className={`${btnClass} green`} @@ -114,18 +245,31 @@ export default function DrgtSttn() { <div className="grid grid-cols-6 gap-4 "> <div className="col-span-6"> <ListDiv> - <UiTable - headName={[]} - tableData={[]} - headlist={drgtHeadListExport} - pageSize={20} - total={10000} - cellClick={(index: number | undefined, headId: string) => { }} - onPageChange={() => { }} - ></UiTable> + <ScrollArea className="h-[66vh]"> + <UiTable + headName={undefined} + tableData={tableData || []} + headlist={drgtHeadListExport} + pageSize={14} + total={0} + cellClick={(index: number | undefined, headId: string) => { + cellClick(index ?? undefined, headId); + }} + onPageChange={() => { }} + ></UiTable> + </ScrollArea> </ListDiv> </div> </div> + <UiModal + open={drgtModal.isOpen} + onOpenChange={() => { + setDrgtModal({ ...drgtModal, isOpen: !drgtModal.isOpen }); + }} + bodyClass={"w-[50vw] max-w-none h-[80vh]"} + body={<DrgtDetailModal data={drgtModal.data} />} + /> + <Toaster position="top-center" /> </ContentsDiv> </> ) diff --git a/apps/wr/app/matMng/coMng/ldrmntCoSttn/page.tsx b/apps/wr/app/matMng/coMng/ldrmntCoSttn/page.tsx index f5dffbf8b..eb43b29bf 100644 --- a/apps/wr/app/matMng/coMng/ldrmntCoSttn/page.tsx +++ b/apps/wr/app/matMng/coMng/ldrmntCoSttn/page.tsx @@ -44,7 +44,7 @@ export default function LdrmntCoSttn() { const [searchTxt, setSearchTxt] = useState<string>(); const ref = useRef<ReturnType>({} as ReturnType); const [excelData, setExcelData] = useState<any[]>(); - + const [isExcelDownload, setIsExcelDownload] = useState(false); const excelClick = useCallback(() => { excelDownload( @@ -57,14 +57,22 @@ export default function LdrmntCoSttn() { }, []); const excelDownloadClick = () => { - downloadExcel({ - data: excelData ?? [], - fileName: "잠재수요처 관리", - sheetNameArray: ["Sheet1"], - headerRange: "A1:F1" - }) + ldrmntSearch(1, "10000", "0", ""); + setIsExcelDownload(true); } + useEffect(() => { + if (isExcelDownload && excelData && excelData.length > 0) { + downloadExcel({ + data: excelData, + fileName: "잠재수요처 관리", + sheetNameArray: ["Sheet1"], + headerRange: "A1:F1" + }); + setIsExcelDownload(false); + } + }, [excelData, isExcelDownload]); + const [isExcelOpen, setIsExcelOpen] = useState<{ isModal: boolean; }>({ isModal: false }); @@ -73,14 +81,21 @@ export default function LdrmntCoSttn() { if (index !== undefined) { setDetailModal({ isOpen: true, data: ldrmntRes[index] }); } - } const getSelCity = (val: string | number) => { - const cityValue = String(val); // Ensure val is treated as a string - const data = getCity(cityValue, code.sggCd); - setSelectSgg(data); - setObjSearch({ city: cityValue, sgg: '' }); + const cityValue = String(val); + + if (cityValue === "0") { + setSelectSgg([]); + setObjSearch({ city: cityValue, sgg: "" }); + } else { + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); + + const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); + setObjSearch({ city: cityValue, sgg: firstSggVal }); + } }; const getSgg = (val: string | number) => { @@ -99,8 +114,8 @@ export default function LdrmntCoSttn() { const tbData = Array.isArray(data) ? data.map((item: customLdrmnt) => ({ ...item, - ctpvCd: item.fctryAddr.split(" ")[0] || "", - sggCd: item.fctryAddr.split(" ")[1] || "" + ctpvCd: item.fctryAddr?.split(" ")[0] || "", + sggCd: item.fctryAddr?.split(" ")[1] || "" })) : []; @@ -114,8 +129,8 @@ export default function LdrmntCoSttn() { const data = Array.isArray(exData) ? exData.map((item: customLdrmnt) => { return { - "시도": item.fctryAddr.split(" ")[0] || "", - "시군구": item.fctryAddr.split(" ")[1] || "", + "시도": item.fctryAddr?.split(" ")[0] || "", + "시군구": item.fctryAddr?.split(" ")[1] || "", "회사명": item.dmdCoNm, "공장주소": item.fctryAddr, "공장관리번호": item.fctryMngNo, @@ -125,10 +140,9 @@ export default function LdrmntCoSttn() { setExcelData(data) }, [excelList]) - useEffect(() => { - let cityTxt = ''; - let sggTxt = ''; + let cityTxt = '' + let sggTxt = '' if (objSearch?.city.toString().length > 0) { cityTxt = code.cityCd.find((item) => item.val === objSearch?.city)?.text ?? ''; @@ -138,21 +152,23 @@ export default function LdrmntCoSttn() { sggTxt = code.sggCd.find((item) => item.val === objSearch?.sgg)?.text ?? ''; } - let cityNm = ""; + if (cityTxt === '전체' || cityTxt === '') { + cityTxt = '0' + } + + let cityNm = "" if (sggTxt !== "0") { - cityNm = cityTxt + " " + sggTxt; + cityNm = cityTxt + " " + sggTxt } else { - cityNm = cityTxt; + cityNm = cityTxt } - if (cityNm) { - if (cityTxt === '') cityNm = "0"; - ldrmntSearch(pageNation, cityNm, searchTxt || ""); + ldrmntSearch(pageNation, '20', cityNm, searchTxt || ""); } else { - ldrmntSearch(pageNation, "", searchTxt || ""); + ldrmntSearch(pageNation, '20', '0', searchTxt || ""); } - }, [pageNation, objSearch, code, searchTxt, ldrmntSearch]) + }, [pageNation]); return ( <> @@ -175,6 +191,7 @@ export default function LdrmntCoSttn() { label="시군구" selectClass={""} selectArray={selectSgg || []} + value={objSearch.sgg} // value props 추가 onSelectValue={getSgg} /> <SearchInput @@ -189,7 +206,7 @@ export default function LdrmntCoSttn() { </SearchFormLeft> <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> - <div className="flex flex-row space-x-2"> + <div className="flex flex-row space-x-2 items-center"> <Button size={"sm"} onClick={(e) => { @@ -205,6 +222,10 @@ export default function LdrmntCoSttn() { sggTxt = code.sggCd.find((item) => item.val === objSearch?.sgg)?.text ?? ''; } + if (cityTxt === '전체' || cityTxt === '') { + cityTxt = '0' + } + let cityNm = "" if (sggTxt !== "0") { @@ -212,10 +233,8 @@ export default function LdrmntCoSttn() { } else { cityNm = cityTxt } - if (cityNm) { - if (cityTxt === '') cityNm = "0"; - ldrmntSearch(pageNation, cityNm, searchTxt || ""); + ldrmntSearch(pageNation, '20', cityNm, searchTxt || ""); } }} > diff --git a/apps/wr/app/page.tsx b/apps/wr/app/page.tsx index 4c6c58faf..c2be341cf 100644 --- a/apps/wr/app/page.tsx +++ b/apps/wr/app/page.tsx @@ -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> </> ); diff --git a/apps/wr/app/ruseFcltMng/fcltReg/htdflReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/htdflReg/page.tsx index 8227e0347..9b3688e61 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/htdflReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/htdflReg/page.tsx @@ -16,73 +16,80 @@ import { Button } from "@common/components"; import ListDiv from "@common/business_components/ui/list_div"; import { useEffect, useState } from "react"; import { tableData } from "@/components/lib/func/commom"; -import HtdflRegForm from "@/components/ui/form/fcltReg/htdflReg/htdflRegForm"; +import HtdflRegForm from "@/components/ui/form/fcltReg/htdflReg/htdflRegForm"; import type { chgBuilding } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import BdstChgModal from "@/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal"; import type { customHtdflBdst } from "@/app/store/ruseFcltMng/fcltReg/htdflReg"; import { BdstReg } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal"; +import { Toaster } from "react-hot-toast"; export default function Home() { - const { code, htdfl, initHtdfl,getHtdflBdstData } = useWrStore((state) => state); + const { code, htdfl, initHtdflReg, getHtdflBdstData ,htdflNmSearch} = useWrStore( + (state) => state + ); const [searchTxt, setSearchTxt] = useState(""); const [pageNation, setPageNation] = useState(1); const [htdflForm, setHtdflForm] = useState<customHtdflBdst>(); const [bdstTable, setbdstTable] = useState<customHtdflBdst[]>(); - const [issNewReg,setIsNewReg] = useState<boolean>( false); + const [issNewReg, setIsNewReg] = useState<boolean>(false); + + const [bdstChgProps, setBdstChgProps] = useState<{ + isOpen: boolean; + chgBuilding: chgBuilding[]; + }>({ isOpen: false, chgBuilding: [] }); - const [bdstChgProps, setBdstChgProps] = useState<{ - isOpen: boolean; - chgBuilding: chgBuilding[]; - }>({ isOpen: false, chgBuilding: [] }); - const { htdflRes } = htdfl; useEffect(() => { if (!htdfl.isInit) { - initHtdfl('0'); + initHtdflReg('41') } - }, [htdfl.isInit, initHtdfl]); - + }, [htdfl.isInit, initHtdflReg]); useEffect(() => { - if (code.sggCd.length > 0) { + if (htdflRes&&htdflRes.length>0) { - const htdflData = tableData(htdflRes, { code }).map( - (item: customHtdflBdst) => { - return { - ...item, - bdstChgYn: item?.chgBuilding && item.chgBuilding.length > 0 && item.chgBuilding[0]?.bdstChgNm !== null ? 'Y' : 'N', - bdstUsgCd: item.reuseBuilding[0]?.bdstUsgCd - }; - } - ); - setbdstTable(htdflData); + console.log('htdflRes ', htdflRes) + const htdflData = tableData(htdflRes, { code }).map( + (item: customHtdflBdst) => { + let archArea = item?.archArea.split('.')[0]!=='-1'?item?.archArea.split('.')[0]:'확인불가' + + return { + ...item, + archArea:archArea + + }; + } + ); + setbdstTable(htdflData); + }else{ + setbdstTable([]); } }, [code, htdflRes]); - const cellClick: (val: number|undefined ,headId: string) => void = ( - val - ,headId) => { + const cellClick: (val: number | undefined, headId: string) => void = ( + val, + headId + ) => { if ((bdstTable ?? []).length > 0 && val !== undefined) { { setHtdflForm(htdflRes[val]); } } - - if (headId === "bdstChgYn" && val !== undefined) { - if ( - htdflRes[val]?.bdstInfoPk != null && - htdflRes[val]?.chgBuilding?.length > 0 - ) { - bdstChgModal(htdflRes[val]?.chgBuilding as chgBuilding[]); - } - } - }; - const bdstChgModal = (chgBuilding: chgBuilding[]) => { - setBdstChgProps({ isOpen: true, chgBuilding: chgBuilding }); + // if (headId === "bdstChgYn" && val !== undefined) { + // if ( + // htdflRes[val]?.bdstInfoPk != null && + // htdflRes[val]?.chgBuilding?.length > 0 + // ) { + // bdstChgModal(htdflRes[val]?.chgBuilding as chgBuilding[]); + // } + // } }; + // const bdstChgModal = (chgBuilding: chgBuilding[]) => { + // setBdstChgProps({ isOpen: true, chgBuilding: chgBuilding }); + // }; useEffect(() => { - getHtdflBdstData(pageNation, "0"); + getHtdflBdstData(pageNation, "41"); }, [pageNation]); return ( @@ -103,14 +110,23 @@ export default function Home() { /> </SearchFormLeft> <SearchFormRight> - <div className="flex flex-row space-x-2 items-center"> - <Button size={"sm"} onClick={()=>{initHtdfl(searchTxt||"")}}>조회</Button> - <Button + <div className="flex flex-row space-x-2 items-center"> + <Button size={"sm"} - onClick={() => setIsNewReg(!issNewReg)} - > - 신규등록 - </Button> + onClick={() => { + if(searchTxt&&searchTxt.length>0){ + htdflNmSearch(String(41),searchTxt) + }else{ + initHtdflReg(String(41) ); + + } + }} + > + 조회 + </Button> + <Button size={"sm"} onClick={() => setIsNewReg(!issNewReg)}> + 신규등록 + </Button> </div> </SearchFormRight> </SearchDiv> @@ -126,41 +142,50 @@ export default function Home() { pageSize={100} total={10} headName={"건축물 목록"} - cellClick={(val, headId) => { cellClick(val || undefined, headId); } } + cellClick={(val, headId) => { + cellClick(val || undefined, headId); + }} onPageChange={setPageNation} - > - </UiTable> + ></UiTable> </ListDiv> </div> <div className="col-span-2 max-h-full"> - <ListDiv> - <ScrollArea className="h-[60vh]"> - <HtdflRegForm - htdflForm={htdflForm ?? ({} as customHtdflBdst)} - isModal={false} - /> - </ScrollArea> + <ListDiv> + <ScrollArea className="h-[60vh]"> + <HtdflRegForm + htdflForm={htdflForm ?? ({} as customHtdflBdst)} + isModal={false} + /> + </ScrollArea> </ListDiv> </div> </div> - <UiModal - body={<BdstReg moType="HTB"></BdstReg>} - bodyClass="w-[68vw] max-w-none h-[90vh]" - open={issNewReg} - onOpenChange={(open) => { - setIsNewReg(open); - }} - /> - - <UiModal + <UiModal + body={ + <BdstReg + moType="HTB" + isModal={false} + setIsModal={setIsNewReg} + ></BdstReg> + } + bodyClass="w-[68vw] max-w-none h-[90vh]" + open={issNewReg} + onOpenChange={(open) => { + setIsNewReg(open); + }} + /> + +<Toaster position="top-center" /> +{/* <UiModal bodyClass={""} open={bdstChgProps.isOpen} - body={<BdstChgModal chgBuilding={bdstChgProps.chgBuilding}></BdstChgModal>} + body={ + <BdstChgModal chgBuilding={bdstChgProps.chgBuilding}></BdstChgModal> + } onOpenChange={(open) => { setBdstChgProps({ ...bdstChgProps, isOpen: open }); - } - } - /> + }} + /> */} </ContentsDiv> </> ); diff --git a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx index 441eabf25..9f243627c 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/page.tsx @@ -29,18 +29,22 @@ 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"; +import { createDefaultPbnstItem } from "./utilsZod"; + + + export default function PublicReuseFaciltyInsert() { const { code, pbnst, - searchFclts, pubHasuInit, + pbnstInsert, pbnstRegClick, pbnstRuseFcltUpdate, pubHasuSearch, @@ -50,17 +54,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>[]; }>({ @@ -68,6 +70,8 @@ export default function PublicReuseFaciltyInsert() { // defaultValues: { items: pbnstRegArrayZod.parse([]) }, mode: "onChange", }); + const { isDirty } = form.formState; + const { fields: items, append, @@ -76,6 +80,8 @@ export default function PublicReuseFaciltyInsert() { control: form.control, name: `items`, }); + + const formWatch = form.watch("items"); useEffect(() => { if (!pbnst.isInit) { pubHasuInit("1", "41"); @@ -83,37 +89,98 @@ export default function PublicReuseFaciltyInsert() { }, [pbnst.isInit, pubHasuInit]); const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => { - const data = writeRgtrAndMdfr(valuse.items); + + if (!isDirty) { + toast.error("변경사항이 없습니다."); // 변경사항이 없으면 메시지 표시 + return; + } const data = writeRgtrAndMdfr(valuse.items); let bizs = [] as fcBizType[]; data.forEach((fclt) => { bizs = [...bizs, ...fclt.fcBizs]; }); - const pbnst =data.map((fclt) => { - return({ - 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[]); + 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>[] }>) => { + console.log("errors:", errors); + + const findAllErrors = ( + errorObject: any, + path: (string | number)[] = [] + ): { message: string; path: (string | number)[] }[] => { + if (!errorObject) return []; + + return Object.keys(errorObject).flatMap((key) => { + const value = errorObject[key]; + const currentKey = !isNaN(parseInt(key)) ? parseInt(key) : key; + const newPath = [...path, currentKey]; + + if (value && typeof value === "object") { + if (value.message) { + return [{ message: value.message, path: newPath }]; + } + + return findAllErrors(value, newPath); + } + return []; + }); + }; + + const errorList = findAllErrors(errors); + + if (errorList.length > 0) { + errorList.forEach((errorInfo) => { + const { message, path } = errorInfo; + let prefix = ""; + + const itemsIndex = path.indexOf("items"); + + if (itemsIndex > -1 && path.length > itemsIndex + 1) { + const itemIdx = path[itemsIndex + 1] as number; + const facilityName = form.getValues(`items.${itemIdx}.ruseUtztnFcltNm`); + if (facilityName) { + prefix += `[${facilityName}] `; + } + + const fcBizsIndex = path.indexOf("fcBizs"); + if (fcBizsIndex > -1 && path.length > fcBizsIndex + 1) { + const bizIdx = path[fcBizsIndex + 1] as number; + const cycle = form.getValues(`items.${itemIdx}.fcBizs.${bizIdx}.cycl`); + if (cycle) { + prefix += `[${cycle}차수] `; + } + } + } + + // 최종 오류 메시지 출력 + toast.error(`${prefix}${message}`); + }); } - }, [code, pbnstHasuRes, searchFclts]); + }; + useEffect(() => { + console.log("form errors:", form.formState.errors); + }, [form.formState.errors]); + + 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 +188,8 @@ export default function PublicReuseFaciltyInsert() { setSelectRow(pbnstHasuRes?.[val]); if (selectRow) { pbnstRegClick(selectRow); + } else { + console.error("선택된 행이 없습니다."); } }; @@ -129,12 +198,13 @@ export default function PublicReuseFaciltyInsert() { }; useEffect(() => { - if (pbnst.pbnstRes&& pbnst.pbnstRes?.length>0) { - console.log("pbnst.pbnstRes", pbnstRes); - form.reset({ items: pbnstRegArrayZod.parse(pbnst.pbnstRes) }); - }else{ + if (pbnst.pbnstRes && pbnst.pbnstRes?.length > 0) { - console.log("pbnst.pbnstRes is null", pbnstRes); + + + form.reset({ items: pbnst.pbnstRes }); + + } else { form.reset({ items: [] }); } }, [pbnst.pbnstRes]); @@ -150,52 +220,53 @@ 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: + // String(selecTableRow.bscFctCd) + String(pbnstRes.length + 1), + // ruseUtztnFcltNm: null, + // fcBizs: [ + // fcBizSchema.parse({ + // pbnstRuseFcltFk: + // String(lastPbnst.pbnstRuseFcltPk) + + // String(pbnstRes.length + 1), + // cycl: 1, + // }), + // ] as fcBizType[], + // }) + // ); + append(createDefaultPbnstItem(selecTableRow, pbnstRes.length)); + setIsRuseReg({ isModal: true }); + } + } else { + if (selecTableRow) { append( pbnstRegZod.parse({ - pbnstRuseFcltPk: - String(selecTableRow.bscFctCd) + String(pbnstRes.length + 1), + pbnstRuseFcltPk: String(selecTableRow.bscFctCd) + "1", ruseUtztnFcltNm: "", + regDt: null, + mdfcnDt: null, fcBizs: [ fcBizSchema.parse({ - pbnstRuseFcltFk: - String(lastPbnst.pbnstRuseFcltPk) + - String(pbnstRes.length + 1), + pbnstRuseFcltFk: String(selecTableRow.bscFctCd) + "1", cycl: 1, }), ] as fcBizType[], }) ); - setIsRuseReg({ ...isRuseReg, 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 }); + 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 +277,7 @@ useEffect(() =>{ <SearchFormLeft> <SearchInput className={"col-span-3 my-7 !text-3xl"} - label="건물명" + label="시설명" textValue={searchTxt || ""} setText={(e) => { setSearchTxt(e); @@ -228,7 +299,7 @@ useEffect(() =>{ size={"sm"} className="" onClick={() => { - // pbnstMngData([] as customPbnst[]); + // pbnstMngData([] as customPbnst[]); if (!selectRow) { toast.error( "하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요" @@ -251,6 +322,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 +339,97 @@ 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 }); + } + }} + + onInvalid ={onInvalid} ></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> diff --git a/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/utilsZod.tsx b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/utilsZod.tsx new file mode 100644 index 000000000..3433a62c5 --- /dev/null +++ b/apps/wr/app/ruseFcltMng/fcltReg/pbnstReg/utilsZod.tsx @@ -0,0 +1,38 @@ +import type { fcBizType, pubHasuType } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; + +export function createDefaultPbnstItem( + selecTableRow: pubHasuType, + index: number + ) { + const pk = String(selecTableRow.bscFctCd) + String(index + 1); + + return { + pbnstRuseFcltPk: pk, + swtrmtFcltFk: "", + ruseUtztnFcltNm: "", // 필수 입력이므로 빈 문자열로 + fcltCpct: 0, + ruseCpct: 0, + splyExtraCpct: 0, + regDt: null, + rgtr: "test", + mdfcnDt: null, + mdfr: "test", + fcFiles: [], + fcBizs: [ + createDefaultBiz(pk, 1), // 첫 차수 + ], + fcOpers: [], + }; + } + + + export function createDefaultBiz( + pbnstRuseFcltFk: string, + cycl: number + ): fcBizType { + return { + pbnstRuseFcltFk, + cycl, + // 필요한 값 있으면 여기에 추가 + } as fcBizType; + } \ No newline at end of file diff --git a/apps/wr/app/ruseFcltMng/fcltReg/rainReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/rainReg/page.tsx index 31743aaa9..8ecc1221f 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/rainReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/rainReg/page.tsx @@ -21,11 +21,14 @@ import { import ListDiv from "@common/business_components/ui/list_div"; import { Button } from "@common/components"; import { useEffect, useState } from "react"; +import { Toaster } from "react-hot-toast"; export default function Home() { - const { rain, code, initRain, rainSearch, getRainData } = useWrStore((state) => { - return state; - }); + const { rain, code, initRain, rainSearch, getRainData } = useWrStore( + (state) => { + return state; + } + ); const { rainRes } = rain; const [rainForm, setRainForm] = useState<customRainBdst>(); const [issNewReg, setIsNewReg] = useState<boolean>(false); @@ -38,8 +41,9 @@ export default function Home() { }>({ isOpen: false, chgBuilding: [] }); useEffect(() => { + setIsNewReg(false); if (!rain.isInit) { - initRain('41'); + initRain("41"); } }, [rain.isInit, initRain]); @@ -47,8 +51,11 @@ export default function Home() { if (code.sggCd.length > 0) { const rainData = tableData(rainRes, { code }).map( (item: customRainBdst) => { + let archArea = item?.archArea.split('.')[0]!=='-1'?item?.archArea.split('.')[0]:'확인불가' return { ...item, + archArea:archArea, + bdstChgYn: item.chgBuilding.length > 0 ? "Y" : "N", }; } @@ -80,7 +87,6 @@ export default function Home() { }; useEffect(() => { - console.log("getPageNation", pageNation); getRainData(pageNation, "41"); }, [pageNation]); @@ -129,35 +135,39 @@ export default function Home() { <div className="col-span-4 h-full min-h-0"> <ListDiv className="min-h-0"> {/* <ScrollArea className="h-[60vh]"> */} - <UiTable - tableData={bdstTable || []} - headlist={rain.upHeadList} - pageSize={20} - total={10000} - headName={"건축물 목록"} - cellClick={(val, headId) => { - cellClick(val, headId); - }} - onPageChange={setPageNation} - > - </UiTable> + <UiTable + tableData={bdstTable || []} + headlist={rain.upHeadList} + pageSize={20} + total={10000} + headName={"건축물 목록"} + cellClick={(val, headId) => { + cellClick(val, headId); + }} + onPageChange={setPageNation} + ></UiTable> {/* </ScrollArea> */} </ListDiv> </div> <div className="col-span-2 max-h-full"> - <ListDiv className=""> <ScrollArea className="h-[60vh]"> - <RainRegForm - rainForm={rainForm ?? ({} as customRainBdst)} - isModal={false} - ></RainRegForm> + <RainRegForm + rainForm={rainForm ?? ({} as customRainBdst)} + isModal={false} + ></RainRegForm> </ScrollArea> </ListDiv> </div> </div> <UiModal - body={<BdstReg moType="RNB"></BdstReg>} + body={ + <BdstReg + moType="RNB" + isModal={issNewReg} + setIsModal={setIsNewReg} + ></BdstReg> + } bodyClass="w-[68vw] max-w-none h-[90vh]" open={issNewReg} onOpenChange={(open) => { @@ -175,6 +185,8 @@ export default function Home() { setBdstChgProps({ ...bdstChgProps, isOpen: open }); }} /> + + <Toaster position="top-center" /> </ContentsDiv> </> ); diff --git a/apps/wr/app/ruseFcltMng/fcltReg/rswtReg/page.tsx b/apps/wr/app/ruseFcltMng/fcltReg/rswtReg/page.tsx index 2931690b9..94224d40d 100644 --- a/apps/wr/app/ruseFcltMng/fcltReg/rswtReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltReg/rswtReg/page.tsx @@ -20,6 +20,7 @@ import { import ListDiv from "@common/business_components/ui/list_div"; import { Button } from "@common/components"; import { useEffect, useState } from "react"; +import { Toaster } from "react-hot-toast"; export default function Home() { const { code, rswt, initRswt, rswtBdstSearch, getRswtBdstData } = useWrStore( @@ -42,11 +43,16 @@ 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) => { + let archArea = + item?.archArea.split(".")[0] !== "-1" + ? item?.archArea.split(".")[0] + : "확인불가"; return { ...item, + archArea: archArea, bdstChgYn: item.chgBuilding[0]?.bdstChgNm != null ? "Y" : "N", bdstUsgCd: item.reuseBuilding[0]?.bdstUsgCd, }; @@ -151,7 +157,13 @@ export default function Home() { </div> <UiModal - body={<BdstReg moType="RST"></BdstReg>} + body={ + <BdstReg + moType="RST" + setIsModal={setIsNewReg} + isModal={issNewReg} + ></BdstReg> + } bodyClass="w-[68vw] max-w-none h-[90vh]" open={issNewReg} onOpenChange={(open) => { @@ -168,6 +180,8 @@ export default function Home() { setBdstChgProps({ ...bdstChgProps, isOpen: open }); }} /> + + <Toaster position="top-center" /> </ContentsDiv> </> ); diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/htdflSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/htdflSttn/page.tsx index e43d5b04f..959367202 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/htdflSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/htdflSttn/page.tsx @@ -8,6 +8,7 @@ import type { } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import { excelDownload, + getCity, getCityOrSgg, tableData, } from "@/components/lib/func/commom"; @@ -16,16 +17,23 @@ import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDeta import { ContentsDiv, SearchBaseForm, + SearchDiv, + SearchFormLeft, + SearchFormRight, + SearchInput, + SelectBox, Title, UiModal, UiTable, type ReturnType, + type SelectDataType, } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; -import { Button } from "@common/components/ui"; +import { Button, Label } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; import { excelHtdflSttnParsing } from "@/app/store/ruseFcltMng/fcltSttn/htdflSttn/excel"; import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; +import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal"; export default function HtdflSttn() { const { code, htdflSttn, htdflSttnSearch, initHtdfl } = useWrStore( @@ -38,6 +46,8 @@ export default function HtdflSttn() { const [pageNation, setPageNation] = useState(1); const [isChHead, setIsChHead] = useState(false); const [bdstTable, setBdstTable] = useState<customRainBdst[]>(); + const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>() + const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" }); const [detailModal, setDetailModal] = useState<{ isOpen: boolean; data: customRainBdst | undefined; @@ -46,17 +56,47 @@ export default function HtdflSttn() { isOpen: boolean; chBuilding: chgBuilding[]; }>({ isOpen: false, chBuilding: [] }); - - const codeData = getCityOrSgg(ref); + const [fcltNm, setFcltNm] = useState<string>(""); const excelClick = useCallback(() => { - if (codeData) htdflSttnSearch(1, "10000", codeData); - }, [htdflSttnSearch, codeData]); + // 최신 검색 조건 반영 + const con = getConValue(); + htdflSttnSearch(1, "10000", String(con), fcltNm, code.cityCd, code.sggCd); + }, [htdflSttnSearch, objSearch, code.cityCd, code.sggCd]); useEffect(() => { - if (codeData != null) htdflSttnSearch(pageNation, "20", codeData); + htdflSttnSearch(pageNation, "20", String(getConValue()), fcltNm, code.cityCd, code.sggCd); }, [pageNation]); + const getSelCity = (val: string | number) => { + const cityValue = String(val); + + if (cityValue === "0") { + setSelectSgg([]); + setObjSearch({ city: cityValue, sgg: "" }); + } else { + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); + + const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); + setObjSearch({ city: cityValue, sgg: firstSggVal }); + } + }; + + const getSgg = (val: string | number) => { + setObjSearch({ ...objSearch, sgg: String(val) }) + }; + + const getConValue = () => { + if (!objSearch.city || objSearch.city === "0") { + return "0"; + } + if (objSearch.sgg && objSearch.sgg !== "0") { + return objSearch.sgg; + } + return objSearch.city; + }; + const cellClick = (index: number | undefined, headId: string) => { if ( headId !== "chBuildingYn" && @@ -67,42 +107,34 @@ export default function HtdflSttn() { } }; useEffect(() => { - const excelDown = async () => { - if (htdflSttn.excelList.length > 0) { - if (htdflSttn.excelList.length === 0) return; - const excelData = await tableData(htdflSttn.excelList, { code }); - const htdflExcel = excelHtdflSttnParsing(excelData, code.cityCd, code.sggCd); - excelDownload( - htdflExcel as [[]], - "온배수시설 현황", - "sample_htdflSttn.xlsx", - "A1:S3" - ); - } - }; - excelDown(); + if (htdflSttn.excelList.length > 0) { + if (htdflSttn.excelList.length === 0) return; + excelDownload( + htdflSttn.excelList, + "온배수시설 현황", + "sample_htdflSttn.xlsx", + "A1:S3" + ); + } }, [htdflSttn.excelList, code]); useEffect(() => { if (!htdflSttn.isInit) { - initHtdfl(codeData ?? "0"); + initHtdfl(1, "20", "0", fcltNm, code.cityCd, code.sggCd); } }, [htdflSttn.isInit, initHtdfl]); useEffect(() => { - if (htdflSttnRes.length > 0 && code.cityCd.length > 0) { - const data = tableData(htdflSttnRes, { code }); - const chData = data - .map((item: any) => ({ - ...item, - ...item.reuseBuilding[0], - ...item.reuseBuilding[0].htdflObj, - chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", - })) - .filter((item: any) => item.htdflObj !== undefined); - - setBdstTable(chData); - } + const data = tableData(htdflSttnRes, { code }); + const chData = data + .map((item: any) => ({ + ...item, + ...item.reuseBuilding[0], + ...item.reuseBuilding[0].htdflObj, + chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", + })) + .filter((item: any) => item.htdflObj !== undefined); + setBdstTable(chData); }, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]); return ( @@ -111,43 +143,76 @@ export default function HtdflSttn() { <div className="grid grid-cols-4 gap-4 "> <div className="col-span-4"> <Title title={"온배수 시설 현황"} /> - <SearchBaseForm - selectSidoData={code.cityCd} - selectSigunData={code.sggCd} - selectRegionData={code.rvbsnCd} - selectUnitWaterShedData={code.lgbsnCd} - selectMiddleRegionData={code.mdmbsnCd} - selectEnvPartData={code.lclgvrnmntCd} - rightChildren={<Button - className={btnClass} - onClick={() => { - setIsChHead(true); - } } - > - 조회항목설정 - </Button>} - leftChildren={undefined} - queryClick={function (returnType: ReturnType): void { - // '{partValue: 'A', sidoValue: '0', sigunValue: '0', regionValue: '0', unitWaterShedValue: '0', …}' - let con; - if (returnType.sigunValue != "0") { - con = returnType.sigunValue; - } else { - con = returnType.sidoValue; - } - htdflSttnSearch(pageNation, "20", con); - } } - excelClick={excelClick} - initClick={function (): void { } } - btnClass={btnClass} - ref={ref} isAdmin={false} /> + + <SearchDiv> + <SearchFormLeft> + <div className="col-span-10 flex flex-row"> + <SelectBox + className="col-span-2 w-1/6 h-full text-2xl" + label="시도" + selectClass={""} + selectArray={code.cityCd} + onSelectValue={getSelCity} + /> + <SelectBox + className="col-span-2 w-1/6 h-full text-2xl ml-4" + label="시군구" + selectClass={""} + selectArray={selectSgg || []} + value={objSearch.sgg} // value props 추가 + onSelectValue={getSgg} + /> + <div className="col-span-2 flex flex-row space-x-2"> + <Label className="w-28 mt-2 text-xl text-gray-600"> + {"시설명"} + </Label> + <SearchInput + className={"w-full h-full text-2xl"} + label={""} + textValue={fcltNm || ""} + setText={setFcltNm} + /> + </div> + </div> + </SearchFormLeft> + <SearchFormRight + className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> + <div className="flex flex-row space-x-2 items-center"> + <Button + size={"sm"} + onClick={() => { + const con = getConValue(); + htdflSttnSearch(pageNation, "20", String(con), fcltNm, code.cityCd, code.sggCd); + }} + > + 조회 + </Button> + <Button + size={"sm"} + className={`${btnClass} green`} + onClick={excelClick} + > + 다운로드 + </Button> + <Button + size={"sm"} + className={btnClass} + onClick={() => { + setIsChHead(true); + }} + > + 조회항목설정 + </Button> + </div> + </SearchFormRight> + </SearchDiv> </div> </div> <div className="grid grid-cols-6 gap-4 "> <div className="col-span-6"> <ListDiv> <UiTable - headName={[]} + headName={undefined} tableData={bdstTable || []} children={ <HeadMakeColSpan upHeadList={htdflSttn.htdflSttNodeList} /> diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx index 631e24350..0b6dffe07 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/rainSttn/page.tsx @@ -4,6 +4,7 @@ import { useWrStore } from "@/app/store"; import { btnClass } from "@/app/store/common/code"; import { excelDownload, + getCity, getCityOrSgg, tableData, } from "@/components/lib/func/commom"; @@ -11,13 +12,19 @@ import HeadChModal from "@/components/ui/modal/sttn/headChModal/headChModal"; import { ContentsDiv, SearchBaseForm, + SearchDiv, + SearchFormLeft, + SearchFormRight, + SearchInput, + SelectBox, Title, UiModal, UiTable, type ReturnType, + type SelectDataType, } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; -import { Button } from "@common/components/ui"; +import { Button, Label } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; import type { chgBuilding, @@ -26,6 +33,7 @@ import type { import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; import BdstChgModal from "@/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal"; +import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal"; export default function RainSttn() { const { code, rainSttn, rainSttnSearch, rainSttnInit } = useWrStore( @@ -37,65 +45,94 @@ 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 [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>() + const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" }); + const [detailModal, setDetailModal] = useState<{ isOpen: boolean, data: customRainBdst | undefined }>({ data: undefined, isOpen: false }) const [bdstChgProps, setBdstChgProps] = useState<{ isOpen: boolean; chBuilding: chgBuilding[]; }>({ isOpen: false, chBuilding: [] }); - - const codeData = getCityOrSgg(ref); + const [fcltNm, setFcltNm] = useState<string>(""); 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 }); } } }; + const getSelCity = (val: string | number) => { + const cityValue = String(val); + + if (cityValue === "0") { + setSelectSgg([]); + setObjSearch({ city: cityValue, sgg: "" }); + } else { + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); + + const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); + setObjSearch({ city: cityValue, sgg: firstSggVal }); + } + }; + + const getSgg = (val: string | number) => { + setObjSearch({ ...objSearch, sgg: String(val) }) + }; + + const getConValue = () => { + if (!objSearch.city || objSearch.city === "0") { + return "0"; + } + if (objSearch.sgg && objSearch.sgg !== "0") { + return objSearch.sgg; + } + return objSearch.city; + }; + useEffect(() => { if (!rainSttn.isInit) { - rainSttnInit(codeData ?? "0"); + rainSttnInit(1, "20", "0", fcltNm, code.cityCd, code.sggCd); } }, [rainSttn.isInit, rainSttnInit]); useEffect(() => { - if (rainSttnRes.length > 0 && code.cityCd.length > 0) { - const data = tableData(rainSttnRes, { code }); - const chData = data - .map((item: any) => ({ + const data = tableData(rainSttnRes, { code }); + const chData = data + .map((item: any) => { + return { ...item, ...item.reuseBuilding[0], ...item.reuseBuilding[0].rainyObj, + dtfcltCpct: item.reuseBuilding[0].rainyObj.dtfcltCpct === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.dtfcltCpct, + instlCst: item.reuseBuilding[0].instlCst === -1 + ? "확인불가" + : item.reuseBuilding[0].instlCst, + instlCstSprtAmt: item.reuseBuilding[0].instlCstSprtAmt === -1 + ? "확인불가" + : item.reuseBuilding[0].instlCstSprtAmt, chBuildingYn: item.chgBuilding[0]?.bdstChgNm !== null ? "Y" : "N", - })) - .filter((item: any) => item.rainyObj !== undefined); - - setBdstTable(chData); - } + pntfColArea: item.reuseBuilding[0].rainyObj.pntfColArea === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.pntfColArea, + filtrTrtmfctCpct: item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct === -1 + ? "확인불가" + : item.reuseBuilding[0].rainyObj.filtrTrtmfctCpct, + archArea: Math.floor(item.archArea) === -1 ? "확인불가" : item.archArea, + }; + }) + .filter((item: any) => item.rainyObj !== undefined); + setBdstTable(chData); }, [code, rainSttnRes, rainSttn.rainSttHeadList]); useEffect(() => { - if (codeData != null) rainSttnSearch(pageNation, "20", codeData, code.cityCd, code.sggCd); + rainSttnSearch(pageNation, "20", String(getConValue()), fcltNm, code.cityCd, code.sggCd); }, [pageNation]); useEffect(() => { @@ -111,8 +148,10 @@ export default function RainSttn() { }, [rainSttn.excelList]); const excelClick = useCallback(() => { - if (codeData) rainSttnSearch(1, "10000", codeData, code.cityCd, code.sggCd); - }, [rainSttnSearch, codeData, code.cityCd, code.sggCd]); + // 최신 검색 조건 반영 + const con = getConValue(); + rainSttnSearch(1, "10000", String(con), fcltNm, code.cityCd, code.sggCd); + }, [rainSttnSearch, objSearch, code.cityCd, code.sggCd]); return ( <> @@ -120,47 +159,75 @@ export default function RainSttn() { <div className="grid grid-cols-4 gap-4 "> <div className="col-span-4"> <Title title={"빗물재이용 시설 현황"} /> - <SearchBaseForm - selectSidoData={code.cityCd} - selectSigunData={code.sggCd} - selectRegionData={code.rvbsnCd} - selectUnitWaterShedData={code.lgbsnCd} - selectMiddleRegionData={code.mdmbsnCd} - selectEnvPartData={code.lclgvrnmntCd} - rightChildren={ - <Button - className={btnClass} - onClick={() => { - setIsChHead(true); - }} - > - 조회항목설정 - </Button> - } - leftChildren={undefined} - queryClick={function (returnType: ReturnType): void { - // '{partValue: 'A', sidoValue: '0', sigunValue: '0', regionValue: '0', unitWaterShedValue: '0', …}' - let con; - if (returnType.sigunValue != "0") { - con = returnType.sigunValue; - } else { - con = returnType.sidoValue; - } - rainSttnSearch(1, "20", con, code.cityCd, code.sggCd); - }} - excelClick={excelClick} - initClick={function (): void { }} - btnClass={btnClass} - ref={ref} - isAdmin={false} - /> + <SearchDiv> + <SearchFormLeft> + <div className="col-span-10 flex flex-row"> + <SelectBox + className="col-span-2 w-1/6 h-full text-2xl" + label="시도" + selectClass={""} + selectArray={code.cityCd} + onSelectValue={getSelCity} + /> + <SelectBox + className="col-span-2 w-1/6 h-full text-2xl ml-4" + label="시군구" + selectClass={""} + selectArray={selectSgg || []} + value={objSearch.sgg} // value props 추가 + onSelectValue={getSgg} + /> + <div className="col-span-2 flex flex-row space-x-2"> + <Label className="w-28 mt-2 text-xl text-gray-600"> + {"시설명"} + </Label> + <SearchInput + className={"w-full h-full text-2xl"} + label={""} + textValue={fcltNm || ""} + setText={setFcltNm} + /> + </div> + </div> + </SearchFormLeft> + <SearchFormRight + className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> + <div className="flex flex-row space-x-2 items-center"> + <Button + size={"sm"} + onClick={() => { + const con = getConValue(); + rainSttnSearch(pageNation, "20", String(con), fcltNm, code.cityCd, code.sggCd); + }} + > + 조회 + </Button> + <Button + size={"sm"} + className={`${btnClass} green`} + onClick={excelClick} + > + 다운로드 + </Button> + <Button + size={"sm"} + className={btnClass} + onClick={() => { + setIsChHead(true); + }} + > + 조회항목설정 + </Button> + </div> + </SearchFormRight> + </SearchDiv> </div> </div> <div className="grid grid-cols-6 gap-4 "> <div className="col-span-6"> <ListDiv> <UiTable - headName={[]} + headName={undefined} tableData={bdstTable || []} children={ <HeadMakeColSpan upHeadList={rainSttn.rainSttNodeList} /> @@ -168,9 +235,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> diff --git a/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx b/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx index 7a3444a9b..ee7a344f8 100644 --- a/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/fcltSttn/rswtSttn/page.tsx @@ -5,23 +5,31 @@ import type { chgBuilding } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import type { customRswtBdst } from "@/app/store/ruseFcltMng/fcltReg/rswtReg"; import { excelDownload, + getCity, getCityOrSgg, tableData, } from "@/components/lib/func/commom"; import BdstChgModal from "@/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal"; +import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal"; import HeadChModal from "@/components/ui/modal/sttn/headChModal/headChModal"; import SttnDetailModal from "@/components/ui/modal/sttn/sttnDetailModal/sttnDetailModal"; import { ContentsDiv, SearchBaseForm, + SearchDiv, + SearchFormLeft, + SearchFormRight, + SearchInput, + SelectBox, Title, UiModal, UiTable, type ReturnType, + type SelectDataType, } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; -import { Button } from "@common/components/ui"; +import { Button, Label } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; export default function RswtSttn() { @@ -34,13 +42,14 @@ export default function RswtSttn() { const [isChHead, setIsChHead] = useState(false); const ref = useRef<ReturnType>({} as ReturnType); const [bdstTable, setBdstTable] = useState<customRswtBdst[]>(); + const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>() + const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" }); const [detailModal, setDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false }) const [bdstChgProps, setBdstChgProps] = useState<{ isOpen: boolean; chBuilding: chgBuilding[]; }>({ isOpen: false, chBuilding: [] }); - - const codeCityOrSgg = getCityOrSgg(ref); + const [fcltNm, setFcltNm] = useState<string>(""); const cellClick = (index: number | undefined, headId: string) => { @@ -50,37 +59,67 @@ export default function RswtSttn() { if ( bdstTable[index] && bdstTable[index]?.chBuildingYn === 'Y' - ) { - setBdstChgProps({ isOpen: true, chBuilding: bdstTable[index].chgBuilding }); } } } + const getSelCity = (val: string | number) => { + const cityValue = String(val); + + if (cityValue === "0") { + setSelectSgg([]); + setObjSearch({ city: cityValue, sgg: "" }); + } else { + const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); + + const firstSggVal = data.find(item => item.val === "0")?.val || (data[0]?.val ?? ""); + setObjSearch({ city: cityValue, sgg: firstSggVal }); + } + }; + + const getSgg = (val: string | number) => { + setObjSearch({ ...objSearch, sgg: String(val) }) + }; + + const getConValue = () => { + if (!objSearch.city || objSearch.city === "0") { + return "0"; + } + if (objSearch.sgg && objSearch.sgg !== "0") { + return objSearch.sgg; + } + return objSearch.city; + }; + useEffect(() => { if (!rswtSttn.isInit) { - rswtSttnInit("0"); + rswtSttnInit(1, "20", "0", fcltNm, code.cityCd, code.sggCd); } }, [rswtSttn.isInit, rswtSttnInit]); useEffect(() => { - if (rswtSttnRes.length > 0 && code.cityCd.length > 0) { - const data = tableData(rswtSttnRes, { code }); - const chData = data - .map((item: customRswtBdst) => ({ + const data = tableData(rswtSttnRes, { code }); + const chData = data + .map((item: customRswtBdst) => { + return { ...item, ...item.reuseBuilding[0], ...item.reuseBuilding[0]?.rswtrObj, - chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N' - })) - .filter( - (item: customRswtBdst) => - item.reuseBuilding[0]?.rswtrObj !== undefined - ); + chBuildingYn: item?.chgBuilding[0]?.bdstChgNm !== null && item?.chgBuilding.length > 0 ? 'Y' : 'N', + instlCst: item.reuseBuilding[0]?.instlCst === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCst, + instlCstSprtAmt: item.reuseBuilding[0]?.instlCstSprtAmt === -1 ? "확인불가" : item.reuseBuilding[0]?.instlCstSprtAmt, + archArea : item?.archArea.split('.')[0]!=='-1'?item?.archArea.split('.')[0]:'확인불가' + } + }) + .filter( + (item: customRswtBdst) => + item.reuseBuilding[0]?.rswtrObj !== undefined + ); - setBdstTable(chData); - } + setBdstTable(chData); }, [code, rswtSttnRes]); useEffect(() => { @@ -95,16 +134,14 @@ export default function RswtSttn() { } }, [rswtSttn.excelList]); - const excelClick = useCallback( - (returnType: ReturnType) => { - if (codeCityOrSgg) - rswtSttnSearch(1, "10000", codeCityOrSgg, code.cityCd, code.sggCd); - }, - [rswtSttnSearch, code.cityCd, code.sggCd, codeCityOrSgg] - ); + const excelClick = useCallback(() => { + // 최신 검색 조건 반영 + const con = getConValue(); + rswtSttnSearch(1, "10000", String(con), fcltNm, code.cityCd, code.sggCd); + }, [rswtSttnSearch, objSearch, code.cityCd, code.sggCd]); useEffect(() => { - if (codeCityOrSgg != null) rswtSttnSearch(pageNation, "20", codeCityOrSgg, code.cityCd, code.sggCd); + rswtSttnSearch(pageNation, "20", String(getConValue()), fcltNm, code.cityCd, code.sggCd); }, [pageNation]); return ( @@ -113,38 +150,75 @@ export default function RswtSttn() { <div className="grid grid-cols-4 gap-4 "> <div className="col-span-4"> <Title title={"중수도시설 현황"} /> - <SearchBaseForm - selectSidoData={code.cityCd} - selectSigunData={code.sggCd} - selectRegionData={code.rvbsnCd} - selectUnitWaterShedData={code.lgbsnCd} - selectMiddleRegionData={code.mdmbsnCd} - selectEnvPartData={code.lclgvrnmntCd} - rightChildren={<Button - className={btnClass} - onClick={() => { - setIsChHead(true); - } } - > - 조회항목설정 - </Button>} - leftChildren={undefined} - queryClick={function (returnType: ReturnType): void { - // '{partValue: 'A', sidoValue: '0', sigunValue: '0', regionValue: '0', unitWaterShedValue: '0', …}' - if (codeCityOrSgg) - rswtSttnSearch(1, "20", codeCityOrSgg, code.cityCd, code.sggCd); - } } - excelClick={excelClick} - initClick={function (): void { } } - btnClass={btnClass} - ref={ref} isAdmin={false} /> + <SearchDiv> + <SearchFormLeft> + <div className="col-span-10 flex flex-row"> + <SelectBox + className="col-span-2 w-1/6 h-full text-2xl" + label="시도" + selectClass={""} + selectArray={code.cityCd} + onSelectValue={getSelCity} + /> + <SelectBox + className="col-span-2 w-1/6 h-full text-2xl ml-4" + label="시군구" + selectClass={""} + selectArray={selectSgg || []} + value={objSearch.sgg} // value props 추가 + onSelectValue={getSgg} + /> + <div className="col-span-2 flex flex-row space-x-2"> + <Label className="w-28 mt-2 text-xl text-gray-600"> + {"시설명"} + </Label> + <SearchInput + className={"w-full h-full text-2xl"} + label={""} + textValue={fcltNm || ""} + setText={setFcltNm} + /> + </div> + </div> + </SearchFormLeft> + <SearchFormRight + className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> + <div className="flex flex-row space-x-2 items-center"> + <Button + size={"sm"} + onClick={() => { + const con = getConValue(); + rswtSttnSearch(pageNation, "20", String(con), fcltNm, code.cityCd, code.sggCd); + }} + > + 조회 + </Button> + <Button + size={"sm"} + className={`${btnClass} green`} + onClick={excelClick} + > + 다운로드 + </Button> + <Button + size={"sm"} + className={btnClass} + onClick={() => { + setIsChHead(true); + }} + > + 조회항목설정 + </Button> + </div> + </SearchFormRight> + </SearchDiv> </div> </div> <div className="grid grid-cols-6 gap-4 "> <div className="col-span-6"> <ListDiv> <UiTable - headName={[]} + headName={undefined} tableData={bdstTable || []} children={ <HeadMakeColSpan upHeadList={rswtSttn.rswtSttNodeList} /> diff --git a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead.tsx index 10a0bf85f..28ba6c512 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead.tsx @@ -26,10 +26,10 @@ export const headMakeprfmnc = ( key={head.id} rowSpan={1} colSpan={ - makeHeadNm.find((ch) => ch.headNm === head.upName)?.length + makeHeadNm?.find((ch) => ch.headNm === head.upName)?.length } > - {makeHeadNm.find((ch) => ch.headNm === head.upName)?.text} + {makeHeadNm?.find((ch) => ch.headNm === head.upName)?.text} </TableHead> ) : null )} @@ -39,7 +39,7 @@ export const headMakeprfmnc = ( {upHeadList .filter((data) => data.visible != false) .map((head: TableUpperProps) => - makeHeadNm.find((ch) => ch.headNm === head.upName) ? ( + makeHeadNm?.find((ch) => ch.headNm === head.upName) ? ( <TableHead className={`border border-gray-600 px-2 py-1 `} key={head.id} diff --git a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx index 18dbc7943..0ac9145a5 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/chkgdnReg/page.tsx @@ -1,10 +1,14 @@ "use client"; import { useWrStore } from "@/app/store"; -import { type rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; -import type { - rswtChkZodType, - rswtrChkDetailsZodType, +import { + commonCd, + rainPageSize, + type rainChkZodType, +} from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; +import { + rswtPageSize, + type rswtrChkDetailsZodType, } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn"; import { excelArrayDownload, @@ -15,23 +19,27 @@ import ChkgdExcel from "@/components/ui/modal/chkgd/chkgdExcel/chkgdExcel"; import ChkgdModal from "@/components/ui/modal/chkgd/chkgdModal/chkgdModal"; import RainChkgdTable from "@/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable"; import RswtChkgdTable from "@/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable"; +import { SearchInput } from "@common/business_components"; import { ContentsDiv, SearchDiv, SearchFormLeft, SearchFormRight, - SearchInput, SelectBox, Title, UiModal, } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button, Label } from "@common/components/ui"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@common/components/ui/tooltip"; import { useCallback, useEffect, useState } from "react"; -import { Toaster } from "react-hot-toast"; -import { shallow } from "zustand/shallow"; +import toast, { Toaster } from "react-hot-toast"; -//----------------------- export default function Page() { const { code, @@ -42,9 +50,10 @@ export default function Page() { rainChkDelete, rainChkExcelDownload, rainChkHeadChange, + rainChkNmSearch, isRainSttn, - rswtChkgdnInit, + rswtChkNmSearch, rswtChkPolInit, rswtChkDetilsUpdate, rswtChkDelete, @@ -61,38 +70,87 @@ export default function Page() { rswtrChkDetailsZodType[] >([]); const [deleteIdx, setDeleteIdx] = useState<number[]>([]); - + const [rainPageNation, setRainPageNation] = useState(1); + const [rswtPageNation, setRswtPageNation] = useState(1); + const [fcltNm, setFcltNm] = useState<string>(""); const [selFcltDiv, setSelFcltDiv] = useState("RNB"); const [isModal, setIsModal] = useState({ open: false, data: {}, }); - const cellClick = () => { - if (searchYear) rainChkInit(searchYear, "0"); - }; - const [searchYear, setSearchYear] = useState<string>( String(new Date().getFullYear()) ); + const cellClick = () => { + if (searchYear) + if (selFcltDiv === "RNB") { + if (fcltNm) { + rainChkNmSearch( + rainPageNation, + rainPageSize, + searchYear, + commonCd, + fcltNm + ); + } else { + rainChkInit(rainPageNation, rainPageSize, searchYear, commonCd); + } + } else { + if (fcltNm) { + rswtChkNmSearch( + rainPageNation, + rainPageSize, + searchYear, + commonCd, + fcltNm + ); + } else { + rswtChkgdnInit(rswtPageNation, rswtPageSize, searchYear, commonCd); + } + } + }; + const rainPageChange = (pageNo: number) => { + if (pageNo > 0) { + setRainPageNation(pageNo); + rainChkInit(pageNo, rainPageSize, searchYear, commonCd); + } + }; + const rswtPageChange = (pageNo: number) => { + if (pageNo > 0) { + setRswtPageNation(pageNo); + rswtChkgdnInit(pageNo, rswtPageSize, searchYear, commonCd); + } + }; useEffect(() => { if (!rainChkgdn.isInit) { - rainChkInit(new Date().getFullYear().toString(), "0"); + rainChkInit( + 1, + rainPageSize, + new Date().getFullYear().toString(), + commonCd + ); isRainSttn(false); setIsModal({ data: {}, open: false }); setIsRegExcel(false); } + }, [rainChkgdn.isInit, rainChkInit, rainPageSize]); + useEffect(() => { if (!rswtChkgdn.isInit) { - rswtChkgdnInit(new Date().getFullYear().toString(), "0"); + rswtChkgdnInit( + 1, + rswtPageSize, + new Date().getFullYear().toString(), + commonCd + ); setIsModal({ data: {}, open: false }); setIsRegExcel(false); } - }, [rainChkgdn.isInit, rainChkInit]); + }, [rswtChkgdn.isInit, rswtChkgdnInit, rswtPageSize]); useEffect(() => { - if (rswtChkgdn.excelList[0]&&rswtChkgdn.excelList[0]?.data.length > 1) { - + if (rswtChkgdn.excelList[0] && rswtChkgdn.excelList[0]?.data.length > 1) { excelArrayDownload( "중수도시설 지도점검", "sample_rs_chk.xlsx", @@ -152,9 +210,17 @@ export default function Page() { }; const chkUpdateTable = (selFcltDiv: string) => { if (selFcltDiv === "RNB") { + if (updateRainDetail && updateRainDetail.length > 0) { rainChkDetilUpdate(updateRainDetail); } else { + toast.error("수정을 원하는 행을 선택하고 저장버튼을 눌러주세요 "); + } + } else { + if (updateRswtDetail && updateRswtDetail.length > 0) { rswtChkDetilsUpdate(updateRswtDetail); + } else { + toast.error("수정을 원하는 행을 선택하고 저장버튼을 눌러주세요 "); + } } }; @@ -175,9 +241,9 @@ export default function Page() { selectClass={""} selectArray={yearOptions} value={searchYear} - onSelectValue={(val: string | number) => - setSearchYear(String(val)) - } + onSelectValue={(val: string | number) => { + setSearchYear(String(val)); + }} /> </div> <div className="col-span-2 flex flex-row space-x-2"> @@ -189,14 +255,24 @@ export default function Page() { label={""} selectClass={""} selectArray={rainChkgdn.fcltDiv} - onSelectValue={(val: string | number) =>{ - if(val === "RST") rswtChkPolInit(); - setSelFcltDiv(String(val)) - } - } + onSelectValue={(val: string | number) => { + if (val === "RST") rswtChkPolInit(); + setSelFcltDiv(String(val)); + }} value={selFcltDiv ?? rainChkgdn.fcltDiv[0]?.val} /> </div> + <div className="col-span-2 flex flex-row space-x-2"> + <Label className="w-28 mt-2 text-xl text-gray-600"> + {"시설명"} + </Label> + <SearchInput + className={"w-full h-full text-2xl"} + label={""} + textValue={fcltNm || ""} + setText={setFcltNm} + /> + </div> </SearchFormLeft> <SearchFormRight className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> <div className="space-y"> @@ -217,6 +293,10 @@ export default function Page() { > 삭제 </Button> + + <TooltipProvider> + <Tooltip> + <TooltipTrigger asChild> <Button size={"sm"} onClick={() => { @@ -225,6 +305,16 @@ export default function Page() { > 저장 </Button> + </TooltipTrigger> + <TooltipContent side="bottom" sideOffset={5}> + <h2 className="text-xl"> + 아래 테이블에 선택을 체크하면 입력셀이 활성화 됩니다. + </h2> + </TooltipContent> + </Tooltip> + </TooltipProvider> + + <Button size={"sm"} onClick={() => { @@ -251,13 +341,18 @@ export default function Page() { detailUp={setUpdateRainDetail} handleClick={handleClick} rainChkData={rainChkgdn.rainChk} - /> + pageNation={rainPageNation} + onPageChange={rainPageChange} + pageCount={rainChkgdn.rainChkCount} /> ) : ( <RswtChkgdTable detaiRswtlUpdate={setUpdateRSwtDetail} deleteRswtData={setDeleteIdx} handleRswtClick={handleClick} rswtChkData={rswtChkgdn.rswtChkRes} + pageNation={rswtPageNation} + onPageChange={rswtPageChange} + pageCount={rswtChkgdn.rswtChkCount} /> )} </ListDiv> @@ -265,17 +360,28 @@ export default function Page() { </div> <UiModal open={isModal.open} - onOpenChange={() => { - setIsModal({ open: !open, data: {} }); + onOpenChange={(open: boolean) => { + setIsModal((prev) => ({ ...prev, open })); }} bodyClass={"w-[80vw] max-w-none"} - body={<ChkgdModal type={selFcltDiv} obj={isModal.data} />} + body={ + <ChkgdModal + type={selFcltDiv} + obj={isModal.data} + setIsModal={(open) => setIsModal((prev) => ({ ...prev, open }))} + /> + } ></UiModal> <UiModal open={isRegExcel} onOpenChange={setIsRegExcel} bodyClass={"w-[30vw] max-w-none"} - body={<ChkgdExcel type={selFcltDiv}></ChkgdExcel>} + body={ + <ChkgdExcel + type={selFcltDiv} + setIsRegExcel={setIsRegExcel} + ></ChkgdExcel> + } /> <Toaster position="top-center" /> </ContentsDiv> diff --git a/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx b/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx index 6be50a8c5..c54725ff2 100644 --- a/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncReg/pbnstPrfmncReg/page.tsx @@ -25,7 +25,7 @@ import { } from "@common/components/ui"; import { useState, useEffect, useCallback } from "react"; import { set } from "react-hook-form"; -import { Toaster } from "react-hot-toast"; +import toast, { Toaster } from "react-hot-toast"; import { headMakeprfmnc } from "../chkgdnReg/TableHead"; import { InputTableBody } from "@/components/lib/func/TableData"; import { @@ -37,6 +37,7 @@ import { type PrfType, } from "@/app/store/prfmncReg/pbnstPrfmncReg/type"; import { excelToPbnPrfData } from "@/app/store/prfmncReg/pbnstPrfmncReg/excel"; +import data from "@common/components/ui/icon-finder/data"; export default function prfmncPrfmncReg() { const { code, prfmnc, prfmncHasuInit, prfmnRegData } = useWrStore( @@ -51,11 +52,12 @@ export default function prfmncPrfmncReg() { }>({ isModal: false, data: {} as FcPrfType }); const [inputData, setInputData] = useState<any[]>(); const [pbnstRuseFcltPk, setPbnstRuseFcltPk] = useState<string>(); - const [pageNation , setPageNation] = useState<number>(1); - + const [pageNation, setPageNation] = useState<number>(1); + const [cellNum, setCellNum] = useState<number>(1); const [searchYear, setSearchYear] = useState<string>( String(new Date().getFullYear()) ); + const excelClick = useCallback(() => { excelDownload( [[]], @@ -65,33 +67,43 @@ export default function prfmncPrfmncReg() { ); }, [prfmnc.excelList]); - const onSave = useCallback(() => { + const onSave = useCallback( + async() => { if (pbnstRuseFcltPk) { - prfmnRegData( + await prfmnRegData(pageNation,'41', excelToPbnPrfData( inputData as unknown as JSON[], searchYear, pbnstRuseFcltPk ) as unknown as PrfType ); - prfmncHasuInit(String(pageNation), "41"); + // cellClick(cellNum) } - }, [inputData, searchYear, pbnstRuseFcltPk,prfmncHasuInit,pageNation]); - - - + }, [inputData, searchYear, pbnstRuseFcltPk, pageNation,cellNum]); const cellClick = useCallback( - (index: number | undefined, headId: string) => { + (index: number | undefined) => { if (index !== undefined) { setIsExcelOPen({ isModal: false, data: prfmncRes[index] || ({} as FcPrfType), }); - const inUsageData = structuredClone(initValInUsage); - const outUsageData = structuredClone(initValOutUsage); - const inSubtotal = structuredClone(subTotalObj); - const outSubtotal = structuredClone(subTotalObj); + const inUsageData = structuredClone(initValInUsage).map((row) => ({ + ...row, + 구분: "장내용수", + })); + const outUsageData = structuredClone(initValOutUsage).map((row) => ({ + ...row, + 구분: "장외용수", + })); + const inSubtotal = { + ...structuredClone(subTotalObj), + 구분: "장내용수", + }; + const outSubtotal = { + ...structuredClone(subTotalObj), + 구분: "장외용수", + }; if (prfmncRes[index]?.prfList) { prfmncRes[index].prfList?.forEach((prfItem) => { const month = Number(prfItem?.ym?.slice(4)); // "MM" @@ -127,7 +139,11 @@ export default function prfmncPrfmncReg() { }); inUsageData.push(inSubtotal); outUsageData.push(outSubtotal); - const usageTotalObj: { [key: string]: any } = structuredClone(totalObj); + const usageTotalObj: { [key: string]: any } = { + ...structuredClone(totalObj), + 구분: "총합계", + 용수명: "", + }; for (const key in usageTotalObj) { if (["usage", "gubun", "edit"].includes(key)) continue; const val = @@ -138,7 +154,6 @@ export default function prfmncPrfmncReg() { } const tableData = [...inUsageData, ...outUsageData, usageTotalObj]; setPbnstRuseFcltPk(prfmncRes[index].pbnstRuseFcltPk); - setInputData(tableData); } else { setInputData([]); @@ -150,19 +165,24 @@ export default function prfmncPrfmncReg() { useEffect(() => { if (!prfmnc.isInit) { - prfmncHasuInit('1', "41"); + prfmncHasuInit(1, "41"); } }, [prfmnc.isInit, prfmncHasuInit]); useEffect(() => { setbdstTable(prfmncRes); - }, [prfmncRes]); + + // cellNum이 0일 경우(첫 번째 행)도 유효한 인덱스이므로 undefined 여부만 체크합니다. + if (cellNum !== undefined) { + cellClick(cellNum); + } + }, [prfmncRes, cellNum, cellClick]); return ( <> <ContentsDiv> <div className="grid grid-cols-4 gap-4"> <div className="col-span-4"> - <Title title={"하수재이용 시설 등록"} /> + <Title title={"하수재이용 실적 등록"} /> <SearchDiv> <SearchFormLeft> <div className="col-span-2 flex flex-row space-x-2"> @@ -211,7 +231,12 @@ export default function prfmncPrfmncReg() { <Button size={"sm"} onClick={() => { - setIsExcelOPen({ ...isExcelOpen, isModal: true }); + if (cellNum === undefined) { + toast.error("하수처리재이용시설을 선택해주세요."); + return; + } else { + setIsExcelOPen({ ...isExcelOpen, isModal: true }); + } }} > 파일등록 @@ -231,13 +256,15 @@ export default function prfmncPrfmncReg() { <ListDiv className="min-h-0"> <ScrollArea className="h-[60vh]"> <UiTable + key={'leftTable'} tableData={bdstTable || []} headlist={pbnstPrfmncHeadList} pageSize={10} total={10000} headName={"하수재이용시설 목록"} cellClick={(index: number | undefined, headId: string) => { - cellClick(index, headId); + cellClick(index); + setCellNum(index ?? 0); }} onPageChange={setPageNation} children={undefined} @@ -257,7 +284,7 @@ export default function prfmncPrfmncReg() { <TableHeader className="font-bold"> {headMakeprfmnc(monthHeadList, [])} </TableHeader> - <TableBody> + <TableBody key={prfmnc.pageKey+'pageKey'}> {InputTableBody(inputData ?? [], monthHeadList, setInputData)} </TableBody> </Table> @@ -266,8 +293,23 @@ export default function prfmncPrfmncReg() { </div> </ContentsDiv> <UiModal - bodyClass={""} - body={<PbnPrfExcelModal data={isExcelOpen.data}></PbnPrfExcelModal>} + bodyClass={"w-[30vw] max-w-none"} + body={ + <PbnPrfExcelModal + data={isExcelOpen.data} + setIsExcelOPen={(open) => { + setIsExcelOPen({ ...isExcelOpen, isModal: open }) + + }} + setModalExcelData={ async(excelData ) =>{ + await prfmnRegData(pageNation,'41', + excelData, + ); + // 데이터 저장 후 UI 업데이트는 useEffect가 감지하여 처리하므로 여기서는 호출하지 않습니다. + } + } + ></PbnPrfExcelModal> + } open={isExcelOpen.isModal} onOpenChange={() => { setIsExcelOPen({ isModal: false, data: {} as FcPrfType }); diff --git a/apps/wr/app/ruseFcltMng/prfmncSttn/chkgdnSttn/page.tsx b/apps/wr/app/ruseFcltMng/prfmncSttn/chkgdnSttn/page.tsx index f9a799c24..a191de9ee 100644 --- a/apps/wr/app/ruseFcltMng/prfmncSttn/chkgdnSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncSttn/chkgdnSttn/page.tsx @@ -1,14 +1,12 @@ "use client"; import { useWrStore } from "@/app/store"; -import { type rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; -import type { rswtChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn"; +import { commonCd, rainPageSize } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; +import { rswtPageSize } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn"; import { - excelArrayDownload, excelDownload, - yearOptions, + yearOptions } from "@/components/lib/func/commom"; -import ChkgdExcel from "@/components/ui/modal/chkgd/chkgdExcel/chkgdExcel"; import ChkgdModal from "@/components/ui/modal/chkgd/chkgdModal/chkgdModal"; import RainChkgdTable from "@/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable"; import RswtChkgdTable from "@/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable"; @@ -20,56 +18,99 @@ import { SearchInput, SelectBox, Title, - UiModal, + UiModal } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button, Label } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; import { Toaster } from "react-hot-toast"; -//----------------------- export default function Page() { const { code, rainChkSttn, rswtChkSttn, - - rainChkSttnInit, - rainChkSttnSearch, + rainChkSttnNmSearch, rswtChkSttnInit, + rswtChkNmSttnSearch, isRainSttn, - isRswtSttn + isRswtSttn, + rswtChkPolInit, } = useWrStore((state) => { return state; }); const [selFcltDiv, setSelFcltDiv] = useState("RNB"); + const [rainPageNation, setRainPageNation] = useState(1); + const [rswtPageNation, setRswtPageNation] = useState(1); + const [fcltNm, setFcltNm] = useState<string>(""); const [isModal, setIsModal] = useState({ open: false, data: {}, }); const [searchYear, setSearchYear] = useState<string>(String(new Date().getFullYear())); const cellClick = () => { - if (searchYear) rainChkSttnSearch(searchYear, "0"); + if (searchYear) { + if (selFcltDiv === "RNB") { + if (fcltNm) { + rainChkSttnNmSearch( + rainPageNation, + rainPageSize, + searchYear, + commonCd, + fcltNm + ); + } else { + rainChkSttnInit(rainPageNation, rainPageSize, searchYear, commonCd); + } + } else { + if (fcltNm) { + rswtChkNmSttnSearch( + rswtPageNation, + rswtPageSize, + searchYear, + commonCd, + fcltNm + ); + } else { + rswtChkSttnInit(rswtPageNation, rswtPageSize, searchYear, commonCd); + } + } + } + }; + + const rainPageChange = (pageNo: number) => { + if (pageNo > 0) { + setRainPageNation(pageNo); + rainChkSttnInit(pageNo, rainPageSize, searchYear, commonCd); + } + }; + + const rswtPageChange = (pageNo: number) => { + if (pageNo > 0) { + setRswtPageNation(pageNo); + rswtChkSttnInit(pageNo, rswtPageSize, searchYear, commonCd); + } }; useEffect(() => { if (!rainChkSttn.isInit) { isRainSttn(true) - rainChkSttnInit( "0"); + console.log("여기", rainPageSize) + rainChkSttnInit(1, rainPageSize, new Date().getFullYear().toString(), commonCd); setIsModal({ data: {}, open: false }); } - }, [rainChkSttn.isInit, rainChkSttnInit]); - - useEffect(() => { + }, [rainChkSttn.isInit, rainChkSttnInit, rainPageSize]); + useEffect(() => { if (!rswtChkSttn.isInit) { setIsModal({ data: {}, open: false }); - rswtChkSttnInit( "0"); + rswtChkSttnInit(1, rswtPageSize, new Date().getFullYear().toString(), commonCd); isRswtSttn(true) } - },[rswtChkSttn.isInit, rswtChkSttnInit]) + }, [rswtChkSttn.isInit, rswtChkSttnInit, rswtPageSize]) + const handleClick = useCallback( @@ -78,25 +119,25 @@ export default function Page() { }, [isModal] ); -const excelDown=() =>{ - if(rainChkSttn.sttnExcelList && selFcltDiv === "RNB"){ - excelDownload( - rainChkSttn.sttnExcelList, - "빗물이용시설 지도점검 현황", - "sample_rainChkSttn.xlsx", - "A1:Q2", - 3 - ); - }else if(rswtChkSttn.sttnExcelList && selFcltDiv === "RST"){ - excelDownload( - rswtChkSttn.sttnExcelList, - "중수도시설 지도점검 현황", - "sample_rswtChkSttn.xlsx", - "A1:X2", - 3 - ); + const excelDown = () => { + if (rainChkSttn.sttnExcelList && selFcltDiv === "RNB") { + excelDownload( + rainChkSttn.sttnExcelList, + "빗물이용시설 지도점검 현황", + "sample_rainChkSttn.xlsx", + "A1:Q2", + 3 + ); + } else if (rswtChkSttn.sttnExcelList && selFcltDiv === "RST") { + excelDownload( + rswtChkSttn.sttnExcelList, + "중수도시설 지도점검 현황", + "sample_rswtChkSttn.xlsx", + "A1:X2", + 3 + ); + } } -} return ( <ContentsDiv> @@ -125,10 +166,23 @@ const excelDown=() =>{ label={""} selectClass={""} selectArray={rainChkSttn.fcltDiv} - onSelectValue={(val: string | number) => - setSelFcltDiv(String(val)) - } - value={selFcltDiv ?? rainChkSttn.fcltDiv[0]?.val} + onSelectValue={(val: string | number) => { + if (val === "RST") rswtChkPolInit(); + setSelFcltDiv(String(val)) + } + } + value={selFcltDiv ?? rainChkSttn.fcltDiv[0]?.val} + /> + </div> + <div className="col-span-2 flex flex-row space-x-2"> + <Label className="w-28 mt-2 text-xl text-gray-600"> + {"시설명"} + </Label> + <SearchInput + className={"w-full text-2xl"} + label={""} + textValue={fcltNm || ""} + setText={setFcltNm} /> </div> </SearchFormLeft> @@ -143,7 +197,7 @@ const excelDown=() =>{ > 조회 </Button> - <Button color="green" size={"sm"} onClick={(e) =>{excelDown()}}> + <Button color="green" size={"sm"} onClick={(e) => { excelDown() }}> 다운로드 </Button> </div> @@ -157,17 +211,23 @@ const excelDown=() =>{ <ListDiv> {selFcltDiv === "RNB" ? ( <RainChkgdTable - deleteData={() => {}} - detailUp={() => {}} + deleteData={() => { }} + detailUp={() => { }} handleClick={handleClick} rainChkData={rainChkSttn.rainChkRes} + pageNation={rainPageNation} + onPageChange={rainPageChange} + pageCount={rainChkSttn.rainChkCount} /> ) : ( <RswtChkgdTable - detaiRswtlUpdate={() => {}} - deleteRswtData={() => {}} + detaiRswtlUpdate={() => { }} + deleteRswtData={() => { }} handleRswtClick={handleClick} rswtChkData={rswtChkSttn.rswtChkRes} + pageNation={rswtPageNation} + onPageChange={rswtPageChange} + pageCount={rswtChkSttn.rswtChkCount} /> )} </ListDiv> @@ -179,7 +239,7 @@ const excelDown=() =>{ setIsModal({ open: !open, data: {} }); }} bodyClass={"w-[80vw] max-w-none"} - body={<ChkgdModal type={selFcltDiv} obj={isModal.data} />} + body={<ChkgdModal type={selFcltDiv} obj={isModal.data} setIsModal={() => { }} />} ></UiModal> <Toaster position="top-center" /> </ContentsDiv> diff --git a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx index a50c08071..3e860d744 100644 --- a/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx +++ b/apps/wr/app/ruseFcltMng/prfmncSttn/pbnstPrfmncSttn/page.tsx @@ -8,9 +8,11 @@ import { excelDownload, yearOptions } from "@/components/lib/func/commom"; import SearchInputText from "@/components/ui/searchInput/searchInput"; import { CHART_FORM, ContentsDiv, MainChart, ScrollArea, SearchBaseForm, SearchInput, SelectBox, Title, UiTable, type ChartConfig, type ReturnType } from "@common/business_components"; import ListDiv from "@common/business_components/ui/list_div"; -import { Button, Label } from "@common/components/ui"; +import { Button, Label, Table, TableBody, TableHeader } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; import toast, { Toaster } from "react-hot-toast"; +import { headMakeprfmnc } from "../../prfmncReg/chkgdnReg/TableHead"; +import { InputTableBody } from "@/components/lib/func/TableData"; export default function PbnstPrfmncSttn() { const { @@ -34,9 +36,9 @@ export default function PbnstPrfmncSttn() { const [swtrmtFcltNm, setSwtrmtFcltNm] = useState<string>(); const [pbnRuseNm, setPbnRuseNm] = useState<string>(""); - const [searchYear, setSearchYear] = useState<string>( - String(new Date().getFullYear()) - ); + const [searchYear, setSearchYear] = useState<string>( + String(new Date().getFullYear()) + ); const [excelData, setExcelData] = useState<any[]>([]); const excelClick = useCallback(() => { excelDownload( @@ -66,8 +68,6 @@ export default function PbnstPrfmncSttn() { chartData5: lineChartDataProps[]; chartData6: lineChartDataProps[]; chartData7: lineChartDataProps[]; - chartData8: lineChartDataProps[]; - chartData9: lineChartDataProps[]; }>(); const [outLineData, setOutLineData] = useState<{ chartData1: lineChartDataProps[]; @@ -76,6 +76,8 @@ export default function PbnstPrfmncSttn() { chartData4: lineChartDataProps[]; chartData5: lineChartDataProps[]; chartData6: lineChartDataProps[]; + chartData7: lineChartDataProps[]; + chartData8: lineChartDataProps[]; }>(); type dnChartForm = { label: string, val: number, fill?: string }; @@ -83,15 +85,20 @@ export default function PbnstPrfmncSttn() { type lineChartDataProps = { label: string, val: number, color: string }; const prfTableData = (prfList: PrfType[], year: string) => { - const inUsageData = structuredClone(initValInUsage); - const outUsageData = structuredClone(initValOutUsage); - - const inSubtotal = structuredClone(subTotalObj); - const outSubtotal = structuredClone(subTotalObj); + const inUsageData = structuredClone(initValInUsage).map(row => ({ + ...row, + 구분: "장내용수", + })); + const outUsageData = structuredClone(initValOutUsage).map(row => ({ + ...row, + 구분: "장외용수", + })); + const inSubtotal = { ...structuredClone(subTotalObj), 구분: "장내용수" }; + const outSubtotal = { ...structuredClone(subTotalObj), 구분: "장외용수" }; 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}월`; @@ -142,7 +149,7 @@ export default function PbnstPrfmncSttn() { setPrfData([]); } } - + const [chartId, setChartId] = useState<string>(); useEffect(() => { // 누적 변수 초기화 @@ -198,8 +205,8 @@ export default function PbnstPrfmncSttn() { prfTableData([], year) rawData.forEach((item) => { // 실적 테이블 데이터 - if (chartId){ - if(item.prfList?.[0]?.ym.slice(0, 4) === year && item.prfList?.[0]?.pbnstRuseFcltFk === chartId){ + if (chartId) { + if (item.prfList?.[0]?.ym.slice(0, 4) === year && item.prfList?.[0]?.pbnstRuseFcltFk === chartId) { prfTableData(item.prfList, year); } item.prfList.filter((e) => e.ym.slice(0, 4) === year && e.pbnstRuseFcltFk === chartId).forEach((element) => { @@ -221,16 +228,18 @@ export default function PbnstPrfmncSttn() { indstUswtr += element.indstUswtr; // 월별 합계 - inUsageSum = element.wshwtr + inUsageSum = + element.wshwtr + element.coolUswtr + element.cleanUswtr + element.drwtUswtr + element.dltnUswtr + element.ithlRswtr + element.ithlEtcUswtr - + element.cleanToilUswtr - + element.resn; - outUsageSum = element.lndwtr + outUsageSum = + element.cleanToilUswtr + + element.resn + + element.lndwtr + element.hydUswtr + element.ugwtChr + element.farmUswtr @@ -251,9 +260,10 @@ export default function PbnstPrfmncSttn() { dltnUswtrData.push({ label: `${month}월`, val: element.dltnUswtr, color: "purple" }); ithlRswtrData.push({ label: `${month}월`, val: element.ithlRswtr, color: "green" }); ithlEtcUswtrData.push({ label: `${month}월`, val: element.ithlEtcUswtr, color: "blue" }); + + cleanToilUswtrData.push({ label: `${month}월`, val: element.cleanToilUswtr, color: "magenta" }); resnData.push({ label: `${month}월`, val: element.resn, color: "skyblue" }); - lndwtrData.push({ label: `${month}월`, val: element.lndwtr, color: "red" }); hydUswtrData.push({ label: `${month}월`, val: element.hydUswtr, color: "orange" }); ugwtChrData.push({ label: `${month}월`, val: element.ugwtChr, color: "yellow" }); @@ -264,41 +274,37 @@ export default function PbnstPrfmncSttn() { } }); + let dnTotalIn = 0; + let dnTotalOut = 0; + inUsageArr.forEach((item) => { + dnTotalIn += item + }) + outUsageArr.forEach((item) => { + dnTotalOut += item + }) + // 도넛차트 데이터 - dnData.push({ label: "세척수", val: wshwtr, fill: "red" }); - dnData.push({ label: "냉각수", val: coolUswtr, fill: "orange" }); - dnData.push({ label: "청소수", val: cleanUswtr, fill: "yellow" }); - dnData.push({ label: "식수대", val: drwtUswtr, fill: "green" }); - dnData.push({ label: "희석용수", val: dltnUswtr, fill: "skyblue" }); - dnData.push({ label: "중수도", val: ithlRswtr, fill: "blue" }); - dnData.push({ label: "기타", val: ithlEtcUswtr, fill: "purple" }); - dnData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "red" }); - dnData.push({ label: "세척살수용수", val: resn, fill: "orange" }); - dnData.push({ label: "조경용수", val: lndwtr, fill: "yellow" }); - dnData.push({ label: "친수용수", val: hydUswtr, fill: "green" }); - dnData.push({ label: "지하수충전", val: ugwtChr, fill: "skyblue" }); - dnData.push({ label: "농업용수", val: farmUswtr, fill: "blue" }); - dnData.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "purple" }); - dnData.push({ label: "공업용수", val: indstUswtr, fill: "black" }); + dnData.push({ label: "장내용수", val: dnTotalIn, fill: "#8DD8FF" }); + dnData.push({ label: "장외용수", val: dnTotalOut, fill: "#FFF085" }); // 장내용수 도넛 - dnInData.push({ label: "세척수", val: wshwtr, fill: "yellow" }); - dnInData.push({ label: "냉각수", val: coolUswtr, fill: "green" }); - dnInData.push({ label: "청소수", val: cleanUswtr, fill: "yellow" }); - dnInData.push({ label: "식수대", val: drwtUswtr, fill: "green" }); - dnInData.push({ label: "희석용수", val: dltnUswtr, fill: "yellow" }); - dnInData.push({ label: "중수도", val: ithlRswtr, fill: "green" }); - dnInData.push({ label: "기타", val: ithlEtcUswtr, fill: "yellow" }); - dnInData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "green" }); - dnInData.push({ label: "세척살수용수", val: resn, fill: "yellow" }); + dnInData.push({ label: "세척수", val: wshwtr, fill: "#F7CFD8" }); + dnInData.push({ label: "냉각수", val: coolUswtr, fill: "#F4F8D3" }); + dnInData.push({ label: "청소수", val: cleanUswtr, fill: "#A6D6D6" }); + dnInData.push({ label: "식수대", val: drwtUswtr, fill: "#8E7DBE" }); + dnInData.push({ label: "희석용수", val: dltnUswtr, fill: "#FF8383" }); + dnInData.push({ label: "중수도", val: ithlRswtr, fill: "#FFF574" }); + dnInData.push({ label: "기타", val: ithlEtcUswtr, fill: "#A1D6CB" }); // 장외용수 도넛 - dnOutData.push({ label: "조경용수", val: lndwtr, fill: "yellow" }); - dnOutData.push({ label: "친수용수", val: hydUswtr, fill: "black" }); - dnOutData.push({ label: "지하수충전", val: ugwtChr, fill: "yellow" }); - dnOutData.push({ label: "농업용수", val: farmUswtr, fill: "black" }); - dnOutData.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "yellow" }); - dnOutData.push({ label: "공업용수", val: indstUswtr, fill: "black" }); + dnOutData.push({ label: "청소화장실용수", val: cleanToilUswtr, fill: "#A19AD3" }); + dnOutData.push({ label: "세척살수용수", val: resn, fill: "#B1F0F7" }); + dnOutData.push({ label: "조경용수", val: lndwtr, fill: "#81BFDA" }); + dnOutData.push({ label: "친수용수", val: hydUswtr, fill: "#F5F0CD" }); + dnOutData.push({ label: "지하수충전", val: ugwtChr, fill: "#FADA7A" }); + dnOutData.push({ label: "농업용수", val: farmUswtr, fill: "#A6AEBF" }); + dnOutData.push({ label: "하천등유지용수", val: rvrKeepUswtr, fill: "#C5D3E8" }); + dnOutData.push({ label: "공업용수", val: indstUswtr, fill: "#D0E8C5" }); // 라인차트 데이터 setInLineData({ @@ -308,17 +314,17 @@ export default function PbnstPrfmncSttn() { chartData4: drwtUswtrData, chartData5: dltnUswtrData, chartData6: ithlRswtrData, - chartData7: ithlEtcUswtrData, - chartData8: cleanToilUswtrData, - chartData9: resnData + chartData7: ithlEtcUswtrData }); setOutLineData({ - chartData1: lndwtrData, - chartData2: hydUswtrData, - chartData3: ugwtChrData, - chartData4: farmUswtrData, - chartData5: rvrKeepUswtrData, - chartData6: indstUswtrData, + chartData1: cleanToilUswtrData, + chartData2: resnData, + chartData3: lndwtrData, + chartData4: hydUswtrData, + chartData5: ugwtChrData, + chartData6: farmUswtrData, + chartData7: rvrKeepUswtrData, + chartData8: indstUswtrData, }); setBarData(barData); setChartData(dnData); @@ -401,15 +407,7 @@ export default function PbnstPrfmncSttn() { chartData7: { label: '기타', color: "blue", - }, - chartData8: { - label: '청소화장실용수', - color: "magenta", - }, - chartData9: { - label: '세척살수용수', - color: "skyblue", - }, + } } satisfies ChartConfig; setLnInChartConfig(newChartConfig); } @@ -420,26 +418,34 @@ export default function PbnstPrfmncSttn() { if (outLineData?.chartData1) { const newChartConfig = { chartData1: { - label: '조경용수', + label: '청소화장실용수', color: "red", }, chartData2: { + label: '세척살수용수', + color: "pink", + }, + chartData3: { + label: '조경용수', + color: "gray", + }, + chartData4: { label: '친수용수', color: "orange", }, - chartData3: { + chartData5: { label: '지하수충전', color: "yellow", }, - chartData4: { + chartData6: { label: '농업용수', color: "green", }, - chartData5: { + chartData7: { label: '하천등유지용수', color: "blue", }, - chartData6: { + chartData8: { label: '공업용수', color: "purple", }, @@ -466,7 +472,7 @@ export default function PbnstPrfmncSttn() { useEffect(() => { setbdstTable(prfmncRes); - if(prfmncRes?.[0]?.pbnstRuseFcltPk) setChartId(prfmncRes[0].pbnstRuseFcltPk) + if (prfmncRes?.[0]?.pbnstRuseFcltPk) setChartId(prfmncRes[0].pbnstRuseFcltPk) }, [prfmncRes]); @@ -608,7 +614,7 @@ export default function PbnstPrfmncSttn() { <h1 className="">장내·외 월별 하수재이용실적 현황</h1> <div className="flex"> <div className="w-1/3"> - <MainChart chartConfig={barChartConfig} chartData={barData} chartTitle={"월별 장내·외 용수 총 실적"} chartForm={CHART_FORM.DOUBLEBAR} /> + <MainChart chartConfig={barChartConfig} chartData={barData} chartTitle={"월별 장내·외 용수 총 실적"} chartForm={CHART_FORM.DOUBLEBAR} showLegend={true} /> </div> <div className="w-1/3"> <MainChart chartConfig={lnInChartConfig} chartData={inLineData} chartTitle={"월별 장내 용수 실적"} chartForm={CHART_FORM.LINE} /> @@ -619,25 +625,44 @@ export default function PbnstPrfmncSttn() { </div> <h1 className="">하수재이용실적 현황</h1> <ListDiv className="col-span-8 pr-6 "> - <ScrollArea className="h-[60vh]"> - <div className="card-header"> + {/* <ScrollArea className="h-[60vh]"> */} + {/* <div className="card-header"> <h3 className="text-2xl font-medium text-label"> {"하수처리수재이용 실적(m3) 등록결과"} </h3> - </div> + </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> + <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> + + {/* <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> + + <Table variant="secondary"> + <TableHeader className="font-bold"> + {headMakeprfmnc(monthHeadList, [])} + </TableHeader> + <TableBody> + {InputTableBody(prfData ?? [], monthHeadList, setPrfData)} + </TableBody> + </Table> + </ScrollArea> + </ListDiv> */} + {/* </ScrollArea> */} </ListDiv> </ListDiv> diff --git a/apps/wr/app/stats/rainStat/page.tsx b/apps/wr/app/stats/rainStat/page.tsx index 08990028f..45d32d3a4 100644 --- a/apps/wr/app/stats/rainStat/page.tsx +++ b/apps/wr/app/stats/rainStat/page.tsx @@ -210,8 +210,9 @@ export default function RainStat() { ...item, ctpvCd: item.cityText }; - }) + }).filter((item: RainStatProps) => item.cityText !== '없음') : []; + setBdstTable(tbData); }, [code, rainStatList]); diff --git a/apps/wr/app/stats/rswtStat/page.tsx b/apps/wr/app/stats/rswtStat/page.tsx index c0741433c..997e95ca5 100644 --- a/apps/wr/app/stats/rswtStat/page.tsx +++ b/apps/wr/app/stats/rswtStat/page.tsx @@ -46,7 +46,7 @@ export default function RswtStat() { ...item, ctpvCd: item.cityText }; - }) + }).filter((item: RswtStatProps) => item.cityText !== '없음') : []; setRswtTable(tbData); }, [code, rswtStatList]) diff --git a/apps/wr/app/store/common/code/basicCode.tsx b/apps/wr/app/store/common/code/basicCode.tsx new file mode 100644 index 000000000..56c59b502 --- /dev/null +++ b/apps/wr/app/store/common/code/basicCode.tsx @@ -0,0 +1,204 @@ +import type { SelectDataType } from "@common/business_components"; + +export const yn: SelectDataType[] = [ + { val: "Y", text: "Y" }, + { val: "N", text: "N" }, +]; +export const bdstFilterYn: SelectDataType[] = [ + { val: "Y", text: "Y" }, + { val: "N", text: "N" }, + { val: "D", text: "확인불가" }, + { val: "F", text: "미가동" }, +]; +// 최우수(그린1)/우수(그린2), 우량(그린3), 일반(그린4), 확인불가 체크 +export const bdstGrntchClass: SelectDataType[] = [ + { val: "최우수(그린1)", text: "최우수(그린1)" }, + { val: "우수(그린2)", text: "우수(그린2)" }, + { val: "우량(그린3)", text: "우량(그린3)" }, + { val: "일반(그린4)", text: "일반(그린4)" }, + { val: "확인불가", text: "확인불가" }, +]; +export const bdstIncenYn: SelectDataType[] = [ + { val: "Y", text: "Y" }, + { val: "N", text: "N" }, + { val: "D", text: "확인불가" }, +]; +//처리장연계/자체+연계/자체처리 +export const erctUswtrPrcsMeth: SelectDataType[] = [ + { val: "처리장연계", text: "처리장연계" }, + { val: "자체+연계", text: "자체+연계" }, + { val: "자체처리", text: "자체처리" }, +]; + +export const rusePrcsMthdCd: SelectDataType[] = [ + { val: "물리학적처리", text: "물리학적처리" }, + { val: "생물학적처리", text: "생물학적처리" }, +]; + +export const ruseSeCd: SelectDataType[] = [ + { val: "신규", text: "신규" }, + { val: "증설", text: "증설" }, + { val: "관로", text: "관로" }, + { val: "감량", text: "감량" }, + { val: "개량", text: "개량" }, +]; + +export const ruseBizMthdCd: SelectDataType[] = [ + { val: "재정", text: "재정" }, + { val: "민간투자", text: "민간투자" }, + { val: "자체", text: "자체" }, +]; + +//공통 건축물 용도 +export const bdstUsgCd: SelectDataType[] = [ + { val: "-1", text: "확인불가" }, + { val: "1", text: "운동장" }, + { val: "2", text: "체육관" }, + { val: "3", text: "공공업무시설" }, + { val: "4", text: "공공기관의청사" }, + { val: "5", text: "아파트" }, + { val: "6", text: "연립주택" }, + { val: "7", text: "다세대주택" }, + { val: "8", text: "기숙사" }, + { val: "9", text: "초등학교" }, + { val: "10", text: "중학교" }, + { val: "11", text: "고등학교" }, + { val: "12", text: "전문대학" }, + { val: "13", text: "대학및대학교" }, + { val: "14", text: "골프장" }, + { val: "15", text: "대규모점포" }, + { val: "16", text: "기타" }, +]; + +export const rswtBdstType: SelectDataType[] = [ + { text: "숙박업", val: "17" }, + { text: "목욕장업", val: "18" }, + { text: "공장", val: "19" }, + { text: "발전시설", val: "20" }, + { text: "관광단지개발사업", val: "21" }, + { text: "도시개발사업", val: "22" }, + { text: "산업단지개발사업", val: "23" }, + { text: "택지개발사업", val: "24" }, + { text: "대규모점포(대형마트)", val: "25" }, + { text: "대규모점포(전문점)", val: "26" }, + { text: "대규모점포(백화점)", val: "27" }, + { text: "대규모점포(쇼핑센터)", val: "28" }, + { text: "대규모점포(복합쇼핑몰)", val: "29" }, + { text: "물류시설", val: "30" }, + { text: "운수시설(여객자동차터미널)", val: "31" }, + { text: "운수시설(철도시설)", val: "32" }, + { text: "운수시설(공항시설)", val: "33" }, + { text: "운수시설(항만시설)", val: "34" }, + { text: "업무시설(공공업무시설)", val: "35" }, + { text: "업무시설(금융업소)", val: "36" }, + { text: "업무시설(사무소)", val: "37" }, + { text: "업무시설(결혼상담소 등 소개업소)", val: "38" }, + { text: "업무시설(출판사)", val: "39" }, + { text: "업무시설(신문사)", val: "40" }, + { text: "업무시설(오피스텔)", val: "41" }, + { text: "교정시설", val: "42" }, + { text: "방송국", val: "43" }, + { text: "전신전화국", val: "44" }, + { text: "기타", val: "45" }, +]; + +export const orgwtKndCd = [ + { text: "오수(변기 오수 제외)", val: "1" }, + { text: "오수", val: "2" }, + { text: "폐수", val: "3" }, + { text: "오수 및 폐수", val: "4" }, + { text: "기타", val: "5" }, +]; + +export const chBuCd = [ + { val: "증축", text: "증축" }, + { val: "개축", text: "개축" }, + { val: "재축", text: "재축" }, +]; + +export const dmdUszCd = [ + { val: "조경용수", text: "조경용수" }, + { val: "청소화장실용수", text: "청소화장실용수" }, + { val: "농업용수", text: "농업용수" }, + { val: "공업용수", text: "공업용수" }, + { val: "친수용수", text: "친수용수" }, + { val: "기타", text: "기타" }, +]; + +export const ruseMngMtd = [ + { val: "자체운영", text: "자체운영" }, + { val: "공기업", text: "공기업" }, + { val: "민간(대행)", text: "민간(대행)" }, +]; + +export const pplQulity = [ + { val: "도복장강관", text: "도복장강관" }, + { val: "PE관", text: "PE관" }, + { val: "스테인레스관", text: "스테인레스관" }, + { val: "덕타일주철관", text: "덕타일주철관" }, + { val: "유리섬유복합관", text: "유리섬유복합관" }, + { val: "기타", text: "기타" }, +]; +export const pplLenght = [ + { val: "D100", text: "D100" }, + { val: "D200", text: "D200" }, + { val: "D250", text: "D250" }, + { val: "D300", text: "D300" }, + { val: "D350", text: "D350" }, + { val: "D400", text: "D400" }, + { val: "D450", text: "D450" }, + { val: "D500", text: "D500" }, + { val: "D700", text: "D700" }, + { val: "D900", text: "D900" }, + { val: "D1000​", text: "D1000​" }, +]; + +export const rainFcltCd = [ + { val: "지붕", text: "지붕" }, + { val: "건물옥상", text: "건물옥상" }, + { val: "골프장 연못", text: "골프장 연못" }, + { val: "운동장 바닥", text: "운동장 바닥" }, + { val: "운동장 지붕", text: "운동장 지붕" }, + { val: "주차장", text: "주차장" }, + { val: "태양광패널", text: "태양광패널" }, + { val: "연못", text: "연못" }, + { val: "기타", text: "기타" }, + { val: "확인불가", text: "확인불가" }, +]; + +//물리화학적처리(스크린, 응집, 침전)/생물학적처리(활성슬러지법 등)/소독(염소, UV 등)/기타 +export const rswtPrscCdMthdCd = [ + { + val: "물리화학적처리(스크린,응집,침전)", + text: "물리화학적처리(스크린,응집,침전)", + }, + { + val: "생물학적처리(활성슬러지법 등)", + text: "생물학적처리(활성슬러지법 등)", + }, + { val: "소독(염소, UV 등)", text: "소독(염소, UV 등)" }, + { val: "기타", text: "기타" }, +]; + +export const htdPowerMthdCd= [ + { + val: "석탄화력발전", + text: "석탄화력발전", + }, +]; + +//(공업용수/양식장/농업용수) +export const htdUtztnUsgCd= [ + { + val: "공업용수", + text: "공업용수", + }, + { + val: "양식장", + text: "양식장", + }, + { + val: "농업용수", + text: "농업용수", + }, +]; diff --git a/apps/wr/app/store/common/code/index.ts b/apps/wr/app/store/common/code/index.ts index e7fca8180..b3cf48603 100644 --- a/apps/wr/app/store/common/code/index.ts +++ b/apps/wr/app/store/common/code/index.ts @@ -1,184 +1,13 @@ import { getFetch } from "@/components/lib/api"; import type { EnvPartSelectDataType, SelectDataType } from "@common/business_components/ui"; - - - +import { bdstFilterYn, bdstGrntchClass, bdstIncenYn, bdstUsgCd, chBuCd, dmdUszCd, erctUswtrPrcsMeth, htdPowerMthdCd, htdUtztnUsgCd, orgwtKndCd, pplLenght, pplQulity, rainFcltCd, rswtPrscCdMthdCd, ruseBizMthdCd, ruseMngMtd, rusePrcsMthdCd, ruseSeCd, yn } from "./basicCode"; export const btnClass = "p-4 m-2 text-white rounded-sm text-lg font-medium " -const yn: SelectDataType[] = [ - { val: 'Y', text: 'Y' }, - { val: 'N', text: 'N' }, -] - -//공통 건축물 용도 -const bdstUsgCd: SelectDataType[] = [ - { val: '-1', text: "확인불가" }, - { val: '1', text: "운동장" }, - { val: '2', text: "체육관" }, - { val: '3', text: "공공업무시설" }, - { val: '4', text: "공공기관의청사" }, - { val: '5', text: "아파트" }, - { val: '6', text: "연립주택" }, - { val: '7', text: "다세대주택" }, - { val: '8', text: "기숙사" }, - { val: '9', text: "초등학교" }, - { val: '10', text: "중학교" }, - { val: '11', text: "고등학교" }, - { val: '12', text: "전문대학" }, - { val: '13', text: "대학및대학교" }, - { val: '14', text: "골프장" }, - { val: '15', text: "대규모점포" }, - { val: '16', text: "기타" }, -]; -//빗물 집수면 종류 용도 -// const rainFcltCd = [ -// { val: "1", text: "지붕" }, -// { val: "2", text: "건물옥상" }, -// { val: "3", text: "골프장 연못" }, -// { val: "4", text: "운동장 바닥" }, -// { val: "5", text: "운동장 지붕" }, -// { val: "6", text: "주차장" }, -// { val: "7", text: "태양광패널" }, -// { val: "8", text: "연못" }, -// { val: "9", text: "기타" }, -// { val: "10", text: "확인불가" }, -// ]; -// 중수도 필수 원수종류 -const rswtBdstType: SelectDataType[] = [ - { text: "숙박업", val: "17" }, - { text: "목욕장업", val: "18" }, - { text: "공장", val: "19" }, - { text: "발전시설", val: "20" }, - { text: "관광단지개발사업", val: "21" }, - { text: "도시개발사업", val: "22" }, - { text: "산업단지개발사업", val: "23" }, - { text: "택지개발사업", val: "24" }, - { text: "대규모점포(대형마트)", val: "25" }, - { text: "대규모점포(전문점)", val: "26" }, - { text: "대규모점포(백화점)", val: "27" }, - { text: "대규모점포(쇼핑센터)", val: "28" }, - { text: "대규모점포(복합쇼핑몰)", val: "29" }, - { text: "물류시설", val: "30" }, - { text: "운수시설(여객자동차터미널)", val: "31" }, - { text: "운수시설(철도시설)", val: "32" }, - { text: "운수시설(공항시설)", val: "33" }, - { text: "운수시설(항만시설)", val: "34" }, - { text: "업무시설(공공업무시설)", val: "35" }, - { text: "업무시설(금융업소)", val: "36" }, - { text: "업무시설(사무소)", val: "37" }, - { text: "업무시설(결혼상담소 등 소개업소)", val: "38" }, - { text: "업무시설(출판사)", val: "39" }, - { text: "업무시설(신문사)", val: "40" }, - { text: "업무시설(오피스텔)", val: "41" }, - { text: "교정시설", val: "42" }, - { text: "방송국", val: "43" }, - { text: "전신전화국", val: "44" }, - { text: "기타", val: "45" } -]; - -const orgwtKndCd = [ - { text: "오수(변기 오수 제외)", val: "1" }, - { text: "오수", val: "2" }, - { text: "폐수", val: "3" }, - { text: "오수 및 폐수", val: "4" }, - { text: "기타", val: "5" }, -]; - -const chBuCd = [ - { val: "증축", text: "증축" }, - { val: "개축", text: "개축" }, - { val: "재축", text: "재축" }, -] - - - -const dmdUszCd = [ - { val: "조경용수", text: "조경용수" }, - { val: "청소화장실용수", text: "청소화장실용수" }, - { val: "농업용수", text: "농업용수" }, - { val: "공업용수", text: "공업용수" }, - { val: "친수용수", text: "친수용수" }, - { val: "기타", text: "기타" }, - -] - -const ruseMngMtd = [ - { val: "자체운영", text: "자체운영" }, - { val: "공기업", text: "공기업" }, - { val: "민간(대행)", text: "민간(대행)" }, -] - - -const pplQulity = [ - { val: "도복장강관", text: "도복장강관" }, - { val: "PE관", text: "PE관" }, - { val: "스테인레스관", text: "스테인레스관" }, - { val: "덕타일주철관", text: "덕타일주철관" }, - { val: "유리섬유복합관", text: "유리섬유복합관" }, - { val: "기타", text: "기타" }, - -] -const pplLenght = [ - { val: "D100", text: "D100" }, - { val: "D200", text: "D200" }, - { val: "D250", text: "D250" }, - { val: "D300", text: "D300" }, - { val: "D350", text: "D350" }, - { val: "D400", text: "D400" }, - { val: "D450", text: "D450" }, - { val: "D500", text: "D500" }, - { val: "D700", text: "D700" }, - { val: "D900", text: "D900" }, - { val: "D1000​", text: "D1000​" }, - -] export const buildingType = { RST:"RST", RNB:"RNB", HTB:"HTB" } - -const rainFcltCd = [ - { val: "지붕", text: "지붕" }, - { val: "건물옥상", text: "건물옥상" }, - { val: "골프장 연못", text: "골프장 연못" }, - { val: "운동장 바닥", text: "운동장 바닥" }, - { val: "운동장 지붕", text: "운동장 지붕" }, - { val: "주차장", text: "주차장" }, - { val: "태양광패널", text: "태양광패널" }, - { val: "연못", text: "연못" }, - { val: "기타", text: "기타" }, - { val: "확인불가", text: "확인불가" }, -]; - -export type comCodeState = { - code: { - isInit: boolean; - chBuCd: SelectDataType[] - rainFcltCd: SelectDataType[]; - bdstUsgCd: SelectDataType[]; - cityCd: SelectDataType[]; - sggCd: SelectDataType[]; - rvbsnCd: SelectDataType[]; - mdmbsnCd: SelectDataType[]; - lgbsnCd: SelectDataType[]; - lclgvrnmntCd: EnvPartSelectDataType[]; - orgwtKndCd: SelectDataType[]; - yn: SelectDataType[]; - pplQulity: SelectDataType[]; - pplLenght: SelectDataType[]; - dmdUszCd:SelectDataType[] - buildingType: { - RST:string, - RNB:string, - HTB:string - } - ruseMngMtd:SelectDataType[] - }; -}; - -export type comCodeType = comCodeState & comCodeActions; - export const getSggCd = async (set: any) => { const sgg = await getFetch("common", "/addr/sgg"); /* const sggCd = sgg.map((item: any) => ({ @@ -187,7 +16,7 @@ export const getSggCd = async (set: any) => { })); */ const sggCd = [ - { text: "전체", val: '0' }, + // { text: "전체", val: '0' }, ...sgg .map((item: any) => ({ text: item.gugun || '', @@ -304,6 +133,47 @@ export const getEnvPartCd = async (set: any) => { + +export type comCodeState = { + code: { + isInit: boolean; + chBuCd: SelectDataType[] + rainFcltCd: SelectDataType[]; + bdstUsgCd: SelectDataType[]; + cityCd: SelectDataType[]; + sggCd: SelectDataType[]; + rvbsnCd: SelectDataType[]; + mdmbsnCd: SelectDataType[]; + lgbsnCd: SelectDataType[]; + lclgvrnmntCd: EnvPartSelectDataType[]; + orgwtKndCd: SelectDataType[]; + yn: SelectDataType[]; + pplQulity: SelectDataType[]; + pplLenght: SelectDataType[]; + dmdUszCd:SelectDataType[] + buildingType: { + RST:string, + RNB:string, + HTB:string + } + ruseMngMtd:SelectDataType[] + ruseSeCd: SelectDataType[]; + ruseBizMthdCd: SelectDataType[]; + rusePrcsMthdCd: SelectDataType[]; + erctUswtrPrcsMeth: SelectDataType[]; + bdstFilterYn: SelectDataType[]; + bdstIncenYn: SelectDataType[]; + bdstGrntchClass:SelectDataType[]; + rswtPrscCdMthdCd:SelectDataType[]; + htdPowerMthdCd:SelectDataType[] + htdUtztnUsgCd:SelectDataType[] + }; +}; + +export type comCodeType = comCodeState & comCodeActions; + + + export const comCodeStateExport: comCodeState = { code: { isInit: false, @@ -323,6 +193,16 @@ export const comCodeStateExport: comCodeState = { dmdUszCd:dmdUszCd, buildingType:buildingType, ruseMngMtd:ruseMngMtd, + ruseSeCd:ruseSeCd, + ruseBizMthdCd: ruseBizMthdCd, + rusePrcsMthdCd:rusePrcsMthdCd, + erctUswtrPrcsMeth:erctUswtrPrcsMeth, + bdstFilterYn:bdstFilterYn, + bdstIncenYn:bdstIncenYn, + bdstGrntchClass:bdstGrntchClass, + rswtPrscCdMthdCd:rswtPrscCdMthdCd, + htdPowerMthdCd:htdPowerMthdCd, + htdUtztnUsgCd:htdUtztnUsgCd, }, }; @@ -330,9 +210,6 @@ export type comCodeActions = { codeInit: () => void; }; - - - export const codeActionExport: (set: any) => comCodeActions = (set: any) => { return { codeInit: async () => { diff --git a/apps/wr/app/store/common/searchCode/index.ts b/apps/wr/app/store/common/searchCode/index.ts index 8f05eca09..656bc33d9 100644 --- a/apps/wr/app/store/common/searchCode/index.ts +++ b/apps/wr/app/store/common/searchCode/index.ts @@ -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,9 +67,10 @@ 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( "common", `/prcsFclt/info/1/11/${searchNm}` @@ -40,6 +83,7 @@ export const searchActions: (set: any) => comFcltActions = (set: any) => { addr: item.addr, fctCpc: item.fctCpc, })); + set((state: comFcltState) => ({ ...state, searchFclts: searchFclts, @@ -51,10 +95,13 @@ export const searchActions: (set: any) => comFcltActions = (set: any) => { `/reuseFc/request/${searchNm}/${cnt}` ); - console.log(coList); - set((state: comFcltState) => { - return (state.demandCompList = [...coList]); - }); + + if(coList&&coList.length>0){ + + set((state: comFcltState) => { + return (state.demandCompList = [...coList]); + }); + } }, regDemandCompany: async (data: DemandCompType) => { try { @@ -69,9 +116,10 @@ export const searchActions: (set: any) => comFcltActions = (set: any) => { } else { message = "fail"; } + + set((state: comFcltState) => { - state.dmdIsSucees = message; - return state; + return state.dmdIsSucees = message; }); } catch (err) { console.log(err); @@ -89,8 +137,7 @@ export const searchActions: (set: any) => comFcltActions = (set: any) => { } set((state: comFcltState) => { - state.dmdIsSucees = message; - return state; + return state.dmdIsSucees = message; }); } catch (err) { console.log(err); diff --git a/apps/wr/app/store/map/index.tsx b/apps/wr/app/store/map/index.tsx new file mode 100644 index 000000000..6a1e76c9b --- /dev/null +++ b/apps/wr/app/store/map/index.tsx @@ -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: [] + })); + } + } + }; +}; \ No newline at end of file diff --git a/apps/wr/app/store/matDmd/coMatRvw/headList.tsx b/apps/wr/app/store/matDmd/coMatRvw/headList.tsx index 7b43e4b00..6d9bf8862 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/headList.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/headList.tsx @@ -29,12 +29,12 @@ export const coRvwHeadListExport: CommonTableProps[]= [ visible: true, }, { - id: "regDt", //바꾸기 + id: "dmndTmng", title: "신청일자", visible: true, }, { - id: "state", //바꾸기 + id: "splyFcltFlg", title: "진행상태", visible: true, }, diff --git a/apps/wr/app/store/matDmd/coMatRvw/index.tsx b/apps/wr/app/store/matDmd/coMatRvw/index.tsx index 89836957b..21239fce3 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/index.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/index.tsx @@ -1,13 +1,18 @@ -import { constCostHeadListExport, coRvwHeadListExport, coRvwNodeListExport, rateFeeHeadListExport } from "./headList"; +import { + constCostHeadListExport, + coRvwHeadListExport, + coRvwNodeListExport, + rateFeeHeadListExport, +} from "./headList"; import type { CommonTableProps } from "../../common/type/header"; import type { DedMatUpdateType, DmdCoReqType } from "./type"; -import { fetchType, getFetch, putFectch } from "@/components/lib/api"; -import toast from "react-hot-toast/headless"; +import { fetchType, getFetch, getFetchWithCount, putFectch } from "@/components/lib/api"; +import toast from "react-hot-toast"; const coRvwHeadList = coRvwHeadListExport; const coRvwNodeList = coRvwNodeListExport; -const rateFeeHeadList =rateFeeHeadListExport; -const constCostHeadList=constCostHeadListExport +const rateFeeHeadList = rateFeeHeadListExport; +const constCostHeadList = constCostHeadListExport; export type coRvwState = { coRvw: { isInit: boolean; @@ -16,6 +21,7 @@ export type coRvwState = { coRvwNodeList: CommonTableProps[]; rateFeeHeadList: CommonTableProps[]; constCostHeadList: CommonTableProps[]; + coMatRvwCount:number; }; }; @@ -25,8 +31,9 @@ export const coRvwStateExport: coRvwState = { coMatRvwRes: [], coRvwHeadList: coRvwHeadList, coRvwNodeList: coRvwNodeList, - rateFeeHeadList:rateFeeHeadList, - constCostHeadList:constCostHeadList + rateFeeHeadList: rateFeeHeadList, + constCostHeadList: constCostHeadList, + coMatRvwCount:1 }, }; @@ -37,7 +44,7 @@ type coRvwActions = { pageNo: number, pageCnt: number, startYear: string, - endYear: string, + endYear: string ) => void; coMatRvwUpdate: ( splyCd: string, @@ -50,7 +57,7 @@ type coRvwActions = { startYear: string, endYear: string, sggCd: string, - dmdCoNm: string, + dmdCoNm: string ) => void; coMatRvwInqua: ( pageNo: number, @@ -65,10 +72,10 @@ const coMatInit = async ( pageNo: number, pageCnt: number, startYear: string, - endYear: string, + endYear: string ) => { - const url = `/reuseFc/request/infoList/sub/${pageNo}/${pageCnt}/${startYear}/${endYear}`; - const coMatRvwRes = await getFetch(fetchType.WR, url); + const url = `/reuseFc/request/infoList/Sub/${pageNo}/${pageCnt}/${startYear}/${endYear}`; + const coMatRvwRes = await getFetchWithCount(fetchType.WR, url); return coMatRvwRes; }; @@ -79,7 +86,7 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { pageNo: number, pageCnt: number, startYear: string, - endYear: string, + endYear: string ) => { const coMatRvwRes = await coMatInit(pageNo, pageCnt, startYear, endYear); @@ -88,7 +95,8 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { coRvw: { ...state.coRvw, init: false, - coMatRvwRes: coMatRvwRes, + coMatRvwRes: coMatRvwRes.data, + coMatRvwCount:coMatRvwRes.count }, })); }, @@ -98,25 +106,37 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { data: DedMatUpdateType ) => { try { - const isUpdate = await putFectch( + const response = await putFectch( `/reuseFc/request/cst/${splyCd}/${dmdCd}`, data ); + if (response.status === 200) { - const coMatRvwRes = coMatInit( - 1, - 10, - new Date().getFullYear().toString(), - new Date().getFullYear().toString(), - ); - return set((state: coRvwState) => ({ - ...state, - coRvw: { - ...state.coRvw, - init: false, - coMatRvwRes: coMatRvwRes, - }, - })); + if(data.flg ==='Sub'){ + + toast.success("매칭 검토로 저장되었습니다."); + }else{ + toast.success("매칭 검토완료로 저장되었습니다."); + } + + const coMatRvwRes = await coMatInit( + 1, + 10, + new Date().getFullYear().toString(), + new Date().getFullYear().toString() + ); + return set((state: coRvwState) => ({ + ...state, + coRvw: { + ...state.coRvw, + coMatRvwRes: coMatRvwRes.data, + coMatRvwCount:coMatRvwRes.count, + init: false, + }, + })); + } else { + toast.error("매칭 검토 저장에 실패했습니다."); + } } catch (e) { toast.error("매칭 검토 저장 오류입니다."); } @@ -127,9 +147,9 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { startYear: string, endYear: string, sggCd: string, - dmdCoNm: string, + dmdCoNm: string ) => { - let url = `/reuseFc/request/infoList/sub/${pageNo}/${pageCnt}/${startYear}/${endYear}`; + let url = `/reuseFc/request/infoList/Sub/${pageNo}/${pageCnt}/${startYear}/${endYear}`; if (sggCd.length > 0) { url += `/${sggCd}`; @@ -142,14 +162,15 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { url += `/0`; } - console.log(' url ', url) - const coMatRvwRes = await getFetch(fetchType.WR, url); + console.log(" url ", url); + const coMatRvwRes = await getFetchWithCount(fetchType.WR, url); return set((state: coRvwState) => ({ ...state, coRvw: { ...state.coRvw, - coMatRvwRes: coMatRvwRes, + coMatRvwRes: coMatRvwRes.data, + coMatRvwCount:coMatRvwRes.count, }, })); }, @@ -161,23 +182,24 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { sggCd: string ) => { try { - let url = `/reuseFc/request/infoList/sub/${pageNo}/${pageCnt}/${startYear}/${endYear}`; + let url = `/reuseFc/request/infoList/Sub/${pageNo}/${pageCnt}/${startYear}/${endYear}`; if (sggCd.length > 0) { url += `/${sggCd}/0`; - }else if(sggCd ==='0'){ + } else if (sggCd === "0") { url += `/0/0`; } - const coMatRvwRes = await getFetch(fetchType.WR, url); + const coMatRvwRes = await getFetchWithCount(fetchType.WR, url); return set((state: coRvwState) => ({ ...state, coRvw: { ...state.coRvw, - coMatRvwRes: coMatRvwRes, + coMatRvwRes: coMatRvwRes.data, + coMatRvwCount:coMatRvwRes.count, }, })); } catch (e) { console.log(" error ", e); } - } + }, }; }; diff --git a/apps/wr/app/store/matDmd/coMatRvw/type.tsx b/apps/wr/app/store/matDmd/coMatRvw/type.tsx index 9f146c0fb..2121c49fe 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/type.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/type.tsx @@ -1,173 +1,148 @@ import { z } from "zod"; - const DemandInfoSchema = z.object({ - dmdInfoSn: z.number(), - dmdInfoFk: z.string(), - dmndTmng: z.coerce.date(), - dmndQty: z.number(), - wntUntprc: z.number(), - grssClfg: z.number(), - combResdch: z.number(), - trbt: z.number(), - blgcOxygDmndQnt: z.number(), - smll: z.number(), - crmty: z.number().nullish(), - tn: z.number(), - tph: z.number(), - ph: z.number(), - clIonQnt: z.number(), - cdtnv: z.number(), - rmrk: z.string(), - useUsgCd: z.string(), - mtchnCmptnYmd: z.string().nullish(), - rvwCmptnYmd: z.string().nullish() - }); - - const DemandMatchingSchema = z.object({ - dmdInfoFk: z.string().nullish(), - splyFcltFk: z.string().nullish(), - splyFcltFlg: z.string().nullish(), - pbnstRuseFcltBizFk: z.number().nullish(), - splyBgngDay: z.coerce.date().nullish(), - splyEndDay: z.coerce.date().nullish(), - splyCpct: z.number().nullish(), - splyFmYn: z.string().nullish(), - useUsgCd: z.string().nullish(), - dmdInfoSn: z.number().nullish(), - splyCndtList:z.array(z.string().nullish() ).nullish() - }); - - const DemandCoSchema = z.object({ - dmdInfoPk: z.string(), - dmdCoNm: z.string(), - ctpvCd: z.string(), - sggCd: z.string(), - daddr: z.string(), - splyBgngDay: z.coerce.date(), - splyEndDay: z.coerce.date(), - splyCpct: z.number(), - splyFmYn: z.string().nullish(), - telno: z.string(), - useUsgCd: z.string().nullish(), - regDt: z.coerce.date().nullish(), - rgtr: z.string().nullish(), - mdfcnDt: z.coerce.date().nullish(), - mdfr: z.string().nullish(), - dmdAplyPswd: z.string(), - demandInfo: z.array(DemandInfoSchema.nullish()).nullish(), // 안쓰는 거임 - demandMatchings: z.array(DemandMatchingSchema.nullish()).nullish()// 안쓰는 거임 - }); - - const DemandCstSchema = z.object({ - dmdInfoSn: z.number(), - usgPrcsProcs: z.string().nullish(), - cstrnCst: z.number(), - fcltSbsdCstrnCst: z.number(), - prcsFcltCpctCstrnCst: z.number() - }); - - export const DmdCoReqSchema = z.object({ - cityText:z.string().nullish(), - sggText:z.string().nullish(), - dmdInfoSn: z.number(), - dmdInfoFk: z.string(), - dmndTmng: z.coerce.date(), - dmndQty: z.number(), - wntUntprc: z.number().nullish(), - grssClfg: z.number().nullish(), - combResdch: z.number().nullish(), - trbt: z.number().nullish(), - blgcOxygDmndQnt: z.number(), - smll: z.number().nullish(), - crmty: z.number().nullish(), - tn: z.number().nullish(), - tph: z.number().nullish(), - ph: z.number().nullish(), - clIonQnt: z.number().nullish(), - cdtnv: z.number(), - rmrk: z.string().nullish(), - useUsgCd: z.string().nullish(), - mtchnCmptnYmd: z.string().nullish(), - rvwCmptnYmd: z.string().nullish(), - demandCo: DemandCoSchema.nullish(), - demandMatching: DemandMatchingSchema.nullish(), - demandCst: DemandCstSchema, - ruseUtztnFcltNm:z.string().nullish(), - totalConstCost:z.number().nullish(), +const DemandInfoSchema = z.object({ + dmdInfoSn: z.number(), + dmdInfoFk: z.string().nullish(), + dmndTmng: z.coerce.date().nullish(), + dmndQty: z.number().nullish(), + wntUntprc: z.number().nullish(), + grssClfg: z.number().nullish(), + combResdch: z.number().nullish(), + trbt: z.number().nullish(), + blgcOxygDmndQnt: z.number(), + smll: z.number().nullish(), + crmty: z.number().nullish(), + tn: z.number().nullish(), + tph: z.number().nullish(), + ph: z.number(), + clIonQnt: z.number().nullish(), + cdtnv: z.number().nullish(), + rmrk: z.string().nullish(), + useUsgCd: z.string().nullish(), +}); - }); +const DemandMatchingSchema = z.object({ + dmdInfoFk: z.string().nullish(), + splyFcltFk: z.string().nullish(), + splyFcltFlg: z.string().nullish(), + pbnstRuseFcltBizFk: z.number().nullish(), + splyBgngDay: z.coerce.date().nullish(), + splyEndDay: z.coerce.date().nullish(), + splyCpct: z.number().nullish(), + splyFmYn: z.string().nullish(), + useUsgCd: z.string().nullish(), + dmdInfoSn: z.number().nullish(), + splyCndtList: z.array(z.string().nullish()).nullish(), + mtchnCmptnYmd: z.string().nullish(), + rvwCmptnYmd: z.string().nullish(), +}); +const DemandCoSchema = z.object({ + dmdInfoPk: z.string().nullish(), + dmdCoNm: z.string().nullish(), + ctpvCd: z.string().nullish(), + sggCd: z.string().nullish(), + daddr: z.string().nullish(), + splyBgngDay: z.coerce.date().nullish(), + splyEndDay: z.coerce.date().nullish(), + splyCpct: z.number().nullish(), + splyFmYn: z.string().nullish(), + telno: z.string().nullish(), + useUsgCd: z.string().nullish(), + regDt: z.coerce.date().nullish(), + rgtr: z.string().nullish(), + mdfcnDt: z.coerce.date().nullish(), + mdfr: z.string().nullish(), + dmdAplyPswd: z.string().nullish(), + demandInfo: z.array(DemandInfoSchema.nullish()).nullish(), // 안쓰는 거임 + demandMatchings: z.array(DemandMatchingSchema.nullish()).nullish(), // 안쓰는 거임 +}); + +const DemandCstSchema = z.object({ + dmdInfoSn: z.number().nullish(), + usgPrcsProcs: z.string().nullish(), + cstrnCst: z.number().default(0), + fcltSbsdCstrnCst: z.number().default(0), + prcsFcltCpctCstrnCst: z.number().default(0), +}); + +export const DmdCoReqSchema = z.object({ + cityText: z.string().nullish(), + sggText: z.string().nullish(), + dmdInfoSn: z.number().default(0), + dmdInfoFk: z.string().default(''), + dmndTmng: z.coerce.date().nullish(), + dmndQty: z.number().nullish(), // null 또는 undefined 허용 + blgcOxygDmndQnt: z.number().nullish(), + wntUntprc: z.number().nullish(), + grssClfg: z.number().nullish(), + combResdch: z.number().nullish(), + trbt: z.number().nullish(), + smll: z.number().nullish(), + crmty: z.number().nullish(), + tn: z.number().nullish(), + tph: z.number().nullish(), + ph: z.number().nullish(), + clIonQnt: z.number().nullish(), + cdtnv: z.number().nullish(), + rmrk: z.string().nullish(), + useUsgCd: z.string().nullish(), + mtchnCmptnYmd: z.string().nullish(), + rvwCmptnYmd: z.string().nullish(), + demandCo: DemandCoSchema.nullish(), + demandMatching: DemandMatchingSchema.nullish(), + demandCst: DemandCstSchema.nullish(), + ruseUtztnFcltNm: z.string().nullish(), + totalConstCost: z.number().nullish(), +}); export type DmdCoReqType = z.infer<typeof DmdCoReqSchema>; export type DmdMatTableType = { - cityText:string, - sggText:string, - dmdCoNm:string, - daddr:string, - telno:string, - regDt:string, - splyFcltFlg:string -} + cityText: string; + sggText: string; + dmdCoNm: string; + daddr: string; + telno: string; + regDt: string; + splyFcltFlg: string; +}; - -export type DedMatUpdateType = -{ +export type DedMatUpdateType = { dmdInfoSn: number; usgPrcsProcs: string; cstrnCst: number; fcltSbsdCstrnCst: number; prcsFcltCpctCstrnCst: number; rgtr: string; -} - + flg?:string; +}; export type RvwHistTableType = { - cityText:string, - sggText:string, - dmdCoNm:string, - daddr:string, - telno:string, - regDt:string, - rvwCmptnYmd:string, - mtchnCmptnYmd:string, - ruseUtztnFcltNm:string, - dmndQty:string, - useUsgCd:string, - splyFcltFlg:string - completeYn:boolean - dmdInfoSn :string + cityText: string; + sggText: string; + dmdCoNm: string; + daddr: string; + telno: string; + regDt: string; + rvwCmptnYmd: string; + mtchnCmptnYmd: string; + ruseUtztnFcltNm: string; + dmndQty: string; + useUsgCd: string; + splyFcltFlg: string; + completeYn: boolean; + dmdInfoSn: string; +}; -} - -export type RvwHistTableType = { - cityText:string, - sggText:string, - dmdCoNm:string, - daddr:string, - telno:string, - regDt:string, - rvwCmptnYmd:string, - mtchnCmptnYmd:string, - ruseUtztnFcltNm:string, - dmndQty:string, - useUsgCd:string, - splyFcltFlg:string - completeYn:boolean - dmdInfoSn :string - -} - - - -export type conCostFeeType= { - yr: string, - fctCpc: string, - fctCd: string, - stdCost: string -} +export type conCostFeeType = { + yr: string; + fctCpc: string; + fctCd: string; + stdCost: string; +}; export type rateFeeType = { - yr: string, - incExpCd: string, - stdCost: number, - rt: number -} \ No newline at end of file + yr: string; + incExpCd: string; + stdCost: number; + rt: number; +}; diff --git a/apps/wr/app/store/matDmd/coSttn/headList.tsx b/apps/wr/app/store/matDmd/coSttn/headList.tsx index 80fed0d76..62c3002ac 100644 --- a/apps/wr/app/store/matDmd/coSttn/headList.tsx +++ b/apps/wr/app/store/matDmd/coSttn/headList.tsx @@ -44,6 +44,11 @@ export const coSttHeadListExport: z.infer<typeof sttnHeadListType> = [ title: "전화번호", visible: true, }, + { + id: "regDt", + title: "등록일자", + visible: true, + }, ]; export const coSttNodeListExport: CommonTableProps[] = [ diff --git a/apps/wr/app/store/matDmd/coSttn/index.tsx b/apps/wr/app/store/matDmd/coSttn/index.tsx index 0de37bbfc..5e8baf859 100644 --- a/apps/wr/app/store/matDmd/coSttn/index.tsx +++ b/apps/wr/app/store/matDmd/coSttn/index.tsx @@ -2,7 +2,7 @@ import type { CommonTableProps } from "@/app/store/common/type/header"; import { fetchType, getFetch, getFetchWithCount } from "@/components/lib/api"; import { coSttHeadListExport, coSttNodeListExport } from "./headList"; import { excelCoSttnParsing } from "./excel"; -import type { DemandCompType } from "@common/business_components/ui"; +import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; // 테이블 헤드 리스트 const coSttNodeList = coSttNodeListExport; diff --git a/apps/wr/app/store/matDmd/drgtSttn/headList.tsx b/apps/wr/app/store/matDmd/drgtSttn/headList.tsx index 40eae47e3..dcdc655b5 100644 --- a/apps/wr/app/store/matDmd/drgtSttn/headList.tsx +++ b/apps/wr/app/store/matDmd/drgtSttn/headList.tsx @@ -1,5 +1,19 @@ +import { z } from "zod"; import type { CommonTableProps } from "../../common/type/header"; +export const drgtZod = z.object({ + ctpvCd: z.string().default('').nullish(), + sggNm: z.string().default('').nullish(), + normal: z.string().default(''), + attention: z.string().default(''), + caution: z.string().default(''), + warning: z.string().default(''), + ranking: z.string().default(''), + year: z.string().default(''), + month: z.string().default(''), + drgtState: z.string().default('') +}) + export const drgtHeadListExport: CommonTableProps[] = [ { id: "ctpvCd", @@ -10,21 +24,13 @@ export const drgtHeadListExport: CommonTableProps[] = [ colSpan: 1, }, { - id: "sggCd", + id: "sggNm", title: "시군구", visible: true, row: 1, rowSpan: 1, colSpan: 1, }, - { - id: "haengjung", - title: "행정동", - visible: true, - row: 1, - rowSpan: 1, - colSpan: 1, - }, { id: "normal", title: "정상(횟수)", @@ -83,16 +89,9 @@ export const drgtDetailHeadListExport: CommonTableProps[] = [ row: 1, rowSpan: 1, colSpan: 1, - },{ - id: "day", - title: "일", - visible: true, - row: 1, - rowSpan: 1, - colSpan: 1, }, { - id: "state", + id: "drgtState", title: "가뭄상태", visible: true, row: 1, diff --git a/apps/wr/app/store/matDmd/drgtSttn/index.tsx b/apps/wr/app/store/matDmd/drgtSttn/index.tsx index edbbc2f28..4211d2d08 100644 --- a/apps/wr/app/store/matDmd/drgtSttn/index.tsx +++ b/apps/wr/app/store/matDmd/drgtSttn/index.tsx @@ -1,27 +1,171 @@ +import { fetchType, getFetch, postFectch } from "@/components/lib/api"; import type { CommonTableProps } from "../../common/type/header"; -import { drgtHeadListExport } from "./headList"; +import { drgtHeadListExport, drgtZod } from "./headList"; +import type { z } from "zod"; const drgtHeadList = drgtHeadListExport; +export type customDrgt = z.infer<typeof drgtZod>; + +const drgtRes: customDrgt[] = []; + export type drgtState = { - drgt:{ - drgtHeadList: CommonTableProps[]; - } + drgt: { + drgtHeadList: CommonTableProps[]; + drgtRes: customDrgt[]; + drgtMoRes: customDrgt[]; + isInit: boolean; + } } type drgtActions = { + drgtInit: () => void + drgtSearch: (fromDate: string, toDate: string, sido: string[]) => void + drgtModalSearch: (fromDate: string, toDate: string, sgg: string) => void }; export type drgtType = drgtState & drgtActions; export const drgtStateExport: drgtState = { - drgt: { - drgtHeadList: drgtHeadList, - } + drgt: { + drgtHeadList: drgtHeadList, + drgtRes: drgtRes, + drgtMoRes: drgtRes, + isInit: false, + } } +const initFetch = async (fromDate: string, toDate: string, sido:string[]) => { +console.log("sido",sido) + const drgtData = await postFectch( + `/il/getDataCount/${fromDate}/${toDate}`, + sido, + fetchType.WR + ); + + //주의, 관심, 정상 추출 + const statusData = Array.from( + new Set(drgtData.data.map((e) => e.lvngMfgUswtrDrgtInfo)) + ); + + const statusMap = drgtData.data.reduce((acc: any, { sggNm, lvngMfgUswtrDrgtInfo: status, countDrgt }: { sggNm: string; lvngMfgUswtrDrgtInfo: string; countDrgt: number }) => { + if (!acc[sggNm]) { + acc[sggNm] = { sggNm, ...Object.fromEntries(statusData.map((e) => [e, 0])) }; + } + acc[sggNm][status] = Number(countDrgt); + return acc; + }, {}); + const data = Object.values(statusMap); + + const formatStatus = data.map((item) => { + const attention = item['관심'] || 0; + const caution = item['주의'] || 0; + return { + sggNm: item.sggNm, + normal: String(item['정상'] || 0), + caution: String(caution), + attention: String(attention), + warning: String(attention + caution), + }; + }); + + const ranking = Array.from( + new Set(formatStatus.map(e => Number(e.warning))) + ).sort((a, b) => b - a); + + const rankData = ranking.reduce((acc, val, idx) => { + acc[val] = idx + 1; + return acc; + }, {}); + + const drgtRes = formatStatus.map(item => ({ + ...item, + warning: String(item.warning), + ranking: rankData[Number(item.warning)], + })); + + return drgtRes; + +} + + export const drgtActionsExport: (set: any) => drgtActions = ( set: any ) => { - return {}; -}; \ No newline at end of file + return { + drgtInit: async () => { + try { + const today = new Date(); + const year = today.getFullYear(); + const month = String(today.getMonth() + 1).padStart(2, '0'); + const formatted = `${year}${month}`; + const fromDate = formatted; + const toDate = formatted; + + //FIXME: 페이지 처음 로딩될때 데이터 보려고 6월로 박아놓음 이거 fromDate로 고치기 + const drgtRes = await initFetch("202506", toDate, []); + set((state: drgtState) => { + return { + ...state, + drgt: { + ...state.drgt, + drgtRes: drgtRes, + isInit: false, + }, + } + }); + } catch (e) { + console.log(e) + } + }, + drgtSearch: async (fromDate: string, toDate: string, sido: string[]) => { + try { + const drgtRes = await initFetch(fromDate, toDate, sido); + + set((state: drgtState) => { + return { + ...state, + drgt: { + ...state.drgt, + drgtRes: drgtRes, + }, + } + }); + } catch (e) { + console.log(e) + } + }, + drgtModalSearch: async (fromDate: string, toDate: string, sgg: string) => { + try { + const drgtMoData = await getFetch( + fetchType.WR, + `/il/getData/${fromDate}/${toDate}/${sgg}/${sgg}` + ); + + const drgtMoRes = drgtMoData.map((item) => { + const year = item.anlsYmd.slice(0, 4); + const month = item.anlsYmd.slice(4, 6); + const drgtState = item.lvngMfgUswtrDrgtInfo; + const sggNm = item.sggNm; + return { + year: year, + month: month, + drgtState: drgtState, + sggNm: sggNm + } + }); + set((state: drgtState) => { + return { + ...state, + drgt: { + ...state.drgt, + drgtMoRes: drgtMoRes, + }, + } + }); + } catch (e) { + console.log(e) + } + }, + } +} \ No newline at end of file diff --git a/apps/wr/app/store/matDmd/ldrmntCoSttn/excel.tsx b/apps/wr/app/store/matDmd/ldrmntCoSttn/excel.tsx index dc0601127..ad7990cfc 100644 --- a/apps/wr/app/store/matDmd/ldrmntCoSttn/excel.tsx +++ b/apps/wr/app/store/matDmd/ldrmntCoSttn/excel.tsx @@ -46,29 +46,17 @@ export const names: nameMap = { } -export const excelPbnstSttnParsing = (data: any[]): string[][] => { +export const excelLdrmntParsing = (data: any[]): 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 ?? ""), + item.fctryAddr?.split(" ")[0] || "", + item.fctryAddr?.split(" ")[1] || "", + item.dmdCoNm, + item.fctryAddr, + item.fctryMngNo, + item.telno, ]) }) return excelExportData; diff --git a/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx b/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx index d1176528a..6a5f18247 100644 --- a/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx +++ b/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx @@ -2,10 +2,8 @@ import type { CommonTableProps } from "@/app/store/common/type/header"; import { deleteFectch, fetchType, getFetch, postFectch, putFectch } from "@/components/lib/api"; import { ToastFormUi } from "@/components/ui/toast/toastFromUi"; import { z } from "zod"; -import { ldrmntHeadListExport, ldrmntNodeListExport, ldrmntZod } from "./headList"; import { TEST_UUID } from "../../ruseFcltMng/fcltReg/rainReg"; - - +import { ldrmntHeadListExport, ldrmntNodeListExport, ldrmntZod } from "./headList"; // 테이블 헤드 리스트 const ldrmntHeadList = ldrmntHeadListExport; @@ -34,7 +32,7 @@ const excelDownList: ExcelType[] = []; type ldrmntActions = { ldrmntRegData: (data: any[]) => void, ldrmntInit: (con: string) => void - ldrmntSearch: (page: number, sggCd: string, coNm: string) => void; + ldrmntSearch: (page: number, sggCd: string, total: string, coNm: string) => void; ldrmntUpdate: (ldrmntRes: customLdrmnt) => void; ldrmntDelete: () => void; }; @@ -61,6 +59,7 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = ( fetchType.WR, `/reuseFc/request/temp/1/20/${con}` ); + set((state: ldrmntState) => { return { ...state, @@ -68,6 +67,7 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = ( ...state.ldrmnt, ldrmntRes: ldrmntRes, isInit: false, + excelList: excelDownList, }, } }); @@ -101,44 +101,57 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = ( } }, - ldrmntSearch: async (page: number, sggCd: string, coNm: string) => { + ldrmntSearch: async (page: number, total: string, sggCd: string, coNm: string) => { let searchUrl = ""; if (sggCd !== "") { if (coNm !== "") { - searchUrl = `/reuseFc/request/temp/${page}/20/${sggCd}/${coNm}`; + searchUrl = `/reuseFc/request/temp/${page}/${total}/${sggCd}/${coNm}`; } else { - searchUrl = `/reuseFc/request/temp/${page}/20/${sggCd}`; + searchUrl = `/reuseFc/request/temp/${page}/${total}/${sggCd}`; } } - try { const ldrmntRes = await getFetch( fetchType.WR, searchUrl ); - ldrmntRes.forEach((item: customLdrmnt) => { - excelDownList.push({ - ctpvCd: item.ctpvCd, - sggCd: item.sggCd, - dmdCoNm: item.dmdCoNm, - fctryAddr: item.fctryAddr, - fctryMngNo: item.fctryMngNo, - telno: item.telno, + if (Number(total) > 5000) { + let excelDownList: ExcelType[] = []; + ldrmntRes.forEach((item: customLdrmnt) => { + excelDownList.push({ + ctpvCd: item.ctpvCd, + sggCd: item.sggCd, + dmdCoNm: item.dmdCoNm, + fctryAddr: item.fctryAddr, + fctryMngNo: item.fctryMngNo, + telno: item.telno, + }) }) - }) + set((state: ldrmntState) => { + return { + ...state, + ldrmnt: { + ...state.ldrmnt, + excelList: excelDownList, - set((state: ldrmntState) => { - return { - ...state, - ldrmnt: { - ...state.ldrmnt, - ldrmntRes: ldrmntRes, - excelList: excelDownList, - }, - } - }) + }, + }; + }); + return excelDownList; + } else + set((state: ldrmntState) => { + return { + ...state, + ldrmnt: { + ...state.ldrmnt, + ldrmntRes: ldrmntRes, + isInit: false, + }, + }; + }); + return ldrmntRes; } catch (e) { console.log(e) } diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/excel.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/excel.tsx new file mode 100644 index 000000000..bb0c18d6a --- /dev/null +++ b/apps/wr/app/store/matDmd/ruseCaseMng/excel.tsx @@ -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", + "사업종료일": "bizEndDate", + "공급처_시설종류": "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; +}; \ No newline at end of file diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx index 125866601..8c3a232e7 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/headList.tsx @@ -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, diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx index c54b0db1a..5a2ad235e 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx @@ -1,24 +1,124 @@ +import { fetchType, getFetch, getFetchWithCount, postFectch } from "@/components/lib/api"; import type { TableUpperProps } from "../../common/type/header"; import { ruseCaseHeadListExport } from "./headList"; - +import { toast } from "react-hot-toast"; export type ruseCaseState = { - ruseCase: { - ruseHeadList:TableUpperProps[]; - }; + ruseCaseMng: { + ruseHeadList:TableUpperProps[]; + ruseCaseMngList:ruseCaseMngExcelProps[]; + status: number; + count: number; }; -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: (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => Promise<void>; + insertRuseCaseMng: (data: any) => Promise<void>; +}; + +export const fetchSearchRuseCaseMng = async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { + const tailUrl = '/'+pageNo+'/'+pageSize+ ((sigunguCd !== '전체' && sigunguCd !=='') ? `/${sigunguCd}` : (sidoCd != '전체' && sidoCd != '') ? `/${sidoCd}` : `/0`) + (usageCd? `/${usageCd}` : '/0') + (name? `/${name}` : ''); + const resp = await getFetchWithCount(fetchType.WR,`/reuseFc/pubFcRuseCase${tailUrl}`); + return {data:resp.data, count:resp.count}; +}; + +export const fetchInsertRuseCaseMng = async (data: any) => { + const response = await postFectch('/reuseFc/pubFcRuseCase', data, fetchType.WR); + return {data:response.data, status:response.status}; +}; + export type ruseCaseType = ruseCaseState & ruseCaseActions; export const ruseCaseStateExport: ruseCaseState = { - ruseCase: { - ruseHeadList:ruseCaseHeadListExport, + ruseCaseMng: { + ruseHeadList:ruseCaseHeadListExport, + ruseCaseMngList:[], + status: 0, + count: 0, }, }; -export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( + +export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( set: any ) => { return { - + selectRuseCaseMng: async (pageNo: number, pageSize: number, sidoCd: string, sigunguCd: string, usageCd : string, name: string) => { + const res = await fetchSearchRuseCaseMng(pageNo, pageSize, sidoCd, sigunguCd, usageCd, name); + set((state: ruseCaseState) => ({ + ruseCaseMng: { + ...state.ruseCaseMng, + ruseCaseMngList: res.data, + count: res.count, + }, + })); + }, + + insertRuseCaseMng: async (data: any) => { + const response = await fetchInsertRuseCaseMng(data); + if(response.status === 200){ + toast.success("물재이용사례 등록이 완료되었습니다. "); + }else{ + toast.error("물재이용사례 등록에 실패하였습니다. "); + } + set((state: ruseCaseState) => ({ + ruseCaseMng: { + ...state.ruseCaseMng, + status: response.status, + }, + })); + }, }; -}; +}; \ No newline at end of file diff --git a/apps/wr/app/store/matDmd/ruseRvwHstry/headList.tsx b/apps/wr/app/store/matDmd/ruseRvwHstry/headList.tsx index 90c793c2f..a90f5831b 100644 --- a/apps/wr/app/store/matDmd/ruseRvwHstry/headList.tsx +++ b/apps/wr/app/store/matDmd/ruseRvwHstry/headList.tsx @@ -28,7 +28,7 @@ export const ruseHstryHeadListExport:TableUpperProps[]= [ visible: true, }, { - id: "regDt", //바꾸기 + id: "dmndTmng", //바꾸기 title: "신청일자", visible: true, }, diff --git a/apps/wr/app/store/matDmd/ruseRvwHstry/index.tsx b/apps/wr/app/store/matDmd/ruseRvwHstry/index.tsx index 3338bc467..cfd991b4d 100644 --- a/apps/wr/app/store/matDmd/ruseRvwHstry/index.tsx +++ b/apps/wr/app/store/matDmd/ruseRvwHstry/index.tsx @@ -5,7 +5,7 @@ import type { TableUpperProps, } from "../../common/type/header"; import { ruseHstryHeadListExport } from "./headList"; -import { fetchType, getFetch, postFectch, putFectch } from "@/components/lib/api"; +import { fetchType, getFetch, getFetchWithCount, postFectch, putFectch } from "@/components/lib/api"; import type { DmdCoReqType } from "../coMatRvw/type"; import toast from "react-hot-toast"; import { TEST_UUID } from "../../ruseFcltMng/fcltReg/rainReg"; @@ -16,6 +16,7 @@ export type ruseHstryState = { ruseHist: { isInit: boolean; rvwHistRes: DmdCoReqType[]; + rvwHistCount:number; ruseHistHeadList: TableUpperProps[]; }; }; @@ -25,6 +26,7 @@ export const ruseHstryStateExport: ruseHstryState = { isInit: false, rvwHistRes: [], ruseHistHeadList: ruseHistHeadList, + rvwHistCount:1, }, }; @@ -79,14 +81,14 @@ const irvwHistInit = async ( startYear: string, endYear: string, ) => { - let url = `/reuseFc/request/infoList/matched/${pageNo}/${pageCnt}/${startYear}/${endYear}`; + let url = `/reuseFc/request/infoList/Matched/${pageNo}/${pageCnt}/${startYear}/${endYear}`; // console.log("sggCd ", sggCd); // if (sggCd) { // url += `/${sggCd}`; // } - const rvwHistRes = await getFetch(fetchType.WR, url); + const rvwHistRes = await getFetchWithCount(fetchType.WR, url); return rvwHistRes; }; @@ -115,7 +117,8 @@ export const ruseHstryActionsExport: (set: any) => ruseHstryActions = ( ruseHist: { ...state.ruseHist, init: false, - rvwHistRes: rvwHistRes, + rvwHistRes: rvwHistRes.data, + rvwHistCount:rvwHistRes.count }, })); }, @@ -140,8 +143,8 @@ export const ruseHstryActionsExport: (set: any) => ruseHstryActions = ( ruseHist: { ...state.ruseHist, init: false, - rvwHistRes: rvwHistRes, - }, + rvwHistRes: rvwHistRes.data, + rvwHistCount:rvwHistRes.count }, })); } } catch (e) { @@ -158,7 +161,7 @@ export const ruseHstryActionsExport: (set: any) => ruseHstryActions = ( dmdCoNm: string, ruseUtztnFcltNm: string ) => { - let url = `/reuseFc/request/infoList/matched/${pageNo}/${pageCnt}/${startYear}/${endYear}`; + let url = `/reuseFc/request/infoList/Matched/${pageNo}/${pageCnt}/${startYear}/${endYear}`; if (sggCd.length > 0) { url += `/${sggCd}`; @@ -175,14 +178,14 @@ export const ruseHstryActionsExport: (set: any) => ruseHstryActions = ( } else { url += `/0`; } - const rvwHistRes = await getFetch(fetchType.WR, url); + const rvwHistRes = await getFetchWithCount(fetchType.WR, url); return set((state: ruseHstryState) => ({ ...state, ruseHist: { ...state.ruseHist, - rvwHistRes: rvwHistRes, - }, + rvwHistRes: rvwHistRes.data, + rvwHistCount:rvwHistRes.count }, })); }, ruseRvwInqua: async ( @@ -193,16 +196,17 @@ export const ruseHstryActionsExport: (set: any) => ruseHstryActions = ( sggCd: string ) => { try { - let url = `/reuseFc/request/infoList/matched/${pageNo}/${pageCnt}/${startYear}/${endYear}`; + let url = `/reuseFc/request/infoList/Matched/${pageNo}/${pageCnt}/${startYear}/${endYear}`; if (sggCd.length > 0) { url += `/${sggCd}/0`; } - const rvwHistRes = await getFetch(fetchType.WR, url); + const rvwHistRes = await getFetchWithCount(fetchType.WR, url); return set((state: ruseHstryState) => ({ ...state, ruseHist: { ...state.ruseHist, - rvwHistRes: rvwHistRes, + rvwHistRes: rvwHistRes.data, + rvwHistCount:rvwHistRes.count }, })); } catch (e) { @@ -224,6 +228,15 @@ export const ruseHstryActionsExport: (set: any) => ruseHstryActions = ( new Date().getFullYear().toString(), new Date().getFullYear().toString(), ); + + return set((state: ruseHstryState) => ({ + ...state, + ruseHist: { + ...state.ruseHist, + rvwHistRes: rvwHistRes.data, + rvwHistCount:rvwHistRes.count + }, + })); } } catch (e) { console.log(" error ", e); diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx index 5754c02fc..58e8d6bf0 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel.tsx @@ -94,15 +94,15 @@ export const excelToRainChkData = (excelData: any, year: string) => { } const makeGuidDetail = Array.from({ length: 12 }, (_, i) => { let month = `${i + 1}`; - const splyData = item[`보급_${month}월`]; + const splyData = item[`대체_${month}월`]; const ruseData = item[`빗물_${month}월`]; if (month.length < 2) month = "0" + month; return { utztnUsgCd: item["이용용도"], ym: year + month, chckYmd: checkYmd, - splyUswtrUtztnQnt: Number(splyData ?? 0), - ruseFcltUtztnQnt: Number(ruseData ?? 0), + splyUswtrUtztnQnt: Number(splyData) || 0, + ruseFcltUtztnQnt: Number(ruseData) || 0, flg: "RNB", }; }); @@ -120,13 +120,13 @@ export const excelToRainChkData = (excelData: any, year: string) => { rainyDetails: [ { utztnUsgCd: item["이용용도"] ?? "", - ph: Number(item["PH"] ?? 0), - trbt: Number(item["탁도"] ?? 0), - grssClfg: Number(item["총대장균군수"] ?? 0), - etc: item["기타"] ?? "", // 필드 누락 방지 - vltnDt: vltnDt ?? "", - vltnMttr: item["위반사항"] ?? "", - levyAmt: Number(item["과태료부과금액(천원)"] ?? 0), + ph: Number(item["PH"] || 0), + trbt: Number(item["탁도"] || 0), + grssClfg: Number(item["총대장균군수"] || 0), + etc: item["기타"] || "", // 필드 누락 방지 + vltnDt: vltnDt || "-", + vltnMttr: item["위반사항"] || "-", + levyAmt: Number(item["과태료부과금액(천원)"] || 0), rgtr: TEST_UUID, }, ], diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx index 3cc75d70c..3cf1b5b11 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList.tsx @@ -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({ @@ -114,10 +114,22 @@ export type splRuseWaterType = { ruse12: number; }; -export const rainHeadNmExport: chHeadType[] = [ +export const rainMainHeadNmExport: chHeadType[] = [ + { headNm: "useQuilty", text: "이용수질(필요시)", length: 3 }, + { headNm: "feeHist", text: "과태료 부과내역", length: 3 }, +]; + +export const rainRuseHeadNmExport: chHeadType[] = [ + { headNm: "useQuilty", text: "이용수질(필요시)", length: 3 }, + { headNm: "feeHist", text: "과태료 부과내역", length: 3 }, + { headNm: "ruseWater", text: "연간대체수 이용량(m³)", length: 13 }, + { headNm: "detailQulity", text: "분기", length: 11 }, +]; + + +export const rainSplHeadNmExport: chHeadType[] = [ { headNm: "useQuilty", text: "이용수질(필요시)", length: 3 }, { headNm: "feeHist", text: "과태료 부과내역", length: 3 }, - { headNm: "ruseWater", text: "연간보급수 이용량(m³)", length: 13 }, { headNm: "detailQulity", text: "분기", length: 11 }, { headNm: "splWater", text: "연간빗물 이용량(m³)", length: 13 }, ]; @@ -134,7 +146,7 @@ export const rainChkgdnHeadListExport: TableUpperProps[] = [ { id: "dtfcltCpct", title: "빗물저류조용량(m³)", mergeCell: true }, { id: "chckYmd", title: "점검일", mergeCell: true }, { id: "utztnUsgCd", title: "이용용도" }, - { id: "totalSplyUswtrUtztnQnt", title: "연간보급수이용량(m³)" }, + { id: "totalSplyUswtrUtztnQnt", title: "연간대체수이용량(m³)" }, { id: "totalRuseFcltUtztnQnt", title: "연간빗물이용량(m³)" }, { id: "ph", diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx index ff612a0fb..069f1f760 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rainChkgdn/index.tsx @@ -1,27 +1,31 @@ import { buildingType } from "@/app/store/common/code"; -import { - TEST_UUID, -} from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import type { TableUpperProps } from "@/app/store/common/type/header"; +import { TEST_UUID } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import { deleteFectch, fetchType, getFetch, + getFetchWithCount, postFectch, putFectch, } from "@/components/lib/api"; -import { ToastFormUi } from "@/components/ui/toast/toastFromUi"; import type { SelectDataType } from "@common/business_components/ui"; +import toast from "react-hot-toast"; import { z } from "zod"; import { buildingToExcelType } from "./excel"; import { rainChkgdnHeadListExport, rainChkZod, - rainHeadNmExport, + rainMainHeadNmExport, + rainRuseHeadNmExport, + rainSplHeadNmExport, ruseHeadListExport, splHeadListExport, type chHeadType, } from "./headList"; -import type { TableUpperProps } from "@/app/store/common/type/header"; + +export const rainPageSize = 5; +export const commonCd = "41"; export type rainChkZodType = z.infer<typeof rainChkZod>; const fcltDiv = [ @@ -29,30 +33,41 @@ const fcltDiv = [ { val: "RNB", text: "빗물이용시설" }, ]; -const rainHeadNm = rainHeadNmExport; +const rainRuseHeadNm = rainRuseHeadNmExport; +const rainSplHeadNm = rainSplHeadNmExport; const rainChkgdnHeadList = rainChkgdnHeadListExport; const ruseHeadList = ruseHeadListExport; const splHeadList = splHeadListExport; +const rainMainHeadNm = rainMainHeadNmExport const rainChk: rainChkZodType[] = []; export type rainChkgdnState = { rainChkgdn: { rainChkgdnHeadList: TableUpperProps[]; + rainSplHeadNm: chHeadType[] + rainRuseHeadNm: chHeadType[] rainChk: rainChkZodType[]; - rainHeadNm: chHeadType[]; ruseHeadList: TableUpperProps[]; splHeadList: TableUpperProps[]; isInit: boolean; isModalClose: boolean; fcltDiv: SelectDataType[]; + rainMainHeadNm: chHeadType[] excelList: []; isSttn: boolean; + rainChkCount: number; + isLoading: boolean; }; }; type rainChkgdnActions = { - rainChkInit: (sggCd: string, searchTxt: string) => void; + rainChkInit: ( + pageNo: number, + cnt: number, + sggCd: string, + searchTxt: string + ) => void; rainChkDetilUpdate(rainChk: rainChkZodType[]): Promise<void>; rainChkGuidUpdate(rainChk: rainChkZodType): Promise<void>; rainChkExcelDownload( @@ -61,7 +76,14 @@ type rainChkgdnActions = { ): Promise<void>; rainChkExcelDataUPload(excelData: rainChkZodType[]): Promise<void>; rainChkDelete(chkgdnPks: number[]): Promise<void>; - rainChkHeadChange: (rainChkgdn: rainChkgdnState, isEdit :boolean) => void; + rainChkHeadChange: (rainChkgdn: rainChkgdnState, isEdit: boolean) => void; + rainChkNmSearch: ( + pageNo: number, + cnt: number, + year: string, + sggCd: string, + txt: string + ) => void; }; export type rainChkgdnType = rainChkgdnState & rainChkgdnActions; @@ -69,7 +91,9 @@ export type rainChkgdnType = rainChkgdnState & rainChkgdnActions; export const rainChkgdnStateExport: rainChkgdnState = { rainChkgdn: { rainChkgdnHeadList: rainChkgdnHeadList, - rainHeadNm: rainHeadNm, + rainRuseHeadNm: rainRuseHeadNm, + rainSplHeadNm: rainSplHeadNm, + rainMainHeadNm: rainMainHeadNm, rainChk: rainChk, splHeadList: splHeadList, ruseHeadList: ruseHeadList, @@ -78,6 +102,8 @@ export const rainChkgdnStateExport: rainChkgdnState = { excelList: [], fcltDiv: fcltDiv, isSttn: false, + rainChkCount: 1, + isLoading: false, }, }; @@ -98,11 +124,23 @@ const makeMdfrChk = (data: rainChkZodType[], type: string, toDo: string) => { return chData; }; -const initFetch = async (txt: string | undefined, sgg: string | undefined) => { - const rainRes = await getFetch( +const initFetch = async ( + pageNo: number, + cnt: number, + year: string | undefined, + sgg: string | undefined +) => { + const currentYear = new Date().getFullYear().toString(); + + const rainRes = await getFetchWithCount( "wr", - `/reuseChk/rainy` + (txt ? `/${txt}` : "2024") + (sgg ? `/${sgg}` : "/0") + `/reuseChk/rainy` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/10") + + (year ? `/${year}` : `/${currentYear}`) + + (sgg ? `/${sgg}` : "/0") ); + return rainRes; }; @@ -128,39 +166,89 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( })); }, rainChkExcelDataUPload: async (excelData: rainChkZodType[]) => { + set((state: rainChkgdnType) => ( + { rainChkgdn: { ...state.rainChkgdn, isLoading: true } })); try { const res = await postFectch("/reuseChk/rainy", excelData); - if (res.ok) { - ToastFormUi({ - body: "지도점검 등록이 완료되었습니다. ", - title: "지도점검 등록", - }); + if (res.status === 200) { + const currentYear = new Date().getFullYear().toString(); + const rainChkRes = await initFetch( + 1, + rainPageSize, + currentYear, + commonCd + ); + set((state: rainChkgdnState) => ({ + ...state, + rainChkgdn: { + ...state.rainChkgdn, + rainChk: rainChkRes.data, + rainChkCount: rainChkRes.count, + }, + })); + toast.success("지도점검 등록이 완료되었습니다."); + } else { + toast.error("지도점검 등록이 실패되었습니다."); } - const rainChkRes = await initFetch("2024", "0"); - return set((state: rainChkgdnState) => ({ + } catch (error) { + console.log(error); + toast.error("지도점검 등록이 실패되었습니다."); + } finally { + set((state: rainChkgdnType) => ({ rainChkgdn: { ...state.rainChkgdn, isLoading: false } })); + } + }, + rainChkInit: async ( + pageNo: number, + cnt: number, + year: string, + sggCd: string + ) => { + // set((state: rainChkgdnType) => ({ rainChkgdn: { ...state.rainChkgdn, isLoading: true } })); + try { + const rainChkRes = await initFetch(pageNo, cnt, year, sggCd); + set((state: rainChkgdnState) => ({ ...state, rainChkgdn: { ...state.rainChkgdn, - rainChk: rainChkRes, + rainChk: rainChkRes.data, + rainChkCount: rainChkRes.count, + isInit: false, // 무한 루프 방지를 위해 isInit을 true로 설정 }, })); } catch (error) { console.log(error); - ToastFormUi({ - body: "지도점검 등록이 실패되었습니다.", - title: "지도점검 등록", - }); + // 에러가 발생하더라도 무한 루프에 빠지지 않도록 isInit을 true로 설정합니다. + // set((state: rainChkgdnType) => ({ + // rainChkgdn: { ...state.rainChkgdn, isInit: true }, + // })); + } finally { + // set((state: rainChkgdnType) => ({ rainChkgdn: { ...state.rainChkgdn, isLoading: false } })); } }, - rainChkInit: async (searchTxt: string, sggCd: string) => { + rainChkNmSearch: async ( + pageNo: number, + cnt: number, + year: string, + sggCd: string, + txt: string + ) => { try { - const rainChkRes = await initFetch(searchTxt, sggCd); - + const currentYear = new Date().getFullYear(); + const rainChkRes = await getFetchWithCount( + "wr", + `/reuseChk/rainy` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/10") + + (year ? `/${year}` : `/${currentYear}`) + + (sggCd ? `/${sggCd}` : "/0") + + (txt ? `/${txt}` : "") + ); return set((state: rainChkgdnState) => ({ ...state, rainChkgdn: { ...state.rainChkgdn, - rainChk: rainChkRes, + rainChk: rainChkRes.data, + rainChkCount: rainChkRes.count, }, })); } catch (error) { @@ -171,46 +259,62 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( try { const chData = await makeMdfrChk(rainChk, "rain", "rainDetil"); - const res = await postFectch("/reuseChk/rainy", chData); - if (res.ok) { - ToastFormUi({ - body: "지도점검 보급수 및 밋물이용량 수정이 완료되었습니다. ", - title: "지도점검 수정", - }); + const res = await putFectch("/reuseChk/rainy/list", chData); + if (res) { + toast.success( + "지도점검 대체수 및 밋물이용량 수정이 완료되었습니다. " + ); + const currentYear = new Date().getFullYear().toString(); + const rainChkRes = await initFetch( + 1, + rainPageSize, + currentYear, + "41" + ); + return set((state: rainChkgdnState) => ({ + ...state, + rainChkgdn: { + ...state.rainChkgdn, + rainChk: rainChkRes.data, + rainChkCount: rainChkRes.count, + }, + })); + } else { + toast.error("지도점검 대체수 및 밋물이용량 수정 오류입니다."); } - const rainChkRes = await initFetch("2024", "0"); - return set((state: rainChkgdnState) => ({ - ...state, - rainChkgdn: { - ...state.rainChkgdn, - rainChk: rainChkRes, - }, - })); } catch (error) { console.log(error); - alert("수정에 실패하였습니다."); + toast.error("지도점검 대체수 및 밋물이용량 수정 오류입니다."); } }, rainChkGuidUpdate: async (rainChk: rainChkZodType) => { try { //const chData = await makeMdfrChk(rainChk, "guideDetails", "rainDetil"); const res = await putFectch("/reuseChk/rainy", rainChk); - if (res.ok) { - ToastFormUi({ - body: "지도점검 수질 및 과태료 수정이 완료되었습니다. ", - title: "지도점검 수정", - }); + if (res.status === 200) { + toast.success("지도점검 수질 및 과태료 수정이 완료되었습니다. "); + + const currentYear = new Date().getFullYear().toString(); + const rainChkRes = await initFetch( + 1, + rainPageSize, + currentYear, + "41" + ); + return set((state: rainChkgdnState) => ({ + ...state, + rainChkgdn: { + ...state.rainChkgdn, + rainChk: rainChkRes.data, + rainChkCount: rainChkRes.count, + }, + })); + } else { + toast.error("지도점검 수질 및 과태료 수정 오류입니다."); + } - const rainChkRes = await initFetch("2024", "0"); - return set((state: rainChkgdnState) => ({ - ...state, - rainChkgdn: { - ...state.rainChkgdn, - rainChk: rainChkRes, - }, - })); } catch (error) { - console.log(error); + toast.error("지도점검 수질 및 과태료 수정 오류입니다."); } }, rainChkDelete: async (chkgdnPks: number[]) => { @@ -218,40 +322,45 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( const res = await deleteFectch(`/reuseChk/rainy`, chkgdnPks); if (res) { - ToastFormUi({ - body: "삭제가 완료되었습니다. ", - title: "지도점검 삭제", - }); + toast.success("삭제가 완료되었습니다. "); + + const currentYear = new Date().getFullYear().toString(); + const rainChkRes = await initFetch( + 1, + rainPageSize, + currentYear, + "41" + ); + return set((state: rainChkgdnState) => ({ + ...state, + rainChkgdn: { + ...state.rainChkgdn, + rainChk: rainChkRes.data, + rainChkCount: rainChkRes.count, + }, + })); + } else { + toast.error("삭제시 오류가 발생하였습니다.. "); + } - const rainChkRes = await initFetch("2024", "0"); - return set((state: rainChkgdnState) => ({ - ...state, - rainChkgdn: { - ...state.rainChkgdn, - rainChk: rainChkRes, - }, - })); } catch (error) { - ToastFormUi({ body: "삭제를 실패했습니다.. ", title: "지도점검 삭제" }); + toast.error("삭제시 오류가 발생하였습니다.. "); } }, - rainChkHeadChange: async (rainChkgdn: rainChkgdnState , isEdit :boolean) => { + rainChkHeadChange: async (rainChkgdn: rainChkgdnState, isEdit: boolean) => { try { const rainChkgdnHeadList = rainChkgdn.rainChkgdn.rainChkgdnHeadList; const splHeadList = rainChkgdn.rainChkgdn.splHeadList; const ruseHeadList = rainChkgdn.rainChkgdn.ruseHeadList; - rainChkgdnHeadList.forEach(( head) => { - if(head.edit!== undefined) - head.edit=isEdit; + rainChkgdnHeadList.forEach((head) => { + if (head.edit !== undefined) head.edit = isEdit; }); splHeadList.forEach((head) => { - if(head.edit!== undefined) - head.edit=isEdit; + if (head.edit !== undefined) head.edit = isEdit; }); ruseHeadList.forEach((head) => { - if(head.edit!== undefined) - head.edit=isEdit; + if (head.edit !== undefined) head.edit = isEdit; }); return set((state: rainChkgdnState) => ({ @@ -259,8 +368,8 @@ export const rainChkgdnActionsExport: (set: any) => rainChkgdnActions = ( rainChkgdn: { ...state.rainChkgdn, rainChkgdnHeadList: rainChkgdnHeadList, - splHeadList:splHeadList, - ruseHeadList:ruseHeadList + splHeadList: splHeadList, + ruseHeadList: ruseHeadList, }, })); } catch (error) { diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/headList.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/headList.tsx index 0e690dbb9..c9fc9f078 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/headList.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/headList.tsx @@ -1,6 +1,6 @@ +import type { TableUpperProps } from "@/app/store/common/type/header"; import { z } from "zod"; import { guideDetail, type chHeadType } from "../rainChkgdn/headList"; -import type { TableUpperProps } from "@/app/store/common/type/header"; export type polutionType = { chkgdnDtlFk: number; @@ -109,6 +109,24 @@ export const rswtChkgdZod = z.object({ rswtrPollutions: z.array(rswtrPolutionsZode), }); + + +export const rswtRuseHeadNmExport: chHeadType[] = [ + { headNm: "useQuilty", text: "이용수질(필요시)", length: 3 }, + { headNm: "feeHist", text: "과태료 부과내역", length: 3 }, + { headNm: "ruseWater", text: "연간중수 이용량(m³)", length: 13 }, + { headNm: "detailQulity", text: "분기", length: 11 }, +]; + +export const rswtSplHeadNmExport: chHeadType[] = [ + { headNm: "useQuilty", text: "이용수질(필요시)", length: 3 }, + { headNm: "feeHist", text: "과태료 부과내역", length: 3 }, + { headNm: "detailQulity", text: "분기", length: 11 }, + { headNm: "splWater", text: "연간대체수 이용량(m³)", length: 13 }, +]; + + + export const rswtHeadNmExport: chHeadType[] = [ { headNm: "useQuilty", text: "연간 평균 이용수질", length: 10 }, { headNm: "feeHist", text: "과태료 부과내역", length: 3 }, @@ -126,7 +144,7 @@ export const rswtChkHeadListExport: TableUpperProps[] = [ { id: "spwtPositn", title: "채수지점" ,mergeCell: true}, { id: "chckYmd", title: "점검일" ,mergeCell: true}, { id: "utztnUsgCd", title: "이용용도" }, - { id: "splyUswtrUtztnQnt", title: "연간보급수이용량(m³)" ,type: "number"}, + { id: "splyUswtrUtztnQnt", title: "연간대체수이용량(m³)" ,type: "number"}, { id: "ruseFcltUtztnQnt", title: "연간중수도이용량(m³)",type: "number"}, { diff --git a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx index eb0010047..4f647da1e 100644 --- a/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx +++ b/apps/wr/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/index.tsx @@ -1,18 +1,24 @@ +import { buildingType } from "@/app/store/common/code"; +import type { TableUpperProps } from "@/app/store/common/type/header"; import { TEST_UUID, } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; -import type { - SelectDataType, - SheetParameter, -} from "@common/business_components"; import { deleteFectch, fetchType, getFetch, + getFetchWithCount, postFectch, putFectch, } from "@/components/lib/api"; -import { buildingType } from "@/app/store/common/code"; +import { ToastFormUi } from "@/components/ui/toast/toastFromUi"; +import type { + SelectDataType, + SheetParameter, +} from "@common/business_components"; +import toast from "react-hot-toast"; +import { z } from "zod"; +import { type chHeadType } from "../rainChkgdn/headList"; import { rswtMakeExcelSample } from "./excel"; import { rswtChkgdZod, @@ -20,15 +26,19 @@ import { rswtDetailHeadListExport, rswtHeadNmExport, rswtrDetailsZod, + rswtRuseHeadNmExport, + rswtSplHeadNmExport, } from "./headList"; -import { z } from "zod"; -import { ToastFormUi } from "@/components/ui/toast/toastFromUi"; -import { type chHeadType } from "../rainChkgdn/headList"; -import type { TableUpperProps } from "@/app/store/common/type/header"; + +export const rswtPageSize = 5; const rswtChkHeadList = rswtChkHeadListExport; const rswtDetailHeadList = rswtDetailHeadListExport; const rswtHeadNm = rswtHeadNmExport; +const rswtRuseHeadNm =rswtRuseHeadNmExport +const rswtSplHeadNm =rswtSplHeadNmExport + + export type rswtChkZodType = z.infer<typeof rswtChkgdZod>; export type rswtrChkDetailsZodType = z.infer<typeof rswtrDetailsZod>; export type rswtChkgdnState = { @@ -40,13 +50,29 @@ export type rswtChkgdnState = { excelList: SheetParameter[]; rswtChkRes: rswtChkZodType[]; rswtChkPolGuide: rswtChkZodType[]; + rswtSplHeadNm:chHeadType[] + rswtRuseHeadNm:chHeadType[] isInit: boolean; - isSttn:boolean + isSttn: boolean; + isLoading:boolean; + rswtChkCount: number; }; }; type rswtChkgdnActions = { - rswtChkgdnInit: (year: string, sggCd: string) => void; + rswtChkgdnInit: ( + pageNo: number, + cnt: number, + year: string, + sggCd: string + ) => void; + rswtChkNmSearch: ( + pageNo: number, + cnt: number, + year: string, + sggCd: string, + txt: string + ) => void; rswtrChkExcelDownload( sidoCode: SelectDataType[], sggCode: SelectDataType[] @@ -57,7 +83,7 @@ type rswtChkgdnActions = { rswtChkPolAndGuide: (chkGdnPk: number) => void; rswtChkPolInit: () => void; rwstPolAndGuideUpdate: (rswtChk: rswtChkZodType[]) => void; - rswtChkHeadChange: (rswtChkgdn: rswtChkgdnState, isEdit: boolean) => void; + rswtChkHeadChange: (rswtChkgdn: rswtChkgdnState, isEdit: boolean) => void; }; export type rswtChkgdnType = rswtChkgdnState & rswtChkgdnActions; @@ -67,24 +93,41 @@ export const rswtChkgdnStateExport: rswtChkgdnState = { rswtChkHeadList: rswtChkHeadList, rswtDetailHeadList: rswtDetailHeadList, rswtHeadNm: rswtHeadNm, - excelList: [{ - data: [[]], headerNum: 0, headerRange: "A1:V3", + rswtRuseHeadNm:rswtRuseHeadNm, + rswtSplHeadNm:rswtSplHeadNm, + + excelList: [ + { + data: [[]], + headerNum: 0, + headerRange: "A1:V3", sheetName: "", - startCol: '1' - }], + startCol: "1", + }, + ], rswtChkRes: [], rswtChkPolGuide: [], isInit: false, - isSttn:false, + isSttn: false, + isLoading:false, + rswtChkCount: 1, }, }; -const initFetch = async (year: string | undefined, sgg: string | undefined) => { - const rswtChkRes = await getFetch( +const initFetch = async ( + pageNo: number, + cnt: number, + year: string | undefined, + sgg: string | undefined +) => { + const currentYear = new Date().getFullYear().toString(); + const rswtChkRes = await getFetchWithCount( "wr", - `/reuseChk/reuseWater/` + - (year ? `${year}` : "2024") + - (sgg ? `/${sgg}` : "/0") + `/reuseChk/reuseWater` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/20") + + (year ? `/${year}` : `/${currentYear}`) + + (sgg ? `/${sgg}` : "/0") ); return rswtChkRes; }; @@ -121,12 +164,12 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( excelList.push({ data: exceldata.guideDetail, headerNum: 0, - headerRange: "A1:AI1", + headerRange: "A1:AJ1", }); excelList.push({ data: exceldata.polutions, headerNum: 0, - headerRange: "A1:AZ1", + headerRange: "A1:BB1", }); return set((state: rswtChkgdnState) => ({ @@ -136,16 +179,63 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( }, })); }, - rswtChkgdnInit: async (year: string, sggCd: string) => { + rswtChkgdnInit: async ( + pageNo: number, + cnt: number, + year: string, + sggCd: string + ) => { + // set((state: rswtChkgdnType) => ({ rswtChkgdn: { ...state.rswtChkgdn, isLoading: true } })); try { - const rswtChkRes = await initFetch("2024", sggCd); + const rswtChkRes = await initFetch(pageNo, cnt, year, sggCd); + + console.log("rswtChkRes", rswtChkRes) + + set((state: rswtChkgdnState) => ({ + ...state, + rswtChkgdn: { + ...state.rswtChkgdn, + rswtChkRes: rswtChkRes.data, + rswtChkCount: rswtChkRes.count, + isInit: true, // 무한 루프를 방지하기 위해 isInit을 true로 설정 + }, + })); + } catch (error) { + // console.log(error); + // set((state: rswtChkgdnType) => ({ + // rswtChkgdn: { ...state.rswtChkgdn, isInit: true }, + // })); + } finally { + // set((state: rswtChkgdnType) => ({ rswtChkgdn: { ...state.rswtChkgdn, isLoading: false } })); + } + }, + rswtChkNmSearch: async ( + pageNo: number, + cnt: number, + year: string, + sggCd: string, + txt: string + ) => { + try { + console.log("여기") + const currentYear = new Date().getFullYear().toString(); + const rswtChkRes = await getFetchWithCount( + "wr", + `/reuseChk/reuseWater` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/20") + + (year ? `/${year}` : `/${currentYear}`) + + (sggCd ? `/${sggCd}` : "/0") + + (txt ? `/${txt}` : "") + ); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { ...state.rswtChkgdn, - rswtChkRes: rswtChkRes, - isInit: false, + rswtChkRes: rswtChkRes.data, + rswtChkCount: rswtChkRes.count, + // isInit: false, }, })); } catch (error) { @@ -154,6 +244,8 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( }, rswtChkExceDataUpload: async (excelData: rswtChkZodType[]) => { try { + set((state: rswtChkgdnType) => ( + { rswtChkgdn: { ...state.rswtChkgdn, isLoading: true } })); const res = await postFectch("/reuseChk/reuseWater", excelData); if (res.ok) { ToastFormUi({ @@ -161,62 +253,67 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( title: "지도점검 등록", }); } - // const rainChkRes = await initFetch('2024', '0'); + const currentYear = new Date().getFullYear().toString(); + const rswtChkRes = await initFetch(1, rswtPageSize, currentYear, "0"); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { ...state.rswtChkgdn, + rswtChkRes: rswtChkRes.data, + rswtChkCount: rswtChkRes.count, }, })); } catch (error) { console.log(error); + }finally{ + set((state: rswtChkgdnType) => ( + { rswtChkgdn: { ...state.rswtChkgdn, isLoading: false } })); } + }, rswtChkDetilsUpdate: async (rswtChk: rswtrChkDetailsZodType[]) => { try { const chData = await makeMdfrChk(rswtChk); - const res = await putFectch("/reuseChk/reuseWater", chData); if (res.ok) { - ToastFormUi({ - body: "지도점검 과태료 수정 수정이 완료되었습니다. ", - title: "지도점검 수정", - }); + toast.success("지도점검 과태료 수정이 완료되었습니다. ",) } - const rswtChkRes = await initFetch("2024", "0"); + const currentYear = new Date().getFullYear().toString(); + const rswtChkRes = await initFetch(1, rswtPageSize, currentYear, "0"); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { ...state.rswtChkgdn, - rswtChkRes: rswtChkRes, + rswtChkRes: rswtChkRes.data, + rswtChkCount: rswtChkRes.count, }, })); } catch (error) { console.log(error); - alert("수정에 실패하였습니다."); + toast.error("지도점검 과태료 수정 오류입니다. ",) + } }, rswtChkDelete: async (chkgdnPks: number[]) => { try { const res = await deleteFectch(`/reuseChk/reuseWater`, chkgdnPks); - if (res.ok) { - ToastFormUi({ - body: "삭제가 완료되었습니다. ", - title: "지도점검 삭제", - }); + if (res) { + toast.success("삭제가 완료되었습니다.") } - const rswtChkRes = await initFetch("2024", "0"); + const currentYear = new Date().getFullYear().toString(); + const rswtChkRes = await initFetch(1, rswtPageSize, currentYear, "0"); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { ...state.rswtChkgdn, - rswtChkRes: rswtChkRes, + rswtChkRes: rswtChkRes.data, + rswtChkCount: rswtChkRes.count, }, })); } catch (error) { - ToastFormUi({ body: "삭제를 실패했습니다.. ", title: "지도점검 삭제" }); + toast.error("삭제 오류입니다.") } }, rswtChkPolAndGuide: async (chkGdnPk: number) => { @@ -233,7 +330,11 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( rswtChkPolGuide: rswtChkPolGuide, }, })); - } catch (error) {} + } catch (error) { + + + + } }, rswtChkPolInit: () => { try { @@ -254,12 +355,10 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( chData[0] ); if (res.ok) { - ToastFormUi({ - body: "지도점검 연간 수질 및 보급수/중수도 수정이 완료되었습니다. ", - title: "지도점검 수정", - }); + toast.success("지도점검 연간 수질 및 대체수/중수도 수정이 완료되었습니다.") } - const rswtChkRes = await initFetch("2024", "0"); + const currentYear = new Date().getFullYear().toString(); + const rswtChkRes = await initFetch(1, rswtPageSize, currentYear, "0"); return set((state: rswtChkgdnState) => ({ ...state, rswtChkgdn: { @@ -269,10 +368,9 @@ export const rswtChkgdnActionsExport: (set: any) => rswtChkgdnActions = ( })); } catch (error) { console.log(error); - ToastFormUi({ - body: "지도점검 연간 수질 및 보급수/중수도 수정이 실패하였습니다.. ", - title: "지도점검 수정", - }); + + toast.error("지도점검 연간 수질 및 대체수/중수도 수정 오류입니다.") + } }, rswtChkHeadChange: async (rswtChkgdn: rswtChkgdnState, isEdit: boolean) => { diff --git a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/headList.tsx b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/headList.tsx index 544f07516..abde1778f 100644 --- a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/headList.tsx +++ b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/headList.tsx @@ -4,7 +4,7 @@ import type { TableUpperProps } from "../../common/type/header"; export const pbnPrfRegExcelZod = z.object({ ym: z.string().default(""), bscFctNm: z.string().default(""), - pbnstRuseFcltFk:z.string(), + pbnstRuseFcltFk: z.string(), pbnstRuseFcltNm: z.string().default(""), file: z.instanceof(File).optional(), }); @@ -18,20 +18,21 @@ export const pbnstPrfmncHeadListExport: TableUpperProps[] = [ ]; export const monthHeadListExport: TableUpperProps[] = [ + { id: "구분", title: "구분", mergeCell: true }, { id: "용수명", title: "용수명" }, { id: "total", title: "계" }, - { id: "1월", title: "1월" ,edit: true }, - { id: "2월", title: "2월" ,edit: true }, - { id: "3월", title: "3월" ,edit: true }, - { id: "4월", title: "4월" ,edit: true }, - { id: "5월", title: "5월" ,edit: true }, - { id: "6월", title: "6월" ,edit: true }, - { id: "7월", title: "7월" ,edit: true }, - { id: "8월", title: "8월" ,edit: true }, - { id: "9월", title: "9월" ,edit: true }, - { id: "10월", title: "10월" ,edit: true }, - { id: "11월", title: "11월" ,edit: true }, - { id: "12월", title: "12월" ,edit: true }, + { id: "1월", title: "1월", edit: true }, + { id: "2월", title: "2월", edit: true }, + { id: "3월", title: "3월", edit: true }, + { id: "4월", title: "4월", edit: true }, + { id: "5월", title: "5월", edit: true }, + { id: "6월", title: "6월", edit: true }, + { id: "7월", title: "7월", edit: true }, + { id: "8월", title: "8월", edit: true }, + { id: "9월", title: "9월", edit: true }, + { id: "10월", title: "10월", edit: true }, + { id: "11월", title: "11월", edit: true }, + { id: "12월", title: "12월", edit: true }, ]; diff --git a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx index 87e827f60..387f56eb7 100644 --- a/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx +++ b/apps/wr/app/store/prfmncReg/pbnstPrfmncReg/index.tsx @@ -16,6 +16,7 @@ export type pbnstPrfmncState = { monthHeadList: TableUpperProps[]; prfmncRes: FcPrfType[]; excelList: Number[]; + pageKey:number }; }; @@ -26,21 +27,22 @@ export const pbnstPrfmncStateExport: pbnstPrfmncState = { monthHeadList: monthHeadList, prfmncRes: [], excelList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + pageKey:1 }, }; export type pbnstPrfmncType = pbnstPrfmncState & pbnstPrfmncActions; type pbnstPrfmncActions = { - prfmncHasuInit: (page: string, sggOrCity: string) => void; - prfmnRegData: (data: PrfType) => void; + prfmncHasuInit: (page: number, sggOrCity: string) => void; + prfmnRegData: (page:number,sggOrCity:string, data: PrfType) => void; }; const checkWrRegList = async () => { const wrRegList = await getFetch("wr", `/reuseFc/pubDetailList`); return wrRegList; }; -const hasuListFetch = async (page: string, sggOrCity: string) => { +const hasuListFetch = async (page: number, sggOrCity: string) => { const checkWrRegListRes = await checkWrRegList(); const rainRes = await postFectch( `/prcsFclt/ruseInfo/${page ? page : 1}/10/A/${sggOrCity ? sggOrCity : "11"}/0/1/SW`, @@ -74,10 +76,9 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( set: any ) => { return { - prfmncHasuInit: async (page: string, sggOrCity: string) => { + prfmncHasuInit: async (page: number, sggOrCity: string) => { try { const prfmncRes = await hasuListFetch(page, sggOrCity); - console.log("prfmncRes", prfmncRes); return set((state: pbnstPrfmncState) => ({ ...state, prfmnc: { @@ -90,7 +91,7 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( console.log(errorHasu); } }, - prfmnRegData: async (data: PrfType) => { + prfmnRegData: async (page:number,sggOrCity:string,data: PrfType) => { try { const ressponse = await postFectch( "/reuseFc/pubFcPrf", @@ -101,6 +102,17 @@ export const pbnstPrfmncActionsExport: (set: any) => pbnstPrfmncActions = ( if (ressponse.status === 200) { toast.success("하수재이용 시설 실적 등록이 완료되었습니다."); + const prfmncRes = await hasuListFetch(page, sggOrCity); + return set((state: pbnstPrfmncState) => ({ + ...state, + prfmnc: { + ...state.prfmnc, + prfmncRes: prfmncRes, + isInit: false, + pageKey: state.prfmnc.pageKey + 1 + }, + })); + } } catch (e) { console.log(e); diff --git a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rainChkgdnSttn/headList.tsx b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rainChkgdnSttn/headList.tsx index f112db0bd..8c90e0829 100644 --- a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rainChkgdnSttn/headList.tsx +++ b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rainChkgdnSttn/headList.tsx @@ -11,7 +11,7 @@ export const rainChkgdnHeadSttnExport: TableUpperProps[] = [ { id: "dtfcltCpct", title: "빗물저류조용량(m³)", mergeCell: true }, { id: "chckYmd", title: "점검일", mergeCell: true }, { id: "utztnUsgCd", title: "이용용도" }, - { id: "totalSplyUswtrUtztnQnt", title: "연간보급수이용량(m³)" }, + { id: "totalSplyUswtrUtztnQnt", title: "연간대체수이용량(m³)" }, { id: "totalRuseFcltUtztnQnt", title: "연간빗물이용량(m³)" }, { id: "ph", diff --git a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rainChkgdnSttn/index.tsx b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rainChkgdnSttn/index.tsx index 9fd948856..e99bdb7a8 100644 --- a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rainChkgdnSttn/index.tsx +++ b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rainChkgdnSttn/index.tsx @@ -1,11 +1,10 @@ +import type { TableUpperProps } from "@/app/store/common/type/header"; import type { rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; -import { type TableUpperProps } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; -import { getFetch } from "@/components/lib/api"; -import { ToastFormUi } from "@/components/ui/toast/toastFromUi"; +import { rainMainHeadNmExport, type chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; +import { getFetchWithCount } from "@/components/lib/api"; import type { SelectDataType } from "@common/business_components/ui"; -import { rainChkgdnHeadSttnExport, ruseSttnHeadListExport, splSttnHeadListExport } from "./headList"; -import { rainHeadNmExport, type chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; import { buildingExcelRainSttn } from "./excel"; +import { rainChkgdnHeadSttnExport, ruseSttnHeadListExport, splSttnHeadListExport } from "./headList"; const fcltDiv = [ { val: "RST", text: "중수도시설" }, @@ -16,29 +15,28 @@ const fcltDiv = [ const rainChkgdnHeadList = rainChkgdnHeadSttnExport; const ruseHeadList = ruseSttnHeadListExport; const splHeadList = splSttnHeadListExport; -const rainHeadNm = rainHeadNmExport - -const rainChk: rainChkZodType[] = []; +const rainHeadNm = rainMainHeadNmExport export type rainChkSttnState = { rainChkSttn: { rainChkRes: rainChkZodType[]; fcltDiv: SelectDataType[]; rainChkgdnHeadList: TableUpperProps[]; - rainHeadNm:chHeadType[]; + rainHeadNm: chHeadType[]; ruseHeadList: TableUpperProps[]; splHeadList: TableUpperProps[]; - sttnExcelList:[] + sttnExcelList: [] isInit: boolean; - isSttn:boolean; + isSttn: boolean; + rainChkCount: number; }; }; type rainChkSttnActions = { - rainChkSttnInit: ( sggCd: string) => void; - rainChkSttnSearch: (searchTxt: string, sggCd: string) => void; - isRainSttn:(isSttn:boolean) => void; - getRainSttnExcel:(excelList:rainChkZodType[]) => void; + rainChkSttnInit: (pageNo: number, cnt: number, searchTxt: string, sggCd: string) => void; + isRainSttn: (isSttn: boolean) => void; + getRainSttnExcel: (excelList: rainChkZodType[]) => void; + rainChkSttnNmSearch: (pageNo: number, cnt: number, year: string, sggCd: string, txt: string) => void }; export type rainChkSttnType = rainChkSttnState & rainChkSttnActions; @@ -50,17 +48,23 @@ export const rainChkSttnStateExport: rainChkSttnState = { rainChkgdnHeadList: rainChkgdnHeadList, ruseHeadList: ruseHeadList, splHeadList: splHeadList, - rainHeadNm:rainHeadNm, + rainHeadNm: rainHeadNm, isInit: false, - sttnExcelList:[], - isSttn:false, + sttnExcelList: [], + isSttn: false, + rainChkCount: 1, }, }; -const initFetch = async (txt: string | undefined, sgg: string | undefined) => { - const rainRes = await getFetch( +const initFetch = async (pageNo: number, cnt: number, txt: string | undefined, sgg: string | undefined) => { + const currentYear = new Date().getFullYear().toString(); + const rainRes = await getFetchWithCount( "wr", - `/reuseChk/rainy/` + (txt ? `${txt}` : "2024") + (sgg ? `/${sgg}` : "/0") + `/reuseChk/rainy` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/5") + + (txt ? `/${txt}` : `/${currentYear}`) + + (sgg ? `/${sgg}` : "/0") ); return rainRes; }; @@ -68,48 +72,66 @@ export const rainChkSttnActionsExport: (set: any) => rainChkSttnActions = ( set: any ) => { return { - rainChkSttnInit: async ( sggCd: string) => { + rainChkSttnInit: async (pageNo: number, cnt: number, searchTxt: string, sggCd: string) => { try { - const rainChkRes = await initFetch(undefined, sggCd); + const rainChkRes = await initFetch(pageNo, cnt, searchTxt, sggCd); return set((state: rainChkSttnState) => ({ ...state, rainChkSttn: { ...state.rainChkSttn, - rainChkRes:rainChkRes + rainChkRes: rainChkRes.data, + rainChkCount: rainChkRes.count, + isSttn: true }, })); } catch (error) { console.log(error); } }, - rainChkSttnSearch: async (searchTxt: string, sggCd: string) => { + rainChkSttnNmSearch: async ( + pageNo: number, + cnt: number, + year: string, + sggCd: string, + txt: string + ) => { try { - const rainChkRes = await initFetch(searchTxt, sggCd); - + const currentYear = new Date().getFullYear(); + const rainChkRes = await getFetchWithCount( + "wr", + `/reuseChk/rainy` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/5") + + (year ? `/${year}` : `/${currentYear}`) + + (sggCd ? `/${sggCd}` : "/0") + + (txt ? `/${txt}` : "") + ); return set((state: rainChkSttnState) => ({ ...state, rainChkSttn: { ...state.rainChkSttn, - rainChkRes:rainChkRes + rainChkRes: rainChkRes.data, + rainChkCount: rainChkRes.count, }, })); } catch (error) { console.log(error); } - },isRainSttn :(isSttn:boolean) =>{ + }, + isRainSttn: (isSttn: boolean) => { try { return set((state: rainChkSttnState) => ({ ...state, rainChkSttn: { ...state.rainChkSttn, - isSttn:isSttn + isSttn: isSttn }, })); } catch (error) { console.log(error); } - },getRainSttnExcel: async (excelList:rainChkZodType[]) => { + }, getRainSttnExcel: async (excelList: rainChkZodType[]) => { try { const excelData = buildingExcelRainSttn(excelList) @@ -118,7 +140,7 @@ export const rainChkSttnActionsExport: (set: any) => rainChkSttnActions = ( ...state, rainChkSttn: { ...state.rainChkSttn, - sttnExcelList:excelData + sttnExcelList: excelData }, })); } catch (error) { diff --git a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/excel.tsx b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/excel.tsx index ff104bc91..6e6b5730c 100644 --- a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/excel.tsx +++ b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/excel.tsx @@ -19,6 +19,7 @@ export const buildingExcelRswtSttn = (excelList: any[]) => { String(item.trbt) ?? "", String(item.bod) ?? "", String(item.smll) ?? "", + String(item.crmty) ?? "", String(item.tn) ?? "", String(item.tph) ?? "", String(item.ph) ?? "", diff --git a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/headList.tsx b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/headList.tsx index f67e98101..91f6bf718 100644 --- a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/headList.tsx +++ b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/headList.tsx @@ -1,15 +1,15 @@ import type { TableUpperProps } from "@/app/store/common/type/header"; export const rswtChkSttnHeadListExport: TableUpperProps[] = [ - { id: "order", title: "순번", rowSpan: true }, - { id: "cityText", title: "시도", rowSpan: true }, - { id: "sggText", title: "시군구", rowSpan: true }, - { id: "bdstNm", title: "시설명", rowSpan: true }, - { id: "operMngCd", title: "운영관리형태", rowSpan: true }, - { id: "anlOperCst", title: "연간운영비(원/년)", rowSpan: true }, - { id: "prcsCpct", title: "처리용량(m³)", rowSpan: true }, - { id: "spwtPositn", title: "채수지점", rowSpan: true }, - { id: "chckYmd", title: "점검일", rowSpan: true }, + { id: "order", title: "순번", mergeCell: true }, + { id: "cityText", title: "시도", mergeCell: true }, + { id: "sggText", title: "시군구", mergeCell: true }, + { id: "bdstNm", title: "시설명", mergeCell: true }, + { id: "operMngCd", title: "운영관리형태", mergeCell: true }, + { id: "anlOperCst", title: "연간운영비(원/년)", mergeCell: true }, + { id: "prcsCpct", title: "처리용량(m³)", mergeCell: true }, + { id: "spwtPositn", title: "채수지점", mergeCell: true }, + { id: "chckYmd", title: "점검일", mergeCell: true }, { id: "utztnUsgCd", title: "이용용도" }, { id: "splyUswtrUtztnQnt", title: "연간보급수이용량(m³)", type: "number" }, { id: "ruseFcltUtztnQnt", title: "연간중수도이용량(m³)", type: "number" }, diff --git a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/index.tsx b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/index.tsx index b985cd944..7891d73fb 100644 --- a/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/index.tsx +++ b/apps/wr/app/store/prfmncSttn/chkgdnSttn/rswtChkgdnSttn/index.tsx @@ -1,10 +1,10 @@ -import { rswtHeadNmExport } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/headList"; -import { rswtChkSttnHeadListExport, rswtDetailSttnHeadListExport } from "./headList"; import type { TableUpperProps } from "@/app/store/common/type/header"; import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; import type { rswtChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn"; -import { getFetch } from "@/components/lib/api"; +import { rswtHeadNmExport } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/headList"; +import { getFetch, getFetchWithCount } from "@/components/lib/api"; import { buildingExcelRswtSttn } from "./excel"; +import { rswtChkSttnHeadListExport, rswtDetailSttnHeadListExport } from "./headList"; const rswtChkHeadList = rswtChkSttnHeadListExport; const rswtDetailHeadList = rswtDetailSttnHeadListExport; @@ -20,14 +20,15 @@ export type rswtChkSttnState = { rswtChkRes: rswtChkZodType[]; isInit: boolean; isSttn: boolean; + rswtChkCount: number; }; }; type rswtChkSttnActions = { - rswtChkSttnInit: ( sggCd: string) => void; - isRswtSttn: (isSttn:boolean) => void; - getRswtSttnExcel:(excelList:rswtChkZodType[]) => void; - + rswtChkSttnInit: (pageNo: number, cnt: number, searchTxt: string, sggCd: string) => void; + isRswtSttn: (isSttn: boolean) => void; + getRswtSttnExcel: (excelList: rswtChkZodType[]) => void; + rswtChkNmSttnSearch: (pageNo: number, cnt: number, year: string, sggCd: string, txt: string) => void; }; export type rswtChkSttnType = rswtChkSttnState & rswtChkSttnActions; @@ -40,68 +41,104 @@ export const rswtChkStateExport: rswtChkSttnState = { sttnExcelList: [], rswtChkRes: [], isInit: false, - isSttn:false, + isSttn: false, + rswtChkCount: 1, }, }; -const initFetch = async (year: string | undefined, sgg: string | undefined) => { - const rswtChkRes = await getFetch( +const initFetch = async (pageNo: number, cnt: number, year: string | undefined, sgg: string | undefined) => { + const currentYear = new Date().getFullYear().toString(); + const rswtChkRes = await getFetchWithCount( "wr", - `/reuseChk/reuseWater/` + - (year ? `${year}` : "2024") + - (sgg ? `/${sgg}` : "/0") + `/reuseChk/reuseWater` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/20") + + (year ? `/${year}` : `/${currentYear}`) + + (sgg ? `/${sgg}` : "/0") ); return rswtChkRes; }; - export const rswtChkSttnActionsExport: (set: any) => rswtChkSttnActions = ( set: any ) => { return { - rswtChkSttnInit: async ( sggCd: string) => { + rswtChkSttnInit: async (pageNo: number, cnt: number, searchTxt: string, sggCd: string) => { try { - const rswtChkRes = await initFetch("2024", sggCd); + const rswtChkRes = await initFetch(pageNo, cnt, searchTxt, sggCd); return set((state: rswtChkSttnState) => ({ ...state, rswtChkSttn: { ...state.rswtChkSttn, - rswtChkRes: rswtChkRes, + rswtChkRes: rswtChkRes.data, + rswtChkCount: rswtChkRes.count, isInit: false, }, })); } catch (error) { console.log(error); } - },isRswtSttn :(isSttn:boolean) =>{ - try { - return set((state: rswtChkSttnState) => ({ - ...state, - rswtChkSttn: { - ...state.rswtChkSttn, - isSttn:isSttn - }, - })); - } catch (error) { - console.log(error); - } - },getRswtSttnExcel: async (excelList:rswtChkZodType []) => { - try { - - const excelData = buildingExcelRswtSttn(excelList) - - return set((state: rswtChkSttnState) => ({ - ...state, - rswtChkSttn: { - ...state.rswtChkSttn, - sttnExcelList:excelData - }, - })); - } catch (error) { - console.log(error); - } - } + }, + isRswtSttn: (isSttn: boolean) => { + try { + return set((state: rswtChkSttnState) => ({ + ...state, + rswtChkSttn: { + ...state.rswtChkSttn, + isSttn: isSttn + }, + })); + } catch (error) { + console.log(error); + } + }, + getRswtSttnExcel: async (excelList: rswtChkZodType[]) => { + try { - + const excelData = buildingExcelRswtSttn(excelList) + + return set((state: rswtChkSttnState) => ({ + ...state, + rswtChkSttn: { + ...state.rswtChkSttn, + sttnExcelList: excelData + }, + })); + } catch (error) { + console.log(error); + } + }, + rswtChkNmSttnSearch: async ( + pageNo: number, + cnt: number, + year: string, + sggCd: string, + txt: string + ) => { + try { + const currentYear = new Date().getFullYear().toString(); + const rswtChkRes = await getFetchWithCount( + "wr", + `/reuseChk/reuseWater` + + (pageNo ? `/${pageNo}` : "/1") + + (cnt ? `/${cnt}` : "/20") + + (year ? `/${year}` : `/${currentYear}`) + + (sggCd ? `/${sggCd}` : "/0") + + (txt ? `/${txt}` : "") + ); + + return set((state: rswtChkSttnState) => ({ + ...state, + rswtChkSttn: { + ...state.rswtChkSttn, + rswtChkRes: rswtChkRes.data, + rswtChkCount: rswtChkRes.count, + isInit: false, + }, + })); + } catch (error) { + console.log(error); + } + }, }; }; diff --git a/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx b/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx index b513085ae..db8b6a717 100644 --- a/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx +++ b/apps/wr/app/store/prfmncSttn/pbnstPrfmncSttn/index.tsx @@ -148,7 +148,7 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions let usageSum = 0; let year = ""; const yearsData = [] - + res.data.forEach((item) => { if (item.prfList.length === 0) { const year = ""; // 연도 정보가 없으면 빈 문자열, 필요시 item에서 추출 @@ -161,26 +161,26 @@ export const pbnstPrfmncSttnActionsExport: (set: any) => pbnstPrfmncSttnActions item.prfList.forEach((acc) => { year = acc.ym?.substring(0, 4) ?? ""; inUsageSum = - (acc.wshwtr ?? 0) + - (acc.coolUswtr ?? 0) + - (acc.cleanUswtr ?? 0) + - (acc.drwtUswtr ?? 0) + - (acc.dltnUswtr ?? 0) + - (acc.ithlRswtr ?? 0) + - (acc.ithlEtcUswtr ?? 0) + - (acc.cleanToilUswtr ?? 0) + - (acc.resn ?? 0); + (acc.wshwtr ?? 0) + //조경용수 + (acc.coolUswtr ?? 0) + //냉각수 + (acc.cleanUswtr ?? 0) + //청소수 + (acc.drwtUswtr ?? 0) + //식수대 + (acc.dltnUswtr ?? 0) + //희석용수 + (acc.ithlRswtr ?? 0) + //중수도 + (acc.ithlEtcUswtr ?? 0) //기타 outUsageSum = - (acc.lndwtr ?? 0) + - (acc.hydUswtr ?? 0) + - (acc.ugwtChr ?? 0) + - (acc.farmUswtr ?? 0) + - (acc.rvrKeepUswtr ?? 0) + - (acc.indstUswtr ?? 0); + (acc.cleanToilUswtr ?? 0) + //청소화장실용수 + (acc.resn ?? 0)+ //세척살수용도 + (acc.lndwtr ?? 0) + //조경용수 + (acc.hydUswtr ?? 0) + //친수용수 + (acc.ugwtChr ?? 0) + //지하수충전 + (acc.farmUswtr ?? 0) + //농업용수 + (acc.rvrKeepUswtr ?? 0) + //하천등유지용수 + (acc.indstUswtr ?? 0); //공업용수 const yearData = yearsData.find( (yearItem) => yearItem.year === year && - yearItem.pbnstRuseFcltPk == acc.pbnstRuseFcltPk + yearItem.pbnstRuseFcltPk == acc.pbnstRuseFcltFk ); if (yearData) { diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/bdstReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/bdstReg/index.tsx index 931cd1305..418f4b45f 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/bdstReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/bdstReg/index.tsx @@ -79,8 +79,9 @@ const isRswtBdst = (data: any): data is customRswtBdst =>{ data.reuseBuilding=chData; } - if(data?.chgBuilding.length>0){ - const chRgtr = data.chgBuilding.map((item) =>({...item, rgtr:TEST_UUID, mdfr: item.mdfr ?? null })) + + if('chgBuilding' in data && Array.isArray(data.chgBuilding) && data.chgBuilding.length > 0){ + const chRgtr = data.chgBuilding.map((item) =>({...item, rgtr:TEST_UUID, mdfr: item.mdfr ?? null })) data.chgBuilding =chRgtr; } return data; @@ -127,7 +128,7 @@ const isRswtBdst = (data: any): data is customRswtBdst =>{ data.reuseBuilding=chData; } - if(data?.chgBuilding.length>0){ + if('chgBuilding' in data && Array.isArray(data.chgBuilding) && data.chgBuilding.length > 0){ const chMdfr = data.chgBuilding.map((item) =>({...item, mdfr:TEST_UUID, archChgArea: item.archChgArea ?? '' })) data.chgBuilding =chMdfr; } diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/headList.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/headList.tsx new file mode 100644 index 000000000..da1fa6f09 --- /dev/null +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/headList.tsx @@ -0,0 +1,10 @@ +import type { TableUpperProps } from "@/app/store/common/type/header"; + +export const htdTableHeadList: TableUpperProps[] = [ + { id: "cityText", title: "시도" }, + { id: "sggText", title: "시군구" }, + { id: "bdstNm", title: "발전소명" }, + { id: "daddr", title: "위치" }, + { id: "frstArchPrmsnDt", title: "준공년도" }, + { id: "archArea", title: "건축면적" }, +]; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx index 0a51ebb5d..317f68e10 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/htdflReg/index.tsx @@ -1,9 +1,67 @@ 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, + htdflReuseBuildingSchema, + validationMessages, +} from "../rainReg/type"; // Import baseReuseBuildingSchema, chgBuildingSchema, validationMessages import type { TableUpperProps } from "@/app/store/common/type/header"; +import { htdTableHeadList } from "./headList"; +import toast from "react-hot-toast"; + +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 + .string() + .min(1, { 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() + .max(100, { message: "온도는 100이상이 될 수 없습니다." }) + .default(0), // Changed max to 100 based on message, // 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(), @@ -14,86 +72,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() , - archArea: z.number().default(0), + archChgPrmsnDt: z + .date() + .default(() => new Date()) + .nullish(), + archArea: z.string().default(''), 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), + htdflReuseBuildingSchema.extend({ + htdflObj: hdtflObjSchema, }) ) .default([]), + //온배수는 건축물은 등록 하는것이 다름 + // chgBuilding: z.array(chgBuildingSchema).nullish(), // Use imported chgBuildingSchema }); - - export type customHtdflBdst = z.infer<typeof hdtflZod> - - +export type customHtdflBdst = z.infer<typeof hdtflZod>; export type htdflFacState = { htdfl: { isInit: boolean; @@ -102,45 +99,46 @@ 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; - + initHtdflReg: (code: string) => void; + htdflNmSearch: (code: string | number, fcltNm: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[] = []; export type htdflFacType = htdflFacState & htdflFacSActions; - export const htdflFacStateExport: htdflFacState = { htdfl: { isInit: false, - upHeadList: upHeadList, + upHeadList: htdTableHeadList, htdflRes: htdflRes, }, }; +const initFetch = async (code: string) => { -const initFetch = async (code:string|number) => { - const rswtRes = await getFetch( - "wr", - `/facilityList/1/20/${code}`) + + console.log(' initFetch code ', code) + let url = `/drain/facilityList/1/20` + url+=`/${code}` + + + const rswtRes = await getFetch("wr", url); 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) - // 여기서 상태 업데이트 return set((state: htdflFacState) => ({ ...state, htdfl: { @@ -153,11 +151,9 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( console.log(e); } }, - - - initHtdfl: async (code: string ) => { + initHtdflReg: async (code: string) => { try { - const htdflRes = await initFetch(code); + const htdflRes = await initFetch( String(code)); set((state: htdflFacState) => { return { ...state, @@ -172,15 +168,21 @@ 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 res = await putFectch("/drain/facility", data); - if (res.ok) { - alert(" 수정이 완료되었습니다."); + + + const chData = await makeMdfr(data); + const res = await putFectch("/drain/facility", chData); + + if (res.status) { + toast.success("온배수 건축물 수정이 완료되었습니다."); } - const htdflRes = await initFetch(code); + const htdflRes = await getFetch( + "wr", + `/drain/facilityList/1/20/${code}` + ); set((state: htdflFacState) => ({ ...state, htdfl: { @@ -190,44 +192,20 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( }, })); } catch (errorUpdate) { - alert( - "업데이트 오류입니다. " + - (errorUpdate instanceof Error ? errorUpdate.message : "") - ); + toast.error("온배수 건축물 수정 오류입니다.."); } }, - htdflBdstReg: async (data: customHtdflBdst , code:string|number) => { + htdflBdstReg: async (data: customHtdflBdst, code: string | number) => { try { - const chData=await makeRstr(data) - const res = await postFectch("/drain/facility", data); - if (res.ok) { - - 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, - bdstInfoFk: chPK, - }) - ); - const chch = postFectch("/reuseFc/chgBuilding", makeCh); - return chch; - }) - .then((chch: any) => { - if (chch.ok) { - alert("등록이 완료되었습니다."); - } - }); - }else{ - alert("등록이 완료되었습니다."); - - } + // data는 단일 객체이므로 .map을 사용할 수 없습니다. + // 등록 시에는 건축물 변경 이력이 없으므로 chgBuilding을 빈 배열로 설정하고, + // API가 배열을 기대하므로 단일 객체를 배열로 감싸줍니다. + const chData = makeRstr(data); + const res = await postFectch("/drain/facility", chData); + if (res.status ===200) { + toast.success("온배수 건축물 등록이 완료되었습니다."); } - const htdflRes = await initFetch(code); + const htdflRes = await initFetch(String(code)); set((state: htdflFacState) => ({ ...state, htdfl: { @@ -237,8 +215,26 @@ export const htdflFacActionsExport: (set: any) => htdflFacSActions = ( }, })); } catch (errorReg) { - alert("등록 오류입니다."); - } + toast.error("온배수 건축물 등록 오류입니다.."); + + } + },htdflNmSearch :async(code:string | number, fcltNm:string) =>{ + try { + + const htdflRes = await getFetch("wr", `/drain/facilityList/1/20/${code}/${fcltNm}`); + set((state: htdflFacState) => { + return { + ...state, + htdfl: { + ...state.htdfl, + htdflRes: htdflRes, + isInit: false, + }, + }; + }); + } catch (error) { + console.log(error); + } }, }; }; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/headList.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/headList.tsx index 536114f3f..d77f4d7f0 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/headList.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/headList.tsx @@ -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" }, ]; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx index fb799c26c..e4bedff60 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/index.tsx @@ -52,7 +52,9 @@ export type pubFacState = { fcOrgwtsHeadList: TableUpperProps[]; pbnstMngRegData: customPbnst[]; pbnsUpdateIndex: pbnstInsertIndex; + fcOrgwtsNmList:pubHasuType[]; dmdComList: any[]; + apiMessage:string; }; }; type pubFacActions = { @@ -70,6 +72,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,7 +93,9 @@ export const pubFacStateExport: pubFacState = { pbnstBizCycle: [{ text: "없음", val: "1" }], pbnstMngRegData: [], pbnsUpdateIndex: {} as pbnstInsertIndex, + fcOrgwtsNmList:[], dmdComList: [], + apiMessage:'' }, }; @@ -102,12 +107,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 +180,7 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => { try { const pbnstHasuRes = await hasuListFetch(page, sggOrCity); + return set((state: pubFacState) => ({ ...state, pbnst: { @@ -267,8 +271,13 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => { ruseRes ); + if(response){ + + toast.success(` 하수재이용 시설 정보가 수정되었습니다.`); + } } catch (error) { console.error("Error pbnstRuseFcltUpdate:", error); + toast.error(` 하수재이용 시설 정보가 수정오류입니다.`); } } ,pbnstRuseBizUpdate : async(ruseRes:fcBizType[])=>{ try { @@ -278,6 +287,10 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => { `/reuseFc/pubFcBiz`, ruseRes ); + + if(response){ + toast.success(` 하수재이용 시설 사업이 수정되었습니다.`); + } const pbnstRes = await hasuListFetch("11", "11"); return set((state: pubFacState) => ({ @@ -290,6 +303,7 @@ export const pubFacActionsExport: (set: any) => pubFacActions = (set: any) => { })); } catch (error) { console.error("Error pbnstRuseFcltUpdate:", error); + toast.error(` 하수재이용 시설 사업이 수정이 실패되엇습니다..`); } } , getBizCycle: async (pbnstObj: customPbnst) => { @@ -399,6 +413,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??[] + }, + })); + + + } }; }; diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx index a34985810..ad3496a54 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/pbnstReg/type.tsx @@ -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(''), + majorPrcsMsrcCd: z.string().nullish(), + 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,10 +234,13 @@ 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, + ruseUtztnFcltNm: z + .preprocess( + (v) => (v !== null && v !== undefined ? String(v).trim() : ""), // null/undefined → "", 공백 제거 + z.string().min(1, "시설 이름을 입력하세요")), + fcltCpct: zNumDefaultZero, ruseCpct: zNumDefaultZero, splyExtraCpct: zNumDefaultZero, regDt: zDateDefaultNow, @@ -229,16 +249,20 @@ export const pbnstRegZod = z.object({ mdfr: z.string().default("test"), fcFiles: z.array(fcFileSchema).default([]).nullish(), fcBizs: z.array(fcBizSchema).default([]), - fcOpers: z.array(fcOperSchema).default([]).nullish(), + fcOpers: z + .array(fcOperSchema) + .refine((val) => val.length >= 1, { + message: "재이용시설의 운영정보를 최소 하나를 등록해주세요", + }) + .default([]), // 기본값 설정 }); -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>; - diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/index.tsx index 5b3ad6813..6ceb75531 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/index.tsx @@ -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().nullish() , 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(), @@ -81,11 +34,11 @@ export const rainZod = z.object({ bdstNm: z.string().default(""), frstArchPrmsnDt: z.date().default(() => new Date()), archChgPrmsnDt: z.coerce.date().nullish().default(null), - archArea: z.number().default(0), + archArea: z.string().default(''), 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([]) }) @@ -209,26 +162,28 @@ export const rainFacActionsExport: (set: any) => rainFacSActions = ( const test =await makeRstr(data); const res = await postFectch("/reuseFc/rainyInfo", test); - if(res.ok){ + if(res.status===200){ if(data.chgBuilding.length > 0){ - await res.json().then((resData: any) => { - const chPK = resData?.data?.bdstInfoPk - const makeCh= resData.data.chgBuilding.map((item: chgBuilding) => + + const chPK = res?.data?.bdstInfoPk + const makeCh= res.data.chgBuilding.map((item: chgBuilding) => ( { ...item, bdstInfoFk: chPK, }) ); - const chch= postFectch("/reuseFc/chgBuilding", makeCh); - return chch; - }).then((chch: any) => { - if(chch.ok) { - alert("등록이 완료되었습니다."); + const chch= await postFectch("/reuseFc/chgBuilding", makeCh); + + if(chch.status ===200) { + toast.success("건축물 등록이 완료되었습니다."); + }else{ + toast.error("건축물 증축/개축/중축 오류입니다."); } - }) }else{ - alert("등록이 완료되었습니다."); + toast.success("건축물 등록이 완료되었습니다."); } + }else{ + toast.error("건축물 등록이 완료되었습니다."); } const rainRes = await initFetch(code); set((state: rainFacState) => ({ @@ -241,7 +196,7 @@ export const rainFacActionsExport: (set: any) => rainFacSActions = ( })); } catch (e) { console.log("e", e); - alert("등록 오류입니다."); + toast.error("등록시 오류가 발생하였습니다."); } finally { } }, @@ -249,9 +204,9 @@ export const rainFacActionsExport: (set: any) => rainFacSActions = ( try { const chData =await makeMdfr(data); const res = await putFectch("/reuseFc/rainyInfo", chData); - console.log('rainUpdate', res); - if(res.ok){ - alert(" 수정이 완료되었습니다.") + if(res.status === 200){ + toast.success("수정이 완료되었습니다."); + } const rainRes = await initFetch(code); set((state: rainFacState) => ({ @@ -264,7 +219,7 @@ export const rainFacActionsExport: (set: any) => rainFacSActions = ( })); } catch (e) { console.log("e", e); - alert("업데이트 오류입니다."); + toast.error("수정시 오류가 발생하였습니다."); } finally { } } diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx new file mode 100644 index 000000000..2d17b4601 --- /dev/null +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/rainReg/type.tsx @@ -0,0 +1,90 @@ +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() + .max(4, { message: validationMessages.yearFormat }) + .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) + .refine((val) => val === -1 || val >= 1, { + message: validationMessages.requiredSelect("건축물용도"), + }), + 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(), +}); + + + +export const htdflReuseBuildingSchema = 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().nullish(), + anlOperCst: z.number().default(0), + instlCstSprtYn: z.string().default("Y"), + instlCstSprtAmt: z.number().nullish(), + grntchBdstCertYn: z.string().default("Y"), + grntchBdstCertYr: z + .string() + .max(4, { message: validationMessages.yearFormat }) + .refine( + (year) => year.length === 0 || /^\d{4}$/.test(year), + { + message: validationMessages.yearFormat, + } + ) + .nullish(), + 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).nullish(), + lawTrgtYn: z.string().default("Y").nullish(), + rgtr: z.string().nullish(), + mdfr: z.string().nullish(), +}); + + + diff --git a/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx index 3c2504b9a..d39412d8d 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltReg/rswtReg/index.tsx @@ -1,9 +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"; +import toast from "react-hot-toast"; + + 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("처리시설 용량") }), + 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(), @@ -14,80 +37,15 @@ export const rswtZod = z.object({ frstArchPrmsnDt: z.date().default(() => new Date()), archChgPrmsnDt: z.coerce.date().nullish().default(null), chBuildingYn: z.string().nullish(), - archArea: z.number().default(0), + archArea: z.string().default(''), archChgArea: z.number().default(0), rgtr: z.string().nullish().default(null), mdfr: z.string().nullish().default(null), 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>; @@ -195,8 +153,8 @@ export const rswtFacActionsExport: (set: any) => rswtFacSActions = ( try { const chData = await makeMdfr(data); const res = await putFectch("/reuseFc/waterreuseFc", chData); - if (res.ok) { - alert("등록이 완료되었습니다."); + if (res.status ===200) { + toast.success("중수도 건축물 수정이 완료되었습니다."); const rswtRes = await initFetch(code); set((state: rswtFacState) => ({ ...state, @@ -208,10 +166,8 @@ export const rswtFacActionsExport: (set: any) => rswtFacSActions = ( })); } } catch (errorUpdate) { - alert( - "업데이트 오류입니다. " + - (errorUpdate instanceof Error ? errorUpdate.message : "") - ); + toast.error("중수도 건축물 수정 오류입니다. "); + } finally { } }, @@ -219,29 +175,28 @@ export const rswtFacActionsExport: (set: any) => rswtFacSActions = ( try { const chData = await makeRstr(data); const res = await postFectch("/reuseFc/waterreuseFc", chData); - if (res.ok) { - 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, - bdstInfoFk: chPK, - }) - ); - const chch = postFectch("/reuseFc/chgBuilding", makeCh); - return chch; - }) - .then((chch: any) => { - if (chch.ok) { - alert("등록이 완료되었습니다."); - } - }); + if (res.status===200) { + if(data.chgBuilding.length > 0){ + + const chPK = res?.data?.bdstInfoPk + const makeCh= res.data.chgBuilding.map((item: chgBuilding) => + ( { + ...item, + bdstInfoFk: chPK, + }) + ); + const chch= await postFectch("/reuseFc/chgBuilding", makeCh); + + if(chch.status ===200) { + toast.success("건축물 등록이 완료되었습니다."); + }else{ + toast.error("건축물 증축/개축/중축 오류입니다."); + } } else { - alert("등록이 완료되었습니다."); + toast.success("건축물 등록이 완료되었습니다."); } + }else{ + toast.error("건축물 등록이 완료되었습니다."); } const rainRes = await initFetch(code); set((state: rswtFacState) => ({ @@ -254,7 +209,7 @@ export const rswtFacActionsExport: (set: any) => rswtFacSActions = ( })); } catch (e) { console.log("e", e); - alert("등록 오류입니다."); + toast.error("등록시 오류가 발생하였습니다."); } finally { } }, diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/htdflSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/htdflSttn/index.tsx index dccc8719c..eb37d0d73 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/htdflSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/htdflSttn/index.tsx @@ -5,6 +5,7 @@ import type { z } from "zod"; import { controlHeadView } from "../rainSttn"; import type { customHtdflBdst } from "../../fcltReg/htdflReg"; import { fetchType, getFetch } from "@/components/lib/api"; +import type { SelectDataType } from "@common/business_components/ui"; const htdflSttHeadList = htdflSttHeadListExport; const htdflSttNodeList = htdflSttNodeListExport; @@ -20,8 +21,12 @@ export type htdflSttnState = { }; }; type htdflSttnActions = { - initHtdfl: (con: string) => void; - htdflSttnSearch: (page: number, total: string, con: string) => void; + initHtdfl: (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => void; + htdflSttnSearch: (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => void; htdflChSttnHead: ( headList: z.infer<typeof sttnHeadListType>, ) => void; @@ -38,10 +43,17 @@ export const htdflSttnStateExport: htdflSttnState = { isInit: false, }, }; -const initFetch = async (code: string | number) => { +const initFetch = async (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => { const htdflRes = await getFetch( "wr", - `/drain/facilityList/1/20/${code}`) + `/drain/facilityList` + + (page ? `/${page}` : "/1") + + (total ? `/${total}` : "/20") + + (con ? `/${con}` : "/0") + + (txt ? `/${txt}` : "") + ); return htdflRes; }; @@ -50,9 +62,11 @@ export const htdflSttnActionsExport: (set: any) => htdflSttnActions = ( set: any ) => { return { - initHtdfl: async (code: string | number) => { + initHtdfl: async (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => { try { - const htdflSttnRes = await initFetch(code); + const htdflSttnRes = await initFetch(page, total, con, txt, sidoCode, sggCode); set((state: htdflSttnState) => { return { ...state, @@ -67,17 +81,22 @@ export const htdflSttnActionsExport: (set: any) => htdflSttnActions = ( console.log(error); } }, - htdflSttnSearch: async (page: number, total: string, con: string) => { + htdflSttnSearch: async (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => { try { + console.log("page", page) const htdflSttnRes = await getFetch( fetchType.WR, - `/drain/facilityList/${page}/${total}/${con}` + `/drain/facilityList` + + (page ? `/${page}` : "/1") + + (total ? `/${total}` : "/20") + + (con ? `/${con}` : "/0") + + (txt ? `/${txt}` : "") ); + if (Number(total) > 9000) { - - set((state: htdflSttnState) => { - return { ...state, htdflSttn: { diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/rainSttn/headList.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/rainSttn/headList.tsx index 7db00678d..8dd2a4d6f 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/rainSttn/headList.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/rainSttn/headList.tsx @@ -31,8 +31,8 @@ export const rainSttHeadListExport: z.infer<typeof sttnHeadListType> = [ visible: true, }, { - id: "bdstUsgCd", - title: "건축물용도코드", + id: "bdsgUsgText", + title: "건축물용도", visible: true, }, { @@ -209,8 +209,8 @@ export const rainSttNodeListExport: CommonTableProps[] = [ colSpan: 1, }, { - id: "bdstUsgCd", - title: "건축물용도코드", + id: "bdsgUsgText", + title: "건축물용도", upName: "bdnstInfo", visible: true, row: 2, diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/rainSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/rainSttn/index.tsx index 5aa54ee48..eb2c08d37 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/rainSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/rainSttn/index.tsx @@ -1,4 +1,4 @@ -import type { rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; +import type { rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; import { fetchType, getFetch } from "@/components/lib/api"; import { type customRainBdst, @@ -16,34 +16,34 @@ import type { CommonTableProps } from "@/app/store/common/type/header"; import type { SelectDataType } from "@common/business_components/ui"; // 테이블 헤드 리스트 -const rainSttHeadList = rainSttHeadListExport; +const rainSttHeadList = rainSttHeadListExport; const rainSttNodeList = rainSttNodeListExport; const rainSttnDetailNodeList = rainSttnDetailNodeListExport; const rainSttnDetailHeadList = rainSttnDetailHeadListExport; const rainSttnRes: customRainBdst[] = []; -const rainChkRes : rainChkZodType ={} as rainChkZodType +const rainChkRes: rainChkZodType = {} as rainChkZodType -const visibleHeadList = (falseHeads:CommonTableProps[], orgNodeList:CommonTableProps[])=>{ +const visibleHeadList = (falseHeads: CommonTableProps[], orgNodeList: CommonTableProps[]) => { const visibleHeads = orgNodeList.map((node) => { - const copyNode = {...node} - falseHeads.forEach((head)=>{ - if(copyNode.id ===head.id) - copyNode.visible = head.visible + const copyNode = { ...node } + falseHeads.forEach((head) => { + if (copyNode.id === head.id) + copyNode.visible = head.visible }) return copyNode; }) return visibleHeads } -const getColspanReductionMap = (falseHeads:CommonTableProps[], orgList:CommonTableProps[]):Record<string, number> => { - const reduceMap:Record<string, number> = {}; +const getColspanReductionMap = (falseHeads: CommonTableProps[], orgList: CommonTableProps[]): Record<string, number> => { + const reduceMap: Record<string, number> = {}; falseHeads.forEach(head => { const node = orgList.find(n => n.id === head.id); if (node?.upName) { reduceMap[node.upName] = (reduceMap[node.upName] || 0) + 1; - if (node.row&& node.row >= 3) { + if (node.row && node.row >= 3) { const upperUpName = orgList.find(n => n.id === node.upName)?.upName; if (upperUpName) { reduceMap[upperUpName] = (reduceMap[upperUpName] || 0) + 1; @@ -78,35 +78,37 @@ const applyColspanReduction = ( }); }; // 일단 헤더가 3개 이상인경우 visible 처리를 위해서 공통함수로 작업 -export const controlHeadView = (headList:CommonTableProps[], orgNodeList:CommonTableProps[] ) =>{ +export const controlHeadView = (headList: CommonTableProps[], orgNodeList: CommonTableProps[]) => { const falseHeads = headList.filter((item) => item.visible == false); const viewHeadList = visibleHeadList(falseHeads, orgNodeList) - const reduceMap= getColspanReductionMap( falseHeads,viewHeadList) - return applyColspanReduction(viewHeadList,reduceMap); + const reduceMap = getColspanReductionMap(falseHeads, viewHeadList) + return applyColspanReduction(viewHeadList, reduceMap); } - - export type rainSttnState = { rainSttn: { rainSttnRes: customRainBdst[]; rainSttHeadList: CommonTableProps[]; rainSttNodeList: CommonTableProps[]; - rainSttnDetailHeadList:CommonTableProps[] - rainSttnDetailNodeList:CommonTableProps[] - rainChkRes:rainChkZodType + rainSttnDetailHeadList: CommonTableProps[] + rainSttnDetailNodeList: CommonTableProps[] + rainChkRes: rainChkZodType excelList: []; isInit: boolean; }; }; type rainSttnActions = { - rainSttnInit:(con:string) => void - rainSttnSearch: (page: number, total: string, con: string, sidoCode: SelectDataType[], sggCode: SelectDataType[]) => void; + rainSttnInit: (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => void + rainSttnSearch: (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => void; rainChSttnHead: ( headList: z.infer<typeof sttnHeadListType>, ) => void; - rainSttnChkSearch:(rusePk:string) => void; + rainSttnChkSearch: (rusePk: string) => void; }; export type rainSttnType = rainSttnState & rainSttnActions; @@ -115,10 +117,10 @@ export const rainSttnStateExport: rainSttnState = { rainSttn: { rainSttHeadList: rainSttHeadList, rainSttNodeList: rainSttNodeList, - rainSttnDetailHeadList:rainSttnDetailHeadList, - rainSttnDetailNodeList:rainSttnDetailNodeList, + rainSttnDetailHeadList: rainSttnDetailHeadList, + rainSttnDetailNodeList: rainSttnDetailNodeList, rainSttnRes: rainSttnRes, - rainChkRes:rainChkRes, + rainChkRes: rainChkRes, excelList: [], isInit: false, }, @@ -128,12 +130,18 @@ export const rainSttnActionsExport: (set: any) => rainSttnActions = ( set: any ) => { return { - rainSttnInit: async( con:string)=>{ - try{ + rainSttnInit: async (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => { + try { - const rainSttnRes = await getFetch( + const rainSttnRes = await getFetch( fetchType.WR, - `/reuseFc/rainyInfoList/1/20/${con}` + `/reuseFc/rainyInfoList` + + (page ? `/${page}` : "/1") + + (total ? `/${total}` : "/20") + + (con ? `/${con}` : "/0") + + (txt ? `/${txt}` : "") ); set((state: rainSttnState) => { return { @@ -145,17 +153,21 @@ export const rainSttnActionsExport: (set: any) => rainSttnActions = ( }, }; }); - }catch(e){ + } catch (e) { console.log(e) } }, - rainSttnSearch: async (page: number, total: string, con: string, - sidoCode: SelectDataType[], - sggCode: SelectDataType[]) => { + rainSttnSearch: async (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => { try { const rainSttnRes = await getFetch( fetchType.WR, - `/reuseFc/rainyInfoList/${page}/${total}/${con}` + `/reuseFc/rainyInfoList` + + (page ? `/${page}` : "/1") + + (total ? `/${total}` : "/20") + + (con ? `/${con}` : "/0") + + (txt ? `/${txt}` : "") ); if (Number(total) > 9000) { const rainExcel = excelRainSttnParsing(rainSttnRes, sidoCode, sggCode); @@ -186,7 +198,7 @@ export const rainSttnActionsExport: (set: any) => rainSttnActions = ( rainChSttnHead: async ( headList: z.infer<typeof sttnHeadListType> ) => { - const changeNodeList= controlHeadView(headList,rainSttNodeList) + const changeNodeList = controlHeadView(headList, rainSttNodeList) return set((state: rainSttnState) => ({ rainSttn: { @@ -196,21 +208,21 @@ export const rainSttnActionsExport: (set: any) => rainSttnActions = ( }, })); }, - rainSttnChkSearch: async( - rusePk:string - )=>{ + rainSttnChkSearch: async ( + rusePk: string + ) => { const rainChkRes = await getFetch( fetchType.WR, `/reuseChk/rainy/${rusePk}` ); - - return set((state: rainSttnState) =>({ + + return set((state: rainSttnState) => ({ rainSttn: { ...state.rainSttn, - rainChkRes:rainChkRes + rainChkRes: rainChkRes }, - }) ) + })) } }; diff --git a/apps/wr/app/store/ruseFcltMng/fcltSttn/rswtSttn/index.tsx b/apps/wr/app/store/ruseFcltMng/fcltSttn/rswtSttn/index.tsx index 301cd965e..8fbe2c26d 100644 --- a/apps/wr/app/store/ruseFcltMng/fcltSttn/rswtSttn/index.tsx +++ b/apps/wr/app/store/ruseFcltMng/fcltSttn/rswtSttn/index.tsx @@ -28,9 +28,12 @@ export type rswtSttnState = { }; type rswtSttnActions = { - rswtSttnInit: (con: string) => void; - rswtSttnSearch: (page: number, total: string, con: string, sidoCode: SelectDataType[], - sggCode: SelectDataType[]) => void; + rswtSttnInit: (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => void; + rswtSttnSearch: (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => void; rswtChSttnHead: ( headList: z.infer<typeof sttnHeadListType>, ) => void; @@ -54,12 +57,18 @@ export const rswtSttnActionsExport: (set: any) => rswtSttnActions = ( set: any ) => { return { - rswtSttnInit: async (con: string) => { + rswtSttnInit: async (page: number, total: string, con: string, txt: string, + sidoCode: SelectDataType[], + sggCode: SelectDataType[]) => { try { const rswtSttnRes = await getFetch( fetchType.WR, - `/reuseFc/waterreuseFc/1/20/${con}` + `/reuseFc/waterreuseFc` + + (page ? `/${page}` : "/1") + + (total ? `/${total}` : "/20") + + (con ? `/${con}` : "/0") + + (txt ? `/${txt}` : "") ); set((state: rswtSttnState) => { return { @@ -75,13 +84,18 @@ export const rswtSttnActionsExport: (set: any) => rswtSttnActions = ( console.log(e) } }, - rswtSttnSearch: async (page: number, total: string, con: string, + rswtSttnSearch: async (page: number, total: string, con: string, txt: string, sidoCode: SelectDataType[], sggCode: SelectDataType[]) => { try { + console.log("헐", page) const rswtSttnRes = await getFetch( fetchType.WR, - `/reuseFc/waterreuseFc/${page}/${total}/${con}` + `/reuseFc/waterreuseFc` + + (page ? `/${page}` : "/1") + + (total ? `/${total}` : "/20") + + (con ? `/${con}` : "/0") + + (txt ? `/${txt}` : "") ); if (Number(total) > 9000) { const rswtExcel = excelRSwtSttnParsing(rswtSttnRes, sidoCode, sggCode); diff --git a/apps/wr/app/store/store.tsx b/apps/wr/app/store/store.tsx index 4557662f8..7520f7998 100644 --- a/apps/wr/app/store/store.tsx +++ b/apps/wr/app/store/store.tsx @@ -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), diff --git a/apps/wr/components/lib/func/TableData.tsx b/apps/wr/components/lib/func/TableData.tsx index dbb3aa7a6..8bed17187 100644 --- a/apps/wr/components/lib/func/TableData.tsx +++ b/apps/wr/components/lib/func/TableData.tsx @@ -2,6 +2,7 @@ import type { TableUpperProps } from "@/app/store/common/type/header"; import type { rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; import type { rswtChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn"; import { Checkbox, Input, TableCell, TableRow } from "@common/components/ui"; +import { useEffect } from "react"; export const rainChkTableData = (chkData: rainChkZodType[]) => { // 1. chkgdnFk + utztnUsgCd 기준 누계 정보 만들기 @@ -54,27 +55,32 @@ export const rainChkTableData = (chkData: rainChkZodType[]) => { export const rswtChkTableData = (chkData: rswtChkZodType[]) => { // 1. chkgdnFk + utztnUsgCd 기준 누계 정보 만들기 const makeRswtData: any[] = []; - chkData.forEach((root) => { - root.rswtrDetails.forEach((detail) => { - let flatRswtChk = {}; - const findGuide = root.guideDetails.find( - (guide) => guide.utztnUsgCd === detail.utztnUsgCd - ); - const findPoluttion = root.rswtrPollutions.find( - (polution) => polution.utztnUsgCd === detail.utztnUsgCd - ); - const operMngCd = root?.reuseBuilding.operMngCd; - flatRswtChk = { - ...root, - ...detail, - ...findGuide, - ...findPoluttion, - ...flatRswtChk, - operMngCd: operMngCd, - }; - makeRswtData.push(flatRswtChk); + + + if(chkData&&chkData.length>0){ + + chkData?.forEach((root) => { + root.rswtrDetails.forEach((detail) => { + let flatRswtChk = {}; + const findGuide = root.guideDetails.find( + (guide) => guide.utztnUsgCd === detail.utztnUsgCd + ); + const findPoluttion = root.rswtrPollutions.find( + (polution) => polution.utztnUsgCd === detail.utztnUsgCd + ); + const operMngCd = root?.reuseBuilding.operMngCd; + flatRswtChk = { + ...root, + ...detail, + ...findGuide, + ...findPoluttion, + ...flatRswtChk, + operMngCd: operMngCd, + }; + makeRswtData.push(flatRswtChk); + }); }); - }); + } return makeRswtData; }; @@ -93,6 +99,7 @@ export const chRowspanTable = ( let order = 0 // rowspan된 셀 렌더링 여부 추적용 const renderedMap = new Map<string, Set<number>>(); + return ( <> {makeTableData.map((row, rowIndex) => ( @@ -103,6 +110,7 @@ export const chRowspanTable = ( const key = head.id; const isMerged = head.mergeCell; const value = row[key]; + // 병합 대상이면 중복 렌더링 여부 판단 if (isMerged) { const renderedSet = renderedMap.get(key) ?? new Set<number>(); @@ -152,7 +160,7 @@ export const chRowspanTable = ( onChange={(e) => { handleChange(rowIndex, head.id, e.target.value); }} - value={value ?? ""} + value={value || ""} disabled={!selectedIds.includes(row.chkgdnPk)} /> ) : ( @@ -174,8 +182,6 @@ export const chRowspanTable = ( ); }; - - export const headVisibleChange = (headList: TableUpperProps[], isEdit: boolean) => { return headList.forEach((head) => { if (head.edit !== undefined) @@ -187,63 +193,123 @@ export const headVisibleChange = (headList: TableUpperProps[], isEdit: boolean) }); } - const handleChange = ( rowIndex: number, field: any, value: string | number, - setInputData: (data:any) => void + setInputData: (data: any) => void ) => { - if(Number(value) < 0 ||! Number(value)){ - value = 0; - }else{ + if (Number(value) < 0 || !Number(value)) { + value = 0; + } else { value = Number(value); } - setInputData((prevData:any) => - prevData?.map((row:any, index: number) => { - if (index === rowIndex) { - return { - ...row, - [field ]: value, - }; - } - return row; - }) - ); + setInputData((prevData: any) => + prevData?.map((row: any, index: number) => { + if (index === rowIndex) { + return { + ...row, + [field]: value, + }; + } + return row; + }) + ); }; ; - - -export const InputTableBody = (chgRes: any[] | [] ,headList:TableUpperProps[], setInputData: (data: any[]) => void) => { - return ( +export const InputTableBody = ( + chgRes: any[] | [], + headList: TableUpperProps[], + setInputData: (data: any[]) => void +) => { + // mergeCell 처리용 + const getRowSpan = (data: any[], key: string, rowIndex: number) => { + if (rowIndex > 0 && data[rowIndex][key] === data[rowIndex - 1][key]) { + return 0; // 이전과 같으면 셀을 그리지 않음 + } + let span = 1; + for (let i = rowIndex + 1; i < data.length; i++) { + if (data[i][key] === data[rowIndex][key]) span++; + else break; + } + return span; + }; + return ( <> - {chgRes.map((data, dataIndex) => ( - <TableRow key={dataIndex + "row"}> - {headList.map((head, headIndex) => ( - <TableCell key={head.id + headIndex} className="w-36" > - {head.edit ===true && data.edit !==false? - <Input - className={`${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} !text-xl h-10 w-full`} - onChange={(e) => - handleChange(dataIndex, head.id, e.target.value,setInputData) - } - value={data[head.id as keyof any] ?? ""} - /> : ( - <div - className={` - ${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} - !text-xl w-full h-10 - `} + {chgRes.map((data, dataIndex) => { + // 소계/총합계 행 판별 + const isSubTotal = data["용수명"] === "소계"; + const isTotal = data["구분"] === 0; + + const bgClass = isTotal + ? "bg-[#d5e1eb] font-bold" + : isSubTotal + ? "bg-[#e6eef5] font-semibold" + : ""; + + return ( + <TableRow key={dataIndex + "row"}> + {headList.map((head, headIndex) => { + // 총합계 행이면 "구분"에서 colSpan=2로 병합 + if ( + data["구분"] === 0 && + head.id === "구분" + ) { + return ( + <TableCell + key={head.id + headIndex} + colSpan={2} + className="font-bold text-center bg-[#d5e1eb]" > - {data[head.id as keyof any] ?? ""} - </div> - ) + 총합계 + </TableCell> + ); + } + // 총합계(합계) 행이면 "용수명" 컬럼은 렌더링하지 않음 + if ( + data["구분"] === 0 && + head.id === "용수명" + ) { + return null; + } + // mergeCell 처리 + if (head.mergeCell) { + const rowSpan = getRowSpan(chgRes, head.id, dataIndex); + if (rowSpan === 0) return null; + return ( + <TableCell key={head.id + headIndex} rowSpan={rowSpan} className={`w-36 align-middle text-center whitespace-nowrap ${bgClass}`}> + {data[head.id as keyof any] ?? ""} + </TableCell> + ); } - </TableCell> - ))} - </TableRow> - ))} + // 기본 셀 + return head.edit === true && data.edit !== false ? ( + <TableCell key={head.id + headIndex} className={`w-36 ${bgClass}`}> + <Input + className={`${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} !text-xl h-10 w-full`} + onChange={(e) => + handleChange(dataIndex, head.id, e.target.value, setInputData) + } + value={data[head.id as keyof any] ?? ""} + /> + </TableCell> + ) : ( + <TableCell key={head.id + headIndex} className={`w-36 ${bgClass}`}> + <div + className={` + ${head.id !== "utztnUsgCd" ? "text-right" : "text-center"} + !text-xl w-full h-10 + `} + > + {data[head.id as keyof any] ?? ""} + </div> + </TableCell> + ); + })} + </TableRow> + ); + })} </> ); -} \ No newline at end of file +}; \ No newline at end of file diff --git a/apps/wr/components/lib/func/commom.tsx b/apps/wr/components/lib/func/commom.tsx index e5dfc8c0c..7d8c2ffcc 100644 --- a/apps/wr/components/lib/func/commom.tsx +++ b/apps/wr/components/lib/func/commom.tsx @@ -9,12 +9,22 @@ import { import type { RefObject } from "react"; export const getCity = (val: string, sggCd: SelectDataType[]) => { - const data = sggCd - .filter((item) => String(item.val).substring(0, 2) === val) + + let data:any + if(val !=='0'){ + data = sggCd + .filter((item) => String(item.val).substring(0, 2) === val) + .map((item) => ({ + val: item.val, + text: item.text, // 한국어 텍스트 + })); + }else{ + data = sggCd .map((item) => ({ val: item.val, text: item.text, // 한국어 텍스트 })); + } data.unshift({ val: "0", text: "전체" }); return data; }; @@ -103,7 +113,18 @@ export const handleCommaNumberChange = ( ) => { const raw = e.target.value.replace(/,/g, ""); if (!/^\d*$/.test(raw)) return; - onChange(raw === "" ? "" : Number(raw)); + onChange(raw === "" ? "" : Number(raw)); +}; + + +// onchange 텍스트를 숫자로 and ,제거 +export const handleMinusNumberChange = ( + e: React.ChangeEvent<HTMLInputElement>, + onChange: (value: number | "") => void +) => { + const raw = e.target.value.replace(/,/g, ""); + if (!/^\d*$/.test(raw)) return; + onChange(raw === "" ? -1 : Number(raw)); }; // 숫자입력시 , 삽입 @@ -144,18 +165,30 @@ export const yearOptions: { val: string; text: string }[] = Array.from( }) ); -export const getdmdMatFlag = (flag: string|undefined) => { - let flagMat = ""; +export const getdmdMatFlag = (flag: string | undefined | null) => { + const cleanFlag = (flag ?? "").trim(); - if(flag === undefined || flag === null) { - flagMat = "신청"; - }else if(flag.trim() === "Sub") { - flagMat = "검토"; - }else if(flag.trim() === "matched") { - flagMat = "매칭완료"; - }else { - flagMat = "신청"; + switch (cleanFlag) { + case "Sub": + return "검토"; + case "Matched": + return "매칭완료"; + case "Checked": + return '검토완료'; + default: + return "신청"; } - return flagMat; }; + + +export const formatPhoneNumber=(tel:string) => { + const onlyNums = tel.replace(/[^0-9]/g, ''); + if (onlyNums.length === 10) { + return onlyNums.replace(/(\d{2})(\d{4})(\d{4})/, '$1-$2-$3'); + } else if (onlyNums.length === 11) { + return onlyNums.replace(/(\d{3})(\d{4})(\d{4})/, '$1-$2-$3'); + } else { + return tel; // 유효하지 않으면 빈 문자열 + } +} \ No newline at end of file diff --git a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx index b22f9474e..ee06893c9 100644 --- a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx +++ b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx @@ -22,8 +22,7 @@ import { UiTable, } from "@common/business_components/ui"; import PopoverUi from "@common/business_components/ui/popover_ui"; -import { Button, Input, Label } from "@common/components/ui"; -import type { read } from "fs"; +import { Button, Checkbox, Input, Label } from "@common/components/ui"; import { useCallback, useEffect, useRef, useState } from "react"; import { type UseFormReturn } from "react-hook-form"; import toast from "react-hot-toast"; @@ -31,9 +30,11 @@ import toast from "react-hot-toast"; export default function ConCostForm({ form, type, + selectionVersion, }: { form: UseFormReturn<DmdCoReqType>; type: string; + selectionVersion: number; }) { const { constCostRes, @@ -46,6 +47,8 @@ export default function ConCostForm({ const ref = useRef<boolean>(false); + const [checkFinish, setCheckFinish] = useState<boolean>(false); + useEffect(() => { if (!ref.current) { const currentYear = new Date().getFullYear(); @@ -55,6 +58,38 @@ export default function ConCostForm({ } }, [ref, getConstCostFee, getRateFee]); + + useEffect(() => { + // 다른 행을 선택하면(selectionVersion 변경) 체크박스 상태를 초기화합니다. + setCheckFinish(false); + }, [selectionVersion]); + + + const confrirmToast = () => { + + if (checkFinish) { + toast((t) => ( + <span className="flex flex-col items-center"> + 검토완료 변경시 공사비는 변경할수 없습니다. + <br /> + 진행하시겠습니까? + <Button + onClick={() => { + toast.dismiss(t.id); + + + }} + > + 확인 + </Button> + <Button onClick={() => toast.dismiss(t.id)}>취소</Button> + </span> + )); + } else { + + } + }; + const zipDowonLoad = () => { fetch(`/2024_constarctFeeProgram.zip`, {}).then(async (response) => { const blobRet = await response.blob(); @@ -70,26 +105,40 @@ export default function ConCostForm({ const dmndQtyWatch = form.watch("dmndQty"); const totalConstCost = form.watch("totalConstCost"); const cstrnCst = form.watch("demandCst.cstrnCst"); + const useUsgCdWatch = form.watch("useUsgCd"); const calculateConstCost = useCallback(() => { const usgPrcsProcs = usgPrcsProcsWatch; + if (!dmndQtyWatch) { + toast.error("요구수량(M3/d)을 확인해주세요 계산할수 없습니다. "); + return; + } + if (usgPrcsProcs === null || usgPrcsProcs === undefined) { + toast.error( + "용도에 따른 처리공정을 확인할수 없습니다. 용도를 확인해주세요. " + ); + return; + } + const dmndQtyYear = dmndQtyWatch * 365; if (usgPrcsProcs && usgPrcsProcs?.length > 0) { - const firstProc = getConstFctCode(usgPrcsProcs?.split("+")[0] || "") || ""; - const secondProc = getConstFctCode(usgPrcsProcs?.split("+")[1] || "") || ""; + const firstProc = + getConstFctCode(usgPrcsProcs?.split("+")[0] || "") || ""; + const secondProc = + getConstFctCode(usgPrcsProcs?.split("+")[1] || "") || ""; const { lower: firstLower, upper: firstUpper } = findBounds( constCostRes, firstProc || "", - 'fctCd', - 'fctCpc', + "fctCd", + "fctCpc", Number(dmndQtyYear) ); const { lower: secondLower, upper: secondUpper } = findBounds( constCostRes, secondProc || "", - 'fctCd', - 'fctCpc', + "fctCd", + "fctCpc", Number(dmndQtyYear) ); @@ -108,48 +157,86 @@ export default function ConCostForm({ const groupCodes = ["BD", "CE", "SE", "WD"]; const groupedBounds: { [key: string]: { lower?: any; upper?: any } } = {}; for (const code of groupCodes) { - - groupedBounds[code] = findBounds(rateFeeRes, code, 'incExpCd', 'stdCost',result); + groupedBounds[code] = findBounds( + rateFeeRes, + code, + "incExpCd", + "stdCost", + result + ); } const resultRate = transformAndInterpolate(groupedBounds, result); - const fcltSbsdCstrnCst = (result* (resultRate.BD + resultRate.CE + resultRate.SE + resultRate.WD) / 100); + const fcltSbsdCstrnCst = + (result * + (resultRate.BD + resultRate.CE + resultRate.SE + resultRate.WD)) / + 100; const realResult = result + fcltSbsdCstrnCst; - form.setValue("demandCst.prcsFcltCpctCstrnCst", Math.round(result) ); - form.setValue("demandCst.fcltSbsdCstrnCst", Math.round(fcltSbsdCstrnCst)) ; - form.setValue("totalConstCost", Math.round(realResult)); + form.setValue("demandCst.prcsFcltCpctCstrnCst", Math.round(result)); + form.setValue("demandCst.fcltSbsdCstrnCst", Math.round(fcltSbsdCstrnCst)); + form.setValue("totalConstCost", Math.round(realResult)); } }, [constCostRes, rateFeeRes, usgPrcsProcsWatch, dmndQtyWatch]); const onSubmit = (value: DmdCoReqType) => { - - if( !form.getValues('demandCst.prcsFcltCpctCstrnCst') ){ - toast.error('공사비 계산 진행 후 저장을 눌러주새요') - return + if (value.demandCst && !value.demandCst.prcsFcltCpctCstrnCst) { + toast.error("공사비 계산 진행 후 저장을 눌러주새요"); + return; } if (type === "mat") { - const data: DedMatUpdateType = { - dmdInfoSn: form.getValues("dmdInfoSn"), - usgPrcsProcs: form.getValues("demandCst.usgPrcsProcs"), - cstrnCst: form.getValues("demandCst.cstrnCst"), - fcltSbsdCstrnCst: form.getValues("demandCst.fcltSbsdCstrnCst"), - prcsFcltCpctCstrnCst: form.getValues("demandCst.prcsFcltCpctCstrnCst"), - rgtr: TEST_UUID, - }; - coMatRvwUpdate("11000SW001R1", form.getValues("dmdInfoFk"), data); + if (value.demandCst) { + const data: DedMatUpdateType = { + dmdInfoSn: value.dmdInfoSn, + usgPrcsProcs: value.demandCst.usgPrcsProcs || "", + cstrnCst: value.demandCst.cstrnCst || 0, + fcltSbsdCstrnCst: value.demandCst.fcltSbsdCstrnCst || 0, + prcsFcltCpctCstrnCst: value.demandCst.prcsFcltCpctCstrnCst || 0, + flg: checkFinish?'Checked':'Sub', + rgtr: TEST_UUID, + }; + if (checkFinish) { + toast((t) => ( + <span className="flex flex-col items-center text-2xl" > + 검토완료 변경시 공사비는 변경할수 없습니다. + <br /> + 진행하시겠습니까? + <div className="flex gap-2 mt-2"> + <Button + size='sm' + + onClick={() => { + toast.dismiss(t.id); + coMatRvwUpdate("11000SW001R1", form.getValues("dmdInfoFk"), data); + }} + > + 확인 + </Button> + <Button + size='sm' + onClick={() => toast.dismiss(t.id)}>취소</Button> + </div> + </span> + )); + } else { + coMatRvwUpdate("11000SW001R1", form.getValues("dmdInfoFk"), data); + } + } } else if (type === "hist") { - - delete value.totalConstCost - delete value.ruseUtztnFcltNm - - + delete value.totalConstCost; + delete value.ruseUtztnFcltNm; ruseRvwCalUpdate(value); } }; useEffect(() => { - console.log(" form ", form.formState.errors); + console.log(" form ", form.formState.errors); }, [form.formState]); + useEffect(() => { + form.register("dmdInfoSn"); + form.register("dmdInfoFk"); + form.register("ruseUtztnFcltNm"); + }, []); + return ( <> <hr className="mt-8" /> @@ -163,18 +250,30 @@ export default function ConCostForm({ <Button className="mx-3" size={"sm"} onClick={calculateConstCost}> 계산 </Button> - <Button className="mr-3" size={"sm"} onClick={form.handleSubmit(onSubmit)}> + <Button + className="mr-3" + size={"sm"} + onClick={form.handleSubmit(onSubmit)} + > 저장 </Button> - <Label>관로 표준공사비 산정 프로그램</Label> - <Button - color="green" - size={"sm"} - onClick={zipDowonLoad} - className="bg-none ml-6" - > - 다운로드 - </Button> + <Label>검토완료</Label> + <Checkbox + className="ml-3 mr-6" + checked={checkFinish} + onCheckedChange={(checked) => + // 'indeterminate' 상태를 무시하고 순수 boolean 값으로만 상태를 업데이트합니다. + setCheckFinish(checked === true)} + ></Checkbox> + <Label>관로 표준공사비 산정 프로그램</Label> + <Button + color="green" + size={"sm"} + onClick={zipDowonLoad} + className="bg-none ml-6" + > + 다운로드 + </Button> </div> <div className="col-span-12 w-full"> <h1 className={formH1Class}>기본사항</h1> @@ -255,7 +354,7 @@ export default function ConCostForm({ placeholder="" type="text" onChange={field.onChange} - value={form.getValues("demandCst.usgPrcsProcs") ?? ""} + value={field.value ?? ""} className="mt-2" /> </FormUi> @@ -285,13 +384,15 @@ export default function ConCostForm({ <div className="col-span-12 w-full"> <h1 className={formH1Class}>공사비 입력사항</h1> </div> - <div className="col-span-3 mt-4"> + + <div className="col-span-2 mt-4"> <div className="flex items-center justify-between"> <PopoverUi childrenTriger={ <> <Label className="underline"> - 처리시설용량에 따른 공사비(백만원) <i className="diveicon di-search" /> + 처리시설용량에 따른 공사비(백만원){" "} + <i className="diveicon di-search" /> </Label> </> } @@ -324,7 +425,7 @@ export default function ConCostForm({ )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <div className="flex items-center justify-between"> <PopoverUi childrenTriger={ @@ -363,12 +464,13 @@ export default function ConCostForm({ )} /> </div> - <div className="col-span-3 mt-4"> + <div className="col-span-2 mt-4"> <PopoverUi childrenTriger={ <> <Label className="underline"> - 총사업비 (백만원) <i className="diveicon di-search" /> + 처리시설 총사업비(백만원){" "} + <i className="diveicon di-search" /> </Label> </> } @@ -405,7 +507,7 @@ export default function ConCostForm({ )} /> </div> - <div className="col-span-3 mt-4"> + {/* <div className="col-span-3 mt-4"> <div className="flex items-center justify-between"> <Label>공급처 선정</Label> <Button size={"sm"} onClick={(e) => {}} className="bg-none"> @@ -428,8 +530,8 @@ export default function ConCostForm({ </FormUi> )} /> - </div> - <div className="col-span-4 mt-4"> + </div> */} + <div className="col-span-3 mt-4"> <FormField control={form.control} name="demandCst.cstrnCst" @@ -448,7 +550,7 @@ export default function ConCostForm({ )} /> </div> - <div className="col-span-4 mt-4 mb-3"> + <div className="col-span-3 mt-4 mb-3"> <Label className="text-xl"> 처리시설 + 공급관로 총사업비(백만원) </Label> diff --git a/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx b/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx index 5ab5edf98..31e8dbacb 100644 --- a/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx +++ b/apps/wr/components/ui/form/fcltReg/bdstReg/bdstReg.tsx @@ -16,11 +16,10 @@ import { type SelectDataType, } from "@common/business_components"; import { Button, Checkbox, Input } from "@common/components"; -import { Check } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { useFieldArray } from "react-hook-form"; -export default function BdstReg({ form }: { form: any }) { +export default function BdstReg({ form, type }: { form: any; type?: string }) { const { code } = useWrStore((state) => { return state; }); @@ -37,23 +36,16 @@ export default function BdstReg({ form }: { form: any }) { bdstUsgCd: "", }); }; -// 잠깐 주석처리 - // const removeLastItem = () => { - // if (fields.length > 0) { - // remove(fields.length - 1); - // } - // }; const removeSelected = () => { - const idxsToRemove = fields - .map((item, idx) => selectedIds.includes(item.id) ? idx : -1) - .filter(index => index > -1) - .sort((prevIndex, nextIndex) => nextIndex - prevIndex); + const idxsToRemove = fields + .map((item, idx) => (selectedIds.includes(item.id) ? idx : -1)) + .filter((index) => index > -1) + .sort((prevIndex, nextIndex) => nextIndex - prevIndex); remove(idxsToRemove); setSelectedIds([]); }; - const [selectSgg, setSelectSgg] = useState<SelectDataType[] | undefined>(); const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", @@ -90,18 +82,14 @@ export default function BdstReg({ form }: { form: any }) { const [selectedIds, setSelectedIds] = useState<string[]>([]); const toggleSelect = (id: string) => { - setSelectedIds(prev => - prev.includes(id) - ? prev.filter(x => x !== id) - : [...prev, id] + setSelectedIds((prev) => + prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id] ); }; return ( <> - <h1 className={formH1Class}> - 건축물 시설 정보 - </h1> + <h1 className={formH1Class}> { type !== "HTB"?"건축물 기본 정보": '발전소 기본 정보'}</h1> <div className="grid grid-cols-6 gap-2 mt-4"> <div className="col-span-3"> <FormField @@ -152,10 +140,10 @@ export default function BdstReg({ form }: { form: any }) { control={form.control} name="bdstNm" render={({ field }) => ( - <FormUi label="건축물명"> + <FormUi label={ type !== "HTB"?"건축물명": '발전소명'}> <Input className={selectClass} - placeholder="1,000,000" + placeholder= {type !== "HTB"?"건축물 명 입력하세요":'발전소명 입력하세요' } type="text" {...field} /> @@ -168,10 +156,10 @@ export default function BdstReg({ form }: { form: any }) { control={form.control} name="daddr" render={({ field }) => ( - <FormUi label="위치"> + <FormUi label= { type !== "HTB"?"건축물 주소": '발전소 주소'} > <Input className={selectClass} - placeholder="1,000,000" + placeholder="위치를 입력해주세요" type="search" {...field} /> @@ -179,162 +167,176 @@ export default function BdstReg({ form }: { form: any }) { )} /> </div> - <div className="col-span-2"> - <FormField - control={form.control} - name="reuseBuilding.0.bdstUsgCd" - render={({ field }) => ( - <FormUi label="건축물용도"> - <SelectBox - className={""} - selectClass={selectClass} - label={""} - selectArray={code.bdstUsgCd} - onSelectValue={(val) => { - field.onChange(Number(val)); - }} - value={field.value} - /> - </FormUi> - )} - /> - </div> + <div className="col-span-2"> <FormField control={form.control} name="frstArchPrmsnDt" render={({ field }) => ( - <FormUi label="최초건축허가일"> + <FormUi label= { type !== "HTB"?"최초건축허가일": '준공년도'} > <DatePicker mode={"single"} field={{ - value: field.value || new Date(), + value: field.value ?? new Date(), onChange: (date: Date | undefined) => field.onChange(date), }} - selectClass={'w-full'} + selectClass={"w-full"} ></DatePicker> </FormUi> )} /> </div> - <div className="col-span-2"> - <FormField - control={form.control} - name="archArea" - render={({ field }) => ( - <FormUi label="건축면적(m³)"> - <Input - className={selectClass} - placeholder="1,000,000" - type="number" - onChange={(e) => { - handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - /> - </FormUi> - )} - /> - </div> - <div className="col-span-2"> - <FormField - control={form.control} - name="reuseBuilding.0.lawTrgtYn" - render={({ field }) => ( - <FormUi label="법정의무대상여부"> - <SelectBox - className={""} - selectClass={selectClass} - label={""} - selectArray={code.yn} - onSelectValue={(val) => { - field.onChange(val); - }} - value={field.value} - /> - </FormUi> - )} - /> - </div> </div> - <h1 className={formH1Class}> - 중축, 개축, 재축 - </h1> - <div className="flex justify-end gap-2 mt-4"> - <Button type="button" className="" size={"sm"} onClick={plusHist}> - 이력추가 - </Button> - <Button - type="button" - className="!bg-red-500 text-white" - size={"sm"} - onClick={removeSelected} - > - 삭제 - </Button> - </div> - {fields.map((field, index) => ( - <div key={field.id} className="grid grid-cols-[auto,1fr,1fr,1fr] gap-2 mt-4 items-center"> - <div className="col-span-1 flex justify-center mr-4"> - <Checkbox - checked={selectedIds.includes(field.id)} - onCheckedChange={() => toggleSelect(field.id)} - /> - </div> - <div className=""> - <FormField - control={form.control} - name={`chgBuilding.${index}.bdstChgNm`} - render={({ field: f }) => ( - <FormUi label="구분"> - <SelectBox - selectClass={selectClass} - selectArray={code.chBuCd} - onSelectValue={val => f.onChange(val)} - value={f.value ?? code.chBuCd[0]?.val} className={""} label={""} /> - </FormUi> - )} - /> - </div> - <div className=""> - <FormField - control={form.control} - name={`chgBuilding.${index}.archChgPrmsnDt`} - render={({ field: f }) => ( - <FormUi label="허가일"> - <DatePicker - mode="single" - field={{ - value: safeDate(f.value) || new Date(), - onChange: (date) => f.onChange(date), - }} - selectClass="w-full" + {type !== "HTB" ? ( + <> + <div className="grid grid-cols-6 gap-2 mt-4 "> + <div className="col-span-2"> + <FormField + control={form.control} + name="reuseBuilding.0.bdstUsgCd" + render={({ field }) => ( + <FormUi label="건축물용도"> + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.bdstUsgCd} + onSelectValue={(val) => { + field.onChange(Number(val)); + }} + value={field.value ?? code.bdstUsgCd[0]?.val} + /> + </FormUi> + )} + /> + </div> + + <div className="col-span-2"> + <FormField + control={form.control} + name="archArea" + render={({ field }) => ( + <FormUi label="건축면적(m³)"> + <Input + className={selectClass} + placeholder="면적을 입력하세요" + type="text" + onChange={(e) => { + field.onChange(e.target.value) + }} + value={field.value} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-2"> + <FormField + control={form.control} + name="reuseBuilding.0.lawTrgtYn" + render={({ field }) => ( + <FormUi label="법정의무대상여부"> + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.yn} + onSelectValue={(val) => { + field.onChange(val); + }} + value={field.value ?? code.yn[0]?.val} + /> + </FormUi> + )} + /> + </div> + </div> + <h1 className={formH1Class}>중축, 개축, 재축</h1> + <div className="flex justify-end gap-2 mt-4"> + <Button type="button" className="" size={"sm"} onClick={plusHist}> + 이력추가 + </Button> + <Button + type="button" + className="!bg-red-500 text-white" + size={"sm"} + onClick={removeSelected} + > + 삭제 + </Button> + </div> + {fields.map((field, index) => ( + <div + key={field.id} + className="grid grid-cols-[auto,1fr,1fr,1fr] gap-2 mt-4 items-center" + > + <div className="col-span-1 flex justify-center mr-4"> + <Checkbox + checked={selectedIds.includes(field.id)} + onCheckedChange={() => toggleSelect(field.id)} /> - </FormUi> - )} - /> - </div> - <div className=""> - <FormField - control={form.control} - name={`chgBuilding.${index}.archChgArea`} - render={({ field: f }) => ( - <FormUi label="면적"> - <Input - placeholder="-" - type="text" - onChange={e => handleCommaNumberChange(e, f.onChange)} - value={formatCommaNumber(f.value)} - className={selectClass} + </div> + <div className=""> + <FormField + control={form.control} + name={`chgBuilding.${index}.bdstChgNm`} + render={({ field: f }) => ( + <FormUi label="구분"> + <SelectBox + selectClass={selectClass} + selectArray={code.chBuCd} + onSelectValue={(val) => f.onChange(val)} + value={f.value ?? code.chBuCd[0]?.val} + className={""} + label={""} + /> + </FormUi> + )} /> - </FormUi> - )} - /> - </div> - </div> - ))} - <div className="border-b border-gray-300 my-4"> </div> + </div> + <div className=""> + <FormField + control={form.control} + name={`chgBuilding.${index}.archChgPrmsnDt`} + render={({ field: f }) => ( + <FormUi label="허가일"> + <DatePicker + mode="single" + field={{ + value: safeDate(f.value) || new Date(), + onChange: (date) => f.onChange(date), + }} + selectClass="w-full" + /> + </FormUi> + )} + /> + </div> + <div className=""> + <FormField + control={form.control} + name={`chgBuilding.${index}.archChgArea`} + render={({ field: f }) => ( + <FormUi label="면적"> + <Input + placeholder="-" + type="text" + onChange={(e) => f.onChange} + value={f.value} + className={selectClass} + /> + </FormUi> + )} + /> + </div> + </div> + ))} + <div className="border-b border-gray-300 my-4"> </div> + </> + ) : ( + <></> + )} </> ); } diff --git a/apps/wr/components/ui/form/fcltReg/htdflReg/htdflRegForm.tsx b/apps/wr/components/ui/form/fcltReg/htdflReg/htdflRegForm.tsx index 70e62eb7f..83051672d 100644 --- a/apps/wr/components/ui/form/fcltReg/htdflReg/htdflRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/htdflReg/htdflRegForm.tsx @@ -23,15 +23,18 @@ import { handleCommaNumberChange, } from "@/components/lib/func/commom"; import { formH1Class } from "@/components/lib/class/comClass"; +import toast from "react-hot-toast"; export default function HtdflRegForm({ htdflForm, isModal, - readonly = false + readonly = false, + setIsModal, }: { htdflForm: customHtdflBdst; isModal: boolean; readonly?: boolean; + setIsModal?: (modal: boolean) => void; }) { const { code, htdflBdstUpdate, htdflBdstReg } = useWrStore((state) => state); @@ -41,16 +44,24 @@ export default function HtdflRegForm({ resolver: zodResolver(hdtflZod), defaultValues: defaultValues, }); + const [regCtpvCd, regSggcd] = form.watch(["ctpvCd", "sggCd"]); const onSubmit = (values: z.infer<typeof hdtflZod>) => { if (isModal) { - htdflBdstReg(values, '41'); + const data = regCtpvCd ? regCtpvCd : regSggcd ? regSggcd : "noCd"; + + if (data === "noCd") { + toast.error("시도/시군구 코드를 선택해야합니다."); + return; + } + htdflBdstReg(values,data); + if (setIsModal) setIsModal(false); } else { - htdflBdstUpdate(values, '41'); + + htdflBdstUpdate(values, "41"); } }; - const cleanHtdflData = (raw: any): z.infer<typeof hdtflZod> => { return { ...raw, @@ -80,9 +91,31 @@ export default function HtdflRegForm({ console.log("form errors:", form.formState.errors); }, [form.formState.errors]); + // form.reset은 비동기로 동작할 수 있어, useEffect 하나로 관리하여 의도치 않은 초기화를 방지합니다. useEffect(() => { - if (hdtflZod) form.reset(cleanHtdflData(htdflForm)); - }, [htdflForm]); + // Case 1: 수정 또는 '복사하여 등록' 등 데이터가 주입되는 경우 + if (htdflForm && Object.keys(htdflForm).length > 0) { + form.reset(cleanHtdflData(htdflForm)); + } + // Case 2: 데이터가 없는 순수 신규 등록 모드인 경우 + if(isModal) { + const firstYnValue = code.yn?.[0]?.val; + const firstHtdPowerMthdCd = code.htdPowerMthdCd?.[0]?.val; + const firstHtdUtztnUsgCd = code.htdUtztnUsgCd?.[0]?.val; + + if (firstHtdPowerMthdCd) { + form.setValue('reuseBuilding.0.htdflObj.etgtType', firstHtdPowerMthdCd, { shouldDirty: true }); + } + if (firstYnValue) { + form.setValue('reuseBuilding.0.htdflObj.plwtYn', firstYnValue as "Y" | "N", { shouldDirty: true }); + form.setValue('reuseBuilding.0.htdflObj.prcsProcsYn', firstYnValue as "Y" | "N", { shouldDirty: true }); + form.setValue('reuseBuilding.0.htdflObj.fmYn', firstYnValue as "Y" | "N", { shouldDirty: true }); + } + if (firstHtdUtztnUsgCd) { + form.setValue('reuseBuilding.0.htdflObj.utztnUsg', firstHtdUtztnUsgCd, { shouldDirty: true }); + } + } + }, [htdflForm, code, form]); return ( <div className="!mx-[1.6rem] !p-0r"> @@ -91,20 +124,30 @@ export default function HtdflRegForm({ {/* <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> + <h1 className="text-2xl font-medium text-label col-span-10"> + 온배수 시설 등록 + </h1> {!readonly && ( - <Button type="submit" className="col-start-12 col-span-1" size={"sm"}> + <Button + type="submit" + className="col-start-12 col-span-1" + size={"sm"} + > 저장 </Button> )} </div> </div> - {isModal && htdflForm != undefined ? <BdstReg form={form} /> : <></>} + {isModal && htdflForm != undefined ? ( + <BdstReg form={form} type="HTB" /> + ) : ( + <></> + )} {isModal ? ( - <h1 className="text-xl col-span-10 mt-4"> - 온배수 시설 정보{" "} - </h1> + <div className=" my-4"> + <h1 className={formH1Class}>온배수 시설 정보 </h1> + </div> ) : ( <></> )} @@ -115,15 +158,14 @@ export default function HtdflRegForm({ control={form.control} name="reuseBuilding.0.htdflObj.etgtType" render={({ field }) => ( - <FormUi label="발전유형(코드정비후 수정)"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" + <FormUi label="발전유형"> + <SelectBox + className={""} + label={""} + selectClass={selectClass} + selectArray={code.htdPowerMthdCd} + onSelectValue={readonly ? () => {} : field.onChange} value={field.value ?? ""} - onChange={(e) => { - if (!readonly) field.onChange(e) - }} readOnly={readonly} /> </FormUi> @@ -136,13 +178,14 @@ export default function HtdflRegForm({ control={form.control} name="reuseBuilding.0.htdflObj.fclCpct" // 이름 이상함. render={({ field }) => ( - <FormUi label="설비용량"> + <FormUi label="설비용량(KW)"> <Input className={selectClass} placeholder="1,000,000" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -162,8 +205,8 @@ export default function HtdflRegForm({ label={""} selectClass={selectClass} selectArray={code.yn} - onSelectValue={readonly ? () => { } : field.onChange} - value={field.value ?? code.yn[0]?.val} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value ?? ""} readOnly={readonly} /> </FormUi> @@ -172,9 +215,7 @@ export default function HtdflRegForm({ </div> </div> <div className=" my-4"> - <h1 className={formH1Class}> - 온배수 시설 운영현황 - </h1> + <h1 className={formH1Class}>온배수 시설 운영현황</h1> </div> <div className="grid grid-cols-10 gap-1 mt-4"> @@ -183,14 +224,14 @@ export default function HtdflRegForm({ control={form.control} name="reuseBuilding.0.htdflObj.orgwtWtritSpt" render={({ field }) => ( - <FormUi label="원수취수 지점(코드정비후 수정)"> + <FormUi label="원수취수 지점"> <Input className={selectClass} - placeholder="1,000,000" + placeholder="" type="text" - value={field.value ?? 0} + value={field.value ?? ''} onChange={(e) => { - if (!readonly) field.onChange(Number(e.target.value)); + if (!readonly) field.onChange((e.target.value)); }} readOnly={readonly} /> @@ -206,11 +247,11 @@ export default function HtdflRegForm({ <FormUi label="원수발생 공정"> <Input className={selectClass} - placeholder="1,000,000" - type="" + placeholder="" + type="text" value={field.value ?? ""} onChange={(e) => { - if (!readonly) field.onChange + if (!readonly) field.onChange(e.target.value); }} readOnly={readonly} /> @@ -223,13 +264,35 @@ export default function HtdflRegForm({ control={form.control} name="reuseBuilding.0.htdflObj.wtritEntrcTp" render={({ field }) => ( - <FormUi label="취수구 온도"> + <FormUi label="취수구 온도(℃)"> <Input className={selectClass} placeholder="1,000,000" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); + }} + value={formatCommaNumber(field.value)} + readOnly={readonly} + /> + </FormUi> + )} + /> + </div> + <div className="col-span-5"> + <FormField + control={form.control} + name="reuseBuilding.0.htdflObj.emsnExitTp" + render={({ field }) => ( + <FormUi label="배출구 온도(℃)"> + <Input + className={selectClass} + placeholder="10" + type="text" + onChange={(e) => { + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -249,7 +312,8 @@ export default function HtdflRegForm({ placeholder="1,000,000" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -263,14 +327,14 @@ export default function HtdflRegForm({ control={form.control} name="reuseBuilding.0.htdflObj.prcsProcsYn" render={({ field }) => ( - <FormUi label="처리공정"> + <FormUi label="처리공정 유무"> <SelectBox className={""} label={""} selectClass={selectClass} selectArray={code.yn} - onSelectValue={readonly ? () => { } : field.onChange} - value={field.value ?? code.yn[0]?.val} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value ?? ""} readOnly={readonly} /> </FormUi> @@ -288,7 +352,8 @@ export default function HtdflRegForm({ placeholder="1,000,000" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -308,8 +373,8 @@ export default function HtdflRegForm({ selectClass={selectClass} label={""} selectArray={code.yn} - onSelectValue={readonly ? () => { } : field.onChange} - value={field.value ?? code.yn[0]?.val} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value ?? ""} readOnly={readonly} /> </FormUi> @@ -327,7 +392,8 @@ export default function HtdflRegForm({ placeholder="1,000,000" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -342,14 +408,13 @@ export default function HtdflRegForm({ name="reuseBuilding.0.htdflObj.utztnUsg" render={({ field }) => ( <FormUi label="이용용도"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.htdUtztnUsgCd} + onSelectValue={readonly ? () => {} : field.onChange} value={field.value ?? ""} - onChange={() => { - if (!readonly) field.onChange - }} readOnly={readonly} /> </FormUi> @@ -359,9 +424,7 @@ export default function HtdflRegForm({ </div> <div className=" my-2"> - <h1 className={formH1Class}> - 요구수질 - </h1> + <h1 className={formH1Class}>수질</h1> </div> <div className="grid grid-cols-12 gap-4"> @@ -376,7 +439,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -397,7 +461,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -420,7 +485,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -441,7 +507,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -464,7 +531,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -485,7 +553,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -508,7 +577,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -529,7 +599,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -552,7 +623,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -573,7 +645,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -596,7 +669,8 @@ export default function HtdflRegForm({ placeholder="10" type="text" onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); + if (!readonly) + handleCommaNumberChange(e, field.onChange); }} value={formatCommaNumber(field.value)} readOnly={readonly} @@ -606,26 +680,6 @@ export default function HtdflRegForm({ /> </div> - <div className="col-span-6"> - <FormField - control={form.control} - name="reuseBuilding.0.htdflObj.emsnExitTp" - render={({ field }) => ( - <FormUi label="배출구온도"> - <Input - className={selectClass} - placeholder="10" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={readonly} - /> - </FormUi> - )} - /> - </div> </div> </form> </Form> diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx index df63b3520..a1d433f2d 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm.tsx @@ -30,7 +30,11 @@ import { Button, DataTable, Input, Label } from "@common/components"; import { useEffect, useRef, useState } from "react"; import { useFieldArray, useForm, type UseFormReturn } from "react-hook-form"; import MngRegFormDetail from "../mngReg/mngRegFormDetail/mngRegFormDetail"; -import { safeDate } from "@/components/lib/func/commom"; +import { + formatCommaNumber, + handleCommaNumberChange, + safeDate, +} from "@/components/lib/func/commom"; import SearchInputText from "@/components/ui/searchInput/searchInput"; import SdmInsertForm from "@common/business_components/ui/sdmInsertForm"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; @@ -55,18 +59,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 +80,71 @@ 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); + 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) { + + 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; @@ -145,6 +192,23 @@ export function PbnstBuseForm({ name: `items.${indexMng}.fcBizs.${indexBiz}.fcDemands`, }); + const demandsContainerRef = useRef<HTMLDivElement>(null); + const prevDemandsLengthRef = useRef(fcDemands.length); + + // 수요처 추가 시 새로 추가된 항목으로 스크롤하는 효과 + useEffect(() => { + // 수요처 항목이 추가되었는지 확인 (길이가 이전보다 길어졌는지) + if (fcDemands.length > prevDemandsLengthRef.current) { + // 컨테이너의 마지막 자식 요소(새로 추가된 수요처 폼)로 부드럽게 스크롤 + demandsContainerRef.current?.lastElementChild?.scrollIntoView({ + behavior: "smooth", + block: "end", + }); + } + // 다음 렌더링을 위해 현재 길이를 ref에 저장 + prevDemandsLengthRef.current = fcDemands.length; + }, [fcDemands.length]); + const { fields: fcOrgwtFiled } = useFieldArray({ control: form.control, name: `items.${indexMng}.fcBizs.${indexBiz}.fcOrgwts`, @@ -250,24 +314,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 ( <> @@ -320,7 +366,7 @@ export function PbnstBuseForm({ <MngRegFormDetail key={`buziMoney-${indexMng}-${indexBiz}`} formList={businessMoneyInfo} - h1Title={"사업비"} + h1Title={"사업비(원)"} form={form} moClass="grid grid-cols-12 gap-4" sonClass="col-span-4" @@ -373,6 +419,7 @@ export function PbnstBuseForm({ </div> <RuseMngTable key={`fcOrgwts-${indexMng}-${indexBiz}`} // 이게 핵심! + tableData={ form.getValues( `items.${indexMng}.fcBizs.${indexBiz}.fcOrgwts` @@ -398,7 +445,7 @@ export function PbnstBuseForm({ <Label className="text-right">합계</Label> <Input type="text" - value={sumCpct} + value={ formatCommaNumber(sumCpct)} readOnly={true} className="w-32 border-gray-300 rounded pr-8 " /> @@ -457,7 +504,8 @@ export function PbnstBuseForm({ <MngRegFormDetail formList={ruseWaterQulityList} - h1Title={"하수처리수 재이용시설 보증수질"} + h1Title={`하수처리수 재이용시설 보증수질 `} + subTitle="※재처리시설이 있는 경우만 작성" form={form} moClass="grid grid-cols-12 gap-4" sonClass="col-span-4" @@ -477,7 +525,7 @@ export function PbnstBuseForm({ size={commonButonSize} className="mt-6 mr-3" > - 수요항목 추가 + 수요처 추가 </Button> <Button type="button" @@ -490,17 +538,14 @@ export function PbnstBuseForm({ size={commonButonSize} className="mt-6" > - 수요처 추가 + 수요처 신규등록 </Button> </div> - {( - form.getValues( - `items.${indexMng}.fcBizs.${indexBiz}.fcDemands` - ) || [] - ).map((field, index) => ( + <div ref={demandsContainerRef}> + {fcDemands.map((field, index) => ( <div - key={`fcDemands-${indexMng}-${indexBiz}`} // 이게 핵심! - className="grid grid-cols-12 gap-4 mt-3" + key={field.id} // useFieldArray의 고유 id를 key로 사용 + className="grid grid-cols-12 gap-4 mt-3 border-b border-gray-400 pb-3" > <div key={index + "keySon1"} className="col-span-4"> <FormField @@ -631,14 +676,14 @@ export function PbnstBuseForm({ control={form.control} name={`items.${indexMng}.fcBizs.${indexBiz}.fcDemands.${index}.splyCpct`} render={({ field: f }) => ( - <FormUi label="(계약)공급량(m3/일)"> + <FormUi label="(계약)공급량(m³/일)"> <Input placeholder="" - type="number" - value={f.value ?? 0} + type="text" onChange={(e) => { - f.onChange(Number(e.target.value)); + handleCommaNumberChange(e, f.onChange); }} + value={formatCommaNumber(f.value)} /> </FormUi> )} @@ -665,7 +710,8 @@ export function PbnstBuseForm({ /> </div> </div> - ))} + ))} + </div> </div> </form> </Form> @@ -674,9 +720,12 @@ 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 + initTableData={form.getValues( + `items.${indexMng}.fcBizs.${indexBiz}.fcOrgwts` + ) ?? []} closeModal={() => { setIsFcOrgwts(false); }} diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx index d07d24a23..e1454342b 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm.tsx @@ -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> diff --git a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegFormDetail/mngRegFormDetail.tsx b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegFormDetail/mngRegFormDetail.tsx index 971888e10..234efa7c7 100644 --- a/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegFormDetail/mngRegFormDetail.tsx +++ b/apps/wr/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegFormDetail/mngRegFormDetail.tsx @@ -2,21 +2,19 @@ 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, h1Title, + subTitle, form, moClass, sonClass, @@ -25,6 +23,7 @@ export default function MngRegFormDetail({ }: { formList: formDetailType[]; h1Title?: string; + subTitle?:string form: any; moClass?: string; indexBiz?: number; @@ -36,6 +35,8 @@ export default function MngRegFormDetail({ return ( <> {h1Title ? <h1 className={formH1Class}>{h1Title}</h1> : <></>} + {subTitle ? <h3 className={'items-center'}>{subTitle}</h3> : <></>} + <div className={moClass}> {formList?.map((formHead, index) => { const fieldId = @@ -55,9 +56,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 +66,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() ?? (Array.isArray(potentialOptions) && potentialOptions[0]?.val )} + 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> diff --git a/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx b/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx index 9ec4fc5c0..7333f6091 100644 --- a/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/rainReg/rainRegForm.tsx @@ -4,29 +4,32 @@ import { useWrStore } from "@/app/store"; import { rainZod, selectClass, - type customRainBdst + type customRainBdst, } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; import { formH1Class, formH2Class } from "@/components/lib/class/comClass"; import { formatCommaNumber, - handleCommaNumberChange, + handleMinusNumberChange, } from "@/components/lib/func/commom"; import { DatePicker, FormUi } from "@common/business_components"; import { Form, FormField, SelectBox } from "@common/business_components/ui"; -import { Button, Input } from "@common/components/ui"; +import { Button, Checkbox, Input, Label } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import BdstReg from "../bdstReg/bdstReg"; +import toast from "react-hot-toast"; export default function RainRegForm({ rainForm, isModal, - readonly = false + readonly = false, + setIsModal, }: { rainForm: customRainBdst; isModal: boolean; + setIsModal?: (modal: boolean) => void; readonly?: boolean; }) { const { code, rainBdstReg, rainBdstUpdate, rain } = useWrStore( @@ -38,6 +41,7 @@ export default function RainRegForm({ resolver: zodResolver(rainZod), defaultValues: defaultValues, }); + const instlCst = form.watch(`reuseBuilding.0.instlCst`); const [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctYn] = form.watch([ "reuseBuilding.0.grntchBdstCertYn", @@ -46,24 +50,33 @@ 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", }, })), }; @@ -80,12 +93,24 @@ export default function RainRegForm({ form.setValue("reuseBuilding.0.rainyObj.filtrTrtmfctCpct", 0); form.setValue("reuseBuilding.0.rainyObj.filtrTrtmfctPrs", ""); } + + if (grntchBdstCertYn === "D") { + form.setValue("reuseBuilding.0.grntchBdstCertYr", "0000"); + form.setValue("reuseBuilding.0.grntchBdstCertNo", "확인불가"); + form.setValue("reuseBuilding.0.grntchBdstCertGrd", "확인불가"); + } else if (instlCstSprtYn === "D") { + form.setValue("reuseBuilding.0.instlCstSprtAmt", -1); + } else if (filtrTrtmfctYn === "D") { + form.setValue("reuseBuilding.0.rainyObj.filtrTrtmfctCpct", -1); + form.setValue("reuseBuilding.0.rainyObj.filtrTrtmfctPrs", "확인불가"); + } }, [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctYn]); + const [regCtpvCd, regSggcd] = form.watch(["ctpvCd", "sggCd"]); + // form 값이 변경될 때마다 로그 출력 useEffect(() => { - const subscription = form.watch((value) => { - }); + const subscription = form.watch((value) => {}); return () => subscription.unsubscribe(); }, [form]); @@ -95,7 +120,15 @@ export default function RainRegForm({ const onSubmit = (values: z.infer<typeof rainZod>) => { if (isModal) { - rainBdstReg(values, "41"); + const data = regCtpvCd ? regCtpvCd : regSggcd ? regSggcd : "noCd"; + + if (data === "noCd") { + toast.error("시도/시군구 코드를 선택해야합니다."); + return; + } + + rainBdstReg(values, data); + if (setIsModal) setIsModal(false); } else { rainBdstUpdate(values, "41"); } @@ -112,10 +145,14 @@ export default function RainRegForm({ <div> <div className="grid grid-cols-12 flex items-center card-header"> <h1 className="text-2xl font-medium text-label col-span-10"> - {isModal ? "건축물 운영정보" : "빗물이용시설 정보"} + {isModal ? "건축물 정보" : "빗물이용시설 정보"} </h1> {!readonly && ( - <Button type="submit" className="col-start-12 col-span-1" size={"sm"}> + <Button + type="submit" + className="col-start-12 col-span-1" + size={"sm"} + > 저장 </Button> )} @@ -129,12 +166,9 @@ export default function RainRegForm({ ) : ( <></> )} - <h1 className={formH1Class}> - 기본정보 - </h1> - - <div className="grid grid-cols-4 gap-2 mt-4"> - <div className="col-span-2"> + <h1 className={formH1Class}>기본정보</h1> + <div className="grid grid-cols-6 gap-2 mt-4"> + <div className="col-span-3"> <FormField control={form.control} name="reuseBuilding.0.instlCmptnDt" @@ -146,9 +180,9 @@ 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} @@ -158,48 +192,93 @@ export default function RainRegForm({ /> </div> - <div className="col-span-2"> + <div className="col-span-3"> <FormField control={form.control} - name="reuseBuilding.0.instlCst" - render={({ field }) => ( - <FormUi label="설치비"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={readonly} - /> - </FormUi> - )} + name={`reuseBuilding.0.instlCst`} + render={({ field }) => { + const value = field.value; + const isUnknown = value === null || value === -1; // 또는 Number(value) === -1 + return ( + <FormUi label="설치비(원)"> + <Input + placeholder="1,000,000" + type="text" + className={selectClass} + readOnly={readonly} + value={ + isUnknown ? "확인불가" : formatCommaNumber(value) + } + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + onChange={(e) => { + if (readonly) return; + handleMinusNumberChange(e, field.onChange); + }} + /> + </FormUi> + ); + }} /> </div> </div> <div className=""> - <h1 className={formH1Class}> - 빗물이용시설 정보 - </h1> + <h1 className={formH1Class}>빗물이용시설 정보</h1> </div> <div> - <h2 className={formH2Class}>집수면</h2> + <h2 className={formH2Class}>집수면 및 저류조</h2> </div> - <div className="grid grid-cols-10 gap-4 mt-4 "> - <div className="col-span-5"> + <div className="grid grid-cols-12 gap-4 mt-4 "> + <div className="col-span-4"> + <FormField + control={form.control} + name="reuseBuilding.0.rainyObj.dtfcltCpct" + render={({ field }) => { + const isUnknown = field.value === null || field.value === -1; // 또는 Number(value) === -1 + return ( + <FormUi label="저류조 용량(m³)"> + <Input + className={selectClass} + placeholder="1,000,000" + type="text" + value={ + isUnknown + ? "확인불가" + : formatCommaNumber(field.value) + } + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + onChange={(e) => { + if (readonly) return; + handleMinusNumberChange(e, field.onChange); + }} + readOnly={readonly} + /> + </FormUi> + ); + }} + /> + </div> + <div className="col-span-4"> <FormField control={form.control} name="reuseBuilding.0.rainyObj.pntfColKnd" render={({ field }) => ( - <FormUi label="종류"> + <FormUi label="집수면 종류"> <SelectBox className={""} selectClass={selectClass} label={""} selectArray={rainFcltCd} - onSelectValue={readonly ? () => { } : field.onChange} + onSelectValue={readonly ? () => {} : field.onChange} value={field.value ?? rainFcltCd[0]?.val} readOnly={readonly} /> @@ -207,24 +286,39 @@ export default function RainRegForm({ )} /> </div> - <div className="col-span-5"> + <div className="col-span-4"> <FormField control={form.control} name="reuseBuilding.0.rainyObj.pntfColArea" - render={({ field }) => ( - <FormUi label="면적(m³)"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={readonly} - /> - </FormUi> - )} + render={({ field }) => { + const value = field.value; + const isUnknown = value === null || value === -1; // 또는 Number(value) === -1 + return ( + <div className="flex items-center gap-2"> + <FormUi label="집수면 용량(m³)"> + <Input + className={selectClass} + placeholder="1,000,000" + type="text" + value={ + isUnknown ? "확인불가" : formatCommaNumber(value) + } + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + onChange={(e) => { + if (readonly) return; + handleMinusNumberChange(e, field.onChange); + }} + readOnly={readonly} + /> + </FormUi> + </div> + ); + }} /> </div> </div> @@ -243,9 +337,9 @@ export default function RainRegForm({ className={""} selectClass={selectClass} label={""} - selectArray={code.yn} - onSelectValue={readonly ? () => { } : field.onChange} - value={field.value || code.yn[0]?.val} + selectArray={code.bdstFilterYn} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value ?? code.yn[0]?.val} readOnly={readonly} /> </FormUi> @@ -256,45 +350,66 @@ export default function RainRegForm({ <FormField control={form.control} name="reuseBuilding.0.rainyObj.filtrTrtmfctCpct" - render={({ field }) => ( - <FormUi label="용량(m³/일)"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={filtrTrtmfctYn !== "Y"} - /> - </FormUi> - )} + render={({ field }) => { + const isUnknown = field.value === null || field.value === -1; // 또는 Number(value) === -1 + return ( + <FormUi label="용량(m³/일)"> + <Input + className={selectClass} + placeholder="1,000,000" + type="text" + value={ + isUnknown + ? "확인불가" + : formatCommaNumber(field.value) + } + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + onChange={(e) => { + if (readonly) return; + handleMinusNumberChange(e, field.onChange); + }} + readOnly={readonly} + disabled={filtrTrtmfctYn !== "Y"} + /> + </FormUi> + ); + }} /> </div> <div className="col-span-4"> <FormField control={form.control} name="reuseBuilding.0.rainyObj.filtrTrtmfctPrs" - render={({ field }) => ( - <FormUi label="처리공정"> - <Input - className={selectClass} - placeholder="" - type="text" - value={field.value ?? ""} - onChange={field.onChange} - readOnly={filtrTrtmfctYn !== "Y"} - /> - </FormUi> - )} + render={({ field }) => { + const value = field.value; + const isUnknown = value === null || value === "-"; // 또는 Number(value) === -1 + + return ( + <div className="flex items-center gap-2"> + <FormUi label="처리공정"> + <Input + className={" h-full !text-xl col-span-4"} + placeholder="처리공정 입력" + type="text" + value={field.value ?? ""} + onChange={field.onChange} + readOnly={readonly} + disabled={filtrTrtmfctYn !== "Y"} + /> + </FormUi> + </div> + ); + }} /> </div> </div> <div className=" my-2"> - <h1 className={formH1Class}> - 인센티브 정보 - </h1> + <h1 className={formH1Class}>인센티브 정보</h1> </div> <div> <h2 className={formH2Class}>설치비 지원</h2> @@ -310,8 +425,8 @@ export default function RainRegForm({ className={""} selectClass={selectClass} label={""} - selectArray={code.yn} - onSelectValue={readonly ? () => { } : field.onChange} + selectArray={code.bdstIncenYn} + onSelectValue={readonly ? () => {} : field.onChange} value={field.value ?? code.yn[0]?.val} readOnly={readonly} /> @@ -323,24 +438,37 @@ export default function RainRegForm({ <FormField control={form.control} name="reuseBuilding.0.instlCstSprtAmt" - render={({ field }) => ( - <FormUi label="지원금액(원)"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={instlCstSprtYn !== "Y"} - /> - </FormUi> - )} + render={({ field }) => { + const value = field.value; + const isUnknown = value === null || value === -1; // 또는 Number(value) === -1 + return ( + <FormUi label="지원금액(원)"> + <Input + className={selectClass} + placeholder="" + type="text" + readOnly={readonly || isUnknown} + value={ + isUnknown ? "확인불가" : formatCommaNumber(value) + } + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + onChange={(e) => { + if (readonly) return; + handleMinusNumberChange(e, field.onChange); + }} + disabled={instlCstSprtYn !== "Y"} + /> + </FormUi> + ); + }} /> </div> </div> - <div> <h2 className={formH2Class}>녹색건축물 인증</h2> </div> @@ -355,8 +483,8 @@ export default function RainRegForm({ className={""} selectClass={selectClass} label={""} - selectArray={code.yn} - onSelectValue={readonly ? () => { } : field.onChange} + selectArray={code.bdstIncenYn} + onSelectValue={readonly ? () => {} : field.onChange} value={field.value ?? code.yn[0]?.val} /> </FormUi> @@ -367,40 +495,72 @@ export default function RainRegForm({ <FormField control={form.control} name="reuseBuilding.0.grntchBdstCertYr" - render={({ field }) => ( - <FormUi label="인증년도"> - <Input - className={selectClass} - placeholder="2024" - type="" - value={field.value ?? ""} - onChange={(e) => { - if (!readonly) field.onChange(e); - }} - readOnly={grntchBdstCertYn !== "Y"} - /> - </FormUi> - )} + render={({ field }) => { + const isUnknown = + field.value === null || field.value === "0000"; // 또는 Number(value) === -1 + return ( + <FormUi label="인증년도"> + <Input + className={selectClass} + placeholder="2024" + type="" + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + value={isUnknown ? "확인불가" : (field.value ?? "")} + onChange={(e) => { + if (readonly) return; + if (e.target.value === "확인불가") { + field.onChange("0000"); + } else { + field.onChange(e.target.value); + } + }} + disabled={grntchBdstCertYn !== "Y"} + max={4} + /> + </FormUi> + ); + }} /> </div> <div className="col-span-3 "> <FormField control={form.control} name="reuseBuilding.0.grntchBdstCertNo" - render={({ field }) => ( - <FormUi label="인증번호"> - <Input - className={selectClass} - placeholder="ab1234678" - type="" - value={field.value ?? ""} - onChange={(e) => { - if (!readonly) field.onChange(e); - }} - readOnly={grntchBdstCertYn !== "Y"} - /> - </FormUi> - )} + render={({ field }) => { + const isUnknown = + field.value === null || field.value === "0000"; // 또는 Number(value) === -1 + return ( + <FormUi label="인증번호"> + <Input + className={selectClass} + placeholder="ab1234678" + type="" + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + value={isUnknown ? "확인불가" : (field.value ?? "")} + onChange={(e) => { + if (readonly) return; + + if (e.target.value === "확인불가") { + field.onChange(""); + } else { + field.onChange(e.target.value); + } + }} + disabled={grntchBdstCertYn !== "Y"} + /> + </FormUi> + ); + }} /> </div> <div className="col-span-3"> @@ -409,15 +569,14 @@ export default function RainRegForm({ name="reuseBuilding.0.grntchBdstCertGrd" render={({ field }) => ( <FormUi label="인증등급"> - <Input - className={selectClass} - placeholder="A" - type="" - value={field.value ?? ""} - onChange={(e) => { - if (!readonly) field.onChange(e); - }} - readOnly={grntchBdstCertYn !== "Y"} + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.bdstGrntchClass} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value ?? code.bdstGrntchClass[0]?.val} + readOnly={readonly} /> </FormUi> )} diff --git a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx index a4f29477a..0fbffbf59 100644 --- a/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx +++ b/apps/wr/components/ui/form/fcltReg/rswtrReg/rswtrRegForm.tsx @@ -10,6 +10,7 @@ import { formH1Class, formH2Class } from "@/components/lib/class/comClass"; import { formatCommaNumber, handleCommaNumberChange, + handleMinusNumberChange, } from "@/components/lib/func/commom"; import { DatePicker, @@ -21,17 +22,19 @@ import { import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect } from "react"; -import { useForm } from "react-hook-form"; +import { useForm, type UseFormReturn } from "react-hook-form"; import { z } from "zod"; import BdstReg from "../bdstReg/bdstReg"; export default function RswtrRegForm({ rswtForm, isModal, - readonly = false + setIsModal, + readonly = false, }: { rswtForm: customRswtBdst; isModal: boolean; + setIsModal?: (isModal: boolean) => void; readonly?: boolean; }) { const { rswt, code, rswtBdstUpdate, rswtBdstReg } = useWrStore( @@ -54,36 +57,49 @@ export default function RswtrRegForm({ const onSubmit = (values: z.infer<typeof rswtZod>) => { if (isModal) { rswtBdstReg(values, "41"); + if (setIsModal) setIsModal(false); } else { rswtBdstUpdate(values, "41"); } }; - const cleanRswtData = (raw: any): z.infer<typeof rswtZod> => { + + const filtrTrtmfctCpct = form.watch('reuseBuilding.0.rswtrObj.filtrTrtmfctCpct') + const cleanRswtData = (raw: any): z.infer<typeof rswtZod> => { + + console.log('raw ', raw) 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), + instlCmptnDt: item?.instlCmptnDt + ? new Date(item.instlCmptnDt) + : new Date(), // item?.instlCmptnDt가 유효하지 않으면 현재 날짜로 초기화 operMngCd: item?.operMngCd ?? "", // null → "" - grntchBdstCertYr: - typeof item?.grntchBdstCertYr === "string" - ? item?.grntchBdstCertYr - : "", // null → "" + instlCstSprtYn: item?.instlCstSprtYn || code.yn[0]?.val, // null → "Y" + grntchBdstCertYn: item?.grntchBdstCertYn|| code.yn[0]?.val, rswtrObj: { ...item?.rswtrObj, + filtrTrtmfctYn: item?.rainyObj?.filtrTrtmfctYn || code.yn[0]?.val, + filtrTrtmfctCpct: item?.rswtrObj?.filtrTrtmfctCpct ?? null, + filtrTrtmfctProcs: item?.rswtrObj?.filtrTrtmfctProcs || code.rswtPrscCdMthdCd[0]?.val, + orgwtKndCd: item?.rswtrObj?.orgwtKndCd || code.orgwtKndCd[0]?.val, }, })), }; }; - - useEffect(()=>{ - console.log("rswtForm:", rswtForm) - }, [rswtForm]) - + // useEffect(() =>{ + // console.log(' formform ', form.watch()) + // },[form.formState]) + useEffect(() =>{ + console.log(' filtrTrtmfctCpct ', filtrTrtmfctCpct) + },[filtrTrtmfctCpct]) + useEffect(() => { console.log("form errors:", form.formState.errors); }, [form.formState.errors]); @@ -95,6 +111,16 @@ export default function RswtrRegForm({ } else if (instlCstSprtYn === "N") { form.setValue("reuseBuilding.0.instlCstSprtAmt", 0); } + + + if (grntchBdstCertYn === "D") { + form.setValue("reuseBuilding.0.grntchBdstCertYr", "0000"); + form.setValue("reuseBuilding.0.grntchBdstCertNo", "확인불가"); + form.setValue("reuseBuilding.0.grntchBdstCertGrd", "확인불가"); + } else if (instlCstSprtYn === "D") { + form.setValue("reuseBuilding.0.instlCstSprtAmt", -1); + } + }, [grntchBdstCertYn, instlCstSprtYn, filtrTrtmfctProcs, form]); useEffect(() => { @@ -112,7 +138,11 @@ export default function RswtrRegForm({ 건축물 운영정보 </h1> {!readonly && ( - <Button type="submit" className="col-start-12 col-span-1" size={"sm"}> + <Button + type="submit" + className="col-start-12 col-span-1" + size={"sm"} + > 저장 </Button> )} @@ -151,68 +181,37 @@ export default function RswtrRegForm({ )} /> </div> - <div className="col-span-2"> <FormField control={form.control} name="reuseBuilding.0.instlCst" - render={({ field }) => ( - <FormUi label="설치비"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={readonly} - /> - </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={selectClass} - placeholder="1,000,000" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={readonly} - /> - </FormUi> - )} - /> - </div> - - <div className="col-span-2"> - <FormField - control={form.control} - name="reuseBuilding.0.rswtrObj.oblgTrgtWtrUsqty" - render={({ field }) => ( - <FormUi label="의무대상 물사용량(m³)"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={readonly} - /> - </FormUi> - )} + render={({ field }) => { + const value = field.value; + const isUnknown = value === null || value === -1; // 또는 Number(value) === -1 + return ( + <FormUi label="설치비"> + <Input + className={selectClass} + placeholder="1,000,000" + type="text" + value={ + isUnknown ? "확인불가" : formatCommaNumber(value) + } + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + onChange={(e) => { + if (readonly) return; + handleMinusNumberChange(e, field.onChange); + }} + readOnly={readonly} + /> + </FormUi> + ); + }} /> </div> </div> @@ -225,20 +224,36 @@ export default function RswtrRegForm({ <FormField control={form.control} name="reuseBuilding.0.rswtrObj.filtrTrtmfctCpct" - render={({ field }) => ( - <FormUi label="처리시설 용량"> - <Input - className={selectClass} - placeholder="" - type="number" - value={field.value ?? 0} - onChange={(e) => { - if (!readonly) field.onChange(Number(e.target.value)); - }} - readOnly={readonly} - /> - </FormUi> - )} + render={({ field }) => { + const value = field.value; + const isUnknown = value === null || value === -1; // 또는 Number(value) === -1 + + return ( + <FormUi label="처리시설 용량"> + <Input + className={selectClass} + placeholder="" + type="text" + value={ + isUnknown ? "확인불가" : formatCommaNumber(value) + } + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + + console.log('value') + field.onChange(""); // 초기화 + } + }} + onChange={(e) => { + if (readonly) return; + handleMinusNumberChange(e, field.onChange); + }} + readOnly={readonly} + /> + </FormUi> + ); + }} /> </div> <div className="col-span-4"> @@ -252,7 +267,7 @@ export default function RswtrRegForm({ label={""} selectClass={selectClass} selectArray={code.orgwtKndCd} - onSelectValue={readonly ? () => { } : field.onChange} + onSelectValue={readonly ? () => {} : field.onChange} value={field.value ?? code.orgwtKndCd[0]?.val} readOnly={readonly} /> @@ -266,14 +281,13 @@ export default function RswtrRegForm({ name="reuseBuilding.0.rswtrObj.filtrTrtmfctProcs" render={({ field }) => ( <FormUi label="주요처리공정"> - <Input - className={selectClass} - placeholder="" - type="" - value={field.value ?? ""} - onChange={(e) => { - if (!readonly) field.onChange - }} + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.rswtPrscCdMthdCd} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value ?? code.rswtPrscCdMthdCd[0]?.val} readOnly={readonly} /> </FormUi> @@ -299,9 +313,9 @@ export default function RswtrRegForm({ className={""} selectClass={selectClass} label={""} - selectArray={code.yn} - onSelectValue={readonly ? () => { } : field.onChange} - value={field.value ?? code.yn[0]?.val} + selectArray={code.bdstIncenYn} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value ?? code.bdstIncenYn[0]?.val} readOnly={readonly} /> </FormUi> @@ -309,23 +323,37 @@ export default function RswtrRegForm({ /> </div> <div className="col-span-1"> - <FormField + <FormField control={form.control} name="reuseBuilding.0.instlCstSprtAmt" - render={({ field }) => ( - <FormUi label="지원금액(원)"> - <Input - className={selectClass} - placeholder="1,000,000" - type="text" - onChange={(e) => { - if (!readonly) handleCommaNumberChange(e, field.onChange); - }} - value={formatCommaNumber(field.value)} - readOnly={instlCstSprtYn !== "Y"} - /> - </FormUi> - )} + render={({ field }) => { + const value = field.value; + const isUnknown = value === null || value === -1; // 또는 Number(value) === -1 + return ( + <FormUi label="지원금액(원)"> + <Input + className={selectClass} + placeholder="" + type="text" + readOnly={readonly || isUnknown} + value={ + isUnknown ? "확인불가" : formatCommaNumber(value) + } + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } + }} + onChange={(e) => { + if (readonly) return; + handleMinusNumberChange(e, field.onChange); + }} + disabled={instlCstSprtYn === "N"||instlCstSprtYn === "D"} + /> + </FormUi> + ); + }} /> </div> </div> @@ -344,9 +372,9 @@ export default function RswtrRegForm({ className={""} selectClass={selectClass} label={""} - selectArray={code.yn} - onSelectValue={readonly ? () => { } : field.onChange} - value={field.value ?? code.yn[0]?.val} + selectArray={code.bdstIncenYn} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value || code.yn[0]?.val} readOnly={readonly} /> </FormUi> @@ -357,41 +385,75 @@ export default function RswtrRegForm({ <FormField control={form.control} name="reuseBuilding.0.grntchBdstCertYr" - render={({ field }) => ( + render={({ field }) => + { + const isUnknown = field.value === null || field.value === "0000"; // 또는 Number(value) === -1 + return ( <FormUi label="인증년도"> <Input className={selectClass} placeholder="2024" - type="text" - {...field} - value={field.value ?? ""} - onChange={(e) => { - if (!readonly) field.onChange(e) + type="" + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } }} - readOnly={grntchBdstCertYn !== "Y"} + value={ + isUnknown?'확인불가':field.value ?? ""} + onChange={(e) => { + if (readonly) return + if (e.target.value === '확인불가') { + field.onChange('0000'); + }else{ + field.onChange(e.target.value); + } + }} + disabled={grntchBdstCertYn == "N" || grntchBdstCertYn == "D"} + max={4} /> </FormUi> - )} + )}} /> </div> <div className="col-span-3"> <FormField control={form.control} name="reuseBuilding.0.grntchBdstCertNo" - render={({ field }) => ( + render={({ field }) => + { + const isUnknown = field.value === null || field.value === "0000"; // 또는 Number(value) === -1 + return ( <FormUi label="인증번호"> <Input className={selectClass} placeholder="ab1234678" type="" - value={field.value ?? ""} - onChange={(e) => { - if (!readonly) field.onChange(e) + onFocus={() => { + // 입력 시 자동으로 확인불가 해제 + if (isUnknown && !readonly) { + field.onChange(""); // 초기화 + } }} - readOnly={grntchBdstCertYn !== "Y"} + value={ + isUnknown?'확인불가':field.value ?? ""} + onChange={(e) => { + if (readonly) return + + if (e.target.value === '확인불가') { + field.onChange(''); + }else{ + field.onChange(e.target.value); + } + }} + disabled={grntchBdstCertYn == "N" || grntchBdstCertYn == "D"} + + + /> </FormUi> - )} + )}} /> </div> <div className="col-span-3"> @@ -400,15 +462,14 @@ export default function RswtrRegForm({ name="reuseBuilding.0.grntchBdstCertGrd" render={({ field }) => ( <FormUi label="인증등급"> - <Input - className={selectClass} - placeholder="A" - type="" - value={field.value ?? ""} - onChange={(e) => { - if (!readonly) field.onChange(e) - }} - readOnly={grntchBdstCertYn !== "Y"} + <SelectBox + className={""} + selectClass={selectClass} + label={""} + selectArray={code.bdstGrntchClass} + onSelectValue={readonly ? () => {} : field.onChange} + value={field.value ?? code.bdstGrntchClass[0]?.val} + readOnly={readonly} /> </FormUi> )} diff --git a/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx b/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx index 15badc59d..5aa67fd3a 100644 --- a/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx +++ b/apps/wr/components/ui/modal/chkgd/chkgdExcel/chkgdExcel.tsx @@ -1,41 +1,46 @@ import { useWrStore } from "@/app/store"; import { type rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; import { excelToRainChkData } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/excel"; -import { chkgdRegExcel } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; +import { chkgdRegExcel } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; import type { rswtChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn"; import { excelToRswtGuideData, excelToRswtPolution, } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/excel"; -import { ToastFormUi } from "@/components/ui/toast/toastFromUi"; import { filePicked, filePickedArray, - FileUpload, Form, FormField, FormUi, SelectBox, Title, } from "@common/business_components/ui"; +import { Card } from "@common/business_components/ui/chart/lib/card"; import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; +import toast from "react-hot-toast"; import type { z } from "zod"; -export default function ChkgdExcel({ type }: { type: string }) { - const { - rswtChkgdn, - rainChkExcelDataUPload, - rswtChkExceDataUpload, - } = useWrStore((state) => { - return state; - }); - const [files, setFiles] = useState<File[] | null>(null); +export default function ChkgdExcel({ type,setIsRegExcel }: { type: string, setIsRegExcel:(open:boolean) =>void }) { + const { rswtChkgdn, rainChkExcelDataUPload, rswtChkExceDataUpload } = + useWrStore((state) => { + return state; + }); const [excelData, setExcelData] = useState< rswtChkZodType[] | rainChkZodType[] >(); + + const [file, setFile] = useState<File | null>(null); + + const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const selectedFile = e.target.files?.[0]; + if (selectedFile) { + setFile(selectedFile); + } + }; const [excelRswtData, setExcelRswtData] = useState<rswtChkZodType[]>(); const currentYear = new Date().getFullYear(); @@ -49,7 +54,7 @@ export default function ChkgdExcel({ type }: { type: string }) { ); const defaultValues: z.infer<typeof chkgdRegExcel> = chkgdRegExcel.parse({ - chckYr: String(currentYear) + chckYr: String(currentYear), }); const form = useForm<z.infer<typeof chkgdRegExcel>>({ @@ -58,14 +63,10 @@ export default function ChkgdExcel({ type }: { type: string }) { }); useEffect(() => { - if (files) { - const file = files[0]; + if (file) { if (form.getValues("chckYr").length < 1) { - ToastFormUi({ - body: "파일업로드 이전에 등록년도를 작성해주세요 ", - title: "지도점검 등록", - }); - setFiles(null); + toast.error("파일업로드 이전에 등록년도를 작성해주세요 "); + setFile(null); return; } if (file) @@ -87,7 +88,6 @@ export default function ChkgdExcel({ type }: { type: string }) { form.getValues("chckYr") ); - const keyGenerator = (item: rswtChkZodType) => `${item.bdstRuseFcltFk}|${item.chckYmd}|${item.utztnUsgCd}`; const totalData: rswtChkZodType[] = guideData.map((guide) => { @@ -98,12 +98,11 @@ export default function ChkgdExcel({ type }: { type: string }) { guide.utztnUsgCd === polution.utztnUsgCd ); - if (fusion) { return { ...guide, rswtrPollutions: fusion?.rswtrPollutions, - rswtrDetails:fusion?.rswtrDetails + rswtrDetails: fusion?.rswtrDetails, }; } else { return { @@ -118,19 +117,17 @@ export default function ChkgdExcel({ type }: { type: string }) { (item) => !guideKey.has(keyGenerator(item)) ); + const listTotalData: rswtChkZodType[] = [ + ...totalData, + ...onlypolution, + ]; - const listTotalData:rswtChkZodType[] =[ - ...totalData, - ...onlypolution - ] - - - setExcelRswtData(listTotalData); + setExcelRswtData(listTotalData); } }); } } - }, [files]); + }, [file]); function findMatchingItems<T>( arrayA: T[], @@ -144,34 +141,31 @@ export default function ChkgdExcel({ type }: { type: string }) { (!condition || condition(item)) && bKeySet.has(keySelector(item)) ); } - const onSubmit = (values: z.infer<typeof chkgdRegExcel>) => { if (values.chckYr === "") { - ToastFormUi({ body: "등록년도를 작성해주세요", title: "지도점검 등록" }); + toast.error("등록년도를 작성해주세요"); return; } if (type === "RNB") { if (excelData === undefined || excelData?.length === 0) { - ToastFormUi({ - body: "빈 파일 및 파일 오류 입니다.", - title: "지도점검 등록", - }); + toast.error("빈 파일 및 파일 오류 입니다."); + return; } else { - rainChkExcelDataUPload(excelData as rainChkZodType[]); + setIsRegExcel(false) } } else if (type === "RST") { if (excelRswtData === undefined || excelRswtData?.length === 0) { - ToastFormUi({ - body: "빈 파일 및 파일 오류 입니다.", - title: "중수지도점검 등록", - }); + toast.error("빈 파일 및 파일 오류 입니다."); + return; } else { rswtChkExceDataUpload(excelRswtData as rswtChkZodType[]); + setIsRegExcel(false) + } } }; @@ -223,13 +217,13 @@ export default function ChkgdExcel({ type }: { type: string }) { name="file" render={({ field }) => ( <FormUi label="점검결과 파일"> - <FileUpload - files={files || []} - setFiles={(files: File[] | null) => { - if (files) setFiles(files); - }} - fileTypes="xlsx" - ></FileUpload> + <div className="space-y-4 p-4"> + <Input + type="file" + onChange={handleFileChange} + accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" + /> + </div> </FormUi> )} /> diff --git a/apps/wr/components/ui/modal/chkgd/chkgdModal/chkgdModal.tsx b/apps/wr/components/ui/modal/chkgd/chkgdModal/chkgdModal.tsx index 9572d1228..edbd9e570 100644 --- a/apps/wr/components/ui/modal/chkgd/chkgdModal/chkgdModal.tsx +++ b/apps/wr/components/ui/modal/chkgd/chkgdModal/chkgdModal.tsx @@ -1,16 +1,14 @@ import { useWrStore } from "@/app/store"; -import { ScrollArea, Title } from "@common/business_components/ui"; -import { Button } from "@common/components/ui"; -import { useEffect, useState } from "react"; -import { headMakeprfmnc } from "@/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead"; -import { GuideDetailsTable } from "@/components/ui/table/chkgdTable/guideDetailTable/guideDetailTable"; +import type { TableUpperProps } from "@/app/store/common/type/header"; import type { splRuseWaterType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; import type { rswtrPolutionsZode } from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn/headList"; +import { GuideDetailsTable } from "@/components/ui/table/chkgdTable/guideDetailTable/guideDetailTable"; +import { ScrollArea, Title } from "@common/business_components/ui"; +import { Button } from "@common/components/ui"; +import React, { useEffect, useState } from "react"; import type { z } from "zod"; -import React from "react"; -import type { TableUpperProps } from "@/app/store/common/type/header"; -export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { +export default function ChkgdModal({ type, obj, setIsModal }: { type: string; obj: any, setIsModal: (open: boolean) => void }) { const { rainChkgdn, rswtChkgdn, @@ -39,7 +37,6 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { "4": [] as z.infer<typeof rswtrPolutionsZode>[], }); - const { rainChk, ruseHeadList, splHeadList } = rainChkgdn; const [updateData, setUpdateData] = useState<splRuseWaterType[]>(); const initStats = (): z.infer<typeof rswtrPolutionsZode> => ({ @@ -97,7 +94,6 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { target = { utztnUsgCd: code, ruseTotal: 0, splTotal: 0 }; acc.push(target); } - // 월별 값 누적 target[ruseMonthKey] = (target[ruseMonthKey] ?? 0) + ruseValue; target[splMonthKey] = (target[splMonthKey] ?? 0) + splValue; @@ -106,7 +102,17 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { target.splTotal += splValue; return acc; }, []); - setGuidData(result); + + if (result) { + // '기타' 항목을 배열의 맨 뒤로 정렬합니다. + result.sort((a: splRuseWaterType, b: splRuseWaterType) => { + if (a.utztnUsgCd === "기타") return 1; // a를 b 뒤로 보냅니다. + if (b.utztnUsgCd === "기타") return -1; // b를 a 뒤로 보냅니다. + return 0; // 순서를 유지합니다. + }); + } + + setGuidData(result as splRuseWaterType[]); if (obj.rswtrPollutions) { const resultPolution: Record<string, any[]> = { @@ -144,21 +150,29 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { const average = { utztnUsgCd: "평균", edit: false, - grssClfgUswtr: total.grssClfgUswtr / onlyData.length, - combResdch: total.combResdch / onlyData.length, - trbt: total.trbt / onlyData.length, - bod: total.bod / onlyData.length, - smll: total.smll / onlyData.length, - crmty: total.crmty / onlyData.length, - tph: total.tph / onlyData.length, - tn: total.tn / onlyData.length, - ph: total.ph / onlyData.length, - clIonQnt: total.clIonQnt / onlyData.length, + grssClfgUswtr: (total.grssClfgUswtr / onlyData.length).toFixed(1), + combResdch: (total.combResdch / onlyData.length).toFixed(1), + trbt: (total.trbt / onlyData.length).toFixed(1), + bod: (total.bod / onlyData.length).toFixed(1), + smll: (total.smll / onlyData.length).toFixed(1), + crmty: (total.crmty / onlyData.length).toFixed(1), + tph: (total.tph / onlyData.length).toFixed(1), + tn: (total.tn / onlyData.length).toFixed(1), + ph: (total.ph / onlyData.length).toFixed(1), + clIonQnt: (total.clIonQnt / onlyData.length).toFixed(1), }; if (!resultPolution[nodeKey]) { resultPolution[nodeKey] = []; } + + // '기타' 항목을 배열의 맨 뒤로 정렬합니다. + resultPolution[nodeKey].sort((a: any, b: any) => { + if (a.utztnUsgCd === "기타") return 1; // a를 b 뒤로 보냅니다. + if (b.utztnUsgCd === "기타") return -1; // b를 a 뒤로 보냅니다. + return 0; // 순서를 유지합니다. + }); + resultPolution[nodeKey].push(average); }); @@ -200,12 +214,14 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { obj.guideDetails = realGuideDetail; if (type === "RNB") { rainChkGuidUpdate(obj); + setIsModal(false) } else { const polution = Object.values(upPolutionData) .flat() .filter((item) => item.utztnUsgCd !== "평균"); obj.rswtrPollutions = polution; rwstPolAndGuideUpdate([obj]); + setIsModal(false) } }; @@ -231,7 +247,8 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { </div> <div className="flex justify-between"> <h3 className="text-2xl font-bold text-gray-900 mt-2 ml-6"> - {`(${obj.bdstNm})`} + {type === "RNB" + ? `(${obj.bdstNm})` : `(${obj.building.bdstNm})`} </h3> {!rainChkSttn.isSttn ? ( <Button className="" onClick={allDataUpdate}> @@ -245,12 +262,13 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { <ScrollArea className="h-[60vh]"> <div className="card-header"> <h3 className="text-2xl font-medium text-label"> - {`연간보급수이용량`} + {`연간대체수이용량`} </h3> </div> <GuideDetailsTable chgRes={guidData || []} - headList={headList.ruseHeadList} + headList={headList.splHeadList} + upheadList={type === "RNB" ? rainChkgdn.rainSplHeadNm : rswtChkgdn.rswtSplHeadNm} type={""} setChGuidData={(data) => { setChGuidData(data); @@ -263,7 +281,8 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { </div> <GuideDetailsTable chgRes={guidData || []} - headList={headList.splHeadList} + headList={headList.ruseHeadList} + upheadList={type === "RNB" ? rainChkgdn.rainRuseHeadNm : rswtChkgdn.rswtRuseHeadNm} type={""} setChGuidData={(data) => { setChGuidData(data); @@ -285,6 +304,7 @@ export default function ChkgdModal({ type, obj }: { type: string; obj: any }) { <GuideDetailsTable chgRes={value} headList={headList?.rswtDetailHeadList} + upheadList={rswtChkgdn.rswtSplHeadNm} type={""} setChGuidData={(data) => { getPolutionData(data); diff --git a/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx b/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx index 28dc882c3..b4b87663e 100644 --- a/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx +++ b/apps/wr/components/ui/modal/chkgd/pbnPrfExcelModal/pbnPrfExcelModal.tsx @@ -18,12 +18,13 @@ import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; +import toast from "react-hot-toast"; import type { z } from "zod"; -export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) { +export default function PbnPrfExcelModal({ data ,setIsExcelOPen ,setModalExcelData}: { data: FcPrfType ,setIsExcelOPen:(open:boolean )=>void,setModalExcelData:(excelData :PrfType)=>void }) { const { prfmnRegData } = useWrStore((state) => state); - const [files, setFiles] = useState<File[] | null>(null); + const [file, setFile] = useState<File | null>(null); const [excelData, setExcelData] = useState<PrfType[]>(); const form = useForm<z.infer<typeof pbnPrfRegExcelZod>>({ resolver: zodResolver(pbnPrfRegExcelZod), @@ -31,10 +32,20 @@ export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) { ym: String(new Date().getFullYear()), }, }); + + const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { + const selectedFile = e.target.files?.[0]; + if (selectedFile) { + setFile(selectedFile); + } + }; const onSubmit = (values: z.infer<typeof pbnPrfRegExcelZod>) => { if (excelData) { - prfmnRegData(excelData as unknown as PrfType); + // prfmnRegData(1,'41', excelData as unknown as PrfType); + + setModalExcelData(excelData as unknown as PrfType) + setIsExcelOPen(false) } @@ -46,111 +57,120 @@ export default function PbnPrfExcelModal({ data }: { data: FcPrfType }) { form.setValue("pbnstRuseFcltNm", data.ruseUtztnFcltNm); }, [data]); - useEffect(() => { - console.log("excelData", excelData); - }, [excelData]); useEffect(() => { - if (files) { - const file = files[0]; + if (file) { + const isFile = file if (form.getValues("ym").length < 1) { - ToastFormUi({ - body: "파일업로드 이전에 등록년도를 작성해주세요 ", - title: "지도점검 등록", - }); - setFiles(null); + toast.error("파일업로드 이전에 등록년도를 작성해주세요 " ); + setFile(null); return; } - if (file) - filePicked(file, 0).then((res) => { + if (isFile) { + filePicked(isFile, 0).then((res) => { if (res !== undefined) - setExcelData(excelToPbnPrfData(res as unknown as JSON[], form.getValues("ym"),form.getValues('pbnstRuseFcltFk') ) as PrfType[]) + setExcelData(excelToPbnPrfData(res as unknown as JSON[], form.getValues("ym"), form.getValues('pbnstRuseFcltFk')) as PrfType[]) }); + } } - }, [files]); + }, [file]); return ( <> <div> <Form {...form}> <form onSubmit={form.handleSubmit(onSubmit)}> - <div> + <div className="flex justify-between items-center"> <Title title={"하수재이용 실적등록"}> -
-
-

- {"점검정보 입력"} -

- -
+

+ {"점검정보 입력"} +

-
+
( field.onChange(val)} /> )} /> +
+
+ ( + + + + )} + /> +
+
+ ( + + + + )} + /> +
+
+
+ ( + + {/* { + if (files) setFiles(files); + }} + fileTypes="xlsx" + maxFiles={1} - ( - - - - )} - /> - ( - - - - )} - /> - ( - - { - if (files) setFiles(files); - }} - fileTypes="xlsx" - maxFiles={1} - > - - )} - /> + + > */} + +
+ +
+
+ )} + /> +
diff --git a/apps/wr/components/ui/modal/coMat/drgtDetailModal.tsx b/apps/wr/components/ui/modal/coMat/drgtDetailModal.tsx index a6b05ca77..880c789c3 100644 --- a/apps/wr/components/ui/modal/coMat/drgtDetailModal.tsx +++ b/apps/wr/components/ui/modal/coMat/drgtDetailModal.tsx @@ -1,48 +1,77 @@ "use client" import { btnClass } from "@/app/store/common/code" +import type { customDrgt } from "@/app/store/matDmd/drgtSttn" import { drgtDetailHeadListExport } from "@/app/store/matDmd/drgtSttn/headList" -import { ContentsDiv, Title, UiTable } from "@common/business_components/ui" +import { downloadExcel, Title, UiTable } from "@common/business_components/ui" import ListDiv from "@common/business_components/ui/list_div" -import { Button } from "@common/components/ui" +import { Button, ScrollArea } from "@common/components/ui" +import { useEffect, useState } from "react" + +export default function DrgtDetailModal({ + data, +}: { + data: customDrgt; +}) { + const [excelData, setExcelData] = useState(); + const excelClick = () => { + downloadExcel({ + data: excelData ?? [], + fileName: "우리동네가뭄 상세현황", + sheetNameArray: ["Sheet1"], + headerRange: "A1:C1" + }) + } + + useEffect(() => { + const resData = Array.isArray(data) ? data : [data]; + const exData = Array.isArray(resData) + ? resData.map((item: customDrgt) => { + return { + "년": item.year, + "월": item.month, + "가뭄상태": item.drgtState, + } + }) : []; + setExcelData(exData) + }, [data]) -export default function DrgtDetailModal() { return ( <> - -
-
- - <div className="w-2/12 mx-5 flex flex-col items-center justify-center space-x-2 "> - <div className="flex flex-row space-x-2"> - <Button - size={"sm"} - className={`${btnClass} green`} - onClick={() => { }} - > - 다운로드 - </Button> - </div> - <div className="grid grid-cols-6 gap-4 "> - <div className="col-span-6"> - <ListDiv> - <UiTable - headName={[]} - tableData={[]} - headlist={drgtDetailHeadListExport} - pageSize={10} - total={10000} - cellClick={(index: number | undefined, headId: string) => { }} - onPageChange={() => { }} - ></UiTable> - </ListDiv> - </div> - </div> + <div className="grid grid-cols-4 gap-4 h-full"> + <div className="col-span-4"> + <div className="flex items-center justify-between"> + <Title title={`우리동네가뭄 상세내용 - ${data[0].sggNm}`} /> + <div className="flex flex-row space-x-2"> + <Button + size={"sm"} + className={`${btnClass} green mt-12`} + onClick={excelClick} + > + 다운로드 + </Button> + </div> + </div> + <hr className="mt-2 mb-2" /> + <div className="grid grid-cols-6 gap-4 "> + <div className="col-span-6"> + <ListDiv> + <ScrollArea className="h-[63vh]"> + <UiTable + headName={undefined} + tableData={data} + headlist={drgtDetailHeadListExport} + pageSize={10} + total={10000} + cellClick={(index: number | undefined, headId: string) => { }} + onPageChange={() => { }} + ></UiTable> + </ScrollArea> + </ListDiv> </div> </div> </div> - - </ContentsDiv> + </div> </> ) } \ No newline at end of file diff --git a/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx b/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx new file mode 100644 index 000000000..57424996e --- /dev/null +++ b/apps/wr/components/ui/modal/coMat/ruseCaseMng/ruseCaseMngExcelModal.tsx @@ -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={"물재이용 사례 등록"}> +
+
+

+ {"물재이용 사례 파일"} +

+ +
+ +
+ ( + + { + console.log("files", files) + if (files && files?.length === 1) setFiles(files[0]); + }} + fileTypes="" + maxFiles={1} + > + + )} + /> +
+ + +
+ + ); +} \ No newline at end of file diff --git a/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx b/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx index 1fa8fa8f3..89f15a3cc 100644 --- a/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx +++ b/apps/wr/components/ui/modal/coReg/coRegExcel/coRegExcelModal.tsx @@ -1,10 +1,10 @@ import { useWrStore } from "@/app/store"; import { coRegExcel } from "@/app/store/matDmd/coSttn/headList"; import type { customLdrmnt } from "@/app/store/matDmd/ldrmntCoSttn"; -import { excelToLdrmntData, names } from "@/app/store/matDmd/ldrmntCoSttn/excel"; +import { names } from "@/app/store/matDmd/ldrmntCoSttn/excel"; import { filePicked, FileUpload, FormUi, Title } from "@common/business_components/ui"; import { Form, FormField } from "@common/business_components/ui/form"; -import { Button, Input } from "@common/components/ui"; +import { Button, Progress } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; @@ -26,82 +26,86 @@ export default function CoRegExcel() { ldrmntDelete(); if (file) { filePicked(file, 0).then((res) => { + //TODO:loading try { - console.log("res", res) let array: { [key: string]: string | number }[] = [] - if (Array.isArray(res)) - res.forEach((item, index) => { + let isError = false; + if (Array.isArray(res)) { + res.some((item, index) => { let object: { [key: string]: string | number } = {} - Object.keys(names).forEach((key) => { + Object.keys(names).some((key) => { if (item && names[key] !== undefined) { - if (key === "공장관리번호") - item[key] = String(item[key]) + // if (key === "공장관리번호") { + // item[key] = String(item[key]) + // } else { object[names[key] as string] = item[key]; + // } } - + return false; }) + if (isError === true) + return true; array.push(object) if (index != 0 && index % 5000 == 0) { ldrmntRegData(array); - console.log("array", array) array = [] array.length = 0; - // throw new Error("error") } + return false; }) - ldrmntRegData(array); - console.log("array끝", array) + } + if (isError === false) + ldrmntRegData(array); } catch (e) { console.log(e) + alert(e) + return true; } }); }; } + return ( + <> +
+
+ +
+ +
+
+

+ {"잠재수요처 파일"} +

- return ( - <> -
- - -
- -
-
-

- {"잠재수요처 파일"} -

+ +
- -
- -
- - ( - - { - console.log("files", files) - if (files && files?.length === 1) setFiles(files[0]); - }} - fileTypes="" - maxFiles={1} - > - - )} - /> -
- - -
- - ); - } +
+ ( + + { + if (files && files?.length === 1) setFiles(files[0]); + }} + fileTypes="" + maxFiles={1} + > + + )} + /> +
+ + +
+ + ); +} diff --git a/apps/wr/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal.tsx b/apps/wr/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal.tsx index 03c257c59..0b071aa91 100644 --- a/apps/wr/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/bdstChgModal/bdstChgModal.tsx @@ -14,7 +14,6 @@ const {bdst,rain} = useWrStore((state) => { return state; }); const { bdstChgHeadList } = bdst; - console.log("chgBuilding", chgBuilding); return ( diff --git a/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx b/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx index 315f9397f..c534c38af 100644 --- a/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/bdstRegModal/bdstRegModal.tsx @@ -29,9 +29,11 @@ export type objSearchType = { interface BdstRegProps { moType: T; + isModal:boolean; + setIsModal?: (open: boolean) => void; } -export function BdstReg({ moType }: BdstRegProps) { +export function BdstReg({ moType,setIsModal,isModal }: BdstRegProps) { const { rain, code, bdst,findBdst } = useWrStore((state) => { return state; }); @@ -44,13 +46,13 @@ export function BdstReg({ moType }: BdstRegProps) { const getSelCity = (val: string | number) => { const cityValue = String(val); // Ensure val is treated as a string const data = getCity(cityValue, code.sggCd); + setSelectSgg(data); - getSgg(data[0]?.val || ""); + // getSgg(data[0]?.val || ""); setObjSearch({ city: cityValue, sgg: '' }); }; const getSgg = (val: string | number) => { - console.log(val); setObjSearch({...objSearch,sgg: String(val)}) }; @@ -84,10 +86,7 @@ export function BdstReg({ moType }: BdstRegProps) { if (data) { setRainBdstForm(data); } - - } - } useEffect(()=>{ @@ -121,7 +120,12 @@ export function BdstReg({ moType }: BdstRegProps) {
-

건축물 목록

+

+ 건축물 목록 + + (유의 사항: 신규등록하려는 건축물이 목록에 없을 경우 하단 건축물 정보에 추가하여 주시기 바랍니다.) + +

({ moType }: BdstRegProps) {
{ moType === 'RNB'? - + :moType==='RST' - ? - : + ? {setIsModal && setIsModal(open)}}> + :{setIsModal && setIsModal(open)}}> }
diff --git a/apps/wr/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal.tsx b/apps/wr/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal.tsx index 92b40e146..5b1a6f0f6 100644 --- a/apps/wr/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/fcOrgwtsRegModal/fcOrgwtsRegModal.tsx @@ -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"; @@ -10,139 +14,205 @@ import { Button } from "@common/business_components/ui/table/lib/button"; import { Input } from "@common/components/ui"; import { ArrowLeft, ArrowRight } from "lucide-react"; import { useEffect, useRef, useState } from "react"; +import toast from "react-hot-toast"; -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, + initTableData, +}: { + onRightSet?: (rightData: fcOrgwtType[]) => void; + closeModal?: (val: boolean) => void; + initTableData?: any[]; +}) { + const { code, pbnst, getSwtrmtFcltNm, searchFclts } = useWrStore( + (state) => state + ); const { fcOrgwtsHeadList, pbnstHasuRes } = pbnst; const [bdstTable, setbdstTable] = useState(); const rightRef = useRef([] as pubHasuType[]); const leftRef = useRef([] as pubHasuType[]); const [rightData, setRightData] = useState(); const [inputVal, setInputVal] = useState(""); - - useEffect(()=>{ - if(Array.isArray(searchFclts)){ + const saveData = () => { + if (rightData && rightData?.length > 0) { + if (onRightSet) + onRightSet( + (rightData as pubHasuType[])?.map((item) => { + return { + ...item, + swtrmtFcltFk: item.bscFctCd, // 임시값, 실제 데이터에 맞게 수정 + orgwtWtritQnt: Number(item.fctCpc), // 임시값, 실제 데이터에 맞게 수정 + tmsYn: "N", // 임시값, 실제 데이터에 맞게 수정 + dschgFmYn: "N", // 임시값, 실제 데이터에 맞게 수정 + dtlPstnExpln: item.addr, // 임시값, 실제 데이터에 맞게 수정 + choose: false, // 임시값, 실제 데이터에 맞게 수정 + } + // 필요한 경우 다른 속성들도 추가 + }) as unknown as fcOrgwtType[] + ); + + closeModal?.(false) + } + else { + toast.error("취수하수처리장 목록을 선택해주세요.") + } + } + + 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) + const data = pbnstHasuRes.filter((item) => !initTableData?.find((initItem) => initItem.ruseUtztnFcltNm === item.bscFctNm)) + leftRef.current?.filter((item) => !initTableData?.find((initItem) => initItem.ruseUtztnFcltNm === item.bscFctNm)); + setbdstTable(tableData(data, { code })); } - }, [code, pbnstHasuRes]); + }, [code, pbnstHasuRes, initTableData]); + 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]) + if (initTableData && Array.isArray(initTableData)) { + setRightData(initTableData.map(item => ({ + ...item, + choose: item.choose, + sido: item.dtlPstnExpln?.split(" ")[0] || "", + gugun: item.dtlPstnExpln?.split(" ")[1] || "", + bscFctNm: item.ruseUtztnFcltNm, + addr: item.dtlPstnExpln, + orgwtWtritQnt: String(item.orgwtWtritQnt), + fctCpc: String(item.orgwtWtritQnt), + swtrmtFcltFk: item.swtrmtFcltFk, + }))); + rightRef.current = initTableData.map(item => ({ ...item })); + } + }, []); return ( - <> -
-
- - <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="취수하수처리장 추가" /> + + <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} + + <Label className="text-xl text-gray-600">시설명</Label> + <Input + className="w-48 text-xl" + type="text" + value={inputVal} + onChange={(e) => setInputVal(e.target.value)} /> - </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([]) - } - } + <Button size="sm" onClick={() => { + + if(inputVal){ + + getSwtrmtFcltNm(inputVal) + }else{ + const filterData = pbnstHasuRes.filter((item) => ! rightData?.find(( right) => right.bscFctNm ===item.bscFctNm)) + setbdstTable(tableData(filterData, { code })); + } } - 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} - /> + + }> + 조회 + </Button> + <Button size="sm" onClick={saveData}>저장</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> ); } diff --git a/apps/wr/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal.tsx b/apps/wr/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal.tsx index b930a36bb..846e427c6 100644 --- a/apps/wr/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal.tsx +++ b/apps/wr/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal.tsx @@ -1,6 +1,7 @@ "use client"; import { useWrStore } from "@/app/store"; import { + pbnstRegZod, type customPbnst, type PbnstRegArrrayType, type pubHasuType, @@ -16,17 +17,25 @@ import { } from "@common/components/ui"; import { useEffect, useState } from "react"; import { PbnstBuseForm } from "@/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm"; -import { useFieldArray, type UseFormReturn } from "react-hook-form"; +import { + useFieldArray, + type FieldErrors, + type UseFormReturn, +} from "react-hook-form"; import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg"; +import toast from "react-hot-toast"; +import type { z } from "zod"; export function PbnstRegModal({ - data, form, + setPbnstInsert, + onInvalid }: { - data: pubHasuType; form: UseFormReturn<{ items: PbnstRegArrrayType }>; + setPbnstInsert: (data: customPbnst) => void; + onInvalid:(errors: FieldErrors<{ items: z.infer<typeof pbnstRegZod>[] }>) => void }) { - const { pbnst, pbnstInsert } = useWrStore((state) => { + const { pbnstInsert } = useWrStore((state) => { return state; }); const [pbnstBdstForm, setPbnstBdstForm] = useState<customPbnst>( @@ -35,40 +44,93 @@ 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]) : []; - if(data && data[0]){ - pbnstInsert(data[0]) - } - }; + + const onSubmit = async (valuse: { items: PbnstRegArrrayType }) => { + const item = valuse.items[indexMng]; + const data = item ? writeRgtrAndMdfr([item]) : []; + if (data && data[0]) { + setPbnstInsert(data[0]); + } + }; + // const onInvalid = ( + // errors: FieldErrors<{ items: z.infer<typeof pbnstRegZod>[] }> + // ) => { + // // 재귀 함수를 사용해 중첩된 에러 객체에서 모든 에러 메시지와 경로를 찾습니다. + + + // const findAllErrors = ( + // errorObject: any, + // path: (string | number)[] = [] + // ): { message: string; path: (string | number)[] }[] => { + // if (!errorObject) return []; + + // 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}`); + // }); + // } + // }; 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 +148,7 @@ export function PbnstRegModal({ 차수별정보 </TabsTrigger> <Button - onClick={ form.handleSubmit(onSubmit)} + onClick={form.handleSubmit(onSubmit, onInvalid)} size={"sm"} className="!justify-end" > @@ -104,7 +166,7 @@ export function PbnstRegModal({ </TabsContent> <TabsContent className="max-h-full" value="buse"> <PbnstBuseForm - form={form} + form={form} indexMng={indexMng} indexBiz={indexBiz} setIndexBiz={setIndexBiz} diff --git a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx index d20d10591..cec8babd0 100644 --- a/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx +++ b/apps/wr/components/ui/modal/sttn/pbnstSttnModal/pbnstSttnModal.tsx @@ -13,6 +13,7 @@ import { type customPbnst, type fcBizType, } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; +import { formH1Class } from "@/components/lib/class/comClass"; import { PbnstMngForm } from "@/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm"; import { ScrollArea, Title, UiTable } from "@common/business_components/ui"; import { HeadMakeColSpan } from "@common/business_components/ui/table/HeadMakeColSpan"; @@ -28,10 +29,10 @@ 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"); + const [ruseTab, setRuseTab] = useState<string>("totalPrf"); const [ruseBizData, setRuseBizData] = useState<{ data: any[]; head: TableUpperProps[]; @@ -229,7 +230,8 @@ export function PbnstSttnModal({ data }: { data: customPbnst }) { (item) => item.pbnstRuseFcltPk === data.pbnstRuseFcltPk )?.prfList; - if (prfList) {console.log("prfList", prfList); + if (prfList) { + console.log("prfList", prfList); getTablePrfData(prfList); } @@ -237,22 +239,26 @@ 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]) + }, [data, form]) - useEffect(()=>{ + useEffect(() => { console.log("prfData", prfData); - },[prfData]) + }, [prfData]) return ( <> <div> - <Title title={"하수재이용 시설 상세정보"} /> + {titleVisible && ( + <> + <Title title={"하수재이용 시설 상세정보"} /> + </> + )} <ScrollArea className={"h-[70vh]"}> <PbnstMngForm form={form} @@ -260,72 +266,79 @@ export function PbnstSttnModal({ data }: { data: customPbnst }) { indexMng={indexMng} setIndexMng={setIndexMng} /> - <Tabs defaultValue="fclt" onValueChange={setFcltTab}> - <TabsList className=""> - <TabsTrigger className="!text-xl" value="fclt"> - 하수처리수 재이용시설 현황 - </TabsTrigger> - <TabsTrigger className="!text-xl" value="sply"> - 재이용 원수 취수 현황 - </TabsTrigger> - <TabsTrigger className="!text-xl" value="ppi"> - 재이용 공급관로 현황 - </TabsTrigger> - <TabsTrigger className="!text-xl" value="guarantee"> - 재이용 보증수질 현황 - </TabsTrigger> - <TabsTrigger className="!text-xl" value="dmdCo"> - 재이용 수요처 현황 - </TabsTrigger> - </TabsList> - <TabsContent className="max-h-full" value={fcltTab}> - <UiTable - headName={undefined} - tableData={ruseTableData || []} - headlist={ruseHeadList || []} - pageSize={0} - total={0} - cellClick={undefined} - onPageChange={function (page: number): void { - throw new Error("Function not implemented."); - }} - children={ - fcltTab === "fclt" ? ( - <HeadMakeColSpan upHeadList={pbnstSttn.ruseFcltNodeList} /> - ) : undefined - } - ></UiTable> - </TabsContent> - </Tabs> - - <Tabs onValueChange={setRuseTab}> - <TabsList className=""> - <TabsTrigger className="!text-xl" value="totalPrf"> - 장내외합계 - </TabsTrigger> - <TabsTrigger className="!text-xl" value="inPrf"> - 장내용수 - </TabsTrigger> - <TabsTrigger className="!text-xl" value="outPrf"> - 장외용수 - </TabsTrigger> - </TabsList> - <TabsContent className="max-h-full" value={ruseTab}> - {prfData && ( + <div className="px-6"> + <div className=""> + <h1 className={formH1Class}>하수재이용시설 현황</h1> + </div> + <Tabs defaultValue="fclt" onValueChange={setFcltTab}> + <TabsList className="mt-4"> + <TabsTrigger className="!text-xl" value="fclt"> + 하수처리수 재이용시설 현황 + </TabsTrigger> + <TabsTrigger className="!text-xl" value="sply"> + 재이용 원수 취수 현황 + </TabsTrigger> + <TabsTrigger className="!text-xl" value="ppi"> + 재이용 공급관로 현황 + </TabsTrigger> + <TabsTrigger className="!text-xl" value="guarantee"> + 재이용 보증수질 현황 + </TabsTrigger> + <TabsTrigger className="!text-xl" value="dmdCo"> + 재이용 수요처 현황 + </TabsTrigger> + </TabsList> + <TabsContent className="max-h-full" value={fcltTab}> <UiTable headName={undefined} - tableData={prfData[ruseTab as keyof typeof prfData] || []} - headlist={monthHeadList || []} + tableData={ruseTableData || []} + headlist={ruseHeadList || []} pageSize={0} total={0} cellClick={undefined} onPageChange={function (page: number): void { throw new Error("Function not implemented."); }} + children={ + fcltTab === "fclt" ? ( + <HeadMakeColSpan upHeadList={pbnstSttn.ruseFcltNodeList} /> + ) : undefined + } ></UiTable> - )} - </TabsContent> - </Tabs> + </TabsContent> + </Tabs> + <div className=""> + <h1 className={formH1Class}>하수재이용실적</h1> + </div> + <Tabs defaultValue="totalPrf" onValueChange={setRuseTab}> + <TabsList className="mt-4"> + <TabsTrigger className="!text-xl" value="totalPrf"> + 장내외합계 + </TabsTrigger> + <TabsTrigger className="!text-xl" value="inPrf"> + 장내용수 + </TabsTrigger> + <TabsTrigger className="!text-xl" value="outPrf"> + 장외용수 + </TabsTrigger> + </TabsList> + <TabsContent className="max-h-full" value={ruseTab}> + {prfData && ( + <UiTable + headName={undefined} + tableData={prfData[ruseTab as keyof typeof prfData] || []} + headlist={monthHeadList || []} + pageSize={0} + total={0} + cellClick={undefined} + onPageChange={function (page: number): void { + throw new Error("Function not implemented."); + }} + ></UiTable> + )} + </TabsContent> + </Tabs> + </div> </ScrollArea> </div> </> diff --git a/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx b/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx index edffcc71d..b7229a559 100644 --- a/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx +++ b/apps/wr/components/ui/modal/sttn/sttnDetailModal/coDatailModal.tsx @@ -7,7 +7,7 @@ import { handleCommaNumberChange } from "@/components/lib/func/commom"; import { DatePicker, Form, FormField, FormUi, ScrollArea } from "@common/business_components/ui"; import { Button, Input } from "@common/components/ui"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import type { z } from "zod"; @@ -33,8 +33,24 @@ export default function SttnDetailModal({ useEffect(() => { if (ldrmntZod) form.reset(formObj); - }, [formObj]); + + useEffect(() => { + const male = Number(form.watch("maleEmpCnt") ?? 0); + const female = Number(form.watch("fmaleEmpCnt") ?? 0); + const frgnrMale = Number(form.watch("frgnrMaleEmpCnt") ?? 0); + const frgnrFemale = Number(form.watch("frgnrFmaleEmpCnt") ?? 0); + + const total = male + female + frgnrMale + frgnrFemale; + + form.setValue("totalEmpCnt", total); + }, [ + form.watch("maleEmpCnt"), + form.watch("fmaleEmpCnt"), + form.watch("frgnrMaleEmpCnt"), + form.watch("frgnrFmaleEmpCnt"), + ]); + return ( <div> <ScrollArea className="h-[85vh]"> @@ -324,6 +340,7 @@ export default function SttnDetailModal({ handleCommaNumberChange(e, field.onChange); }} value={field.value ?? 0} + min={0} /> </FormUi> )} @@ -343,6 +360,7 @@ export default function SttnDetailModal({ field.onChange(e.target.value); }} value={field.value ?? 0} + min={0} /> </FormUi> )} @@ -362,6 +380,7 @@ export default function SttnDetailModal({ field.onChange(e.target.value); }} value={field.value ?? 0} + min={0} /> </FormUi> )} @@ -381,6 +400,7 @@ export default function SttnDetailModal({ field.onChange(e.target.value); }} value={field.value ?? 0} + min={0} /> </FormUi> )} @@ -400,6 +420,8 @@ export default function SttnDetailModal({ field.onChange(e.target.value); }} value={field.value ?? 0} + min={0} + readOnly={true} /> </FormUi> )} diff --git a/apps/wr/components/ui/searchInput/searchInput.tsx b/apps/wr/components/ui/searchInput/searchInput.tsx index 796c8c298..be63b9685 100644 --- a/apps/wr/components/ui/searchInput/searchInput.tsx +++ b/apps/wr/components/ui/searchInput/searchInput.tsx @@ -62,9 +62,6 @@ export default function SearchInputText({ document.removeEventListener("mousedown", handleClickOutside); }; }, []); -useEffect(() =>{ - console.log('searchList ', searchList) -},[searchList]) return ( <div ref={wrapperRef}> diff --git a/apps/wr/components/ui/table/chkgdTable/guideDetailTable/guideDetailTable.tsx b/apps/wr/components/ui/table/chkgdTable/guideDetailTable/guideDetailTable.tsx index 94be4ebb6..627537dcb 100644 --- a/apps/wr/components/ui/table/chkgdTable/guideDetailTable/guideDetailTable.tsx +++ b/apps/wr/components/ui/table/chkgdTable/guideDetailTable/guideDetailTable.tsx @@ -4,6 +4,7 @@ import { useWrStore } from "@/app/store"; import type { TableUpperProps } from "@/app/store/common/type/header"; import type { } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; +import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; import { InputTableBody } from "@/components/lib/func/TableData"; import { Input, @@ -13,16 +14,18 @@ import { TableHeader, TableRow, } from "@common/components/ui"; -import { useEffect, useState, type Dispatch, type SetStateAction } from "react"; +import { useEffect, useState, type Dispatch, type SetStateAction } from "react"; export function GuideDetailsTable({ chgRes, headList, + upheadList, type, setChGuidData, }: { chgRes: any[]; - headList: TableUpperProps[]; + headList: TableUpperProps[]; + upheadList: chHeadType[]; type: string; setChGuidData: (chgRes: any[]) => void; }) { @@ -35,21 +38,21 @@ export function GuideDetailsTable({ useEffect(() => { - setChGuidData(inputData??[] ); + setChGuidData(inputData ?? []); }, [inputData]); - + return ( <> - <div className={`card !m-[1.2rem] !p-0r `}> - <Table variant="secondary"> - <TableHeader className="font-bold">{headMakeprfmnc(headList, rainChkgdn?.rainHeadNm)}</TableHeader> - <TableBody > - {InputTableBody(inputData ?? [],headList, setInputData) } - </TableBody> - </Table> - </div> + <div className={`card !m-[1.2rem] !p-0r `}> + <Table variant="secondary"> + <TableHeader className="font-bold">{headMakeprfmnc(headList, upheadList)}</TableHeader> + <TableBody > + {InputTableBody(inputData ?? [], headList, setInputData)} + </TableBody> + </Table> + </div> </> ); } diff --git a/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx b/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx index d0ad437b3..aba225687 100644 --- a/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx +++ b/apps/wr/components/ui/table/chkgdTable/rainChkgd/rainChkgdTable.tsx @@ -1,15 +1,16 @@ "use client"; import { headMakeprfmnc } from "@/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead"; import { useWrStore } from "@/app/store"; -import type { rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; -import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; import type { TableUpperProps } from "@/app/store/common/type/header"; +import { rainPageSize, type rainChkZodType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn"; +import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; import { tableData } from "@/components/lib/func/commom"; import { chRowspanTable, - headVisibleChange, rainChkTableData } from "@/components/lib/func/TableData"; +import LoadingOverlay from "@common/business_components/ui/loadingBar"; +import UiPageNations from "@common/business_components/ui/table/uiPageNations"; import { Table, TableBody, @@ -17,21 +18,27 @@ import { } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; export default function RainChkgdTable({ - detailUp, - handleClick=() => {}, + detailUp, + handleClick = () => { }, deleteData, rainChkData, + pageNation, + onPageChange, + pageCount, }: { detailUp: (data: rainChkZodType[]) => void; - handleClick?: (data:any) => void; - deleteData:(selectedIds:number[])=>void + handleClick?: (data: any) => void; + deleteData: (selectedIds: number[]) => void rainChkData?: rainChkZodType[]; + pageNation: number; + onPageChange: (page: number) => void; + pageCount: number; }) { - const { code, rainChkgdn ,rainChkSttn,getRainSttnExcel } = useWrStore((state) => state); + const { code, rainChkgdn, rainChkSttn, getRainSttnExcel } = useWrStore((state) => state); const [makeTableData, setMakeTableData] = useState<any[]>([]); const [selectedIds, setSelectedIds] = useState<number[]>([]); const [upRaintDetail, setUpRaintDetail] = useState<rainChkZodType[]>([]); - const [headList, setHeadList] = useState< {headList:TableUpperProps[],upHeadList:chHeadType[]}>({headList:[], upHeadList:[]}); + const [headList, setHeadList] = useState<{ headList: TableUpperProps[], upHeadList: chHeadType[] }>({ headList: [], upHeadList: [] }); const toggleSelection = (pk: number, checked: boolean) => { @@ -46,30 +53,31 @@ export default function RainChkgdTable({ ) => { setMakeTableData((prev) => - prev.map((row, idx) => { - return idx === rowIndex ? { ...row, [field]: value } : row + prev.map((row, idx) => { + return idx === rowIndex ? { ...row, [field]: value } : row } ) ); }; - const handleCellClick= useCallback((index: number, headId: string) => { + const handleCellClick = useCallback((index: number, headId: string) => { if ( headId === "totalSplyUswtrUtztnQnt" || headId === "totalRuseFcltUtztnQnt" ) { - + handleClick(makeTableData[index]) } - },[makeTableData, handleClick]); + }, [makeTableData, handleClick]); useEffect(() => { setSelectedIds([])// 셀렉트 초기화를 위한 작업 - if(rainChkData){ - const rainChg = rainChkData.map((item: any) => { + if (rainChkData && rainChkData.length > 0) { + + const rainChg = rainChkData?.map((item: any) => { const bud = tableData([item.building], { code }); return { ...item, @@ -77,16 +85,19 @@ export default function RainChkgdTable({ rowspan: item.rainyDetails?.length, }; }); + setMakeTableData(rainChkTableData(rainChg)); + } else { + setMakeTableData([]); } }, [rainChkData, code]); useEffect(() => { //데이터를 평탄화 한것중에 체크박스 선택한 테이블만 선택 - + //현황 페이지에서 필요한 엑셀 데이터를 바뀔때마다 넣기 위해서 getRainSttnExcel(makeTableData); - const selectedData = makeTableData.filter((item) =>selectedIds.includes(item.chkgdnPk)); + const selectedData = makeTableData.filter((item) => selectedIds.includes(item.chkgdnPk)); if (selectedData.length > 0) { const updateTableData = rainChkgdn.rainChk @@ -101,7 +112,6 @@ export default function RainChkgdTable({ ); if (findRain != undefined) { - console.log(findRain) return { ...rd, ph: Number(findRain.ph), @@ -127,63 +137,70 @@ export default function RainChkgdTable({ } }) .filter(Boolean); - + setUpRaintDetail(updateTableData as rainChkZodType[]); } - }, [makeTableData,selectedIds]); + }, [makeTableData, selectedIds]); useEffect(() => { detailUp(upRaintDetail); }, [upRaintDetail]); - useEffect(() =>{ - if(selectedIds.length >0){ + useEffect(() => { + if (selectedIds.length > 0) { // 삭제를 위한 데이터 삽입 - deleteData(selectedIds ) + deleteData(selectedIds) } - },[selectedIds]) + }, [selectedIds]) ///현황일 경우 input 비활성화 한다. - useEffect(() => { - if(rainChkSttn.isSttn){ - + useEffect(() => { + if (rainChkSttn.isSttn) { + setHeadList({ headList: rainChkSttn.rainChkgdnHeadList, upHeadList: rainChkSttn.rainHeadNm }) - }else{ + } else { setHeadList({ headList: rainChkgdn.rainChkgdnHeadList, - upHeadList: rainChkgdn.rainHeadNm + upHeadList: rainChkgdn.rainMainHeadNm }) } - - }, [rainChkSttn.isSttn, rainChkgdn.rainChkgdnHeadList , rainChkSttn.rainChkgdnHeadList]); - + }, [rainChkSttn.isSttn, rainChkgdn.rainChkgdnHeadList, rainChkSttn.rainChkgdnHeadList]); return ( - <Table variant="secondary"> - <TableHeader className="font-bold"> - {headMakeprfmnc( - headList?.headList, - headList?.upHeadList - )} - </TableHeader> + <> + <LoadingOverlay isLoading={rainChkgdn.isLoading}></LoadingOverlay> + <Table variant="secondary"> + <TableHeader className="font-bold"> + {headMakeprfmnc( + headList?.headList, + headList?.upHeadList + )} + </TableHeader> - <TableBody> - {chRowspanTable( - makeTableData, - headList.headList, - handleChange, - toggleSelection, - selectedIds, - handleCellClick - )} - </TableBody> - </Table> + <TableBody> + {chRowspanTable( + makeTableData, + headList.headList, + handleChange, + toggleSelection, + selectedIds, + handleCellClick + )} + </TableBody> + </Table> + <UiPageNations + total={pageCount} + cur={pageNation} + pageSize={rainPageSize} + onPageChange={onPageChange} + /> + </> ); } diff --git a/apps/wr/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable.tsx b/apps/wr/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable.tsx index da6cacc9a..7a43df3d8 100644 --- a/apps/wr/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable.tsx +++ b/apps/wr/components/ui/table/chkgdTable/rswtChkgd/rswtChkgdTable.tsx @@ -1,35 +1,44 @@ "use client"; import { headMakeprfmnc } from "@/app/ruseFcltMng/prfmncReg/chkgdnReg/TableHead"; import { useWrStore } from "@/app/store"; -import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; -import type { - rswtChkZodType, - rswtrChkDetailsZodType, -} from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn"; import type { TableUpperProps } from "@/app/store/common/type/header"; +import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; +import { + rswtPageSize, + type rswtChkZodType, + type rswtrChkDetailsZodType, +} from "@/app/store/prfmncReg/chkgdnReg/rswrtChkgdn"; import { tableData } from "@/components/lib/func/commom"; import { chRowspanTable, rswtChkTableData, } from "@/components/lib/func/TableData"; +import LoadingOverlay from "@common/business_components/ui/loadingBar"; +import UiPageNations from "@common/business_components/ui/table/uiPageNations"; import { Table, TableBody, TableHeader } from "@common/components/ui"; import { useCallback, useEffect, useState } from "react"; export default function RswtChkgdTable({ detaiRswtlUpdate, - handleRswtClick = () => {}, + handleRswtClick = () => { }, deleteRswtData, rswtChkData, + pageNation, + onPageChange, + pageCount, }: { detaiRswtlUpdate: (data: rswtrChkDetailsZodType[]) => void; handleRswtClick?: (data: any) => void; deleteRswtData: (selectedIds: number[]) => void; rswtChkData: rswtChkZodType[]; + pageNation: number; + onPageChange: (page: number) => void; + pageCount: number; }) { - const { code, rswtChkgdn,rswtChkSttn , rswtChkgdnInit, rswtChkPolAndGuide,getRswtSttnExcel } = useWrStore( + const { code, rswtChkgdn, rswtChkSttn, rswtChkgdnInit, rswtChkPolAndGuide, getRswtSttnExcel } = useWrStore( (state) => state ); - const [headList, setHeadList] = useState< {headList:TableUpperProps[],upHeadList:chHeadType[]}>({headList:[], upHeadList:[]}); + const [headList, setHeadList] = useState<{ headList: TableUpperProps[], upHeadList: chHeadType[] }>({ headList: [], upHeadList: [] }); const [makeTableData, setMakeTableData] = useState<any[]>([]); const [selectedIds, setSelectedIds] = useState<number[]>([]); @@ -67,7 +76,6 @@ export default function RswtChkgdTable({ }, [rswtChkgdn.rswtChkPolGuide]); useEffect(() => { - console.log("makeTableData ", makeTableData) getRswtSttnExcel(makeTableData) const selectedData = makeTableData .map((item) => { @@ -111,25 +119,24 @@ export default function RswtChkgdTable({ }, [selectedIds]); ///현황일 경우 input 비활성화 한다. - useEffect(() => { - if(rswtChkSttn.isSttn){ + useEffect(() => { + if (rswtChkSttn.isSttn) { setHeadList({ headList: rswtChkSttn.rswtChkSttnHeadList, upHeadList: rswtChkSttn.rswtSttnHeadNm }) - }else{ + } else { setHeadList({ headList: rswtChkgdn.rswtChkHeadList, upHeadList: rswtChkgdn.rswtHeadNm }) } - - }, [rswtChkSttn.isSttn, rswtChkgdn.rswtChkHeadList , rswtChkSttn.rswtChkSttnHeadList]); - + }, [rswtChkSttn.isSttn, rswtChkgdn.rswtChkHeadList, rswtChkSttn.rswtChkSttnHeadList]); return ( <> + <LoadingOverlay isLoading={rswtChkgdn.isLoading}></LoadingOverlay> <Table variant="secondary"> <TableHeader className="font-bold"> {headMakeprfmnc(headList?.headList, headList?.upHeadList)} @@ -146,6 +153,12 @@ export default function RswtChkgdTable({ )} </TableBody> </Table> + <UiPageNations + total={pageCount} + cur={pageNation} + pageSize={rswtPageSize} + onPageChange={onPageChange} + /> </> ); } diff --git a/apps/wr/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm.tsx b/apps/wr/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm.tsx index acb923687..6f4832c68 100644 --- a/apps/wr/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm.tsx +++ b/apps/wr/components/ui/table/pbnstReg/ruseMngTableForm/ruseMngTableForm.tsx @@ -3,6 +3,10 @@ import type { TableUpperProps } from "@/app/store/common/type/header"; import type { chHeadType } from "@/app/store/prfmncReg/chkgdnReg/rainChkgdn/headList"; import type { customPbnst as OriginalCustomPbnst } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg/type"; import { selectClass } from "@/app/store/ruseFcltMng/fcltReg/rainReg"; +import { + formatCommaNumber, + handleCommaNumberChange, +} from "@/components/lib/func/commom"; import { DatePicker, @@ -32,12 +36,24 @@ type RuseMngTableProps = { indexMng?: number; indexBiz?: number; form?: any; - formNm?:string + formNm?: string; }; const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>( - ({ tableData, headList, upHeadList, code, onTableSet, indexMng, form,formNm,indexBiz }, ref) => { - + ( + { + tableData, + headList, + upHeadList, + code, + onTableSet, + indexMng, + form, + formNm, + indexBiz, + }, + ref + ) => { return ( <> <Table key={`table-${indexMng}-${indexBiz}`} variant="secondary"> @@ -73,26 +89,31 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>( typeof indexMng === "number" && indexMng >= 0 && typeof indexBiz === "number" && - indexBiz >= 0? - `items.${indexMng}.fcBizs.${indexBiz}.${formNm}.${i}.${head.id}`: - typeof indexMng === "number" && indexMng >= 0? - `items.${indexMng}.${formNm}.${i}.${head.id}`: - `items.${indexMng}.${formNm}.${i}.${head.id}`} + indexBiz >= 0 + ? `items.${indexMng}.fcBizs.${indexBiz}.${formNm}.${i}.${head.id}` + : typeof indexMng === "number" && indexMng >= 0 + ? `items.${indexMng}.${formNm}.${i}.${head.id}` + : `items.${indexMng}.${formNm}.${i}.${head.id}` + } render={({ field }) => ( <FormUi label=""> {head.edit === true ? ( <Input className={`${head.type === "number" ? "text-right" : "text-center"} !text-xl`} - type={head.type} + type="text" onChange={(txt) => { - field.onChange(txt.target.value); + if (head.type === "number") { + handleCommaNumberChange(txt, field.onChange); + } else { + field.onChange(txt.target.value); + } }} value={ head.type === "number" - ? (Number(field.value) ?? 0) + ? formatCommaNumber(field.value) : field.value || "" } - readOnly ={head.readonly ? true : false} + readOnly={head.readonly ? true : false} /> ) : head.type === "select" ? ( <SelectBox @@ -102,43 +123,41 @@ const RuseMngTableForm = React.forwardRef<any, RuseMngTableProps>( (item) => item.name === head.selectData )?.val ?? [] } - value={ - field.value || "" - } + value={field.value || ""} className={""} selectClass={selectClass} onSelectValue={(val) => { - - if(head.readonly === true){ - return - }else{ - + if (head.readonly === true) { + return; + } else { field.onChange(val); } }} - readOnly ={head.readonly ? true : false} + readOnly={head.readonly ? true : false} // Disabled prop removed as SelectBox does not support it ></SelectBox> ) : head.type === "date" ? ( <DatePicker mode="single" field={{ - value:typeof field.value === "string" ? new Date(field.value) : field.value, + value: + typeof field.value === "string" + ? new Date(field.value) + : field.value, onChange: (date: Date | undefined) => { - console.log("date", date); - field.onChange(date); // readonly 상태가 아닐 때만 값 변경 + field.onChange(date); // readonly 상태가 아닐 때만 값 변경 }, }} selectClass={"w-full"} - readOnly ={head.readonly ? true : false} + readOnly={head.readonly ? true : false} /> ) : head.type === "checkbox" ? ( <Checkbox className="" - checked={ field.value || false} + checked={field.value || false} onCheckedChange={(checked) => { - field.onChange(checked); + field.onChange(checked); }} ></Checkbox> ) : ( diff --git a/apps/wr/middleware.ts b/apps/wr/middleware.ts index 330252074..5acc41863 100644 --- a/apps/wr/middleware.ts +++ b/apps/wr/middleware.ts @@ -5,6 +5,9 @@ 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 || ""; +const SEWERAGE_API_URL = process.env.NEXT_PUBLIC_SEWERAGE_API_URL || ""; + export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; console.log(`[Middleware] Request URL: ${req.url}`); @@ -44,9 +47,26 @@ 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)); + } + + if (pathname.startsWith("/sewerage-service")) { + const queryParams = searchParams.toString(); + const newUrl = `${SEWERAGE_API_URL}${pathname.replace("/sewerage-service/api", "")}${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*", "/sewerage-service/:path*"], }; diff --git a/apps/wr/public/images/map/layericon/BML_CBAS_PS.png b/apps/wr/public/images/map/layericon/BML_CBAS_PS.png new file mode 100644 index 000000000..5ff4add3e Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_CBAS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_ELEV_PS.png b/apps/wr/public/images/map/layericon/BML_ELEV_PS.png new file mode 100644 index 000000000..83970a331 Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_ELEV_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_ETCP_PS.png b/apps/wr/public/images/map/layericon/BML_ETCP_PS.png new file mode 100644 index 000000000..df9e4957b Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_ETCP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_STMS_PS.png b/apps/wr/public/images/map/layericon/BML_STMS_PS.png new file mode 100644 index 000000000..423f2e8fc Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_STMS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/BML_SYMB_PS.png b/apps/wr/public/images/map/layericon/BML_SYMB_PS.png new file mode 100644 index 000000000..497589bea Binary files /dev/null and b/apps/wr/public/images/map/layericon/BML_SYMB_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_BYST_PS.png b/apps/wr/public/images/map/layericon/RDL_BYST_PS.png new file mode 100644 index 000000000..1e78cba68 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_BYST_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_CROS_PS.png b/apps/wr/public/images/map/layericon/RDL_CROS_PS.png new file mode 100644 index 000000000..57b5f2ca5 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_CROS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png b/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png new file mode 100644 index 000000000..29c0b77b4 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_ETCT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png b/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png new file mode 100644 index 000000000..8a1ac0074 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_OCUP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_RDSN_PS.png b/apps/wr/public/images/map/layericon/RDL_RDSN_PS.png new file mode 100644 index 000000000..5629de9fb Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_RDSN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_SCLT_PS.png b/apps/wr/public/images/map/layericon/RDL_SCLT_PS.png new file mode 100644 index 000000000..7281dc61d Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_SCLT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png b/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png new file mode 100644 index 000000000..087b4e941 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_SEPC_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_STAT_PS.png b/apps/wr/public/images/map/layericon/RDL_STAT_PS.png new file mode 100644 index 000000000..13d8ea689 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_STAT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_STLT_PS.png b/apps/wr/public/images/map/layericon/RDL_STLT_PS.png new file mode 100644 index 000000000..489524b5c Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_STLT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TFSN_PS.png b/apps/wr/public/images/map/layericon/RDL_TFSN_PS.png new file mode 100644 index 000000000..6dfff6696 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TFSN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TRCT_PS.png b/apps/wr/public/images/map/layericon/RDL_TRCT_PS.png new file mode 100644 index 000000000..ed1a72d8a Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TRCT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TREE_PS.png b/apps/wr/public/images/map/layericon/RDL_TREE_PS.png new file mode 100644 index 000000000..0ee84dedc Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TREE_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TRSN_PS.png b/apps/wr/public/images/map/layericon/RDL_TRSN_PS.png new file mode 100644 index 000000000..15675cb31 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TRSN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png b/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png new file mode 100644 index 000000000..ffc225159 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TSCT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_CLAY_PS.png b/apps/wr/public/images/map/layericon/SWL_CLAY_PS.png new file mode 100644 index 000000000..e603ffa9c Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_CLAY_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_CLAY_PS_RED.png b/apps/wr/public/images/map/layericon/SWL_CLAY_PS_RED.png new file mode 100644 index 000000000..0e5450688 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_CLAY_PS_RED.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_DEPT_PS.png b/apps/wr/public/images/map/layericon/SWL_DEPT_PS.png new file mode 100644 index 000000000..fede6c304 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_DEPT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS.png new file mode 100644 index 000000000..75217f494 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLACK.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLACK.png new file mode 100644 index 000000000..5d2e8a70a Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLACK.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLUE.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLUE.png new file mode 100644 index 000000000..e521cb84a Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_BLUE.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_GRAY.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_GRAY.png new file mode 100644 index 000000000..e19f3d8b3 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_GRAY.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_GREEN.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_GREEN.png new file mode 100644 index 000000000..4503c13c6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_GREEN.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_RED.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_RED.png new file mode 100644 index 000000000..b8fa45f9c Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_RED.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_MANH_PS_YELLOW.png b/apps/wr/public/images/map/layericon/SWL_MANH_PS_YELLOW.png new file mode 100644 index 000000000..b7de1fbfd Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_MANH_PS_YELLOW.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPEW_PS.png b/apps/wr/public/images/map/layericon/SWL_SPEW_PS.png new file mode 100644 index 000000000..d260ea3a6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPEW_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS.png new file mode 100644 index 000000000..84c7992e4 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLACK.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLACK.png new file mode 100644 index 000000000..fe67f8f98 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLACK.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLUE.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLUE.png new file mode 100644 index 000000000..691f2f45e Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_BLUE.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREEN.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREEN.png new file mode 100644 index 000000000..d09c4bd97 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREEN.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREY.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREY.png new file mode 100644 index 000000000..32d0bf519 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_GREY.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_SPOT_PS_RED.png b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_RED.png new file mode 100644 index 000000000..a2da36f9b Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_SPOT_PS_RED.png differ diff --git a/apps/wr/public/images/map/layericon/SWL_VENT_PS.png b/apps/wr/public/images/map/layericon/SWL_VENT_PS.png new file mode 100644 index 000000000..834eeec21 Binary files /dev/null and b/apps/wr/public/images/map/layericon/SWL_VENT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPBD_ENTRC.png b/apps/wr/public/images/map/layericon/TL_SPBD_ENTRC.png new file mode 100644 index 000000000..8e7de8593 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPBD_ENTRC.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPGF_LOC.png b/apps/wr/public/images/map/layericon/TL_SPGF_LOC.png new file mode 100644 index 000000000..74b0adac3 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPGF_LOC.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPOT_NOIN.png b/apps/wr/public/images/map/layericon/TL_SPOT_NOIN.png new file mode 100644 index 000000000..4800beaeb Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPOT_NOIN.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_BANO.png b/apps/wr/public/images/map/layericon/TL_SPRD_BANO.png new file mode 100644 index 000000000..8d08dca29 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_BANO.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_CRSRD.png b/apps/wr/public/images/map/layericon/TL_SPRD_CRSRD.png new file mode 100644 index 000000000..57b5f2ca5 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_CRSRD.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_NONM.png b/apps/wr/public/images/map/layericon/TL_SPRD_NONM.png new file mode 100644 index 000000000..e68c05ab6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_NONM.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_RENM.png b/apps/wr/public/images/map/layericon/TL_SPRD_RENM.png new file mode 100644 index 000000000..4800beaeb Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_RENM.png differ diff --git a/apps/wr/public/images/map/layericon/TL_SPRD_RWNM.png b/apps/wr/public/images/map/layericon/TL_SPRD_RWNM.png new file mode 100644 index 000000000..e68c05ab6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/TL_SPRD_RWNM.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BMAN_PS.png b/apps/wr/public/images/map/layericon/UFL_BMAN_PS.png new file mode 100644 index 000000000..f2f64a641 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BMAN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BOPC_PS.png b/apps/wr/public/images/map/layericon/UFL_BOPC_PS.png new file mode 100644 index 000000000..0f8e99f45 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BOPC_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BPIP_PS.png b/apps/wr/public/images/map/layericon/UFL_BPIP_PS.png new file mode 100644 index 000000000..dcc3622a7 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BPIP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BPOL_PS.png b/apps/wr/public/images/map/layericon/UFL_BPOL_PS.png new file mode 100644 index 000000000..4f1102989 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BPOL_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_BTRS_PS.png b/apps/wr/public/images/map/layericon/UFL_BTRS_PS.png new file mode 100644 index 000000000..557c17089 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_BTRS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_GMNT_PS.png b/apps/wr/public/images/map/layericon/UFL_GMNT_PS.png new file mode 100644 index 000000000..6081d8b00 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_GMNT_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_GPIP_PS.png b/apps/wr/public/images/map/layericon/UFL_GPIP_PS.png new file mode 100644 index 000000000..5d7e86fd2 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_GPIP_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_GPRS_PS.png b/apps/wr/public/images/map/layericon/UFL_GPRS_PS.png new file mode 100644 index 000000000..79c120f20 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_GPRS_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_GVAL_PS.png b/apps/wr/public/images/map/layericon/UFL_GVAL_PS.png new file mode 100644 index 000000000..e8f7cde2e Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_GVAL_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_KMAN_PS.png b/apps/wr/public/images/map/layericon/UFL_KMAN_PS.png new file mode 100644 index 000000000..764ce8ba5 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_KMAN_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_KPOL_PS.png b/apps/wr/public/images/map/layericon/UFL_KPOL_PS.png new file mode 100644 index 000000000..d9fa712cf Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_KPOL_PS.png differ diff --git a/apps/wr/public/images/map/layericon/UFL_VALV_PS.png b/apps/wr/public/images/map/layericon/UFL_VALV_PS.png new file mode 100644 index 000000000..1b5d70178 Binary files /dev/null and b/apps/wr/public/images/map/layericon/UFL_VALV_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_FIRE_PS.png b/apps/wr/public/images/map/layericon/WTL_FIRE_PS.png new file mode 100644 index 000000000..4137a3f74 Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_FIRE_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_FLOW_PS.png b/apps/wr/public/images/map/layericon/WTL_FLOW_PS.png new file mode 100644 index 000000000..285f7edfa Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_FLOW_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_MANH_PS.png b/apps/wr/public/images/map/layericon/WTL_MANH_PS.png new file mode 100644 index 000000000..5f2a57cfe Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_MANH_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_META_PS.png b/apps/wr/public/images/map/layericon/WTL_META_PS.png new file mode 100644 index 000000000..22c7af31b Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_META_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_PIPE_PS.png b/apps/wr/public/images/map/layericon/WTL_PIPE_PS.png new file mode 100644 index 000000000..c9c6863f6 Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_PIPE_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_RSRV_PS.png b/apps/wr/public/images/map/layericon/WTL_RSRV_PS.png new file mode 100644 index 000000000..d7b307a61 Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_RSRV_PS.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_RSRV_PS_GRAY.png b/apps/wr/public/images/map/layericon/WTL_RSRV_PS_GRAY.png new file mode 100644 index 000000000..33a667a1e Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_RSRV_PS_GRAY.png differ diff --git a/apps/wr/public/images/map/layericon/WTL_VALV_PS.png b/apps/wr/public/images/map/layericon/WTL_VALV_PS.png new file mode 100644 index 000000000..ca2b4788b Binary files /dev/null and b/apps/wr/public/images/map/layericon/WTL_VALV_PS.png differ diff --git a/apps/wr/public/images/map/layericon/default.png b/apps/wr/public/images/map/layericon/default.png new file mode 100644 index 000000000..92bf1f587 Binary files /dev/null and b/apps/wr/public/images/map/layericon/default.png differ diff --git a/apps/wr/public/images/map/layericon/reset.png b/apps/wr/public/images/map/layericon/reset.png new file mode 100644 index 000000000..45f889206 Binary files /dev/null and b/apps/wr/public/images/map/layericon/reset.png differ diff --git a/apps/wr/public/sample_pbnstPrfmncReg.xlsx b/apps/wr/public/sample_pbnstPrfmncReg.xlsx index 67f0d559e..1f2ff81aa 100644 Binary files a/apps/wr/public/sample_pbnstPrfmncReg.xlsx and b/apps/wr/public/sample_pbnstPrfmncReg.xlsx differ diff --git a/apps/wr/public/sample_rainy.xlsx b/apps/wr/public/sample_rainy.xlsx index 73a52d89c..ec7577c51 100644 Binary files a/apps/wr/public/sample_rainy.xlsx and b/apps/wr/public/sample_rainy.xlsx differ diff --git a/apps/wr/public/sample_ruseCaseMng.xlsx b/apps/wr/public/sample_ruseCaseMng.xlsx new file mode 100644 index 000000000..96027d557 Binary files /dev/null and b/apps/wr/public/sample_ruseCaseMng.xlsx differ diff --git a/packages/common/business_components/package.json b/packages/common/business_components/package.json index da6f8c344..2c0b6ea47 100644 --- a/packages/common/business_components/package.json +++ b/packages/common/business_components/package.json @@ -44,6 +44,7 @@ "recharts": "^2.15.1", "sonner": "^2.0.1", "tailwind-merge": "^3.0.1", + "tailwindcss-animate": "^1.0.7", "xlsx": "^0.18.5", "xlsx-populate": "^1.21.0", "zod": "^3.24.2" diff --git a/packages/common/business_components/src/ui/chart/DoubleBar.tsx b/packages/common/business_components/src/ui/chart/DoubleBar.tsx index b201b3bf9..9627485aa 100644 --- a/packages/common/business_components/src/ui/chart/DoubleBar.tsx +++ b/packages/common/business_components/src/ui/chart/DoubleBar.tsx @@ -7,7 +7,6 @@ import { } from "./lib/chart"; import type { CSSProperties } from "react"; -/* const customStyles = { legendContainer: { display: "flex", @@ -18,22 +17,6 @@ const customStyles = { marginTop: "10px", } as CSSProperties, }; -*/ - -const customStyles = { - legendContainer: { - display: "flex", - flexWrap: "wrap", - justifyContent: "center", - alignItems: "center", - gap: "12px", - fontSize: "13px", - fontWeight: "normal", - marginTop: "16px", - marginBottom: "8px", - minHeight: "32px", - } as CSSProperties, -}; type BarChartProps = { chartConfig: ChartConfig | undefined; @@ -77,7 +60,7 @@ const CustomTooltip = ({ }; // 커스텀 범례 (글씨 크기 키우기 + 스타일 추가) -/*const CustomLegend = (props: any) => { +const CustomLegend = (props: any) => { const { payload } = props; return ( <div style={customStyles.legendContainer}> @@ -89,38 +72,6 @@ const CustomTooltip = ({ ))} </div> ); -};*/ -const CustomLegend = (props: any) => { - const { payload } = props; - if (!payload) return null; - - return ( - <div style={customStyles.legendContainer}> - {payload.map((entry: any, index: number) => ( - <div - key={`legend-${index}`} - className="flex items-center" - style={{ - minWidth: "90px", - margin: "2px 0", - }} - > - <div - style={{ - width: "14px", - height: "14px", - backgroundColor: entry.color, - borderRadius: "3px", - marginRight: "8px", - minWidth: "14px", - minHeight: "14px", - }} - ></div> - <span style={{ fontSize: "13px", whiteSpace: "nowrap" }}>{entry.value}</span> - </div> - ))} - </div> - ); }; export default function DoubleBarChart({chartConfig=undefined, chartData=[], chartTitle='차트', chartClassName='w-full h-full', graphSize=40, yFieldText='', showLegend=false} : BarChartProps) { @@ -132,7 +83,7 @@ export default function DoubleBarChart({chartConfig=undefined, chartData=[], cha .map(key => item[key]) ).flat()); }; - + console.log(chartData); const barMaxValue = getMaxValue(chartData, 'barVal'); return ( <> diff --git a/packages/common/business_components/src/ui/chart/LineChart.tsx b/packages/common/business_components/src/ui/chart/LineChart.tsx index 28b4ceabf..23570912d 100644 --- a/packages/common/business_components/src/ui/chart/LineChart.tsx +++ b/packages/common/business_components/src/ui/chart/LineChart.tsx @@ -1,11 +1,11 @@ "use client" import type { CSSProperties } from "react"; import { CartesianGrid, Legend, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts"; +import type { ContentType } from "recharts/types/component/DefaultLegendContent"; import { ChartContainer, type ChartConfig } from "./lib/chart"; -import type { ContentType } from "recharts/types/component/DefaultLegendContent"; type LineChartProps = { chartConfig: ChartConfig | undefined; @@ -193,7 +193,7 @@ export default function Linechart({ > <CartesianGrid vertical={false} /> <XAxis dataKey="label" fontSize="8px" fontWeight="bold" /> - <YAxis yAxisId="left" className="text-[12px] font-bold" domain={[0, Math.ceil(lineMaxValue * 1.1)]} tickCount={6} orientation="left" label={{ value: yFieldText, className:"text-[12px] font-bold", angle: -90, position: 'insideLeft' }} /> + <YAxis yAxisId="left" className="text-[12px] font-bold" tickFormatter={(value) => value.toLocaleString()} tickCount={6} orientation="left" label={{ value: yFieldText, className:"text-[12px] font-bold", angle: -90, position: 'insideLeft' }} /> <Tooltip content={<CustomTooltip />} /> {showLegend && <Legend content={CustomLegend as ContentType}/>} {Object.keys(chartData).map((key) => { diff --git a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx index 2b64ab439..6afbde5e6 100644 --- a/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx +++ b/packages/common/business_components/src/ui/date_month_picker/mothPicker.tsx @@ -34,17 +34,17 @@ export type MonthPickerProps = { export function MonthPicker({ currentMonth, onMonthChange, startYear }: MonthPickerProps) { const today = startOfToday(); - const currentYear = today.getFullYear(); +const currentYear = today.getFullYear(); const yearFromProp = format(currentMonth, "yyyy"); - const selectedYear = parseInt(yearFromProp); +const selectedYear = parseInt(yearFromProp); // 현재 월의 시작 날짜 const fullYearStart = parse(yearFromProp, "yyyy", new Date()); const yearEnd = selectedYear === currentYear ? today : endOfYear(fullYearStart); - - + + // startYear가 없으면 기존과 동일하게 (현재년도 - 20년부터 현재년도까지) @@ -59,10 +59,10 @@ export function MonthPicker({ currentMonth, onMonthChange, startYear }: MonthPic cn( "inline-flex h-9 w-16 items-center justify-center rounded-md p-0 text-sm font-normal ring-offset-white transition-colors hover:bg-slate-100 hover:text-slate-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 aria-selected:opacity-100 dark:ring-offset-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50 dark:focus-visible:ring-slate-800", isEqual(month, currentMonth) && - "bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900", + "bg-slate-900 text-slate-50 hover:bg-slate-900 hover:text-slate-50 focus:bg-slate-900 focus:text-slate-50 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50 dark:hover:text-slate-900 dark:focus:bg-slate-50 dark:focus:text-slate-900", !isEqual(month, currentMonth) && - isEqual(month, getStartOfCurrentMonth()) && - "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50" + isEqual(month, getStartOfCurrentMonth()) && + "bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50" ); return ( @@ -118,7 +118,7 @@ export function MonthPicker({ currentMonth, onMonthChange, startYear }: MonthPic onClick={() => onMonthChange(month, true)} > <time dateTime={format(month, "yyyy-MM-dd")}> - {format(month, 'MMM', { locale: ko })} + {format(month, 'MMM', { locale: ko })} </time> </button> </div> diff --git a/packages/common/business_components/src/ui/loadingBar/index.tsx b/packages/common/business_components/src/ui/loadingBar/index.tsx index 37991d1d2..4d637dd25 100644 --- a/packages/common/business_components/src/ui/loadingBar/index.tsx +++ b/packages/common/business_components/src/ui/loadingBar/index.tsx @@ -1,5 +1,5 @@ -import React from 'react'; import { LoadingWithAnimation } from '@common/components/ui/loading'; +import React from 'react'; interface LoadingOverlayProps { isLoading: boolean; diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 6e13d70c5..ef5c21d5b 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -1,5 +1,6 @@ import { Search } from 'lucide-react'; import OlMap from 'ol/Map'; +import Overlay from 'ol/Overlay'; import View from 'ol/View'; import { defaults as defaultControls, OverviewMap } from 'ol/control'; import { defaults } from 'ol/interaction'; @@ -14,27 +15,53 @@ import GisToolBar from './GisToolBar'; import GisTree from './GisTree'; import './lib/css/gis.common.css'; import GisApp from './lib/gis.core'; +// import FacilityInfoPopup from './FacilityInfoPopup'; export const GisContext = createContext({ gisCore: null, gisMap: null, treeData: null, checkedLayerIds: null }); export const useGisContext = () => useContext(GisContext); -function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = false, isMiniMap = false, setIsOpen, setFeatureData, featureData, searchView: SearchView, detailView: DetailView, onFeatureSelect, setIsSearchModalOpen }) { +function Gis({ + isOpen, + gisMaps, + isSearchModalOpen, + treeData = [], + isSearch = false, + isMiniMap = false, + setIsOpen, + setFeatureData, + featureData, + searchView: SearchView, + detailView: DetailView, + onFeatureSelect, + setIsSearchModalOpen, + onCheckedLayersChange +}) { const mapRef = useRef(); const [mapObject, setMapObject] = useState({}); const [tabVisible, setTabVisible] = useState(true); + const [layerNm, setLayerNm] = useState('') // const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태 // const [featureData, setFeatureData] = useState(null) // 선택된 피쳐 데이터 // const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태 const gisCore = useMemo(() => new GisApp.core({ targetId: mapRef.current }), []); const mapInstanceRef = useRef(null); const checkedLayerIds = useRef([]); + + + // 팝업 데이터를 저장할 상태와 오버레이 참조를 저장할 ref 추가 + // const [facilityInfoPopupData, setFacilityInfoPopupData] = useState([]); + // const facilityOverlaysRef = useRef([]); + // 체크된 레이어 상태 관리 + const [checkedLayers, setCheckedLayers] = useState([]); + const [checkedLayersData, setCheckedLayersData] = useState([]); + let gisMap = null; const closePopup = () => { setIsOpen(false); // 팝업 닫기 setFeatureData(null) }; - // SearchModal, GisContent 등에서 호출 + // Feature Click Event const handleFeatureSelect = useCallback((data) => { gisMap = mapInstanceRef.current; @@ -89,8 +116,10 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal setFeatureData(data); setIsOpen(true); setIsSearchModalOpen(false); + onFeatureSelect?.(data); // 선택된 피쳐 데이터 콜백 호출 }, [mapInstanceRef]); + // 지도 생성 및 초기화 useEffect(() => { if (!mapRef.current) return; if (mapInstanceRef.current) return; // 이미 생성된 맵이 있다면 중단 @@ -104,7 +133,7 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal ]); register(proj4); - //맵 생성 + // OpenLayers 지도 객체 생성 const map = new OlMap({ layers: [gisCore.baseLayers['base'], gisCore.baseLayers['satellite']], renderer: 'webgl', @@ -132,7 +161,7 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal gisMaps.current = map - //미니맵 + // 미니맵 추가 if (isMiniMap) { const overviewMap = new OverviewMap({ layers: [GisApp.Module.core.baseMiniMapLayers['base']], @@ -147,6 +176,7 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal map.addControl(overviewMap); } + // 스케일 컨트롤 및 베이스 레이어 설정 gisCore.__proto__.map = map; gisCore.scaleControl(); gisCore.changeBaseLayer('base'); @@ -154,16 +184,20 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal mapInstanceRef.current = map; // 생성 완료 플래그 설정 }, []); + // treeData 변경 시 레이어 코드 생성 useEffect(() => { if (!treeData || treeData.length === 0) return; + // 기존 오버레이 및 레이어 모두 삭제 gisCore.deleteAllOverlay(); gisCore.deleteAllLayer(); + // 트리 데이터를 레이어 코드 객체로 변환 const layerCode = gisCore.convertLayerInfosToLayerCode(treeData, 'lyrSn'); - GisApp.layerCode = layerCode; + GisApp.layerCode = layerCode; // 전역 레이어 코드 설정 //setTreeData(data); + // 편집 가능한 레이어 목록 생성 const layerItems = getLayerItems(layerCode); //setEditLayerItems(prev => [...layerItems]) }, [treeData]); @@ -193,33 +227,168 @@ function Gis({ isOpen, gisMaps, isSearchModalOpen, treeData = [], isSearch = fal } } + // // 데이터 로드 및 팝업 생성 로직 + // useEffect(() => { + // if (!mapInstanceRef.current) return; // 맵이 생성되지 않았으면 실행 안 함 + + // // 1. 팝업 데이터 가져오기 (feature.js의 getData와 유사) + // const fetchPopupData = async () => { + // // 실제 API 호출 로직으로 대체해야 합니다. + // // 예시: const response = await yourApiService.getFcltyWtCnt(); + // // setFacilityInfoPopupData(response.data); + + // // 임시 목업 데이터 + // const mockData = [ + // { ftrIdn: 1, ftrCde: 'SA206', bplcNm: '처리장A', fcltyUseYn: 'Y', x: 127.005, y: 37.282, aCnt: 10, cCnt: 5, eCnt: 3, mCnt: 8, iCnt: 2 }, + // { ftrIdn: 2, ftrCde: 'SA112', bplcNm: '처리장B', fcltyUseYn: 'N', x: 127.010, y: 37.285, aCnt: 8, cCnt: 4, eCnt: 2, mCnt: 6, iCnt: 1 } + // ]; + // setFacilityInfoPopupData(mockData); + // }; + + // fetchPopupData(); + + // }, [mapInstanceRef.current]); // 맵 인스턴스가 생성된 후 한 번만 실행 + + // // 데이터가 변경되면 오버레이를 생성/업데이트하는 로직 + // useEffect(() => { + // const map = mapInstanceRef.current; + // if (!map || facilityInfoPopupData.length === 0) return; + + // // 기존 오버레이 정리 + // facilityOverlaysRef.current.forEach(overlay => map.removeOverlay(overlay)); + // facilityOverlaysRef.current = []; + + // facilityInfoPopupData.forEach(data => { + // const popupId = `${data.ftrIdn}_${data.ftrCde}`; + + // // 팝업을 담을 div 엘리먼트 생성 + // const popupElement = document.createElement('div'); + // popupElement.id = popupId; + + // // OpenLayers Overlay 생성 + // const position = olProj.transform([Number(data.x), Number(data.y)], GisApp.FROM_PROJ, GisApp.TO_PROJ); + // const overlay = new Overlay({ + // element: popupElement, + // position: position, + // offset: [-75, -210], // 팝업 너비의 절반, 높이만큼 오프셋 조정 + // positioning: 'bottom-center', + // className: 'facility-popup-overlay' // 필요시 공통 클래스 추가 + // }); + + // map.addOverlay(overlay); + // facilityOverlaysRef.current.push(overlay); + + // // React 컴포넌트를 popupElement에 렌더링 + // const root = createRoot(popupElement); + // root.render(<FacilityInfoPopup data={data} />); + // }); + + // // 초기 가시성 업데이트 + // updatePopupVisibility(); + + // }, [facilityInfoPopupData]); // 팝업 데이터가 변경될 때마다 실행 + + // // 레이어 가시성에 따라 팝업 가시성을 제어하는 함수 + // const updatePopupVisibility = useCallback(() => { + // const map = mapInstanceRef.current; + // if (!map) return; + + // const visibleLayers = new Set(); + // map.getLayers().forEach(layer => { + // if (layer.getVisible()) { + // // feature.js의 로직처럼 typeName이나 ftrCde를 사용해 필터링 + // const typeName = layer.get('typeName'); + // if (typeName === 'VW_WTL_FCLTY_AS') { // 예시 레이어 이름 + // visibleLayers.add(layer.get('ftrCde')); + // } + // } + // }); + + // facilityOverlaysRef.current.forEach(overlay => { + // const element = overlay.getElement(); + // const ftrCde = element.querySelector('table')?.classList[0]; // FacilityPopup의 클래스에서 ftrCde 가져오기 + + // if (visibleLayers.has(ftrCde)) { + // element.style.display = ''; + // } else { + // element.style.display = 'none'; + // } + // }); + // }, []); + + // GisTree에서 체크 상태가 변경될 때 이 함수를 호출하도록 연결해야 합니다. + // 예시: GisTree 컴포넌트에 onCheck 콜백을 추가하고, 그 콜백에서 이 함수를 호출 + // useEffect(() => { + // // 레이어 가시성이 변경될 때마다 팝업 가시성 업데이트 + // const map = mapInstanceRef.current; + // if (!map) return; + + // const handleLayerVisibilityChange = () => { + // updatePopupVisibility(); + // }; + + // map.getLayers().on('change:length', handleLayerVisibilityChange); + // map.getLayers().forEach(layer => { + // layer.on('change:visible', handleLayerVisibilityChange); + // }); + + // return () => { + // // 클린업 + // map.getLayers().un('change:length', handleLayerVisibilityChange); + // map.getLayers().forEach(layer => { + // layer.un('change:visible', handleLayerVisibilityChange); + // }); + // }; + // }, [updatePopupVisibility]); + + // 체크된 레이어 정보를 처리하는 함수 + const handleCheckedLayersChange = useCallback((checkedIds, checkedNodesData) => { + setTimeout(() => { + setCheckedLayers(checkedIds); + setCheckedLayersData(checkedNodesData); + onCheckedLayersChange?.(checkedIds, checkedNodesData); + }, 0); + }, [onCheckedLayersChange]); + return ( <> <GisContext.Provider value={{ gisCore, gisMap: mapInstanceRef.current, treeData, checkedLayerIds }}> <div className="h-[calc(100vh-10rem)] flex relative"> <div ref={mapRef} className="gis-map flex-1 relative" style={{ height: '100%', padding: 0 }} /> - <GisTree visible={tabVisible} setVisible={setTabVisible} /> + <GisTree + visible={tabVisible} + setVisible={setTabVisible} + onCheckedChange={handleCheckedLayersChange} + onLayerIconClick={(nodeNm) => { + setLayerNm(nodeNm); setIsOpen(true); const dummyFeature = { + getProperties: () => ({}), // 최소 구조 + }; + onFeatureSelect(dummyFeature); + }} + /> {/* <GisContent onFeatureSelect={handleFeatureSelect} isSearch={isSearch} isSearchModalOpen={isSearchModalOpen} setIsSearchModalOpen={setIsSearchModalOpen} /> */} - {/* isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} - {isOpen && featureData && !isSearchModalOpen && ( + {/* DetailView가 존재할 때만 렌더링 isSearchModalOpen이 true면 visible=false로 팝업 숨김 */} + {isOpen && !isSearchModalOpen && ( <DetailView - featureData={featureData} + featureData={featureData || { getProperties: () => ({}) }} + seletectedLayerNm={layerNm} onClose={closePopup} // 닫기 콜백 전달 /> )} - {SearchView &&( - <SearchView - open={isSearchModalOpen} - onOpenChange={setIsSearchModalOpen} - onFeatureSelect={onFeatureSelect} - /> + {/* SearchView가 존재할 때만 렌더링 */} + {SearchView && ( + <SearchView + open={isSearchModalOpen} + onOpenChange={setIsSearchModalOpen} + onFeatureSelect={handleFeatureSelect} + /> )} <GisLonLat /> <GisToolBar /> <GisMapToggle /> - <GisSelect onFeatureSelect={onFeatureSelect} /> + <GisSelect onFeatureSelect={handleFeatureSelect} /> </div> {isSearch && ( diff --git a/packages/common/business_components/src/ui/map/GisContent.js b/packages/common/business_components/src/ui/map/GisContent.js index a8595efb0..d2594ba95 100644 --- a/packages/common/business_components/src/ui/map/GisContent.js +++ b/packages/common/business_components/src/ui/map/GisContent.js @@ -1,12 +1,8 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import GisApp from './lib/gis.core'; -import { useGisContext } from './Gis'; -import GisLonLat from './GisLonLat'; -import GisSelect from './GisSelect'; -import GisToolBar from './GisToolBar'; -import './lib/css/gis.edit.css'; -import GisMapToggle from './GisMapToggle'; import { Search } from 'lucide-react'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useGisContext } from './Gis'; +import './lib/css/gis.edit.css'; +import GisApp from './lib/gis.core'; export default function GisContent({ edit = false, onFeatureSelect, isSearch, setIsSearchModalOpen }) { const { gisMap, gisCore } = useGisContext(); diff --git a/packages/common/business_components/src/ui/map/GisMapToggle.js b/packages/common/business_components/src/ui/map/GisMapToggle.js index b157dc141..c890f75f8 100644 --- a/packages/common/business_components/src/ui/map/GisMapToggle.js +++ b/packages/common/business_components/src/ui/map/GisMapToggle.js @@ -1,7 +1,5 @@ import React, { useContext, useState } from 'react'; -import { Button } from "@common/components"; import { GisContext } from './Gis'; -import GisApp from './lib/gis.core'; export default React.memo(function MapToggleBtn() { const { gisCore } = useContext(GisContext); diff --git a/packages/common/business_components/src/ui/map/GisSelect.js b/packages/common/business_components/src/ui/map/GisSelect.js index a9116e626..e9deb72f7 100644 --- a/packages/common/business_components/src/ui/map/GisSelect.js +++ b/packages/common/business_components/src/ui/map/GisSelect.js @@ -1,7 +1,7 @@ -import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import React, { useContext, useEffect } from 'react'; +import { GisContext } from './Gis'; import GisApp from './lib/gis.core'; import GisOlSelect from './lib/gis.select'; -import { GisContext } from './Gis'; export default React.memo(function GisSelect({ onFeatureSelect }) { diff --git a/packages/common/business_components/src/ui/map/GisTree.js b/packages/common/business_components/src/ui/map/GisTree.js index 6a2e8face..5b30dc7f4 100644 --- a/packages/common/business_components/src/ui/map/GisTree.js +++ b/packages/common/business_components/src/ui/map/GisTree.js @@ -1,15 +1,16 @@ -import GisApp from './lib/gis.core'; -import React, { useEffect, useState } from 'react'; -import { useGisContext } from './Gis'; -import { DndProvider } from "react-dnd"; import { - Tree, MultiBackend, + Tree, getBackendOptions } from "@minoru/react-dnd-treeview"; import { ChevronLeft, ChevronRight } from 'lucide-react'; +import React, { useEffect, useState } from 'react'; +import { DndProvider } from "react-dnd"; -export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible }) { +import { useGisContext } from './Gis'; +import GisApp from './lib/gis.core'; + +export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible, onCheckedChange, onLayerIconClick }) { const { gisCore, treeData } = useGisContext(); const [checked, setChecked] = useState([]); const [expanded, setExpanded] = useState([]); @@ -24,19 +25,31 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes } }, [treeData]); + // 트리 노드 체크 시 레이어 표시/숨김 useEffect(() => { if (nodes.length > 0) { + // 기본으로 체크된 노드들 찾기 const defaultChecked = getDefaultCheckedNodes(nodes); setChecked(defaultChecked); + console.log('defaultChecked : ', defaultChecked); + + // 기본으로 확장된 노드들 설정 const defaultExpanded = Array.from(new Set([ ...defaultChecked, ...defaultChecked.flatMap((id) => getAncestors(id, nodes)) ])); setExpanded(defaultExpanded); - gisCore.setLayerOnMap(defaultChecked, true); - } - }, [nodes]); + // 기본 체크된 레이어들을 지도에 표시 + gisCore.setLayerOnMap(defaultChecked, true); + + // 초기 체크된 레이어 정보를 상위로 전달 + const defaultCheckedNodesData = nodes.filter(node => defaultChecked.includes(node.id)); + onCheckedChange?.(defaultChecked, defaultCheckedNodesData); + } + }, [nodes]); // onCheckedChange를 의존성에 추가 + + // 초기 표시 여부가 'Y'인 레이어들 찾기 const getDefaultCheckedNodes = (nodes, values = []) => { for (let i = 0; i < nodes.length; i++) { const node = nodes[i]; @@ -94,6 +107,11 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes const unchecked = allIds.filter((nid) => !newChecked.includes(nid)); gisCore.setLayerOnMap(newChecked, true); gisCore.setLayerOnMap(unchecked, false); + + // 체크된 노드 정보와 함께 노드 데이터를 상위로 전달 + const checkedNodesData = nodes.filter(node => newChecked.includes(node.id)); + onCheckedChange?.(newChecked, checkedNodesData); + return newChecked; }); }; @@ -170,7 +188,10 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes image.src = canvas.toDataURL('image/png'); return image; }; - + const handleLayerImgClick = (nodeNm) => { + console.log('click!', nodeNm) + onLayerIconClick(nodeNm) + } if (!treeData || treeData.length === 0) return null; return ( @@ -203,6 +224,7 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes <input type="checkbox" checked={checked.includes(node.id)} onChange={() => handleCheck(node.id)} className="cursor-pointer" /> {node.icon && <span>{node.icon}</span>} <span className="text-[1.3rem] font-medium text-label">{node.text}</span> + {depth > 0 && <span><img className="cursor-pointer w-5 h-5" src='/images/map/layericon/Layer_Select_BTN.png' onClick={()=>{handleLayerImgClick(node.text)}} alt={node.text}></img></span>} </div> )} sort={(a, b) => { diff --git a/packages/common/business_components/src/ui/search_form_left/index.tsx b/packages/common/business_components/src/ui/search_form_left/index.tsx index d0da2cd1a..e7858df2a 100644 --- a/packages/common/business_components/src/ui/search_form_left/index.tsx +++ b/packages/common/business_components/src/ui/search_form_left/index.tsx @@ -301,7 +301,7 @@ function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], sel resetAllValues(); // 리셋 후 트리거를 false로 설정 if (setRtnPartValue) { - setRtnPartValue('A'); + setRtnPartValue('0'); } } }, [resetTrigger]); diff --git a/packages/common/business_components/src/ui/select/index.tsx b/packages/common/business_components/src/ui/select/index.tsx index 6fa78ccf2..8843aa04e 100644 --- a/packages/common/business_components/src/ui/select/index.tsx +++ b/packages/common/business_components/src/ui/select/index.tsx @@ -1,3 +1,5 @@ +// selectBox + import { Label } from "@common/components"; import React from "react"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "./lib/selectBox"; @@ -5,7 +7,8 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from ". /** 차트 기본 스타일 */ interface SelectBox1 { text:string, - val:string|number + val:string|number, + hidden?: boolean } const SelectBox = React.forwardRef< @@ -20,17 +23,29 @@ const SelectBox = React.forwardRef< readOnly?: boolean // readOnly 속성 추가 } >(({ label, className, selectClass, selectArray, onSelectValue, value, readOnly = false }, ref)=>{ - const placeHolder = selectArray?.[0]?.text ?? ""; + // hidden이 true인 첫 번째 항목을 placeholder로 사용 + const hiddenItem = selectArray.find(item => item.hidden); + const placeHolder = hiddenItem?.text ?? "선택"; + + // hidden이 true인 항목만 드롭다운에서 제외 + const visibleItems = selectArray.filter(item => !item.hidden); + + // 현재 값이 hidden 항목이거나 빈 값인 경우 undefined로 설정하여 placeholder가 표시되도록 함 + const currentValue = value !== undefined && + hiddenItem && + (String(value) === String(hiddenItem.val) || value === "" || value === "0" || value === 0) + ? undefined + : (value !== undefined ? String(value) : undefined); return ( <div className={className+" flex items-center"} ref={ref}> {label && <Label className="whitespace-nowrap w-20 text-xl text-gray-600">{label}</Label>} - <Select onValueChange={onSelectValue} value={value !== undefined ? String(value) : undefined}> + <Select onValueChange={onSelectValue} value={currentValue} disabled={readOnly}> <SelectTrigger className={selectClass?selectClass:"flex-1 p-2 border rounded"}> <SelectValue placeholder={placeHolder} /> </SelectTrigger> <SelectContent> - {selectArray.map((selectArray) => ( + {visibleItems.map((selectArray) => ( <SelectItem key={selectArray.val} value={String(selectArray.val)}>{selectArray.text}</SelectItem> ))} </SelectContent> diff --git a/packages/common/business_components/src/ui/table/index-2.tsx b/packages/common/business_components/src/ui/table/index-2.tsx index 5961ea396..9756d2dfa 100644 --- a/packages/common/business_components/src/ui/table/index-2.tsx +++ b/packages/common/business_components/src/ui/table/index-2.tsx @@ -139,7 +139,7 @@ export function UiTable2({ </div> {/* 페이지네이션 하단 고정 */} - { ( + {( <div className="sticky left-0 right-0 bottom-0 bg-white z-20 mt-5"> <UiPageNations total={total} diff --git a/packages/common/business_components/src/ui/table/index.tsx b/packages/common/business_components/src/ui/table/index.tsx index bb90153c2..fe720abc0 100644 --- a/packages/common/business_components/src/ui/table/index.tsx +++ b/packages/common/business_components/src/ui/table/index.tsx @@ -9,28 +9,28 @@ import { boolean } from "zod"; type UiTableProps = { headName: React.ReactNode | undefined; - tableData: { [key: string]:any}[]; + tableData: { [key: string]: any }[]; headlist: UiHeadProps[]; pageSize: number; total: number; - cellClick : ((index:number|undefined, headId:string)=>void )| undefined; + cellClick: ((index: number | undefined, headId: string) => void) | undefined; onPageChange: (page: number) => void; className?: string; resetPageNo?: boolean; children?: React.ReactNode | undefined; boldArray?: number[]; resetSelection?: boolean; - index?:number; + index?: number; } type UiHeadProps = { id: string; title: string; align?: string; - visible?:boolean; + visible?: boolean; } -export function UiTable({ tableData, headlist, pageSize, total, headName, children, cellClick, onPageChange, className = '', resetPageNo = false, boldArray = [], resetSelection = false, index=-1 }: UiTableProps) { +export function UiTable({ tableData, headlist, pageSize, total, headName, children, cellClick, onPageChange, className = '', resetPageNo = false, boldArray = [], resetSelection = false, index = -1 }: UiTableProps) { const [cur, setCur] = useState<number>(1); const [idx, setIdx] = useState<number>(index); const [isCheck, seIsCheck] = useState<boolean>(false); @@ -54,77 +54,75 @@ export function UiTable({ tableData, headlist, pageSize, total, headName, childr onPageChange(page); } - const handleCellClick = (index: number,headId:string )=> { + const handleCellClick = (index: number, headId: string) => { if (cellClick) { - cellClick(index,headId); + cellClick(index, headId); setIdx(index); } }; - + return ( <> {headName ? <div className="card-header"> <h3 className="text-2xl font-medium text-label">{headName}</h3> - </div> : <></>} + </div> : <></>} <div className={`card !m-[1.2rem] !p-0r ${className}`}> <Table variant="secondary"> <TableHeader className="font-bold"> {children ? children : <HeadMakeColSpan upHeadList={headlist} />} </TableHeader> <TableBody className='text-center'> - {tableData.length > 0 ? tableData?.map((el: { [key: string]: any}, index) => ( + {tableData.length > 0 ? tableData?.map((el: { [key: string]: any }, index) => ( <TableRow key={`row-${index}`}> {headlist - .filter((item) => item.visible != false) - .map((head) => ( - head.id && head.id.length > 1 && ( - boldArray.includes(index) - ? - <TableCell - className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : 'text-center'} ${ - cellClick && idx === index ? '!font-[1000] cursor-pointer' : - cellClick ? 'cursor-pointer' : '' - } !font-bold`} - onClick={() => handleCellClick(index, head.id)} - key={`cell-${head.id}-${index}`} - > - {typeof el[head.id] !== 'number'?String(el[head.id] ?? ''): String(el[head.id]).includes('.') - ? String(el[head.id]).split('.').map((part, index) => - index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part - ).join('.') - : String(el[head.id] ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")} - </TableCell> - : - <TableCell - className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : 'text-center'} ${ - cellClick && idx === index ? '!font-[1000] cursor-pointer' : - cellClick ? 'cursor-pointer' : '' - }`} - onClick={() => handleCellClick(index, head.id)} - key={`cell-${head.id}-${index}`} - > - {typeof el[head.id] === 'boolean' - ?<Checkbox - checked={el[head.id] === true} - onCheckedChange={(checked) => { - el[head.id]= checked; - }} - > - <Check className="h-6 w-6 text-primary" /> - </Checkbox> - : - typeof el[head.id] !== 'number'? - String(el[head.id] ?? ''): - String(el[head.id]).includes('.') - ? String(el[head.id]).split('.').map((part, index) => - index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part - ).join('.') - : - String(el[head.id] ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")} - </TableCell> - ) - ))} + .filter((item) => item.visible != false) + .map((head) => ( + head.id && head.id.length > 1 && ( + boldArray.includes(index) + ? + <TableCell + className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : 'text-center'} ${cellClick && idx === index ? '!font-[1000] cursor-pointer' : + cellClick ? 'cursor-pointer' : '' + } !font-bold`} + onClick={() => handleCellClick(index, head.id)} + key={`cell-${head.id}-${index}`} + > + {typeof el[head.id] !== 'number' ? String(el[head.id] ?? '') : String(el[head.id]).includes('.') + ? String(el[head.id]).split('.').map((part, index) => + index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part + ).join('.') + : String(el[head.id] ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")} + </TableCell> + : + <TableCell + className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : 'text-center'} ${cellClick && idx === index ? '!font-[1000] cursor-pointer' : + cellClick ? 'cursor-pointer' : '' + }`} + onClick={() => handleCellClick(index, head.id)} + key={`cell-${head.id}-${index}`} + > + {typeof el[head.id] === 'boolean' + ? <Checkbox + checked={el[head.id] === true} + onCheckedChange={(checked) => { + el[head.id] = checked; + }} + > + <Check className="h-6 w-6 text-primary" /> + </Checkbox> + : + typeof el[head.id] !== 'number' ? + String(el[head.id] ?? '') : + String(el[head.id]).includes('.') + ? String(el[head.id]).split('.').map((part, index) => + index === 0 ? part.replace(/\B(?=(\d{3})+(?!\d))/g, ",") : part + ).join('.') + : + String(el[head.id] ?? '').replace(/\B(?=(\d{3})+(?!\d))/g, ",")} + </TableCell> + ) + ))} </TableRow> )) : <></>} </TableBody> diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 142279c5e..555292098 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -580,6 +580,9 @@ importers: tailwind-merge: specifier: ^3.0.1 version: 3.0.2 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.17) xlsx: specifier: ^0.18.5 version: 0.18.5 @@ -2533,7 +2536,7 @@ packages: dnd-multi-backend: 8.1.2(dnd-core@16.0.1) framer-motion: 11.18.2(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 - react-dnd: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1) + react-dnd: 16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1) react-dnd-html5-backend: 16.0.1 react-dnd-touch-backend: 16.0.1 react-dom: 18.3.1(react@18.3.1)