358 lines
13 KiB
TypeScript
358 lines
13 KiB
TypeScript
'use client'
|
|
|
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
import moment from 'moment';
|
|
|
|
import {
|
|
CHART_FORM,
|
|
type ChartConfig,
|
|
MainChart,
|
|
UiModal,
|
|
} from '@common/business_components';
|
|
import { SelectBox } from '@common/business_components/ui';
|
|
import { Button, Input, Label } from '@common/components';
|
|
|
|
import type { TRdiiAnls } from './aplcnYms';
|
|
import * as fn from './aplcnYms';
|
|
import { useRdiiChartModalStore } from '../chartModalStore';
|
|
|
|
type ModalOpenProp = {
|
|
pplMngNo?: string; // 관로관리번호
|
|
};
|
|
|
|
type LineBarChartData = {
|
|
label: string;
|
|
lineVal0: number;
|
|
barVal0: number;
|
|
};
|
|
|
|
const INITIAL_ANALYSIS_OPTIONS = [{ val: 0, text: '선택' }];
|
|
|
|
const RdiiChart = ({ pplMngNo = '' }: ModalOpenProp) => {
|
|
const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useRdiiChartModalStore();
|
|
|
|
const [aplcnYm, setAplcnYm] = useState('');
|
|
const [rdiiAnlss, setRdiiAnlss] = useState<TRdiiAnls[]>([]);
|
|
const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>(
|
|
INITIAL_ANALYSIS_OPTIONS,
|
|
);
|
|
const [rdiiAnls, setRdiiAnls] = useState<TRdiiAnls | null>(null); // 강우유입수분석정보
|
|
const [rainInfwtAnlsSn, setRainInfwtAnlsSn] = useState<number>(0);// 강우유입수분석 정보 선택시 사용
|
|
const [chartData, setChartData] = useState<LineBarChartData[]>([]);
|
|
const [rdiiQty, setRdiiQty] = useState<string>('0'); // 강우유입수량
|
|
const [rainTotal, setRainTotal] = useState<string>('0'); // 적용강우량
|
|
const [totalVolume, setTotalVolume] = useState<string>('0');// 총체적 m³/mm
|
|
|
|
const aplcnYms = fn.useAplcnYms('rdiiAnlsRslt'); // 상수사용월 데이터 가져오기
|
|
const normalizedPplMngNo = pplMngNo ? `L${pplMngNo}` : '';
|
|
|
|
const chartForm = CHART_FORM.LINEBAR2;
|
|
const chartConfig = {
|
|
barVal0: {
|
|
label: '강수량',
|
|
color: '#33AAFF',
|
|
},
|
|
lineVal0: {
|
|
label: 'RDII',
|
|
color: '#FF0000',
|
|
},
|
|
} as ChartConfig;
|
|
|
|
const hasChartData = useMemo(() => chartData.length > 0, [chartData]);
|
|
|
|
const resetState = () => {
|
|
setAplcnYm(''); // 상수사용월 초기화
|
|
setRdiiAnlss([]); // 강우유입수분석 초기화
|
|
setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS); // 강우유입수분석정보 초기화
|
|
setRdiiAnls(null); // 강우유입수분석정보 초기화
|
|
setRainInfwtAnlsSn(0); // 강우유입수분석 일련번호 초기화
|
|
setChartData([]);
|
|
setRdiiQty('0'); // 강우유입수량 초기화
|
|
setRainTotal('0');// 적용강우량 초기화
|
|
setTotalVolume('0');// 총체적 m³/mm 초기화
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!isModalOpen) {
|
|
resetState();
|
|
}
|
|
}, [isModalOpen]);
|
|
|
|
const handleAplcnYmChange = async (val: string | number) => {
|
|
const selectedValue = String(val);
|
|
|
|
setAplcnYm(selectedValue);
|
|
setRdiiAnlss([]);
|
|
setRdiiAnls(null);
|
|
setRainInfwtAnlsSn(0);
|
|
setChartData([]);
|
|
setRdiiQty('0');
|
|
setRainTotal('0');
|
|
setTotalVolume('0');
|
|
|
|
if (!selectedValue || selectedValue === '0') {
|
|
setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const data = await fn.selectRdiiAnls(selectedValue); // 상수사용월 변경시 분석 정보 리스트 가져오기
|
|
const list = data ?? [];
|
|
setRdiiAnlss(list);
|
|
setRdiiAnlsInfos(fn.getOptionsRdiiAnlsInfo(list));
|
|
} catch (error) {
|
|
console.error('selectRdiiAnls 에러:', error);
|
|
setRdiiAnlss([]);
|
|
setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS);
|
|
}
|
|
};
|
|
|
|
const handleRdiiAnlsSnChange = (val: string | number) => {
|
|
const selectedValue = Number(val);
|
|
|
|
setRainInfwtAnlsSn(selectedValue);
|
|
setChartData([]);
|
|
setRdiiQty('0');
|
|
setRainTotal('0');
|
|
setTotalVolume('0');
|
|
|
|
if (selectedValue === 0) {
|
|
setRdiiAnls(null);
|
|
return;
|
|
}
|
|
|
|
const selectedAnls = rdiiAnlss.find((anls) => anls.rainInfwtAnlsSn === selectedValue) ?? null;
|
|
setRdiiAnls(selectedAnls);
|
|
};
|
|
|
|
const fetchFilteredData = async () => {
|
|
if (!rainInfwtAnlsSn || !normalizedPplMngNo) {
|
|
setChartData([]);
|
|
setRdiiQty('0');
|
|
setRainTotal('0');
|
|
setTotalVolume('0');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const [rsltData, rainData, qtyData] = await Promise.all([
|
|
fn.selectRdiiAnlsRslt(rainInfwtAnlsSn, normalizedPplMngNo),
|
|
fn.selectRainData(rainInfwtAnlsSn),
|
|
fn.selectPplRdiiQty(rainInfwtAnlsSn, normalizedPplMngNo),
|
|
]);
|
|
|
|
const rsltDataFormatted = fn.getChartDataRdiiAnlsRslt(rsltData ?? []);
|
|
const rainDataFormatted = fn.getChartDataRainData(rainData ?? []);
|
|
const rainMap = new Map(rainDataFormatted.map((item) => [item.label, item.barVal0]));
|
|
|
|
rsltDataFormatted.forEach((item) => {
|
|
const matchedVal = rainMap.get(item.label);
|
|
if (matchedVal !== undefined) {
|
|
item.barVal0 = matchedVal;
|
|
}
|
|
});
|
|
|
|
setChartData(rsltDataFormatted);// 차트 데이터 설정
|
|
|
|
const avgRdii = Number(qtyData?.rdiih ?? 0);
|
|
const appliedRainfall = Number(qtyData?.raintotal ?? 0);
|
|
const rdiiPerRainfall =
|
|
qtyData?.raintotal && qtyData?.raintotal !== 0
|
|
? Math.round(avgRdii * Number(qtyData?.sustaintime ?? 0) / qtyData.raintotal)
|
|
: 0;
|
|
|
|
setRdiiQty(Math.round(avgRdii * 24).toLocaleString('ko-KR'));
|
|
setRainTotal(appliedRainfall.toLocaleString('ko-KR'));
|
|
setTotalVolume(rdiiPerRainfall.toLocaleString('ko-KR'));
|
|
} catch (error) {
|
|
console.error('rdiiRsltChart 에러:', error);
|
|
setChartData([]);
|
|
setRdiiQty('0');
|
|
setRainTotal('0');
|
|
setTotalVolume('0');
|
|
}
|
|
};
|
|
|
|
const renderInfoInput = (label: string, value?: string | number | null) => (
|
|
<div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3">
|
|
<Label className="w-auto whitespace-nowrap text-[14px] font-semibold text-[#5c6470]">
|
|
{label}
|
|
</Label>
|
|
<Input
|
|
className="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white px-3 text-xl text-[#2b3440]"
|
|
placeholder=""
|
|
value={value ?? ''}
|
|
readOnly
|
|
/>
|
|
</div>
|
|
);
|
|
|
|
const renderBody = () => (
|
|
<div className="text-black">
|
|
<div className="mb-4 flex items-start justify-between">
|
|
<div className="text-[18px] font-semibold text-[#111827]" />
|
|
</div>
|
|
|
|
<div className="grid min-h-[68vh] grid-cols-[minmax(0,36%)_minmax(0,64%)] gap-5">
|
|
<section className="relative rounded-[12px] border border-[#b1b1b1] bg-white p-5">
|
|
<h3 className="mb-6 text-[20px] font-extrabold tracking-[-0.02em] text-black">
|
|
강우유입수분석 선택 조건
|
|
</h3>
|
|
|
|
<div className="rounded-[6px] border border-[#d9dde3] bg-[#eff1f4] px-4 py-6">
|
|
<div className="space-y-3">
|
|
<div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3">
|
|
<Label className="w-auto whitespace-nowrap text-[14px] font-semibold text-[#5c6470]">
|
|
상수사용월:
|
|
</Label>
|
|
<SelectBox
|
|
className="w-full"
|
|
selectClass="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white text-xl"
|
|
label=""
|
|
selectArray={aplcnYms}
|
|
value={aplcnYm}
|
|
onSelectValue={handleAplcnYmChange}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-[minmax(0,32%)_minmax(0,68%)] items-center gap-3">
|
|
<Label className="w-auto whitespace-nowrap text-[14px] font-semibold text-[#5c6470]">
|
|
분석정보:
|
|
</Label>
|
|
<SelectBox
|
|
className="w-full"
|
|
selectClass="h-[42px] rounded-[5px] border border-[#d7dde5] bg-white text-xl"
|
|
label=""
|
|
selectArray={rdiiAnlsInfos}
|
|
value={rainInfwtAnlsSn}
|
|
onSelectValue={handleRdiiAnlsSnChange}
|
|
/>
|
|
</div>
|
|
|
|
{renderInfoInput('오수전환율:', rdiiAnls?.drtwSwtcRt ?? '')}
|
|
{renderInfoInput('수용가-맨홀 매칭:', rdiiAnls?.atflMpngNm ?? '')}
|
|
{renderInfoInput('관망도 파일명:', rdiiAnls?.atflInpNm ?? '')}
|
|
{renderInfoInput('강우 파일명:', rdiiAnls?.atflRainNm ?? '')}
|
|
{renderInfoInput('RTK계수 출처:', rdiiAnls?.srcNm ?? '')}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="absolute inset-x-0 bottom-0 mb-[40px] flex justify-center">
|
|
<Button size="lg" onClick={fetchFilteredData}>
|
|
조회
|
|
</Button>
|
|
|
|
<Button size="lg" color="gray" onClick={resetState}>
|
|
초기화
|
|
</Button>
|
|
</div>
|
|
</section>
|
|
|
|
<section className="relative rounded-[12px] border border-[#b1b1b1] bg-white p-5">
|
|
<h3 className="mb-6 text-[20px] font-extrabold tracking-[-0.02em] text-black">
|
|
강우유입수분석 결과
|
|
</h3>
|
|
|
|
<div className="mb-6 flex gap-5">
|
|
{/* 좌측 카드 */}
|
|
<div className="flex min-h-[98px] min-w-0 basis-2/5 grow shrink-[1.4] items-center justify-between rounded-[16px] border border-[#5fa3ea] bg-white px-6 py-4">
|
|
<div className="text-[16px] font-bold text-[#ef4444]">
|
|
상수사용량 적용연월
|
|
</div>
|
|
<div className="text-[24px] font-extrabold leading-none text-[#111]">
|
|
{aplcnYm ? moment(aplcnYm, 'YYYYMM').format('YYYY-MM') : '-'}
|
|
</div>
|
|
</div>
|
|
|
|
{/* 우측 카드 */}
|
|
<div className="flex min-h-[98px] min-w-0 basis-3/5 grow shrink items-center gap-5 rounded-[16px] border border-[#5fa3ea] bg-white px-6 py-4">
|
|
{/* 좌측: 제목 (세로 가운데 정렬) */}
|
|
<div className="flex min-w-0 basis-[32%] items-center">
|
|
<div className="text-[16px] font-bold text-[#ef4444] break-keep">
|
|
강우유입수량 분석결과
|
|
</div>
|
|
</div>
|
|
|
|
{/* 우측: 지표 영역 */}
|
|
<div className="flex min-w-0 basis-[68%] flex-col gap-[2px]">
|
|
<div className="flex min-w-0 items-center gap-3">
|
|
<div className="min-w-0 basis-[48%] text-right text-[15px] text-[#555]">
|
|
평균RDII
|
|
</div>
|
|
<div className="min-w-0 basis-[32%] text-right text-[18px] font-extrabold text-[#111] tabular-nums">
|
|
{rdiiQty}
|
|
</div>
|
|
<div className="min-w-0 basis-[20%] text-left text-[14px] text-[#111]">
|
|
m³/d
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex min-w-0 items-center gap-3">
|
|
<div className="min-w-0 basis-[48%] text-right text-[15px] text-[#555]">
|
|
적용 강우량
|
|
</div>
|
|
<div className="min-w-0 basis-[32%] text-right text-[18px] font-extrabold text-[#111] tabular-nums">
|
|
{rainTotal}
|
|
</div>
|
|
<div className="min-w-0 basis-[20%] text-left text-[14px] text-[#111]">
|
|
mm
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex min-w-0 items-center gap-3">
|
|
<div className="min-w-0 basis-[48%] text-right text-[15px] text-[#555]">
|
|
강우량 대비 RDII
|
|
</div>
|
|
<div className="min-w-0 basis-[32%] text-right text-[18px] font-extrabold text-[#111] tabular-nums">
|
|
{totalVolume}
|
|
</div>
|
|
<div className="min-w-0 basis-[20%] text-left text-[14px] text-[#111]">
|
|
m³/mm
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-[10px] bg-white px-2 pb-2 pt-3">
|
|
{hasChartData ? (
|
|
<MainChart
|
|
chartConfig={chartConfig}
|
|
chartForm={chartForm}
|
|
chartData={chartData}
|
|
chartTitle={'RDII 그래프'}
|
|
yFieldText2="강수량(mm)"
|
|
yFieldText="유량(m³/h)"
|
|
/>
|
|
) : (
|
|
<div className="flex h-[464px] items-center justify-center text-[18px] text-[#2c2f33]">
|
|
데이터가 없습니다.
|
|
</div>
|
|
)}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
return (
|
|
<UiModal
|
|
open={isModalOpen}
|
|
onOpenChange={setIsModalOpen}
|
|
headerTitle=""
|
|
bodyClass={`
|
|
w-[78vw]
|
|
max-w-none
|
|
min-w-[1100px]
|
|
min-h-[72vh]
|
|
overflow-visible
|
|
z-[50]
|
|
`}
|
|
body={renderBody()}
|
|
noFooterYn="Y"
|
|
/>
|
|
);
|
|
};
|
|
|
|
export default React.memo(RdiiChart);
|