mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:45:23 +02:00
feat: add stock, vendor and buybox badges to Top Movers table
- Added StockBadge, VendorStockBadge and BuyBoxWarningBadge to TopMovers component - Updated Dashboard to pass stockMap, vendorStockMap, buyBoxLostMap and top50Mode props - Updated App.tsx to pass the required maps to Dashboard - Table now shows 3 new columns: Stock (GMBH), Vendor (Amazon Warehouse), and Buy Box status Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
co-authored by
Qwen-Coder
parent
167273e659
commit
19f1137a30
@@ -836,7 +836,16 @@ const App: React.FC = () => {
|
||||
<FilterBar filters={filters} onFilterChange={handleFilterChange} options={filterOptions} />
|
||||
<div className="mt-6">
|
||||
<div className={view === 'dashboard' ? '' : 'hidden'}>
|
||||
<Dashboard data={ytdAggregatedData} filterState={filters} adsData={ytdFilteredAdsData} stockMap={stockMap} rawData={ytdFilteredData} />
|
||||
<Dashboard
|
||||
data={ytdAggregatedData}
|
||||
filterState={filters}
|
||||
adsData={ytdFilteredAdsData}
|
||||
stockMap={stockMap}
|
||||
vendorStockMap={vendorStockMap}
|
||||
buyBoxLostMap={buyBoxLostMap}
|
||||
top50Mode={top50Mode}
|
||||
rawData={ytdFilteredData}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Suspense fallback={<LoadingSpinner />}>
|
||||
|
||||
@@ -12,6 +12,10 @@ interface DashboardProps {
|
||||
contextData?: AggregatedData | null;
|
||||
adsData?: AdsRecord[];
|
||||
rawData?: SalesRecord[];
|
||||
stockMap?: Map<string, number>;
|
||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
|
||||
top50Mode?: 'eu' | 'uk';
|
||||
}
|
||||
|
||||
const COLORS = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#8b5cf6', '#0ea5e9'];
|
||||
@@ -814,7 +818,13 @@ const Dashboard: React.FC<DashboardProps> = ({ data, contextData, adsData = [],
|
||||
{/* Top Movers Table - Full Width */}
|
||||
{rawData && rawData.length > 0 && (
|
||||
<div className="mt-6">
|
||||
<TopMovers data={rawData} />
|
||||
<TopMovers
|
||||
data={rawData}
|
||||
stockMap={stockMap}
|
||||
vendorStockMap={vendorStockMap}
|
||||
buyBoxLostMap={buyBoxLostMap}
|
||||
top50Mode={top50Mode}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -2,15 +2,23 @@ import React, { useState, useMemo, useCallback } from 'react';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { SalesRecord } from '../types';
|
||||
import { DownloadIcon } from './Icons';
|
||||
import { StockBadge } from './StockBadge';
|
||||
import { VendorStockBadge } from './VendorStockBadge';
|
||||
import { BuyBoxWarningBadge } from './BuyBoxWarningBadge';
|
||||
|
||||
interface TopMoversProps {
|
||||
data: SalesRecord[];
|
||||
stockMap?: Map<string, number>;
|
||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
|
||||
top50Mode?: 'eu' | 'uk';
|
||||
}
|
||||
|
||||
type Metric = 'sellOut' | 'units';
|
||||
|
||||
interface SkuAggr {
|
||||
sku: string;
|
||||
asin: string;
|
||||
title: string;
|
||||
line: string;
|
||||
previousValue: number;
|
||||
@@ -27,7 +35,11 @@ const MoversTable: React.FC<{
|
||||
previousYear: number;
|
||||
currentYear: number;
|
||||
type: 'growth' | 'decline';
|
||||
}> = ({ title, data, metric, previousYear, currentYear, type }) => {
|
||||
stockMap?: Map<string, number>;
|
||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
|
||||
top50Mode?: 'eu' | 'uk';
|
||||
}> = ({ title, data, metric, previousYear, currentYear, type, stockMap, vendorStockMap, buyBoxLostMap, top50Mode = 'eu' }) => {
|
||||
|
||||
const formatValue = (val: number) => {
|
||||
if (metric === 'sellOut') return `€${val.toLocaleString('de-DE', { maximumFractionDigits: 0 })}`;
|
||||
@@ -84,6 +96,9 @@ const MoversTable: React.FC<{
|
||||
<th className="px-6 py-3 border-b border-border w-16 text-center">Rank</th>
|
||||
<th className="px-6 py-3 border-b border-border">SKU Details</th>
|
||||
<th className="px-6 py-3 border-b border-border">Product Line</th>
|
||||
<th className="px-6 py-3 border-b border-border text-center">Stock</th>
|
||||
<th className="px-6 py-3 border-b border-border text-center">Vendor</th>
|
||||
<th className="px-6 py-3 border-b border-border text-center">Buy Box</th>
|
||||
<th className="px-6 py-3 border-b border-border text-right">{previousYear}</th>
|
||||
<th className="px-6 py-3 border-b border-border text-right">{currentYear}</th>
|
||||
<th className="px-6 py-3 border-b border-border text-right">Diff</th>
|
||||
@@ -113,6 +128,25 @@ const MoversTable: React.FC<{
|
||||
</span>
|
||||
</td>
|
||||
|
||||
{/* Stock Badge */}
|
||||
<td className="px-6 py-3 text-center">
|
||||
<StockBadge stock={stockMap?.get(item.sku)} />
|
||||
</td>
|
||||
|
||||
{/* Vendor Stock Badge */}
|
||||
<td className="px-6 py-3 text-center">
|
||||
<VendorStockBadge
|
||||
asin={item.asin}
|
||||
vendorStockMap={vendorStockMap}
|
||||
mode={top50Mode}
|
||||
/>
|
||||
</td>
|
||||
|
||||
{/* Buy Box Warning Badge */}
|
||||
<td className="px-6 py-3 text-center">
|
||||
<BuyBoxWarningBadge asin={item.asin} buyBoxLostMap={buyBoxLostMap} />
|
||||
</td>
|
||||
|
||||
<td className="px-6 py-3 text-right text-slate-500">
|
||||
{formatValue(item.previousValue)}
|
||||
</td>
|
||||
@@ -148,7 +182,7 @@ const MoversTable: React.FC<{
|
||||
);
|
||||
};
|
||||
|
||||
const TopMovers: React.FC<TopMoversProps> = ({ data }) => {
|
||||
const TopMovers: React.FC<TopMoversProps> = ({ data, stockMap, vendorStockMap, buyBoxLostMap, top50Mode = 'eu' }) => {
|
||||
const [metric, setMetric] = useState<Metric>('sellOut');
|
||||
const [viewMode, setViewMode] = useState<'growth' | 'decline'>('growth');
|
||||
|
||||
@@ -167,14 +201,14 @@ const TopMovers: React.FC<TopMoversProps> = ({ data }) => {
|
||||
if (!currentYear || !previousYear) return { growers: [], decliners: [] };
|
||||
|
||||
// Map: SKU -> { currentVal, previousVal, metadata }
|
||||
const map = new Map<string, { current: number; previous: number; title: string; line: string }>();
|
||||
const map = new Map<string, { current: number; previous: number; title: string; line: string; asin: string }>();
|
||||
|
||||
data.forEach(row => {
|
||||
// Only care about the two comparison years
|
||||
if (row.year !== currentYear && row.year !== previousYear) return;
|
||||
|
||||
if (!map.has(row.sku)) {
|
||||
map.set(row.sku, { current: 0, previous: 0, title: row.title, line: row.line });
|
||||
map.set(row.sku, { current: 0, previous: 0, title: row.title, line: row.line, asin: row.asin });
|
||||
}
|
||||
|
||||
const entry = map.get(row.sku)!;
|
||||
@@ -204,6 +238,7 @@ const TopMovers: React.FC<TopMoversProps> = ({ data }) => {
|
||||
|
||||
list.push({
|
||||
sku,
|
||||
asin: val.asin,
|
||||
title: val.title,
|
||||
line: val.line,
|
||||
previousValue: val.previous,
|
||||
@@ -297,6 +332,10 @@ const TopMovers: React.FC<TopMoversProps> = ({ data }) => {
|
||||
previousYear={previousYear}
|
||||
currentYear={currentYear}
|
||||
type={viewMode}
|
||||
stockMap={stockMap}
|
||||
vendorStockMap={vendorStockMap}
|
||||
buyBoxLostMap={buyBoxLostMap}
|
||||
top50Mode={top50Mode}
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user