kcg-ai-monitoring/frontend/src/lib/charts/BaseChart.tsx
htlee e6319a571c refactor: 모노레포 구조로 전환 (frontend/ + backend/ + database/)
Phase 1: 모노레포 디렉토리 구조 구축

- 기존 React 프로젝트를 frontend/ 디렉토리로 이동 (git mv)
- backend/ 디렉토리 생성 (Phase 2에서 Spring Boot 초기화)
- database/migration/ 디렉토리 생성 (Phase 2에서 Flyway 마이그레이션)
- 루트 .gitignore에 frontend/, backend/ 경로 반영
- 루트 CLAUDE.md를 모노레포 가이드로 갱신
- Makefile 추가 (dev/build/lint 통합 명령)
- frontend/vite.config.ts에 /api → :8080 백엔드 proxy 설정
- .githooks/pre-commit을 모노레포 구조에 맞게 갱신
  (frontend/ 변경 시 frontend/ 내부에서 검증)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 08:47:24 +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 as unknown as ECharts;
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 }}
/>
);
}