cm:유역변경

This commit is contained in:
ljpark
2025-12-12 14:51:35 +09:00
parent 9306cb6814
commit 1bd5da6309
6 changed files with 90 additions and 34 deletions
+2 -2
View File
@@ -7,5 +7,5 @@ NEXT_PUBLIC_SERVER_API_URL=http://dev.onasys.co.kr:8000
NEXT_PUBLIC_LINK_BACKEND_API_HOST=http://rsone.iptime.org:10014
NEXT_PUBLIC_TINY_API_KEY=t9nc4z6inquvvd5pq1vea8wcp7lb01qmqseh6egms9vpaju2
# NEXT_PUBLIC_SERVER_API_URL2=http://con.mind-one.co.kr:11083/
NEXT_PUBLIC_SERVER_API_URL2=http://192.168.1.222:8883/
NEXT_PUBLIC_SERVER_API_URL2=http://con.mind-one.co.kr:11083/
# NEXT_PUBLIC_SERVER_API_URL2=http://192.168.1.222:8883/
+39 -22
View File
@@ -5,6 +5,43 @@ import { ENDPOINT } from "@/shared/config";
import type { OperWqltContent, OperFcltContent, ReuseStatusContent, OperationService
} from "../model/fo-interface";
// 기준년도 조회
export type YearState = {
year: string | null;
};
export type YearStateActions = {
setYear: (year: string | null) => void;
};
export type YearStore = YearState & YearStateActions;
export const defaultYearInitState: YearState = {
year: null,
};
export const createYearStore = (initState: YearState = defaultYearInitState) =>
createStore<YearStore>()((set) => ({
...initState,
setYear: (year) => set(() => ({ year })),
}));
export const yearStore = createYearStore();
// 유역
export type BasinState = {
selectedBasin: string;
};
export type BasinStateActions = {
setSelectedBasin: (selectedBasin: string ) => void;
};
export type BasinStore = BasinState & BasinStateActions;
export const defaultBasinInitState: BasinState = {
selectedBasin: "한강",
};
export const createBasinStore = (initState: BasinState = defaultBasinInitState) =>
createStore<BasinStore>()((set) => ({
...initState,
setSelectedBasin: (selectedBasin) => set(() => ({ selectedBasin })),
}));
export const basinStore = createBasinStore();
//수계별 시설운영 개소수 및 가동률
export type OperFcltState = {
operFcltStatus: OperFcltContent[] | null;
@@ -50,6 +87,7 @@ const initialWqltData: OperWqltContent[] = [
export const defaultInitOperWqltState: OperWqltState = {
operWqltStatus: initialWqltData,
};
export const operFcltStore = createOperFcltStore();
// 물재이용 현황
export type ReuseState = {
@@ -73,29 +111,8 @@ export const createReuseStore = (initState: ReuseState = defaultInitReuseState)
...initState,
setReuseStatus: (reuseStatus) => set(() => ({ reuseStatus })),
}));
// 기준년도 조회
export type YearState = {
year: string | null;
};
export type YearStateActions = {
setYear: (year: string | null) => void;
};
export type YearStore = YearState & YearStateActions;
export const defaultYearInitState: YearState = {
year: null,
};
export const createYearStore = (initState: YearState = defaultYearInitState) =>
createStore<YearStore>()((set) => ({
...initState,
setYear: (year) => set(() => ({ year })),
}));
// 전역으로 공유할 store 인스턴스
export const operFcltStore = createOperFcltStore();
export const reuseStore = createReuseStore();
export const yearStore = createYearStore();
export class OperationApiService implements OperationService {
@@ -1,8 +1,20 @@
import React, { useEffect, useRef } from "react";
import { useStore } from "zustand";
import styles from "./korea-basin-map.module.css"; // 추가
import { basinStore } from "../api/fo-service";
const SVG_URL = "/images/region_map.svg"; // public 폴더에 region_map.svg 위치 필요
// 2. 유역 키를 매핑하는 로직 (예: "한강" -> "han")
export const basinKeyMap: { [key: string]: string } = {
"한강": "han",
"금강": "geum",
"낙동강": "nakdong",
"영산강": "yeongsan",
};
// --- 라벨 좌표(수동 고정값) : inset 적용 영역 기준 퍼센트 또는 px 사용 ---
// - 퍼센트: { x:0~1, y:0~1 } (권장: 반응형에 유리)
// - 픽셀: { px:number, py:number } (inset 좌상단 원점 기준)
const MANUAL_LABEL_POS = {
han: { x: 0.52, y: 0.14 },
geum: { x: 0.28, y: 0.47 },
@@ -56,6 +68,7 @@ function fmt(n: number) {
return new Intl.NumberFormat("ko-KR").format(n);
}
export function KoreaBasinMap() {
const stageRef = useRef<HTMLDivElement>(null);
const svgLayerRef = useRef<HTMLDivElement>(null);
@@ -65,12 +78,28 @@ export function KoreaBasinMap() {
const rowsRef = useRef<HTMLTableSectionElement>(null);
const colTitleRef = useRef<HTMLTableCellElement>(null);
const { selectedBasin } = useStore(basinStore);
const selectedBasinKey = basinKeyMap[selectedBasin];
// selectedBasinKey prop이 변경될 때 지도를 업데이트하는 useEffect
useEffect(() => {
if (selectedBasinKey) {
const svg = svgLayerRef.current?.querySelector("svg");
if (!svg) return;
// 모든 유역의 활성화 상태를 초기화합니다.
svg.querySelectorAll('.basin.active').forEach(el => el.classList.remove('active'));
const targetGroup = svg.querySelector(`.basin[data-key="${selectedBasinKey}"]`) as SVGGElement | null;
targetGroup?.classList.add('active');
}
}, [selectedBasinKey]);
useEffect(() => {
let svg: SVGSVGElement | null = null;
let gs: SVGGElement[] = [];
let labels: HTMLDivElement[] = [];
console.log("Loading SVG from:", SVG_URL);
// console.log("Loading SVG from:", SVG_URL);
fetch(SVG_URL)
.then((r) => {
console.log("SVG fetch response:", r.status, r.statusText);
@@ -173,6 +202,7 @@ export function KoreaBasinMap() {
labels.forEach((l) => l.classList.remove("active"));
};
// 유역정보
function showFor(g: SVGGElement) {
console.log("showFor called for:", g);
const key = (g as SVGGElement & { dataset: { key: string; label: string } }).dataset.key;
@@ -1,10 +1,11 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { useStore } from "zustand";
import { tokenManager } from "@/entities/auth/lib/token-manager";
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import { KoreaBasinMap } from "../lib/korea-basin-map";
import { KoreaBasinMap, basinKeyMap } from "../lib/korea-basin-map";
import {
Chart as ChartJS,
CategoryScale,
@@ -48,8 +49,7 @@ interface UserInfo {
function DashboardPage() {
const [userInfo, setUserInfo] = useState<UserInfo | null>(null);
const [error, setError] = useState("");
const router = useRouter();
const router = useRouter();
useEffect(() => {
const fetchUserInfo = async () => {
console.log("fetchUserInfo called");
@@ -8,13 +8,17 @@ import BasinSelect from "./basIn-select";
import { Doughnut } from "react-chartjs-2";
import type { Chart as ChartJS } from "chart.js";
// import type { OperFcltContent } from "../model/fo-interface";
import { OperationApi, operFcltStore } from "../api/fo-service";
import { OperationApi, operFcltStore, basinStore } from "../api/fo-service";
const FoFcltCntRt: React.FC = () => {
const [year, setYear] = useState<string | null>(null);
const [selectedBasin, setSelectedBasin] = useState<string>("한강");
const { selectedBasin, setSelectedBasin } = useStore(basinStore);
const operFcltStatus = useStore(operFcltStore, (state) => state.operFcltStatus);
const onBasinChange = (value: string) => {
setSelectedBasin(value);
};
useEffect(() => {
const fetchData = async () => {
try {
@@ -55,7 +59,7 @@ const FoFcltCntRt: React.FC = () => {
<div className="card card-border">
<div className="card-header text-foreground">
<h3 className="text-2xl font-medium">수계별 시설운영 개소수 및 가동률</h3>
<BasinSelect selectedBasin={selectedBasin} onValueChange={setSelectedBasin} />
<BasinSelect selectedBasin={selectedBasin} onValueChange={onBasinChange} />
</div>
<div className="h-60 p-4">
<div className="flex gap-2 h-full">
@@ -3,6 +3,7 @@
* 수계별 유입ㆍ방류수질 분석
*/
import React, { useEffect, useState, useMemo } from 'react';
import { useStore } from "zustand";
import { Line } from 'react-chartjs-2';
import BasinSelect from "./basIn-select";
import {
@@ -11,7 +12,7 @@ import {
Title, Tooltip, Legend, type ChartOptions,
} from 'chart.js';
import type { OperWqltContent } from "../model/fo-interface";
import { OperationApi } from "../api/fo-service";
import { OperationApi, basinStore } from "../api/fo-service";
import { defaultInitOperWqltState } from "../api/fo-service";
// Chart.js 등록
@@ -20,11 +21,15 @@ ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, T
type WaterQualityType = 'BOD' | 'TOC' | 'SS' | 'TP' | 'TN';
const FoWqltInflEffl: React.FC = () => {
const [selectedBasin, setSelectedBasin] = useState<string>("한강");
const { selectedBasin, setSelectedBasin } = useStore(basinStore);
const [selectedTab, setSelectedTab] = useState<WaterQualityType>('BOD');
const [wqltData, setWqltData] = useState<OperWqltContent[]>([]);
const tabs: WaterQualityType[] = ['BOD', 'TOC', 'SS', 'TP', 'TN'];
const onBasinChange = (value: string) => {
setSelectedBasin(value);
};
useEffect(() => {
setWqltData(defaultInitOperWqltState.operWqltStatus);
const fetchData = async () => {
@@ -192,7 +197,7 @@ const FoWqltInflEffl: React.FC = () => {
<div className="card card-border">
<div className="card-header text-foreground">
<h3 className="text-2xl font-medium">수계별 유입ㆍ방류수질 분석</h3>
<BasinSelect selectedBasin={selectedBasin} onValueChange={setSelectedBasin} />
<BasinSelect selectedBasin={selectedBasin} onValueChange={onBasinChange} />
</div>
<div className="h-60">
<div style={{ padding: '4px', height: '100%', display: 'flex', flexDirection: 'column' }}>