mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 14:35:23 +02:00
Fix: Implement dynamic column detection to handle Excel structure changes (insertion of PM Classification). This fixes the Missing data error in Pricing Units and other views.
This commit is contained in:
@@ -0,0 +1,18 @@
|
|||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
const buf = fs.readFileSync('data (3).xlsx');
|
||||||
|
const wb = XLSX.read(buf, {type: 'buffer'});
|
||||||
|
const ws = wb.Sheets[wb.SheetNames[0]];
|
||||||
|
const data = XLSX.utils.sheet_to_json(ws, {header: 1});
|
||||||
|
|
||||||
|
const itemRow = data.find(r => String(r[0]) === '75432');
|
||||||
|
if (itemRow) {
|
||||||
|
console.log('Row for 75432:');
|
||||||
|
console.log(`Index 0 (Article No): ${itemRow[0]}`);
|
||||||
|
console.log(`Index 31 (Units Inner): ${itemRow[31]}`);
|
||||||
|
console.log(`Index 32 (Units Outer): ${itemRow[32]}`);
|
||||||
|
console.log(`Index 33 (Units Pallet): ${itemRow[33]}`);
|
||||||
|
} else {
|
||||||
|
console.log('Item 75432 not found');
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -0,0 +1,13 @@
|
|||||||
|
import * as XLSX from 'xlsx';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
const buf = fs.readFileSync('data (3).xlsx');
|
||||||
|
const wb = XLSX.read(buf, {type: 'buffer'});
|
||||||
|
const ws = wb.Sheets[wb.SheetNames[0]];
|
||||||
|
const data = XLSX.utils.sheet_to_json(ws, {header: 1});
|
||||||
|
const headers = data[0];
|
||||||
|
|
||||||
|
console.log('Headers:');
|
||||||
|
headers.forEach((h, i) => {
|
||||||
|
console.log(`${i}: ${h}`);
|
||||||
|
});
|
||||||
+18
-14
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useMemo, useEffect } from 'react';
|
import React, { useState, useMemo, useEffect } from 'react';
|
||||||
import * as XLSX from 'xlsx';
|
import * as XLSX from 'xlsx';
|
||||||
import { AppState, ExcelRow, COLUMNS } from './types';
|
import { AppState, ExcelRow, resolveColumnIndices, COLUMNS } from './types';
|
||||||
|
import { ColumnsProvider } from './contexts/ColumnsContext';
|
||||||
import { Sidebar } from './components/Sidebar';
|
import { Sidebar } from './components/Sidebar';
|
||||||
import { TopBar } from './components/TopBar';
|
import { TopBar } from './components/TopBar';
|
||||||
import { ProductDescriptions } from './components/ProductDescriptions';
|
import { ProductDescriptions } from './components/ProductDescriptions';
|
||||||
@@ -121,18 +122,19 @@ export default function App() {
|
|||||||
console.log('Fetching synced data from Supabase...');
|
console.log('Fetching synced data from Supabase...');
|
||||||
const syncedData = await getAllSyncedRows(session?.access_token);
|
const syncedData = await getAllSyncedRows(session?.access_token);
|
||||||
|
|
||||||
|
const resolvedCols = resolveColumnIndices(headers);
|
||||||
const editableColumns = new Set([
|
const editableColumns = new Set([
|
||||||
COLUMNS.CLASSIFICATION,
|
resolvedCols.CLASSIFICATION,
|
||||||
COLUMNS.LONG_DE, COLUMNS.LONG_EN,
|
resolvedCols.LONG_DE, resolvedCols.LONG_EN,
|
||||||
COLUMNS.SHORT_DE, COLUMNS.SHORT_EN,
|
resolvedCols.SHORT_DE, resolvedCols.SHORT_EN,
|
||||||
COLUMNS.DETAILS_DE, COLUMNS.DETAILS_EN,
|
resolvedCols.DETAILS_DE, resolvedCols.DETAILS_EN,
|
||||||
COLUMNS.INNER_L, COLUMNS.INNER_W, COLUMNS.INNER_H,
|
resolvedCols.INNER_L, resolvedCols.INNER_W, resolvedCols.INNER_H,
|
||||||
COLUMNS.OUTER_L, COLUMNS.OUTER_W, COLUMNS.OUTER_H,
|
resolvedCols.OUTER_L, resolvedCols.OUTER_W, resolvedCols.OUTER_H,
|
||||||
COLUMNS.UNITS_OUTER, COLUMNS.MOQ,
|
resolvedCols.UNITS_OUTER, resolvedCols.MOQ,
|
||||||
COLUMNS.VERIFIED_DIMS,
|
resolvedCols.VERIFIED_DIMS,
|
||||||
COLUMNS.VALIDATED_CHECK,
|
resolvedCols.VALIDATED_CHECK,
|
||||||
COLUMNS.VALIDATED_NOTE,
|
resolvedCols.VALIDATED_NOTE,
|
||||||
COLUMNS.PRODUCT_TYPE
|
resolvedCols.PRODUCT_TYPE
|
||||||
]);
|
]);
|
||||||
|
|
||||||
headers.forEach((h: any, i: number) => {
|
headers.forEach((h: any, i: number) => {
|
||||||
@@ -142,7 +144,7 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const articleNoIdx = COLUMNS.ARTICLE_NO;
|
const articleNoIdx = resolvedCols.ARTICLE_NO;
|
||||||
const processedRows = rows.map(row => {
|
const processedRows = rows.map(row => {
|
||||||
const articleNo = String(row[articleNoIdx]);
|
const articleNo = String(row[articleNoIdx]);
|
||||||
const synced = syncedData[articleNo];
|
const synced = syncedData[articleNo];
|
||||||
@@ -530,7 +532,8 @@ export default function App() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen bg-[#040d1a] text-slate-200 flex flex-col font-sans overflow-hidden">
|
<ColumnsProvider headers={appState.headers}>
|
||||||
|
<div className="h-screen bg-[#040d1a] text-slate-200 flex flex-col font-sans overflow-hidden">
|
||||||
<TopBar
|
<TopBar
|
||||||
stats={stats}
|
stats={stats}
|
||||||
activeModule={activeModule}
|
activeModule={activeModule}
|
||||||
@@ -695,5 +698,6 @@ export default function App() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</ColumnsProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { Search, Filter, Edit2, ChevronDown, ChevronUp, Info, Package, X } from 'lucide-react';
|
import { Search, Filter, Edit2, ChevronDown, ChevronUp, Info, Package, X } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||||
@@ -13,6 +14,7 @@ interface ArticleDetailsProps {
|
|||||||
type TabType = 'all' | 'missingDetailsDE' | 'missingDetailsEN' | 'missingAnyDetails' | 'lowStock';
|
type TabType = 'all' | 'missingDetailsDE' | 'missingDetailsEN' | 'missingAnyDetails' | 'lowStock';
|
||||||
|
|
||||||
export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProps) {
|
export function ArticleDetails({ data, onEdit, rowStatuses }: ArticleDetailsProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
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('');
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useMemo, useState } from 'react';
|
import React, { useMemo, useState } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
|
|
||||||
@@ -9,6 +10,7 @@ interface DataCompletenessProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function DataCompleteness({ data, headers }: DataCompletenessProps) {
|
export function DataCompleteness({ data, headers }: DataCompletenessProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
const [sortCol, setSortCol] = useState<number | 'score'>('score');
|
const [sortCol, setSortCol] = useState<number | 'score'>('score');
|
||||||
const [sortDesc, setSortDesc] = useState(false);
|
const [sortDesc, setSortDesc] = useState(false);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { AlertTriangle, CheckCircle2, ChevronDown, ChevronRight, Edit2, Package, Boxes, Scale, Loader2, RefreshCw, Layers, Link2, Search, Filter, X, Undo2 } from 'lucide-react';
|
import { AlertTriangle, CheckCircle2, ChevronDown, ChevronRight, Edit2, Package, Boxes, Scale, Loader2, RefreshCw, Layers, Link2, Search, Filter, X, Undo2 } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { ConfirmModal } from './ConfirmModal';
|
import { ConfirmModal } from './ConfirmModal';
|
||||||
@@ -35,6 +36,7 @@ interface NearDuplicateCluster {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureState, rowStatuses, onRevertRow }: DimensionsViewProps) {
|
export function DimensionsView({ data, headers, onEdit, onSaveRow, onCaptureState, rowStatuses, onRevertRow }: DimensionsViewProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
|
||||||
const [expandedNearDuplicates, setExpandedNearDuplicates] = useState<Set<number>>(new Set());
|
const [expandedNearDuplicates, setExpandedNearDuplicates] = useState<Set<number>>(new Set());
|
||||||
const [showOnlyInconsistent, setShowOnlyInconsistent] = useState(true);
|
const [showOnlyInconsistent, setShowOnlyInconsistent] = useState(true);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useRef, useCallback } from 'react';
|
import React, { useState, useRef, useCallback } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { X, Sparkles, Save, Loader2, Languages, Package, CheckCircle2, Mic, MicOff } from 'lucide-react';
|
import { X, Sparkles, Save, Loader2, Languages, Package, CheckCircle2, Mic, MicOff } from 'lucide-react';
|
||||||
import { generateGemini } from '../services/gemini';
|
import { generateGemini } from '../services/gemini';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
@@ -126,6 +127,7 @@ const FieldEditor = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: EditPanelProps) {
|
export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: EditPanelProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
longDe: row[COLUMNS.LONG_DE] || '',
|
longDe: row[COLUMNS.LONG_DE] || '',
|
||||||
longEn: row[COLUMNS.LONG_EN] || '',
|
longEn: row[COLUMNS.LONG_EN] || '',
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { History, RotateCcw, ChevronDown, ChevronRight, User, Calendar, Tag, Search, X, Edit2 } from 'lucide-react';
|
import { History, RotateCcw, ChevronDown, ChevronRight, User, Calendar, Tag, Search, X, Edit2 } from 'lucide-react';
|
||||||
import { getHistory, deleteHistoryEntry, HistoryEntry } from '../lib/supabase';
|
import { getHistory, deleteHistoryEntry, HistoryEntry } from '../lib/supabase';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
|
|
||||||
interface HistoryViewProps {
|
interface HistoryViewProps {
|
||||||
@@ -13,6 +14,7 @@ interface HistoryViewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function HistoryView({ headers, data, onRevert, onEdit, sessionToken }: HistoryViewProps) {
|
export function HistoryView({ headers, data, onRevert, onEdit, sessionToken }: HistoryViewProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
const [history, setHistory] = useState<HistoryEntry[]>([]);
|
const [history, setHistory] = useState<HistoryEntry[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [expandedId, setExpandedId] = useState<string | null>(null);
|
const [expandedId, setExpandedId] = useState<string | null>(null);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { Search, ChevronDown, ChevronUp, X, Edit2, Save } from 'lucide-react';
|
import { Search, ChevronDown, ChevronUp, X, Edit2, Save } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||||
@@ -65,6 +66,7 @@ interface EditingState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: MissingDataViewProps) {
|
export function MissingDataView({ data, headers, onSaveRow, onCaptureState }: MissingDataViewProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
const [activeTab, setActiveTab] = useState<TabType>('missingLaunch');
|
const [activeTab, setActiveTab] = useState<TabType>('missingLaunch');
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [sortCol, setSortCol] = useState<number | null>(null);
|
const [sortCol, setSortCol] = useState<number | null>(null);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { Clock, Undo2, Package, Box, DollarSign, FileText, Search, Filter, X } from 'lucide-react';
|
import { Clock, Undo2, Package, Box, DollarSign, FileText, Search, Filter, X } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
|
|
||||||
@@ -12,6 +13,7 @@ interface PendingValidationViewProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PendingValidationView({ data, pendingRows, rowStatuses, onRevertRow, onEdit }: PendingValidationViewProps) {
|
export function PendingValidationView({ data, pendingRows, rowStatuses, onRevertRow, onEdit }: PendingValidationViewProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
|
|
||||||
const pendingEntries = Object.entries(pendingRows);
|
const pendingEntries = Object.entries(pendingRows);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useMemo, useRef, useCallback, useEffect } from 'react';
|
import React, { useState, useMemo, useRef, useCallback, useEffect } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import {
|
import {
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
@@ -48,6 +49,7 @@ function findCol(headers: string[], ...keywords: string[]): number {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, rowStatuses }: PricingViewProps) {
|
export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit, rowStatuses }: PricingViewProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
const [filterMode, setFilterMode] = useState<FilterMode>('all_errors');
|
const [filterMode, setFilterMode] = useState<FilterMode>('all_errors');
|
||||||
const [search, setSearch] = useState('');
|
const [search, setSearch] = useState('');
|
||||||
const [searchMode, setSearchMode] = useState<'all' | 'selected'>('all');
|
const [searchMode, setSearchMode] = useState<'all' | 'selected'>('all');
|
||||||
@@ -193,15 +195,10 @@ export function PricingView({ data, headers, onSaveRow, onCaptureState, onEdit,
|
|||||||
const nwIdx = findCol(headers, 'nw');
|
const nwIdx = findCol(headers, 'nw');
|
||||||
const gwIdx = findCol(headers, 'gw');
|
const gwIdx = findCol(headers, 'gw');
|
||||||
|
|
||||||
// Units/Outer column — try dynamic detection first, fall back to hardcoded index
|
const unitsOuterIdx = COLUMNS.UNITS_OUTER;
|
||||||
const unitsOuterIdx = findCol(headers, 'units', 'outer') >= 0
|
|
||||||
? findCol(headers, 'units', 'outer')
|
|
||||||
: findCol(headers, 'vpe') >= 0
|
|
||||||
? findCol(headers, 'vpe')
|
|
||||||
: COLUMNS.UNITS_OUTER;
|
|
||||||
|
|
||||||
return { uvpIdx, srpCols: srp, containerCols: container, nwIdx, gwIdx, unitsOuterIdx };
|
return { uvpIdx, srpCols: srp, containerCols: container, nwIdx, gwIdx, unitsOuterIdx };
|
||||||
}, [headers]);
|
}, [headers, COLUMNS]);
|
||||||
|
|
||||||
// ── Error analysis per row ────────────────────────────────────────────────
|
// ── Error analysis per row ────────────────────────────────────────────────
|
||||||
const analyzedRows = useMemo(() => {
|
const analyzedRows = useMemo(() => {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { ExcelRow, COLUMNS } from '../types';
|
import { ExcelRow } from '../types';
|
||||||
|
import { useColumns } from '../contexts/ColumnsContext';
|
||||||
import { Search, Filter, Edit2, ChevronDown, ChevronUp, X } from 'lucide-react';
|
import { Search, Filter, Edit2, ChevronDown, ChevronUp, X } from 'lucide-react';
|
||||||
import { cn } from '../lib/utils';
|
import { cn } from '../lib/utils';
|
||||||
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||||
@@ -12,12 +13,11 @@ interface ProductDescriptionsProps {
|
|||||||
rowStatuses: Record<string, string>;
|
rowStatuses: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TabType = 'all' | 'missingLongDE' | 'missingLongEN' | 'missingShortDE' | 'missingShortEN' | 'complete' | 'incomplete';
|
|
||||||
|
|
||||||
// Description columns that should only have Present/Missing filters
|
|
||||||
const DESCRIPTION_COLUMNS = [COLUMNS.LONG_DE, COLUMNS.LONG_EN, COLUMNS.SHORT_DE, COLUMNS.SHORT_EN, COLUMNS.DETAILS_DE, COLUMNS.DETAILS_EN];
|
|
||||||
|
|
||||||
export function ProductDescriptions({ data, headers, asinColumnIndex, onEdit, rowStatuses }: ProductDescriptionsProps) {
|
export function ProductDescriptions({ data, headers, asinColumnIndex, onEdit, rowStatuses }: ProductDescriptionsProps) {
|
||||||
|
const COLUMNS = useColumns();
|
||||||
|
|
||||||
|
// Description columns that should only have Present/Missing filters
|
||||||
|
const DESCRIPTION_COLUMNS = [COLUMNS.LONG_DE, COLUMNS.LONG_EN, COLUMNS.SHORT_DE, COLUMNS.SHORT_EN, COLUMNS.DETAILS_DE, COLUMNS.DETAILS_EN];
|
||||||
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('');
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import React, { createContext, useContext, useMemo } from 'react';
|
||||||
|
import { COLUMNS, resolveColumnIndices } from '../types';
|
||||||
|
|
||||||
|
type ColumnIndices = typeof COLUMNS;
|
||||||
|
|
||||||
|
const ColumnsContext = createContext<ColumnIndices>(COLUMNS);
|
||||||
|
|
||||||
|
export function ColumnsProvider({ headers, children }: { headers: string[], children: React.ReactNode }) {
|
||||||
|
const resolved = useMemo(() => {
|
||||||
|
if (!headers || headers.length === 0) return COLUMNS;
|
||||||
|
return resolveColumnIndices(headers);
|
||||||
|
}, [headers]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ColumnsContext.Provider value={resolved}>
|
||||||
|
{children}
|
||||||
|
</ColumnsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useColumns() {
|
||||||
|
return useContext(ColumnsContext);
|
||||||
|
}
|
||||||
+71
-19
@@ -9,6 +9,7 @@ export interface AppState {
|
|||||||
asinColumnIndex: number | null;
|
asinColumnIndex: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Canonical column indices (defaults)
|
||||||
export const COLUMNS = {
|
export const COLUMNS = {
|
||||||
ARTICLE_NO: 0,
|
ARTICLE_NO: 0,
|
||||||
ARTICLE_NAME: 2,
|
ARTICLE_NAME: 2,
|
||||||
@@ -16,28 +17,79 @@ export const COLUMNS = {
|
|||||||
LICENSE: 8,
|
LICENSE: 8,
|
||||||
DETAILS_EN: 9,
|
DETAILS_EN: 9,
|
||||||
DETAILS_DE: 10,
|
DETAILS_DE: 10,
|
||||||
BARCODE: 28,
|
BARCODE: 29,
|
||||||
TARIFF_CODE: 58,
|
TARIFF_CODE: 60,
|
||||||
COUNTRY_ORIGIN: 60,
|
COUNTRY_ORIGIN: 62,
|
||||||
LONG_DE: 62,
|
LONG_DE: 64,
|
||||||
LONG_EN: 63,
|
LONG_EN: 65,
|
||||||
SHORT_DE: 64,
|
SHORT_DE: 66,
|
||||||
SHORT_EN: 65,
|
SHORT_EN: 67,
|
||||||
RECOMMENDED_AGE: 67,
|
RECOMMENDED_AGE: 70,
|
||||||
CLASSIFICATION: 11,
|
CLASSIFICATION: 11,
|
||||||
ITEM_AVAILABLE: 14,
|
ITEM_AVAILABLE: 16,
|
||||||
MOQ: 27,
|
MOQ: 28,
|
||||||
UNITS_INNER: 31,
|
UNITS_INNER: 32,
|
||||||
UNITS_OUTER: 32,
|
UNITS_OUTER: 33,
|
||||||
ASIN: 33,
|
ASIN: 76,
|
||||||
INNER_W: 42,
|
INNER_W: 43,
|
||||||
INNER_L: 43,
|
INNER_L: 44,
|
||||||
INNER_H: 44,
|
INNER_H: 45,
|
||||||
OUTER_W: 47,
|
OUTER_W: 48,
|
||||||
OUTER_L: 48,
|
OUTER_L: 49,
|
||||||
OUTER_H: 49,
|
OUTER_H: 50,
|
||||||
VERIFIED_DIMS: 100,
|
VERIFIED_DIMS: 100,
|
||||||
VALIDATED_CHECK: 101,
|
VALIDATED_CHECK: 101,
|
||||||
VALIDATED_NOTE: 102,
|
VALIDATED_NOTE: 102,
|
||||||
PRODUCT_TYPE: 103
|
PRODUCT_TYPE: 103
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Search patterns for dynamic detection
|
||||||
|
export const COLUMN_PATTERNS: Record<keyof typeof COLUMNS, string[]> = {
|
||||||
|
ARTICLE_NO: ['article', 'no'],
|
||||||
|
ARTICLE_NAME: ['article', 'name'],
|
||||||
|
LINE: ['line'],
|
||||||
|
LICENSE: ['license'],
|
||||||
|
DETAILS_EN: ['details', 'english'],
|
||||||
|
DETAILS_DE: ['details', 'german'],
|
||||||
|
BARCODE: ['article', 'barcode'],
|
||||||
|
TARIFF_CODE: ['tariff', 'code'],
|
||||||
|
COUNTRY_ORIGIN: ['country', 'origin'],
|
||||||
|
LONG_DE: ['long', 'description', 'german'],
|
||||||
|
LONG_EN: ['long', 'description', 'english'],
|
||||||
|
SHORT_DE: ['short', 'description', 'german'],
|
||||||
|
SHORT_EN: ['short', 'description', 'english'],
|
||||||
|
RECOMMENDED_AGE: ['recommended', 'age'],
|
||||||
|
CLASSIFICATION: ['classification'],
|
||||||
|
ITEM_AVAILABLE: ['item', 'available'],
|
||||||
|
MOQ: ['moq'],
|
||||||
|
UNITS_INNER: ['units', 'inner'],
|
||||||
|
UNITS_OUTER: ['units', 'outer'],
|
||||||
|
ASIN: ['asin'],
|
||||||
|
INNER_W: ['inner', 'w'],
|
||||||
|
INNER_L: ['inner', 'l'],
|
||||||
|
INNER_H: ['inner', 'h'],
|
||||||
|
OUTER_W: ['outer', 'w'],
|
||||||
|
OUTER_L: ['outer', 'l'],
|
||||||
|
OUTER_H: ['outer', 'h'],
|
||||||
|
// Virtual/Extra columns stay hardcoded or managed elsewhere
|
||||||
|
VERIFIED_DIMS: ['verified'],
|
||||||
|
VALIDATED_CHECK: ['validated'],
|
||||||
|
VALIDATED_NOTE: ['note'],
|
||||||
|
PRODUCT_TYPE: ['product', 'type']
|
||||||
|
};
|
||||||
|
|
||||||
|
export function resolveColumnIndices(headers: string[]): typeof COLUMNS {
|
||||||
|
const resolved = { ...COLUMNS };
|
||||||
|
const h = headers.map(val => String(val || '').toLowerCase());
|
||||||
|
|
||||||
|
Object.entries(COLUMN_PATTERNS).forEach(([key, patterns]) => {
|
||||||
|
const idx = h.findIndex(headerText =>
|
||||||
|
patterns.every(p => headerText.includes(p.toLowerCase()))
|
||||||
|
);
|
||||||
|
if (idx >= 0) {
|
||||||
|
resolved[key as keyof typeof COLUMNS] = idx;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return resolved;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user