diff --git a/packages/common/business_components/package.json b/packages/common/business_components/package.json index 5120980c2..1f02a708e 100644 --- a/packages/common/business_components/package.json +++ b/packages/common/business_components/package.json @@ -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": { diff --git a/packages/common/business_components/src/ui/dialog_ui/index.tsx b/packages/common/business_components/src/ui/dialog_ui/index.tsx index 0f14c211e..b9e341279 100644 --- a/packages/common/business_components/src/ui/dialog_ui/index.tsx +++ b/packages/common/business_components/src/ui/dialog_ui/index.tsx @@ -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; diff --git a/packages/common/business_components/src/ui/excel_download/index.tsx b/packages/common/business_components/src/ui/excel_download/index.tsx new file mode 100644 index 000000000..d1370b95c --- /dev/null +++ b/packages/common/business_components/src/ui/excel_download/index.tsx @@ -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'); + }; \ No newline at end of file diff --git a/packages/common/business_components/src/ui/index.ts b/packages/common/business_components/src/ui/index.ts index c3022740d..e2273e8e5 100644 --- a/packages/common/business_components/src/ui/index.ts +++ b/packages/common/business_components/src/ui/index.ts @@ -16,4 +16,5 @@ export * from "./search_form_right"; export * from "./table_div"; export * from "./list_div"; export * from "./tab"; -export * from "./dialog_ui" \ No newline at end of file +export * from "./dialog_ui"; +export * from "./excel_download"; \ No newline at end of file diff --git a/packages/common/business_components/src/ui/search_form/index.tsx b/packages/common/business_components/src/ui/search_form/index.tsx index 34a6d41e6..860c99c69 100644 --- a/packages/common/business_components/src/ui/search_form/index.tsx +++ b/packages/common/business_components/src/ui/search_form/index.tsx @@ -1,7 +1,7 @@ function SearchDiv ({children}:{ children: React.ReactNode; }) { return (
- {children} + {children}
) } 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 950306231..248e8f625 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 @@ -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('0'); + const [showPart, setShowPart] = useState(''); + const [sidoValue, setSidoValue] = useState('0'); + const [sigunValue, setSigunValue] = useState('0'); + const [regionValue, setRegionValue] = useState('0'); + const [unitWaterShedValue, setUnitWaterShedValue] = useState('0'); + const [middleRegionValue, setMiddleRegionValue] = useState('0'); + const [envPartValue, setEnvPartValue] = useState('0'); + const [envPartSidoValue, setEnvPartSidoValue] = useState('0'); + const [envPartSigunValue, setEnvPartSigunValue] = useState('0'); + + 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 [filteredEnvPartSigunDataSelected, setFilteredEnvPartSigunDataSelected] = useState([]); + + // 시군구 세팅 + 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 ( -
- {props.children} + isShowPart ? ( +
+ onSelectValue(val.toString(), 'part')} value={partValue} selectClass={undefined}/> + {showPart === '01' ? ( + <> + onSelectValue(val.toString(), 'region')} value={regionValue} selectClass={undefined}/> + onSelectValue(val.toString(), 'unitWaterShed')} value={unitWaterShedValue} selectClass={undefined}/> + onSelectValue(val.toString(), 'middleRegion')} value={middleRegionValue} selectClass={undefined}/> + + ) : showPart === '02' ? ( + <> + onSelectValue(val.toString(), 'envPart')} value={envPartValue} selectClass={undefined}/> + onSelectValue(val.toString(), 'envPartSido')} value={envPartSidoValue} selectClass={undefined}/> + onSelectValue(val.toString(), 'envPartSigun')} value={envPartSigunValue} selectClass={undefined}/> + + ) : ( + <> + onSelectValue(val.toString(), 'sido')} value={sidoValue} selectClass={undefined}/> + onSelectValue(val.toString(), 'sigun')} value={sigunValue} selectClass={undefined}/> + + )} + {children}
+ ) : ( +
+ {children} +
+ ) ) } diff --git a/packages/common/business_components/src/ui/search_form_left/searchFormLeft.md b/packages/common/business_components/src/ui/search_form_left/searchFormLeft.md new file mode 100644 index 000000000..04531c27f --- /dev/null +++ b/packages/common/business_components/src/ui/search_form_left/searchFormLeft.md @@ -0,0 +1,375 @@ +## SearchFormLeft 모듈 +#### 검색영역 좌측 모듈로 행정별, 유역별, 환경청을 선택했을 때 검색 영역을 세팅해주고 childnode를 붙여서 출력해주는 모듈 +#### 1. 컴포넌트 호출방법 +```js +import { SearchFormLeft } from "@common/business_components"; + +..... + +// state 지정 +const [partValue, setPartValue] = useState("0"); +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 [envPartSidoValue, setEnvPartSidoValue] = useState("0"); +const [envPartSigunValue, setEnvPartSigunValue] = useState("0"); +const [resetTrigger, setResetTrigger] = useState(false); +..... + + + +``` +  ㆍisShowPart : 검색단에서 구분(행정별,유역별,환경청별) 세팅 여부(보일 경우 : Y, 아닐경우 : N 혹은 미첨부) + +  ㆍselectSidoData : 전체 시도 데이터 + +  ㆍselectSigunData : 전체 시군구구 데이터 + +  ㆍselectRegionData : 전체 유역역 데이터 + +  ㆍselectUnitWaterShedData : 전체 단위유역 데이터 + +  ㆍselectMiddleRegionData : 전체 중권역 데이터 + +  ㆍselectEnvPartData : 환경청 전체 데이터 + +  ㆍsetRtnPartValue : 선택한 구분 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnSidoValue : 선택한 시도 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnSigunValue : 선택한 시군구 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnRegionValue : 선택한 유역 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnUnitWaterShedValue : 선택한 단위유역 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnMiddleRegionValue : 선택한 중유역 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnEnvPartValue : 선택한 환경청 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnEnvPartSidoValue : 선택한 환경청의 시도 set함수(조회시 페이지에서 사용) + +  ㆍsetRtnEnvPartSigunValue : 선택한 환경청 시도의 시군구 set함수(조회시 페이지에서 사용) + +  ㆍresetTrigger : 초기화 trigger 변수 + + + +#### 2. 세팅방법 +```js +/** + * store - index.js + * ※ 업무파트별로 common/폴더에 스토어를 만들어놓는걸 추천! + * ※ fetch 구문은 각자가 개발 후 return 된 데이터 타입만 맞추면 된다. + */ + +// 시도 API 호출 함수 추가 +const fetchSidoData = async (): Promise => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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; +} + +....... + +// 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("0"); +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 [envPartSidoValue, setEnvPartSidoValue] = useState("0"); +const [envPartSigunValue, setEnvPartSigunValue] = useState("0"); +const [resetTrigger, setResetTrigger] = useState(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]); + +// 테이블 호출 컴포넌트 + index !== undefined && cellClick(index)} headName={"시설별 수질현황"} tableData={mainList} headlist={upHeadList2} pageSize={10} total={total} resetPageNo={resetPage} onPageChange={handlePageChange}> + + +``` + + diff --git a/packages/common/business_components/src/ui/search_form_right/index.tsx b/packages/common/business_components/src/ui/search_form_right/index.tsx index 676b90849..fda7eefd6 100644 --- a/packages/common/business_components/src/ui/search_form_right/index.tsx +++ b/packages/common/business_components/src/ui/search_form_right/index.tsx @@ -1,11 +1,11 @@ -import React from 'react' +import React from 'react'; function SearchFormRight(props: {children: React.ReactNode}) { return ( -
+
{props.children}
) } -export { SearchFormRight }; \ No newline at end of file +export { SearchFormRight }; diff --git a/packages/common/business_components/src/ui/search_input/index.tsx b/packages/common/business_components/src/ui/search_input/index.tsx index bd634aa21..06ffce4ff 100644 --- a/packages/common/business_components/src/ui/search_input/index.tsx +++ b/packages/common/business_components/src/ui/search_input/index.tsx @@ -21,9 +21,9 @@ const SearchInput = React.forwardRef - {label && } - + {label && } +
) }); -export { SearchInput }; \ No newline at end of file +export { SearchInput }; diff --git a/packages/common/business_components/src/ui/search_input_select/index.tsx b/packages/common/business_components/src/ui/search_input_select/index.tsx index 5a386b77b..bf8787356 100644 --- a/packages/common/business_components/src/ui/search_input_select/index.tsx +++ b/packages/common/business_components/src/ui/search_input_select/index.tsx @@ -24,12 +24,12 @@ const SearchInputSelect = React.forwardRef - {label && } + {label && }
- + @@ -38,4 +38,4 @@ const SelectBox = React.forwardRef< ) }) -export { SelectBox }; \ No newline at end of file +export { SelectBox }; diff --git a/packages/common/business_components/src/ui/select/lib/selectBox.tsx b/packages/common/business_components/src/ui/select/lib/selectBox.tsx index 08e3abed3..cc4bac4d1 100644 --- a/packages/common/business_components/src/ui/select/lib/selectBox.tsx +++ b/packages/common/business_components/src/ui/select/lib/selectBox.tsx @@ -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< 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) => ( )) @@ -116,7 +116,7 @@ const SelectItem = React.forwardRef< {headName}
: <>}
- - +
+ {children ? <>{children} : {headlist?.map((head) => ( {head.title} @@ -63,14 +63,14 @@ export function UiTable({ tableData, headlist, pageSize, total, children, headNa {headlist?.map((head) => ( 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] ?? '')} ))} diff --git a/packages/common/components/src/ui/table/index.tsx b/packages/common/components/src/ui/table/index.tsx index 797f48dfd..082b2e33b 100644 --- a/packages/common/components/src/ui/table/index.tsx +++ b/packages/common/components/src/ui/table/index.tsx @@ -29,7 +29,7 @@ const TableHeader = React.forwardRef< HTMLTableSectionElement, React.HTMLAttributes >(({ className, ...props }, ref) => ( - + )); TableHeader.displayName = "TableHeader"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index baa723e45..9d4ad4920 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'}