Merge branch 'iipbs/common/main' into iipbs/sn/ii

This commit is contained in:
swcho
2025-08-05 16:40:04 +09:00
2 changed files with 189 additions and 4 deletions
@@ -117,6 +117,11 @@ function Gis({
onCheckedLayersChange,
searchBar: SearchBar,
waterDepth: WaterDepth,
isPlaying = false,
setIsPlaying = () => {},
targetLayerTypes = [],
orderedLayers = [],
checkInterval = 3000,
height: height = "h-[calc(100vh-10rem)]",
}) {
const mapRef = useRef();
@@ -305,12 +310,19 @@ function Gis({
onFeatureSelect(dummyFeature);
}}
imageVisible={imgVisble}
isPlaying={isPlaying}
setIsPlaying={setIsPlaying}
targetLayerTypes={targetLayerTypes}
orderedLayers={orderedLayers}
checkInterval={checkInterval}
/>
{typeof WaterDepth === "function" && (
<div className={`absolute bottom-4 z-40 ${tabVisible ? 'left-[260px]' : 'left-4'}`}>
<WaterDepth
onClose={() => setIsOpen(false)}
isPlaying={isPlaying}
setIsPlaying={setIsPlaying}
/>
</div>
)}
@@ -5,18 +5,36 @@ import {
DndProvider
} from "@minoru/react-dnd-treeview";
import { ChevronLeft, ChevronRight } from 'lucide-react';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState, useRef, useCallback } from 'react';
import { useGisContext } from './Gis';
import GisApp from './lib/gis.core';
export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes = () => { }, visible, setVisible, onCheckedChange, onLayerIconClick,imageVisible=false }) {
export default React.memo(function GisTree({
checkedNodes = [],
setCheckedNodes = () => { },
visible,
setVisible,
onCheckedChange,
onLayerIconClick,
imageVisible = false,
isPlaying = false,
setIsPlaying = () => {},
targetLayerTypes = [],
orderedLayers = [],
checkInterval = 3000,
}) {
const { gisCore, treeData } = useGisContext();
const [checked, setChecked] = useState([]);
const [expanded, setExpanded] = useState([]);
const [nodes, setNodes] = useState([]);
const [activeTab, setActiveTab] = useState('layers');
const currentAnimationIndexRef = useRef(0);
const targetNodesRef = useRef([]); // 대상 노드들을 저장할 ref
const isPausedRef = useRef(false); // 일시정지 상태를 저장할 ref
const timeoutIdRef = useRef(null); // setTimeout ID를 저장할 ref
useEffect(() => {
if (treeData && treeData.length > 0) {
const nodeData = setNodeValues(treeData);
@@ -191,8 +209,163 @@ export default React.memo(function GisTree({ checkedNodes = [], setCheckedNodes
if (!checked.includes(nodeId)) alert('시설을 선택해주세요')
else onLayerIconClick(nodeNm)
}
// 특정 노드를 체크/언체크하는 함수
const checkSpecificNode = useCallback((nodeId, shouldCheck = true) => {
setChecked((prevChecked) => {
let newChecked = [...prevChecked];
const isCurrentlyChecked = prevChecked.includes(nodeId);
if (shouldCheck && !isCurrentlyChecked) {
// 체크하기
const descendants = getDescendants(nodeId, nodes);
newChecked = Array.from(new Set([...newChecked, nodeId, ...descendants]));
const ancestors = getAncestors(nodeId, nodes);
ancestors.forEach((p) => {
if (isEveryChildChecked(p, nodes, newChecked)) {
newChecked.push(p);
}
});
} else if (!shouldCheck && isCurrentlyChecked) {
// 언체크하기
const descendants = getDescendants(nodeId, nodes);
newChecked = newChecked.filter((cid) => cid !== nodeId && !descendants.includes(cid));
const ancestors = getAncestors(nodeId, nodes);
ancestors.forEach((p) => {
if (newChecked.includes(p)) {
newChecked = newChecked.filter((cid) => cid !== p);
}
});
}
// 지도에 레이어 표시/숨김 적용
const allIds = nodes.map((n) => n.id);
const unchecked = allIds.filter((nid) => !newChecked.includes(nid));
gisCore.setLayerOnMap(newChecked, true);
gisCore.setLayerOnMap(unchecked, false);
// 상위로 변경 사항 전달
const checkedNodesData = nodes.filter(node => newChecked.includes(node.id));
onCheckedChange?.(newChecked, checkedNodesData);
return newChecked;
});
}, [nodes]);
// 침수위험지도 실행 함수
const handleAutoCheckAnimation = useCallback((isEnabled, layerTypes = [], interval = 3000) => {
// 기존 타임아웃 정리
if (timeoutIdRef.current) {
clearTimeout(timeoutIdRef.current);
timeoutIdRef.current = null;
}
if (!isEnabled || layerTypes.length === 0) {
// 애니메이션 비활성화 시 상태 초기화
isPausedRef.current = false;
targetNodesRef.current = [];
currentAnimationIndexRef.current = 0;
return;
}
// 대상 노드들을 찾아서 특정 순서로 정렬 후 배열로 반환
const getTargetNodesInOrder = () => {
const orderedNodes = [];
orderedLayers.forEach(lyrSn => {
const node = nodes.find(n => n.id === lyrSn && layerTypes.includes(n.data?.lyrTyNm));
if (node) {
orderedNodes.push(node);
}
});
return orderedNodes;
};
const targetNodes = getTargetNodesInOrder();
if (targetNodes.length === 0) {
console.warn('대상 레이어를 찾을 수 없습니다:', layerTypes);
return;
}
// 처음 시작하는 경우에만 초기화
if (targetNodesRef.current.length === 0) {
targetNodesRef.current = targetNodes;
currentAnimationIndexRef.current = 0;
console.log('자동 체크 대상 노드들 (순서대로):', targetNodes.map(n => ({ id: n.id, name: n.text, type: n.data?.lyrTyNm })));
// 모든 대상 레이어를 먼저 언체크
targetNodes.forEach(node => {
checkSpecificNode(node.id, false);
});
}
// 애니메이션 재개/시작
isPausedRef.current = false;
const processNextLayer = () => {
// 일시정지 상태이거나 애니메이션이 비활성화되면 중단
if (isPausedRef.current || !isPlaying) {
return;
}
const currentTargetNodes = targetNodesRef.current;
const currentIndex = currentAnimationIndexRef.current;
if (currentIndex < currentTargetNodes.length) {
checkSpecificNode(currentTargetNodes[currentIndex].id, true);
if (currentIndex > 0) {
checkSpecificNode(currentTargetNodes[currentIndex - 1].id, false);
}
currentAnimationIndexRef.current++;
// 다음 레이어 처리를 위한 타이머 설정
timeoutIdRef.current = setTimeout(processNextLayer, interval);
} else {
// 애니메이션 완료 - 상태 초기화 및 완료 신호 전송
console.log('애니메이션 완료');
isPausedRef.current = false;
targetNodesRef.current = [];
currentAnimationIndexRef.current = 0;
timeoutIdRef.current = null;
setIsPlaying?.(false);
}
};
processNextLayer();
}, [nodes, checkSpecificNode, setIsPlaying, isPlaying]);
// 애니메이션 일시정지 함수
const pauseAnimation = useCallback(() => {
if (timeoutIdRef.current) {
clearTimeout(timeoutIdRef.current);
timeoutIdRef.current = null;
}
isPausedRef.current = true;
}, []);
// 외부에서 전달받은 props로 자동 체크 제어
useEffect(() => {
if (isPlaying) handleAutoCheckAnimation(true, targetLayerTypes, checkInterval); // 재생 시작/재개
else pauseAnimation(); // 일시정지
}, [isPlaying, handleAutoCheckAnimation, targetLayerTypes, checkInterval, pauseAnimation]);
// 컴포넌트 언마운트 시 정리
useEffect(() => {
return () => {
if (timeoutIdRef.current) {
clearTimeout(timeoutIdRef.current);
}
};
}, []);
if (!treeData || treeData.length === 0) return null;
return (
<>
<div