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