mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 12:25:22 +02:00
Refine StockBadge design to Top 50 style and move to title row across all views
This commit is contained in:
@@ -406,12 +406,14 @@ const AdsPerformance: React.FC<AdsPerformanceProps> = ({ data, filters, top50Ran
|
|||||||
<Top50Badge key={i} rank={r.rank} label={r.label} theme={r.theme} />
|
<Top50Badge key={i} rank={r.rank} label={r.label} theme={r.theme} />
|
||||||
))}
|
))}
|
||||||
<span className="text-white font-bold text-sm tracking-tight">{row.asin}</span>
|
<span className="text-white font-bold text-sm tracking-tight">{row.asin}</span>
|
||||||
|
</div>
|
||||||
|
<span className="text-[10px] uppercase font-black text-indigo-400 tracking-tighter">SKU: {row.sku}</span>
|
||||||
|
<div className="flex items-start gap-2">
|
||||||
|
<span className="text-[10px] text-slate-400 truncate max-w-[200px] leading-tight" title={row.title}>{row.title}</span>
|
||||||
{stockMap && (
|
{stockMap && (
|
||||||
<StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} />
|
<StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] uppercase font-black text-indigo-400 tracking-tighter">SKU: {row.sku}</span>
|
|
||||||
<span className="text-[10px] text-slate-400 truncate max-w-[220px] leading-tight" title={row.title}>{row.title}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<TableCell value={row.salesTotal} prevValue={row.prevData?.salesTotal} format="currency" />
|
<TableCell value={row.salesTotal} prevValue={row.prevData?.salesTotal} format="currency" />
|
||||||
|
|||||||
+7
-10
@@ -1078,19 +1078,16 @@ const DataGrid: React.FC<DataGridProps> = ({ data, hasCustomerFilter, adsData, s
|
|||||||
<tbody className="divide-y divide-border text-slate-300">
|
<tbody className="divide-y divide-border text-slate-300">
|
||||||
{paginatedRows.map((row) => (
|
{paginatedRows.map((row) => (
|
||||||
<tr key={row.id} className="hover:bg-slate-800/50 transition-colors group">
|
<tr key={row.id} className="hover:bg-slate-800/50 transition-colors group">
|
||||||
{/* Dimension Values */}
|
|
||||||
{effectiveDimensions.map(dim => (
|
{effectiveDimensions.map(dim => (
|
||||||
<td key={dim} className="px-4 py-3 font-medium text-slate-200 break-words max-w-xs">
|
<td key={dim} className="px-4 py-3 font-medium text-slate-200 break-words max-w-xs">
|
||||||
{dim === 'title'
|
{dim === 'title'
|
||||||
? <div className="line-clamp-2" title={row.title}>{row.title || '-'}</div>
|
? <div className="flex items-start gap-2">
|
||||||
: (dim === 'sku' || dim === 'asin')
|
<span className="line-clamp-2" title={row.title}>{row.title || '-'}</span>
|
||||||
? <div className="flex items-center gap-2">
|
{stockMap && (
|
||||||
<span>{(row[dim as keyof PivotRow] as string) || '-'}</span>
|
<StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} />
|
||||||
{stockMap && dim === 'sku' && (
|
)}
|
||||||
<StockBadge stock={stockMap.get((row[dim] as string)?.replace(/(DE|EN)$/i, ''))} />
|
</div>
|
||||||
)}
|
: (row[dim as keyof PivotRow] as string) || '-'
|
||||||
</div>
|
|
||||||
: (row[dim as keyof PivotRow] as string) || '-'
|
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import * as XLSX from 'xlsx';
|
|||||||
import { ProductForecastData, FilterState } from '../types';
|
import { ProductForecastData, FilterState } from '../types';
|
||||||
import { DownloadIcon } from './Icons';
|
import { DownloadIcon } from './Icons';
|
||||||
import { StockBadge } from './StockBadge';
|
import { StockBadge } from './StockBadge';
|
||||||
import { StockBadge } from './StockBadge';
|
|
||||||
import {
|
import {
|
||||||
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||||
LineChart, Line, Legend, ComposedChart, Area
|
LineChart, Line, Legend, ComposedChart, Area
|
||||||
@@ -15,7 +14,6 @@ interface ForecastViewProps {
|
|||||||
top50Ranking?: {
|
top50Ranking?: {
|
||||||
eu: Map<string, number>;
|
eu: Map<string, number>;
|
||||||
uk: Map<string, number>;
|
uk: Map<string, number>;
|
||||||
stockMap?: Map<string, number>;
|
|
||||||
};
|
};
|
||||||
stockMap?: Map<string, number>;
|
stockMap?: Map<string, number>;
|
||||||
}
|
}
|
||||||
@@ -33,7 +31,7 @@ const Top50Badge: React.FC<{ rank: number; label: string; theme?: 'amber' | 'ind
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking, stockMap, stockMap }) => {
|
const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking, stockMap }) => {
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
const [showOnlyTop50, setShowOnlyTop50] = useState(false);
|
||||||
const [top50Mode, setTop50Mode] = useState<'eu' | 'uk'>('eu');
|
const [top50Mode, setTop50Mode] = useState<'eu' | 'uk'>('eu');
|
||||||
@@ -411,8 +409,11 @@ const ForecastView: React.FC<ForecastViewProps> = ({ data, filters, top50Ranking
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/* Title Row */}
|
{/* Title Row */}
|
||||||
<div className="text-[13px] font-medium text-slate-100 leading-snug group-hover:text-white transition-colors">
|
<div className="flex items-start gap-2 text-[13px] font-medium text-slate-100 leading-snug group-hover:text-white transition-colors">
|
||||||
{p.title}
|
<span className="truncate max-w-[170px]">{p.title}</span>
|
||||||
|
{stockMap && (
|
||||||
|
<StockBadge stock={stockMap.get(p.sku?.replace(/(DE|EN)$/i, ''))} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Line Row */}
|
{/* Line Row */}
|
||||||
<div className="text-[10px] font-black text-[#ec4899] uppercase tracking-[0.15em]">
|
<div className="text-[10px] font-black text-[#ec4899] uppercase tracking-[0.15em]">
|
||||||
|
|||||||
@@ -11,24 +11,27 @@ export const StockBadge: React.FC<StockBadgeProps> = ({ stock }) => {
|
|||||||
const isLow = stock < 50;
|
const isLow = stock < 50;
|
||||||
const isOut = stock <= 0;
|
const isOut = stock <= 0;
|
||||||
|
|
||||||
let colorClass = "bg-emerald-500/20 text-emerald-400 border-emerald-500/30";
|
let themeClasses = "from-indigo-600 to-purple-600 border-indigo-400/50";
|
||||||
let icon = "📦";
|
let icon = "📦";
|
||||||
|
|
||||||
if (isOut) {
|
if (isOut) {
|
||||||
colorClass = "bg-rose-500/20 text-rose-400 border-rose-500/30";
|
themeClasses = "from-rose-600 to-red-700 border-rose-400/50";
|
||||||
icon = "❌";
|
icon = "❌";
|
||||||
} else if (isLow) {
|
} else if (isLow) {
|
||||||
colorClass = "bg-amber-500/20 text-amber-400 border-amber-500/30";
|
themeClasses = "from-amber-500 to-orange-600 border-amber-400/50";
|
||||||
icon = "⚠️";
|
icon = "⚠️";
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-full text-[10px] font-black border uppercase tracking-tighter shadow-sm ${colorClass}`}
|
className={`inline-flex items-center gap-1.5 px-2 py-1 rounded bg-gradient-to-br text-white border shadow-lg transition-all hover:scale-105 group/stock ${themeClasses}`}
|
||||||
title={`Total Stock (GMBH): ${stock}`}
|
title={`Total Stock (GMBH): ${stock}`}
|
||||||
>
|
>
|
||||||
<span className="text-xs">{icon}</span>
|
<span className="text-xs filter drop-shadow-sm">{icon}</span>
|
||||||
<span>{stock.toLocaleString('de-DE')}</span>
|
<div className="flex flex-col leading-none">
|
||||||
|
<span className="text-[8px] font-black uppercase tracking-tighter opacity-80">Stock</span>
|
||||||
|
<span className="text-[10px] font-bold tracking-tight">{stock.toLocaleString('de-DE')}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -517,11 +517,13 @@ const WeeklyGrid: React.FC<WeeklyGridProps> = ({ data, top50Ranking, onDrillDown
|
|||||||
{row.sku || '-'}
|
{row.sku || '-'}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[10px] font-bold text-slate-500 bg-slate-800 px-1.5 py-0.5 rounded border border-white/5">{row.asin}</span>
|
<span className="text-[10px] font-bold text-slate-500 bg-slate-800 px-1.5 py-0.5 rounded border border-white/5">{row.asin}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-2 mb-1">
|
||||||
|
<span className="text-[11px] text-white/70 truncate w-[190px] leading-tight" title={row.title}>{row.title}</span>
|
||||||
{stockMap && (
|
{stockMap && (
|
||||||
<StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} />
|
<StockBadge stock={stockMap.get(row.sku?.replace(/(DE|EN)$/i, ''))} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[11px] text-white/70 truncate w-[210px] leading-tight mb-1" title={row.title}>{row.title}</span>
|
|
||||||
<span className="text-[9px] text-fuchsia-400/80 font-bold uppercase tracking-widest">{row.line}</span>
|
<span className="text-[9px] text-fuchsia-400/80 font-bold uppercase tracking-widest">{row.line}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const XLSX = require('xlsx');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function inspectForecast(filename, targetSku) {
|
||||||
|
const filePath = path.join(process.cwd(), 'public', filename);
|
||||||
|
const workbook = XLSX.readFile(filePath);
|
||||||
|
const sheetName = workbook.SheetNames[0];
|
||||||
|
const sheet = workbook.Sheets[sheetName];
|
||||||
|
const data = XLSX.utils.sheet_to_json(sheet);
|
||||||
|
|
||||||
|
console.log(`Searching for SKU: ${targetSku} in ${filename}`);
|
||||||
|
const results = data.filter(row =>
|
||||||
|
String(row['SKU'] || row['sku'] || '').includes(targetSku) ||
|
||||||
|
String(row['ASIN'] || row['asin'] || '').includes(targetSku)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (results.length === 0) {
|
||||||
|
console.log('No results found.');
|
||||||
|
} else {
|
||||||
|
results.forEach(r => console.log(JSON.stringify(r, null, 2)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetSku = process.argv[2] || '46234';
|
||||||
|
inspectForecast('fc UK 26.xlsx', targetSku);
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
const XLSX = require('xlsx');
|
||||||
|
|
||||||
|
async function inspect() {
|
||||||
|
const workbook = XLSX.readFile('Item Availability.xlsx');
|
||||||
|
const sheet = workbook.Sheets[workbook.SheetNames[0]];
|
||||||
|
const data = XLSX.utils.sheet_to_json(sheet, { header: 1 });
|
||||||
|
|
||||||
|
console.log('--- HEADERS ---');
|
||||||
|
console.log(data[0]);
|
||||||
|
|
||||||
|
console.log('\n--- DATA (Top 5) ---');
|
||||||
|
console.log(data.slice(1, 6));
|
||||||
|
}
|
||||||
|
|
||||||
|
inspect();
|
||||||
Binary file not shown.
Reference in New Issue
Block a user