feat, remove:

- 관 용도, 관 재질 공통 코드 Store 등록
- I/I 분석 페이지 시도, 시군구 공통 코드 적용
- I/I 분석 페이지 환경청 삭제
- commonStore.ts 삭제
- Store 데이터 호출 로직을 layout.tsx로 이동
This commit is contained in:
swcho
2025-07-31 15:48:38 +09:00
parent 2e7b06d970
commit 723eabea41
16 changed files with 245 additions and 341 deletions
+13
View File
@@ -1,10 +1,12 @@
"use client";
import React, { useEffect } from 'react';
import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { BaseLayout } from "@common/components";
import type { MenuItem } from "@/entities/menu";
import { useMenuStore } from "@/shared/lib";
import useStore from '../store/useStore';
export default function MapLayout({
children,
@@ -17,6 +19,17 @@ export default function MapLayout({
const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
const { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore();
// 시도, 시군구 호출
useEffect(() => {
fetchCtpvData();
fetchSggData();
fetchBIZ_SE_CD();
fetchBIZ_TY_CD();
}, []);
const handleMenuClick = (item: MenuItem) => {
setCurrentMenuInfo(item);
router.push(item.urlPath);
@@ -1,25 +0,0 @@
import { create } from 'zustand';
export type SelectDataType = {
text: string;
val: string;
sido?: string; // 시도 코드 (선택 사항)
}
interface CityCdStore {
cityCd: string;
setCityCd: (cityCd: string) => void;
sidos: SelectDataType[];
setSidos: (sidos: SelectDataType[]) => void;
siguns : SelectDataType[];
setSiguns: (siguns: SelectDataType[]) => void;
}
export const useCityCdStore = create<CityCdStore>((set) => ({
cityCd: '',
setCityCd: (cityCd: string) => set({ cityCd }),
sidos: [],
setSidos: (sidos: SelectDataType[]) => set({ sidos }),
siguns: [],
setSiguns: (siguns: SelectDataType[]) => set({ siguns }),
}));
@@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react'
import InputSection from '../InputSection';
import { handleInputChange } from '../common';
import { getData } from "@/components/lib/api";
// import { useBizByFcltStore } from "../../../../store/StoreProvider";
import useStore from '../../../../store/useStore';
// 하수관로
const inputColumn = [
@@ -30,21 +30,7 @@ const MsSwagePpl = ({ featureData, onSaveClick }: { featureData: any, onSaveClic
inspection: { data: inspectionData, setData: setInspectionData },
};
// const BIZ_SE_CD = useBizByFcltStore(state => state.BIZ_SE_CD);
const BIZ_SE_CD = [
{ lwpoCd: "100", lwpoCdNm: "관로기술진단" },
{ lwpoCd: "200", lwpoCdNm: "개대체" },
{ lwpoCd: "201", lwpoCdNm: "유지보수" },
{ lwpoCd: "203", lwpoCdNm: "청소, 준설" },
{ lwpoCd: "204", lwpoCdNm: "단순보수" },
];
// const BIZ_TY_CD = useBizByFcltStore(state => state.BIZ_TY_CD);
const BIZ_TY_CD = [
{ lwpoCd: "SV", lwpoCdNm: "용역" },
{ lwpoCd: "SF", lwpoCdNm: "자체" },
{ lwpoCd: "SC", lwpoCdNm: "공사" },
];
const { BIZ_SE_CD, BIZ_TY_CD } = useStore();
// 마운트 및 업데이트 시 API 호출
+3 -9
View File
@@ -6,7 +6,6 @@ import { useCallback, useEffect, useRef, useState } from "react";
import GisApp from "@common/business_components/ui/map/lib/gis.core";
import SearchModal from "./searchModal/searchModal";
import FeatureClickEventPopup from "./components/popupComponent/featureClickEventPopup";
import useStore from "../../store/useStore";
// 타입 정의 추가
export type LyrStngProps = {
@@ -39,13 +38,11 @@ const Map = () => {
const [isOpen, setIsOpen] = useState(false); // 팝업 열기/닫기 상태
const [isSearchModalOpen, setIsSearchModalOpen] = useState(false); // SearchModal 열림 상태
const [featureData, setFeatureData] = useState(null); // 선택된 피쳐 데이터
const { fetchCtpvData, fetchSggData } = useStore();
// 시도, 시군구 호출
useEffect(() => {
fetchCtpvData();
fetchSggData();
}, [fetchCtpvData, fetchSggData]);
fetchData();
}, []);
const fetchData = async () => {
try {
@@ -56,9 +53,6 @@ const Map = () => {
}
};
useEffect(() => {
fetchData();
}, []);
// SearchModal, GisContent 등에서 호출
const onFeatureSelect = (data: any) => {
let map = gisMaps.current;
@@ -1,40 +1,15 @@
import React from 'react';
import { YearRangeSelectBox } from '@/components/ui/YearRangeSelectBox';
import { SelectBox } from '@common/business_components/ui';
import { Button, Input, Label } from '@common/components';
import React from 'react';
//import { useDcTree, flattenFolders, useSggCd, useCityCd } from './components/searchModalReportApi';
// import { useBizByFcltStore } from "../../../store/StoreProvider";
import useStore from '../../../store/useStore';
// 운영정보 '구분' 선택 목록
// const WORK_TYPES = [
// { val: "maintenance", text: "보수 및 갱신" },
// { val: "cleaning", text: "청소 및 준설" },
// { val: "inspection", text: "점검 및 진단" },
// ];
export const WORK_TYPES = [
{ val: "['200', '201', '204']", text: "보수 및 갱신" },
{ val: "['100']", text: "점검 및 진단" },
{ val: "['203']", text: "청소 및 준설" },
];
// const BIZ_SE_CD = useBizByFcltStore(state => state.BIZ_SE_CD);
export const BIZ_SE_CD = [
{ lwpoCd: "100", lwpoCdNm: "관로기술진단" },
{ lwpoCd: "200", lwpoCdNm: "개대체" },
{ lwpoCd: "201", lwpoCdNm: "유지보수" },
{ lwpoCd: "203", lwpoCdNm: "청소, 준설" },
{ lwpoCd: "204", lwpoCdNm: "단순보수" },
];
// const BIZ_TY_CD = useBizByFcltStore(state => state.BIZ_TY_CD);
export const BIZ_TY_CD = [
{ lwpoCd: "0", lwpoCdNm: "전체" },
{ lwpoCd: "SV", lwpoCdNm: "용역" },
{ lwpoCd: "SF", lwpoCdNm: "자체" },
{ lwpoCd: "SC", lwpoCdNm: "공사" },
];
export const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({
val: item.lwpoCd,
text: item.lwpoCdNm,
}));
// 운영정보 - 사업 현황
export const operationTableHeadlist = [
{ id: 'rowNumber', title: '일련번호' },
@@ -110,6 +85,14 @@ const OperationTab: React.FC<OperationTabProps> = ({
setVar2,
setYearFilter,
}) => {
const { BIZ_TY_CD } = useStore();
const BIZ_TY_CD_SELECT_ARRAY = BIZ_TY_CD.map(item => ({
val: item.lwpoCd,
text: item.lwpoCdNm,
}));
return (
<div className="relative flex flex-col border rounded-lg p-5 w-full h-[10vh]">
<div className="flex items-center mb-3">
@@ -3,24 +3,22 @@ import { UiModal, UITab, UiTable } from '@common/business_components/ui';
import axios from 'axios';
import React, { useEffect, useMemo, useState } from 'react';
import { ExclnSewagePumpCol2, MsSwageMhlCol2, MsSwagePplCol2, stofcbrCol2, SwtrmtFcltCol2, SwtrmtPrcsZoneCol2 } from '../components/common/table';
import { type SelectDataType } from '../commonStore';
import { MS_SWAGE_PPL_CODES, MS_SWAGE_PPL_USE_CODES, MS_SWAGE_MHL_CODES } from '../components/common/table';
import { getData } from "@/components/lib/api";
import BasicTab, { BASIC_TYPES } from './basicTab';
import OperationTab, { BIZ_TY_CD, operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab';
import ReportTab, { reportTableHeadlist } from './reportTab';
import OperationTab, { operationCleaningTableHeadlist, operationInspectionTableHeadlist, operationMaintenanceTableHeadlist, operationTableHeadlist, WORK_TYPES } from './operationTab';
import ReportTab from './reportTab';
import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
import useStore from '../../../store/useStore';
const API_URL = `${process.env.NEXT_PUBLIC_SEWERAGE_API_URL}`;
// 탭
const TABS = [
{ val: "basic", text: "기본정보" },
{ val: "operation", text: "운영정보" },
{ val: "report", text: "하수도정비기본계획보고서" },
];
// 날짜 조합 함수
const getDateString = (
year: string,
@@ -67,10 +65,6 @@ const SearchModal: React.FC<SearchModalProps> = ({
const [ctpvName, setCtpvName] = useState<string>('');
const [sgg, setSgg] = useState<number>(0);
const [sggName, setSggName] = useState<string>('');
const [ctpvCd, setCtpvCd] = useState('0'); // 하수정비기본계획 report
const [sigungu, setSigungu] = useState('0'); // 하수정비기본계획 report
const [gubun, setGubun] = useState(""); // 하수정비기본계획 report
const [reportGubun, setReportGubun] = useState("0"); // 하수정비기본계획 report
const [start1, setStart1] = useState("");
const [start2, setStart2] = useState("");
const [end1, setEnd1] = useState("");
@@ -81,7 +75,8 @@ const SearchModal: React.FC<SearchModalProps> = ({
const [var4, setVar4] = useState("");
// 시작, 종료
const [yearFilter, setYearFilter] = useState({ start: "", end: "" });
const [filteredSiguns, setFilteredSiguns] = useState<SelectDataType[]>([{ val: '0', text: '전체', sido: '0' }]);
const { BIZ_TY_CD } = useStore();
// 오늘 날짜 구하기
@@ -1,12 +1,12 @@
'use client'
import { getData } from '@/components/lib/api'
import ContentLayout from '@/components/ui/contentLayout'
import { GovSidoSigunguSearchForm } from '@/components/ui/govSidoSigunguSearchForm'
import { DatePicker, DateMonthPicker, Form, SearchFormRight, SelectBox } from '@common/business_components/ui'
import { DateMonthPicker, Form, SelectBox } from '@common/business_components/ui'
import ListDiv from '@common/business_components/ui/list_div'
import { Button, Label } from '@common/components/ui'
import { useEffect, useState, type ReactNode } from 'react'
import LoadingOverlay from '@/components/ui/LoadingBar'
import { SidoSigunguSelectBox } from '@/components/ui/SidoSigunguSelectBox'
type TitleName = {
mainTitle: string
@@ -40,12 +40,10 @@ type BaseLayout = {
firstDivBtn?: boolean
selectBox?: SelectBox
showDatePicker?: boolean;
enoCd?: string | number
setEnoCd?: (enoCd: string) => void
ctpvCd?: string | number
setCtpvCd?: (ctpvCd: string) => void
ctpvCd: number;
sggCd: number;
sggName?: string;
sigungu?: ApiSelectOption
setSigungu?: (sigungu: ApiSelectOption) => void
selectedSwageTreatmentPlant?: string | number;
onSwageTreatmentPlantChange?: (value: string | number) => void;
selectedSwageProcessingZone?: string | number;
@@ -54,23 +52,16 @@ type BaseLayout = {
setAplcnYmFrom?: (aplcnYmFrom: string) => void;
aplcnYmTo?: string
setAplcnYmTo?: (aplcnYmTo: string) => void;
setCtpvCd: (ctpv: number) => void;
setSggCd: (sgg: number) => void;
setSggName?: (sggName: string) => void;
isLoading?: boolean;
// Excel 다운로드 관련 props 추가
onExcelDownload?: () => void;
onDeleteFile?: () => void;
onAnalysis?: () => void;
selectedRowData?: any;
}
// TODO: 나중에 이벤트 추가되면 파라미터 추가 필요 ?
/**
*
* @param children
* @param titleName : 메인 타이틀, 첫번째 요소 타이틀, 두번째 요소 타이틀
* @param form : zod 스키마
* @param onSubmitEvent : 폼 이벤트 처리
* @param graphBtn : 하단에 그래프 버튼 유무 처리
* @returns
*/
const BaseLayout = ({
children,
titleName,
@@ -80,42 +71,36 @@ const BaseLayout = ({
analysisBtn,
selectBox,
showDatePicker = false,
enoCd,
setEnoCd,
ctpvCd,
setCtpvCd,
sggCd,
sggName,
sigungu,
setSigungu,
selectedSwageTreatmentPlant,
onSwageTreatmentPlantChange,
selectedSwageProcessingZone,
onSwageProcessingZoneChange,
aplcnYmFrom,
setAplcnYmFrom,
aplcnYmTo,
setAplcnYmTo,
setCtpvCd,
setSggCd,
setSggName,
isLoading,
// Excel 다운로드 관련 props 추가
onExcelDownload,
onDeleteFile,
onAnalysis,
selectedRowData
}: BaseLayout) => {
const defaultOption: ApiSelectOption = { val: '0', text: '전체' };
const [processingZoneOptions, setProcessingZoneOptions] = useState<ApiSelectOption[]>([]);
const [treatmentFacilityOptions, setTreatmentFacilityOptions] = useState<ApiSelectOption[]>([]);
const [isLoadingAreaData, setIsLoadingAreaData] = useState<boolean>(isLoading || false);
const [atflId, setAtflId] = useState('')
const [fileNm, setFileNm] = useState('')
const [dcrpPplMtncBizSn, setDcrpPplMtncBizSn] = useState('')
useEffect(() => {
onSwageTreatmentPlantChange?.(0);
const fetchData = async () => {
if (!sigungu || sigungu.val === '0' || !sigungu.val) {
if (!sggName) {
console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sigungu?.val);
setProcessingZoneOptions([defaultOption]);
setTreatmentFacilityOptions([defaultOption]);
setIsLoadingAreaData(false);
return;
@@ -123,20 +108,6 @@ const BaseLayout = ({
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') {
// // console.log("시군구 코드", sigungu.val);
// 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',
@@ -145,39 +116,26 @@ const BaseLayout = ({
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
}
if (sggName) cqlFiltersFacility.push(`sgg_nm ILIKE '%${sggName}%'`);
if (cqlFiltersFacility.length > 0) queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and '));
// 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 facilityRes = await 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]);
console.log('facilityRes.data', facilityRes.data);
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
}));
const facilities = (facilityRes.data || [])
.map((item: any) => ({
text: item.swtrmtFcltNm,
val: item.fid
}))
.sort((a: ApiSelectOption, b: ApiSelectOption) => a.text.localeCompare(b.text, 'ko'));
setTreatmentFacilityOptions([defaultOption, ...facilities]);
console.log("BaseLayout Fetched Treatment Facilities:", [defaultOption, ...facilities]);
} catch (error) {
console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error);
setProcessingZoneOptions([defaultOption]);
setTreatmentFacilityOptions([defaultOption]);
} finally {
setIsLoadingAreaData(false);
@@ -185,7 +143,7 @@ const BaseLayout = ({
};
fetchData();
}, [sigungu]); // sigungu 상태가 변경될 때마다 실행
}, [sggName]); // sgg 상태가 변경될 때마다 실행
// 파일 다운로드
const isChckFile = () => {
@@ -215,10 +173,10 @@ const BaseLayout = ({
<h3 className="text-2xl font-medium">{selectBox.title}</h3>
</div>
)}
<div className="flex items-center w-full">
<div className="ml-2 mt-2 flex items-center w-full">
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링
<>
<Label className="ml-2 mr-4 w-20 text-xl font-medium text-gray-600 whitespace-nowrap">상수사용기간</Label>
<Label className="mr-4 w-20 text-xl font-medium text-gray-600 whitespace-nowrap">상수사용기간</Label>
<DateMonthPicker
field={{
value: aplcnYmFrom || (() => { // aplcnYm이 없으면 현재 연월을 기본값으로 설정
@@ -253,24 +211,26 @@ const BaseLayout = ({
/>
</>
)}
<GovSidoSigunguSearchForm
isShowPart={true}
setRtnEnvPartValue={(val) => setEnoCd?.(val)}
setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)}
setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달
>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-4">하수처리장</Label>
<SelectBox
label={''}
value={selectedSwageTreatmentPlant}
selectArray={treatmentFacilityOptions}
className="w-[160px]"
selectClass="rounded-lg bg-white truncate"
onSelectValue={(val) => onSwageTreatmentPlantChange?.(val)}
selectContentClass="z-[101]"
/>
{/* <SelectBox label={"하수처리구역"} value={selectedSwageProcessingZone} selectArray={processingZoneOptions} className="col-span-3" selectClass="rounded-lg bg-white truncate" onSelectValue={(val) => onSwageProcessingZoneChange?.(val)} selectContentClass="z-[101]" /> */}
</GovSidoSigunguSearchForm>
<SidoSigunguSelectBox
sido={ctpvCd}
sigungu={sggCd}
onSidoChange={(sidoId) => {
setCtpvCd(sidoId);
setSggCd(0);
}}
onSigunguChange={setSggCd}
setSggName={setSggName}
/>
<Label className="text-xl text-gray-600 font-medium whitespace-nowrap mr-4">하수처리장</Label>
<SelectBox
label={''}
value={selectedSwageTreatmentPlant}
selectArray={treatmentFacilityOptions}
className="w-[160px]"
selectClass="rounded-lg bg-white truncate"
onSelectValue={(val) => onSwageTreatmentPlantChange?.(val)}
selectContentClass="z-[101]"
/>
{selectBox?.searchChk && (
<div className='flex flex-1 justify-end mr-6'>
<Button size="sm" onClick={() => selectBox.onSearchClick?.()}>조회</Button>
@@ -49,13 +49,12 @@ interface ApiSelectOption {
const IrSetting = () => {
const [input1, setInput1] = useState('0.8'); // 오수전환율 초기값
const [input2, setInput2] = useState('');
const [select1, setSelect1] = useState('0'); // 환경청코드
const [select2, setSelect2] = useState('0'); // 시도코드
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 상태를 객체로 변경
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장코드
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역코드
const [select1, setSelect1] = useState<string | number>('0'); // 하수처리장코드
const [formResetKey, setFormResetKey] = useState(0); // FileUploadForm 리셋을 위한 key 상태
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
const { data: userInfo } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
@@ -115,11 +114,7 @@ const IrSetting = () => {
setSelectedDate(`${year}${month}`);
setInput1(''); // 입력 필드 초기화
setInput2(''); // 입력 필드 초기화
setSelect1('0'); // 선택 필드 초기화
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4(0);
setSelect5(0);
setSelect1(0);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
};
@@ -164,7 +159,7 @@ const IrSetting = () => {
setIsLoadingResult(true); // 로딩 상태 시작
// 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장)
if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, {
if (!validateBasicSelectionsWithAlert(select1, {
requireEnv: true,
requireSido: true,
requireSigungu: true,
@@ -299,15 +294,11 @@ const IrSetting = () => {
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값)
ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값)
sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값)
swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0)
? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
ctpvCd: (ctpvCd ? String(ctpvCd) : null),
sggCd: (sggCd ? String(sggCd) : null), // 시군구코드 (선택값)
swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0)
? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리장코드 (선택값)
swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0)
? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리구역코드 (선택값)
aplcnYm: selectedDate, // 적용연월
drtwSwtcRt: parseFloat(input1) || null, // 오수전환율 (숫자로 변환)
anlsInfo: input2, // 분석정보
@@ -346,16 +337,14 @@ const IrSetting = () => {
graphBtn={false}
analysisBtn={'침입수분석 실행'}
selectBox={{ title: '분석 대상지역 선택' }}
enoCd={select1}
setEnoCd={setSelect1}
ctpvCd={select2}
setCtpvCd={setSelect2}
sigungu={select3}
setSigungu={setSelect3}
selectedSwageTreatmentPlant={select4}
onSwageTreatmentPlantChange={setSelect4}
selectedSwageProcessingZone={select5}
onSwageProcessingZoneChange={setSelect5}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwageTreatmentPlant={select1}
onSwageTreatmentPlantChange={setSelect1}
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
isLoading={isLoadingResult}
onAnalysis={handleAnalysisClick}
>
+17 -29
View File
@@ -114,11 +114,10 @@ export default function RTKForm() {
initialFileInfo, // 2: 강우자료
]);
const [select1, setSelect1] = useState('0'); // 환경청코드 (enoCd)
const [select2, setSelect2] = useState('0'); // 시도코드 (ctpvCd)
const [select3, setSelect3] = useState<ApiSelectOption>({ val: '0', text: '전체' }); // 시군구 (sggCd)
const [select4, setSelect4] = useState<string | number>('0'); // 하수처리장 (swageTmplntCd)
const [select5, setSelect5] = useState<string | number>('0'); // 하수처리구역 (swtrmtZoneCd)
const [select1, setSelect1] = useState<string | number>('0'); // 하수처리장 (swageTmplntCd)
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
const { data: userInfo } = useQuery({
queryKey: [ENDPOINT.USER_SERVICE.USERS],
@@ -142,10 +141,6 @@ export default function RTKForm() {
]);
setFormResetKey(prevKey => prevKey + 1); // FileUploadForm 재마운트를 위해 key 변경
setSelect1('0');
setSelect2('0');
setSelect3({ val: '0', text: '전체' });
setSelect4('0');
setSelect5('0');
};
// FileUploadForm의 onFileSelect prop으로 전달될 콜백 함수
@@ -182,7 +177,7 @@ export default function RTKForm() {
setIsLoadingResult(true); // 로딩 상태 시작
// 기본 선택 항목들 검증 (환경청, 시도, 시군구, 하수처리장)
if (!validateBasicSelectionsWithAlert(select1, select2, select3, select4, {
if (!validateBasicSelectionsWithAlert(select1, {
requireEnv: true,
requireSido: true,
requireSigungu: true,
@@ -295,16 +290,12 @@ export default function RTKForm() {
// 새로운 JSON 데이터 생성
const combinedAnalysisData = {
enoCd: (select1 && select1 !== '0') ? select1.toString() : null, // 환경청코드 (선택값)
aplcnYm: formData.date, // 적용연월 (YYYYMM 형식)
ctpvCd: (select2 && select2 !== '0') ? select2.toString() : null, // 시도코드 (선택값)
sggCd: (select3.val && select3.val !== '0') ? select3.val.toString() : null, // 시군구코드 (선택값)
swageTmplntCd: (select4 && select4 !== '0' && select4 !== 0)
? select4.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
ctpvCd: ctpvCd ? String(ctpvCd) : null, // 시도코드 (선택값)
sggCd: sggCd ? String(sggCd) : null, // 시군구코드 (선택값)
swageTmplntCd: (select1 && select1 !== '0' && select1 !== 0)
? select1.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리장코드 (선택값)
swtrmtZoneCd: (select5 && select5 !== '0' && select5 !== 0)
? select5.toString().split('.').pop() || null // '.'으로 split해서 마지막 부분만 사용
: null, // 하수처리구역코드 (선택값)
atflCd: file1?.atflCd, // 첨부파일CD (오수관망도)
atflId: file1?.atflId, // 첨부파일ID (오수관망도)
atflInpNm: file1?.file?.name || "", // 오수관망도 파일명
@@ -395,7 +386,6 @@ export default function RTKForm() {
}
return (
// <BaseLayout searchBox={{ title: '분석 대상지역 선택' }} titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }} form={form} onSubmitEvent={() => { }} graphBtn={false} >
<BaseLayout
selectBox={{ title: '분석 대상지역 선택' }}
titleName={{ mainTitle: 'RDII 분석', firstCardTitle: 'RDII 분석자료 입력', secondCardTitle: '실측자료 등록' }}
@@ -403,16 +393,14 @@ export default function RTKForm() {
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={false}
analysisBtn={'RDII분석 실행'}
enoCd={select1}
setEnoCd={setSelect1}
ctpvCd={select2}
setCtpvCd={setSelect2}
sigungu={select3}
setSigungu={setSelect3}
selectedSwageTreatmentPlant={select4}
onSwageTreatmentPlantChange={setSelect4}
selectedSwageProcessingZone={select5}
onSwageProcessingZoneChange={setSelect5}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwageTreatmentPlant={select1}
onSwageTreatmentPlantChange={setSelect1}
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
isLoading={isLoadingResult}
onAnalysis={handleAnalysisClick}
>
@@ -1,5 +1,5 @@
'use client'
import { deleteData, getData, useCookieAuth } from '@/components/lib/api'
import { deleteData, getData } from '@/components/lib/api'
import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { useState, useEffect } from 'react'
@@ -11,12 +11,6 @@ import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
const formSchema = z.object({
enoCd: z.string().default('0'), // 환경청코드
ctpvCd: z.string().default('0'), // 시도코드
sggCd: z.object({ // 시군구
val: z.string().default('0'),
text: z.string().default('전체'),
}).default({ val: '0', text: '전체' }),
swageTmplntCd: z.string().default('0'), // 하수처리장
swtrmtZoneCd: z.string().default('0'), // 하수처리구역
aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경
@@ -54,14 +48,10 @@ const tableColumn = [
]
const Result = () => {
const AUTH = useCookieAuth();
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
enoCd: '0',
ctpvCd: '0',
sggCd: { val: '0', text: '전체' },
swageTmplntCd: '0',
swtrmtZoneCd: '0',
aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정
@@ -88,6 +78,9 @@ const Result = () => {
const [resetSelection, setResetSelection] = useState<boolean>(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [curPage, setCurPage] = useState(1); // 현재 페이지
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
useEffect(() => {
// setCurPage(1);
@@ -127,11 +120,9 @@ const Result = () => {
let inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
"ctpvCd": ctpvCd === 0 ? null : ctpvCd,
"sggCd": sggCd === 0 ? null : sggCd,
"swageTmplntCd": processedSwageTmplntCd,
"auth": AUTH,
};
console.log("Search DTO:", inftwtAnlsSearchDto);
@@ -163,7 +154,8 @@ const Result = () => {
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
};
});
})
.filter((item: any) => item.scsYn === 'Y');
// 선택된 행 초기화
setSelectedRowData(null);
@@ -289,20 +281,18 @@ const Result = () => {
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={true}
showDatePicker={true}
enoCd={form.watch("enoCd")}
setEnoCd={(val) => form.setValue("enoCd", val)}
ctpvCd={form.watch("ctpvCd")}
setCtpvCd={(val) => form.setValue("ctpvCd", val)}
sigungu={form.watch("sggCd")}
setSigungu={(val) => form.setValue("sggCd", val)}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwageTreatmentPlant={form.watch("swageTmplntCd")}
onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))}
selectedSwageProcessingZone={form.watch("swtrmtZoneCd")}
onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))}
aplcnYmFrom={form.watch("aplcnYmFrom")}
setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }}
aplcnYmTo={form.watch("aplcnYmTo")}
setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }}
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
isLoading={isLoadingResult}
// Excel 다운로드 핸들러 추가
onExcelDownload={handleExcelDownload}
@@ -1,6 +1,6 @@
'use client'
import { deleteData, getData, useCookieAuth } from '@/components/lib/api'
import { UiTable, downloadExcel, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
import { deleteData, getData } from '@/components/lib/api'
import { UiTable, downloadExcelWithKoreanHeaders } from '@common/business_components/ui'
import { zodResolver } from '@hookform/resolvers/zod'
import { useState, useEffect } from 'react'
import { useForm } from 'react-hook-form'
@@ -11,12 +11,6 @@ import { formatDisplayData } from '@/components/lib/utils/formatDisplayValue';
const formSchema = z.object({
enoCd: z.string().default('0'), // 환경청코드
ctpvCd: z.string().default('0'), // 시도코드
sggCd: z.object({ // 시군구
val: z.string().default('0'),
text: z.string().default('전체'),
}).default({ val: '0', text: '전체' }),
swageTmplntCd: z.string().default('0'), // 하수처리장
swtrmtZoneCd: z.string().default('0'), // 하수처리구역
aplcnYmFrom: z.string().default(() => { // Date에서 string(yyyyMM 형식)으로 변경
@@ -55,9 +49,6 @@ const Result = () => {
const form = useForm<ResultFormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
enoCd: '0',
ctpvCd: '0',
sggCd: { val: '0', text: '전체' },
swageTmplntCd: '0',
swtrmtZoneCd: '0',
aplcnYmFrom: (() => { // 현재 연월을 yyyyMM 형식으로 설정
@@ -76,8 +67,6 @@ const Result = () => {
}
})
const AUTH = useCookieAuth();
const [tableData, setTableData] = useState<any[]>([]); // API 조회 결과 저장 상태
const [totalData, setTotalData] = useState(0) //총 데이터 수l
const [isLoadingResult, setIsLoadingResult] = useState<boolean>(false); // 로딩 상태
@@ -85,6 +74,9 @@ const Result = () => {
const [resetSelection, setResetSelection] = useState<boolean>(false);
const [isResetPage, setIsResetPage] = useState<boolean>(false);
const [curPage, setCurPage] = useState(1); // 현재 페이지
const [ctpvCd, setCtpvCd] = useState<number>(0); // 시도 코드 상태
const [sggCd, setSggCd] = useState<number>(0); // 시군구 코드 상태
const [sggName, setSggName] = useState<string>(''); // 시군구 이름 상태
useEffect(() => {
// setCurPage(1);
@@ -124,12 +116,9 @@ const Result = () => {
let inftwtAnlsSearchDto = {
"aplcnYmFrom": formValues.aplcnYmFrom && formValues.aplcnYmFrom !== '' ? formValues.aplcnYmFrom : null,
"aplcnYmTo": formValues.aplcnYmTo && formValues.aplcnYmTo !== '' ? formValues.aplcnYmTo : null,
"enoCd": formValues.enoCd === '0' ? null : formValues.enoCd,
"ctpvCd": formValues.ctpvCd === '0' ? null : formValues.ctpvCd,
"sggCd": formValues.sggCd?.val === '0' ? null : formValues.sggCd?.val,
"ctpvCd": ctpvCd === 0 ? null : ctpvCd,
"sggCd": sggCd === 0 ? null : sggCd,
"swageTmplntCd": processedSwageTmplntCd,
// "swtrmtZoneCd": formValues.swtrmtZoneCd === '0' ? null : formValues.swtrmtZoneCd,
"auth": AUTH,
};
console.log("Search DTO:", inftwtAnlsSearchDto);
@@ -162,7 +151,8 @@ const Result = () => {
rowNumber: (curPage - 1) * 10 + index + 1, // 1부터 시작하는 일련번호 추가
scsYn: item.scsYn === null || item.scsYn === undefined ? 'N' : item.scsYn // scsYn이 null이면 'N'으로 변경
};
});
})
.filter((item: any) => item.scsYn === 'Y');
// 선택된 행 초기화
setSelectedRowData(null);
@@ -289,20 +279,18 @@ const Result = () => {
onSubmitEvent={() => { }} // BaseLayout의 form 태그에 연결될 onSubmit
graphBtn={true}
showDatePicker={true}
enoCd={form.watch("enoCd")}
setEnoCd={(val) => form.setValue("enoCd", val)}
ctpvCd={form.watch("ctpvCd")}
setCtpvCd={(val) => form.setValue("ctpvCd", val)}
sigungu={form.watch("sggCd")}
setSigungu={(val) => form.setValue("sggCd", val)}
ctpvCd={ctpvCd}
sggCd={sggCd}
sggName={sggName}
selectedSwageTreatmentPlant={form.watch("swageTmplntCd")}
onSwageTreatmentPlantChange={(val) => form.setValue("swageTmplntCd", String(val))}
selectedSwageProcessingZone={form.watch("swtrmtZoneCd")}
onSwageProcessingZoneChange={(val) => form.setValue("swtrmtZoneCd", String(val))}
aplcnYmFrom={form.watch("aplcnYmFrom")}
setAplcnYmFrom={(val: string) => { form.setValue("aplcnYmFrom", val) }}
aplcnYmTo={form.watch("aplcnYmTo")}
setAplcnYmTo={(val: string) => { form.setValue("aplcnYmTo", val) }}
setCtpvCd={setCtpvCd}
setSggCd={setSggCd}
setSggName={setSggName}
isLoading={isLoadingResult} // 로딩 상태 전달
// Excel 다운로드 핸들러 추가
onExcelDownload={handleExcelDownload}
+11
View File
@@ -1,11 +1,13 @@
"use client";
import React, { useEffect} from 'react';
import { usePathname, useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { BaseLayout } from "@common/components";
import type { MenuItem } from "@/entities/menu";
import { useMenuStore } from "@/shared/lib";
import { PageLayout } from "@/shared/ui/page-layout";
import useStore from '../store/useStore';
export default function WithBaseLayout({
children,
@@ -18,6 +20,15 @@ export default function WithBaseLayout({
const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
const { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } = useStore();
useEffect(() => {
fetchCtpvData();
fetchSggData();
fetchBIZ_SE_CD();
fetchBIZ_TY_CD();
}, []);
const handleMenuClick = (item: MenuItem) => {
setCurrentMenuInfo(item);
router.push(item.urlPath);
@@ -87,10 +87,7 @@ export const validateMultipleSelectionsWithAlert = (
* @returns 검증 결과
*/
export const validateBasicSelections = (
select1: string,
select2: string,
select3: ApiSelectOption,
select4: string | number,
select1: string | number,
options: {
requireEnv?: boolean;
requireSido?: boolean;
@@ -108,21 +105,6 @@ export const validateBasicSelections = (
const validationConfigs: SelectionValidationConfig[] = [
{
value: select1,
description: '환경청',
required: requireEnv
},
{
value: select2,
description: '시도',
required: requireSido
},
{
value: select3,
description: '시군구',
required: requireSigungu
},
{
value: select4,
description: '하수처리장',
required: requireTreatmentPlant
}
@@ -133,18 +115,12 @@ export const validateBasicSelections = (
/**
* 기본 선택 항목들 검증 및 alert 표시
* @param select1 환경청코드
* @param select2 시도코드
* @param select3 시군구 객체
* @param select4 하수처리장코드
* @param select1 하수처리장코드
* @param options 각 항목의 필수 여부 설정
* @returns 검증 결과
*/
export const validateBasicSelectionsWithAlert = (
select1: string,
select2: string,
select3: ApiSelectOption,
select4: string | number,
select1: string | number,
options: {
requireEnv?: boolean;
requireSido?: boolean;
@@ -152,7 +128,7 @@ export const validateBasicSelectionsWithAlert = (
requireTreatmentPlant?: boolean;
} = {}
): boolean => {
const result = validateBasicSelections(select1, select2, select3, select4, options);
const result = validateBasicSelections(select1, options);
if (!result.isValid && result.errorMessage) {
alert(result.errorMessage);
return false;
@@ -3,10 +3,6 @@ import { SelectBox } from '@common/business_components/ui'
import { Label } from '@common/components';
import useStore from '../../store/useStore';
interface Option {
id: number;
name: string;
}
interface SidoSigunguSelectBoxProps {
sido: number;
+64
View File
@@ -0,0 +1,64 @@
import { getGeoData, getCmsData } from "@/components/lib/api";
export interface Option { id: number, name: string }
export type BIZ_CD_TYPE = {
lwpoCd: string,
upCd: string,
lwpoCdNm: string,
unit: string,
prntLwpoCd: null
};
export const fetchCtpvData = async (set: (state: any) => void) => {
try {
const ctpvData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson');
const ctpvList: Option[] = ctpvData.features
.map((feature: any) => ({
id: feature.properties.mctpv_cd.trim(),
name: feature.properties.mctpv_korn_nm
}))
.sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko'));
set({ ctpv: ctpvList });
} catch (e: any) {
alert("시도 데이터 로드 실패: " + e.message);
console.error("CTPV 데이터 로드 실패:", e);
}
};
export const fetchSggData = async (set: (state: any) => void) => {
try {
const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson');
const sggList: Option[] = sggData.features
.map((feature: any) => ({
id: feature.properties.sgg_cd,
name: feature.properties.sgg_korn_nm
}))
.sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko'));
set({ sgg: sggList });
} catch (e: any) {
alert("시군구 데이터 로드 실패: " + e.message);
console.error("SGG 데이터 로드 실패:", e);
}
};
export const fetchBIZ_SE_CD = async (set: (state: any) => void) => {
try {
const res = await getCmsData(`/api/v1/code/cache/BIZSECD`);
set({ BIZ_SE_CD: res });
} catch (e: any) {
alert("BIZ_SE_CD 데이터 로드 실패: " + e.message);
console.error("BIZ_SE_CD 데이터 로드 실패:", e);
}
};
export const fetchBIZ_TY_CD = async (set: (state: any) => void) => {
try {
const res = await getCmsData(`/api/v1/code/cache/BIZTYCD`);
set({ BIZ_TY_CD: res });
} catch (e: any) {
alert("BIZ_TY_CD 데이터 로드 실패: " + e.message);
console.error("BIZ_TY_CD 데이터 로드 실패:", e);
}
};
+20 -24
View File
@@ -1,41 +1,37 @@
import { useMemo } from "react";
import { create } from "zustand";
import { getGeoData } from "@/components/lib/api";
import { get } from "http";
import { getGeoData, getCmsData } from "@/components/lib/api";
import { fetchCtpvData, fetchSggData, fetchBIZ_SE_CD, fetchBIZ_TY_CD } from "./index";
interface Option { id: number, name: string }
export type BIZ_CD_TYPE = {
lwpoCd: string,
upCd: string,
lwpoCdNm: string,
unit: string,
prntLwpoCd: null
};
interface SnStoreState {
ctpv: Option[];
sgg: Option[];
BIZ_SE_CD: BIZ_CD_TYPE[];
BIZ_TY_CD: BIZ_CD_TYPE[];
fetchCtpvData: () => Promise<void>;
fetchSggData: () => Promise<void>;
fetchBIZ_SE_CD: () => Promise<void>;
fetchBIZ_TY_CD: () => Promise<void>;
}
const useStore = create<SnStoreState>((set) => ({
// Define your store state and actions here
ctpv: [],
sgg: [],
fetchCtpvData: async () => {
const ctpvData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_ctpv&outputFormat=application%2Fjson');
const ctpvList: Option[] = ctpvData.features
.map((feature: any) => ({
id: feature.properties.mctpv_cd.trim(),
name: feature.properties.mctpv_korn_nm
}))
.sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko'));
set({ ctpv: ctpvList });
},
fetchSggData: async () => {
const sggData = await getGeoData('/hasudo/ows?service=WFS&version=1.0.0&request=GetFeature&typeName=hasudo%3Atb_ms_sgg&outputFormat=application%2Fjson');
const sggList: Option[] = sggData.features
.map((feature: any) => ({
id: feature.properties.sgg_cd,
name: feature.properties.sgg_korn_nm
}))
.sort((a: Option, b: Option) => a.name.localeCompare(b.name, 'ko'));
set({ sgg: sggList });
}
BIZ_SE_CD: [],
BIZ_TY_CD: [],
fetchCtpvData: () => fetchCtpvData(set),
fetchSggData: () => fetchSggData(set),
fetchBIZ_SE_CD: () => fetchBIZ_SE_CD(set),
fetchBIZ_TY_CD: () => fetchBIZ_TY_CD(set),
}));
export default useStore;