mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 15:55:23 +02:00
feat: implement per-column metric sorting in Weekly Sales grid
- Removed global Units/Spend toggle. - Added separate clickable 'Units' and 'Spend' buttons in each week header. - Updated handleSort and sort indicators for per-metric control. - Fixed code duplication in WeeklyGrid.tsx.
This commit is contained in:
+36
-37
@@ -19,7 +19,6 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
|
|||||||
|
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [activeMetric, setActiveMetric] = useState<'units' | 'spend'>('units');
|
|
||||||
|
|
||||||
// Default sort: most recent week, descending, units
|
// Default sort: most recent week, descending, units
|
||||||
const [sortConfig, setSortConfig] = useState<SortConfig>(() => {
|
const [sortConfig, setSortConfig] = useState<SortConfig>(() => {
|
||||||
@@ -34,14 +33,14 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
|
|||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
}, [searchTerm]);
|
}, [searchTerm]);
|
||||||
|
|
||||||
const handleSort = (weekKey: string) => {
|
const handleSort = (weekKey: string, metric: 'units' | 'spend') => {
|
||||||
setSortConfig(prev => {
|
setSortConfig(prev => {
|
||||||
if (prev?.key === weekKey && prev.metric === activeMetric) {
|
if (prev?.key === weekKey && prev.metric === metric) {
|
||||||
// Just toggle direction if already sorting by this week and metric
|
// Toggle direction if same week & same metric
|
||||||
return { key: weekKey, direction: prev.direction === 'asc' ? 'desc' : 'asc', metric: activeMetric };
|
return { key: weekKey, direction: prev.direction === 'asc' ? 'desc' : 'asc', metric };
|
||||||
}
|
}
|
||||||
// Start fresh with DESC for the current active metric
|
// Switch to new week/metric with default DESC
|
||||||
return { key: weekKey, direction: 'desc', metric: activeMetric };
|
return { key: weekKey, direction: 'desc', metric };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,7 +80,7 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
|
|||||||
|
|
||||||
const totalPages = Math.ceil(sortedRows.length / ROWS_PER_PAGE);
|
const totalPages = Math.ceil(sortedRows.length / ROWS_PER_PAGE);
|
||||||
|
|
||||||
// Calculate totals per week (always based on full filtered dataset, not just page)
|
// Calculate totals per week
|
||||||
const weekTotals = useMemo(() => {
|
const weekTotals = useMemo(() => {
|
||||||
const totals: { [weekKey: string]: { units: number, spend: number } } = {};
|
const totals: { [weekKey: string]: { units: number, spend: number } } = {};
|
||||||
weeks.forEach(week => {
|
weeks.forEach(week => {
|
||||||
@@ -108,9 +107,8 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 animate-fade-in">
|
<div className="flex flex-col gap-4 animate-fade-in">
|
||||||
{/* Toolbar: Search & Metric Toggle & Pagination */}
|
{/* Toolbar: Search & Pagination */}
|
||||||
<div className="flex flex-col lg:flex-row justify-between items-center gap-4 bg-slate-900/50 p-4 border border-white/10 rounded-xl">
|
<div className="flex flex-col lg:flex-row justify-between items-center gap-4 bg-slate-900/50 p-4 border border-white/10 rounded-xl">
|
||||||
<div className="flex flex-col md:flex-row items-center gap-4 w-full lg:w-auto">
|
|
||||||
<div className="relative w-full md:w-80">
|
<div className="relative w-full md:w-80">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -124,22 +122,6 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center bg-slate-950 p-1 rounded-lg border border-white/10">
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveMetric('units')}
|
|
||||||
className={`px-4 py-1.5 rounded-md text-xs font-black uppercase tracking-widest transition-all ${activeMetric === 'units' ? 'bg-indigo-500 text-white shadow-lg' : 'text-slate-500 hover:text-slate-300'}`}
|
|
||||||
>
|
|
||||||
Units
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setActiveMetric('spend')}
|
|
||||||
className={`px-4 py-1.5 rounded-md text-xs font-black uppercase tracking-widest transition-all ${activeMetric === 'spend' ? 'bg-indigo-500 text-white shadow-lg' : 'text-slate-500 hover:text-slate-300'}`}
|
|
||||||
>
|
|
||||||
Spend
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<span className="text-sm text-slate-500">
|
<span className="text-sm text-slate-500">
|
||||||
Showing {Math.min(sortedRows.length, (currentPage - 1) * ROWS_PER_PAGE + 1)}-{Math.min(sortedRows.length, currentPage * ROWS_PER_PAGE)} of {sortedRows.length}
|
Showing {Math.min(sortedRows.length, (currentPage - 1) * ROWS_PER_PAGE + 1)}-{Math.min(sortedRows.length, currentPage * ROWS_PER_PAGE)} of {sortedRows.length}
|
||||||
@@ -175,18 +157,35 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
|
|||||||
{weeks.map(week => (
|
{weeks.map(week => (
|
||||||
<th
|
<th
|
||||||
key={week}
|
key={week}
|
||||||
className={`p-3 text-[10px] font-black uppercase tracking-widest text-center border-r border-white/10 min-w-[120px] cursor-pointer hover:bg-white/5 transition-colors select-none ${sortConfig?.key === week ? 'text-indigo-400 bg-white/[0.02]' : 'text-slate-400'}`}
|
className={`p-0 text-[10px] font-black uppercase tracking-widest text-center border-r border-white/10 min-w-[130px] transition-colors select-none ${sortConfig?.key === week ? 'bg-white/[0.02]' : ''}`}
|
||||||
onClick={() => handleSort(week)}
|
|
||||||
>
|
>
|
||||||
<div className="flex flex-col items-center gap-1">
|
<div className="flex flex-col h-full">
|
||||||
<span className="text-xs">{week.split('-')[1]}/{week.split('-')[0].slice(-2)}</span>
|
{/* Week Label */}
|
||||||
{sortConfig?.key === week && (
|
<div className="p-2 border-b border-white/5 bg-slate-800/30 text-xs text-white">
|
||||||
<div className="flex flex-col items-center -mt-0.5">
|
{week.split('-')[1]}/{week.split('-')[0].slice(-2)}
|
||||||
<span className="text-[12px] leading-none">{sortConfig.direction === 'asc' ? '↑' : '↓'}</span>
|
|
||||||
<span className="text-[7px] font-bold text-indigo-500 uppercase">{sortConfig.metric}</span>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Units Sort Trigger */}
|
||||||
|
<div
|
||||||
|
onClick={() => handleSort(week, 'units')}
|
||||||
|
className={`flex-1 p-1.5 cursor-pointer hover:bg-indigo-500/10 transition-colors flex items-center justify-center gap-1 border-b border-white/5 ${sortConfig?.key === week && sortConfig.metric === 'units' ? 'bg-indigo-500/5 text-indigo-400' : 'text-slate-500 hover:text-slate-300'}`}
|
||||||
|
>
|
||||||
|
<span className="text-[9px]">Units</span>
|
||||||
|
{sortConfig?.key === week && sortConfig.metric === 'units' && (
|
||||||
|
<span className="text-xs font-bold leading-none">{sortConfig.direction === 'asc' ? '↑' : '↓'}</span>
|
||||||
)}
|
)}
|
||||||
<div className="text-[8px] font-normal text-slate-500">Units / Spend</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Spend Sort Trigger */}
|
||||||
|
<div
|
||||||
|
onClick={() => handleSort(week, 'spend')}
|
||||||
|
className={`flex-1 p-1.5 cursor-pointer hover:bg-amber-500/10 transition-colors flex items-center justify-center gap-1 ${sortConfig?.key === week && sortConfig.metric === 'spend' ? 'bg-amber-500/5 text-amber-400' : 'text-slate-500 hover:text-slate-300'}`}
|
||||||
|
>
|
||||||
|
<span className="text-[9px]">Spend</span>
|
||||||
|
{sortConfig?.key === week && sortConfig.metric === 'spend' && (
|
||||||
|
<span className="text-xs font-bold leading-none">{sortConfig.direction === 'asc' ? '↑' : '↓'}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
@@ -200,7 +199,7 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
|
|||||||
<span>{weekTotals[week].units.toLocaleString('de-DE')}</span>
|
<span>{weekTotals[week].units.toLocaleString('de-DE')}</span>
|
||||||
{renderGrowth(weekTotals[week].units, weekTotals[weeks[idx + 1]]?.units)}
|
{renderGrowth(weekTotals[week].units, weekTotals[weeks[idx + 1]]?.units)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-[10px] text-indigo-400">
|
<div className="text-[10px] text-indigo-400 font-bold">
|
||||||
€{weekTotals[week].spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
|
€{weekTotals[week].spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -233,7 +232,7 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data }) => {
|
|||||||
{val > 0 && renderGrowth(val, prevVal)}
|
{val > 0 && renderGrowth(val, prevVal)}
|
||||||
</div>
|
</div>
|
||||||
{spend > 0 && (
|
{spend > 0 && (
|
||||||
<span className={`text-[11px] font-medium ${sortConfig?.key === week && sortConfig.metric === 'spend' ? 'text-indigo-300' : 'text-indigo-400/80'}`}>
|
<span className={`text-[11px] font-medium ${sortConfig?.key === week && sortConfig.metric === 'spend' ? 'text-amber-300' : 'text-indigo-400/80'}`}>
|
||||||
€{spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
|
€{spend.toLocaleString('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user