kcg-ai-monitoring/src/lib/charts/BaseChart.tsx
htlee c0ce01eaf6 chore: 팀 워크플로우 기반 초기 프로젝트 구성
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>
2026-04-06 14:11:29 +09:00

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 }}
/>
);
}