diff --git a/apps/dm/app/(popup-layout)/map/popup/page.tsx b/apps/dm/app/(popup-layout)/map/popup/page.tsx index ea1e4b174..e410ad2a7 100644 --- a/apps/dm/app/(popup-layout)/map/popup/page.tsx +++ b/apps/dm/app/(popup-layout)/map/popup/page.tsx @@ -235,6 +235,7 @@ const Map = () => { <>
{ return (
{ - const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useInfilChartModalStore(); // 모달 상태 관리 스토어 - const [popupOpen, setPopupOpen] = React.useState(false); // 조회조건팝업 - const buttonRef = React.useRef(null); // 버튼 참조 - const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 - const [selectedAplcnYm, setSelectedAplcnYm] = useState(""); +type DataType = { + label: string; + val: number; +}; + +const INITIAL_ANALYSIS_OPTIONS = [{ val: 0, text: '선택' }]; + +const InfilChart = ({ pplMngNo = '' }: ModalOpenProp) => { + const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useInfilChartModalStore(); + + const [selectedAplcnYm, setSelectedAplcnYm] = useState(''); const [inftwtAnlss, setInftwtAnlss] = useState([]); - const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>([{ val: 0, text: '선택' }]); // 침입수분석정보 options - const [inftwtAnls, setInftwtAnls] = useState(null); // 침입수분석정보 - const [selectedInftwtAnls, setSelectedInftwtAnls] = useState(0); // 침입수분석 정보 선택시 사용 + const [inftwtAnlsInfos, setInftwtAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>( + INITIAL_ANALYSIS_OPTIONS, + ); + const [inftwtAnls, setInftwtAnls] = useState(null); + const [selectedInftwtAnls, setSelectedInftwtAnls] = useState(0); const [chartData, setChartData] = useState<{ chartData1: DataType[]; chartData2: DataType[] }>({ - chartData1: [], chartData2: [], + chartData1: [], + chartData2: [], }); - const [inftwtQty, setInftwtQty] = useState("0"); // 침입수량 - const aplcnYms = fn.useAplcnYms('inftwtAnlsRslt'); // 상수사용월 데이터 가져오기 + const [inftwtQty, setInftwtQty] = useState('0'); - pplMngNo = "L" + pplMngNo; // inp파일에 하수관로와 맨홀의 번호가 같으면 분석 오류가 발행하여 - const openPopup = () => { - if (buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산 - setPopupPosition({ - top: rect.bottom + window.scrollY, // 버튼 아래쪽 - left: rect.left + window.scrollX, // 버튼 왼쪽 - }); - } - setPopupOpen(true); // 팝업 열기 - }; + const aplcnYms = fn.useAplcnYms('inftwtAnlsRslt'); + const normalizedPplMngNo = pplMngNo ? `L${pplMngNo}` : ''; const chartForm = CHART_FORM.LINE; const chartConfig = { - chartData1: { label: '순오수량', color: "red", }, - chartData2: { label: '실측하수량', color: "orange", } + chartData1: { label: '순오수량', color: 'red' }, + chartData2: { label: '실측하수량', color: 'orange' }, } as ChartConfig; + const hasChartData = useMemo(() => { + return chartData.chartData1.length > 0 || chartData.chartData2.length > 0; + }, [chartData]); + + const resetState = () => { + setSelectedAplcnYm(''); + setInftwtAnlss([]); + setInftwtAnlsInfos(INITIAL_ANALYSIS_OPTIONS); + setInftwtAnls(null); + setSelectedInftwtAnls(0); + setChartData({ chartData1: [], chartData2: [] }); + setInftwtQty('0'); + }; + useEffect(() => { - if (isModalOpen === false) { - setPopupOpen(true); // 모달이 닫힐 때 조회조건팝업은 열어두기 - setSelectedAplcnYm(""); // 상수사용월 초기화 - setInftwtAnlss([]); // 침입수분석 초기화 - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); // 침입수분석정보 초기화 - setInftwtAnls(null); // 침입수분석정보 초기화 - setSelectedInftwtAnls(0); // 침입수분석 일련번호 초기화 - setChartData({ chartData1: [], chartData2: [], }); // 차트 데이터 초기화 - setInftwtQty("0"); // 침입수량 초기화 + if (!isModalOpen) { + resetState(); // 모달이 닫힐 때 리셋 } }, [isModalOpen]); - // 상수사용월 변경 핸들러 const handleAplcnYmChange = async (val: string | number) => { const selectedValue = String(val); setSelectedAplcnYm(selectedValue); - - // 즉시 관련 상태 초기화 + setInftwtAnlss([]); setInftwtAnls(null); + setSelectedInftwtAnls(0); + setInftwtQty('0'); + setChartData({ chartData1: [], chartData2: [] }); - // 빈 값이면 초기화만 하고 종료 - if (!selectedValue || selectedValue === "0" || selectedValue === "") { - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); + if (!selectedValue || selectedValue === '0') { + setInftwtAnlsInfos(INITIAL_ANALYSIS_OPTIONS); return; } - // API 호출 try { const data = await fn.selectInftwtAnls(selectedValue); - setInftwtAnlss(data ?? []); - setInftwtAnlsInfos(fn.getOptionsInftwtAnlsInfo(data)); + const list = data ?? []; + setInftwtAnlss(list); + setInftwtAnlsInfos(fn.getOptionsInftwtAnlsInfo(list)); } catch (error) { - console.error("selectInftwtAnls 에러:", error); + console.error('selectInftwtAnls 에러:', error); setInftwtAnlss([]); - setInftwtAnlsInfos([{ val: 0, text: '선택' }]); + setInftwtAnlsInfos(INITIAL_ANALYSIS_OPTIONS); } - setSelectedInftwtAnls(0); }; - // 분석정보 변경 핸들러 const handleInftwtAnlsSnChange = (val: string | number) => { const selectedValue = Number(val); - setSelectedInftwtAnls(selectedValue); - // 선택된 침입수분석 정보에 대한 처리 로직 + setSelectedInftwtAnls(selectedValue); + setChartData({ chartData1: [], chartData2: [] }); + setInftwtQty('0'); + if (selectedValue === 0) { setInftwtAnls(null); return; } - const selectedAnls = inftwtAnlss.find(anls => anls.inftwtAnlsSn === selectedValue); - if (selectedAnls) { - setInftwtAnls(selectedAnls); + const selectedAnls = inftwtAnlss.find((anls) => anls.inftwtAnlsSn === selectedValue) ?? null; + setInftwtAnls(selectedAnls); + }; + + const infilRsltChart = async () => { + if (!selectedAplcnYm || selectedInftwtAnls === 0 || !normalizedPplMngNo) { + setChartData({ chartData1: [], chartData2: [] }); + setInftwtQty('0'); + return; + } + + try { + const [inftwtAnlsRslt, oilq, qty] = await Promise.all([ + fn.selectInftwtAnlsRslt(selectedInftwtAnls, normalizedPplMngNo), + fn.selectOilq(selectedInftwtAnls, normalizedPplMngNo).catch((err) => { + console.error('selectOilq 에러:', err); + return []; + }), + fn.selectPplInftwtQty(selectedInftwtAnls, normalizedPplMngNo).catch((err) => { + console.error('selectPplInftwtQty 에러:', err); + return 0; + }), + ]); + + setChartData({ + chartData1: fn.getChartDataInftwtAnlsRslt(inftwtAnlsRslt ?? []), + chartData2: fn.getChartDataOilq(oilq ?? []), + }); + + const normalizedQty = Number(qty ?? 0); + setInftwtQty(normalizedQty.toLocaleString('ko-KR')); + } catch (error) { + console.error('infilRsltChart 에러:', error); + setChartData({ chartData1: [], chartData2: [] }); + setInftwtQty('0'); } }; - const closePopup = () => { // 팝업 닫기 - setPopupOpen(false); - } - const infilRsltChart = () => { // 그래프 & 팝업 닫기 + const renderInfoInput = (label: string, value?: string | number | null) => ( +
+ + +
+ ); - fn.selectInftwtAnlsRslt(selectedInftwtAnls, pplMngNo).then((data) => {// 침입수분석 결과 조회 - const rsltData = fn.getChartDataInftwtAnlsRslt(data ?? []); - setChartData(prev => ({ - ...prev, - chartData1: rsltData // 침입수분석결과 - })); - }); - fn.selectOilq(selectedInftwtAnls, pplMngNo).then((data) => {// 유량 조회 - const rsltData = fn.getChartDataOilq(data ?? []); - setChartData(prev => ({ - ...prev, - chartData2: rsltData // 유량 - })); - }).catch((err) => { - console.error("selectOilq 에러:", err); - setChartData(prev => ({ - ...prev, - chartData2: [] // 유량 데이터 초기화 - })); - }); - fn.selectPplInftwtQty(selectedInftwtAnls, pplMngNo).then((data) => {// 침입수량 조회 - if (data > 0) { - setInftwtQty(data.toLocaleString('ko-KR')); - } - }); - setPopupOpen(false); - }; + const handleReset = () => { + resetState(); + } const renderBody = () => ( - <> -
침입수분석결과
- -
-
- - {/* 상수사용량 적용연월 */} -
- ● 상수사용량 적용연월 -
- - {(selectedAplcnYm) ? moment(selectedAplcnYm, "YYYYMM").format("YYYY-MM") : ""} - -
-
- {/* 침입수량 분석결과 */} -
- ● 침입수량 산정결과 -
- 침입수량 : - {inftwtQty} - m³/d -
-
-
- {/* 차트 */} -
- -
+
+
+
- {popupOpen && ( -
-
침입수분석 입력현황
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - + +
+
+

+ 침입수 분석 선택 조건 +

+ +
+
+
+ + +
+ +
+ + +
+ + {renderInfoInput('오수전환율:', inftwtAnls?.drtwSwtcRt ?? '')} + {renderInfoInput('시간패턴 파일명:', inftwtAnls?.atflHrPttnNm ?? '')} + {renderInfoInput('수용가-맨홀 매칭:', inftwtAnls?.atflMpngNm ?? '')} + {renderInfoInput('관망도 파일명:', inftwtAnls?.atflInpNm ?? '')} + {renderInfoInput('유량 파일명:', inftwtAnls?.atflOilqNm ?? '')}
-
- - + +
+    +
-
)} - {/* */} - - ) +
+ +
+

침입수 분석 결과

+ +
+
+
+ {/* ● */} + 상수사용량 적용연월 +
+
+ {selectedAplcnYm ? moment(selectedAplcnYm, 'YYYYMM').format('YYYY-MM') : '-'} +
+
+ +
+
+ {/* ● */} + 침입수량 산정결과 +
+
+ 침입수량 +
+ {inftwtQty} + m³/d +
+
+
+
+ +
+ {hasChartData ? ( + + ) : ( +
+ 데이터가 없습니다. +
+ )} +
+
+
+
+ ); return ( { onOpenChange={setIsModalOpen} headerTitle="" bodyClass={` - min-h-[50vh] - w-[55vw] - max-w-none - max-h-none - overflow-visible - z-[50] - `} + w-[78vw] + max-w-none + min-w-[1000px] + min-h-[72vh] + overflow-visible + z-[50] + `} body={renderBody()} - noFooterYn='Y' + noFooterYn="Y" /> - ) -} + ); +}; -export default React.memo(InfilChart); \ No newline at end of file +export default React.memo(InfilChart); diff --git a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx index 39617eb21..aa622be1b 100644 --- a/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx +++ b/apps/sn/app/(map-layout)/map/components/popupComponent/chart/rdiiChart.tsx @@ -1,282 +1,339 @@ 'use client' -import { CHART_FORM, type ChartConfig, MainChart, UiModal } from "@common/business_components"; + +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 React, { useEffect, useState } from 'react'; -import moment from "moment"; import type { TRdiiAnls } from './aplcnYms'; import * as fn from './aplcnYms'; -import { useRdiiChartModalStore } from '../chartModalStore'; // 모달 상태 관리 스토어 +import { useRdiiChartModalStore } from '../chartModalStore'; type ModalOpenProp = { - pplMngNo: string, // 관로관리번호 -} -// 차트 -type lineBarChartData = { - label : string; - lineVal0 : number; - barVal0 : number; -} + pplMngNo?: string; // 관로관리번호 +}; +type LineBarChartData = { + label: string; + lineVal0: number; + barVal0: number; +}; -const RdiiChart = ({ pplMngNo }: ModalOpenProp) => { - const { isOpen: isModalOpen, setIsOpen: setIsModalOpen } = useRdiiChartModalStore(); // 모달 상태 관리 스토어 - const [popupOpen, setPopupOpen] = React.useState(false); // 조회조건팝업 - const buttonRef = React.useRef(null); // 버튼 참조 - const [popupPosition, setPopupPosition] = useState<{ top: number; left: number }>({ top: 0, left: 0 }); // 팝업 위치 - const [aplcnYm, setAplcnYm] = useState(""); +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([]); - const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>([{ val: 0, text: '선택' }]); // 강우유입수분석정보 options + const [rdiiAnlsInfos, setRdiiAnlsInfos] = useState<{ text: string; val: number; hidden?: boolean }[]>( + INITIAL_ANALYSIS_OPTIONS, + ); const [rdiiAnls, setRdiiAnls] = useState(null); // 강우유입수분석정보 - const [rainInfwtAnlsSn, setRainInfwtAnlsSn] = useState(0); // 강우유입수분석 정보 선택시 사용 - const [chartData, setChartData] = useState([]); - const [rdiiQty, setRdiiQty] = useState("0"); // 강우유입수량 - const [rainTotal, setRainTotal] = useState("0"); // 적용강우량 - const [totalVolume, setTotalVolume] = useState("0"); // 총체적 m³/mm + const [rainInfwtAnlsSn, setRainInfwtAnlsSn] = useState(0);// 강우유입수분석 정보 선택시 사용 + const [chartData, setChartData] = useState([]); + const [rdiiQty, setRdiiQty] = useState('0'); // 강우유입수량 + const [rainTotal, setRainTotal] = useState('0'); // 적용강우량 + const [totalVolume, setTotalVolume] = useState('0');// 총체적 m³/mm + const aplcnYms = fn.useAplcnYms('rdiiAnlsRslt'); // 상수사용월 데이터 가져오기 - pplMngNo = "L"+pplMngNo; // "L13101003"; - - const openPopup = () => { - if (buttonRef.current) { - const rect = buttonRef.current.getBoundingClientRect(); // 버튼 위치 계산 - setPopupPosition({ - top: rect.bottom + window.scrollY, // 버튼 아래쪽 - left: rect.left + window.scrollX, // 버튼 왼쪽 - }); - } - setPopupOpen(true); // 팝업 열기 - }; + const normalizedPplMngNo = pplMngNo ? `L${pplMngNo}` : ''; const chartForm = CHART_FORM.LINEBAR2; const chartConfig = { - barVal0: { + barVal0: { label: '강수량', - color: "#33AAFF", + color: '#33AAFF', }, lineVal0: { label: 'RDII', - color: "#FF0000", + 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 === false) { - setPopupOpen(true); // 모달이 닫힐 때 팝업은 - setAplcnYm(""); // 상수사용월 초기화 - setRdiiAnlss([]); // 강우유입수분석 초기화 - setRdiiAnlsInfos([{ val: 0, text: '선택' }]); // 강우유입수분석정보 초기화 - setRdiiAnls(null); // 강우유입수분석정보 초기화 - setRainInfwtAnlsSn(0); // 강우유입수분석 일련번호 초기화 - setChartData([]); // 차트 데이터 초기화 - setRdiiQty("0"); // 강우유입수량 초기화 - setRainTotal("0"); // 적용강우량 초기화 - setTotalVolume("0"); // 총체적 m³/mm 초기화 + if (!isModalOpen) { + resetState(); } }, [isModalOpen]); - // 상수사용월 변경시 분석 정보 리스트 가져오기 - useEffect(() => { - // aplcnYm이 비어있거나 "0"이면 API 호출하지 않음 - if (!aplcnYm || aplcnYm === "0" || aplcnYm === "") { - setRainInfwtAnlsSn(0); // 초기화 - setRdiiAnlsInfos([]); + 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; } - fn.selectRdiiAnls(aplcnYm) - .then((data) => { - setRdiiAnlss(data ?? []); // 선택한 월의 분석성공한 RDII분석데이터[] - setRdiiAnlsInfos(fn.getOptionsRdiiAnlsInfo(data)); - setRdiiAnls(null); // 오수전환율 이후 초기화 - setRainInfwtAnlsSn(0); // 초기화 - }) - .catch((error) => { - console.error("selectrdiiAnls 에러:", error); - setRdiiAnlss([]); - setRdiiAnlsInfos([]); - }); - }, [aplcnYm]); - // 값 표시 - useEffect(() => { - // 선택된 강우유입수분석 정보에 대한 처리 로직 - if (rainInfwtAnlsSn === 0) return; - const selectedAnls = rdiiAnlss.find(anls => anls.rainInfwtAnlsSn === rainInfwtAnlsSn); - if (selectedAnls) { - setRdiiAnls(selectedAnls); + 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); } - }, [rainInfwtAnlsSn, rdiiAnlss]); - - const closePopup = () => { // 팝업 닫기 - setPopupOpen(false); - } - const rdiiRsltChart = () => { // 그래프 & 팝업 닫기 - fetchFilteredData(); - fn.selectPplRdiiQty(rainInfwtAnlsSn, pplMngNo).then((data) => {// 강우유입수량 조회 - setRdiiQty(Math.round(data?.rdiih*24).toLocaleString('ko-KR')); // cmd: m³/d 단위로 변환 - if (data?.raintotal && data?.rainTotal !== 0) { - setRainTotal(data?.raintotal.toLocaleString('ko-KR')); // 총강우량mm - setTotalVolume(Math.round(data?.rdiih * data?.sustaintime / data?.raintotal).toLocaleString('ko-KR')); // 총체적 m³/mm - } - }); - setPopupOpen(false); }; - const fetchFilteredData: () => Promise = async () => { - const rsltData = await fn.selectRdiiAnlsRslt(rainInfwtAnlsSn, pplMngNo); - const rsltDataformatted = fn.getChartDataRdiiAnlsRslt(rsltData ?? []); // format 변경 - const rainData = await fn.selectRainData(rainInfwtAnlsSn); - const rainDataformatted = fn.getChartDataRainData(rainData ?? []); // format 변경 - const rainMap = new Map(rainDataformatted.map(item => [item.label, item.barVal0])); + 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) => ( +
+ + +
+ ); - rsltDataformatted.forEach(item => { - const matchedVal = rainMap.get(item.label); - if (matchedVal !== undefined) { - item.barVal0 = matchedVal; - } - }); - setChartData(rsltDataformatted); // 차트 데이터 설정 - } const renderBody = () => ( - <> -
RDII분석결과
- -
-
- - {/* 상수사용량 적용연월 */} -
- ● 상수사용량 적용연월 -
- - {(aplcnYm)? moment(aplcnYm, "YYYYMM").format("YYYY-MM"):""} - -
-
- - {/* 강우유입수량 분석결과 */} -
- ● 강우유입수량 분석결과 -
- 평균RDII : - {rdiiQty} - m³/d -
-
- 적용강우량 : - {rainTotal} - mm -
-
- 강우량대비RDII : - {totalVolume} - m³/mm -
-
-
- {/* 차트 */} -
- -
+
+
+
- {popupOpen && ( -
-
강우유입수분석 입력현황
-
-
- - { - setAplcnYm(String(val)); - }} - /> -
-
- - { - setRainInfwtAnlsSn(Number(val)); - }} - /> -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - + +
+
+

+ 강우유입수분석 선택 조건 +

+ +
+
+
+ + +
+ +
+ + +
+ + {renderInfoInput('오수전환율:', rdiiAnls?.drtwSwtcRt ?? '')} + {renderInfoInput('수용가-맨홀 매칭:', rdiiAnls?.atflMpngNm ?? '')} + {renderInfoInput('관망도 파일명:', rdiiAnls?.atflInpNm ?? '')} + {renderInfoInput('강우 파일명:', rdiiAnls?.atflRainNm ?? '')} + {renderInfoInput('RTK계수 출처:', rdiiAnls?.srcNm ?? '')}
-
- - + +
+ +    +
-
)} - {/* */} - - ) +
+ +
+

+ 강우유입수분석 결과 +

+ +
+ {/* 좌측 카드 */} +
+
+ 상수사용량 적용연월 +
+
+ {aplcnYm ? moment(aplcnYm, 'YYYYMM').format('YYYY-MM') : '-'} +
+
+ + {/* 우측 카드 */} +
+ {/* 좌측: 제목 (세로 가운데 정렬) */} +
+
+ 강우유입수량 분석결과 +
+
+ + {/* 우측: 지표 영역 */} +
+
+
+ 평균RDII +
+
+ {rdiiQty} +
+
+ m³/d +
+
+ +
+
+ 적용 강우량 +
+
+ {rainTotal} +
+
+ mm +
+
+ +
+
+ 강우량 대비 RDII +
+
+ {totalVolume} +
+
+ m³/mm +
+
+
+
+
+ +
+ {hasChartData ? ( + + ) : ( +
+ 데이터가 없습니다. +
+ )} +
+
+
+
+ ); return ( { onOpenChange={setIsModalOpen} headerTitle="" bodyClass={` - min-h-[50vh] - w-[55vw] - max-w-none - max-h-none - overflow-visible - z-[50] - `} + w-[78vw] + max-w-none + min-w-[1100px] + min-h-[72vh] + overflow-visible + z-[50] + `} body={renderBody()} - noFooterYn = 'Y' + noFooterYn="Y" /> - ) -} + ); +}; -export default React.memo(RdiiChart); \ No newline at end of file +export default React.memo(RdiiChart); diff --git a/apps/sn/app/(map-layout)/map/page.tsx b/apps/sn/app/(map-layout)/map/page.tsx index 73f8ae8c7..75b2d573e 100644 --- a/apps/sn/app/(map-layout)/map/page.tsx +++ b/apps/sn/app/(map-layout)/map/page.tsx @@ -171,6 +171,7 @@ const Map = () => { setIsPlaying={setIsPlaying as any} targetLayerTypes={WATER_DEPTH_LAYER_TYPES as any} checkInterval={2500} + showZoomLevel={true} defaultZoom={7.5} defaultLonLat={[127.75, 36]} diff --git a/apps/sn/public/images/map/layericon/RDL_TSCT_PS500.png b/apps/sn/public/images/map/layericon/RDL_TSCT_PS500.png new file mode 100644 index 000000000..b82e2ff89 Binary files /dev/null and b/apps/sn/public/images/map/layericon/RDL_TSCT_PS500.png differ diff --git a/apps/wr/app/(map-layout)/map/mainMap/page.tsx b/apps/wr/app/(map-layout)/map/mainMap/page.tsx index 927703ad9..652fe1924 100644 --- a/apps/wr/app/(map-layout)/map/mainMap/page.tsx +++ b/apps/wr/app/(map-layout)/map/mainMap/page.tsx @@ -388,6 +388,7 @@ const Map = () => { return (
{ <>
state); const { coMatRvwRes, coMatRvwCount } = coRvw; const { regAuthrtYn } = auth; + const alert = useAlertStore((state) => state.setMessage); //init, sub, match const initRef = useRef(false); @@ -253,7 +254,7 @@ export default function CoMatRvw() { const fromNum = Number(fromDate); const toNum = Number(toDate); if (fromNum > toNum) { - toast.error( + alert( "시작년도가 종료년도 이후로 설정할 수 없습니다." ); } else { diff --git a/apps/wr/app/(with-layout)/matMng/coMat/ruseCaseMng/page.tsx b/apps/wr/app/(with-layout)/matMng/coMat/ruseCaseMng/page.tsx index d76549153..d315b2c25 100644 --- a/apps/wr/app/(with-layout)/matMng/coMat/ruseCaseMng/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMat/ruseCaseMng/page.tsx @@ -21,8 +21,9 @@ import { } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button, Label } from "@common/components/ui"; +import { useAlertStore } from "@/src/shared/lib"; import { useEffect, useState } from "react"; -import toast, { Toaster } from "react-hot-toast"; +import { Toaster } from "react-hot-toast"; import { useTranslation } from "react-i18next"; import * as XLSX from "xlsx"; @@ -44,6 +45,7 @@ export default function RuseCaseMng() { const [name, setName] = useState(""); const [resetPage, isResetPage] = useState(false); const { t } = useTranslation("base"); + const alert = useAlertStore((state) => state.setMessage); // 엑셀 등록 모달 const [isExcelOpen, setIsExcelOpen] = useState(false); @@ -84,7 +86,7 @@ export default function RuseCaseMng() { if (!excelList) return; if (excelList.length === 0) { - toast.error(t("excelDownErrMsg")); + alert(t("excelDownErrMsg")); return; } fetch(`/sample_ruseCaseMng.xlsx`, {}).then(async (response) => { diff --git a/apps/wr/app/(with-layout)/matMng/coMat/ruseRvwHstry/page.tsx b/apps/wr/app/(with-layout)/matMng/coMat/ruseRvwHstry/page.tsx index 9b7737236..782becca9 100644 --- a/apps/wr/app/(with-layout)/matMng/coMat/ruseRvwHstry/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMat/ruseRvwHstry/page.tsx @@ -24,9 +24,10 @@ import { } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button, Label } from "@common/components/ui"; +import { useAlertStore } from "@/src/shared/lib"; import moment from "moment"; import { useCallback, useEffect, useState } from "react"; -import toast, { Toaster } from "react-hot-toast"; +import { Toaster } from "react-hot-toast"; import { useTranslation } from "react-i18next"; export default function RuseRvwHstry() { const { @@ -40,6 +41,7 @@ export default function RuseRvwHstry() { } = useWrStore((state) => state); const { rvwHistRes, ruseHistHeadList, rvwHistCount } = ruseHist; const { regAuthrtYn } = auth; + const alert = useAlertStore((state) => state.setMessage); const [pageNation, setPageNatiion] = useState(1); @@ -79,7 +81,7 @@ export default function RuseRvwHstry() { const fromNum = Number(fromDate); const toNum = Number(toDate); if (fromNum > toNum) { - toast.error("시작년도가 종료년도 이후로 설정할 수 없습니다."); + alert("시작년도가 종료년도 이후로 설정할 수 없습니다."); } else { let con: string | number = ""; if (String(objSearch.city).length > 0) { @@ -133,7 +135,7 @@ export default function RuseRvwHstry() { selectedData[index]?.splyFcltFlg === "매칭완료" && headId === "completeYn" ) { - toast.error(t("canNotChangeMatStateMsg")); + alert(t("canNotChangeMatStateMsg")); return; } } @@ -177,7 +179,7 @@ export default function RuseRvwHstry() { // // )); } else { - toast.error( t('isNotMatchFinishMsg')); + alert(t('isNotMatchFinishMsg')); } }; useEffect(() => { diff --git a/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx index f5104154f..85d9ef635 100644 --- a/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMng/coSttn/page.tsx @@ -5,7 +5,7 @@ import { btnClass } from "@/app/store/common/code"; import { excelCoSttnParsing } from "@/app/store/matDmd/coSttn/excel"; import { fetchType, getFetch } from "@/components/lib/api"; import { - confirmToast, + //confirmToast, excelDownload, formatPhoneNumber, getCity, @@ -29,8 +29,10 @@ import type { DemandCompType } from "@common/business_components/ui/sdmInsertFor import { Button } from "@common/components/ui"; import moment from "moment"; import { useCallback, useEffect, useState } from "react"; -import toast, { Toaster } from "react-hot-toast"; +// import toast, { Toaster } from "react-hot-toast"; import { useTranslation } from "react-i18next"; +import { useAlertStore } from "@/src/shared/lib"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; export default function CoSttn() { const { @@ -48,7 +50,8 @@ export default function CoSttn() { getMyUUID } = useWrStore((state) => state); const { regAuthrtYn, mdfcnAuthrtYn } = auth; - + const alert = useAlertStore((state) => state.setMessage); + const { confirm } = useConfirm(); const [bdstTable, setBdstTable] = useState(); const [pageNation, setPageNation] = useState(1); const { coSttnRes, coSttnResCount } = coSttn; @@ -98,7 +101,7 @@ export default function CoSttn() { useEffect(() => { if (!coSttn.isInit) { - coSttnInit("1", "13", "0"); + coSttnInit("1", "13", "0"); // 수요처 목록 조회 } }, [coSttn.isInit, coSttnInit]); @@ -167,16 +170,21 @@ export default function CoSttn() { } }; const { t } = useTranslation('base'); - const initPassword = () => { + const initPassword = async () => { if (updateData) { const dmdInfoId = updateData.dmdInfoId; const dmdInfoSliceId = `${dmdInfoId.slice(0, 3)}-${dmdInfoId.slice(3, 5)}-${dmdInfoId.slice(5, 10)}-${dmdInfoId.slice(10)}` - const handleConfirm = () => { - coSttnPwInit(dmdInfoId); + const handleConfirm = async () => { + const message = await coSttnPwInit(dmdInfoId); + alert(message); }; - confirmToast( t('isInitPw', { dmdInfoId: dmdInfoSliceId }), handleConfirm, 'text-2xl'); + //confirmToast( t('isInitPw', { dmdInfoId: dmdInfoSliceId }), handleConfirm, 'text-2xl'); + if (await confirm(t('isInitPw', { dmdInfoId: dmdInfoSliceId }))) { + await handleConfirm(); + } } else { - toast.error("비밀번호 초기화를 원하는 행을 선택해주세요.") + alert("비밀번호 초기화를 원하는 행을 선택해주세요."); + // toast.error("비밀번호 초기화를 원하는 행을 선택해주세요.") } } @@ -238,7 +246,7 @@ export default function CoSttn() { onClick={() => { /* 저장 로직 */ if (!updateData) { - toast.error("수요처 변경을 원하는 행을 선택해주세요"); + alert("수요처 변경을 원하는 행을 선택해주세요"); } else { setIsDmdModal({ ...isDmdModal, @@ -274,8 +282,8 @@ export default function CoSttn() { headName={[]} tableData={bdstTable || []} headlist={coSttn.coSttHeadList} - pageSize={20} - total={coSttnResCount + 10 || 0} + pageSize={13} + total={coSttnResCount || 0} cellClick={ async(index: number | undefined) => { const uuid = await getMyUUID(); const updateData = coSttnRes[index as number]; @@ -319,7 +327,8 @@ export default function CoSttn() { ? objSearch.sgg : objSearch.city; if (updateData?.dmdInfoId) { - await updateDemandCompany(data as DemandCompType); + const message = await updateDemandCompany(data as DemandCompType); + alert(message); coSttnInit("1", "13", String(con)); setIsDmdModal({ isMoal: false, @@ -332,12 +341,11 @@ export default function CoSttn() { `/reuseFc/request/${data.dmdInfoId}` ); if (dmd) { - toast.error( - `사업자번호 ${dmd.dmdInfoId}는 이미 등록되었습니다. 다시등록해주세요` - ); + alert(`사업자번호 ${dmd.dmdInfoId}는 이미 등록되었습니다. 다시등록해주세요`); return; } - await regDemandCompany(data as DemandCompType); + const message = await regDemandCompany(data as DemandCompType); + alert(message); coSttnInit("1", "13", String(con)); setIsDmdModal({ isMoal: false, @@ -352,7 +360,7 @@ export default function CoSttn() { } > - + {/* */} ); } diff --git a/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx index b55878487..b4ca64268 100644 --- a/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMng/drgtSttn/page.tsx @@ -9,12 +9,14 @@ import type { objSearchType } from "@/components/ui/modal/fcltReg/bdstRegModal/b import { ContentsDiv, DateMonthPicker, downloadExcel, SearchDiv, SearchFormLeft, SearchFormRight, SelectBox, UiModal, UiTable, type SelectDataType } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button, Label, ScrollArea } from "@common/components/ui"; +import { useAlertStore } from "@/src/shared/lib"; import { useEffect, useState, useRef } from "react"; -import { toast, Toaster } from "react-hot-toast"; +import { Toaster } from "react-hot-toast"; export default function DrgtSttn() { const { code, drgt, drgtSearch, drgtModalSearch, drgtInit } = useWrStore((state) => state); const { drgtRes, drgtMoRes } = drgt; + const alert = useAlertStore((state) => state.setMessage); const [fromDate, setFromDate] = useState('202506'); const [toDate, setToDate] = useState('202506'); const [selectSgg, setSelectSgg] = useState([{ val: '0', text: '전체' }]); @@ -32,7 +34,7 @@ export default function DrgtSttn() { if (fromDate && toDate && drgtRes[index]) { drgtModalSearch(fromDate, toDate, drgtRes[index].sggNm ?? ''); } else { - toast.error("조회기간을 선택해주세요."); + alert("조회기간을 선택해주세요."); } } diff --git a/apps/wr/app/(with-layout)/matMng/coMng/ldrmntCoSttn/page.tsx b/apps/wr/app/(with-layout)/matMng/coMng/ldrmntCoSttn/page.tsx index eee7d9f73..13b596b5c 100644 --- a/apps/wr/app/(with-layout)/matMng/coMng/ldrmntCoSttn/page.tsx +++ b/apps/wr/app/(with-layout)/matMng/coMng/ldrmntCoSttn/page.tsx @@ -27,8 +27,9 @@ import { } from "@common/business_components/ui"; import ListDiv from "@common/business_components/ui/list_div"; import { Button } from "@common/components/ui"; +import { useAlertStore } from "@/src/shared/lib"; import { useCallback, useEffect, useState } from "react"; -import toast, { Toaster } from "react-hot-toast"; +import { Toaster } from "react-hot-toast"; import { useTranslation } from "react-i18next"; export default function LdrmntCoSttn() { @@ -46,6 +47,7 @@ export default function LdrmntCoSttn() { const [resetPage, isResetPage] = useState(false); const [isDownloadClicked, setIsDownloadClicked] = useState(false); const { t } = useTranslation('base'); + const alert = useAlertStore((state) => state.setMessage); const { regAuthrtYn, mdfcnAuthrtYn } = auth; const excelClick = useCallback(() => { @@ -113,7 +115,7 @@ export default function LdrmntCoSttn() { headerRange: "A1:F1" }); } else { - toast.error(t('excelDownErrMsg')); + alert(t('excelDownErrMsg')); } setIsDownloadClicked(false); }, [excelData, isDownloadClicked]); @@ -270,7 +272,8 @@ export default function LdrmntCoSttn() { readonly={mdfcnAuthrtYn === 'N'} onFormSubmit={async (value) => { const handleConfirm = async () => { - await ldrmntUpdate(value); + const message = await ldrmntUpdate(value); + alert(message); setDetailModal({ ...detailModal, isOpen: false }); ldrmntSearch(false, pageNation, '20', computeCityNm(), searchTxt || ""); }; diff --git a/apps/wr/app/(with-layout)/ruseFcltMng/fcltReg/pbnstReg/page.tsx b/apps/wr/app/(with-layout)/ruseFcltMng/fcltReg/pbnstReg/page.tsx index 396edb8f0..9ad16e84d 100644 --- a/apps/wr/app/(with-layout)/ruseFcltMng/fcltReg/pbnstReg/page.tsx +++ b/apps/wr/app/(with-layout)/ruseFcltMng/fcltReg/pbnstReg/page.tsx @@ -44,13 +44,14 @@ import { import { PbnstMngForm } from "@/components/ui/form/fcltReg/pbnstReg/mngReg/mngRegForm"; import { PbnstBuseForm } from "@/components/ui/form/fcltReg/pbnstReg/buseReg/bugRegForm"; import { PbnstRegModal } from "@/components/ui/modal/fcltReg/pbnstRegModal/pbnstRegModal"; -import toast, { Toaster } from "react-hot-toast"; // toast 이미 import 되어 있음 +import { Toaster } from "react-hot-toast"; import { writeRgtrAndMdfr } from "@/app/store/ruseFcltMng/fcltReg/pbnstReg"; import { useFieldArray, useForm, type FieldErrors } from "react-hook-form"; // FieldErrors 타입 추가 import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { createDefaultPbnstItem } from "./utilsZod"; import { useTranslation } from "react-i18next"; +import { useAlertStore } from "@/src/shared/lib"; export default function PublicReuseFaciltyInsert() { const { auth, @@ -66,6 +67,7 @@ export default function PublicReuseFaciltyInsert() { } = useWrStore((state) => state); const { t } = useTranslation("base"); + const alert = useAlertStore((state) => state.setMessage); const { pubFacHeadList, pbnstHasuResCount, pbnstHasuRes, pbnstRes } = pbnst; const { regAuthrtYn, mdfcnAuthrtYn, laeSeCd } = auth; const [, setCurrentTab] = useState("mng"); @@ -140,7 +142,7 @@ export default function PublicReuseFaciltyInsert() { const isFcltSuccess = resFclt.status === "fulfilled"; const isBizSuccess = resBiz.status === "fulfilled"; if (isFcltSuccess && isBizSuccess) { - toast.success("하수 재이용 시설 및 사업이 수정되었습니다. "); + alert("하수 재이용 시설 및 사업이 수정되었습니다."); // 메시지 표시, 페이지 이동 등 } else { let message = ""; @@ -151,7 +153,7 @@ export default function PublicReuseFaciltyInsert() { if (!isBizSuccess) { message += "하수재이용 시설 사업 "; } - toast.error(message + "오류 입니다 "); + alert(message + "오류 입니다 "); pubHasuInit("1", laeSeCd); } }); @@ -215,7 +217,7 @@ export default function PublicReuseFaciltyInsert() { } } // 최종 오류 메시지 출력 - toast.error(`${prefix}${message}`); + alert(`${prefix}${message}`); }); } }; @@ -321,8 +323,8 @@ export default function PublicReuseFaciltyInsert() { onClick={() => { // pbnstMngData([] as customPbnst[]); if (!selectRow) { - toast.error( - "하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요" + alert( + "재시용시설을 등록할 하수리시시설을 테이블에서 선택하고 등록버튼을 눌러주세요" ); return; } else { @@ -416,7 +418,7 @@ export default function PublicReuseFaciltyInsert() { onClick={() => { // pbnstMngData([] as customPbnst[]); if (!selectRow) { - toast.error( + alert( "하수리시시설을 테이블을 선택하고 등록버튼을 눌러주세요" ); return; diff --git a/apps/wr/app/store/common/searchCode/index.ts b/apps/wr/app/store/common/searchCode/index.ts index 03d1b0fc5..1478b10a8 100644 --- a/apps/wr/app/store/common/searchCode/index.ts +++ b/apps/wr/app/store/common/searchCode/index.ts @@ -7,7 +7,6 @@ import { import type { pubHasuType } from "../../ruseFcltMng/fcltReg/pbnstReg/type"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import type { conCostFeeType, rateFeeType } from "./type"; -import toast from "react-hot-toast"; export const comFcltStateExport: comFcltState = { @@ -59,8 +58,8 @@ export type comFcltState = { type comFcltActions = { getSwtrmtFcltNm: (searchNm: string) => void; getDemandCompList: (searchNm: string, cnt: number) => void; - regDemandCompany: (data: DemandCompType) => void; - updateDemandCompany: (data: DemandCompType) => void; + regDemandCompany: (data: DemandCompType) => Promise; + updateDemandCompany: (data: DemandCompType) => Promise; getConstCostFee: (year: string) => void; getRateFee: (year: string) => void; }; @@ -109,7 +108,6 @@ export const comFcltSearchAction: (set: any) => comFcltActions = (set: any) => { fetchType.WR ); if (response.status === 200) { - toast.success('수요처가 등록되었습니다. ') set((state: comFcltState) => { return { @@ -117,11 +115,12 @@ export const comFcltSearchAction: (set: any) => comFcltActions = (set: any) => { dmdIsSucees: "success" } }); + return "수요처가 등록되었습니다."; } + return "수요처 등록에 실패하였습니다."; } catch (err) { console.log(err); - toast.error('수요처가 등록 오류입니다.. ') - + return "수요처 등록 중 오류가 발생했습니다."; } }, updateDemandCompany: async (data: DemandCompType) => { @@ -131,7 +130,6 @@ export const comFcltSearchAction: (set: any) => comFcltActions = (set: any) => { let message = ""; if (response.status === 200) { message = "success"; - toast.success('수요처 정보가 변경되었습니다. ' ) } else { message = "fail"; } @@ -142,10 +140,13 @@ export const comFcltSearchAction: (set: any) => comFcltActions = (set: any) => { dmdIsSucees: message } }); + if (response.status === 200) { + return "수요처 정보가 변경되었습니다."; + } + return "수요처 정보 변경에 실패하였습니다."; } catch (err) { console.log(err); - toast.success('수요처 정보가 오류입니다. ' ) - + return "수요처 정보 변경 중 오류가 발생했습니다."; } }, getRateFee: async (year: string) => { diff --git a/apps/wr/app/store/matDmd/coMatRvw/index.tsx b/apps/wr/app/store/matDmd/coMatRvw/index.tsx index 9e857d4d3..0c58db9a6 100644 --- a/apps/wr/app/store/matDmd/coMatRvw/index.tsx +++ b/apps/wr/app/store/matDmd/coMatRvw/index.tsx @@ -1,5 +1,4 @@ import { fetchType, getFetchWithCount, putFectch } from "@/components/lib/api"; -import toast from "react-hot-toast"; import type { CommonTableProps } from "../../common/type/header"; import { constCostHeadListExport, @@ -50,7 +49,7 @@ type coRvwActions = { splyCd: string, dmdCd: string, data: DedMatUpdateType - ) => void; + ) => Promise; coMatRvwSearch: ( pageNo: number, pageCnt: number, @@ -104,20 +103,17 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { splyCd: string, dmdCd: string, data: DedMatUpdateType - ) => { + ): Promise => { try { const response = await putFectch( `/reuseFc/request/cst/${splyCd}/${dmdCd}`, data ); if (response.status === 200) { - - if(data.flg ==='Sub'){ - - toast.success("매칭 검토로 저장되었습니다."); - }else{ - toast.success("매칭 검토완료로 저장되었습니다."); - } + const successMessage = + data.flg === "Sub" + ? "매칭 검토로 저장되었습니다." + : "매칭 검토완료로 저장되었습니다."; const coMatRvwRes = await coMatInit( 1, @@ -125,21 +121,22 @@ export const coRvwActionsExport: (set: any) => coRvwActions = (set: any) => { new Date().getFullYear().toString(), new Date().getFullYear().toString() ); - return set((state: coRvwState) => ({ + set((state: coRvwState) => ({ ...state, coRvw: { ...state.coRvw, coMatRvwRes: coMatRvwRes.data, - coMatRvwCount:coMatRvwRes.count, + coMatRvwCount: coMatRvwRes.count, init: false, }, })); + return successMessage; } else { - toast.error("매칭 검토 저장에 실패했습니다."); + return "매칭 검토 저장에 실패했습니다."; } } catch (e) { - toast.error("매칭 검토 저장 오류입니다."); console.error("coMatRvwUpdate error", e); + return "매칭 검토 저장 오류입니다."; } }, coMatRvwSearch: async ( diff --git a/apps/wr/app/store/matDmd/coSttn/index.tsx b/apps/wr/app/store/matDmd/coSttn/index.tsx index 1f7b2c54b..ca610c381 100644 --- a/apps/wr/app/store/matDmd/coSttn/index.tsx +++ b/apps/wr/app/store/matDmd/coSttn/index.tsx @@ -2,7 +2,6 @@ import type { CommonTableProps } from "@/app/store/common/type/header"; import { fetchType, getFetchWithCount, putFectch } from "@/components/lib/api"; import type { DemandCompType } from "@common/business_components/ui/sdmInsertForm/form"; import { coSttHeadListExport, coSttNodeListExport } from "./headList"; -import toast from "react-hot-toast"; // 테이블 헤드 리스트 const coSttNodeList = coSttNodeListExport; @@ -25,7 +24,7 @@ type coSttnActions = { coSttnInit: (pageNo: string, pageCnt: string, con: string) => void; coSttnSearch: (pageNo: string, pageCnt: string, con: string) => void; coSttnTxtSearch: (pageNo: string, pageCnt: string, con: string, txt: string) => void; - coSttnPwInit: (dmdInfoId: string) => void; + coSttnPwInit: (dmdInfoId: string) => Promise; }; export type coSttnType = coSttnState & coSttnActions; @@ -46,6 +45,7 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( set: any ) => { return { + // 수요처 목록 조회 coSttnInit: async (pageNo: string, pageCnt: string, con: string) => { try { @@ -126,10 +126,14 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( try { const response = await putFectch(`/reuseFc/request/chgPassword/${dmdInfoId}`,[]) if (response.status === 200) { - toast.success('비밀번호가 초기화 되었습니다.') + return "비밀번호가 초기화되었습니다."; } else { - toast.error('비밀번호 초기화에 실패하였습니다') + return "비밀번호 초기화에 실패하였습니다."; } + } catch (err) { + console.log(err); + return "비밀번호 초기화에 실패하였습니다."; + } finally { set((state: coSttnState) => { return { ...state, @@ -139,11 +143,7 @@ export const coSttnActionsExport: (set: any) => coSttnActions = ( } } }); - } catch (err) { - console.log(err); - toast.error('비밀번호 초기화에 실패하였습니다.') - } } } -} \ No newline at end of file +} diff --git a/apps/wr/app/store/matDmd/ldrmntCoSttn/headList.tsx b/apps/wr/app/store/matDmd/ldrmntCoSttn/headList.tsx index 294371c44..bb7c79024 100644 --- a/apps/wr/app/store/matDmd/ldrmntCoSttn/headList.tsx +++ b/apps/wr/app/store/matDmd/ldrmntCoSttn/headList.tsx @@ -72,7 +72,7 @@ export const ldrmntHeadListExport: CommonTableProps[] = [ }, { id: "fctryAddr", - title: "공장주소", + title: "회사주소", visible: true, row: 1, rowSpan: 1, diff --git a/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx b/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx index fed013417..8de791c89 100644 --- a/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx +++ b/apps/wr/app/store/matDmd/ldrmntCoSttn/index.tsx @@ -1,6 +1,5 @@ import type { CommonTableProps } from "@/app/store/common/type/header"; import { deleteFectch, fetchType, getFetchWithCount, postFectch, putFectch } from "@/components/lib/api"; -import toast from "react-hot-toast"; import { z } from "zod"; import { TEST_UUID } from "../../ruseFcltMng/fcltReg/rainReg"; import { ldrmntHeadListExport, ldrmntNodeListExport, ldrmntZod } from "./headList"; @@ -35,7 +34,7 @@ type ldrmntActions = { ldrmntRegData: (data: any[]) => void, ldrmntInit: (isExcel: boolean, con: string) => void ldrmntSearch: (isExcel: boolean, page: number, sggCd: string, total: string, coNm: string) => void; - ldrmntUpdate: (ldrmntRes: customLdrmnt) => void; + ldrmntUpdate: (ldrmntRes: customLdrmnt) => Promise; ldrmntDelete: () => void; loadingOpen: (isOpen: boolean) => void }; @@ -192,12 +191,14 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = ( ); if (res.ok) { - toast.success("잠재수요처 상세정보가 수정되었습니다.",) + return "잠재수요처 상세정보가 수정되었습니다."; } + return "잠재수요처 상세정보 수정에 실패하였습니다."; } catch (error) { console.error("Error ldrmntUpdate:", error); - toast.error("잠재수요처 상세정보 수정에 실패하였습니다.") + return "잠재수요처 상세정보 수정에 실패하였습니다."; } + return "잠재수요처 상세정보가 없습니다."; }, ldrmntDelete: async () => { try { @@ -228,4 +229,4 @@ export const ldrmntActionsExport: (set: any) => ldrmntActions = ( } }, } -} \ No newline at end of file +} diff --git a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx index 587e08fd9..49cc61d1f 100644 --- a/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx +++ b/apps/wr/app/store/matDmd/ruseCaseMng/index.tsx @@ -1,5 +1,4 @@ import { fetchType, getFetchWithCount, postFectch } from "@/components/lib/api"; -import { toast } from "react-hot-toast"; import type { TableUpperProps } from "../../common/type/header"; import { ruseCaseHeadListExport } from "./headList"; import type { ruseCaseMngExcelProps } from "./excel"; @@ -24,7 +23,7 @@ type ruseCaseActions = { usageCd: string, name: string ) => Promise; - insertRuseCaseMng: (data: any) => Promise; + insertRuseCaseMng: (data: any) => Promise; }; const fetchSearchRuseCaseMng = async ( @@ -131,11 +130,10 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( } }, - insertRuseCaseMng: async (data: any) => { + insertRuseCaseMng: async (data: any): Promise => { try { const response = await fetchInsertRuseCaseMng(data); if (response.status === 200) { - toast.success("물재이용사례 등록이 완료되었습니다. "); const res = await getFetchWithCount( fetchType.WR, `/reuseFc/pubFcRuseCase/1/10/0/0` @@ -147,11 +145,13 @@ export const ruseCaseActionsExport: (set: any) => ruseCaseActions = ( count: res.count, }, })); + return "물재이용사례 등록이 완료되었습니다."; } else { + return "물재이용사례 등록에 실패했습니다."; } } catch (e) { console.log(e); - toast.error("물재이용사례 등록에 실패하였습니다. "); + return "물재이용사례 등록에 실패했습니다."; } }, }; diff --git a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx index 186e0bb77..cfb388264 100644 --- a/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx +++ b/apps/wr/components/ui/form/coMat/conCostForm/conCostForm.tsx @@ -13,6 +13,7 @@ import { getInterpolation, transformAndInterpolate, } from "@/components/lib/func/calculateConstCost "; +import { useAlertStore } from "@/src/shared/lib"; import { ConstCostFeeTable } from "@/components/ui/modal/coMat/constCostFeeTable"; import { RateFeeTableModal } from "@/components/ui/modal/coMat/rateFeeModal"; import { @@ -53,6 +54,7 @@ export default function ConCostForm({ getRateFee, getConstCostFee, } = useWrStore((state) => state); + const alert = useAlertStore((state) => state.setMessage); const ref = useRef(false); @@ -108,11 +110,11 @@ export default function ConCostForm({ const calculateConstCost = useCallback(() => { const usgPrcsProcs = usgPrcsProcsWatch; if (!dmndQtyWatch) { - toast.error("요구수량(m³/일)을 확인해주세요 계산할수 없습니다. "); + alert("요구수량(m³/일)을 확인해주세요 계산할수 없습니다. "); return; } if (usgPrcsProcs === null || usgPrcsProcs === undefined) { - toast.error( + alert( "용도에 따른 처리공정을 확인할수 없습니다. 용도를 확인해주세요. " ); return; @@ -175,9 +177,9 @@ export default function ConCostForm({ form.setValue("totalConstCost", Math.round(realResult)); } }, [constCostRes, rateFeeRes, usgPrcsProcsWatch, dmndQtyWatch]); - const onSubmit = (value: DmdCoReqType) => { + const onSubmit = async (value: DmdCoReqType) => { if (value.demandCst && !value.demandCst.prcsFcltCpctCstrnCst) { - toast.error("공사비 계산 진행 후 저장을 눌러주새요"); + alert("공사비 계산 진행 후 저장을 눌러주새요"); return; } @@ -211,10 +213,15 @@ export default function ConCostForm({
); -} \ No newline at end of file +} diff --git a/apps/wr/public/images/map/layericon/RDL_TSCT_PS500.png b/apps/wr/public/images/map/layericon/RDL_TSCT_PS500.png new file mode 100644 index 000000000..b82e2ff89 Binary files /dev/null and b/apps/wr/public/images/map/layericon/RDL_TSCT_PS500.png differ diff --git a/apps/wr/src/shared/ui/alert-dialog.tsx b/apps/wr/src/shared/ui/alert-dialog.tsx index 6160d5301..51e82798c 100644 --- a/apps/wr/src/shared/ui/alert-dialog.tsx +++ b/apps/wr/src/shared/ui/alert-dialog.tsx @@ -21,10 +21,12 @@ function AlertDialog() { {message}

- - - - + +
+ + + +
diff --git a/apps/wr/src/shared/ui/confirm-dialog.tsx b/apps/wr/src/shared/ui/confirm-dialog.tsx index 6a4996bfb..7a8065a98 100644 --- a/apps/wr/src/shared/ui/confirm-dialog.tsx +++ b/apps/wr/src/shared/ui/confirm-dialog.tsx @@ -28,17 +28,19 @@ function ConfirmDialog({ {message}

- - - - - - - + +
+ + + + + + +
diff --git a/apps/wr/tsconfig.json b/apps/wr/tsconfig.json index 493584782..221de4580 100644 --- a/apps/wr/tsconfig.json +++ b/apps/wr/tsconfig.json @@ -28,13 +28,9 @@ "@/*": ["./*"], "@common/components/*": ["../../packages/common/components/src/*"], "@common/components": ["../../packages/common/components/src/index"], - "@common/business_components/*": [ - "../../packages/common/business_components/src/*" - ], - "@common/business_components": [ - "../../packages/common/business_components/src/index" - ], - "@common/network": ["../../packages/common/network"] + "@common/business_components/*": ["../../packages/common/business_components/src/*"], + "@common/business_components": ["../../packages/common/business_components/src/index"], + "@common/network": ["../../packages/common/network"], } }, "include": [ diff --git a/packages/common/business_components/src/ui/map/Gis.js b/packages/common/business_components/src/ui/map/Gis.js index 8a33b1123..249c94a13 100644 --- a/packages/common/business_components/src/ui/map/Gis.js +++ b/packages/common/business_components/src/ui/map/Gis.js @@ -19,6 +19,7 @@ import GisMapToggle from "./GisMapToggle"; import GisSelect from "./GisSelect"; import GisToolBar from "./GisToolBar"; import GisTree from "./GisTree"; +import GisZoomLevel from "./GisZoomLevel"; import "./lib/css/gis.common.css"; import GisApp from "./lib/gis.core"; import LoadingOverlay from "@common/business_components/ui/loadingBar"; @@ -58,6 +59,7 @@ function Gis({ droughtDates = { startYmd: '', endYmd: '' }, defaultZoom = 15, defaultLonLat = [127.4875, 37.4916], + showZoomLevel = false, }) { const mapRef = useRef(); const [mapObject, setMapObject] = useState({}); @@ -325,6 +327,7 @@ function Gis({
)} + {showZoomLevel && }
diff --git a/packages/common/business_components/src/ui/map/GisZoomLevel.js b/packages/common/business_components/src/ui/map/GisZoomLevel.js new file mode 100644 index 000000000..34d74f252 --- /dev/null +++ b/packages/common/business_components/src/ui/map/GisZoomLevel.js @@ -0,0 +1,113 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { useGisContext } from './Gis'; + +export default React.memo(function GisZoomLevel() { + const { gisMap } = useGisContext(); + const trackRef = useRef(null); + const [zoomInfo, setZoomInfo] = useState({ + zoom: 0, + minZoom: 0, + maxZoom: 0, + }); + + useEffect(() => { + if (!gisMap) return undefined; + + const view = gisMap.getView(); + if (!view) return undefined; + + const updateZoomInfo = () => { + setZoomInfo({ + zoom: view.getZoom() ?? 0, + minZoom: view.getMinZoom?.() ?? 0, + maxZoom: view.getMaxZoom?.() ?? 20, + }); + }; + + updateZoomInfo(); + view.on('change:resolution', updateZoomInfo); + + return () => { + view.un('change:resolution', updateZoomInfo); + }; + }, [gisMap]); + + if (!gisMap) return <>; + + const { zoom, minZoom, maxZoom } = zoomInfo; + const range = Math.max(maxZoom - minZoom, 1); + const normalized = Math.min(Math.max((zoom - minZoom) / range, 0), 1); + const fillHeight = `${normalized * 100}%`; + const thumbBottom = `calc(${fillHeight} - 1px)`; + + const updateZoomFromClientY = clientY => { + if (!gisMap || !trackRef.current) return; + + const view = gisMap.getView(); + const rect = trackRef.current.getBoundingClientRect(); + const offsetY = rect.bottom - clientY; + const nextNormalized = Math.min(Math.max(offsetY / rect.height, 0), 1); + const nextZoom = minZoom + nextNormalized * range; + view.setZoom(nextZoom); + }; + + const zoomIn = () => { + const view = gisMap.getView(); + if (view.getMaxZoom() > view.getZoom()) { + view.animate({ + zoom: view.getZoom() + 1, + duration: 250, + }); + } + }; + + const zoomOut = () => { + const view = gisMap.getView(); + if (view.getMinZoom() < view.getZoom()) { + view.animate({ + zoom: view.getZoom() - 1, + duration: 250, + }); + } + }; + + const handleTrackPointerDown = event => { + event.preventDefault(); + updateZoomFromClientY(event.clientY); + + const handlePointerMove = moveEvent => { + updateZoomFromClientY(moveEvent.clientY); + }; + + const handlePointerUp = () => { + window.removeEventListener('pointermove', handlePointerMove); + window.removeEventListener('pointerup', handlePointerUp); + }; + + window.addEventListener('pointermove', handlePointerMove); + window.addEventListener('pointerup', handlePointerUp); + }; + + return ( +
+
+ +
+
+
+
+
+
+ +
+
+ ); +}); diff --git a/packages/common/business_components/src/ui/map/lib/css/gis.common.css b/packages/common/business_components/src/ui/map/lib/css/gis.common.css index f9b67a07b..5ce46a912 100644 --- a/packages/common/business_components/src/ui/map/lib/css/gis.common.css +++ b/packages/common/business_components/src/ui/map/lib/css/gis.common.css @@ -173,6 +173,100 @@ text-indent: -9999px; } +.zoom-level-control { + position: absolute; + right: 20px; + bottom: 60px; + z-index: 2; +} + +.zoom-level-panel { + overflow: hidden; + width: 40px; + background: #fff; + border: 1px solid #9aa7b8; + border-radius: 3px; + box-shadow: 0px 0px 5px #999; + box-sizing: border-box; +} + +.zoom-level-btn { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 30px; + padding: 0; + border: 0; + border-bottom: 1px solid #eeeeee; + background: #fff; + color: #333; + font-size: 26px; + line-height: 1; + cursor: pointer; +} + +.zoom-level-panel .zoom-level-btn:last-child { + border-top: 1px solid #eeeeee; + border-bottom: 0 none; +} + +.zoom-level-btn:hover { + background: #67aee3; + color: #fff; +} + +.zoom-level-gauge { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 128px; + background: #fff; +} + +.zoom-level-track { + position: relative; + width: 12px; + height: 96px; + border-radius: 6px; + cursor: ns-resize; + touch-action: none; + background: + linear-gradient(to top, #b8b8b8, #b8b8b8) center / 4px 100% no-repeat, + repeating-linear-gradient( + to top, + transparent 0, + transparent 8px, + #d9d9d9 8px, + #d9d9d9 9px + ), + linear-gradient(to top, #f5f5f5, #fafafa); +} + +.zoom-level-fill { + position: absolute; + left: 50%; + bottom: 0; + width: 4px; + background: #3b82f6; + border-radius: 999px; + transform: translateX(-50%); +} + +.zoom-level-thumb { + position: absolute; + left: 50%; + width: 22px; + height: 5px; + border: 0.5px solid #111; + border-radius: 2px; + background: #fff; + transform: translateX(-50%); + box-sizing: border-box; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18); +} + .ol-scale-line { background: rgba(0, 60, 136, 0.3); border-radius: 4px; diff --git a/packages/common/business_components/src/ui/map/lib/gis.layer.js b/packages/common/business_components/src/ui/map/lib/gis.layer.js index 1e8bb428c..314b93338 100644 --- a/packages/common/business_components/src/ui/map/lib/gis.layer.js +++ b/packages/common/business_components/src/ui/map/lib/gis.layer.js @@ -118,7 +118,7 @@ GisOlLayer.prototype = { let toProj = GisApp.TO_PROJ; let lyrTyNm = options.lyrTyNm; let typename = this.storageNm + lyrTyNm; - let filter = options?.flterSql && options.flterSql && options.flterSql.trim() !== ''; + const filter = (options?.flterSql ?? '').trim(); let params = {}; params['TYPENAME'] = typename;