141 lines
3.9 KiB
TypeScript
141 lines
3.9 KiB
TypeScript
import React, { useEffect, useState } from 'react';
|
|
import type { ApexOptions } from 'apexcharts';
|
|
|
|
// Chart module is imported dynamically inside the component to avoid SSR/hot-reload issues.
|
|
|
|
interface ApexChartProps {
|
|
type?: 'area' | 'line' | 'bar' | 'pie' | 'donut';
|
|
data?: number[];
|
|
labels?: string[];
|
|
title?: string;
|
|
height?: number;
|
|
color?: string;
|
|
}
|
|
|
|
const ApexChart: React.FC<ApexChartProps> = ({
|
|
type = 'area',
|
|
data = [],
|
|
labels = [],
|
|
title = 'Chart',
|
|
height = 350,
|
|
color = '#F84525',
|
|
}) => {
|
|
const isClient = typeof window !== 'undefined';
|
|
const [Chart, setChart] = useState<any>(null);
|
|
|
|
useEffect(() => {
|
|
if (isClient) {
|
|
import('react-apexcharts')
|
|
.then((m) => setChart(m.default))
|
|
.catch((e) => console.error('Failed to load react-apexcharts', e));
|
|
}
|
|
}, [isClient]);
|
|
|
|
const safeData = Array.isArray(data) ? data.map((n) => (Number.isFinite(Number(n)) ? Number(n) : 0)) : [];
|
|
const isDarkMode = typeof document !== 'undefined' && document.documentElement.classList.contains('dark');
|
|
|
|
// Show loading/no data state if chart not ready or no data
|
|
if (!isClient || !Chart || safeData.length === 0) {
|
|
return (
|
|
<div className="card-enhanced">
|
|
<div className="card-body p-4">
|
|
<h5 className="card-title mb-3">{title || 'Chart'}</h5>
|
|
<div className="text-sm text-muted">
|
|
{!Chart ? 'Cargando gráfico...' : 'No hay datos disponibles.'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
// Ensure options are valid and chart configuration is safe
|
|
const options: ApexOptions = {
|
|
chart: {
|
|
type: type || 'area',
|
|
height: height || 350,
|
|
zoom: { enabled: false },
|
|
toolbar: { show: false },
|
|
foreColor: isDarkMode ? '#91989e' : '#433c3a',
|
|
animations: { enabled: false } // Disable animations for stability
|
|
},
|
|
colors: [color || '#F84525'],
|
|
dataLabels: { enabled: false },
|
|
stroke: {
|
|
curve: 'smooth',
|
|
width: 3,
|
|
},
|
|
fill: type === 'area'
|
|
? {
|
|
type: 'gradient',
|
|
gradient: {
|
|
shadeIntensity: 1,
|
|
type: 'vertical',
|
|
opacityFrom: 0.4,
|
|
opacityTo: 0,
|
|
stops: [0, 70, 97],
|
|
gradientToColors: ['#f7b733'],
|
|
},
|
|
}
|
|
: undefined,
|
|
markers: {
|
|
size: type === 'area' ? 3 : 0,
|
|
strokeWidth: 0,
|
|
hover: { sizeOffset: 2 },
|
|
colors: type === 'area' ? ['#FFFFFF'] : [color || '#F84525'],
|
|
},
|
|
xaxis: {
|
|
categories: labels && labels.length > 0 ? labels : ['Sin datos'],
|
|
axisBorder: { show: false },
|
|
axisTicks: { show: false },
|
|
labels: { style: { colors: isDarkMode ? '#91989e' : '#aaa' } },
|
|
},
|
|
yaxis: {
|
|
labels: { style: { colors: isDarkMode ? '#91989e' : '#aaa' } },
|
|
min: 0
|
|
},
|
|
grid: { borderColor: isDarkMode ? '#26292d' : '#eff2f7' },
|
|
legend: {
|
|
horizontalAlign: 'left',
|
|
labels: { colors: isDarkMode ? '#ffffff' : '#433c3a' },
|
|
},
|
|
title: {
|
|
text: title || 'Chart',
|
|
style: { color: isDarkMode ? '#ffffff' : '#433c3a' }
|
|
},
|
|
theme: { mode: isDarkMode ? 'dark' : 'light' },
|
|
noData: {
|
|
text: 'No hay datos disponibles',
|
|
align: 'center',
|
|
verticalAlign: 'middle',
|
|
style: {
|
|
color: isDarkMode ? '#ffffff' : '#433c3a',
|
|
fontSize: '14px'
|
|
}
|
|
}
|
|
};
|
|
|
|
// Ensure series data is valid
|
|
const series = [{
|
|
name: title || 'Data',
|
|
data: safeData && safeData.length > 0 ? safeData : [0]
|
|
}];
|
|
|
|
return (
|
|
<div className="card-enhanced">
|
|
<div className="card-body p-4">
|
|
<h5 className="card-title mb-3">{title || 'Chart'}</h5>
|
|
{Chart && (
|
|
<Chart
|
|
options={options}
|
|
series={series}
|
|
type={type || 'area'}
|
|
height={height || 350}
|
|
key={`chart-${title}-${safeData.length}`}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ApexChart; |