feat: merge event charts with metric toggle, add pie chart option to events and channels
All checks were successful
Deploy HiHala Dashboard / deploy (push) Successful in 8s
All checks were successful
Deploy HiHala Dashboard / deploy (push) Successful in 8s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import { useSearchParams, Link } from 'react-router-dom';
|
import { useSearchParams, Link } from 'react-router-dom';
|
||||||
import { Line, Bar } from 'react-chartjs-2';
|
import { Line, Bar, Pie } from 'react-chartjs-2';
|
||||||
import { Carousel, EmptyState, FilterControls, MultiSelect, StatCard } from './shared';
|
import { Carousel, EmptyState, FilterControls, MultiSelect, StatCard } from './shared';
|
||||||
import { ExportableChart } from './ChartExport';
|
import { ExportableChart } from './ChartExport';
|
||||||
import { chartColors, chartPalette, createBaseOptions } from '../config/chartConfig';
|
import { chartColors, chartPalette, createBaseOptions } from '../config/chartConfig';
|
||||||
@@ -79,6 +79,9 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
const [activeChart, setActiveChart] = useState(0);
|
const [activeChart, setActiveChart] = useState(0);
|
||||||
const [trendGranularity, setTrendGranularity] = useState('week');
|
const [trendGranularity, setTrendGranularity] = useState('week');
|
||||||
const [selectedSeason, setSelectedSeason] = useState<string>('');
|
const [selectedSeason, setSelectedSeason] = useState<string>('');
|
||||||
|
const [eventMetric, setEventMetric] = useState<'visitors' | 'revenue'>('revenue');
|
||||||
|
const [eventChartType, setEventChartType] = useState<'bar' | 'pie'>('bar');
|
||||||
|
const [channelChartType, setChannelChartType] = useState<'bar' | 'pie'>('bar');
|
||||||
|
|
||||||
const filteredData = useMemo(() => filterData(data, filters), [data, filters]);
|
const filteredData = useMemo(() => filterData(data, filters), [data, filters]);
|
||||||
|
|
||||||
@@ -400,6 +403,16 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
|
|
||||||
const baseOptions = useMemo(() => createBaseOptions(showDataLabels), [showDataLabels]);
|
const baseOptions = useMemo(() => createBaseOptions(showDataLabels), [showDataLabels]);
|
||||||
|
|
||||||
|
const pieOptions = useMemo(() => ({
|
||||||
|
responsive: true,
|
||||||
|
maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: true, position: 'right' as const, labels: { boxWidth: 12, padding: 10, font: { size: 11 }, color: '#64748b' } },
|
||||||
|
tooltip: baseOptions.plugins.tooltip,
|
||||||
|
datalabels: { display: false }
|
||||||
|
}
|
||||||
|
}), [baseOptions]);
|
||||||
|
|
||||||
// Season annotation bands for revenue trend chart
|
// Season annotation bands for revenue trend chart
|
||||||
const seasonAnnotations = useMemo(() => {
|
const seasonAnnotations = useMemo(() => {
|
||||||
const raw = trendData.rawDates;
|
const raw = trendData.rawDates;
|
||||||
@@ -601,14 +614,27 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="chart-card half-width">
|
<div className="chart-card half-width">
|
||||||
<ExportableChart filename="visitors-by-event" title={t('dashboard.visitorsByMuseum')} className="chart-container">
|
<ExportableChart
|
||||||
<Bar data={museumData.visitors} options={{...baseOptions, indexAxis: 'y'}} />
|
filename={eventMetric === 'visitors' ? 'visitors-by-event' : 'revenue-by-event'}
|
||||||
</ExportableChart>
|
title={eventMetric === 'visitors' ? t('dashboard.visitorsByMuseum') : t('dashboard.revenueByMuseum')}
|
||||||
</div>
|
className="chart-container"
|
||||||
|
controls={
|
||||||
<div className="chart-card half-width">
|
<div style={{ display: 'flex', gap: '6px' }}>
|
||||||
<ExportableChart filename="revenue-by-event" title={t('dashboard.revenueByMuseum')} className="chart-container">
|
<div className="toggle-switch">
|
||||||
<Bar data={museumData.revenue} options={{...baseOptions, indexAxis: 'y'}} />
|
<button className={eventMetric === 'visitors' ? 'active' : ''} onClick={() => setEventMetric('visitors')}>{t('dashboard.visitors')}</button>
|
||||||
|
<button className={eventMetric === 'revenue' ? 'active' : ''} onClick={() => setEventMetric('revenue')}>{t('dashboard.revenue')}</button>
|
||||||
|
</div>
|
||||||
|
<div className="toggle-switch">
|
||||||
|
<button className={eventChartType === 'bar' ? 'active' : ''} onClick={() => setEventChartType('bar')}>{t('presentation.bar')}</button>
|
||||||
|
<button className={eventChartType === 'pie' ? 'active' : ''} onClick={() => setEventChartType('pie')}>{t('presentation.pie')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{eventChartType === 'bar'
|
||||||
|
? <Bar data={museumData[eventMetric]} options={{...baseOptions, indexAxis: 'y'}} />
|
||||||
|
: <Pie data={museumData[eventMetric]} options={pieOptions} />
|
||||||
|
}
|
||||||
</ExportableChart>
|
</ExportableChart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -619,8 +645,21 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="chart-card half-width">
|
<div className="chart-card half-width">
|
||||||
<ExportableChart filename="channel-performance" title={t('dashboard.channelPerformance')} className="chart-container">
|
<ExportableChart
|
||||||
<Bar data={channelData} options={{...baseOptions, indexAxis: 'y'}} />
|
filename="channel-performance"
|
||||||
|
title={t('dashboard.channelPerformance')}
|
||||||
|
className="chart-container"
|
||||||
|
controls={
|
||||||
|
<div className="toggle-switch">
|
||||||
|
<button className={channelChartType === 'bar' ? 'active' : ''} onClick={() => setChannelChartType('bar')}>{t('presentation.bar')}</button>
|
||||||
|
<button className={channelChartType === 'pie' ? 'active' : ''} onClick={() => setChannelChartType('pie')}>{t('presentation.pie')}</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{channelChartType === 'bar'
|
||||||
|
? <Bar data={channelData} options={{...baseOptions, indexAxis: 'y'}} />
|
||||||
|
: <Pie data={channelData} options={pieOptions} />
|
||||||
|
}
|
||||||
</ExportableChart>
|
</ExportableChart>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -696,18 +735,22 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
|
|
||||||
<div className="carousel-slide">
|
<div className="carousel-slide">
|
||||||
<div className="chart-card">
|
<div className="chart-card">
|
||||||
<h2>{t('dashboard.visitorsByMuseum')}</h2>
|
<h2>{eventMetric === 'visitors' ? t('dashboard.visitorsByMuseum') : t('dashboard.revenueByMuseum')}</h2>
|
||||||
<div className="chart-container">
|
<div style={{ display: 'flex', gap: '6px', marginBottom: '8px' }}>
|
||||||
<Bar data={museumData.visitors} options={{...baseOptions, indexAxis: 'y'}} />
|
<div className="toggle-switch">
|
||||||
|
<button className={eventMetric === 'visitors' ? 'active' : ''} onClick={() => setEventMetric('visitors')}>{t('dashboard.visitors')}</button>
|
||||||
|
<button className={eventMetric === 'revenue' ? 'active' : ''} onClick={() => setEventMetric('revenue')}>{t('dashboard.revenue')}</button>
|
||||||
|
</div>
|
||||||
|
<div className="toggle-switch">
|
||||||
|
<button className={eventChartType === 'bar' ? 'active' : ''} onClick={() => setEventChartType('bar')}>{t('presentation.bar')}</button>
|
||||||
|
<button className={eventChartType === 'pie' ? 'active' : ''} onClick={() => setEventChartType('pie')}>{t('presentation.pie')}</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="carousel-slide">
|
|
||||||
<div className="chart-card">
|
|
||||||
<h2>{t('dashboard.revenueByMuseum')}</h2>
|
|
||||||
<div className="chart-container">
|
<div className="chart-container">
|
||||||
<Bar data={museumData.revenue} options={{...baseOptions, indexAxis: 'y'}} />
|
{eventChartType === 'bar'
|
||||||
|
? <Bar data={museumData[eventMetric]} options={{...baseOptions, indexAxis: 'y'}} />
|
||||||
|
: <Pie data={museumData[eventMetric]} options={pieOptions} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -724,8 +767,15 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
<div className="carousel-slide">
|
<div className="carousel-slide">
|
||||||
<div className="chart-card">
|
<div className="chart-card">
|
||||||
<h2>{t('dashboard.channelPerformance')}</h2>
|
<h2>{t('dashboard.channelPerformance')}</h2>
|
||||||
|
<div className="toggle-switch" style={{ marginBottom: '8px' }}>
|
||||||
|
<button className={channelChartType === 'bar' ? 'active' : ''} onClick={() => setChannelChartType('bar')}>{t('presentation.bar')}</button>
|
||||||
|
<button className={channelChartType === 'pie' ? 'active' : ''} onClick={() => setChannelChartType('pie')}>{t('presentation.pie')}</button>
|
||||||
|
</div>
|
||||||
<div className="chart-container">
|
<div className="chart-container">
|
||||||
<Bar data={channelData} options={{...baseOptions, indexAxis: 'y'}} />
|
{channelChartType === 'bar'
|
||||||
|
? <Bar data={channelData} options={{...baseOptions, indexAxis: 'y'}} />
|
||||||
|
: <Pie data={channelData} options={pieOptions} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
PointElement,
|
PointElement,
|
||||||
LineElement,
|
LineElement,
|
||||||
BarElement,
|
BarElement,
|
||||||
|
ArcElement,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
@@ -21,7 +21,7 @@ ChartJS.register(
|
|||||||
PointElement,
|
PointElement,
|
||||||
LineElement,
|
LineElement,
|
||||||
BarElement,
|
BarElement,
|
||||||
|
ArcElement,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
Legend,
|
||||||
|
|||||||
@@ -137,6 +137,8 @@
|
|||||||
"addFirst": "أضف شريحتك الأولى",
|
"addFirst": "أضف شريحتك الأولى",
|
||||||
"slideTitle": "عنوان الشريحة",
|
"slideTitle": "عنوان الشريحة",
|
||||||
"chartType": "نوع الرسم البياني",
|
"chartType": "نوع الرسم البياني",
|
||||||
|
"bar": "أعمدة",
|
||||||
|
"pie": "دائري",
|
||||||
"metric": "المقياس",
|
"metric": "المقياس",
|
||||||
"startDate": "تاريخ البداية",
|
"startDate": "تاريخ البداية",
|
||||||
"endDate": "تاريخ النهاية",
|
"endDate": "تاريخ النهاية",
|
||||||
|
|||||||
@@ -137,6 +137,8 @@
|
|||||||
"addFirst": "Add your first slide",
|
"addFirst": "Add your first slide",
|
||||||
"slideTitle": "Slide Title",
|
"slideTitle": "Slide Title",
|
||||||
"chartType": "Chart Type",
|
"chartType": "Chart Type",
|
||||||
|
"bar": "Bar",
|
||||||
|
"pie": "Pie",
|
||||||
"metric": "Metric",
|
"metric": "Metric",
|
||||||
"startDate": "Start Date",
|
"startDate": "Start Date",
|
||||||
"endDate": "End Date",
|
"endDate": "End Date",
|
||||||
|
|||||||
Reference in New Issue
Block a user