feat: add % toggle to channel performance, default events and channel to pie chart
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:
@@ -80,8 +80,9 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
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 [eventMetric, setEventMetric] = useState<'visitors' | 'revenue'>('revenue');
|
||||||
const [eventChartType, setEventChartType] = useState<'bar' | 'pie'>('bar');
|
const [eventChartType, setEventChartType] = useState<'bar' | 'pie'>('pie');
|
||||||
const [channelChartType, setChannelChartType] = useState<'bar' | 'pie'>('bar');
|
const [channelChartType, setChannelChartType] = useState<'bar' | 'pie'>('pie');
|
||||||
|
const [channelDisplayMode, setChannelDisplayMode] = useState<'absolute' | 'percent'>('absolute');
|
||||||
|
|
||||||
const filteredData = useMemo(() => filterData(data, filters), [data, filters]);
|
const filteredData = useMemo(() => filterData(data, filters), [data, filters]);
|
||||||
|
|
||||||
@@ -253,6 +254,16 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
};
|
};
|
||||||
}, [seasonFilteredData, includeVAT]);
|
}, [seasonFilteredData, includeVAT]);
|
||||||
|
|
||||||
|
const channelChartData = useMemo(() => {
|
||||||
|
if (channelDisplayMode === 'absolute') return channelData;
|
||||||
|
const total = channelData.datasets[0].data.reduce((s: number, v: number) => s + v, 0);
|
||||||
|
if (total === 0) return channelData;
|
||||||
|
return {
|
||||||
|
...channelData,
|
||||||
|
datasets: [{ ...channelData.datasets[0], data: channelData.datasets[0].data.map((v: number) => parseFloat(((v / total) * 100).toFixed(1))) }]
|
||||||
|
};
|
||||||
|
}, [channelData, channelDisplayMode]);
|
||||||
|
|
||||||
// District data
|
// District data
|
||||||
const districtData = useMemo(() => {
|
const districtData = useMemo(() => {
|
||||||
const grouped = groupByDistrict(seasonFilteredData, includeVAT);
|
const grouped = groupByDistrict(seasonFilteredData, includeVAT);
|
||||||
@@ -650,15 +661,35 @@ function Dashboard({ data, seasons, userRole, showDataLabels, setShowDataLabels,
|
|||||||
title={t('dashboard.channelPerformance')}
|
title={t('dashboard.channelPerformance')}
|
||||||
className="chart-container"
|
className="chart-container"
|
||||||
controls={
|
controls={
|
||||||
<div className="toggle-switch">
|
<div style={{ display: 'flex', gap: '6px' }}>
|
||||||
<button className={channelChartType === 'bar' ? 'active' : ''} onClick={() => setChannelChartType('bar')}>{t('metrics.bar')}</button>
|
<div className="toggle-switch">
|
||||||
<button className={channelChartType === 'pie' ? 'active' : ''} onClick={() => setChannelChartType('pie')}>{t('metrics.pie')}</button>
|
<button className={channelChartType === 'bar' ? 'active' : ''} onClick={() => setChannelChartType('bar')}>{t('metrics.bar')}</button>
|
||||||
|
<button className={channelChartType === 'pie' ? 'active' : ''} onClick={() => setChannelChartType('pie')}>{t('metrics.pie')}</button>
|
||||||
|
</div>
|
||||||
|
<div className="toggle-switch">
|
||||||
|
<button className={channelDisplayMode === 'absolute' ? 'active' : ''} onClick={() => setChannelDisplayMode('absolute')}>#</button>
|
||||||
|
<button className={channelDisplayMode === 'percent' ? 'active' : ''} onClick={() => setChannelDisplayMode('percent')}>%</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{channelChartType === 'bar'
|
{channelChartType === 'bar'
|
||||||
? <Bar data={channelData} options={{...baseOptions, indexAxis: 'y'}} />
|
? <Bar data={channelChartData} options={{
|
||||||
: <Pie data={channelData} options={pieOptions} />
|
...baseOptions,
|
||||||
|
indexAxis: 'y',
|
||||||
|
plugins: { ...baseOptions.plugins, datalabels: { ...baseOptions.plugins.datalabels, formatter: (v: number) => channelDisplayMode === 'percent' ? v.toFixed(1) + '%' : baseOptions.plugins.datalabels.formatter(v, {} as any) } },
|
||||||
|
scales: { ...baseOptions.scales, x: { ...baseOptions.scales.x, ticks: { ...baseOptions.scales.x.ticks, callback: (v: number | string) => channelDisplayMode === 'percent' ? v + '%' : v } } }
|
||||||
|
}} />
|
||||||
|
: <Pie data={channelChartData} options={{
|
||||||
|
...pieOptions,
|
||||||
|
plugins: {
|
||||||
|
...pieOptions.plugins,
|
||||||
|
datalabels: channelDisplayMode === 'percent'
|
||||||
|
? { display: true, color: '#fff', font: { size: 11, weight: 'bold' as const }, formatter: (v: number) => v > 3 ? v.toFixed(1) + '%' : '' }
|
||||||
|
: { display: false },
|
||||||
|
tooltip: { ...pieOptions.plugins.tooltip, callbacks: { label: (ctx: any) => channelDisplayMode === 'percent' ? ` ${ctx.parsed.toFixed(1)}%` : ` ${formatCurrency(ctx.parsed)}` } }
|
||||||
|
}
|
||||||
|
}} />
|
||||||
}
|
}
|
||||||
</ExportableChart>
|
</ExportableChart>
|
||||||
</div>
|
</div>
|
||||||
@@ -767,14 +798,29 @@ 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' }}>
|
<div style={{ display: 'flex', gap: '6px', marginBottom: '8px' }}>
|
||||||
<button className={channelChartType === 'bar' ? 'active' : ''} onClick={() => setChannelChartType('bar')}>{t('metrics.bar')}</button>
|
<div className="toggle-switch">
|
||||||
<button className={channelChartType === 'pie' ? 'active' : ''} onClick={() => setChannelChartType('pie')}>{t('metrics.pie')}</button>
|
<button className={channelChartType === 'bar' ? 'active' : ''} onClick={() => setChannelChartType('bar')}>{t('metrics.bar')}</button>
|
||||||
|
<button className={channelChartType === 'pie' ? 'active' : ''} onClick={() => setChannelChartType('pie')}>{t('metrics.pie')}</button>
|
||||||
|
</div>
|
||||||
|
<div className="toggle-switch">
|
||||||
|
<button className={channelDisplayMode === 'absolute' ? 'active' : ''} onClick={() => setChannelDisplayMode('absolute')}>#</button>
|
||||||
|
<button className={channelDisplayMode === 'percent' ? 'active' : ''} onClick={() => setChannelDisplayMode('percent')}>%</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="chart-container">
|
<div className="chart-container">
|
||||||
{channelChartType === 'bar'
|
{channelChartType === 'bar'
|
||||||
? <Bar data={channelData} options={{...baseOptions, indexAxis: 'y'}} />
|
? <Bar data={channelChartData} options={{...baseOptions, indexAxis: 'y'}} />
|
||||||
: <Pie data={channelData} options={pieOptions} />
|
: <Pie data={channelChartData} options={{
|
||||||
|
...pieOptions,
|
||||||
|
plugins: {
|
||||||
|
...pieOptions.plugins,
|
||||||
|
datalabels: channelDisplayMode === 'percent'
|
||||||
|
? { display: true, color: '#fff', font: { size: 11, weight: 'bold' as const }, formatter: (v: number) => v > 3 ? v.toFixed(1) + '%' : '' }
|
||||||
|
: { display: false },
|
||||||
|
tooltip: { ...pieOptions.plugins.tooltip, callbacks: { label: (ctx: any) => channelDisplayMode === 'percent' ? ` ${ctx.parsed.toFixed(1)}%` : ` ${formatCurrency(ctx.parsed)}` } }
|
||||||
|
}
|
||||||
|
}} />
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user