feat: bulk ASIN selection via space-separated paste in filters

Allow pasting multiple ASINs separated by spaces in any MultiSelectDropdown
search field and pressing Enter to auto-select all matching options.
Also switch ads CSV/Excel parsing to header-based column mapping for
robustness across different file formats.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christian Vidal Wolf
2026-02-25 14:01:11 +01:00
co-authored by Claude Opus 4.6
parent 69eafe8335
commit 487b8e105e
3 changed files with 47 additions and 65 deletions
+32 -64
View File
@@ -417,7 +417,7 @@ export const processAdsCSV = (file: File): Promise<AdsRecord[]> => {
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<AdsRecord[]> => {
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;