mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 18:55: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' && (
|
{activeModule === 'article_details' && (
|
||||||
<ArticleDetails
|
<ArticleDetails
|
||||||
data={appState.data}
|
data={appState.data}
|
||||||
|
headers={appState.headers}
|
||||||
onEdit={(index) => setEditingRowIndex(index)}
|
onEdit={(index) => setEditingRowIndex(index)}
|
||||||
rowStatuses={rowStatuses}
|
rowStatuses={rowStatuses}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,13 +6,23 @@ import { ColumnFilterPopover } from './ColumnFilterPopover';
|
|||||||
|
|
||||||
interface ArticleDetailsProps {
|
interface ArticleDetailsProps {
|
||||||
data: ExcelRow[];
|
data: ExcelRow[];
|
||||||
|
headers: string[];
|
||||||
onEdit: (index: number) => void;
|
onEdit: (index: number) => void;
|
||||||
rowStatuses: Record<string, string>;
|
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 [activeTab, setActiveTab] = useState<TabType>('all');
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [lineFilter, setLineFilter] = useState('');
|
const [lineFilter, setLineFilter] = useState('');
|
||||||
@@ -22,6 +32,12 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
|
|||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [columnFilters, setColumnFilters] = useState<Record<number, string[]>>({});
|
const [columnFilters, setColumnFilters] = useState<Record<number, string[]>>({});
|
||||||
const [openFilterCol, setOpenFilterCol] = useState<number | null>(null);
|
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>>({
|
const [columnWidths, setColumnWidths] = useState<Record<number, number>>({
|
||||||
[COLUMNS.ARTICLE_NO]: 100,
|
[COLUMNS.ARTICLE_NO]: 100,
|
||||||
[COLUMNS.ARTICLE_NAME]: 200,
|
[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 === '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 === '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 === '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
|
// Search filter
|
||||||
if (search) {
|
if (search) {
|
||||||
@@ -156,6 +177,7 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
|
|||||||
{ id: 'missingDetailsEN', label: 'No Details EN' },
|
{ id: 'missingDetailsEN', label: 'No Details EN' },
|
||||||
{ id: 'missingAnyDetails', label: 'Missing Details' },
|
{ id: 'missingAnyDetails', label: 'Missing Details' },
|
||||||
{ id: 'lowStock', label: 'Out of Stock' },
|
{ id: 'lowStock', label: 'Out of Stock' },
|
||||||
|
{ id: 'missingClassOrLaunch', label: 'Missing Class / Launch' },
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -226,10 +248,15 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
|
|||||||
{[
|
{[
|
||||||
{ col: COLUMNS.ARTICLE_NO, label: 'SKU' },
|
{ col: COLUMNS.ARTICLE_NO, label: 'SKU' },
|
||||||
{ col: COLUMNS.ARTICLE_NAME, label: 'Name' },
|
{ col: COLUMNS.ARTICLE_NAME, label: 'Name' },
|
||||||
{ col: COLUMNS.CLASSIFICATION, label: 'Class' },
|
{ 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.ITEM_AVAILABLE, label: 'Stock' },
|
||||||
{ col: COLUMNS.DETAILS_DE, label: 'Details DE' },
|
{ col: COLUMNS.DETAILS_DE, label: 'Details DE' },
|
||||||
{ col: COLUMNS.DETAILS_EN, label: 'Details EN' },
|
{ col: COLUMNS.DETAILS_EN, label: 'Details EN' },
|
||||||
|
]),
|
||||||
].map(({ col, label }) => (
|
].map(({ col, label }) => (
|
||||||
<th
|
<th
|
||||||
key={col}
|
key={col}
|
||||||
@@ -302,13 +329,34 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
|
|||||||
{row[COLUMNS.ARTICLE_NAME]}
|
{row[COLUMNS.ARTICLE_NAME]}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.CLASSIFICATION] }}>
|
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.CLASSIFICATION] }}>
|
||||||
|
{row[COLUMNS.CLASSIFICATION] ? (
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"px-1.5 py-0.5 rounded-[4px] text-[10px] font-bold border",
|
"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"
|
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] || '—'}
|
{row[COLUMNS.CLASSIFICATION]}
|
||||||
</span>
|
</span>
|
||||||
|
) : getBadge(null)}
|
||||||
</td>
|
</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] }}>
|
<td className="px-3 py-2 truncate" style={{ width: columnWidths[COLUMNS.ITEM_AVAILABLE] }}>
|
||||||
<span className={cn(
|
<span className={cn(
|
||||||
"font-mono font-bold",
|
"font-mono font-bold",
|
||||||
@@ -327,6 +375,8 @@ export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProp
|
|||||||
<div className="truncate text-slate-400" title={row[COLUMNS.DETAILS_EN]}>{row[COLUMNS.DETAILS_EN]}</div>
|
<div className="truncate text-slate-400" title={row[COLUMNS.DETAILS_EN]}>{row[COLUMNS.DETAILS_EN]}</div>
|
||||||
) : getBadge(null)}
|
) : getBadge(null)}
|
||||||
</td>
|
</td>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<td className="px-3 py-2 text-right">
|
<td className="px-3 py-2 text-right">
|
||||||
<button
|
<button
|
||||||
onClick={() => onEdit(index)}
|
onClick={() => onEdit(index)}
|
||||||
|
|||||||
Reference in New Issue
Block a user