mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 12:55:23 +02:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64a9c40ec3 | ||
|
|
1339a34bb0 | ||
|
|
935f3f02ec | ||
|
|
1ef865250a | ||
|
|
22dfa30708 | ||
|
|
4a6440f58a | ||
|
|
9c73efa8cf | ||
|
|
db69ada011 | ||
|
|
80fff047ab | ||
|
|
e773c6c9c4 | ||
|
|
3f3c7eb3b2 |
+35
-14
@@ -19,21 +19,13 @@ import { PendingValidationView } from './components/PendingValidationView';
|
||||
export default function App() {
|
||||
const [session, setSession] = useState<AuthSession | null>(() => getStoredSession());
|
||||
|
||||
const handleSignOut = () => {
|
||||
signOut();
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
if (!session) {
|
||||
return <LoginPage onLogin={() => setSession(getStoredSession())} />;
|
||||
}
|
||||
|
||||
const [appState, setAppState] = useState<AppState>({
|
||||
headers: [],
|
||||
data: [],
|
||||
fileName: '',
|
||||
fileDate: null,
|
||||
hasUnsavedChanges: false
|
||||
hasUnsavedChanges: false,
|
||||
asinColumnIndex: null
|
||||
});
|
||||
const [activeModule, setActiveModule] = useState<'descriptions' | 'article_details' | 'matrix' | 'dimensions' | 'pricing' | 'pending_validation' | 'history'>('descriptions');
|
||||
const [undoHistory, setUndoHistory] = useState<{ data: ExcelRow[], message: string }[]>([]);
|
||||
@@ -44,6 +36,15 @@ export default function App() {
|
||||
const [pendingRows, setPendingRows] = useState<Record<string, { rowIndex: number; originalData: ExcelRow; newData: ExcelRow; articleName: string }>>({});
|
||||
const [isSavingAll, setIsSavingAll] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('[App] session changed:', session ? 'logged in' : 'logged out');
|
||||
}, [session]);
|
||||
|
||||
const handleSignOut = () => {
|
||||
signOut();
|
||||
setSession(null);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const loadDefaultData = async () => {
|
||||
// In dev: Vite proxy handles /dropbox-file (see vite.config.ts)
|
||||
@@ -77,6 +78,14 @@ export default function App() {
|
||||
const rawHeaders = data[0];
|
||||
const rawRows = data.slice(1);
|
||||
|
||||
// Find ASIN column index from headers (case insensitive)
|
||||
const asinIdx = (rawHeaders as string[]).findIndex((h: string) =>
|
||||
String(h).toLowerCase().trim() === 'asin'
|
||||
);
|
||||
if (asinIdx !== -1) {
|
||||
console.log('ASIN column found at index:', asinIdx);
|
||||
}
|
||||
|
||||
console.log('Applying Supabase overrides...');
|
||||
const syncedData = await getAllSyncedRows();
|
||||
const articleNoIdx = COLUMNS.ARTICLE_NO;
|
||||
@@ -127,7 +136,8 @@ export default function App() {
|
||||
data: processedRows,
|
||||
fileName: 'Data-Matrix.xlsx (Cloud Sync)',
|
||||
fileDate: new Date(),
|
||||
hasUnsavedChanges: false
|
||||
hasUnsavedChanges: false,
|
||||
asinColumnIndex: asinIdx !== -1 ? asinIdx : null
|
||||
});
|
||||
setActiveModule('descriptions');
|
||||
}
|
||||
@@ -139,8 +149,8 @@ export default function App() {
|
||||
}
|
||||
};
|
||||
|
||||
loadDefaultData();
|
||||
}, []);
|
||||
if (session) loadDefaultData();
|
||||
}, [session]);
|
||||
|
||||
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
@@ -211,7 +221,8 @@ export default function App() {
|
||||
data: processedRows,
|
||||
fileName: file.name,
|
||||
fileDate: new Date(),
|
||||
hasUnsavedChanges: false
|
||||
hasUnsavedChanges: false,
|
||||
asinColumnIndex: null
|
||||
});
|
||||
setActiveModule('descriptions');
|
||||
}
|
||||
@@ -355,6 +366,14 @@ export default function App() {
|
||||
};
|
||||
}, [appState.data]);
|
||||
|
||||
if (!session) {
|
||||
return <LoginPage onLogin={() => {
|
||||
const stored = getStoredSession();
|
||||
console.log('[App] onLogin, stored session:', stored ? 'found' : 'null');
|
||||
setSession(stored);
|
||||
}} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-[#040d1a] text-slate-200 flex flex-col font-sans overflow-hidden">
|
||||
<TopBar
|
||||
@@ -403,6 +422,8 @@ export default function App() {
|
||||
{activeModule === 'descriptions' && (
|
||||
<ProductDescriptions
|
||||
data={appState.data}
|
||||
headers={appState.headers}
|
||||
asinColumnIndex={appState.asinColumnIndex}
|
||||
onEdit={(index) => setEditingRowIndex(index)}
|
||||
rowStatuses={rowStatuses}
|
||||
/>
|
||||
|
||||
+151
-75
@@ -13,6 +13,96 @@ interface EditPanelProps {
|
||||
onCaptureState: (message: string) => void;
|
||||
}
|
||||
|
||||
interface DimensionInputProps {
|
||||
label: string;
|
||||
field: string;
|
||||
value: string;
|
||||
isModified: boolean;
|
||||
onChange: (val: string) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const DimensionInput = ({ label, value, isModified, onChange, placeholder }: DimensionInputProps) => (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-[10px] font-medium text-slate-500 uppercase tracking-wider">{label}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
placeholder={placeholder}
|
||||
className={cn(
|
||||
"w-full bg-slate-900 border rounded p-2 text-sm text-white focus:outline-none focus:ring-1 transition-colors",
|
||||
isModified ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
interface FieldEditorProps {
|
||||
title: string;
|
||||
field: string;
|
||||
value: string;
|
||||
isModified: boolean;
|
||||
canTranslate: boolean;
|
||||
isGenerated: boolean;
|
||||
isLoading: boolean;
|
||||
onGenerate: () => void;
|
||||
onChange: (val: string) => void;
|
||||
onKeyDown: (e: React.KeyboardEvent) => void;
|
||||
}
|
||||
|
||||
const FieldEditor = ({
|
||||
title,
|
||||
field,
|
||||
value,
|
||||
isModified,
|
||||
canTranslate,
|
||||
isGenerated,
|
||||
isLoading,
|
||||
onGenerate,
|
||||
onChange,
|
||||
onKeyDown
|
||||
}: FieldEditorProps) => (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium text-slate-300">{title}</label>
|
||||
<div className="flex items-center gap-2">
|
||||
{canTranslate && (
|
||||
<div className="flex items-center gap-1 text-[10px] text-slate-500 italic">
|
||||
<Languages className="w-3 h-3" />
|
||||
Can translate from existing
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={onGenerate}
|
||||
disabled={isLoading}
|
||||
className="flex items-center gap-1.5 text-xs font-medium bg-blue-600/20 text-blue-400 hover:bg-blue-600 hover:text-white px-2 py-1 rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{isLoading ? <Loader2 className="w-3 h-3 animate-spin" /> : <Sparkles className="w-3 h-3" />}
|
||||
{canTranslate ? 'Translate with Gemini' : 'Generate with Gemini'}
|
||||
</button>
|
||||
</div>
|
||||
{isGenerated && (
|
||||
<div className="flex items-center gap-1.5 text-[10px] text-emerald-400 bg-emerald-400/10 px-2 py-0.5 rounded w-fit animate-in fade-in slide-in-from-top-1 duration-300">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
AI Generated - You can still edit manually
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<textarea
|
||||
id={`field-${field}`}
|
||||
value={value}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
onKeyDown={onKeyDown}
|
||||
className={cn(
|
||||
"w-full h-32 bg-slate-900 border rounded-md p-3 text-sm text-white focus:outline-none focus:ring-1 transition-colors resize-none",
|
||||
isModified ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500 focus:ring-slate-500"
|
||||
)}
|
||||
placeholder={`Enter ${title}...`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: EditPanelProps) {
|
||||
const [formData, setFormData] = useState({
|
||||
longDe: row[COLUMNS.LONG_DE] || '',
|
||||
@@ -74,7 +164,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
let prompt = '';
|
||||
const baseContext = `Article Name: ${row[COLUMNS.ARTICLE_NAME]}\nArticle Details (EN): ${formData.detailsEn || 'N/A'}\nArticle Details (DE): ${formData.detailsDe || 'N/A'}`;
|
||||
|
||||
const systemPrompt = "You are a professional copywriter and expert translator for CRAZE GmbH, a German toy company. You excel at translating product descriptions between German and English, maintaining the commercial and professional tone while ensuring all technical toy details are accurate. Use clear, engaging language.";
|
||||
const systemPrompt = "You are a professional copywriter and expert translator for CRAZE GmbH, a German toy company. CRITICAL: Output ONLY the translated or generated content. Do not include any introductions, conclusions, or conversational text. Translate EVERYTHING, including phrases in ALL CAPS (maintain the all-caps casing for those phrases in the translation). Your response must contain only the final product description.";
|
||||
|
||||
if (field === 'longDe') {
|
||||
if (formData.longEn) {
|
||||
@@ -84,6 +174,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
2. Maintain the EXACT same length and detailed information.
|
||||
3. Keep all technical specs intact.
|
||||
4. Translate every single paragraph into natural, commercial German for toy buyers.
|
||||
5. Translate ALL text, including titles or phrases in ALL CAPS, and keep them in ALL CAPS in the translation.
|
||||
|
||||
English Text to Translate:
|
||||
${formData.longEn}`;
|
||||
@@ -98,6 +189,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
2. Maintain the EXACT same length and detailed information.
|
||||
3. Keep all technical specs intact.
|
||||
4. Translate every single paragraph into natural, commercial English for toy buyers.
|
||||
5. Translate ALL text, including titles or phrases in ALL CAPS, and keep them in ALL CAPS in the translation.
|
||||
|
||||
German Text to Translate:
|
||||
${formData.longDe}`;
|
||||
@@ -106,7 +198,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
}
|
||||
} else if (field === 'shortDe') {
|
||||
if (formData.shortEn) {
|
||||
prompt = `Translate exactly this short English product description into professional German for the toy market:\n\n${formData.shortEn}`;
|
||||
prompt = `Translate exactly this short English product description into professional German for the toy market. IMPORTANT: Translate everything, including any ALL CAPS phrases, maintaining the all-caps formatting:\n\n${formData.shortEn}`;
|
||||
} else if (formData.longDe) {
|
||||
const targetChars = Math.round(formData.longDe.length * 0.3);
|
||||
prompt = `Create a concise summary of the following German product description. The summary must be approximately ${targetChars} characters long (about 30% of the original). Preserve the most important commercial highlights and features in natural, professional German for B2B toy buyers. Do not use bullet points — write flowing prose.\n\nOriginal description:\n${formData.longDe}`;
|
||||
@@ -115,7 +207,7 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
}
|
||||
} else if (field === 'shortEn') {
|
||||
if (formData.shortDe) {
|
||||
prompt = `Translate exactly this short German product description into professional English for the toy market:\n\n${formData.shortDe}`;
|
||||
prompt = `Translate exactly this short German product description into professional English for the toy market. IMPORTANT: Translate everything, including any ALL CAPS phrases, maintaining the all-caps formatting:\n\n${formData.shortDe}`;
|
||||
} else if (formData.longEn) {
|
||||
const targetChars = Math.round(formData.longEn.length * 0.3);
|
||||
prompt = `Create a concise summary of the following English product description. The summary must be approximately ${targetChars} characters long (about 30% of the original). Preserve the most important commercial highlights and features in natural, professional English for B2B toy buyers. Do not use bullet points — write flowing prose.\n\nOriginal description:\n${formData.longEn}`;
|
||||
@@ -163,70 +255,10 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
onSave(rowIndex, newRow);
|
||||
};
|
||||
|
||||
const DimensionInput = ({ label, field, placeholder }: { label: string, field: keyof typeof formData, placeholder?: string }) => (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label className="text-[10px] font-medium text-slate-500 uppercase tracking-wider">{label}</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formData[field]}
|
||||
onChange={e => setFormData(prev => ({ ...prev, [field]: e.target.value }))}
|
||||
placeholder={placeholder}
|
||||
className={cn(
|
||||
"w-full bg-slate-900 border rounded p-2 text-sm text-white focus:outline-none focus:ring-1 transition-colors",
|
||||
isModified(field) ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
const handleInputKeyDown = (e: React.KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const FieldEditor = ({ title, field }: { title: string, field: keyof typeof formData }) => (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium text-slate-300">{title}</label>
|
||||
<div className="flex items-center gap-2">
|
||||
{((field === 'longDe' && formData.longEn) ||
|
||||
(field === 'longEn' && formData.longDe) ||
|
||||
(field === 'shortDe' && (formData.longDe || formData.shortEn)) ||
|
||||
(field === 'shortEn' && (formData.shortDe || formData.longEn))) && (
|
||||
<div className="flex items-center gap-1 text-[10px] text-slate-500 italic">
|
||||
<Languages className="w-3 h-3" />
|
||||
Can translate from existing
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleGenerate(field)}
|
||||
disabled={loadingField !== null}
|
||||
className="flex items-center gap-1.5 text-xs font-medium bg-blue-600/20 text-blue-400 hover:bg-blue-600 hover:text-white px-2 py-1 rounded transition-colors disabled:opacity-50"
|
||||
>
|
||||
{loadingField === field ? <Loader2 className="w-3 h-3 animate-spin" /> : <Sparkles className="w-3 h-3" />}
|
||||
{((field === 'longDe' && formData.longEn) || (field === 'longEn' && formData.longDe) || (field === 'shortDe' && formData.shortEn) || (field === 'shortEn' && formData.shortDe)) ? 'Translate with Gemini' : 'Generate with Gemini'}
|
||||
</button>
|
||||
</div>
|
||||
{generatedFields.has(field) && (
|
||||
<div className="flex items-center gap-1.5 text-[10px] text-emerald-400 bg-emerald-400/10 px-2 py-0.5 rounded w-fit animate-in fade-in slide-in-from-top-1 duration-300">
|
||||
<CheckCircle2 className="w-3 h-3" />
|
||||
AI Generated - You can still edit manually
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<textarea
|
||||
id={`field-${String(field)}`}
|
||||
value={formData[field]}
|
||||
onChange={e => setFormData(prev => ({ ...prev, [field]: e.target.value }))}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
className={cn(
|
||||
"w-full h-32 bg-slate-900 border rounded-md p-3 text-sm text-white focus:outline-none focus:ring-1 transition-colors resize-none",
|
||||
isModified(field) ? "border-blue-500 focus:ring-blue-500" : "border-slate-700 focus:border-slate-500 focus:ring-slate-500"
|
||||
)}
|
||||
placeholder={`Enter ${title}...`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="fixed inset-0 bg-slate-950/50 backdrop-blur-sm z-40" onClick={onClose} />
|
||||
@@ -290,28 +322,72 @@ export function EditPanel({ row, rowIndex, onSave, onClose, onCaptureState }: Ed
|
||||
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="col-span-3 text-[10px] text-slate-500 font-medium">INNER BOX (L × W × H) cm</div>
|
||||
<DimensionInput label="Length" field="innerL" placeholder="L" />
|
||||
<DimensionInput label="Width" field="innerW" placeholder="W" />
|
||||
<DimensionInput label="Height" field="innerH" placeholder="H" />
|
||||
<DimensionInput label="Length" value={formData.innerL} field="innerL" isModified={isModified('innerL')} onChange={(val) => setFormData(p => ({...p, innerL: val}))} placeholder="L" />
|
||||
<DimensionInput label="Width" value={formData.innerW} field="innerW" isModified={isModified('innerW')} onChange={(val) => setFormData(p => ({...p, innerW: val}))} placeholder="W" />
|
||||
<DimensionInput label="Height" value={formData.innerH} field="innerH" isModified={isModified('innerH')} onChange={(val) => setFormData(p => ({...p, innerH: val}))} placeholder="H" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="col-span-3 text-[10px] text-slate-500 font-medium">OUTER BOX (L × W × H) cm</div>
|
||||
<DimensionInput label="Length" field="outerL" placeholder="L" />
|
||||
<DimensionInput label="Width" field="outerW" placeholder="W" />
|
||||
<DimensionInput label="Height" field="outerH" placeholder="H" />
|
||||
<DimensionInput label="Length" value={formData.outerL} field="outerL" isModified={isModified('outerL')} onChange={(val) => setFormData(p => ({...p, outerL: val}))} placeholder="L" />
|
||||
<DimensionInput label="Width" value={formData.outerW} field="outerW" isModified={isModified('outerW')} onChange={(val) => setFormData(p => ({...p, outerW: val}))} placeholder="W" />
|
||||
<DimensionInput label="Height" value={formData.outerH} field="outerH" isModified={isModified('outerH')} onChange={(val) => setFormData(p => ({...p, outerH: val}))} placeholder="H" />
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<DimensionInput label="Units per Outer" field="unitsOuter" />
|
||||
<DimensionInput label="MOQ" field="moq" />
|
||||
<DimensionInput label="Units per Outer" value={formData.unitsOuter} field="unitsOuter" isModified={isModified('unitsOuter')} onChange={(val) => setFormData(p => ({...p, unitsOuter: val}))} />
|
||||
<DimensionInput label="MOQ" value={formData.moq} field="moq" isModified={isModified('moq')} onChange={(val) => setFormData(p => ({...p, moq: val}))} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FieldEditor title="Long Description (DE)" field="longDe" />
|
||||
<FieldEditor title="Long Description (EN)" field="longEn" />
|
||||
<FieldEditor title="Short Description (DE)" field="shortDe" />
|
||||
<FieldEditor title="Short Description (EN)" field="shortEn" />
|
||||
<FieldEditor
|
||||
title="Long Description (DE)"
|
||||
field="longDe"
|
||||
value={formData.longDe}
|
||||
isModified={isModified('longDe')}
|
||||
canTranslate={!!formData.longEn}
|
||||
isGenerated={generatedFields.has('longDe')}
|
||||
isLoading={loadingField === 'longDe'}
|
||||
onGenerate={() => handleGenerate('longDe')}
|
||||
onChange={(val) => setFormData(p => ({...p, longDe: val}))}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<FieldEditor
|
||||
title="Long Description (EN)"
|
||||
field="longEn"
|
||||
value={formData.longEn}
|
||||
isModified={isModified('longEn')}
|
||||
canTranslate={!!formData.longDe}
|
||||
isGenerated={generatedFields.has('longEn')}
|
||||
isLoading={loadingField === 'longEn'}
|
||||
onGenerate={() => handleGenerate('longEn')}
|
||||
onChange={(val) => setFormData(p => ({...p, longEn: val}))}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<FieldEditor
|
||||
title="Short Description (DE)"
|
||||
field="shortDe"
|
||||
value={formData.shortDe}
|
||||
isModified={isModified('shortDe')}
|
||||
canTranslate={!!formData.shortEn || !!formData.longDe}
|
||||
isGenerated={generatedFields.has('shortDe')}
|
||||
isLoading={loadingField === 'shortDe'}
|
||||
onGenerate={() => handleGenerate('shortDe')}
|
||||
onChange={(val) => setFormData(p => ({...p, shortDe: val}))}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<FieldEditor
|
||||
title="Short Description (EN)"
|
||||
field="shortEn"
|
||||
value={formData.shortEn}
|
||||
isModified={isModified('shortEn')}
|
||||
canTranslate={!!formData.shortDe || !!formData.longEn}
|
||||
isGenerated={generatedFields.has('shortEn')}
|
||||
isLoading={loadingField === 'shortEn'}
|
||||
onGenerate={() => handleGenerate('shortEn')}
|
||||
onChange={(val) => setFormData(p => ({...p, shortEn: val}))}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="p-6 border-t border-slate-700 bg-slate-800/50 flex justify-end gap-3">
|
||||
|
||||
@@ -6,6 +6,8 @@ import { ColumnFilterPopover } from './ColumnFilterPopover';
|
||||
|
||||
interface ProductDescriptionsProps {
|
||||
data: ExcelRow[];
|
||||
headers: string[];
|
||||
asinColumnIndex: number | null;
|
||||
onEdit: (index: number) => void;
|
||||
rowStatuses: Record<string, string>;
|
||||
}
|
||||
@@ -15,7 +17,7 @@ type TabType = 'all' | 'missingLongDE' | 'missingLongEN' | 'missingShortDE' | 'm
|
||||
// Description columns that should only have Present/Missing filters
|
||||
const DESCRIPTION_COLUMNS = [COLUMNS.LONG_DE, COLUMNS.LONG_EN, COLUMNS.SHORT_DE, COLUMNS.SHORT_EN];
|
||||
|
||||
export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescriptionsProps) {
|
||||
export function ProductDescriptions({ data, headers, asinColumnIndex, onEdit, rowStatuses }: ProductDescriptionsProps) {
|
||||
const [activeTab, setActiveTab] = useState<TabType>('all');
|
||||
const [search, setSearch] = useState('');
|
||||
const [lineFilter, setLineFilter] = useState('');
|
||||
@@ -29,6 +31,7 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri
|
||||
const [columnWidths, setColumnWidths] = useState<Record<number, number>>({
|
||||
[COLUMNS.ARTICLE_NO]: 100,
|
||||
[COLUMNS.ARTICLE_NAME]: 250,
|
||||
[COLUMNS.ASIN]: asinColumnIndex !== null ? 150 : 0,
|
||||
[COLUMNS.LINE]: 80,
|
||||
[COLUMNS.LICENSE]: 120,
|
||||
[COLUMNS.CLASSIFICATION]: 100,
|
||||
@@ -284,6 +287,7 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri
|
||||
{[
|
||||
{ col: COLUMNS.ARTICLE_NO, label: 'Article No.' },
|
||||
{ col: COLUMNS.ARTICLE_NAME, label: 'Article Name' },
|
||||
...(asinColumnIndex !== null ? [{ col: asinColumnIndex, label: 'ASIN' }] : []),
|
||||
{ col: COLUMNS.LINE, label: 'Line' },
|
||||
{ col: COLUMNS.LICENSE, label: 'License' },
|
||||
{ col: COLUMNS.CLASSIFICATION, label: 'Classification' },
|
||||
@@ -361,6 +365,9 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri
|
||||
>
|
||||
<td className="px-4 py-3 font-mono text-slate-300 text-xs truncate" style={{ width: columnWidths[COLUMNS.ARTICLE_NO] }}>{row[COLUMNS.ARTICLE_NO]}</td>
|
||||
<td className="px-4 py-3 font-medium text-white truncate" style={{ width: columnWidths[COLUMNS.ARTICLE_NAME] }} title={row[COLUMNS.ARTICLE_NAME]}>{row[COLUMNS.ARTICLE_NAME]}</td>
|
||||
{asinColumnIndex !== null && (
|
||||
<td className="px-4 py-3 font-mono text-slate-300 text-xs truncate" style={{ width: columnWidths[asinColumnIndex] || 150 }} title={row[asinColumnIndex]}>{row[asinColumnIndex] || '—'}</td>
|
||||
)}
|
||||
<td className="px-4 py-3 text-slate-300 text-xs truncate" style={{ width: columnWidths[COLUMNS.LINE] }}>{row[COLUMNS.LINE]}</td>
|
||||
<td className="px-4 py-3 text-slate-300 text-xs truncate" style={{ width: columnWidths[COLUMNS.LICENSE] }} title={row[COLUMNS.LICENSE]}>{row[COLUMNS.LICENSE] || '—'}</td>
|
||||
<td className="px-4 py-3 truncate" style={{ width: columnWidths[COLUMNS.CLASSIFICATION] }}>
|
||||
@@ -391,7 +398,7 @@ export function ProductDescriptions({ data, onEdit, rowStatuses }: ProductDescri
|
||||
})}
|
||||
{paginatedData.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={9} className="px-4 py-8 text-center text-slate-500">
|
||||
<td colSpan={asinColumnIndex !== null ? 10 : 9} className="px-4 py-8 text-center text-slate-500">
|
||||
No products found matching the criteria.
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
+5
-3
@@ -6,6 +6,7 @@ export interface AppState {
|
||||
fileName: string;
|
||||
fileDate: Date | null;
|
||||
hasUnsavedChanges: boolean;
|
||||
asinColumnIndex: number | null;
|
||||
}
|
||||
|
||||
export const COLUMNS = {
|
||||
@@ -23,15 +24,16 @@ export const COLUMNS = {
|
||||
SHORT_DE: 64,
|
||||
SHORT_EN: 65,
|
||||
RECOMMENDED_AGE: 67,
|
||||
CLASSIFICATION: 11, // Column L (index 11)
|
||||
ITEM_AVAILABLE: 14, // Column O (index 14)
|
||||
CLASSIFICATION: 11,
|
||||
ITEM_AVAILABLE: 14,
|
||||
MOQ: 27,
|
||||
UNITS_INNER: 31,
|
||||
UNITS_OUTER: 32,
|
||||
ASIN: 33,
|
||||
INNER_W: 42,
|
||||
INNER_L: 43,
|
||||
INNER_H: 44,
|
||||
OUTER_W: 47,
|
||||
OUTER_L: 48,
|
||||
OUTER_H: 49
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user