Enable TypeScript strict mode and fix all type errors
All checks were successful
Deploy HiHala Dashboard / deploy (push) Successful in 6s
All checks were successful
Deploy HiHala Dashboard / deploy (push) Successful in 6s
- Enable strict: true in tsconfig.json (was false) - Add proper interfaces for all component props (Dashboard, Comparison, Slides) - Add SlideConfig, ChartTypeOption, MetricOption types - Type all function parameters, callbacks, and state variables - Fix dynamic property access with proper keyof assertions - 233 type errors resolved across 5 files Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,17 +20,18 @@ import {
|
||||
getDistrictMuseumMap,
|
||||
getMuseumsForDistrict
|
||||
} from '../services/dataService';
|
||||
import type { DashboardProps, Filters, MuseumRecord } from '../types';
|
||||
|
||||
const defaultFilters = {
|
||||
const defaultFilters: Filters = {
|
||||
year: 'all',
|
||||
district: 'all',
|
||||
museum: 'all',
|
||||
quarter: 'all'
|
||||
};
|
||||
|
||||
const filterKeys = ['year', 'district', 'museum', 'quarter'];
|
||||
const filterKeys: (keyof Filters)[] = ['year', 'district', 'museum', 'quarter'];
|
||||
|
||||
function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setIncludeVAT }) {
|
||||
function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setIncludeVAT }: DashboardProps) {
|
||||
const { t } = useLanguage();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const [pilgrimLoaded, setPilgrimLoaded] = useState(false);
|
||||
@@ -51,7 +52,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
});
|
||||
|
||||
// Update both state and URL
|
||||
const setFilters = (newFilters) => {
|
||||
const setFilters = (newFilters: Filters | ((prev: Filters) => Filters)) => {
|
||||
const updated = typeof newFilters === 'function' ? newFilters(filters) : newFilters;
|
||||
setFiltersState(updated);
|
||||
|
||||
@@ -97,7 +98,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
const yoyChange = useMemo(() => {
|
||||
if (filters.year === 'all') return null;
|
||||
const prevYear = String(parseInt(filters.year) - 1);
|
||||
const prevData = data.filter(row => row.year === prevYear);
|
||||
const prevData = data.filter((row: MuseumRecord) => row.year === prevYear);
|
||||
if (prevData.length === 0) return null;
|
||||
const prevMetrics = calculateMetrics(prevData, includeVAT);
|
||||
return prevMetrics.revenue > 0 ? ((metrics.revenue - prevMetrics.revenue) / prevMetrics.revenue * 100) : null;
|
||||
@@ -106,7 +107,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
// Revenue trend data (weekly or daily)
|
||||
const trendData = useMemo(() => {
|
||||
const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
|
||||
const formatLabel = (dateStr) => {
|
||||
const formatLabel = (dateStr: string) => {
|
||||
if (!dateStr) return '';
|
||||
const [year, month, day] = dateStr.split('-').map(Number);
|
||||
const d = new Date(year, month - 1, day);
|
||||
@@ -166,7 +167,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
filteredData.forEach(row => {
|
||||
const date = row.date;
|
||||
if (!dailyData[date]) dailyData[date] = 0;
|
||||
dailyData[date] += Number(row[revenueField] || row.revenue_incl_tax || 0);
|
||||
dailyData[date] += Number((row as unknown as Record<string, unknown>)[revenueField] || row.revenue_incl_tax || 0);
|
||||
});
|
||||
const days = Object.keys(dailyData).sort();
|
||||
const revenueValues = days.map(d => dailyData[d]);
|
||||
@@ -228,21 +229,21 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
// Quarterly YoY
|
||||
const quarterlyYoYData = useMemo(() => {
|
||||
const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
|
||||
const d2024 = data.filter(row => row.year === '2024');
|
||||
const d2025 = data.filter(row => row.year === '2025');
|
||||
const d2024 = data.filter((row: MuseumRecord) => row.year === '2024');
|
||||
const d2025 = data.filter((row: MuseumRecord) => row.year === '2025');
|
||||
const quarters = ['Q1', 'Q2', 'Q3', 'Q4'];
|
||||
return {
|
||||
labels: quarters,
|
||||
datasets: [
|
||||
{
|
||||
label: '2024',
|
||||
data: quarters.map(q => d2024.filter(r => r.quarter === q.slice(1)).reduce((s, r) => s + parseFloat(r[revenueField] || r.revenue_incl_tax || 0), 0)),
|
||||
data: quarters.map(q => d2024.filter((r: MuseumRecord) => r.quarter === q.slice(1)).reduce((s: number, r: MuseumRecord) => s + parseFloat(String(r[revenueField as keyof MuseumRecord] || r.revenue_incl_tax || 0)), 0)),
|
||||
backgroundColor: chartColors.muted,
|
||||
borderRadius: 4
|
||||
},
|
||||
{
|
||||
label: '2025',
|
||||
data: quarters.map(q => d2025.filter(r => r.quarter === q.slice(1)).reduce((s, r) => s + parseFloat(r[revenueField] || r.revenue_incl_tax || 0), 0)),
|
||||
data: quarters.map(q => d2025.filter((r: MuseumRecord) => r.quarter === q.slice(1)).reduce((s: number, r: MuseumRecord) => s + parseFloat(String(r[revenueField as keyof MuseumRecord] || r.revenue_incl_tax || 0)), 0)),
|
||||
backgroundColor: chartColors.primary,
|
||||
borderRadius: 4
|
||||
}
|
||||
@@ -252,17 +253,17 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
|
||||
// Capture rate
|
||||
const captureRateData = useMemo(() => {
|
||||
const labels = [];
|
||||
const rates = [];
|
||||
const pilgrimCounts = [];
|
||||
const labels: string[] = [];
|
||||
const rates: number[] = [];
|
||||
const pilgrimCounts: number[] = [];
|
||||
[2024, 2025].forEach(year => {
|
||||
[1, 2, 3, 4].forEach(q => {
|
||||
const pilgrims = umrahData[year]?.[q];
|
||||
if (!pilgrims) return;
|
||||
let qData = data.filter(r => r.year === String(year) && r.quarter === String(q));
|
||||
if (filters.district !== 'all') qData = qData.filter(r => r.district === filters.district);
|
||||
if (filters.museum !== 'all') qData = qData.filter(r => r.museum_name === filters.museum);
|
||||
const visitors = qData.reduce((s, r) => s + parseInt(r.visits || 0), 0);
|
||||
let qData = data.filter((r: MuseumRecord) => r.year === String(year) && r.quarter === String(q));
|
||||
if (filters.district !== 'all') qData = qData.filter((r: MuseumRecord) => r.district === filters.district);
|
||||
if (filters.museum !== 'all') qData = qData.filter((r: MuseumRecord) => r.museum_name === filters.museum);
|
||||
const visitors = qData.reduce((s: number, r: MuseumRecord) => s + parseInt(String(r.visits || 0)), 0);
|
||||
labels.push(`Q${q} ${year}`);
|
||||
rates.push((visitors / pilgrims * 100));
|
||||
pilgrimCounts.push(pilgrims);
|
||||
@@ -286,7 +287,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
yAxisID: 'y',
|
||||
datalabels: {
|
||||
display: showDataLabels,
|
||||
formatter: (value) => value.toFixed(2) + '%',
|
||||
formatter: (value: number) => value.toFixed(2) + '%',
|
||||
color: '#1e293b',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
borderRadius: 3,
|
||||
@@ -312,7 +313,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
order: 1,
|
||||
datalabels: {
|
||||
display: showDataLabels,
|
||||
formatter: (value) => (value / 1000000).toFixed(2) + 'M',
|
||||
formatter: (value: number) => (value / 1000000).toFixed(2) + 'M',
|
||||
color: '#1e293b',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
borderRadius: 3,
|
||||
@@ -329,23 +330,23 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
// Quarterly table
|
||||
const quarterlyTable = useMemo(() => {
|
||||
const revenueField = includeVAT ? 'revenue_gross' : 'revenue_net';
|
||||
const d2024 = data.filter(row => row.year === '2024');
|
||||
const d2025 = data.filter(row => row.year === '2025');
|
||||
const d2024 = data.filter((row: MuseumRecord) => row.year === '2024');
|
||||
const d2025 = data.filter((row: MuseumRecord) => row.year === '2025');
|
||||
return [1, 2, 3, 4].map(q => {
|
||||
let q2024 = d2024.filter(r => r.quarter === String(q));
|
||||
let q2025 = d2025.filter(r => r.quarter === String(q));
|
||||
let q2024 = d2024.filter((r: MuseumRecord) => r.quarter === String(q));
|
||||
let q2025 = d2025.filter((r: MuseumRecord) => r.quarter === String(q));
|
||||
if (filters.district !== 'all') {
|
||||
q2024 = q2024.filter(r => r.district === filters.district);
|
||||
q2025 = q2025.filter(r => r.district === filters.district);
|
||||
q2024 = q2024.filter((r: MuseumRecord) => r.district === filters.district);
|
||||
q2025 = q2025.filter((r: MuseumRecord) => r.district === filters.district);
|
||||
}
|
||||
if (filters.museum !== 'all') {
|
||||
q2024 = q2024.filter(r => r.museum_name === filters.museum);
|
||||
q2025 = q2025.filter(r => r.museum_name === filters.museum);
|
||||
q2024 = q2024.filter((r: MuseumRecord) => r.museum_name === filters.museum);
|
||||
q2025 = q2025.filter((r: MuseumRecord) => r.museum_name === filters.museum);
|
||||
}
|
||||
const rev24 = q2024.reduce((s, r) => s + parseFloat(r[revenueField] || r.revenue_incl_tax || 0), 0);
|
||||
const rev25 = q2025.reduce((s, r) => s + parseFloat(r[revenueField] || r.revenue_incl_tax || 0), 0);
|
||||
const vis24 = q2024.reduce((s, r) => s + parseInt(r.visits || 0), 0);
|
||||
const vis25 = q2025.reduce((s, r) => s + parseInt(r.visits || 0), 0);
|
||||
const rev24 = q2024.reduce((s: number, r: MuseumRecord) => s + parseFloat(String(r[revenueField as keyof MuseumRecord] || r.revenue_incl_tax || 0)), 0);
|
||||
const rev25 = q2025.reduce((s: number, r: MuseumRecord) => s + parseFloat(String(r[revenueField as keyof MuseumRecord] || r.revenue_incl_tax || 0)), 0);
|
||||
const vis24 = q2024.reduce((s: number, r: MuseumRecord) => s + parseInt(String(r.visits || 0)), 0);
|
||||
const vis25 = q2025.reduce((s: number, r: MuseumRecord) => s + parseInt(String(r.visits || 0)), 0);
|
||||
const revChg = rev24 > 0 ? ((rev25 - rev24) / rev24 * 100) : 0;
|
||||
const visChg = vis24 > 0 ? ((vis25 - vis24) / vis24 * 100) : 0;
|
||||
const cap24 = umrahData[2024][q] ? (vis24 / umrahData[2024][q] * 100) : null;
|
||||
@@ -545,7 +546,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
tooltip: {
|
||||
...baseOptions.plugins.tooltip,
|
||||
callbacks: {
|
||||
label: (ctx) => {
|
||||
label: (ctx: { dataset: { label?: string }; parsed: { y: number } }) => {
|
||||
if (ctx.dataset.label === 'Capture Rate (%)') {
|
||||
return `Capture Rate: ${ctx.parsed.y.toFixed(2)}%`;
|
||||
}
|
||||
@@ -560,7 +561,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
grid: { color: chartColors.grid },
|
||||
ticks: { font: { size: 12 }, color: '#94a3b8', callback: (v) => v.toFixed(1) + '%' },
|
||||
ticks: { font: { size: 12 }, color: '#94a3b8', callback: (v: number | string) => Number(v).toFixed(1) + '%' },
|
||||
border: { display: false },
|
||||
title: { display: true, text: 'Capture Rate (%)', font: { size: 12 }, color: chartColors.secondary }
|
||||
},
|
||||
@@ -568,7 +569,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
grid: { drawOnChartArea: false },
|
||||
ticks: { font: { size: 12 }, color: '#94a3b8', callback: (v) => (v / 1000000).toFixed(0) + 'M' },
|
||||
ticks: { font: { size: 12 }, color: '#94a3b8', callback: (v: number | string) => (Number(v) / 1000000).toFixed(0) + 'M' },
|
||||
border: { display: false },
|
||||
title: { display: true, text: 'Pilgrims', font: { size: 12 }, color: chartColors.tertiary }
|
||||
}
|
||||
@@ -651,7 +652,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
tooltip: {
|
||||
...baseOptions.plugins.tooltip,
|
||||
callbacks: {
|
||||
label: (ctx) => {
|
||||
label: (ctx: { dataset: { label?: string }; parsed: { y: number } }) => {
|
||||
if (ctx.dataset.label === 'Capture Rate (%)') {
|
||||
return `Capture Rate: ${ctx.parsed.y.toFixed(2)}%`;
|
||||
}
|
||||
@@ -666,14 +667,14 @@ function Dashboard({ data, showDataLabels, setShowDataLabels, includeVAT, setInc
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
grid: { color: chartColors.grid },
|
||||
ticks: { font: { size: 13 }, color: '#94a3b8', callback: (v) => v.toFixed(1) + '%' },
|
||||
ticks: { font: { size: 13 }, color: '#94a3b8', callback: (v: number | string) => Number(v).toFixed(1) + '%' },
|
||||
border: { display: false }
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
grid: { drawOnChartArea: false },
|
||||
ticks: { font: { size: 13 }, color: '#94a3b8', callback: (v) => (v / 1000000).toFixed(0) + 'M' },
|
||||
ticks: { font: { size: 13 }, color: '#94a3b8', callback: (v: number | string) => (Number(v) / 1000000).toFixed(0) + 'M' },
|
||||
border: { display: false }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user