Fix: restore Top50 ranking badges (white/gradient) next to inventory badges across all views

This commit is contained in:
Christian Vidal Wolf
2026-04-27 11:33:07 +02:00
parent 9f8e51e2f3
commit 99e4b2c34b
8 changed files with 113 additions and 6 deletions
+4
View File
@@ -860,6 +860,8 @@ const App: React.FC = () => {
buyBoxLostMap={buyBoxLostMap} buyBoxLostMap={buyBoxLostMap}
top50Mode={top50Mode} top50Mode={top50Mode}
rawData={ytdFilteredData} rawData={ytdFilteredData}
velocityMap={velocityMap}
top50Ranking={top50Ranking2025}
/> />
</div> </div>
@@ -957,6 +959,7 @@ const App: React.FC = () => {
vendorStockMap={vendorStockMap} vendorStockMap={vendorStockMap}
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'} top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
velocityMap={velocityMap} velocityMap={velocityMap}
top50Ranking={top50Ranking2025}
/> />
</div> </div>
</Suspense> </Suspense>
@@ -980,6 +983,7 @@ const App: React.FC = () => {
top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'} top50Mode={filters.customer.includes('Amazon UK') ? 'uk' : 'eu'}
velocityMap={velocityMap} velocityMap={velocityMap}
buyBoxLostMap={buyBoxLostMap} buyBoxLostMap={buyBoxLostMap}
top50Ranking={top50Ranking2025}
/> />
</div> </div>
</Suspense> </Suspense>
+16 -1
View File
@@ -16,6 +16,8 @@ interface DashboardProps {
vendorStockMap?: Map<string, { eu: number; uk: number }>; vendorStockMap?: Map<string, { eu: number; uk: number }>;
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>; buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
top50Mode?: 'eu' | 'uk'; top50Mode?: 'eu' | 'uk';
top50Ranking?: { eu: Map<string, number>; uk: Map<string, number> };
velocityMap?: Map<string, number>;
} }
const COLORS = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#8b5cf6', '#0ea5e9']; const COLORS = ['#6366f1', '#ec4899', '#10b981', '#f59e0b', '#8b5cf6', '#0ea5e9'];
@@ -463,7 +465,18 @@ const GrowthTable: React.FC<{
); );
} }
const Dashboard: React.FC<DashboardProps> = ({ data, contextData, adsData = [], rawData = [], stockMap, vendorStockMap, buyBoxLostMap, top50Mode }) => { const Dashboard: React.FC<DashboardProps> = ({
data,
contextData,
adsData = [],
rawData = [],
stockMap,
vendorStockMap,
buyBoxLostMap,
top50Mode = 'eu',
top50Ranking,
velocityMap
}) => {
const [seasonalityMetric, setSeasonalityMetric] = useState<'sellOut' | 'units'>('sellOut'); const [seasonalityMetric, setSeasonalityMetric] = useState<'sellOut' | 'units'>('sellOut');
const [top10Metric, setTop10Metric] = useState<'sellOut' | 'units'>('sellOut'); const [top10Metric, setTop10Metric] = useState<'sellOut' | 'units'>('sellOut');
@@ -824,6 +837,8 @@ const Dashboard: React.FC<DashboardProps> = ({ data, contextData, adsData = [],
vendorStockMap={vendorStockMap} vendorStockMap={vendorStockMap}
buyBoxLostMap={buyBoxLostMap} buyBoxLostMap={buyBoxLostMap}
top50Mode={top50Mode} top50Mode={top50Mode}
velocityMap={velocityMap}
top50Ranking={top50Ranking}
/> />
</div> </div>
)} )}
+13
View File
@@ -1387,6 +1387,19 @@ const DataGrid: React.FC<DataGridProps> = ({ data, filters, hasCustomerFilter, a
{stockMap && ( {stockMap && (
<StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} /> <StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} />
)} )}
{(() => {
const asin = (row.asin || '').trim().toUpperCase();
if (asin && top50Ranking) {
if (top50Mode === 'eu') {
const rank = top50Ranking.eu.get(asin);
if (rank) return <Top50Badge rank={rank} label="EU" theme="indigo" />;
} else {
const rank = top50Ranking.uk.get(asin);
if (rank) return <Top50Badge rank={rank} label="UK" theme="blue" />;
}
}
return null;
})()}
<VendorStockBadge asin={row.asin} vendorStockMap={vendorStockMap} mode={top50Mode} avgWeeklySales={velocityMap?.get(row.asin.trim().toUpperCase())} /> <VendorStockBadge asin={row.asin} vendorStockMap={vendorStockMap} mode={top50Mode} avgWeeklySales={velocityMap?.get(row.asin.trim().toUpperCase())} />
<BuyBoxWarningBadge asin={row.asin} buyBoxLostMap={buyBoxLostMap} /> <BuyBoxWarningBadge asin={row.asin} buyBoxLostMap={buyBoxLostMap} />
</div> </div>
+30 -2
View File
@@ -4,6 +4,7 @@ import { SalesRecord } from '../types';
import { DownloadIcon } from './Icons'; import { DownloadIcon } from './Icons';
import { StockBadge } from './StockBadge'; import { StockBadge } from './StockBadge';
import { VendorStockBadge } from './VendorStockBadge'; import { VendorStockBadge } from './VendorStockBadge';
import { Top50Badge } from './Top50Badge';
import { BuyBoxWarningBadge } from './BuyBoxWarningBadge'; import { BuyBoxWarningBadge } from './BuyBoxWarningBadge';
interface TopMoversProps { interface TopMoversProps {
@@ -12,6 +13,8 @@ interface TopMoversProps {
vendorStockMap?: Map<string, { eu: number; uk: number }>; vendorStockMap?: Map<string, { eu: number; uk: number }>;
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>; buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
top50Mode?: 'eu' | 'uk'; top50Mode?: 'eu' | 'uk';
velocityMap?: Map<string, number>;
top50Ranking?: { eu: Map<string, number>; uk: Map<string, number> };
} }
type Metric = 'sellOut' | 'units'; type Metric = 'sellOut' | 'units';
@@ -39,7 +42,9 @@ const MoversTable: React.FC<{
vendorStockMap?: Map<string, { eu: number; uk: number }>; vendorStockMap?: Map<string, { eu: number; uk: number }>;
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>; buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
top50Mode?: 'eu' | 'uk'; top50Mode?: 'eu' | 'uk';
}> = ({ title, data, metric, previousYear, currentYear, type, stockMap, vendorStockMap, buyBoxLostMap, top50Mode = 'eu' }) => { velocityMap?: Map<string, number>;
top50Ranking?: { eu: Map<string, number>; uk: Map<string, number> };
}> = ({ title, data, metric, previousYear, currentYear, type, stockMap, vendorStockMap, buyBoxLostMap, top50Mode = 'eu', velocityMap, top50Ranking }) => {
const formatValue = (val: number) => { const formatValue = (val: number) => {
if (metric === 'sellOut') return `${val.toLocaleString('de-DE', { maximumFractionDigits: 0 })}`; if (metric === 'sellOut') return `${val.toLocaleString('de-DE', { maximumFractionDigits: 0 })}`;
@@ -131,6 +136,19 @@ const MoversTable: React.FC<{
{/* Stock Badge */} {/* Stock Badge */}
<td className="px-6 py-3 text-center"> <td className="px-6 py-3 text-center">
<StockBadge stock={stockMap?.get(item.sku?.replace(/(DE|EN)$/i, ''))} /> <StockBadge stock={stockMap?.get(item.sku?.replace(/(DE|EN)$/i, ''))} />
{(() => {
const asin = item.asin.trim().toUpperCase();
if (asin && top50Ranking) {
if (top50Mode === 'eu') {
const rank = top50Ranking.eu.get(asin);
if (rank) return <Top50Badge rank={rank} label="EU" theme="indigo" />;
} else {
const rank = top50Ranking.uk.get(asin);
if (rank) return <Top50Badge rank={rank} label="UK" theme="blue" />;
}
}
return null;
})()}
</td> </td>
{/* Vendor Stock Badge */} {/* Vendor Stock Badge */}
@@ -182,7 +200,15 @@ const MoversTable: React.FC<{
); );
}; };
const TopMovers: React.FC<TopMoversProps> = ({ data, stockMap, vendorStockMap, buyBoxLostMap, top50Mode = 'eu' }) => { const TopMovers: React.FC<TopMoversProps> = ({
data,
stockMap,
vendorStockMap,
buyBoxLostMap,
top50Mode = 'eu',
velocityMap,
top50Ranking
}) => {
const [metric, setMetric] = useState<Metric>('sellOut'); const [metric, setMetric] = useState<Metric>('sellOut');
const [viewMode, setViewMode] = useState<'growth' | 'decline'>('growth'); const [viewMode, setViewMode] = useState<'growth' | 'decline'>('growth');
@@ -336,6 +362,8 @@ const TopMovers: React.FC<TopMoversProps> = ({ data, stockMap, vendorStockMap, b
vendorStockMap={vendorStockMap} vendorStockMap={vendorStockMap}
buyBoxLostMap={buyBoxLostMap} buyBoxLostMap={buyBoxLostMap}
top50Mode={top50Mode} top50Mode={top50Mode}
velocityMap={velocityMap}
top50Ranking={top50Ranking}
/> />
</div> </div>
+17 -1
View File
@@ -4,6 +4,7 @@ import { BSRRecord, CombinedKPIs } from '../types';
import { BuyBoxWarningBadge } from './BuyBoxWarningBadge'; import { BuyBoxWarningBadge } from './BuyBoxWarningBadge';
import { StockBadge } from './StockBadge'; import { StockBadge } from './StockBadge';
import { VendorStockBadge } from './VendorStockBadge'; import { VendorStockBadge } from './VendorStockBadge';
import { Top50Badge } from './Top50Badge';
import { BSRUnitsCorrelationChart } from './BSRUnitsCorrelationChart'; import { BSRUnitsCorrelationChart } from './BSRUnitsCorrelationChart';
interface VendorDataViewProps { interface VendorDataViewProps {
@@ -15,6 +16,7 @@ interface VendorDataViewProps {
vendorStockMap?: Map<string, { eu: number; uk: number }>; vendorStockMap?: Map<string, { eu: number; uk: number }>;
top50Mode?: 'eu' | 'uk'; top50Mode?: 'eu' | 'uk';
velocityMap?: Map<string, number>; velocityMap?: Map<string, number>;
top50Ranking?: { eu: Map<string, number>; uk: Map<string, number> };
} }
interface ChartPoint { interface ChartPoint {
@@ -320,7 +322,8 @@ const VendorDataView: React.FC<VendorDataViewProps> = ({
stockMap, stockMap,
vendorStockMap, vendorStockMap,
top50Mode = 'eu', top50Mode = 'eu',
velocityMap velocityMap,
top50Ranking
}) => { }) => {
const activeMarkets = useMemo(() => { const activeMarkets = useMemo(() => {
const m = new Set(bsrData.map(r => r.market)); const m = new Set(bsrData.map(r => r.market));
@@ -481,6 +484,19 @@ const VendorDataView: React.FC<VendorDataViewProps> = ({
{stockMap && ( {stockMap && (
<StockBadge stock={stockMap.get(productInfo.sku?.replace(/(DE|EN)$/i, ''))} /> <StockBadge stock={stockMap.get(productInfo.sku?.replace(/(DE|EN)$/i, ''))} />
)} )}
{(() => {
const asin = productInfo.asin.trim().toUpperCase();
if (asin && top50Ranking) {
if (top50Mode === 'eu') {
const rank = top50Ranking.eu.get(asin);
if (rank) return <Top50Badge rank={rank} label="EU" theme="indigo" />;
} else {
const rank = top50Ranking.uk.get(asin);
if (rank) return <Top50Badge rank={rank} label="UK" theme="blue" />;
}
}
return null;
})()}
<VendorStockBadge <VendorStockBadge
asin={productInfo.asin} asin={productInfo.asin}
vendorStockMap={vendorStockMap} vendorStockMap={vendorStockMap}
+12
View File
@@ -118,6 +118,18 @@ export const WeeklyRow: React.FC<WeeklyRowProps> = React.memo(({
{stockMap && ( {stockMap && (
<StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} /> <StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} />
)} )}
{(() => {
if (asin && top50Ranking) {
if (top50Mode === 'eu') {
const rank = top50Ranking.eu.get(asin);
if (rank) return <Top50Badge rank={rank} label="EU" theme="indigo" />;
} else {
const rank = top50Ranking.uk.get(asin);
if (rank) return <Top50Badge rank={rank} label="UK" theme="blue" />;
}
}
return null;
})()}
<BuyBoxWarningBadge asin={asin} buyBoxLostMap={buyBoxLostMap} /> <BuyBoxWarningBadge asin={asin} buyBoxLostMap={buyBoxLostMap} />
{experimentMap && ( {experimentMap && (
<ExperimentBadge <ExperimentBadge
+17 -1
View File
@@ -5,6 +5,7 @@ import { ArrowUpDown, AlertCircle, ChevronUp, ChevronDown } from 'lucide-react';
import { StockBadge } from '../StockBadge'; import { StockBadge } from '../StockBadge';
import { VendorStockBadge } from '../VendorStockBadge'; import { VendorStockBadge } from '../VendorStockBadge';
import { BuyBoxWarningBadge } from '../BuyBoxWarningBadge'; import { BuyBoxWarningBadge } from '../BuyBoxWarningBadge';
import { Top50Badge } from '../Top50Badge';
interface MasterTableProps { interface MasterTableProps {
products: Product[]; products: Product[];
@@ -15,6 +16,7 @@ interface MasterTableProps {
top50Mode?: 'eu' | 'uk'; top50Mode?: 'eu' | 'uk';
velocityMap?: Map<string, number>; velocityMap?: Map<string, number>;
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>; buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
top50Ranking?: { eu: Map<string, number>; uk: Map<string, number> };
} }
type SortKey = 'name' | 'grossSales' | 'ppcSpend' | 'deals' | 'promos' | 'chargebacks' | 'netMargin' | 'marginPercent'; type SortKey = 'name' | 'grossSales' | 'ppcSpend' | 'deals' | 'promos' | 'chargebacks' | 'netMargin' | 'marginPercent';
@@ -28,7 +30,8 @@ export const MasterTable: React.FC<MasterTableProps> = ({
vendorStockMap, vendorStockMap,
top50Mode = 'eu', top50Mode = 'eu',
velocityMap, velocityMap,
buyBoxLostMap buyBoxLostMap,
top50Ranking
}) => { }) => {
const [sortKey, setSortKey] = useState<SortKey>('marginPercent'); const [sortKey, setSortKey] = useState<SortKey>('marginPercent');
const [sortOrder, setSortOrder] = useState<SortOrder>('asc'); const [sortOrder, setSortOrder] = useState<SortOrder>('asc');
@@ -196,6 +199,19 @@ export const MasterTable: React.FC<MasterTableProps> = ({
{stockMap && ( {stockMap && (
<StockBadge stock={stockMap.get(product.sku?.replace(/(DE|EN)$/i, ''))} /> <StockBadge stock={stockMap.get(product.sku?.replace(/(DE|EN)$/i, ''))} />
)} )}
{(() => {
const asin = product.asin.trim().toUpperCase();
if (asin && top50Ranking) {
if (top50Mode === 'eu') {
const rank = top50Ranking.eu.get(asin);
if (rank) return <Top50Badge rank={rank} label="EU" theme="indigo" />;
} else {
const rank = top50Ranking.uk.get(asin);
if (rank) return <Top50Badge rank={rank} label="UK" theme="blue" />;
}
}
return null;
})()}
<VendorStockBadge <VendorStockBadge
asin={product.asin} asin={product.asin}
vendorStockMap={vendorStockMap} vendorStockMap={vendorStockMap}
+4 -1
View File
@@ -95,6 +95,7 @@ interface MktDataViewProps {
top50Mode?: 'eu' | 'uk'; top50Mode?: 'eu' | 'uk';
velocityMap?: Map<string, number>; velocityMap?: Map<string, number>;
buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>; buyBoxLostMap?: Map<string, { countries: string[]; reasons: Record<string, string> }>;
top50Ranking?: { eu: Map<string, number>; uk: Map<string, number> };
} }
export default function MktDataView({ export default function MktDataView({
@@ -104,7 +105,8 @@ export default function MktDataView({
vendorStockMap, vendorStockMap,
top50Mode = 'eu', top50Mode = 'eu',
velocityMap, velocityMap,
buyBoxLostMap buyBoxLostMap,
top50Ranking
}: MktDataViewProps) { }: MktDataViewProps) {
const [includeCOGS, setIncludeCOGS] = useState(true); const [includeCOGS, setIncludeCOGS] = useState(true);
const [selectedProduct, setSelectedProduct] = useState<Product | null>(null); const [selectedProduct, setSelectedProduct] = useState<Product | null>(null);
@@ -332,6 +334,7 @@ export default function MktDataView({
top50Mode={top50Mode} top50Mode={top50Mode}
velocityMap={velocityMap} velocityMap={velocityMap}
buyBoxLostMap={buyBoxLostMap} buyBoxLostMap={buyBoxLostMap}
top50Ranking={top50Ranking}
/> />
</div> </div>