mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 13:05:24 +02:00
feat: add vendor stock badges to Sell-in vs Budget first column
Shows aggregated EU and UK vendor stock per SKU (sum across all ASINs) in the first column, matching the style used in other dashboard tabs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d9bbf41c7c
commit
acf3faec36
@@ -2,6 +2,7 @@ import React, { useState, useMemo, useCallback, useRef } from 'react';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { ExcelFilter } from './ExcelFilter';
|
||||
import { ColumnFilterCondition } from '../types';
|
||||
import { AmazonSmileIcon } from './Icons';
|
||||
|
||||
const MONTHS = ['January','February','March','April','May','June','July','August','September','October','November','December'];
|
||||
const MONTHS_SHORT = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
|
||||
@@ -33,6 +34,8 @@ interface ComparisonRow {
|
||||
budgetDiscrepancy: boolean;
|
||||
pctPeriod: number;
|
||||
pctAnnual: number;
|
||||
vendorStockEU: number;
|
||||
vendorStockUK: number;
|
||||
}
|
||||
|
||||
function parseSellIn(buffer: ArrayBuffer): Map<string, SellInRow> {
|
||||
@@ -132,8 +135,34 @@ const ComparisonTableRow: React.FC<{ row: ComparisonRow }> = React.memo(({ row }
|
||||
const barWidth = Math.min(100, row.pctPeriod);
|
||||
return (
|
||||
<tr className="hover:bg-indigo-500/5 transition-colors border-b border-slate-800/50 last:border-0">
|
||||
<td className="px-4 py-3 whitespace-nowrap">
|
||||
<span className="text-xs font-black text-indigo-400 tracking-tighter">{row.sku}</span>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex flex-col gap-1">
|
||||
<span className="text-xs font-black text-indigo-400 tracking-tighter">{row.sku}</span>
|
||||
{(row.vendorStockEU > 0 || row.vendorStockUK > 0) && (
|
||||
<div className="flex flex-col gap-0.5">
|
||||
{row.vendorStockEU > 0 && (
|
||||
<div
|
||||
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded border text-[10px] font-bold bg-slate-200 text-slate-800 border-slate-300 w-fit"
|
||||
title={`Stock Amazon Vendor EU: ${row.vendorStockEU}`}
|
||||
>
|
||||
<AmazonSmileIcon className="w-3 h-3 shrink-0" />
|
||||
<span className="text-[7px] font-black uppercase opacity-60">EU</span>
|
||||
<span>{row.vendorStockEU.toLocaleString('de-DE')}</span>
|
||||
</div>
|
||||
)}
|
||||
{row.vendorStockUK > 0 && (
|
||||
<div
|
||||
className="inline-flex items-center gap-1 px-1.5 py-0.5 rounded border text-[10px] font-bold bg-slate-200 text-slate-800 border-slate-300 w-fit"
|
||||
title={`Stock Amazon Vendor UK: ${row.vendorStockUK}`}
|
||||
>
|
||||
<AmazonSmileIcon className="w-3 h-3 shrink-0" />
|
||||
<span className="text-[7px] font-black uppercase opacity-60">UK</span>
|
||||
<span>{row.vendorStockUK.toLocaleString('de-DE')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-4 py-3 max-w-[280px]">
|
||||
<span className="text-sm text-slate-200 line-clamp-1" title={row.description}>
|
||||
@@ -188,9 +217,10 @@ const ComparisonTableRow: React.FC<{ row: ComparisonRow }> = React.memo(({ row }
|
||||
|
||||
interface ForecastViewProps {
|
||||
asinMetadata?: Map<string, { sku: string; title: string; line: string }>;
|
||||
vendorStockMap?: Map<string, { eu: number; uk: number }>;
|
||||
}
|
||||
|
||||
const ForecastView: React.FC<ForecastViewProps> = ({ asinMetadata }) => {
|
||||
const ForecastView: React.FC<ForecastViewProps> = ({ asinMetadata, vendorStockMap }) => {
|
||||
const [sellInData, setSellInData] = useState<Map<string, SellInRow> | null>(null);
|
||||
const [budgetData, setBudgetData] = useState<Map<string, BudgetRow> | null>(null);
|
||||
const [sellInFileName, setSellInFileName] = useState('');
|
||||
@@ -258,10 +288,17 @@ const ForecastView: React.FC<ForecastViewProps> = ({ asinMetadata }) => {
|
||||
const pctPeriod = periodForecast > 0 ? (actualUnits / periodForecast) * 100 : 0;
|
||||
const pctAnnual = budgetYearTotal > 0 ? (actualUnits / budgetYearTotal) * 100 : 0;
|
||||
const meta = skuMetadata.get(budgetRow.sku);
|
||||
const asins = meta?.asins ?? [];
|
||||
const vendorStockEU = asins.reduce((sum, asin) => {
|
||||
return sum + (vendorStockMap?.get(asin.trim().toUpperCase())?.eu ?? 0);
|
||||
}, 0);
|
||||
const vendorStockUK = asins.reduce((sum, asin) => {
|
||||
return sum + (vendorStockMap?.get(asin.trim().toUpperCase())?.uk ?? 0);
|
||||
}, 0);
|
||||
rows.push({
|
||||
sku: budgetRow.sku,
|
||||
description: budgetRow.description,
|
||||
asins: meta?.asins ?? [],
|
||||
asins,
|
||||
title: meta?.title ?? '',
|
||||
actualUnits,
|
||||
periodForecast,
|
||||
@@ -269,10 +306,12 @@ const ForecastView: React.FC<ForecastViewProps> = ({ asinMetadata }) => {
|
||||
budgetDiscrepancy,
|
||||
pctPeriod,
|
||||
pctAnnual,
|
||||
vendorStockEU,
|
||||
vendorStockUK,
|
||||
});
|
||||
});
|
||||
return rows;
|
||||
}, [budgetData, sellInData, cutoffMonth, skuMetadata]);
|
||||
}, [budgetData, sellInData, cutoffMonth, skuMetadata, vendorStockMap]);
|
||||
|
||||
const passesTextFilter = useCallback((value: string, filter?: ColumnFilterCondition): boolean => {
|
||||
if (!filter) return true;
|
||||
|
||||
Reference in New Issue
Block a user