Restore working state from f17e19f (before mobile overhaul)
Reverting all my changes that broke the desktop layout. Starting fresh for mobile improvements.
This commit is contained in:
@@ -4,7 +4,6 @@ import { Line, Doughnut, Bar } from 'react-chartjs-2';
|
||||
import { Carousel, EmptyState, FilterControls, StatCard } from './shared';
|
||||
import { ExportableChart } from './ChartExport';
|
||||
import { chartColors, createBaseOptions } from '../config/chartConfig';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import {
|
||||
filterData,
|
||||
calculateMetrics,
|
||||
@@ -29,8 +28,7 @@ const defaultFilters = {
|
||||
|
||||
const filterKeys = ['year', 'district', 'museum', 'quarter'];
|
||||
|
||||
function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
const { t } = useLanguage();
|
||||
function Dashboard({ data, showDataLabels }) {
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
// Initialize filters from URL or defaults
|
||||
@@ -69,17 +67,17 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
|
||||
// Stat cards for carousel
|
||||
const statCards = useMemo(() => [
|
||||
{ title: t('metrics.totalRevenue'), value: formatCurrency(metrics.revenue), hasYoy: true },
|
||||
{ title: t('metrics.totalVisitors'), value: formatNumber(metrics.visitors) },
|
||||
{ title: t('metrics.totalTickets'), value: formatNumber(metrics.tickets) },
|
||||
{ title: t('metrics.avgRevenue'), value: formatCurrency(metrics.avgRevPerVisitor) }
|
||||
], [metrics, t]);
|
||||
{ title: 'Total Revenue', value: formatCurrency(metrics.revenue), hasYoy: true },
|
||||
{ title: 'Total Visitors', value: formatNumber(metrics.visitors) },
|
||||
{ title: 'Total Tickets', value: formatNumber(metrics.tickets) },
|
||||
{ title: 'Avg Rev/Visitor', value: formatCurrency(metrics.avgRevPerVisitor) }
|
||||
], [metrics]);
|
||||
|
||||
// Chart carousel labels
|
||||
const chartLabels = useMemo(() => {
|
||||
const labels = [t('charts.revenueTrend'), t('charts.visitors'), t('charts.revenue'), t('charts.quarterly'), t('charts.district'), t('charts.captureRate')];
|
||||
const labels = ['Revenue Trend', 'Visitors', 'Revenue', 'Quarterly', 'District', 'Capture Rate'];
|
||||
return filters.museum === 'all' ? labels : labels.filter((_, i) => i !== 1 && i !== 2);
|
||||
}, [filters.museum, t]);
|
||||
}, [filters.museum]);
|
||||
|
||||
// Dynamic lists from data
|
||||
const years = useMemo(() => getUniqueYears(data), [data]);
|
||||
@@ -250,7 +248,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
color: '#1e293b',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
borderRadius: 3,
|
||||
font: { size: 10, weight: 600 },
|
||||
font: { size: 9, weight: 600 },
|
||||
anchor: 'end',
|
||||
align: 'top',
|
||||
offset: 6
|
||||
@@ -276,7 +274,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
color: '#1e293b',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.9)',
|
||||
borderRadius: 3,
|
||||
font: { size: 10, weight: 600 },
|
||||
font: { size: 9, weight: 600 },
|
||||
anchor: 'start',
|
||||
align: 'bottom',
|
||||
offset: 6
|
||||
@@ -316,59 +314,50 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
const baseOptions = useMemo(() => createBaseOptions(showDataLabels), [showDataLabels]);
|
||||
|
||||
return (
|
||||
<div className="dashboard" id="dashboard-container">
|
||||
<div className="page-title-with-actions">
|
||||
<div className="page-title">
|
||||
<h1>{t('dashboard.title')}</h1>
|
||||
<p>{t('dashboard.subtitle')}</p>
|
||||
</div>
|
||||
<div className="toggle-with-label">
|
||||
<span className="toggle-text">{t('nav.labels')}</span>
|
||||
<div className="toggle-switch">
|
||||
<button className={!showDataLabels ? 'active' : ''} onClick={() => setShowDataLabels(false)}>{t('toggle.off')}</button>
|
||||
<button className={showDataLabels ? 'active' : ''} onClick={() => setShowDataLabels(true)}>{t('toggle.on')}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dashboard">
|
||||
<div className="page-title">
|
||||
<h1>Dashboard</h1>
|
||||
<p>Real-time museum analytics from Google Sheets</p>
|
||||
</div>
|
||||
|
||||
<FilterControls title={t('filters.title')} onReset={resetFilters}>
|
||||
<FilterControls title="Filters" onReset={resetFilters}>
|
||||
<FilterControls.Row>
|
||||
<FilterControls.Group label={t('filters.year')}>
|
||||
<FilterControls.Group label="Year">
|
||||
<select value={filters.year} onChange={e => setFilters({...filters, year: e.target.value})}>
|
||||
<option value="all">{t('filters.allYears')}</option>
|
||||
<option value="all">All Years</option>
|
||||
{years.map(y => <option key={y} value={y}>{y}</option>)}
|
||||
</select>
|
||||
</FilterControls.Group>
|
||||
<FilterControls.Group label={t('filters.district')}>
|
||||
<FilterControls.Group label="District">
|
||||
<select value={filters.district} onChange={e => setFilters({...filters, district: e.target.value, museum: 'all'})}>
|
||||
<option value="all">{t('filters.allDistricts')}</option>
|
||||
<option value="all">All Districts</option>
|
||||
{districts.map(d => <option key={d} value={d}>{d}</option>)}
|
||||
</select>
|
||||
</FilterControls.Group>
|
||||
<FilterControls.Group label={t('filters.museum')}>
|
||||
<FilterControls.Group label="Museum">
|
||||
<select value={filters.museum} onChange={e => setFilters({...filters, museum: e.target.value})}>
|
||||
<option value="all">{t('filters.allMuseums')}</option>
|
||||
<option value="all">All Museums</option>
|
||||
{availableMuseums.map(m => <option key={m} value={m}>{m}</option>)}
|
||||
</select>
|
||||
</FilterControls.Group>
|
||||
<FilterControls.Group label={t('filters.quarter')}>
|
||||
<FilterControls.Group label="Quarter">
|
||||
<select value={filters.quarter} onChange={e => setFilters({...filters, quarter: e.target.value})}>
|
||||
<option value="all">{t('filters.allQuarters')}</option>
|
||||
<option value="1">{t('time.q1')}</option>
|
||||
<option value="2">{t('time.q2')}</option>
|
||||
<option value="3">{t('time.q3')}</option>
|
||||
<option value="4">{t('time.q4')}</option>
|
||||
<option value="all">All Quarters</option>
|
||||
<option value="1">Q1</option>
|
||||
<option value="2">Q2</option>
|
||||
<option value="3">Q3</option>
|
||||
<option value="4">Q4</option>
|
||||
</select>
|
||||
</FilterControls.Group>
|
||||
</FilterControls.Row>
|
||||
</FilterControls>
|
||||
|
||||
{/* Desktop: Grid */}
|
||||
<div className="stats-grid desktop-only" id="dashboard-stats">
|
||||
<StatCard title={t('metrics.totalRevenue')} value={formatCurrency(metrics.revenue)} change={yoyChange} />
|
||||
<StatCard title={t('metrics.totalVisitors')} value={formatNumber(metrics.visitors)} />
|
||||
<StatCard title={t('metrics.totalTickets')} value={formatNumber(metrics.tickets)} />
|
||||
<StatCard title={t('metrics.avgRevenuePerVisitor')} value={formatCurrency(metrics.avgRevPerVisitor)} />
|
||||
<div className="stats-grid desktop-only">
|
||||
<StatCard title="Total Revenue" value={formatCurrency(metrics.revenue)} change={yoyChange} />
|
||||
<StatCard title="Total Visitors" value={formatNumber(metrics.visitors)} />
|
||||
<StatCard title="Total Tickets" value={formatNumber(metrics.tickets)} />
|
||||
<StatCard title="Avg Revenue/Visitor" value={formatCurrency(metrics.avgRevPerVisitor)} />
|
||||
</div>
|
||||
|
||||
{/* Mobile: Stats Carousel */}
|
||||
@@ -392,28 +381,28 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
{!hasData ? (
|
||||
<EmptyState
|
||||
icon="📊"
|
||||
title={t('dashboard.noData')}
|
||||
message={t('dashboard.noDataMessage')}
|
||||
title="No data found"
|
||||
message="No records match your current filters. Try adjusting your selection."
|
||||
action={resetFilters}
|
||||
actionLabel={t('filters.reset')}
|
||||
actionLabel="Reset Filters"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<div className="chart-card full-width" style={{marginBottom: '16px'}} id="quarterly-table">
|
||||
<h2>{t('dashboard.quarterlyComparison')}</h2>
|
||||
<div className="chart-card full-width" style={{marginBottom: '16px'}}>
|
||||
<h2>Quarterly Comparison: 2024 vs 2025</h2>
|
||||
<div className="table-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{t('table.quarter')}</th>
|
||||
<th>{t('table.rev2024')}</th>
|
||||
<th>{t('table.rev2025')}</th>
|
||||
<th>{t('table.change')}</th>
|
||||
<th>{t('table.visitors2024')}</th>
|
||||
<th>{t('table.visitors2025')}</th>
|
||||
<th>{t('table.change')}</th>
|
||||
<th>{t('table.capture2024')}</th>
|
||||
<th>{t('table.capture2025')}</th>
|
||||
<th>Quarter</th>
|
||||
<th>Rev 2024</th>
|
||||
<th>Rev 2025</th>
|
||||
<th>Change</th>
|
||||
<th>Visitors 2024</th>
|
||||
<th>Visitors 2025</th>
|
||||
<th>Change</th>
|
||||
<th>Capture 2024</th>
|
||||
<th>Capture 2025</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -440,58 +429,58 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
</div>
|
||||
|
||||
{/* Desktop: Charts Grid */}
|
||||
<div className="charts-grid desktop-only" id="dashboard-charts">
|
||||
<div className="charts-grid desktop-only">
|
||||
<div className="chart-card full-width">
|
||||
<ExportableChart
|
||||
filename="revenue-trend"
|
||||
title={t('dashboard.revenueTrends')}
|
||||
className="chart-container"
|
||||
controls={
|
||||
<div className="toggle-switch">
|
||||
<button className={trendGranularity === 'day' ? 'active' : ''} onClick={() => setTrendGranularity('day')}>{t('time.daily')}</button>
|
||||
<button className={trendGranularity === 'week' ? 'active' : ''} onClick={() => setTrendGranularity('week')}>{t('time.weekly')}</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<h2>Revenue Trends</h2>
|
||||
<div className="toggle-switch toggle-corner">
|
||||
<button className={trendGranularity === 'day' ? 'active' : ''} onClick={() => setTrendGranularity('day')}>Daily</button>
|
||||
<button className={trendGranularity === 'week' ? 'active' : ''} onClick={() => setTrendGranularity('week')}>Weekly</button>
|
||||
</div>
|
||||
<ExportableChart filename="revenue-trend" className="chart-container">
|
||||
<Line data={trendData} options={{...baseOptions, scales: {...baseOptions.scales, x: {...baseOptions.scales.x, ticks: {...baseOptions.scales.x.ticks, maxTicksLimit: trendGranularity === 'week' ? 15 : 20}}}}} />
|
||||
</ExportableChart>
|
||||
</div>
|
||||
|
||||
{filters.museum === 'all' && (
|
||||
<div className="chart-card half-width">
|
||||
<ExportableChart filename="visitors-by-museum" title={t('dashboard.visitorsByMuseum')} className="chart-container">
|
||||
<Doughnut data={museumData.visitors} options={{...baseOptions, plugins: {...baseOptions.plugins, legend: {display: true, position: 'bottom', labels: {boxWidth: 12, padding: 16, font: {size: 13}}}}}} />
|
||||
<h2>Visitors by Museum</h2>
|
||||
<ExportableChart filename="visitors-by-museum" className="chart-container">
|
||||
<Doughnut data={museumData.visitors} options={{...baseOptions, plugins: {...baseOptions.plugins, legend: {display: true, position: 'bottom', labels: {boxWidth: 12, padding: 16, font: {size: 11}}}}}} />
|
||||
</ExportableChart>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filters.museum === 'all' && (
|
||||
<div className="chart-card half-width">
|
||||
<ExportableChart filename="revenue-by-museum" title={t('dashboard.revenueByMuseum')} className="chart-container">
|
||||
<h2>Revenue by Museum</h2>
|
||||
<ExportableChart filename="revenue-by-museum" className="chart-container">
|
||||
<Bar data={museumData.revenue} options={baseOptions} />
|
||||
</ExportableChart>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="chart-card half-width">
|
||||
<ExportableChart filename="quarterly-yoy" title={t('dashboard.quarterlyRevenue')} className="chart-container">
|
||||
<Bar data={quarterlyYoYData} options={{...baseOptions, plugins: {...baseOptions.plugins, legend: {display: true, position: 'top', align: 'end', labels: {boxWidth: 12, padding: 12, font: {size: 13}}}}}} />
|
||||
<h2>Quarterly Revenue (YoY)</h2>
|
||||
<ExportableChart filename="quarterly-yoy" className="chart-container">
|
||||
<Bar data={quarterlyYoYData} options={{...baseOptions, plugins: {...baseOptions.plugins, legend: {display: true, position: 'top', align: 'end', labels: {boxWidth: 12, padding: 12, font: {size: 11}}}}}} />
|
||||
</ExportableChart>
|
||||
</div>
|
||||
|
||||
<div className="chart-card half-width">
|
||||
<ExportableChart filename="district-performance" title={t('dashboard.districtPerformance')} className="chart-container">
|
||||
<h2>District Performance</h2>
|
||||
<ExportableChart filename="district-performance" className="chart-container">
|
||||
<Bar data={districtData} options={{...baseOptions, indexAxis: 'y'}} />
|
||||
</ExportableChart>
|
||||
</div>
|
||||
|
||||
<div className="chart-card full-width">
|
||||
<ExportableChart filename="capture-rate" title={t('dashboard.captureRateChart')} className="chart-container">
|
||||
<h2>Capture Rate vs Umrah Pilgrims</h2>
|
||||
<ExportableChart filename="capture-rate" className="chart-container">
|
||||
<Line data={captureRateData} options={{
|
||||
...baseOptions,
|
||||
plugins: {
|
||||
...baseOptions.plugins,
|
||||
legend: { display: true, position: 'top', align: 'end', labels: { boxWidth: 12, padding: 12, font: { size: 13 } } },
|
||||
legend: { display: true, position: 'top', align: 'end', labels: { boxWidth: 12, padding: 12, font: { size: 11 } } },
|
||||
tooltip: {
|
||||
...baseOptions.plugins.tooltip,
|
||||
callbacks: {
|
||||
@@ -510,17 +499,17 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
grid: { color: chartColors.grid },
|
||||
ticks: { font: { size: 12 }, color: '#94a3b8', callback: (v) => v.toFixed(1) + '%' },
|
||||
ticks: { font: { size: 10 }, color: '#94a3b8', callback: (v) => v.toFixed(1) + '%' },
|
||||
border: { display: false },
|
||||
title: { display: true, text: 'Capture Rate (%)', font: { size: 12 }, color: chartColors.secondary }
|
||||
title: { display: true, text: 'Capture Rate (%)', font: { size: 10 }, color: chartColors.secondary }
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
position: 'right',
|
||||
grid: { drawOnChartArea: false },
|
||||
ticks: { font: { size: 12 }, color: '#94a3b8', callback: (v) => (v / 1000000).toFixed(0) + 'M' },
|
||||
ticks: { font: { size: 10 }, color: '#94a3b8', callback: (v) => (v / 1000000).toFixed(0) + 'M' },
|
||||
border: { display: false },
|
||||
title: { display: true, text: 'Pilgrims', font: { size: 12 }, color: chartColors.tertiary }
|
||||
title: { display: true, text: 'Pilgrims', font: { size: 10 }, color: chartColors.tertiary }
|
||||
}
|
||||
}
|
||||
}} />
|
||||
@@ -538,10 +527,10 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
>
|
||||
<div className="carousel-slide">
|
||||
<div className="chart-card">
|
||||
<h2>{t('dashboard.revenueTrends')}</h2>
|
||||
<h2>Revenue Trends</h2>
|
||||
<div className="toggle-switch toggle-corner">
|
||||
<button className={trendGranularity === 'day' ? 'active' : ''} onClick={() => setTrendGranularity('day')}>{t('time.daily')}</button>
|
||||
<button className={trendGranularity === 'week' ? 'active' : ''} onClick={() => setTrendGranularity('week')}>{t('time.weekly')}</button>
|
||||
<button className={trendGranularity === 'day' ? 'active' : ''} onClick={() => setTrendGranularity('day')}>Daily</button>
|
||||
<button className={trendGranularity === 'week' ? 'active' : ''} onClick={() => setTrendGranularity('week')}>Weekly</button>
|
||||
</div>
|
||||
<div className="chart-container">
|
||||
<Line data={trendData} options={{...baseOptions, scales: {...baseOptions.scales, x: {...baseOptions.scales.x, ticks: {...baseOptions.scales.x.ticks, maxTicksLimit: 8}}}}} />
|
||||
@@ -552,9 +541,9 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
{filters.museum === 'all' && (
|
||||
<div className="carousel-slide">
|
||||
<div className="chart-card">
|
||||
<h2>{t('dashboard.visitorsByMuseum')}</h2>
|
||||
<h2>Visitors by Museum</h2>
|
||||
<div className="chart-container">
|
||||
<Doughnut data={museumData.visitors} options={{...baseOptions, plugins: {...baseOptions.plugins, legend: {display: true, position: 'bottom', labels: {boxWidth: 12, padding: 12, font: {size: 12}}}}}} />
|
||||
<Doughnut data={museumData.visitors} options={{...baseOptions, plugins: {...baseOptions.plugins, legend: {display: true, position: 'bottom', labels: {boxWidth: 12, padding: 12, font: {size: 10}}}}}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -563,7 +552,7 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
{filters.museum === 'all' && (
|
||||
<div className="carousel-slide">
|
||||
<div className="chart-card">
|
||||
<h2>{t('dashboard.revenueByMuseum')}</h2>
|
||||
<h2>Revenue by Museum</h2>
|
||||
<div className="chart-container">
|
||||
<Bar data={museumData.revenue} options={baseOptions} />
|
||||
</div>
|
||||
@@ -573,16 +562,16 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
|
||||
<div className="carousel-slide">
|
||||
<div className="chart-card">
|
||||
<h2>{t('dashboard.quarterlyRevenue')}</h2>
|
||||
<h2>Quarterly Revenue (YoY)</h2>
|
||||
<div className="chart-container">
|
||||
<Bar data={quarterlyYoYData} options={{...baseOptions, plugins: {...baseOptions.plugins, legend: {display: true, position: 'top', align: 'end', labels: {boxWidth: 12, padding: 8, font: {size: 12}}}}}} />
|
||||
<Bar data={quarterlyYoYData} options={{...baseOptions, plugins: {...baseOptions.plugins, legend: {display: true, position: 'top', align: 'end', labels: {boxWidth: 12, padding: 8, font: {size: 10}}}}}} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="carousel-slide">
|
||||
<div className="chart-card">
|
||||
<h2>{t('dashboard.districtPerformance')}</h2>
|
||||
<h2>District Performance</h2>
|
||||
<div className="chart-container">
|
||||
<Bar data={districtData} options={{...baseOptions, indexAxis: 'y'}} />
|
||||
</div>
|
||||
@@ -591,13 +580,13 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
|
||||
<div className="carousel-slide">
|
||||
<div className="chart-card">
|
||||
<h2>{t('dashboard.captureRateChart')}</h2>
|
||||
<h2>Capture Rate vs Umrah Pilgrims</h2>
|
||||
<div className="chart-container">
|
||||
<Line data={captureRateData} options={{
|
||||
...baseOptions,
|
||||
plugins: {
|
||||
...baseOptions.plugins,
|
||||
legend: { display: true, position: 'top', align: 'end', labels: { boxWidth: 10, padding: 8, font: { size: 13 } } },
|
||||
legend: { display: true, position: 'top', align: 'end', labels: { boxWidth: 10, padding: 8, font: { size: 9 } } },
|
||||
tooltip: {
|
||||
...baseOptions.plugins.tooltip,
|
||||
callbacks: {
|
||||
@@ -616,14 +605,14 @@ function Dashboard({ data, showDataLabels, setShowDataLabels }) {
|
||||
type: 'linear',
|
||||
position: 'left',
|
||||
grid: { color: chartColors.grid },
|
||||
ticks: { font: { size: 13 }, color: '#94a3b8', callback: (v) => v.toFixed(1) + '%' },
|
||||
ticks: { font: { size: 9 }, color: '#94a3b8', callback: (v) => 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: 9 }, color: '#94a3b8', callback: (v) => (v / 1000000).toFixed(0) + 'M' },
|
||||
border: { display: false }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user