277 lines
11 KiB
TypeScript
277 lines
11 KiB
TypeScript
'use client'
|
|
import { getData } from '@/components/lib/api'
|
|
import ContentLayout from '@/components/ui/contentLayout'
|
|
import { GovSidoSigunguSearchForm } from '@/components/ui/govSidoSigunguSearchForm'
|
|
import { DatePicker, Form, SearchFormRight, SelectBox } from '@common/business_components/ui'
|
|
import ListDiv from '@common/business_components/ui/list_div'
|
|
import { SelectBox2 } from '@common/business_components/ui/select/index-2'
|
|
import { Button, Label } from '@common/components/ui'
|
|
import { useEffect, useState, type ReactNode } from 'react'
|
|
import LoadingOverlay from '@/components/ui/LoadingBar'
|
|
|
|
type TitleName = {
|
|
mainTitle: string
|
|
firstCardTitle: string
|
|
secondCardTitle?: string
|
|
}
|
|
type SelectBox = {
|
|
labelNm?: Label
|
|
searchChk?: boolean
|
|
title?: string;
|
|
onSearchClick?: () => Promise<void>;
|
|
}
|
|
type Label = {
|
|
first: string
|
|
second: string
|
|
third: string
|
|
fourth: string
|
|
}
|
|
// API로부터 받아올 데이터 타입을 정의 (SelectBox에 사용될 형태)
|
|
interface ApiSelectOption {
|
|
text: string;
|
|
val: string;
|
|
}
|
|
type BaseLayout = {
|
|
children?: ReactNode[]
|
|
titleName: TitleName
|
|
form: any
|
|
onSubmitEvent: Function
|
|
graphBtn?: boolean
|
|
analysisBtn?: string
|
|
firstDivBtn?: boolean
|
|
selectBox?: SelectBox
|
|
showDatePicker?: boolean;
|
|
enoCd?: string | number
|
|
setEnoCd?: (enoCd: string) => void
|
|
ctpvCd?: string | number
|
|
setCtpvCd?: (ctpvCd: string) => void
|
|
sigungu?: ApiSelectOption
|
|
setSigungu?: (sigungu: ApiSelectOption) => void
|
|
selectedSwageTreatmentPlant?: string | number;
|
|
onSwageTreatmentPlantChange?: (value: string | number) => void;
|
|
selectedSwageProcessingZone?: string | number;
|
|
onSwageProcessingZoneChange?: (value: string | number) => void;
|
|
aplcnYm?: Date
|
|
setAplcnYm?: (aplcnYm: Date | undefined) => 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,
|
|
form,
|
|
onSubmitEvent,
|
|
graphBtn = false,
|
|
analysisBtn,
|
|
selectBox,
|
|
showDatePicker = false,
|
|
enoCd,
|
|
setEnoCd,
|
|
ctpvCd,
|
|
setCtpvCd,
|
|
sigungu,
|
|
setSigungu,
|
|
selectedSwageTreatmentPlant,
|
|
onSwageTreatmentPlantChange,
|
|
selectedSwageProcessingZone,
|
|
onSwageProcessingZoneChange,
|
|
aplcnYm,
|
|
setAplcnYm,
|
|
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(() => {
|
|
const fetchData = async () => {
|
|
if (!sigungu || sigungu.val === '0' || !sigungu.val) {
|
|
console.log("BaseLayout: Skipping fetchData because sigungu.val is:", sigungu?.val);
|
|
setProcessingZoneOptions([defaultOption]);
|
|
setTreatmentFacilityOptions([defaultOption]);
|
|
setIsLoadingAreaData(false);
|
|
return;
|
|
}
|
|
|
|
setIsLoadingAreaData(true);
|
|
try {
|
|
const queryParamsZone = new URLSearchParams({
|
|
lyrTypeNm: 'tb_ms_swage_prcs_zone',
|
|
page: '0',
|
|
size: '100000',
|
|
sort: 'string',
|
|
});
|
|
const cqlFiltersZone: string[] = [];
|
|
if (sigungu.val && sigungu.val !== '0') {
|
|
// 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',
|
|
page: '0',
|
|
size: '100000', // 요청하신 URL에 맞게 50으로 변경 가능
|
|
sort: 'string',
|
|
});
|
|
const cqlFiltersFacility: string[] = [];
|
|
if (sigungu.text && sigungu.val !== '0') {
|
|
cqlFiltersFacility.push(`sgg_nm ILIKE '%${sigungu.text}%'`);
|
|
}
|
|
if (cqlFiltersFacility.length > 0) {
|
|
queryParamsFacility.append('cqlFiter', cqlFiltersFacility.join(' and ')); // API 명세에 따라 cqlFilter 또는 cqlFiter
|
|
}
|
|
|
|
console.log("BaseLayout API URL for Zones:", `v1/fclts?${queryParamsZone.toString()}`);
|
|
console.log("BaseLayout API URL for Facilities:", `v1/fclts?${queryParamsFacility.toString()}`);
|
|
|
|
const [zoneRes, facilityRes] = await Promise.all([
|
|
getData(`v1/fclts?${queryParamsZone.toString()}`),
|
|
getData(`v1/fclts?${queryParamsFacility.toString()}`)
|
|
]);
|
|
|
|
const zones = (zoneRes.data || []).map((item: any) => ({
|
|
text: item.swtrmtZoneNm || item.name || `구역 ${item.id || item.gid || item.objectid}`,
|
|
val: item.fid || item.id || item.gid || item.objectid
|
|
}));
|
|
setProcessingZoneOptions([defaultOption, ...zones]);
|
|
console.log("BaseLayout Fetched Processing Zones:", [defaultOption, ...zones]);
|
|
|
|
|
|
const facilities = (facilityRes.data || []).map((item: any) => ({
|
|
text: item.swtrmtFcltNm || item.name || `시설 ${item.id || item.gid || item.objectid}`,
|
|
val: item.fid || item.id || item.gid || item.objectid
|
|
}));
|
|
setTreatmentFacilityOptions([defaultOption, ...facilities]);
|
|
console.log("BaseLayout Fetched Treatment Facilities:", [defaultOption, ...facilities]);
|
|
|
|
} catch (error) {
|
|
console.error("BaseLayout: 지역/시설 데이터 조회 중 오류 발생:", error);
|
|
setProcessingZoneOptions([defaultOption]);
|
|
setTreatmentFacilityOptions([defaultOption]);
|
|
} finally {
|
|
setIsLoadingAreaData(false);
|
|
}
|
|
};
|
|
|
|
fetchData();
|
|
}, [sigungu]); // sigungu 상태가 변경될 때마다 실행
|
|
|
|
// 파일 다운로드
|
|
const isChckFile = () => {
|
|
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
|
|
if (onExcelDownload) {
|
|
onExcelDownload();
|
|
return;
|
|
}
|
|
return true
|
|
}
|
|
// 파일 삭제
|
|
const isDeleteFile = async () => {
|
|
// Excel 다운로드 함수가 있으면 Excel 다운로드 실행
|
|
if (onDeleteFile) {
|
|
onDeleteFile();
|
|
return;
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Form {...form} className="min-h-screen flex flex-col">
|
|
<LoadingOverlay isLoading={isLoading ?? false} />
|
|
<ListDiv>
|
|
{/* searchBox.title이 있으면 h3 태그로 제목을 표시합니다. */}
|
|
{selectBox?.title && (
|
|
<div className="card-header">
|
|
<h3 className="text-2xl font-medium">{selectBox.title}</h3>
|
|
</div>
|
|
)}
|
|
<div className="flex items-center w-full">
|
|
{showDatePicker && ( // showDatePicker 값에 따라 DatePicker 렌더링
|
|
<>
|
|
<Label className="ml-4 w-20 text-xl text-gray-600 whitespace-nowrap">분석기간</Label>
|
|
<DatePicker
|
|
mode={"single"}
|
|
field={{
|
|
value: aplcnYm ?? new Date(),
|
|
onChange: setAplcnYm ?? ((date: Date | undefined) => console.log("DatePicker onChange:", date))
|
|
}}
|
|
selectClass="ml-5 date-picker-trigger" />
|
|
</>
|
|
)}
|
|
<GovSidoSigunguSearchForm
|
|
isShowPart={true}
|
|
setRtnEnvPartValue={(val) => setEnoCd?.(val)}
|
|
setRtnEnvPartSidoValue={(val) => setCtpvCd?.(val)}
|
|
setRtnEnvPartSigunValue={setSigungu} // 선택된 시군구 값을 받을 콜백 함수 전달
|
|
>
|
|
<SelectBox2 label={"하수처리장"} value={selectedSwageTreatmentPlant} selectArray={treatmentFacilityOptions} className="col-span-3" selectClass="rounded-lg bg-white truncate" onSelectValue={(val) => onSwageTreatmentPlantChange?.(val)} />
|
|
{/* <SelectBox2 label={"하수처리구역"} value={selectedSwageProcessingZone} selectArray={processingZoneOptions} className="col-span-3" selectClass="rounded-lg bg-white truncate" onSelectValue={(val) => onSwageProcessingZoneChange?.(val)} /> */}
|
|
</GovSidoSigunguSearchForm>
|
|
{selectBox?.searchChk ? <SearchFormRight><Button size="sm" onClick={() => selectBox.onSearchClick?.()}>조회</Button></SearchFormRight> : null}
|
|
</div>
|
|
</ListDiv>
|
|
<ListDiv>
|
|
<div className="card-header">
|
|
<h3 className="text-2xl font-medium">{titleName.firstCardTitle}</h3>
|
|
<div>
|
|
{graphBtn ? <Button className="px-4 py-2 !mr-[1.2rem] bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700" size="sm" onClick={isChckFile}>파일 다운로드</Button> : null}
|
|
{/* 그래프 있을 시 */}
|
|
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={isDeleteFile}>삭제</Button> : null}
|
|
{/* 분석 버튼 */}
|
|
{analysisBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm" onClick={onAnalysis}>{analysisBtn}</Button> : null}
|
|
</div>
|
|
</div>
|
|
<Form {...form}>
|
|
<form onSubmit={form.handleSubmit(onSubmitEvent)} className="flex-grow flex flex-col">
|
|
<div className="card !m-[1.2rem] flex-grow flex flex-col">
|
|
{/* 폼 정보 입력 칸 */}
|
|
{children?.[0]}
|
|
</div>
|
|
</form>
|
|
</Form>
|
|
</ListDiv>
|
|
{
|
|
children?.[1] != null ? <ListDiv>
|
|
<div className="card-header flex items-center justify-between">
|
|
<h3 className="text-2xl font-medium">{titleName?.secondCardTitle}</h3>
|
|
<div>
|
|
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm">파일 다운로드</Button> : null}
|
|
{/* 그래프 있을 시 */}
|
|
{graphBtn ? <Button className="px-4 py-2 bg-blue-600 text-white rounded-sm text-sm font-medium hover:bg-blue-700 !mr-[1.2rem]" size="sm">삭제</Button> : null}
|
|
</div>
|
|
</div>
|
|
<div className="card !m-[1.2rem]">{children?.[1]}</div>
|
|
</ListDiv> : null
|
|
}
|
|
</Form>
|
|
)
|
|
}
|
|
|
|
export default BaseLayout |