diff --git a/api/dropbox-proxy.js b/api/dropbox-proxy.js index 79e06dd..895fc94 100644 --- a/api/dropbox-proxy.js +++ b/api/dropbox-proxy.js @@ -1,23 +1,36 @@ -// Vercel serverless function — proxies the Dropbox file server-side (no CORS) +const Dropbox = require('dropbox').Dropbox; + export default async function handler(req, res) { - const fileUrl = - 'https://dl.dropboxusercontent.com/scl/fi/nkyabkq2jdwfudof2ovrl/Data-Matrix.xlsx' + - '?rlkey=1cz5fuabwipqqivihii7sybw0&st=io0g4wvb&dl=1'; + if (req.method === 'GET' && req.query.info === '1') { + const DBX = new Dropbox({ + accessToken: process.env.DROPBOX_ACCESS_TOKEN + }); + + try { + const fileInfo = await DBX.filesGetInfo({ path: '/Data-Matrix.xlsx' }); + return res.json({ + rev: fileInfo.result.rev, + size: fileInfo.result.size, + server_modified: fileInfo.result.server_modified + }); + } catch (err) { + return res.status(500).json({ error: err.message }); + } + } + + const DBX = new Dropbox({ + accessToken: process.env.DROPBOX_ACCESS_TOKEN + }); try { - const upstream = await fetch(fileUrl, { - headers: { 'User-Agent': 'Mozilla/5.0' }, - }); - - if (!upstream.ok) { - return res.status(upstream.status).send(`Dropbox error: ${upstream.status}`); - } - - const buffer = await upstream.arrayBuffer(); + const upstream = await DBX.filesDownload({ path: '/Data-Matrix.xlsx' }); + const buffer = Buffer.from(upstream.result.fileBinary); + res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Cache-Control', 'no-store'); - res.send(Buffer.from(buffer)); + res.send(buffer); } catch (err) { + console.error('Dropbox error:', err); res.status(500).send('Proxy error: ' + err.message); } } diff --git a/api/dropbox-sync.js b/api/dropbox-sync.js new file mode 100644 index 0000000..170fa86 --- /dev/null +++ b/api/dropbox-sync.js @@ -0,0 +1,51 @@ +import { createClient } from '@supabase/supabase-js'; + +const SUPABASE_URL = process.env.SUPABASE_URL || 'https://hwithddwaapyhnfwcesj.supabase.co'; +const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY || 'sb_publishable_fGXkh0bSrAOqSk2jWKAzSg_NJD9YPCv'; + +const supabase = createClient(SUPABASE_URL, SUPABASE_KEY); + +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + const { rows, fileMeta } = req.body; + + if (!rows || !Array.isArray(rows)) { + return res.status(400).json({ error: 'Missing rows data' }); + } + + const articleNoIdx = 0; + + const productsToUpsert = []; + + for (const row of rows) { + const productId = String(row[articleNoIdx]); + if (productId && productId.trim() !== '') { + productsToUpsert.push({ + product_id: productId, + data: row, + status: 'synced', + updated_at: new Date().toISOString() + }); + } + } + + const { error } = await supabase + .from('products') + .upsert(productsToUpsert, { + onConflict: 'product_id', + ignoreDuplicates: false + }); + + if (error) { + console.error('Supabase upsert error:', error); + return res.status(500).json({ error: error.message, detail: 'Failed to upsert products' }); + } + + return res.json({ + success: true, + syncedCount: productsToUpsert.length + }); +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a347982..96511dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.0", "dependencies": { "@google/genai": "^1.29.0", + "@supabase/supabase-js": "^2.103.0", "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", "clsx": "^2.1.1", "dotenv": "^17.2.3", + "dropbox": "^10.34.0", "express": "^4.21.2", "lucide-react": "^0.546.0", "motion": "^12.23.24", @@ -1175,6 +1177,92 @@ "win32" ] }, + "node_modules/@supabase/auth-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.103.0.tgz", + "integrity": "sha512-6zAanO6c+6gpHOlt5Lb9TlBBkJdZiUWkWCJKAxzkywBDcwaHlLJKXnjQGX6GyVCyKRR1e7sTq4re/yRTH6U/9A==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.103.0.tgz", + "integrity": "sha512-YrneV2NjskUkkmkZ2Jt2n3elBgbWzV4Y1M9MM370z2Zd5ZPFqFbY8KIoPwuNjtAGE9YrpKBxnbZqeF07BiN9Og==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/phoenix": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz", + "integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==", + "license": "MIT" + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.103.0.tgz", + "integrity": "sha512-rC3sRxYdPZymkp2CZR1MiNQgbOleD01bGsW8VxEKRR5nMkLZ1NgAS1QTQf78Wh30czFyk505ZYr9Od8/mWT2TA==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.103.0.tgz", + "integrity": "sha512-gcPtXzZ6izyyBVf2of7K3dEt8CScPJn8VcSlQq6oWL9QoE1kqfQl0oFrOMHd5qrcADewxI7OxxosLB8W4XqtIQ==", + "license": "MIT", + "dependencies": { + "@supabase/phoenix": "^0.4.0", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.103.0.tgz", + "integrity": "sha512-DHmlvdAXwtOmZNbkIZi4lkobPR3XjIzoOgzoz5duMf6G+sDeY015YrzMJCnqdccuYr7X5x4yYuSwF//RoN2dvQ==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.103.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.103.0.tgz", + "integrity": "sha512-j/6q5+LtXbR/YOLSLhy7Na74RD1cV2v+KwIIuuqMEjk1JpLEEyu0ynwDHpGoxMncDQl+R5FogaVqZm+85lZvtw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.103.0", + "@supabase/functions-js": "2.103.0", + "@supabase/postgrest-js": "2.103.0", + "@supabase/realtime-js": "2.103.0", + "@supabase/storage-js": "2.103.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@tailwindcss/node": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz", @@ -1549,6 +1637,17 @@ "undici-types": "~6.21.0" } }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, "node_modules/@types/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz", @@ -1602,6 +1701,15 @@ "@types/node": "*" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", @@ -1659,6 +1767,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.27", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", @@ -1905,6 +2019,18 @@ "node": ">=0.8" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -1985,6 +2111,15 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2025,6 +2160,41 @@ "url": "https://dotenvx.com" } }, + "node_modules/dropbox": { + "version": "10.34.0", + "resolved": "https://registry.npmjs.org/dropbox/-/dropbox-10.34.0.tgz", + "integrity": "sha512-5jb5/XzU0fSnq36/hEpwT5/QIep7MgqKuxghEG44xCu7HruOAjPdOb3x0geXv5O/hd0nHpQpWO+r5MjYTpMvJg==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=0.10.3" + }, + "peerDependencies": { + "@types/node-fetch": "^2.5.7" + } + }, + "node_modules/dropbox/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2112,6 +2282,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", @@ -2318,6 +2503,22 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -2564,6 +2765,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2609,6 +2825,15 @@ "node": ">= 14" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -3709,6 +3934,12 @@ "node": ">=0.6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -4367,6 +4598,22 @@ "node": ">= 8" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/wmf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", diff --git a/package.json b/package.json index aa92124..873e65a 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,12 @@ }, "dependencies": { "@google/genai": "^1.29.0", + "@supabase/supabase-js": "^2.103.0", "@tailwindcss/vite": "^4.1.14", "@vitejs/plugin-react": "^5.0.4", "clsx": "^2.1.1", "dotenv": "^17.2.3", + "dropbox": "^10.34.0", "express": "^4.21.2", "lucide-react": "^0.546.0", "motion": "^12.23.24", diff --git a/src/App.tsx b/src/App.tsx index ba480f2..13aacfb 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -47,79 +47,97 @@ export default function App() { useEffect(() => { const loadDefaultData = async () => { - // In dev: Vite proxy handles /dropbox-file (see vite.config.ts) - // In prod: Vercel serverless function at /api/dropbox-proxy handles it - const fileUrl = import.meta.env.DEV - ? '/dropbox-file/scl/fi/nkyabkq2jdwfudof2ovrl/Data-Matrix.xlsx?rlkey=1cz5fuabwipqqivihii7sybw0&st=io0g4wvb&dl=1' - : '/api/dropbox-proxy'; - setIsLoadingDefault(true); setDefaultLoadError(null); try { - console.log('Fetching Data-Matrix.xlsx via Vite proxy...'); - const response = await fetch(fileUrl); - - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - - const arrayBuffer = await response.arrayBuffer(); - if (arrayBuffer.byteLength < 100) { - throw new Error('File too small — possibly empty or error response'); - } - - const wb = XLSX.read(arrayBuffer, { type: 'array' }); - const wsname = wb.SheetNames[0]; - const ws = wb.Sheets[wsname]; - const data = XLSX.utils.sheet_to_json(ws, { header: 1 }); - - if (data.length > 0) { - 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); + const isDev = import.meta.env.DEV; + + let rows: any[][]; + let fileMeta = { rev: '', size: 0 }; + + if (isDev) { + const fileUrl = '/dropbox-file/scl/fi/nkyabkq2jdwfudof2ovrl/Data-Matrix.xlsx?rlkey=1cz5fuabwipqqivihii7sybw0&st=io0g4wvb&dl=1'; + console.log('Fetching Data-Matrix.xlsx from Dropbox...'); + const response = await fetch(fileUrl); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const arrayBuffer = await response.arrayBuffer(); + if (arrayBuffer.byteLength < 100) throw new Error('File too small'); + + const wb = XLSX.read(arrayBuffer, { type: 'array' }); + const wsname = wb.SheetNames[0]; + const ws = wb.Sheets[wsname]; + const data = XLSX.utils.sheet_to_json(ws, { header: 1 }); + rows = data.slice(1); + fileMeta = { rev: 'dev', size: arrayBuffer.byteLength }; + } else { + console.log('Fetching file info from Dropbox...'); + const infoRes = await fetch('/api/dropbox-proxy?info=1'); + if (infoRes.ok) { + fileMeta = await infoRes.json(); + console.log('File meta:', fileMeta); } + + console.log('Fetching Data-Matrix.xlsx from proxy...'); + const response = await fetch('/api/dropbox-proxy'); + if (!response.ok) throw new Error(`HTTP ${response.status}`); + const arrayBuffer = await response.arrayBuffer(); + if (arrayBuffer.byteLength < 100) throw new Error('File too small'); + + const wb = XLSX.read(arrayBuffer, { type: 'array' }); + const wsname = wb.SheetNames[0]; + const ws = wb.Sheets[wsname]; + const data = XLSX.utils.sheet_to_json(ws, { header: 1 }); + rows = data.slice(1); + } - console.log('Applying Supabase overrides...'); + if (rows.length > 0) { + const rawHeaders = (data: any[]) => data[0]; + + console.log('Syncing Excel data to Supabase...'); + const syncRes = await fetch('/api/dropbox-sync', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ rows, fileMeta }) + }); + + if (syncRes.ok) { + const syncResult = await syncRes.json(); + console.log('Supabase sync result:', syncResult); + } else { + console.warn('Supabase sync failed, falling back to direct Excel data'); + } + + console.log('Fetching synced data from Supabase...'); const syncedData = await getAllSyncedRows(); + const articleNoIdx = COLUMNS.ARTICLE_NO; - - const processedRows = rawRows.map(row => { + const processedRows = rows.map(row => { const articleNo = String(row[articleNoIdx]); const synced = syncedData[articleNo]; const finalRow = synced ? synced.data : row; - - // Sync status_check + if (synced && synced.status === 'pending') { setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' })); } - - // Format numeric/price fields to 2 decimal places - return finalRow.map((val, idx) => { + + return finalRow.map((val: any, idx: number) => { if (val === undefined || val === null || val === '') return val; const header = (rawHeaders[idx] || '').toLowerCase(); - - // Skip Article No, Barcodes, and other code-like fields - // But allow if it's a weight/measure column (e.g. Article NW (kg)) + if ((header.includes('id') || header.includes('no') || header.includes('code') || header.includes('art.') || header.includes('barcode') || header.includes('article')) && !(header.includes('nw') || header.includes('gw') || header.includes('weight') || header.includes('kg'))) { return val; } - + const formatKeywords = ['price', 'eur', 'cost', 'msrp', 'net', 'gross', 'netto', 'brutto', 'pp', 'pph', 'uvp', 'vpe', 'stk', 'nw', 'gw', 'weight', 'kg']; const shouldFormat = formatKeywords.some(kw => header.includes(kw)); - + if (typeof val === 'number') { return Number(val.toFixed(2)); } - + if (typeof val === 'string') { const normalized = val.trim().replace(',', '.'); const num = parseFloat(normalized); @@ -130,9 +148,13 @@ export default function App() { return val; }); }); - + + const asinIdx = (rawHeaders(rows) as string[]).findIndex((h: string) => + String(h).toLowerCase().trim() === 'asin' + ); + setAppState({ - headers: rawHeaders, + headers: rawHeaders(rows), data: processedRows, fileName: 'Data-Matrix.xlsx (Cloud Sync)', fileDate: new Date(), diff --git a/vercel.json b/vercel.json index 1ed5d02..72d37c2 100644 --- a/vercel.json +++ b/vercel.json @@ -1,5 +1,6 @@ { "rewrites": [ - { "source": "/api/dropbox-proxy", "destination": "api/dropbox-proxy.js" } + { "source": "/api/dropbox-proxy", "destination": "api/dropbox-proxy.js" }, + { "source": "/api/dropbox-sync", "destination": "api/dropbox-sync.js" } ] }