diff --git a/apps/sn/app/components/ui/govSidoSigunguSearchForm.tsx b/apps/sn/app/components/ui/govSidoSigunguSearchForm.tsx index 3cfb8725c..b46f2b481 100644 --- a/apps/sn/app/components/ui/govSidoSigunguSearchForm.tsx +++ b/apps/sn/app/components/ui/govSidoSigunguSearchForm.tsx @@ -53,7 +53,7 @@ type SearchFormLeftProps = { setRtnMiddleRegionValue?: (value: string) => void; setRtnEnvPartValue?: (value: string) => void; setRtnEnvPartSidoValue?: (value: string) => void; - setRtnEnvPartSigunValue?: (value: string) => void; + setRtnEnvPartSigunValue?: (value: { val: string, text: string }) => void; // setRtnEnvPartSigunValue prop 타입을 객체로 변경 resetTrigger?: boolean; } @@ -169,24 +169,10 @@ function GovSidoSigunguSearchForm({ isShowPart = false, children, selectSidoData } }, [isShowPart]); // isShowPart가 변경될 때 데이터를 다시 로드 - // 시군구 세팅 (행정구역별) - const initializeSigunData = (sidoCd: string) => { - if (sidoCd === '0') { - // setSigunValue('0'); - setFilteredEnvPartSigunData([{ text: "전체", val: '0' }, ...allSigunOptions]); // 전체 시군구 - } else { - const newFilteredData = [ - { text: "전체", val: '0' }, - ...allSigunOptions.filter((item: SelectDataType) => item.val.substring(0, 2) === sidoCd) - ]; - // setSigunValue('0'); - setFilteredEnvPartSigunData(newFilteredData); - } - } - // 환경청 세팅 - 시도 const initializeCityDataByEnvPartSido = (envPartCd: string) => { const foundEnvPart = allEnvPartOptions.find((item: EnvPartSelectDataType) => item.val === envPartCd); + console.log('foundEnvPart:', foundEnvPart); let relevantSidos: SelectDataType[] = []; // 기본값을 빈 배열로 설정 let relevantSigunsForSidoDerivation: SelectDataType[] = []; // 기본값을 빈 배열로 설정 @@ -258,18 +244,36 @@ function GovSidoSigunguSearchForm({ isShowPart = false, children, selectSidoData initializeCityDataByEnvPartSido(val); setEnvPartSidoValue('0'); // 시도 "전체"로 초기화 setEnvPartSigunValue('0'); // 시군구 "전체"로 초기화 - // onSelectValue(filteredEnvPartSidoData[0]?.val || '0', 'envPartSido'); // 이 부분은 setEnvPartSidoValue('0')와 initializeCityDataByEnvPartSigun('0')으로 대체됨 + // 부모에게도 초기화된 값 전달 (text 포함) + if (setRtnEnvPartSidoValue) { + const sidoDefault = filteredEnvPartSidoData.find(item => item.val === '0') || { val: '0', text: '전체' }; + // setRtnEnvPartSidoValue(sidoDefault.val); // 이전 방식: 값만 전달 + // TODO: 만약 시도 값도 객체로 전달해야 한다면 아래와 같이 수정 + // setRtnEnvPartSidoValue({ val: sidoDefault.val, text: sidoDefault.text }); + } + if (setRtnEnvPartSigunValue) { + const sigunDefault = filteredEnvPartSigunData.find(item => item.val === '0') || { val: '0', text: '전체' }; + setRtnEnvPartSigunValue({ val: sigunDefault.val, text: sigunDefault.text }); + } break; case 'envPartSido': // 환경청별 - 시도 선택 setEnvPartSidoValue(val); setRtnEnvPartSidoValue?.(val); initializeCityDataByEnvPartSigun(val); setEnvPartSigunValue('0'); // 시군구 "전체"로 초기화 - // onSelectValue(filteredEnvPartSigunData[0]?.val || '0', 'envPartSigun'); + if (setRtnEnvPartSigunValue) { + const sigunDefault = filteredEnvPartSigunData.find(item => item.val === '0') || { val: '0', text: '전체' }; + setRtnEnvPartSigunValue({ val: sigunDefault.val, text: sigunDefault.text }); + } break; case 'envPartSigun': // 환경청별 - 시군구 선택 setEnvPartSigunValue(val); - setRtnEnvPartSigunValue?.(val); + const selectedSigun = filteredEnvPartSigunData.find(item => item.val === val); + if (setRtnEnvPartSigunValue && selectedSigun) { + setRtnEnvPartSigunValue({ val: selectedSigun.val, text: selectedSigun.text }); + } else if (setRtnEnvPartSigunValue && val === '0') { // "전체"를 명시적으로 처리 (find 실패 대비) + setRtnEnvPartSigunValue({ val: '0', text: '전체' }); + } break; } } @@ -298,7 +302,7 @@ function GovSidoSigunguSearchForm({ isShowPart = false, children, selectSidoData setRtnMiddleRegionValue?.('0'); setRtnEnvPartValue?.('0'); setRtnEnvPartSidoValue?.('0'); - setRtnEnvPartSigunValue?.('0'); + setRtnEnvPartSigunValue?.({ val: '0', text: '전체' }); }; // 리셋 트리거 변경 감지 diff --git a/apps/sn/app/ii/BaseLayout.tsx b/apps/sn/app/ii/BaseLayout.tsx index aa16ce982..3974b2407 100644 --- a/apps/sn/app/ii/BaseLayout.tsx +++ b/apps/sn/app/ii/BaseLayout.tsx @@ -1,4 +1,5 @@ 'use client' +import { getData } from '@/components/lib/api' import ContentLayout from '@/components/ui/contentLayout' import { GovSidoSigunguSearchForm } from '@/components/ui/govSidoSigunguSearchForm' import { Form, SearchFormRight, SelectBox } from '@common/business_components/ui' @@ -6,7 +7,7 @@ import { SidoSigunguSelectBox } from '../components/ui/SidoSigunguSelectBox' import { SelectBox2 } from '@common/business_components/ui/select/index-2' import ListDiv from '@common/business_components/ui/list_div' import { Button, Label } from '@common/components/ui' -import { type ReactNode } from 'react' +import { useEffect, useState, type ReactNode } from 'react' type TitleName = { mainTitle: string @@ -24,6 +25,11 @@ type Label = { third: string fourth: string } +// API로부터 받아올 데이터 타입을 정의 (SelectBox에 사용될 형태) +interface ApiSelectOption { + text: string; + val: string; +} type BaseLayout = { children?: ReactNode[] titleName: TitleName @@ -32,10 +38,16 @@ type BaseLayout = { graphBtn?: boolean firstDivBtn?: boolean searchBox?: SelectBox - sido?: number - setSido?: (sido: number) => void - sigungu?: number - setSigungu?: (sigungu: number) => void + enoCd?: string | number + setEnoCd?: (enoCd: string) => void + ctpvCd?: string | number + setCtpvCd?: (ctpvCd: string) => void + sigungu?: ApiSelectOption + setSigungu?: (sigungu: ApiSelectOption) => void + selectedSwageTreatmentPlant?: string | number; + onSwageTreatmentPlantChange?: (value: string | number) => void; + selectedSwageProcessingZone?: string | number; + onSwageProcessingZoneChange?: (value: string | number) => void; } // TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ? /** @@ -47,7 +59,106 @@ type BaseLayout = { * @param graphBtn : 하단에 그래프 버튼 유무 처리 * @returns */ -const BaseLayout = ({ children, titleName, form, onSubmitEvent, graphBtn = false, searchBox, sido, setSido, sigungu, setSigungu }: BaseLayout) => { +const BaseLayout = ({ + children, + titleName, + form, + onSubmitEvent, + graphBtn = false, + searchBox, + enoCd, + setEnoCd, + ctpvCd, + setCtpvCd, + sigungu, + setSigungu, + selectedSwageTreatmentPlant, + onSwageTreatmentPlantChange, + selectedSwageProcessingZone, + onSwageProcessingZoneChange, +}: BaseLayout) => { + + const defaultOption: ApiSelectOption = { val: '0', text: '선택' }; + + const [processingZoneOptions, setProcessingZoneOptions] = useState([]); + const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState([]); + const [isLoadingAreaData, setIsLoadingAreaData] = useState(false); + + useEffect(() => { + const fetchData = async () => { + if (!sigungu || sigungu.val === '0' || !sigungu.val) { + console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sigungu?.val); + setProcessingZoneOptions([defaultOption]); + setTreatmentFacilityOptions([defaultOption]); + setIsLoadingAreaData(false); + return; + } + + setIsLoadingAreaData(true); + try { + const queryParamsZone = new URLSearchParams({ + lyrTypeNm: 'tb_ms_swage_prcs_zone', + page: '0', + size: '100000', + sort: 'string', + }); + const cqlFiltersZone: string[] = []; + if (sigungu.val && sigungu.val !== '0') { + cqlFiltersZone.push(`sgg_cd='${sigungu.val}'`); + } + if (cqlFiltersZone.length > 0) { + queryParamsZone.append('cqlFiter', cqlFiltersZone.join(' and ')); + } + + const queryParamsFacility = new URLSearchParams({ + lyrTypeNm: 'tb_il_swage_prcs_fclt', + page: '0', + size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능 + sort: 'string', + }); + const cqlFiltersFacility: string[] = []; + if (sigungu.text && sigungu.val !== '0') { + cqlFiltersFacility.push(`sgg_nm ILIKE '%${sigungu.text}%'`); + } + if (cqlFiltersFacility.length > 0) { + queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); // API 명세에 따라 cqlFilter 또는 cqlFiter + } + + console.log("BaseLayout API URL for Zones:", `v1/fclts?${queryParamsZone.toString()}`); + console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`); + + const [zoneRes, facilityRes] = await Promise.all([ + getData(`v1/fclts?${queryParamsZone.toString()}`), + getData(`v1/fclts?${queryParamsFacility.toString()}`) + ]); + + const zones = (zoneRes.data || []).map((item: any) => ({ + text: item.swtrmtZoneNm || item.name || `구역 ${item.id || item.gid || item.objectid}`, + val: item.fid || item.id || item.gid || item.objectid + })); + setProcessingZoneOptions([defaultOption, ...zones]); + console.log("BaseLayout Fetched Processing Zones:", [defaultOption, ...zones]); + + + const facilities = (facilityRes.data || []).map((item: any) => ({ + text: item.swtrmtFcltNm || item.name || `시설 ${item.id || item.gid || item.objectid}`, + val: item.fid || item.id || item.gid || item.objectid + })); + setTreatmentFacilityOptions([defaultOption, ...facilities]); + console.log("BaseLayout Fetched Treatment Facilities:", [defaultOption, ...facilities]); + + } catch (error) { + console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error); + setProcessingZoneOptions([defaultOption]); + setTreatmentFacilityOptions([defaultOption]); + } finally { + setIsLoadingAreaData(false); + } + }; + + fetchData(); + }, [sigungu]); // sigungu 상태가 변경될 때마다 실행 + return ( @@ -63,27 +174,15 @@ const BaseLayout = ({ children, titleName, form, onSubmitEvent, graphBtn = false )}
- {/*
-
*/} - {/* { }} /> - { - setSido?.(sido); - setSigungu?.(sigungu ?? 0); - }} - onSigunguChange={setSigungu ?? (() => { })} - /> - { }} /> - { }} /> */} - - { }} /> - { }} /> + setEnoCd?.(val)} + setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)} + setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달 + > + onSwageTreatmentPlantChange?.(val)} /> + onSwageProcessingZoneChange?.(val)} /> - {/*
-
*/} {searchBox?.searchChk ? : null}
diff --git a/apps/sn/app/ii/irsetting/page.tsx b/apps/sn/app/ii/irsetting/page.tsx index c21610f68..be24229a0 100644 --- a/apps/sn/app/ii/irsetting/page.tsx +++ b/apps/sn/app/ii/irsetting/page.tsx @@ -2,7 +2,7 @@ import { postData } from '@/components/lib/api' import FileUploadForm from '@/components/ui/fileUploadForm' import FormSection from '@/components/ui/formSection' -import { filePicked, DatePicker } from '@common/business_components/ui' +import { DatePicker, filePicked } from '@common/business_components/ui' import { Button, Input } from '@common/components/ui' import { zodResolver } from '@hookform/resolvers/zod' import { useState } from 'react' @@ -29,11 +29,13 @@ interface UploadedFileInfo { // 필요하다면 파일 이름 등 추가 정보 저장 가능 // fileName: string; } - // 초기 파일 정보 상태 const initialFileInfo: UploadedFileInfo = { file: null, atflCd: null, atflId: null }; -const region: { text: string, val: number }[] = [{ text: '양평', val: 1 }, { text: '강남', val: 2 }, { text: 'test', val: 3 }]; +interface ApiSelectOption { + text: string; + val: string; +} const IrSetting = () => { const form = useForm>({ @@ -49,9 +51,6 @@ const IrSetting = () => { } }) - // FileUploadForm에서 전달받은 파일 정보를 저장할 상태 배열 - // 각 인덱스는 FileUploadForm의 fileIdx와 매칭됨 - // 예: 0: 오수관망도, 1: 수용가-맨홀, 2: 오수량 시간패턴, 3: 유량자료 const [uploadedFilesData, setUploadedFilesData] = useState([ initialFileInfo, // 0: 오수관망도 initialFileInfo, // 1: 수용가-맨홀 매칭결과 @@ -62,11 +61,11 @@ const IrSetting = () => { const [selectedDate, setSelectedDate] = useState(new Date()); const [input1, setInput1] = useState(''); const [input2, setInput2] = useState(''); - const [select1, setSelect1] = useState(0); - const [select2, setSelect2] = useState(0); - const [select3, setSelect3] = useState(0); - const [select4, setSelect4] = useState(0); - const [select5, setSelect5] = useState(0); + const [select1, setSelect1] = useState('0'); // 환경청코드 + const [select2, setSelect2] = useState('0'); // 시도코드 + const [select3, setSelect3] = useState({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경 + const [select4, setSelect4] = useState('0'); // 하수처리장코드 + const [select5, setSelect5] = useState('0'); // 하수처리구역코드 const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태 // 폼 초기화 함수 @@ -89,9 +88,9 @@ const IrSetting = () => { setSelectedDate(new Date()); // 날짜 선택 초기화 setInput1(''); // 입력 필드 초기화 setInput2(''); // 입력 필드 초기화 - setSelect1(0); // 선택 필드 초기화 - setSelect2(0); - setSelect3(0); + setSelect1('0'); // 선택 필드 초기화 + setSelect2('0'); + setSelect3({ val: '0', text: '전체' }); setSelect4(0); setSelect5(0); setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경 @@ -191,11 +190,11 @@ const IrSetting = () => { // 새로운 JSON 데이터 생성 const combinedAnalysisData = { - enoCd: "select1", // 환경청코드 - ctpvCd: "select2", // 시도코드 - sggCd: "select3", // 시군구코드 - swageTmplntCd: "select4", // 하수처리장코드 - swtrmtZoneCd: "select5", // 하수처리구역코드 + enoCd: select1.toString(), // 환경청코드 + ctpvCd: select2.toString(), // 시도코드 + sggCd: select3.val.toString(), // 시군구코드 + swageTmplntCd: select4.toString(), // 하수처리장코드 + swtrmtZoneCd: select5.toString(), // 하수처리구역코드 aplcnYm: new Date(selectedDate).toISOString().substring(0, 7).replace("-", ""), // 적용연월 dtrwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환) anlsInfo: input2, // 분석정보 @@ -230,10 +229,16 @@ const IrSetting = () => { onSubmitEvent={() => { }} graphBtn={false} searchBox={{ title: '분석 대상지역 선택' }} - sido={select2} - setSido={setSelect2} + enoCd={select1} + setEnoCd={setSelect1} + ctpvCd={select2} + setCtpvCd={setSelect2} sigungu={select3} - setSigungu={setSelect3}> + setSigungu={setSelect3} + selectedSwageTreatmentPlant={select4} + onSwageTreatmentPlantChange={setSelect4} + selectedSwageProcessingZone={select5} + onSwageProcessingZoneChange={setSelect5}>
{ name='inputdate' form={form} titleNm='▷ 상수사용량 적용기간'> - {/* { if (date) setSelectedDate(date) }} - mode={"single"} /> */} { if (date) { setSelectedDate(date); - // react-hook-form의 inputdate 필드도 업데이트 (선택 사항) form.setValue('inputdate', date, { shouldValidate: true }); } } }} - // className을 직접 사용하거나, DatePicker 내부에서 selectClass를 처리하도록 수정 필요 - // 여기서는 DatePicker의 selectClass prop을 사용한다고 가정 selectClass="ml-5 date-picker-trigger" // date-picker.css의 스타일 적용 /> diff --git a/apps/sn/app/ii/rdii/page.tsx b/apps/sn/app/ii/rdii/page.tsx index 88eda7e91..5f87c52b0 100644 --- a/apps/sn/app/ii/rdii/page.tsx +++ b/apps/sn/app/ii/rdii/page.tsx @@ -12,7 +12,7 @@ import { useState } from "react" import { useFieldArray, useForm } from "react-hook-form" import { z } from "zod" import { excelToMatchingResultData, excelToRainData } from '../../components/lib/utils/excelToJson' -import { EditableFormTable, type EditableTableHeadItem } from '../../components/ui/EditableFormTable' +import { EditableFormTable, type EditableTableHeadItem } from '../../components/ui/editableFormTable' // zod 스키마 정의 @@ -85,6 +85,11 @@ interface UploadedFileInfo { // 초기 파일 정보 상태 const initialFileInfo: UploadedFileInfo = { file: null, atflCd: null, atflId: null }; +interface ApiSelectOption { + text: string; + val: string; +} + export default function RTKForm() { const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태 @@ -96,6 +101,12 @@ export default function RTKForm() { initialFileInfo, // 2: 강우자료 ]); + const [select1, setSelect1] = useState('0'); // 환경청코드 (enoCd) + const [select2, setSelect2] = useState('0'); // 시도코드 (ctpvCd) + const [select3, setSelect3] = useState({ val: '0', text: '전체' }); // 시군구 (sggCd) + const [select4, setSelect4] = useState('0'); // 하수처리장 (swageTmplntCd) + const [select5, setSelect5] = useState('0'); // 하수처리구역 (swtrmtZoneCd) + // form 인스턴스 생성 const form = useForm({ resolver: zodResolver(formSchema), @@ -111,6 +122,11 @@ export default function RTKForm() { initialFileInfo, ]); setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경 + setSelect1('0'); + setSelect2('0'); + setSelect3({ val: '0', text: '전체' }); + setSelect4('0'); + setSelect5('0'); }; // FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수 @@ -202,12 +218,12 @@ export default function RTKForm() { // 새로운 JSON 데이터 생성 const combinedAnalysisData = { - enoCd: "환경청코드", // 환경청코드 + enoCd: select1.toString(), // 환경청코드 aplcnYm: new Date(formData.date).toISOString().substring(0, 7).replace("-", ""), // 적용연월 (YYYYMM 형식) - ctpvCd: "시도코드", // 시도코드 - sggCd: "시군구코드", // 시군구코드 - swageTmplntCd: "하수처리장코드", // 하수처리장코드 - swtrmtZoneCd: "하수처리구역코드", // 하수처리구역코드 + ctpvCd: select2.toString(), // 시도코드 + sggCd: select3.val.toString(), // 시군구코드 + swageTmplntCd: select4.toString(), // 하수처리장코드 + swtrmtZoneCd: select5.toString(), // 하수처리구역코드 atflCd: file1?.atflCd, // 첨부파일CD (오수관망도) atflId: file1?.atflId, // 첨부파일ID (오수관망도) atflInpNm: file1?.file?.name || "", // 오수관망도 파일명 @@ -295,7 +311,25 @@ export default function RTKForm() { } return ( - { }} graphBtn={false} > + // { }} graphBtn={false} > + { }} // BaseLayout의 form 태그에 연결될 onSubmit + graphBtn={false} + // select 상태 및 핸들러 전달 + enoCd={select1} + setEnoCd={setSelect1} + ctpvCd={select2} + setCtpvCd={setSelect2} + sigungu={select3} + setSigungu={setSelect3} + selectedSwageTreatmentPlant={select4} + onSwageTreatmentPlantChange={setSelect4} + selectedSwageProcessingZone={select5} + onSwageProcessingZoneChange={setSelect5} + > {[
diff --git a/apps/sn/app/map/SearchModal.tsx b/apps/sn/app/map/SearchModal.tsx index 35e645ed9..e1c2bd0de 100644 --- a/apps/sn/app/map/SearchModal.tsx +++ b/apps/sn/app/map/SearchModal.tsx @@ -1121,8 +1121,6 @@ const SearchModal: React.FC = ({ z-[101] `} body={renderBody()} - // headerTitle="검색" - // 필요시 footerContent 등 추가 /> ); };