KCG AI 기반 불법조업 탐지·차단 플랫폼 프론트엔드. React 19 + TypeScript 5.9 + Vite 8 + MapLibre + deck.gl + Zustand + Tailwind CSS. SFR 20개 전체 UI 구현 완료, 백엔드 연동 대기. - npm + Nexus 프록시 레지스트리 설정 - 팀 워크플로우 v1.6.1 부트스트랩 파일 배치 - .githooks (commit-msg, post-checkout) - package.json name: kcg-ai-monitoring v0.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
84 lines
2.1 KiB
TypeScript
84 lines
2.1 KiB
TypeScript
/**
|
|
* ECharts 코어 래퍼 컴포넌트
|
|
* - 자동 리사이즈 (ResizeObserver)
|
|
* - kcg-dark 테마 자동 적용
|
|
* - dispose 자동 정리
|
|
*/
|
|
import { useRef, useEffect } from 'react';
|
|
import * as echarts from 'echarts/core';
|
|
import { BarChart, LineChart, PieChart, RadarChart, HeatmapChart } from 'echarts/charts';
|
|
import {
|
|
GridComponent, TooltipComponent, LegendComponent,
|
|
TitleComponent, DatasetComponent, PolarComponent,
|
|
RadarComponent,
|
|
} from 'echarts/components';
|
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
import type { EChartsOption, ECharts } from 'echarts';
|
|
import './theme';
|
|
|
|
echarts.use([
|
|
BarChart, LineChart, PieChart, RadarChart, HeatmapChart,
|
|
GridComponent, TooltipComponent, LegendComponent,
|
|
TitleComponent, DatasetComponent, PolarComponent,
|
|
RadarComponent, CanvasRenderer,
|
|
]);
|
|
|
|
interface BaseChartProps {
|
|
option: EChartsOption;
|
|
className?: string;
|
|
style?: React.CSSProperties;
|
|
height?: number;
|
|
notMerge?: boolean;
|
|
onEvents?: Record<string, (params: unknown) => void>;
|
|
}
|
|
|
|
export function BaseChart({
|
|
option,
|
|
className = '',
|
|
style,
|
|
height = 200,
|
|
notMerge = false,
|
|
onEvents,
|
|
}: BaseChartProps) {
|
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
const chartRef = useRef<ECharts | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!containerRef.current) return;
|
|
|
|
const chart = echarts.init(containerRef.current, 'kcg-dark');
|
|
chartRef.current = chart;
|
|
chart.setOption(option, notMerge);
|
|
|
|
if (onEvents) {
|
|
Object.entries(onEvents).forEach(([event, handler]) => {
|
|
chart.on(event, handler);
|
|
});
|
|
}
|
|
|
|
const ro = new ResizeObserver(() => chart.resize());
|
|
ro.observe(containerRef.current);
|
|
|
|
return () => {
|
|
ro.disconnect();
|
|
chart.dispose();
|
|
chartRef.current = null;
|
|
};
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (chartRef.current) {
|
|
chartRef.current.setOption(option, notMerge);
|
|
}
|
|
}, [option, notMerge]);
|
|
|
|
return (
|
|
<div
|
|
ref={containerRef}
|
|
className={className}
|
|
style={{ width: '100%', height, ...style }}
|
|
/>
|
|
);
|
|
}
|