330 lines
12 KiB
TypeScript
330 lines
12 KiB
TypeScript
"use client";
|
|
|
|
import React, { useEffect, useMemo, useState } from "react";
|
|
import moment from "moment";
|
|
|
|
import {
|
|
CHART_FORM,
|
|
type ChartConfig,
|
|
MainChart,
|
|
} 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";
|
|
|
|
type ModalOpenProp = {
|
|
pplMngNo?: string;
|
|
rainInfwtAnlsSn?: number;
|
|
aplcnYm?: string;
|
|
analysisInfo?: string;
|
|
resultOnly?: boolean;
|
|
};
|
|
|
|
type LineBarChartData = {
|
|
label: string;
|
|
lineVal0: number;
|
|
barVal0: number;
|
|
};
|
|
|
|
const INITIAL_ANALYSIS_OPTIONS = [{ val: 0, text: "선택" }];
|
|
|
|
const normalizePplMngNo = (value: string) => {
|
|
const trimmed = String(value ?? "").trim();
|
|
if (!trimmed) return "";
|
|
return trimmed.startsWith("L") ? trimmed : `L${trimmed}`;
|
|
};
|
|
|
|
const RdiiChart = ({
|
|
pplMngNo = "",
|
|
rainInfwtAnlsSn = 0,
|
|
aplcnYm = "",
|
|
analysisInfo = "",
|
|
resultOnly = false,
|
|
}: ModalOpenProp) => {
|
|
const [selectedAplcnYm, setSelectedAplcnYm] = 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 [selectedRainInfwtAnlsSn, setSelectedRainInfwtAnlsSn] = 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");
|
|
|
|
const normalizedPplMngNo = normalizePplMngNo(pplMngNo);
|
|
const isResultOnlyMode = resultOnly && rainInfwtAnlsSn > 0 && Boolean(normalizedPplMngNo);
|
|
const aplcnYms = fn.useAplcnYms(isResultOnlyMode ? "" : "rdiiAnlsRslt");
|
|
|
|
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 = () => {
|
|
setSelectedAplcnYm("");
|
|
setRdiiAnlss([]);
|
|
setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS);
|
|
setRdiiAnls(null);
|
|
setSelectedRainInfwtAnlsSn(0);
|
|
setChartData([]);
|
|
setRdiiQty("0");
|
|
setRainTotal("0");
|
|
setTotalVolume("0");
|
|
};
|
|
|
|
const loadResultChart = async (targetRainInfwtAnlsSn: number) => {
|
|
if (!targetRainInfwtAnlsSn || !normalizedPplMngNo) {
|
|
setChartData([]);
|
|
setRdiiQty("0");
|
|
setRainTotal("0");
|
|
setTotalVolume("0");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const [rsltData, rainData, qtyData] = await Promise.all([
|
|
fn.selectRdiiAnlsRslt(targetRainInfwtAnlsSn, normalizedPplMngNo),
|
|
fn.selectRainData(targetRainInfwtAnlsSn),
|
|
fn.selectPplRdiiQty(targetRainInfwtAnlsSn, 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("RDII 분석결과 차트 조회 중 오류:", error);
|
|
setChartData([]);
|
|
setRdiiQty("0");
|
|
setRainTotal("0");
|
|
setTotalVolume("0");
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!isResultOnlyMode) return;
|
|
|
|
setSelectedAplcnYm(aplcnYm);
|
|
setSelectedRainInfwtAnlsSn(rainInfwtAnlsSn);
|
|
setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS);
|
|
void loadResultChart(rainInfwtAnlsSn);
|
|
}, [aplcnYm, isResultOnlyMode, normalizedPplMngNo, rainInfwtAnlsSn]);
|
|
|
|
const handleAplcnYmChange = async (val: string | number) => {
|
|
const selectedValue = String(val);
|
|
|
|
setSelectedAplcnYm(selectedValue);
|
|
setRdiiAnlss([]);
|
|
setRdiiAnls(null);
|
|
setSelectedRainInfwtAnlsSn(0);
|
|
setChartData([]);
|
|
setRdiiQty("0");
|
|
setRainTotal("0");
|
|
setTotalVolume("0");
|
|
|
|
if (!selectedValue || selectedValue === "0") {
|
|
setRdiiAnlsInfos(INITIAL_ANALYSIS_OPTIONS);
|
|
return;
|
|
}
|
|
|
|
const data = await fn.selectRdiiAnls(selectedValue);
|
|
const list = data ?? [];
|
|
setRdiiAnlss(list);
|
|
setRdiiAnlsInfos(fn.getOptionsRdiiAnlsInfo(list));
|
|
};
|
|
|
|
const handleRdiiAnlsSnChange = (val: string | number) => {
|
|
const selectedValue = Number(val);
|
|
|
|
setSelectedRainInfwtAnlsSn(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 (!selectedRainInfwtAnlsSn || !normalizedPplMngNo) {
|
|
setChartData([]);
|
|
setRdiiQty("0");
|
|
setRainTotal("0");
|
|
setTotalVolume("0");
|
|
return;
|
|
}
|
|
|
|
await loadResultChart(selectedRainInfwtAnlsSn);
|
|
};
|
|
|
|
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 renderResultPanel = () => (
|
|
<section className="relative rounded-[12px] border border-[#b1b1b1] bg-white p-4">
|
|
<h3 className="mb-4 text-[20px] font-extrabold tracking-[-0.02em] text-black">RDII 분석 결과</h3>
|
|
<div className="mb-4 grid grid-cols-2 gap-4">
|
|
<div className="flex min-h-[98px] items-center justify-between rounded-[14px] border border-[#4f88c6] bg-white px-6 py-4">
|
|
<div className="flex items-center gap-2 text-[16px] font-extrabold text-[#ef4444]">
|
|
<span>상수사용 적용연월</span>
|
|
</div>
|
|
<div className="text-[20px] font-extrabold text-[#222]">
|
|
{selectedAplcnYm ? moment(selectedAplcnYm, "YYYYMM").format("YYYY-MM") : "-"}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex min-h-[98px] items-center justify-between rounded-[14px] border border-[#4f88c6] bg-white px-6 py-4">
|
|
<div className="shrink-0 whitespace-nowrap text-[14px] font-extrabold text-[#ef4444]">
|
|
<span>강우유입수량 분석결과</span>
|
|
</div>
|
|
<div className="ml-4 flex min-w-0 basis-[60%] 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>
|
|
);
|
|
|
|
const renderDefaultBody = () => (
|
|
<div className="text-black">
|
|
<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={selectedAplcnYm}
|
|
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={selectedRainInfwtAnlsSn}
|
|
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>
|
|
|
|
{renderResultPanel()}
|
|
</div>
|
|
</div>
|
|
);
|
|
|
|
if (isResultOnlyMode) {
|
|
return <div className="min-h-dvh bg-white p-3">{renderResultPanel()}</div>;
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-dvh bg-white p-4">
|
|
<h1 className="mb-3 text-2xl font-semibold text-gray-900">RDII 분석</h1>
|
|
{renderDefaultBody()}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default React.memo(RdiiChart); |