feat: show EOL not neccessary for OOC products with 0 stock

This commit is contained in:
christian.vidal
2026-03-27 13:26:16 +01:00
parent c3c46d558e
commit 750e25db89
5 changed files with 57 additions and 13 deletions
+23
View File
@@ -0,0 +1,23 @@
// Vercel serverless function — proxies the Dropbox file server-side (no CORS)
export default async function handler(req, res) {
const fileUrl =
'https://dl.dropboxusercontent.com/scl/fi/nkyabkq2jdwfudof2ovrl/Data-Matrix.xlsx' +
'?rlkey=1cz5fuabwipqqivihii7sybw0&st=io0g4wvb&dl=1';
try {
const upstream = await fetch(fileUrl, {
headers: { 'User-Agent': 'Mozilla/5.0' },
});
if (!upstream.ok) {
return res.status(upstream.status).send(`Dropbox error: ${upstream.status}`);
}
const buffer = await upstream.arrayBuffer();
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Cache-Control', 'no-store');
res.send(Buffer.from(buffer));
} catch (err) {
res.status(500).send('Proxy error: ' + err.message);
}
}
+5 -2
View File
@@ -24,8 +24,11 @@ export default function App() {
useEffect(() => { useEffect(() => {
const loadDefaultData = async () => { const loadDefaultData = async () => {
// Use Vite's dev proxy to avoid CORS issues // In dev: Vite proxy handles /dropbox-file (see vite.config.ts)
const fileUrl = '/dropbox-file/scl/fi/nkyabkq2jdwfudof2ovrl/Data-Matrix.xlsx?rlkey=1cz5fuabwipqqivihii7sybw0&st=io0g4wvb&dl=1'; // In prod: Vercel serverless function at /api/dropbox-proxy handles it
const fileUrl = import.meta.env.DEV
? '/dropbox-file/scl/fi/nkyabkq2jdwfudof2ovrl/Data-Matrix.xlsx?rlkey=1cz5fuabwipqqivihii7sybw0&st=io0g4wvb&dl=1'
: '/api/dropbox-proxy';
setIsLoadingDefault(true); setIsLoadingDefault(true);
setDefaultLoadError(null); setDefaultLoadError(null);
+22 -5
View File
@@ -76,6 +76,14 @@ export function ProductDescriptions({ data, onEdit }: ProductDescriptionsProps)
}; };
const getRowColor = (row: ExcelRow) => { const getRowColor = (row: ExcelRow) => {
// EOL Rule: OOC Classification and 0 stock (Item Available)
const classification = String(row[COLUMNS.CLASSIFICATION] || '').toUpperCase();
const stock = Number(row[COLUMNS.ITEM_AVAILABLE] || 0);
if (classification === 'OOC' && stock === 0) {
return 'bg-yellow-500/10 hover:bg-yellow-500/20'; // EOL Highlight
}
const fields = [row[COLUMNS.LONG_DE], row[COLUMNS.LONG_EN], row[COLUMNS.SHORT_DE], row[COLUMNS.SHORT_EN]]; const fields = [row[COLUMNS.LONG_DE], row[COLUMNS.LONG_EN], row[COLUMNS.SHORT_DE], row[COLUMNS.SHORT_EN]];
const filled = fields.filter(Boolean).length; const filled = fields.filter(Boolean).length;
if (filled === 4) return 'bg-green-900/10 hover:bg-green-900/20'; if (filled === 4) return 'bg-green-900/10 hover:bg-green-900/20';
@@ -83,8 +91,17 @@ export function ProductDescriptions({ data, onEdit }: ProductDescriptionsProps)
return 'bg-yellow-900/10 hover:bg-yellow-900/20'; return 'bg-yellow-900/10 hover:bg-yellow-900/20';
}; };
const Badge = ({ content }: { content: any }) => { const Badge = ({ content, row }: { content: any, row: ExcelRow }) => {
if (content) return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-500/20 text-green-400 border border-green-500/30"></span>; if (content) return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-500/20 text-green-400 border border-green-500/30"></span>;
// EOL Exception
const classification = String(row[COLUMNS.CLASSIFICATION] || '').toUpperCase();
const stock = Number(row[COLUMNS.ITEM_AVAILABLE] || 0);
if (classification === 'OOC' && stock === 0) {
return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-500/20 text-yellow-400 border border-yellow-500/30">EOL not neccessary</span>;
}
return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-500/20 text-red-400 border border-red-500/30"> Missing</span>; return <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-red-500/20 text-red-400 border border-red-500/30"> Missing</span>;
}; };
@@ -184,10 +201,10 @@ export function ProductDescriptions({ data, onEdit }: ProductDescriptionsProps)
<td className="px-4 py-3 font-medium text-white max-w-[300px] truncate" title={row[COLUMNS.ARTICLE_NAME]}>{row[COLUMNS.ARTICLE_NAME]}</td> <td className="px-4 py-3 font-medium text-white max-w-[300px] truncate" title={row[COLUMNS.ARTICLE_NAME]}>{row[COLUMNS.ARTICLE_NAME]}</td>
<td className="px-4 py-3 text-slate-300">{row[COLUMNS.LINE]}</td> <td className="px-4 py-3 text-slate-300">{row[COLUMNS.LINE]}</td>
<td className="px-4 py-3 text-slate-300">{row[COLUMNS.LICENSE]}</td> <td className="px-4 py-3 text-slate-300">{row[COLUMNS.LICENSE]}</td>
<td className="px-4 py-3"><Badge content={row[COLUMNS.LONG_DE]} /></td> <td className="px-4 py-3"><Badge content={row[COLUMNS.LONG_DE]} row={row} /></td>
<td className="px-4 py-3"><Badge content={row[COLUMNS.LONG_EN]} /></td> <td className="px-4 py-3"><Badge content={row[COLUMNS.LONG_EN]} row={row} /></td>
<td className="px-4 py-3"><Badge content={row[COLUMNS.SHORT_DE]} /></td> <td className="px-4 py-3"><Badge content={row[COLUMNS.SHORT_DE]} row={row} /></td>
<td className="px-4 py-3"><Badge content={row[COLUMNS.SHORT_EN]} /></td> <td className="px-4 py-3"><Badge content={row[COLUMNS.SHORT_EN]} row={row} /></td>
<td className="px-4 py-3 text-right"> <td className="px-4 py-3 text-right">
<button <button
onClick={() => onEdit(index)} onClick={() => onEdit(index)}
+3 -1
View File
@@ -22,5 +22,7 @@ export const COLUMNS = {
LONG_EN: 63, LONG_EN: 63,
SHORT_DE: 64, SHORT_DE: 64,
SHORT_EN: 65, SHORT_EN: 65,
RECOMMENDED_AGE: 67 RECOMMENDED_AGE: 67,
CLASSIFICATION: 1, // Classification
ITEM_AVAILABLE: 14 // Item Available
}; };
+4 -5
View File
@@ -1,8 +1,7 @@
{ {
"rewrites": [ "functions": {
{ "api/dropbox-proxy.js": {
"source": "/dropbox-file/:path*", "runtime": "nodejs20.x"
"destination": "https://dl.dropboxusercontent.com/:path*"
} }
] }
} }