feat(charts): show actual dates in trend chart tooltips
Deploy HiHala Dashboard / deploy (push) Successful in 11s

Replace opaque W1/D1/month abbreviation tooltip titles with human-readable
period labels (e.g. "Week 1 · 1 Apr – 7 Apr", "1 April 2025", "April 2025")
in both Dashboard and Comparison trend charts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
fahed
2026-04-30 10:37:05 +03:00
parent c858075232
commit 1070490ad2
2 changed files with 79 additions and 16 deletions
+40 -8
View File
@@ -88,7 +88,7 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
return rows.reduce((s,r) => s + parseFloat(String((r as any)[f[m]]||0)), 0);
}, [revenueField]);
const trendData = useMemo(() => {
const trendResult = useMemo(() => {
const group = (rows: MuseumRecord[], ps: string) => {
const s = new Date(ps); const acc: Record<number, MuseumRecord[]> = {};
rows.forEach(r => {
@@ -102,18 +102,37 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
};
const pg = group(prevData, prevStart), cg = group(filteredData, start);
const maxK = Math.max(...Object.keys(pg).map(Number), ...Object.keys(cg).map(Number), 1);
const s0 = new Date(start);
const fmt = (d: Date) => d.toLocaleDateString('en-GB', { day: 'numeric', month: 'short' });
const labels = Array.from({length:maxK}, (_,i) =>
gran==='week' ? `W${i+1}` : gran==='month' ? L.monthShort[(new Date(start).getMonth()+i)%12] : `D${i+1}`
gran==='week' ? `W${i+1}` : gran==='month' ? L.monthShort[(s0.getMonth()+i)%12] : `D${i+1}`
);
const tooltipLabels = Array.from({length:maxK}, (_,i) => {
if (gran==='week') {
const ws = new Date(s0.getTime() + i * 7 * 86400000);
const we = new Date(s0.getTime() + (i+1) * 7 * 86400000 - 86400000);
return `Week ${i+1} · ${fmt(ws)} ${fmt(we)}`;
}
if (gran==='month') {
const ms = new Date(s0.getFullYear(), s0.getMonth() + i, 1);
return ms.toLocaleDateString('en-GB', { month: 'long', year: 'numeric' });
}
const ds = new Date(s0.getTime() + i * 86400000);
return ds.toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' });
});
const prevYear = parseInt(start.slice(0,4))-1;
return {
labels,
datasets: [
{ label:`${prevYear}`, data:labels.map((_,i) => pg[i+1]||0), borderColor:chartColors.muted, backgroundColor:'transparent', borderWidth:1.5, tension:0.4, pointRadius:0, borderDash:[4,3] },
{ label:start.slice(0,4), data:labels.map((_,i) => cg[i+1]||0), borderColor:chartColors.primary, backgroundColor:chartColors.primary+'18', borderWidth:2.5, tension:0.4, fill:true, pointRadius:gran==='week'?3:1, pointBackgroundColor:chartColors.primary },
]
tooltipLabels,
data: {
labels,
datasets: [
{ label:`${prevYear}`, data:labels.map((_,i) => pg[i+1]||0), borderColor:chartColors.muted, backgroundColor:'transparent', borderWidth:1.5, tension:0.4, pointRadius:0, borderDash:[4,3] },
{ label:start.slice(0,4), data:labels.map((_,i) => cg[i+1]||0), borderColor:chartColors.primary, backgroundColor:chartColors.primary+'18', borderWidth:2.5, tension:0.4, fill:true, pointRadius:gran==='week'?3:1, pointBackgroundColor:chartColors.primary },
]
}
};
}, [filteredData, prevData, prevStart, start, metric, gran, getVal, L]);
const trendData = trendResult.data;
const museumData = useMemo(() => {
const g = groupByMuseum(filteredData, includeVAT);
@@ -172,6 +191,19 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
const barNoLegend: any = { ...chartOpts, plugins:{ ...chartOpts.plugins, legend:{ display:false } } };
return { chartOpts, barHorizOpts, barNoLegend };
}, [baseOpts]);
const trendOpts: any = useMemo(() => ({
...chartOpts,
plugins: {
...chartOpts.plugins,
tooltip: {
...chartOpts.plugins.tooltip,
callbacks: {
title: (items: any[]) => trendResult.tooltipLabels[items[0]?.dataIndex] ?? items[0]?.label,
}
}
}
}), [chartOpts, trendResult.tooltipLabels]);
const pieOptions: any = useMemo(() => ({
responsive: true, maintainAspectRatio: false,
plugins: {
@@ -250,7 +282,7 @@ export default function DashboardDemo({ data, seasons: _seasons, includeVAT, set
{granOpts.map(o => <button key={o.value} type="button" aria-pressed={gran===o.value} className={`alt-ctrl${gran===o.value?' alt-ctrl-on':''}`} onClick={() => setGran(o.value)}>{o.label}</button>)}
</div>
</div>
<div className="alt-chart-wrap alt-chart-wrap--tall"><Line data={trendData} options={chartOpts} /></div>
<div className="alt-chart-wrap alt-chart-wrap--tall"><Line data={trendData} options={trendOpts} /></div>
</div>
<div className="alt-chart-card">