From 6a83109e1c844cc2316b497a04a14c3f15edb5f2 Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Sat, 31 Jan 2026 12:51:47 +0100 Subject: [PATCH] Add metric tooltips showing comparison details (vs previous week) --- components/WeeklyGrid.tsx | 142 +++++++++++++++++++++++++++++++++----- 1 file changed, 123 insertions(+), 19 deletions(-) diff --git a/components/WeeklyGrid.tsx b/components/WeeklyGrid.tsx index 7b3cf38..209c546 100644 --- a/components/WeeklyGrid.tsx +++ b/components/WeeklyGrid.tsx @@ -50,6 +50,71 @@ const useDebounce = (value: string, delay: number) => { return debouncedValue; }; +// Tooltip component to show comparison details on hover +const MetricDetailTooltip: React.FC<{ + children: React.ReactNode; + currentValue: number; + previousValue: number; + currentWeekLabel: string; + previousWeekLabel: string; + metricName: string; + metricColor: string; + formatValue?: (val: number) => string; +}> = ({ children, currentValue, previousValue, currentWeekLabel, previousWeekLabel, metricName, metricColor, formatValue }) => { + const [isVisible, setIsVisible] = useState(false); + + const growth = previousValue > 0 ? ((currentValue - previousValue) / previousValue) * 100 : (currentValue > 0 ? 100 : 0); + const isPositive = growth >= 0; + const format = formatValue || ((v: number) => v.toLocaleString('de-DE')); + + return ( +
setIsVisible(true)} + onMouseLeave={() => setIsVisible(false)} + > +
{children}
+ {isVisible && ( +
+
+ 📊 {metricName} Comparison +
+ +
+ {/* Current Week */} +
+ {currentWeekLabel} + {format(currentValue)} +
+ + {/* Previous Week */} +
+ {previousWeekLabel} + {format(previousValue)} +
+ + {/* Divider */} +
+
+ vs Semana Anterior + + {isPositive ? 'â–²' : 'â–¼'} {Math.abs(growth).toFixed(1)}% + +
+
+ Comparado con la semana inmediatamente anterior +
+
+
+ + {/* Arrow */} +
+
+ )} +
+ ); +}; + const WeeklyRow: React.FC<{ row: WeeklyPivotRow; weeks: string[]; @@ -116,31 +181,70 @@ const WeeklyRow: React.FC<{ const prevVal = row.unitsByWeek[weeks[idx + 1]] || 0; const spend = row.spendByWeek[week] || 0; const prevSpend = row.spendByWeek[weeks[idx + 1]] || 0; + const gv = row.gvByWeek?.[week] || 0; + const prevGv = row.gvByWeek?.[weeks[idx + 1]] || 0; + + const weekNum = week.split('-')[1]; + const prevWeekNum = weeks[idx + 1]?.split('-')[1] || '-'; + return (
-
- 0 ? (sortConfig?.key === week && sortConfig.metric === 'units' ? 'text-indigo-400' : 'text-white') : 'text-slate-700'}`}> - {val > 0 ? val.toLocaleString('de-DE') : '-'} - - {val > 0 && renderGrowth(val, prevVal)} -
+ {/* Units with tooltip */} + +
+ 0 ? (sortConfig?.key === week && sortConfig.metric === 'units' ? 'text-indigo-400' : 'text-white') : 'text-slate-700'}`}> + {val > 0 ? val.toLocaleString('de-DE') : '-'} + + {val > 0 && renderGrowth(val, prevVal)} +
+
+
+ {/* Spend with tooltip */} {spend > 0 && ( -
- - €{spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} - - {renderGrowth(spend, prevSpend)} -
+ `€${v.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}`} + > +
+ + €{spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })} + + {renderGrowth(spend, prevSpend)} +
+
)} - {row.gvByWeek?.[week] > 0 && ( -
- - GV: {row.gvByWeek[week].toLocaleString('de-DE')} - - {renderGrowth(row.gvByWeek[week], row.gvByWeek?.[weeks[idx + 1]] || 0)} -
+ + {/* GV with tooltip */} + {gv > 0 && ( + +
+ + GV: {gv.toLocaleString('de-DE')} + + {renderGrowth(gv, prevGv)} +
+
)}