diff --git a/App.tsx b/App.tsx index 0788378..6b27ea3 100644 --- a/App.tsx +++ b/App.tsx @@ -108,6 +108,7 @@ const App: React.FC = () => { asin: [], sku: [], title: [], + week: [], }); }; @@ -249,6 +250,7 @@ const App: React.FC = () => { asin: getUniqueValues(rawData, 'asin'), sku: getUniqueValues(rawData, 'sku'), title: getUniqueValues(rawData, 'title'), + week: Array.from(new Set(rawData.map(r => r.week).filter(w => w !== undefined))).sort((a, b) => (a as number) - (b as number)).map(w => `W${w}`), }; }, [rawData]); diff --git a/components/DataGrid.tsx b/components/DataGrid.tsx index 803c15c..1757c19 100644 --- a/components/DataGrid.tsx +++ b/components/DataGrid.tsx @@ -651,27 +651,47 @@ const DataGrid: React.FC = ({ data }) => { ); })} - {/* Total Columns per Year */} - {years.map(year => ( - - requestSort(`total_sellOut_${year}`)} - > -
- Sell Out {year} {getSortIcon(`total_sellOut_${year}`)} -
- - requestSort(`total_units_${year}`)} - > -
- Units {year} {getSortIcon(`total_units_${year}`)} -
- -
- ))} + {/* Data Columns: Year Groups */} + {years.map((year, index) => { + const prevYear = years[index + 1]; // Since years are sorted desc: 2025, 2024... next index is prev year + return ( + + {/* Sell Out & Units */} + requestSort(`total_sellOut_${year}`)} + > +
+ Sell Out {year} {getSortIcon(`total_sellOut_${year}`)} +
+ + requestSort(`total_units_${year}`)} + > +
+ Units {year} {getSortIcon(`total_units_${year}`)} +
+ + + {/* Growth Columns (if prev year exists) */} + {prevYear && ( + <> + +
+ S.O. Δ% +
+ + +
+ Units Δ% +
+ + + )} +
+ ); + })} @@ -688,8 +708,27 @@ const DataGrid: React.FC = ({ data }) => { ))} {/* Metric Values */} - {years.map(year => { + {years.map((year, index) => { const data = row.totalsByYear[year]; + const prevYear = years[index + 1]; + const prevData = prevYear ? row.totalsByYear[prevYear] : null; + + // Calculate Growth + let sellOutGrowth = null; + let unitsGrowth = null; + + if (prevYear) { + const currSO = data?.sellOut || 0; + const prevSO = prevData?.sellOut || 0; + if (prevSO !== 0) sellOutGrowth = ((currSO - prevSO) / prevSO) * 100; + else if (currSO > 0) sellOutGrowth = 100; // New entry + + const currUnits = data?.units || 0; + const prevUnits = prevData?.units || 0; + if (prevUnits !== 0) unitsGrowth = ((currUnits - prevUnits) / prevUnits) * 100; + else if (currUnits > 0) unitsGrowth = 100; + } + return ( @@ -698,6 +737,26 @@ const DataGrid: React.FC = ({ data }) => { {data ? data.units.toLocaleString() : '-'} + + {/* Growth Cells */} + {prevYear && ( + <> + = 0 ? 'text-emerald-500' : 'text-red-500') : 'text-slate-600'}`}> + {sellOutGrowth !== null ? ( + + {sellOutGrowth >= 0 ? '↑' : '↓'} {Math.abs(sellOutGrowth).toFixed(0)}% + + ) : '-'} + + = 0 ? 'text-emerald-500' : 'text-red-500') : 'text-slate-600'}`}> + {unitsGrowth !== null ? ( + + {unitsGrowth >= 0 ? '↑' : '↓'} {Math.abs(unitsGrowth).toFixed(0)}% + + ) : '-'} + + + )} ); })} diff --git a/components/FilterBar.tsx b/components/FilterBar.tsx index 10d2115..34f1a0b 100644 --- a/components/FilterBar.tsx +++ b/components/FilterBar.tsx @@ -14,6 +14,7 @@ interface FilterBarProps { asin: string[]; sku: string[]; title: string[]; + week: string[]; }; } @@ -21,54 +22,61 @@ const FilterBar: React.FC = ({ filters, onFilterChange, options return (
- onFilterChange('customer', v)} - className="flex-1" + onFilterChange('customer', v)} + className="flex-1" /> - onFilterChange('year', v)} - className="flex-1" + onFilterChange('year', v)} + className="flex-1" /> - onFilterChange('month', v)} - className="flex-1" + onFilterChange('month', v)} + className="flex-1" /> - onFilterChange('line', v)} - className="flex-1" + onFilterChange('week', v)} + className="flex-1" /> - onFilterChange('sku', v)} - className="flex-1" + onFilterChange('line', v)} + className="flex-1" /> - onFilterChange('title', v)} - className="flex-1" + onFilterChange('sku', v)} + className="flex-1" /> - onFilterChange('asin', v)} - className="flex-1" + onFilterChange('title', v)} + className="flex-1" + /> + onFilterChange('asin', v)} + className="flex-1" />
diff --git a/services/dataProcessor.ts b/services/dataProcessor.ts index c6120ea..d85f9f0 100644 --- a/services/dataProcessor.ts +++ b/services/dataProcessor.ts @@ -531,7 +531,12 @@ export const filterData = (data: SalesRecord[], filters: FilterState): SalesReco const skuMatch = filters.sku.length === 0 || filters.sku.includes(item.sku); const titleMatch = filters.title.length === 0 || filters.title.includes(item.title); - return customerMatch && yearMatch && monthMatch && lineMatch && asinMatch && skuMatch && titleMatch; + // Week Logic: Match "W1", "W2" etc. + // item.week is a number (e.g. 1), filter uses strings "W1" + const weekStr = item.week ? `W${item.week}` : ''; + const weekMatch = filters.week.length === 0 || (weekStr !== '' && filters.week.includes(weekStr)); + + return customerMatch && yearMatch && monthMatch && lineMatch && asinMatch && skuMatch && titleMatch && weekMatch; }); }; diff --git a/types.ts b/types.ts index bb0361a..0094428 100644 --- a/types.ts +++ b/types.ts @@ -22,6 +22,7 @@ export interface FilterState { asin: string[]; sku: string[]; title: string[]; // Added Title filter + week: string[]; // Added Week filter } export interface GrowthMetric { @@ -30,7 +31,7 @@ export interface GrowthMetric { previousYearSellOut: number; sellOutGrowthValue: number; sellOutGrowthPercentage: number; - + currentYearUnits: number; previousYearUnits: number; unitsGrowthValue: number; @@ -62,7 +63,7 @@ export interface SeasonalityPoint { export interface YearlySplitData { name: string; // Dynamic keys like "2023", "2024" or "2023_value", "2023_units" - [key: string]: number | string; + [key: string]: number | string; } export interface AggregatedData { @@ -90,27 +91,27 @@ export interface ChatMessage { // New Interfaces for Dynamic Pivot Grid export interface YearlyData { - sellOut: number; - units: number; + sellOut: number; + units: number; } export interface MonthlyPivot { - monthIndex: number; - byYear: Record; + monthIndex: number; + byYear: Record; } export interface PivotRow { - id: string; - customer: string; - line: string; - title: string; // Added Title - articleName: string; - sku: string; - asin: string; - - // Dynamic buckets - totalsByYear: Record; - months: MonthlyPivot[]; // Always 12 elements + id: string; + customer: string; + line: string; + title: string; // Added Title + articleName: string; + sku: string; + asin: string; + + // Dynamic buckets + totalsByYear: Record; + months: MonthlyPivot[]; // Always 12 elements } export interface TimeSeriesData { @@ -145,22 +146,22 @@ export interface CombinedKPIs { title: string; line: string; sku: string; - + salesTotal: number; unitsTotal: number; - + salesAds: number; unitsAds: number; cost: number; clicks: number; impressions: number; - + salesOrganic: number; unitsOrganic: number; - + paidSalesShare: number; organicSalesShare: number; - + acos: number; tacos: number; roas: number;