From fdf878a4f838d36317d51d79bf9d3a311c70fcdc Mon Sep 17 00:00:00 2001 From: Christian Vidal Wolf Date: Sun, 12 Apr 2026 15:02:52 +0200 Subject: [PATCH] feat: add Missing Class / Launch tab to Article Details Adds a new 'Missing Class / Launch' tab in Article Details that surfaces items where Classification is empty or Launch Date is blank/01-01-1900. The tab shows Classification, Launch Date, and Ready to Order columns for quick reference. Column indices for Launch Date and Ready to Order are resolved dynamically from the Excel headers. Co-Authored-By: Claude Sonnet 4.6 --- src/App.tsx | 1 + src/components/ArticleDetails.tsx | 108 ++++++++++++++++++++++-------- 2 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index eb25ab2..eac919b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -548,6 +548,7 @@ export default function App() { {activeModule === 'article_details' && ( setEditingRowIndex(index)} rowStatuses={rowStatuses} /> diff --git a/src/components/ArticleDetails.tsx b/src/components/ArticleDetails.tsx index 267c364..e34b44a 100644 --- a/src/components/ArticleDetails.tsx +++ b/src/components/ArticleDetails.tsx @@ -6,13 +6,23 @@ import { ColumnFilterPopover } from './ColumnFilterPopover'; interface ArticleDetailsProps { data: ExcelRow[]; + headers: string[]; onEdit: (index: number) => void; rowStatuses: Record; } -type TabType = 'all' | 'missingDetailsDE' | 'missingDetailsEN' | 'missingAnyDetails' | 'lowStock'; +type TabType = 'all' | 'missingDetailsDE' | 'missingDetailsEN' | 'missingAnyDetails' | 'lowStock' | 'missingClassOrLaunch'; -export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProps) { +function isEmptyLaunchDate(val: any): boolean { + if (val === null || val === undefined || val === '') return true; + const s = String(val).trim(); + if (s === '' || s === '0' || s === '1') return true; + // "00/01/1900" or "01/01/1900" variants + if (s.endsWith('/1900')) return true; + return false; +} + +export function ArticleDetails({ data, headers, onEdit, rowStatuses }: ArticleDetailsProps) { const [activeTab, setActiveTab] = useState('all'); const [search, setSearch] = useState(''); const [lineFilter, setLineFilter] = useState(''); @@ -22,6 +32,12 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp const [page, setPage] = useState(1); const [columnFilters, setColumnFilters] = useState>({}); const [openFilterCol, setOpenFilterCol] = useState(null); + + const launchDateCol = useMemo(() => + headers.findIndex(h => h.toLowerCase().includes('launch')), [headers]); + const readyToOrderCol = useMemo(() => + headers.findIndex(h => h.toLowerCase().includes('ready')), [headers]); + const [columnWidths, setColumnWidths] = useState>({ [COLUMNS.ARTICLE_NO]: 100, [COLUMNS.ARTICLE_NAME]: 200, @@ -41,6 +57,11 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp if (activeTab === 'missingDetailsEN') result = result.filter(r => !r.row[COLUMNS.DETAILS_EN]); if (activeTab === 'missingAnyDetails') result = result.filter(r => !r.row[COLUMNS.DETAILS_DE] || !r.row[COLUMNS.DETAILS_EN]); if (activeTab === 'lowStock') result = result.filter(r => Number(r.row[COLUMNS.ITEM_AVAILABLE] || 0) <= 0); + if (activeTab === 'missingClassOrLaunch') result = result.filter(r => { + const missingClass = !r.row[COLUMNS.CLASSIFICATION] || String(r.row[COLUMNS.CLASSIFICATION]).trim() === ''; + const missingLaunch = launchDateCol >= 0 ? isEmptyLaunchDate(r.row[launchDateCol]) : false; + return missingClass || missingLaunch; + }); // Search filter if (search) { @@ -156,6 +177,7 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp { id: 'missingDetailsEN', label: 'No Details EN' }, { id: 'missingAnyDetails', label: 'Missing Details' }, { id: 'lowStock', label: 'Out of Stock' }, + { id: 'missingClassOrLaunch', label: 'Missing Class / Launch' }, ]; return ( @@ -226,10 +248,15 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp {[ { col: COLUMNS.ARTICLE_NO, label: 'SKU' }, { col: COLUMNS.ARTICLE_NAME, label: 'Name' }, - { col: COLUMNS.CLASSIFICATION, label: 'Class' }, - { col: COLUMNS.ITEM_AVAILABLE, label: 'Stock' }, - { col: COLUMNS.DETAILS_DE, label: 'Details DE' }, - { col: COLUMNS.DETAILS_EN, label: 'Details EN' }, + { col: COLUMNS.CLASSIFICATION, label: 'Classification' }, + ...(activeTab === 'missingClassOrLaunch' ? [ + ...(launchDateCol >= 0 ? [{ col: launchDateCol, label: headers[launchDateCol] || 'Launch Date' }] : []), + ...(readyToOrderCol >= 0 ? [{ col: readyToOrderCol, label: headers[readyToOrderCol] || 'Ready to Order' }] : []), + ] : [ + { col: COLUMNS.ITEM_AVAILABLE, label: 'Stock' }, + { col: COLUMNS.DETAILS_DE, label: 'Details DE' }, + { col: COLUMNS.DETAILS_EN, label: 'Details EN' }, + ]), ].map(({ col, label }) => ( - - {row[COLUMNS.CLASSIFICATION] || '—'} - - - - - {row[COLUMNS.ITEM_AVAILABLE] || 0} - - - - {row[COLUMNS.DETAILS_DE] ? ( -
{row[COLUMNS.DETAILS_DE]}
- ) : getBadge(null)} - - - {row[COLUMNS.DETAILS_EN] ? ( -
{row[COLUMNS.DETAILS_EN]}
+ {row[COLUMNS.CLASSIFICATION] ? ( + + {row[COLUMNS.CLASSIFICATION]} + ) : getBadge(null)} + {activeTab === 'missingClassOrLaunch' ? ( + <> + {launchDateCol >= 0 && ( + + {isEmptyLaunchDate(row[launchDateCol]) ? getBadge(null) : ( + {String(row[launchDateCol])} + )} + + )} + {readyToOrderCol >= 0 && ( + + {row[readyToOrderCol] !== null && row[readyToOrderCol] !== undefined && row[readyToOrderCol] !== '' ? ( + {String(row[readyToOrderCol])} + ) : getBadge(null)} + + )} + + ) : ( + <> + + + {row[COLUMNS.ITEM_AVAILABLE] || 0} + + + + {row[COLUMNS.DETAILS_DE] ? ( +
{row[COLUMNS.DETAILS_DE]}
+ ) : getBadge(null)} + + + {row[COLUMNS.DETAILS_EN] ? ( +
{row[COLUMNS.DETAILS_EN]}
+ ) : getBadge(null)} + + + )}