mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 15:15:24 +02:00
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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d1f1c9a467
commit
fdf878a4f8
@@ -548,6 +548,7 @@ export default function App() {
|
||||
{activeModule === 'article_details' && (
|
||||
<ArticleDetails
|
||||
data={appState.data}
|
||||
headers={appState.headers}
|
||||
onEdit={(index) => setEditingRowIndex(index)}
|
||||
rowStatuses={rowStatuses}
|
||||
/>
|
||||
|
||||
@@ -6,13 +6,23 @@ import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||
|
||||
interface ArticleDetailsProps {
|
||||
data: ExcelRow[];
|
||||
headers: string[];
|
||||
onEdit: (index: number) => void;
|
||||
rowStatuses: Record<string, string>;
|
||||
}
|
||||
|
||||
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<TabType>('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<Record<number, string[]>>({});
|
||||
const [openFilterCol, setOpenFilterCol] = useState<number | null>(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<Record<number, number>>({
|
||||
[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 }) => (
|
||||
<th
|
||||
key={col}
|
||||
@@ -302,31 +329,54 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
|
||||
{row[COLUMNS.ARTICLE_NAME]}
|
||||
</td>
|
||||
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.CLASSIFICATION] }}>
|
||||
<span className={cn(
|
||||
"px-1.5 py-0.5 rounded-[4px] text-[10px] font-bold border",
|
||||
String(row[COLUMNS.CLASSIFICATION]).includes('OOC') ? "bg-amber-500/10 text-amber-500 border-amber-500/20" : "bg-slate-700/50 text-slate-400 border-slate-600/50"
|
||||
)}>
|
||||
{row[COLUMNS.CLASSIFICATION] || '—'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.ITEM_AVAILABLE] }}>
|
||||
<span className={cn(
|
||||
"font-mono font-bold",
|
||||
Number(row[COLUMNS.ITEM_AVAILABLE] || 0) <= 0 ? "text-red-400" : "text-emerald-400"
|
||||
)}>
|
||||
{row[COLUMNS.ITEM_AVAILABLE] || 0}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.DETAILS_DE] }}>
|
||||
{row[COLUMNS.DETAILS_DE] ? (
|
||||
<div className="truncate text-slate-400" title={row[COLUMNS.DETAILS_DE]}>{row[COLUMNS.DETAILS_DE]}</div>
|
||||
) : getBadge(null)}
|
||||
</td>
|
||||
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.DETAILS_EN] }}>
|
||||
{row[COLUMNS.DETAILS_EN] ? (
|
||||
<div className="truncate text-slate-400" title={row[COLUMNS.DETAILS_EN]}>{row[COLUMNS.DETAILS_EN]}</div>
|
||||
{row[COLUMNS.CLASSIFICATION] ? (
|
||||
<span className={cn(
|
||||
"px-1.5 py-0.5 rounded-[4px] text-[10px] font-bold border",
|
||||
String(row[COLUMNS.CLASSIFICATION]).includes('OOC') ? "bg-amber-500/10 text-amber-500 border-amber-500/20" : "bg-slate-700/50 text-slate-400 border-slate-600/50"
|
||||
)}>
|
||||
{row[COLUMNS.CLASSIFICATION]}
|
||||
</span>
|
||||
) : getBadge(null)}
|
||||
</td>
|
||||
{activeTab === 'missingClassOrLaunch' ? (
|
||||
<>
|
||||
{launchDateCol >= 0 && (
|
||||
<td className="px-3 py-2 truncate">
|
||||
{isEmptyLaunchDate(row[launchDateCol]) ? getBadge(null) : (
|
||||
<span className="font-mono text-slate-300">{String(row[launchDateCol])}</span>
|
||||
)}
|
||||
</td>
|
||||
)}
|
||||
{readyToOrderCol >= 0 && (
|
||||
<td className="px-3 py-2 truncate">
|
||||
{row[readyToOrderCol] !== null && row[readyToOrderCol] !== undefined && row[readyToOrderCol] !== '' ? (
|
||||
<span className="font-mono text-slate-300">{String(row[readyToOrderCol])}</span>
|
||||
) : getBadge(null)}
|
||||
</td>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.ITEM_AVAILABLE] }}>
|
||||
<span className={cn(
|
||||
"font-mono font-bold",
|
||||
Number(row[COLUMNS.ITEM_AVAILABLE] || 0) <= 0 ? "text-red-400" : "text-emerald-400"
|
||||
)}>
|
||||
{row[COLUMNS.ITEM_AVAILABLE] || 0}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.DETAILS_DE] }}>
|
||||
{row[COLUMNS.DETAILS_DE] ? (
|
||||
<div className="truncate text-slate-400" title={row[COLUMNS.DETAILS_DE]}>{row[COLUMNS.DETAILS_DE]}</div>
|
||||
) : getBadge(null)}
|
||||
</td>
|
||||
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.DETAILS_EN] }}>
|
||||
{row[COLUMNS.DETAILS_EN] ? (
|
||||
<div className="truncate text-slate-400" title={row[COLUMNS.DETAILS_EN]}>{row[COLUMNS.DETAILS_EN]}</div>
|
||||
) : getBadge(null)}
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
<td className="px-3 py-2 text-right">
|
||||
<button
|
||||
onClick={() => onEdit(index)}
|
||||
|
||||
Reference in New Issue
Block a user