mirror of
https://github.com/christianvidalwolf-prog/Craze-Data-check.git
synced 2026-08-03 14:55:24 +02:00
feat: sync Excel data to Supabase on load - detect new Dropbox files automatically
This commit is contained in:
+25
-12
@@ -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';
|
|
||||||
|
|
||||||
try {
|
export default async function handler(req, res) {
|
||||||
const upstream = await fetch(fileUrl, {
|
if (req.method === 'GET' && req.query.info === '1') {
|
||||||
headers: { 'User-Agent': 'Mozilla/5.0' },
|
const DBX = new Dropbox({
|
||||||
|
accessToken: process.env.DROPBOX_ACCESS_TOKEN
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!upstream.ok) {
|
try {
|
||||||
return res.status(upstream.status).send(`Dropbox error: ${upstream.status}`);
|
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 DBX.filesDownload({ path: '/Data-Matrix.xlsx' });
|
||||||
|
const buffer = Buffer.from(upstream.result.fileBinary);
|
||||||
|
|
||||||
const buffer = await upstream.arrayBuffer();
|
|
||||||
res.setHeader('Content-Type', 'application/octet-stream');
|
res.setHeader('Content-Type', 'application/octet-stream');
|
||||||
res.setHeader('Cache-Control', 'no-store');
|
res.setHeader('Cache-Control', 'no-store');
|
||||||
res.send(Buffer.from(buffer));
|
res.send(buffer);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('Dropbox error:', err);
|
||||||
res.status(500).send('Proxy error: ' + err.message);
|
res.status(500).send('Proxy error: ' + err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
Generated
+247
@@ -9,10 +9,12 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^1.29.0",
|
"@google/genai": "^1.29.0",
|
||||||
|
"@supabase/supabase-js": "^2.103.0",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
|
"dropbox": "^10.34.0",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"lucide-react": "^0.546.0",
|
"lucide-react": "^0.546.0",
|
||||||
"motion": "^12.23.24",
|
"motion": "^12.23.24",
|
||||||
@@ -1175,6 +1177,92 @@
|
|||||||
"win32"
|
"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": {
|
"node_modules/@tailwindcss/node": {
|
||||||
"version": "4.2.2",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
|
||||||
@@ -1549,6 +1637,17 @@
|
|||||||
"undici-types": "~6.21.0"
|
"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": {
|
"node_modules/@types/qs": {
|
||||||
"version": "6.15.0",
|
"version": "6.15.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.0.tgz",
|
||||||
@@ -1602,6 +1701,15 @@
|
|||||||
"@types/node": "*"
|
"@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": {
|
"node_modules/@vitejs/plugin-react": {
|
||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
|
||||||
@@ -1659,6 +1767,12 @@
|
|||||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/autoprefixer": {
|
||||||
"version": "10.4.27",
|
"version": "10.4.27",
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
|
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz",
|
||||||
@@ -1905,6 +2019,18 @@
|
|||||||
"node": ">=0.8"
|
"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": {
|
"node_modules/content-disposition": {
|
||||||
"version": "0.5.4",
|
"version": "0.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
"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": {
|
"node_modules/depd": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||||
@@ -2025,6 +2160,41 @@
|
|||||||
"url": "https://dotenvx.com"
|
"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": {
|
"node_modules/dunder-proto": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
@@ -2112,6 +2282,21 @@
|
|||||||
"node": ">= 0.4"
|
"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": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.27.4",
|
"version": "0.27.4",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz",
|
||||||
@@ -2318,6 +2503,22 @@
|
|||||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/formdata-polyfill": {
|
||||||
"version": "4.0.10",
|
"version": "4.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||||
@@ -2564,6 +2765,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
@@ -2609,6 +2825,15 @@
|
|||||||
"node": ">= 14"
|
"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": {
|
"node_modules/iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
@@ -3709,6 +3934,12 @@
|
|||||||
"node": ">=0.6"
|
"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": {
|
"node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
@@ -4367,6 +4598,22 @@
|
|||||||
"node": ">= 8"
|
"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": {
|
"node_modules/wmf": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
|
||||||
|
|||||||
@@ -12,10 +12,12 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@google/genai": "^1.29.0",
|
"@google/genai": "^1.29.0",
|
||||||
|
"@supabase/supabase-js": "^2.103.0",
|
||||||
"@tailwindcss/vite": "^4.1.14",
|
"@tailwindcss/vite": "^4.1.14",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
|
"dropbox": "^10.34.0",
|
||||||
"express": "^4.21.2",
|
"express": "^4.21.2",
|
||||||
"lucide-react": "^0.546.0",
|
"lucide-react": "^0.546.0",
|
||||||
"motion": "^12.23.24",
|
"motion": "^12.23.24",
|
||||||
|
|||||||
+62
-40
@@ -47,66 +47,84 @@ export default function App() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadDefaultData = async () => {
|
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);
|
setIsLoadingDefault(true);
|
||||||
setDefaultLoadError(null);
|
setDefaultLoadError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('Fetching Data-Matrix.xlsx via Vite proxy...');
|
const isDev = import.meta.env.DEV;
|
||||||
const response = await fetch(fileUrl);
|
|
||||||
|
|
||||||
if (!response.ok) {
|
let rows: any[][];
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
let fileMeta = { rev: '', size: 0 };
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await response.arrayBuffer();
|
if (isDev) {
|
||||||
if (arrayBuffer.byteLength < 100) {
|
const fileUrl = '/dropbox-file/scl/fi/nkyabkq2jdwfudof2ovrl/Data-Matrix.xlsx?rlkey=1cz5fuabwipqqivihii7sybw0&st=io0g4wvb&dl=1';
|
||||||
throw new Error('File too small — possibly empty or error response');
|
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 wb = XLSX.read(arrayBuffer, { type: 'array' });
|
||||||
const wsname = wb.SheetNames[0];
|
const wsname = wb.SheetNames[0];
|
||||||
const ws = wb.Sheets[wsname];
|
const ws = wb.Sheets[wsname];
|
||||||
const data = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1 });
|
const data = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1 });
|
||||||
|
rows = data.slice(1);
|
||||||
if (data.length > 0) {
|
fileMeta = { rev: 'dev', size: arrayBuffer.byteLength };
|
||||||
const rawHeaders = data[0];
|
} else {
|
||||||
const rawRows = data.slice(1);
|
console.log('Fetching file info from Dropbox...');
|
||||||
|
const infoRes = await fetch('/api/dropbox-proxy?info=1');
|
||||||
// Find ASIN column index from headers (case insensitive)
|
if (infoRes.ok) {
|
||||||
const asinIdx = (rawHeaders as string[]).findIndex((h: string) =>
|
fileMeta = await infoRes.json();
|
||||||
String(h).toLowerCase().trim() === 'asin'
|
console.log('File meta:', fileMeta);
|
||||||
);
|
|
||||||
if (asinIdx !== -1) {
|
|
||||||
console.log('ASIN column found at index:', asinIdx);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Applying Supabase overrides...');
|
console.log('Fetching Data-Matrix.xlsx from proxy...');
|
||||||
const syncedData = await getAllSyncedRows();
|
const response = await fetch('/api/dropbox-proxy');
|
||||||
const articleNoIdx = COLUMNS.ARTICLE_NO;
|
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 processedRows = rawRows.map(row => {
|
const wb = XLSX.read(arrayBuffer, { type: 'array' });
|
||||||
|
const wsname = wb.SheetNames[0];
|
||||||
|
const ws = wb.Sheets[wsname];
|
||||||
|
const data = XLSX.utils.sheet_to_json<any[]>(ws, { header: 1 });
|
||||||
|
rows = data.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = rows.map(row => {
|
||||||
const articleNo = String(row[articleNoIdx]);
|
const articleNo = String(row[articleNoIdx]);
|
||||||
const synced = syncedData[articleNo];
|
const synced = syncedData[articleNo];
|
||||||
const finalRow = synced ? synced.data : row;
|
const finalRow = synced ? synced.data : row;
|
||||||
|
|
||||||
// Sync status_check
|
|
||||||
if (synced && synced.status === 'pending') {
|
if (synced && synced.status === 'pending') {
|
||||||
setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' }));
|
setRowStatuses(prev => ({ ...prev, [articleNo]: 'pending' }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format numeric/price fields to 2 decimal places
|
return finalRow.map((val: any, idx: number) => {
|
||||||
return finalRow.map((val, idx) => {
|
|
||||||
if (val === undefined || val === null || val === '') return val;
|
if (val === undefined || val === null || val === '') return val;
|
||||||
const header = (rawHeaders[idx] || '').toLowerCase();
|
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') ||
|
if ((header.includes('id') || header.includes('no') || header.includes('code') ||
|
||||||
header.includes('art.') || header.includes('barcode') || header.includes('article')) &&
|
header.includes('art.') || header.includes('barcode') || header.includes('article')) &&
|
||||||
!(header.includes('nw') || header.includes('gw') || header.includes('weight') || header.includes('kg'))) {
|
!(header.includes('nw') || header.includes('gw') || header.includes('weight') || header.includes('kg'))) {
|
||||||
@@ -131,8 +149,12 @@ export default function App() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const asinIdx = (rawHeaders(rows) as string[]).findIndex((h: string) =>
|
||||||
|
String(h).toLowerCase().trim() === 'asin'
|
||||||
|
);
|
||||||
|
|
||||||
setAppState({
|
setAppState({
|
||||||
headers: rawHeaders,
|
headers: rawHeaders(rows),
|
||||||
data: processedRows,
|
data: processedRows,
|
||||||
fileName: 'Data-Matrix.xlsx (Cloud Sync)',
|
fileName: 'Data-Matrix.xlsx (Cloud Sync)',
|
||||||
fileDate: new Date(),
|
fileDate: new Date(),
|
||||||
|
|||||||
+2
-1
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"rewrites": [
|
"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" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user