diff --git a/.gitignore b/.gitignore index c35bd15..4c3515c 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ dist-ssr *.njsproj *.sln *.sw? +.vercel diff --git a/components/MultiSelectDropdown.tsx b/components/MultiSelectDropdown.tsx index 67eed51..3d52790 100644 --- a/components/MultiSelectDropdown.tsx +++ b/components/MultiSelectDropdown.tsx @@ -181,7 +181,20 @@ const MultiSelectDropdown: React.FC = ({ label, select onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); - if (filteredOptions.length > 0) { + // Check if input contains multiple space-separated terms (bulk paste) + const terms = searchTerm.trim().split(/\s+/).filter(Boolean); + if (terms.length > 1) { + // Bulk select: match each term against options (case-insensitive) + const termsLower = terms.map(t => t.toLowerCase()); + const matched = options.filter(opt => + termsLower.some(t => opt.toLowerCase() === t || opt.toLowerCase().includes(t)) + ); + if (matched.length > 0) { + onChange(Array.from(new Set([...selected, ...matched]))); + } + setIsOpen(false); + setSearchTerm(''); + } else if (filteredOptions.length > 0) { const allSelected = filteredOptions.every(opt => selected.includes(opt)); if (allSelected) { // Deselect all filtered diff --git a/services/dataProcessor.ts b/services/dataProcessor.ts index 4186894..1c9b84a 100644 --- a/services/dataProcessor.ts +++ b/services/dataProcessor.ts @@ -417,7 +417,7 @@ export const processAdsCSV = (file: File): Promise => { return new Promise((resolve, reject) => { // @ts-ignore Papa.parse(file, { - header: false, // Index-based mapping + header: true, skipEmptyLines: true, complete: (results: any) => { try { @@ -427,40 +427,22 @@ export const processAdsCSV = (file: File): Promise => { for (let i = 0; i < len; i++) { const row = rows[i]; - if (!Array.isArray(row) || row.length < 12) continue; + if (!row || Object.keys(row).length < 5) continue; - // Check header row (Column A: Customer or Country) - const c0 = String(row[0]).trim().toLowerCase(); - if (c0.includes('customer') || c0.includes('country') || c0.includes('marketplace')) continue; + const countryRaw = getColumnValue(row, ['country', 'marketplace', 'portfolio', 'customer', 'kunde']); + const weekRaw = getColumnValue(row, ['week', 'woche', 'semana']); + const asin = getColumnValue(row, ['asin']); + const costRaw = getColumnValue(row, ['cost', 'spend', 'ausgaben', 'gasto', 'coste', 'ad spend']); + const clicksRaw = getColumnValue(row, ['clicks', 'klicks', 'clics']); + const impressionsRaw = getColumnValue(row, ['impressions', 'impresiones', 'imp']); + const cpcRaw = getColumnValue(row, ['cpc', 'cost-per-click', 'coste por clic']); + const ctrRaw = getColumnValue(row, ['ctr', 'click-through rate', 'click-through-rate', 'click through rate']); + const acosRaw = getColumnValue(row, ['acos', 'advertising cost of sales', 'aCOS']); + const conversionsRaw = getColumnValue(row, ['conversions', 'konversionen', 'orders', 'pedidos', 'total orders']); + const unitsRaw = getColumnValue(row, ['units', 'einheiten', 'unidades', 'units sold', 'total units']); + const salesRaw = getColumnValue(row, ['sales', 'umsatz', 'ventas', 'ad sales', 'total sales', 'sales (30d)']); - // Column mapping for CSV (same as Excel): - // A (0): Country - // B (1): Week - // C (2): ASIN - // D (3): Cost - // E (4): Clicks - // F (5): Impressions - // G (6): CPC - // H (7): CTR % - // I (8): ACOS % - // J (9): Conversions (30d) - // K (10): Units (30d) - // L (11): Sales (30d) - - const countryRaw = row[0]; - const weekRaw = row[1]; - const asin = row[2]; - const costRaw = row[3]; - const clicksRaw = row[4]; - const impressionsRaw = row[5]; - const cpcRaw = row[6]; - const ctrRaw = row[7]; - const acosRaw = row[8]; - const conversionsRaw = row[9]; - const unitsRaw = row[10]; - const salesRaw = row[11]; - - if (!asin || !countryRaw || weekRaw === undefined) continue; + if (!asin || !countryRaw || weekRaw === undefined || weekRaw === '') continue; const weekNum = parseInt(String(weekRaw)); if (isNaN(weekNum) || weekNum < 1 || weekNum > 53) continue; @@ -511,42 +493,28 @@ export const processAdsExcel = async (fileOrBuffer: File | ArrayBuffer): Promise } const worksheet = workbook.Sheets[sheetName]; - // Use header: 1 to get array of arrays (row-based) - const jsonData: any[][] = XLSX.utils.sheet_to_json(worksheet, { header: 1, defval: "" }); + // Use default format (header mapping) instead of array rows + const jsonData: any[] = XLSX.utils.sheet_to_json(worksheet, { defval: "" }); console.log(`Processing sheet ${sheetName}: ${jsonData.length} rows`); - // Skip header row (index 0), process data rows - for (let i = 1; i < jsonData.length; i++) { + // No need to skip index 0 since headers are mapped as keys automatically + for (let i = 0; i < jsonData.length; i++) { const row = jsonData[i]; - if (!row || row.length < 12) continue; + if (!row || Object.keys(row).length < 5) continue; - // Column mapping for Ads Weekly.xlsx: - // A (0): Country - // B (1): Week - // C (2): ASIN - // D (3): Cost - // E (4): Clicks - // F (5): Impressions - // G (6): CPC - // H (7): CTR % - // I (8): ACOS % - // J (9): Conversions (30d) - // K (10): Units (30d) - // L (11): Sales (30d) - - const countryRaw = row[0]; - const weekRaw = row[1]; - const asin = row[2]; - const costRaw = row[3]; - const clicksRaw = row[4]; - const impressionsRaw = row[5]; - const cpcRaw = row[6]; - const ctrRaw = row[7]; - const acosRaw = row[8]; - const conversionsRaw = row[9]; - const unitsRaw = row[10]; - const salesRaw = row[11]; + const countryRaw = getColumnValue(row, ['country', 'marketplace', 'portfolio', 'customer', 'kunde']); + const weekRaw = getColumnValue(row, ['week', 'woche', 'semana']); + const asin = getColumnValue(row, ['asin']); + const costRaw = getColumnValue(row, ['cost', 'spend', 'ausgaben', 'gasto', 'coste', 'ad spend']); + const clicksRaw = getColumnValue(row, ['clicks', 'klicks', 'clics']); + const impressionsRaw = getColumnValue(row, ['impressions', 'impresiones', 'imp']); + const cpcRaw = getColumnValue(row, ['cpc', 'cost-per-click', 'coste por clic']); + const ctrRaw = getColumnValue(row, ['ctr', 'click-through rate', 'click-through-rate', 'click through rate']); + const acosRaw = getColumnValue(row, ['acos', 'advertising cost of sales', 'aCOS']); + const conversionsRaw = getColumnValue(row, ['conversions', 'konversionen', 'orders', 'pedidos', 'total orders']); + const unitsRaw = getColumnValue(row, ['units', 'einheiten', 'unidades', 'units sold', 'total units']); + const salesRaw = getColumnValue(row, ['sales', 'umsatz', 'ventas', 'ad sales', 'total sales', 'sales (30d)']); // Skip if missing essential data if (!asin || !countryRaw || weekRaw === undefined || weekRaw === '') continue;