Merge commit '84147da3c5f1f5505aa389ee31540c2eed9451ee' into iipbs/sn/oi

This commit is contained in:
JIeunhuh
2025-04-22 10:07:36 +09:00
15 changed files with 796 additions and 42 deletions
@@ -42,6 +42,7 @@
"recharts": "^2.15.1",
"sonner": "^2.0.1",
"tailwind-merge": "^3.0.1",
"xlsx": "^0.18.5",
"zod": "^3.24.2"
},
"devDependencies": {
@@ -1,5 +1,4 @@
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTrigger } from "@common/components";
import { useState } from "react";
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader } from "@common/components";
type ModalType = {
bodyClass: string;
@@ -0,0 +1,16 @@
import * as XLSX from 'xlsx';
type ExcelDownloadProps = {
data : JSON | JSON[];
fileName : string;
sheetNameArray : string[];
}
export const downloadExcel = ({ data, fileName = new Date().getTime().toString()+'.xlsx', sheetNameArray = ["Sheet1"] }: ExcelDownloadProps) => {
const worksheet = XLSX.utils.json_to_sheet(Array.isArray(data) ? data : [data]);
const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, sheetNameArray[0]);
//let buffer = XLSX.write(workbook, { bookType: "xlsx", type: "buffer" });
//XLSX.write(workbook, { bookType: "xlsx", type: "binary" });
XLSX.writeFile(workbook, fileName+'.xlsx');
};
@@ -16,4 +16,5 @@ export * from "./search_form_right";
export * from "./table_div";
export * from "./list_div";
export * from "./tab";
export * from "./dialog_ui"
export * from "./dialog_ui";
export * from "./excel_download";
@@ -1,7 +1,7 @@
function SearchDiv ({children}:{ children: React.ReactNode; }) {
return (
<div className="flex card card-border !mt-6">
{children}
{children}
</div>
)
}
@@ -1,10 +1,317 @@
import React from 'react'
import React, { useEffect, useState } from 'react';
import type { SelectDataType } from '../search_input_select';
import { SelectBox } from '../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;
resetTrigger?: boolean;
}
type EnvPartSelectDataType = SelectDataType & {
adstDliStrCd: string,
}
const selectPartData:SelectDataType[] = [
{ text: "행정별", val: '0' },
{ text: "유역별", val: '01' },
{ text: "환경청별", val: '02' },
];
function SearchFormLeft({ isShowPart = false, children, selectSidoData = [], selectSigunData = [], selectRegionData = [], selectUnitWaterShedData = [], selectMiddleRegionData = [], selectEnvPartData = [], setRtnPartValue, setRtnSidoValue, setRtnSigunValue, setRtnRegionValue, setRtnUnitWaterShedValue, setRtnMiddleRegionValue, setRtnEnvPartValue, setRtnEnvPartSidoValue, setRtnEnvPartSigunValue, resetTrigger = false }: SearchFormLeftProps) {
const [partValue, setPartValue] = useState<string>('0');
const [showPart, setShowPart] = useState<string>('');
const [sidoValue, setSidoValue] = useState<string>('0');
const [sigunValue, setSigunValue] = useState<string>('0');
const [regionValue, setRegionValue] = useState<string>('0');
const [unitWaterShedValue, setUnitWaterShedValue] = useState<string>('0');
const [middleRegionValue, setMiddleRegionValue] = useState<string>('0');
const [envPartValue, setEnvPartValue] = useState<string>('0');
const [envPartSidoValue, setEnvPartSidoValue] = useState<string>('0');
const [envPartSigunValue, setEnvPartSigunValue] = useState<string>('0');
const [filteredSigunData, setFilteredSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredUnitWaterShedData, setFilteredUnitWaterShedData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredMiddleRegionData, setFilteredMiddleRegionData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredEnvPartData, setFilteredEnvPartData] = useState<EnvPartSelectDataType[]>([{ text: "전체", val: '0', adstDliStrCd:'' }]);
const [filteredEnvPartSidoData, setFilteredEnvPartSidoData] = useState<EnvPartSelectDataType[]>([{ text: "전체", val: '0', adstDliStrCd:'' }]);
const [filteredEnvPartSigunData, setFilteredEnvPartSigunData] = useState<SelectDataType[]>([{ text: "전체", val: '0' }]);
const [filteredEnvPartSigunDataSelected, setFilteredEnvPartSigunDataSelected] = useState<SelectDataType[]>([]);
// 시군구 세팅
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) => {
switch(type) {
case 'part':
setPartValue(val);
if (setRtnPartValue) {
setRtnPartValue(val);
}
// 구분
if(val == '01') {
setShowPart('01');
initializeMiddleRegionData('0');
initializeUnitWaterShedData('0');
} else if(val == '02') {
setShowPart('02');
setFilteredEnvPartData([
{ text: "전체", val: '0', adstDliStrCd:'' },
...selectEnvPartData.filter((item: EnvPartSelectDataType) => item.val !== null && item.adstDliStrCd !== '')
]);
setEnvPartValue('0');
initializeCityDataByEnvPartSido('0');
} else {
setShowPart('0');
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);
if (setRtnSidoValue) {
setRtnSidoValue(val);
}
initializeSigunData(val);
onSelectValue(selectSigunData[0]?.val || '0', 'sigun');
break;
case 'sigun':
if (setRtnSigunValue) {
setRtnSigunValue(val);
}
setSigunValue(val);
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' :
setEnvPartSidoValue(val);
if (setRtnEnvPartSidoValue) {
setRtnEnvPartSidoValue(val);
}
initializeCityDataByEnvPartSigun(val);
onSelectValue(filteredEnvPartSigunData[0]?.val || '0', 'envPartSigun');
break;
case 'envPartSigun' :
setEnvPartSigunValue(val);
if (setRtnEnvPartSigunValue) {
setRtnEnvPartSigunValue(val);
}
break;
}
}
// 리셋 함수
const resetAllValues = () => {
setPartValue('0');
setShowPart('');
setSidoValue('0');
setSigunValue('0');
setRegionValue('0');
setUnitWaterShedValue('0');
setMiddleRegionValue('0');
setEnvPartValue('0');
setEnvPartSidoValue('0');
setEnvPartSigunValue('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('0');
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]);
function SearchFormLeft(props: {children: React.ReactNode}) {
return (
<div className="grid grid-cols-12 gap-4 w-4/5 mx-4">
{props.children}
isShowPart ? (
<div className="grid grid-cols-12 gap-5 w-11/12 m-4">
<SelectBox className="col-span-3" label={'구분'} selectArray={selectPartData} onSelectValue={(val) => onSelectValue(val.toString(), 'part')} value={partValue} selectClass={undefined}/>
{showPart === '01' ? (
<>
<SelectBox className="col-span-3" label={'유역'} selectArray={selectRegionData} onSelectValue={(val) => onSelectValue(val.toString(), 'region')} value={regionValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'단위유역'} selectArray={filteredUnitWaterShedData} onSelectValue={(val) => onSelectValue(val.toString(), 'unitWaterShed')} value={unitWaterShedValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'중권역'} selectArray={filteredMiddleRegionData} onSelectValue={(val) => onSelectValue(val.toString(), 'middleRegion')} value={middleRegionValue} selectClass={undefined}/>
</>
) : showPart === '02' ? (
<>
<SelectBox className="col-span-3" label={'환경청'} selectArray={filteredEnvPartData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPart')} value={envPartValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'시도'} selectArray={filteredEnvPartSidoData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPartSido')} value={envPartSidoValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'시군구'} selectArray={filteredEnvPartSigunData} onSelectValue={(val) => onSelectValue(val.toString(), 'envPartSigun')} value={envPartSigunValue} selectClass={undefined}/>
</>
) : (
<>
<SelectBox className="col-span-3" label={'시도'} selectArray={selectSidoData} onSelectValue={(val) => onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={undefined}/>
<SelectBox className="col-span-3" label={'시군구'} selectArray={filteredSigunData} onSelectValue={(val) => onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={undefined}/>
</>
)}
{children}
</div>
) : (
<div className="grid grid-cols-12 gap-5 w-11/12 m-4">
{children}
</div>
)
)
}
@@ -0,0 +1,375 @@
## SearchFormLeft 모듈
#### 검색영역 좌측 모듈로 행정별, 유역별, 환경청을 선택했을 때 검색 영역을 세팅해주고 childnode를 붙여서 출력해주는 모듈
#### 1. 컴포넌트 호출방법
```js
import { SearchFormLeft } from "@common/business_components";
.....
// state 지정
const [partValue, setPartValue] = useState<string>("0");
const [sidoValue, setSidoValue] = useState<string>("0");
const [sigunValue, setSigunValue] = useState<string>("0");
const [regionValue, setRegionValue] = useState<string>("0");
const [untitWaterShedValue, setUntitWaterShedValue] = useState<string>("0");
const [middleRegionValue, setMiddleRegionValue] = useState<string>("0");
const [envPartValue, setEnvPartValue] = useState<string>("0");
const [envPartSidoValue, setEnvPartSidoValue] = useState<string>("0");
const [envPartSigunValue, setEnvPartSigunValue] = useState<string>("0");
const [resetTrigger, setResetTrigger] = useState<boolean>(false);
.....
<SearchFormLeft isShowPart={true}
selectSidoData={selectSidoData}
selectSigunData={selectSigunData}
selectRegionData={selectRegionData}
selectUnitWaterShedData={selectUnitWaterShedData}
selectMiddleRegionData={selectMiddleRegionData}
selectEnvPartData={selectEnvPartData}
setRtnPartValue={setPartValue}
setRtnSidoValue={setSidoValue}
setRtnSigunValue={setSigunValue}
setRtnRegionValue={setRegionValue}
setRtnUnitWaterShedValue={setUntitWaterShedValue}
setRtnMiddleRegionValue={setMiddleRegionValue}
setRtnEnvPartValue={setEnvPartValue}
setRtnEnvPartSidoValue={setEnvPartSidoValue}
setRtnEnvPartSigunValue={setEnvPartSigunValue}
resetTrigger={resetTrigger}
>
```
&emsp; ㆍisShowPart : 검색단에서 구분(행정별,유역별,환경청별) 세팅 여부(보일 경우 : Y, 아닐경우 : N 혹은 미첨부)
&emsp; ㆍselectSidoData : 전체 시도 데이터
&emsp; ㆍselectSigunData : 전체 시군구구 데이터
&emsp; ㆍselectRegionData : 전체 유역역 데이터
&emsp; ㆍselectUnitWaterShedData : 전체 단위유역 데이터
&emsp; ㆍselectMiddleRegionData : 전체 중권역 데이터
&emsp; ㆍselectEnvPartData : 환경청 전체 데이터
&emsp; ㆍsetRtnPartValue : 선택한 구분 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnSidoValue : 선택한 시도 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnSigunValue : 선택한 시군구 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnRegionValue : 선택한 유역 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnUnitWaterShedValue : 선택한 단위유역 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnMiddleRegionValue : 선택한 중유역 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnEnvPartValue : 선택한 환경청 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnEnvPartSidoValue : 선택한 환경청의 시도 set함수(조회시 페이지에서 사용)
&emsp; ㆍsetRtnEnvPartSigunValue : 선택한 환경청 시도의 시군구 set함수(조회시 페이지에서 사용)
&emsp; ㆍresetTrigger : 초기화 trigger 변수
#### 2. 세팅방법
```js
/**
* store - index.js
* ※ 업무파트별로 common/폴더에 스토어를 만들어놓는걸 추천!
* ※ fetch 구문은 각자가 개발 후 return 된 데이터 타입만 맞추면 된다.
*/
// 시도 API 호출 함수 추가
const fetchSidoData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/city', null);
const data = res.data;
selectSidoData = [
{ text: "전체", val: '0'},
...data.map((item: sidoDataType) => ({
text: item.sido || '',
val: String(item.adstDstrCd) || ''
}))
];
return selectSidoData;
} catch (error) {
console.error('시도 데이터를 가져오는데 실패했습니다:', error);
return selectSidoData;
}
};
// 시군구 API 호출 함수 추가
const fetchSigunData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/sgg', null);
const data = res.data;
selectSigunData = [
{ text: "전체", val: '0' },
...data
.map((item: sigunDataType) => ({
text: item.gugun || '',
val: String(item.adstDstrCd) || ''
}))
];
return selectSigunData;
} catch (error) {
console.error('시군구 데이터를 가져오는데 실패했습니다:', error);
return selectSigunData;
}
};
// 권역 API 호출 함수 추가
const fetchRegionData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/sarea', null);
const data = res.data;
return [
{ text: "전체", val: '0'},
...data.map((item: regionDataType) => ({
text: item.rvbsn || '',
val: String(item.rvbsnCd) || ''
}))
];
} catch (error) {
console.error('권역 데이터를 가져오는데 실패했습니다:', error);
return selectRegionData;
}
};
// 중권역 API 호출 함수 추가
const fetchMiddleRegionData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/midArea', null);
const data = res.data;
return [
{ text: "전체", val: '0' },
...data
.map((item: middleRegionDataType) => ({
text: item.mdmbsn || '',
val: String(item.rvbsnCd) || ''
}))
];
} catch (error) {
console.error('중권역 데이터를 가져오는데 실패했습니다:', error);
return selectMiddleRegionData;
}
};
// 단위유역 API 호출 함수 추가
const fetchUnitWaterShedData = async (): Promise<SelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/largeArea', null);
const data = res.data;
return [
{ text: "전체", val: '0' },
...data
.map((item: unitWaterShedDataType) => ({
text: item.lgbsn || '',
val: String(item.rvbsnCd) || ''
}))
];
} catch (error) {
console.error('단위유역 데이터를 가져오는데 실패했습니다:', error);
return selectUnitWaterShedData;
}
};
// 환경청 데이터
const fetchEnvPartData = async (): Promise<envPartSelectDataType[]> => {
try {
const res = await getFetch('fo', '/addr/govArea', null);
const data = res.data;
selectEnvPartData = [
{ text: "전체", val: '0', adstDliStrCd: '' },
...data
.map((item: envPartDataType) => ({
text: item.lclgvrnmntNm || '',
val: String(item.lclgvrnmntCd) || '',
adstDliStrCd: item.adstDliStrCd || ''
}))
];
return selectEnvPartData;
} catch (error) {
console.error('환경청 데이터를 가져오는데 실패했습니다:', error);
return selectEnvPartData;
}
};
.......
// 타입 및 state 반환
export type CommonType = {
Common: {
isInit: boolean;
selectSidoData: SelectDataType[];
selectSigunData: SelectDataType[];
selectPartData: SelectDataType[];
selectUpdownData: SelectDataType[];
selectOperationData: SelectDataType[];
selectFacilityPartData: SelectDataType[];
selectSearchYear: SelectDataType[];
selectUnitWaterShedData: SelectDataType[];
selectRegionData: SelectDataType[];
selectMiddleRegionData: SelectDataType[];
selectEnvPartSidoData: SelectDataType[];
selectEnvPartSigunData: SelectDataType[];
selectFlowTerm: SelectDataType[];
selectFlowPanel: SelectDataType[];
selectEnvPartData: envPartSelectDataType[];
}
}
export const commonState:CommonType = {
Common: {
isInit: false,
selectSidoData: selectSidoData,
selectSigunData: selectSigunData,
selectPartData: selectPartData,
selectUpdownData: selectUpdownData,
selectOperationData: selectOperationData,
selectFacilityPartData: selectFacilityPartData,
selectSearchYear: selectSearchYear,
selectRegionData: selectRegionData,
selectUnitWaterShedData: selectUnitWaterShedData,
selectMiddleRegionData: selectMiddleRegionData,
selectEnvPartSidoData : selectEnvPartSidoData,
selectEnvPartSigunData : selectEnvPartSigunData,
selectFlowTerm: selectFlowTerm,
selectFlowPanel: selectFlowPanel,
selectEnvPartData : selectEnvPartData,
}
}
.....
// action export
export type CommonAction = {
initializeCommonData: () => Promise<void>;
}
.......
// reducer 세팅
export const CommonReducer:(set:any)=>CommonAction=(set: any) => {
return {
// 시도 변경 이벤트
initializeCommonData: async () => {
const promiseSet = await Promise.all([fetchSidoData(), fetchSigunData(), fetchRegionData(), fetchUnitWaterShedData(), fetchMiddleRegionData(), fetchEnvPartData()]);
const [sidoData, sggData, regionData, unitData, midData, envData] = promiseSet;
set((state: CommonType) => ({
Common: {
...state.Common,
selectSidoData: sidoData,
selectSigunData: sggData,
selectRegionData: regionData,
selectMiddleRegionData: midData,
selectUnitWaterShedData: unitData,
selectEnvPartData: envData
}
})
)},
}
}
```
```js
/**
* page.tsx
*/
.......
// store에서 액션 호출 (각 페이지 유형별로 세팅)
const {
....
,initializeCommonData
....
} = useFoStore((state) => state);
.......
// 데이터 반환
const {
isInit,
selectPartData,
selectUpdownData,
selectOperationData,
selectFacilityPartData,
selectSidoData,
selectSigunData,
selectRegionData,
selectUnitWaterShedData,
selectMiddleRegionData,
selectEnvPartData,
} = Common;
.......
// 페이지 내에서 사용할 수 있게 state 설정
const [partValue, setPartValue] = useState<string>("0");
const [sidoValue, setSidoValue] = useState<string>("0");
const [sigunValue, setSigunValue] = useState<string>("0");
const [regionValue, setRegionValue] = useState<string>("0");
const [untitWaterShedValue, setUntitWaterShedValue] = useState<string>("0");
const [middleRegionValue, setMiddleRegionValue] = useState<string>("0");
const [envPartValue, setEnvPartValue] = useState<string>("0");
const [envPartSidoValue, setEnvPartSidoValue] = useState<string>("0");
const [envPartSigunValue, setEnvPartSigunValue] = useState<string>("0");
const [resetTrigger, setResetTrigger] = useState<boolean>(false);
<!-- 초기화 기능 -->
const resetVal = () => {
console.log('=== 초기화 ===');
console.log(selectSidoData);
// SelectBox 초기화 - 각 selectArray의 첫 번째 옵션 값 사용
const initialPartValue = selectPartData[0]?.val || "0";
const initialSidoValue = selectSidoData[0]?.val || "0";
const initialSigunValue = selectSigunData[0]?.val || "0";
const initialRegionValue = selectRegionData[0]?.val || "0";
const initialUnitWaterShedValue = selectUnitWaterShedData[0]?.val || "0";
const initialMiddleRegionValue = selectMiddleRegionData[0]?.val || "0";
const initialEnvPartValue = selectEnvPartData[0]?.val || "0";
const initialEnvPartSidoValue = selectEnvPartData[0]?.val || "0";
const initialEnvPartSigunValue = selectEnvPartData[0]?.val || "0";
// 상태값 설정
setPartValue(initialPartValue);
setSidoValue(initialSidoValue);
setSigunValue(initialSigunValue);
setRegionValue(initialRegionValue);
setUntitWaterShedValue(initialUnitWaterShedValue);
setMiddleRegionValue(initialMiddleRegionValue);
setEnvPartValue(initialEnvPartValue);
setEnvPartSidoValue(initialEnvPartSidoValue);
setEnvPartSigunValue(initialEnvPartSigunValue);
// 리셋 후 트리거를 false로 설정
setTimeout(() => {
setResetTrigger(false);
}, 0);
}
// 조회 함수
const chkVal = useCallback(() => {
setResetPage(true);
// 조회 funciton
searchFcltyQltyList(1);
}, [searchFcltyQltyList]);
// 그리드 page 넘어갈 때 필요한 함수
const handlePageChange = useCallback((page: number) => {
setResetPage(false);
searchFcltyQltyList(page);
}, [searchFcltyQltyList]);
// 테이블 호출 컴포넌트
<UiTable cellClick={(index: number | undefined) => index !== undefined && cellClick(index)} headName={"시설별 수질현황"} tableData={mainList} headlist={upHeadList2} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange}>
<HeadMakeColSpan upHeadList={upHeadList2} layoutNum={layoutNum}/>
</UiTable>
```
@@ -1,11 +1,11 @@
import React from 'react'
import React from 'react';
function SearchFormRight(props: {children: React.ReactNode}) {
return (
<div className="w-1/5 mx-5 flex items-center justify-end space-x-2">
<div className="w-1/12 mx-5 flex flex-col items-center justify-center space-x-2">
{props.children}
</div>
)
}
export { SearchFormRight };
export { SearchFormRight };
@@ -21,9 +21,9 @@ const SearchInput = React.forwardRef<HTMLInputElement, {className:string, label:
return (
<div className={className+" flex items-center"} >
{label && <Label className="w-20 text-base text-gray-600">{label}</Label>}
<Input className="flex-1 ml-8 w-3/4 h-10 border rounded" value={textValue} onChange={changeText} onKeyUp={keyUp}></Input>
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
<Input className="!text-lg flex-1 w-3/4 h-14 border rounded" value={textValue} onChange={changeText} onKeyUp={keyUp}></Input>
</div>
)
});
export { SearchInput };
export { SearchInput };
@@ -24,12 +24,12 @@ const SearchInputSelect = React.forwardRef<HTMLInputElement, {className:string,
return (
<div className={className+" flex items-center"} >
{label && <Label className="w-20 text-base text-gray-600">{label}</Label>}
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
<div className="flex w-full items-center">
<Input className="w-2/3 text-lg h-10 p-2 border rounded" value={textValue} onChange={changeText} />
<Input className="w-2/3 text-xl h-14 p-2 border rounded" value={textValue} onChange={changeText} />
<Select onValueChange={onSelectValue} value={selectValue}>
<SelectTrigger className="ml-2 w-1/3 text-lg" >
<SelectValue placeholder={selectData?.[0]?.text ?? ""} />
<SelectTrigger className="ml-2 w-1/3" >
<SelectValue placeholder={selectData?.[0]?.text ?? ""} />
</SelectTrigger>
{/* <SelectContent ref={refs ? (refs as unknown as RefType)?.selectValue : null}> */}
<SelectContent >
@@ -42,4 +42,4 @@ const SearchInputSelect = React.forwardRef<HTMLInputElement, {className:string,
</div>
)
});
export { SearchInputSelect };
export { SearchInputSelect };
@@ -23,7 +23,7 @@ const SelectBox = React.forwardRef<
return (
<div className={className+" flex items-center"} ref={ref}>
{label && <Label className="w-20 text-base text-gray-600">{label}</Label>}
{label && <Label className="w-20 text-xl text-gray-600">{label}</Label>}
<Select onValueChange={onSelectValue} value={value !== undefined ? String(value) : undefined}>
<SelectTrigger className={selectClass?selectClass:"flex-1 p-2 border rounded"}>
<SelectValue placeholder={placeHolder} />
@@ -38,4 +38,4 @@ const SelectBox = React.forwardRef<
)
})
export { SelectBox };
export { SelectBox };
@@ -1,6 +1,6 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown, ChevronUp } from "lucide-react"
import * as React from "react"
import { cn } from "../../../lib/utils"
@@ -17,7 +17,7 @@ const SelectTrigger = React.forwardRef<
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
"flex h-13 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-xl ring-offset-background data-[placeholder]:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
{...props}
@@ -103,7 +103,7 @@ const SelectLabel = React.forwardRef<
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
className={cn("py-1.5 pl-8 pr-2 text-lg font-semibold", className)}
{...props}
/>
))
@@ -116,7 +116,7 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.Item
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-lg outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
@@ -145,14 +145,6 @@ const SelectSeparator = React.forwardRef<
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
export {
Select,
SelectGroup,
SelectValue,
SelectTrigger,
SelectContent,
SelectLabel,
SelectItem,
SelectSeparator,
SelectScrollUpButton,
SelectScrollDownButton,
Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue
}
@@ -50,8 +50,8 @@ export function UiTable({ tableData, headlist, pageSize, total, children, headNa
<h3 className="text-2xl font-medium text-label">{headName}</h3>
</div> : <></>}
<div className={`card !m-[1.2rem] !p-0r ${className}`}>
<Table>
<TableHeader>
<Table variant="secondary">
<TableHeader className="font-bold">
{children ? <>{children}</> : <TableRow>
{headlist?.map((head) => (
<TableHead key={head.id}>{head.title}</TableHead>
@@ -63,14 +63,14 @@ export function UiTable({ tableData, headlist, pageSize, total, children, headNa
<TableRow key={index + "row"}>
{headlist?.map((head) => (
<TableCell
className={`${head.align === 'right' ? 'text-right' : 'text-center'} ${
cellClick && idx === index ? 'opacity-40 cursor-pointer' :
className={`${head.align === 'right' ? 'text-right' : head.align === 'center' ? 'text-center' : typeof el[head.id] === 'number' ? 'text-right' : 'text-center'} ${
cellClick && idx === index ? '!font-[1000] cursor-pointer' :
cellClick ? 'cursor-pointer' : ''
}`}
onClick={() => handleCellClick(index, head.id)}
key={head.id + index}
>
{String(el[head.id] ?? '')}
{typeof el[head.id] === 'number' ? String(el[head.id]).replace(/\B(?=(\d{3})+(?!\d))/g, ",") : String(el[head.id] ?? '')}
</TableCell>
))}
</TableRow>
@@ -29,7 +29,7 @@ const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
<thead ref={ref} className={cn("sticky top-0 bg-secondary [&_tr]:border-b", className)} {...props} />
));
TableHeader.displayName = "TableHeader";
+64 -1
View File
@@ -366,6 +366,9 @@ importers:
tailwind-merge:
specifier: ^3.0.1
version: 3.0.2
xlsx:
specifier: ^0.18.5
version: 0.18.5
zod:
specifier: ^3.24.2
version: 3.24.2
@@ -425,7 +428,7 @@ importers:
specifier: ^15.14.0
version: 15.15.0
postcss:
specifier: ^8.5.1
specifier: ^8.5.3
version: 8.5.3
prettier:
specifier: ^3.4.2
@@ -3954,6 +3957,11 @@ packages:
hasBin: true
dev: true
/adler-32@1.3.1:
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
engines: {node: '>=0.8'}
dev: false
/ajv-formats@2.1.1(ajv@8.17.1):
resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==}
peerDependencies:
@@ -4337,6 +4345,14 @@ packages:
/caniuse-lite@1.0.30001701:
resolution: {integrity: sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==}
/cfb@1.2.2:
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
engines: {node: '>=0.8'}
dependencies:
adler-32: 1.3.1
crc-32: 1.2.2
dev: false
/chai@5.2.0:
resolution: {integrity: sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==}
engines: {node: '>=12'}
@@ -4426,6 +4442,11 @@ packages:
engines: {node: '>=6'}
dev: false
/codepage@1.15.0:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
dev: false
/color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -4503,6 +4524,12 @@ packages:
typescript: 5.6.3
dev: true
/crc-32@1.2.2:
resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
engines: {node: '>=0.8'}
hasBin: true
dev: false
/cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -5617,6 +5644,11 @@ packages:
mime-types: 2.1.35
dev: false
/frac@1.1.2:
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
engines: {node: '>=0.8'}
dev: false
/fraction.js@4.3.7:
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
dev: true
@@ -7672,6 +7704,13 @@ packages:
whatwg-url: 7.1.0
dev: true
/ssf@0.11.2:
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
engines: {node: '>=0.8'}
dependencies:
frac: 1.1.2
dev: false
/stable-hash@0.0.4:
resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==}
dev: true
@@ -8636,11 +8675,21 @@ packages:
dependencies:
isexe: 2.0.0
/wmf@1.0.2:
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
engines: {node: '>=0.8'}
dev: false
/word-wrap@1.2.5:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
dev: true
/word@0.3.0:
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
engines: {node: '>=0.8'}
dev: false
/wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@@ -8670,6 +8719,20 @@ packages:
optional: true
dev: true
/xlsx@0.18.5:
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
engines: {node: '>=0.8'}
hasBin: true
dependencies:
adler-32: 1.3.1
cfb: 1.2.2
codepage: 1.15.0
crc-32: 1.2.2
ssf: 0.11.2
wmf: 1.0.2
word: 0.3.0
dev: false
/y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}