refactor: major architecture improvements

Security:
- Remove exposed NocoDB token from client code
- Add .env.example for environment variables

Shared Components:
- Carousel: touch/keyboard navigation, accessibility
- ChartCard: reusable chart container
- EmptyState: for no-data scenarios
- FilterControls: collapsible filter panel with reset button
- StatCard: metric display with change indicator
- ToggleSwitch: accessible radio-style toggle

Architecture:
- Create src/config/chartConfig.js for shared chart options
- Extract ChartJS registration to single location
- Reduce code duplication in Dashboard and Comparison

UX Improvements:
- Add empty state when filters return no data
- Add Reset Filters button to filter controls
- Add skeleton loader CSS utilities
- Improve focus states for accessibility
- Use shared components in Dashboard and Comparison
This commit is contained in:
fahed
2026-02-02 13:50:23 +03:00
parent 24fa601aec
commit 8a3b6a8d2e
13 changed files with 590 additions and 314 deletions

97
src/config/chartConfig.js Normal file
View File

@@ -0,0 +1,97 @@
import {
Chart as ChartJS,
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarElement,
ArcElement,
Title,
Tooltip,
Legend,
Filler
} from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
// Register ChartJS components once
ChartJS.register(
CategoryScale,
LinearScale,
PointElement,
LineElement,
BarElement,
ArcElement,
Title,
Tooltip,
Legend,
Filler,
ChartDataLabels
);
export const chartColors = {
primary: '#2563eb',
secondary: '#7c3aed',
tertiary: '#0891b2',
success: '#059669',
danger: '#dc2626',
muted: '#94a3b8',
grid: '#f1f5f9'
};
export const createDataLabelConfig = (showDataLabels) => ({
display: showDataLabels,
color: '#1e293b',
font: { size: 10, weight: 600 },
anchor: 'end',
align: 'end',
offset: 4,
padding: 4,
backgroundColor: 'rgba(255, 255, 255, 0.85)',
borderRadius: 3,
formatter: (value) => {
if (value == null) return '';
if (value >= 1000000) return (value / 1000000).toFixed(2) + 'M';
if (value >= 1000) return (value / 1000).toFixed(2) + 'K';
if (value < 100 && value > 0) return value.toFixed(2);
return Math.round(value).toLocaleString();
}
});
export const createBaseOptions = (showDataLabels) => ({
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#1e293b',
padding: 12,
cornerRadius: 8,
titleFont: { size: 12 },
bodyFont: { size: 11 }
},
datalabels: createDataLabelConfig(showDataLabels)
},
scales: {
x: {
grid: { display: false },
ticks: { font: { size: 10 }, color: '#94a3b8' }
},
y: {
grid: { color: chartColors.grid },
ticks: { font: { size: 10 }, color: '#94a3b8' },
border: { display: false }
}
}
});
export const lineDatasetDefaults = {
borderWidth: 2,
tension: 0.4,
fill: true,
pointRadius: 0,
pointHoverRadius: 4
};
export const barDatasetDefaults = {
borderRadius: 4
};