import React, { memo, useMemo, useCallback } from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { formatSensorValue, getSensorPrecision } from '../utils/formatters'; interface SensorChartProps { title: string; data: any[]; dataKeys: string[]; colors: string[]; names?: string[]; height?: number; yAxisId?: string; rightYAxisId?: string; rightDataKeys?: string[]; rightColors?: string[]; rightNames?: string[]; } // 데이터 유효성 검증 함수 - 강화된 검증 const validateChartData = (data: any[], dataKeys: string[], rightDataKeys: string[]): boolean => { if (!data || !Array.isArray(data) || data.length === 0) { return false; } // 모든 데이터 항목이 유효한지 확인 return data.every(item => { if (!item || typeof item !== 'object' || !item.device_id) { return false; } // 모든 dataKeys가 유효한 숫자인지 확인 const allKeys = [...dataKeys, ...rightDataKeys]; return allKeys.every(key => { const value = item[key]; return typeof value === 'number' && !isNaN(value) && isFinite(value); }); }); }; // 기본 더미 데이터 생성 함수 const createDefaultData = (dataKeys: string[], rightDataKeys: string[]) => { const allKeys = [...dataKeys, ...rightDataKeys]; const defaultItem: any = { device_id: 'No Data' }; allKeys.forEach(key => { defaultItem[key] = 0; }); return [defaultItem]; }; // 안전한 숫자 변환 함수 const safeNumber = (value: any, defaultValue: number = 0): number => { if (value === null || value === undefined) { return defaultValue; } const num = Number(value); if (typeof num === 'number' && !isNaN(num) && isFinite(num)) { return num; } return defaultValue; }; const SensorChart: React.FC = memo(({ title, data, dataKeys, colors, names, height = 300, yAxisId, rightYAxisId, rightDataKeys = [], rightColors = [], rightNames = [] }) => { // 차트 데이터 메모이제이션 및 검증 - 의존성 배열 최적화 const chartData = useMemo(() => { // 데이터가 없거나 빈 배열인 경우 기본 데이터 반환 if (!data || !Array.isArray(data) || data.length === 0) { return createDefaultData(dataKeys, rightDataKeys); } // 데이터 유효성 검사 및 정리 const processedData = data .filter(item => item && typeof item === 'object' && item.device_id) .map(item => { const processedItem: any = { device_id: item.device_id || 'Unknown', }; // 모든 dataKeys에 대해 안전하게 값 추출 dataKeys.forEach(key => { processedItem[key] = safeNumber(item[key], 0); }); // 모든 rightDataKeys에 대해 안전하게 값 추출 rightDataKeys.forEach(key => { processedItem[key] = safeNumber(item[key], 0); }); return processedItem; }); // 처리된 데이터가 비어있으면 기본 데이터 반환 if (processedData.length === 0) { return createDefaultData(dataKeys, rightDataKeys); } return processedData; }, [data, dataKeys, rightDataKeys]); // 데이터 유효성 검사 - 메모이제이션 최적화 const isValidData = useMemo(() => { return validateChartData(chartData, dataKeys, rightDataKeys); }, [chartData, dataKeys, rightDataKeys]); // 툴팁 포맷터 메모이제이션 - useCallback 사용 const tooltipFormatter = useCallback((value: any, name: string) => { const sensorType = name.toLowerCase().replace(/\s+/g, ''); return [formatSensorValue(value, getSensorPrecision(sensorType)), name]; }, []); // Y축 설정 메모이제이션 const yAxisConfig = useMemo(() => { const config = { hasLeftYAxis: false, hasRightYAxis: false, leftYAxis: null as React.ReactElement | null, rightYAxis: null as React.ReactElement | null, defaultYAxis: null as React.ReactElement | null }; if (yAxisId) { config.hasLeftYAxis = true; config.leftYAxis = ; } if (rightYAxisId) { config.hasRightYAxis = true; config.rightYAxis = ; } if (!yAxisId && !rightYAxisId) { config.defaultYAxis = ; } return config; }, [yAxisId, rightYAxisId]); // 라인 컴포넌트 메모이제이션 const leftLines = useMemo(() => { return dataKeys.map((key, index) => { // 데이터 키가 실제로 존재하는지 확인 const hasData = chartData.some(item => typeof item[key] === 'number' && !isNaN(item[key])); if (!hasData) return null; return ( ); }).filter(Boolean); }, [dataKeys, colors, names, yAxisId, chartData]); const rightLines = useMemo(() => { return rightDataKeys.map((key, index) => { // 데이터 키가 실제로 존재하는지 확인 const hasData = chartData.some(item => typeof item[key] === 'number' && !isNaN(item[key])); if (!hasData) return null; return ( ); }).filter(Boolean); }, [rightDataKeys, rightColors, rightNames, rightYAxisId, chartData]); // 데이터가 없거나 유효하지 않은 경우 안내 메시지 표시 if (chartData.length === 0 || !isValidData) { return (

{title}

{chartData.length === 0 ? '데이터가 없습니다' : '유효한 데이터가 없습니다'}

); } return (

{title}

{/* Y축 설정 - 메모이제이션된 설정 사용 */} {yAxisConfig.leftYAxis} {yAxisConfig.rightYAxis} {yAxisConfig.defaultYAxis} {/* 메모이제이션된 라인 컴포넌트들 */} {leftLines} {rightLines}
); }); SensorChart.displayName = 'SensorChart'; export default SensorChart;