Add product search and template creation functionality to validation step
This commit is contained in:
@@ -566,6 +566,9 @@ router.get('/field-options', async (req, res) => {
|
||||
};
|
||||
});
|
||||
|
||||
// Add debugging to verify category types
|
||||
console.log(`Returning ${result.categories.length} categories with types: ${Array.from(new Set(result.categories.map(c => c.type))).join(', ')}`);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Error fetching import field options:', error);
|
||||
@@ -715,4 +718,232 @@ router.get('/list-uploads', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Search products from production database
|
||||
router.get('/search-products', async (req, res) => {
|
||||
const { q, company, dateRange } = req.query;
|
||||
|
||||
if (!q) {
|
||||
return res.status(400).json({ error: 'Search term is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const { connection } = await getDbConnection();
|
||||
|
||||
// Build WHERE clause with additional filters
|
||||
let whereClause = `
|
||||
WHERE (
|
||||
p.description LIKE ? OR
|
||||
p.itemnumber LIKE ? OR
|
||||
p.upc LIKE ? OR
|
||||
pc1.name LIKE ? OR
|
||||
s.companyname LIKE ?
|
||||
)`;
|
||||
|
||||
// Add company filter if provided
|
||||
if (company) {
|
||||
whereClause += ` AND p.company = ${connection.escape(company)}`;
|
||||
}
|
||||
|
||||
// Add date range filter if provided
|
||||
if (dateRange) {
|
||||
let dateCondition;
|
||||
const now = new Date();
|
||||
|
||||
switch(dateRange) {
|
||||
case '1week':
|
||||
// Last week: date is after (current date - 7 days)
|
||||
const weekAgo = new Date(now);
|
||||
weekAgo.setDate(now.getDate() - 7);
|
||||
dateCondition = `p.datein >= ${connection.escape(weekAgo.toISOString().slice(0, 10))}`;
|
||||
break;
|
||||
case '1month':
|
||||
// Last month: date is after (current date - 30 days)
|
||||
const monthAgo = new Date(now);
|
||||
monthAgo.setDate(now.getDate() - 30);
|
||||
dateCondition = `p.datein >= ${connection.escape(monthAgo.toISOString().slice(0, 10))}`;
|
||||
break;
|
||||
case '2months':
|
||||
// Last 2 months: date is after (current date - 60 days)
|
||||
const twoMonthsAgo = new Date(now);
|
||||
twoMonthsAgo.setDate(now.getDate() - 60);
|
||||
dateCondition = `p.datein >= ${connection.escape(twoMonthsAgo.toISOString().slice(0, 10))}`;
|
||||
break;
|
||||
case '3months':
|
||||
// Last 3 months: date is after (current date - 90 days)
|
||||
const threeMonthsAgo = new Date(now);
|
||||
threeMonthsAgo.setDate(now.getDate() - 90);
|
||||
dateCondition = `p.datein >= ${connection.escape(threeMonthsAgo.toISOString().slice(0, 10))}`;
|
||||
break;
|
||||
case '6months':
|
||||
// Last 6 months: date is after (current date - 180 days)
|
||||
const sixMonthsAgo = new Date(now);
|
||||
sixMonthsAgo.setDate(now.getDate() - 180);
|
||||
dateCondition = `p.datein >= ${connection.escape(sixMonthsAgo.toISOString().slice(0, 10))}`;
|
||||
break;
|
||||
case '1year':
|
||||
// Last year: date is after (current date - 365 days)
|
||||
const yearAgo = new Date(now);
|
||||
yearAgo.setDate(now.getDate() - 365);
|
||||
dateCondition = `p.datein >= ${connection.escape(yearAgo.toISOString().slice(0, 10))}`;
|
||||
break;
|
||||
default:
|
||||
// If an unrecognized value is provided, don't add a date condition
|
||||
dateCondition = null;
|
||||
}
|
||||
|
||||
if (dateCondition) {
|
||||
whereClause += ` AND ${dateCondition}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Special case for wildcard search
|
||||
const isWildcardSearch = q === '*';
|
||||
const searchPattern = isWildcardSearch ? '%' : `%${q}%`;
|
||||
const exactPattern = isWildcardSearch ? '%' : q;
|
||||
|
||||
// Search for products based on various fields
|
||||
const query = `
|
||||
SELECT
|
||||
p.pid,
|
||||
p.description AS title,
|
||||
p.notes AS description,
|
||||
p.itemnumber AS sku,
|
||||
p.upc AS barcode,
|
||||
p.harmonized_tariff_code,
|
||||
pcp.price_each AS price,
|
||||
p.sellingprice AS regular_price,
|
||||
CASE
|
||||
WHEN EXISTS (SELECT 1 FROM product_inventory WHERE pid = p.pid AND count > 0)
|
||||
THEN (SELECT ROUND(AVG(costeach), 5) FROM product_inventory WHERE pid = p.pid AND count > 0)
|
||||
ELSE (SELECT costeach FROM product_inventory WHERE pid = p.pid ORDER BY daterec DESC LIMIT 1)
|
||||
END AS cost_price,
|
||||
s.companyname AS vendor,
|
||||
sid.supplier_itemnumber AS vendor_reference,
|
||||
sid.notions_itemnumber AS notions_reference,
|
||||
pc1.name AS brand,
|
||||
p.company AS brand_id,
|
||||
pc2.name AS line,
|
||||
p.line AS line_id,
|
||||
pc3.name AS subline,
|
||||
p.subline AS subline_id,
|
||||
pc4.name AS artist,
|
||||
p.artist AS artist_id,
|
||||
COALESCE(CASE
|
||||
WHEN sid.supplier_id = 92 THEN sid.notions_qty_per_unit
|
||||
ELSE sid.supplier_qty_per_unit
|
||||
END, sid.notions_qty_per_unit) AS moq,
|
||||
p.weight,
|
||||
p.length,
|
||||
p.width,
|
||||
p.height,
|
||||
p.country_of_origin,
|
||||
p.totalsold AS total_sold,
|
||||
p.datein AS first_received,
|
||||
pls.date_sold AS date_last_sold
|
||||
FROM products p
|
||||
LEFT JOIN product_current_prices pcp ON p.pid = pcp.pid AND pcp.active = 1
|
||||
LEFT JOIN supplier_item_data sid ON p.pid = sid.pid
|
||||
LEFT JOIN suppliers s ON sid.supplier_id = s.supplierid
|
||||
LEFT JOIN product_categories pc1 ON p.company = pc1.cat_id
|
||||
LEFT JOIN product_categories pc2 ON p.line = pc2.cat_id
|
||||
LEFT JOIN product_categories pc3 ON p.subline = pc3.cat_id
|
||||
LEFT JOIN product_categories pc4 ON p.artist = pc4.cat_id
|
||||
LEFT JOIN product_last_sold pls ON p.pid = pls.pid
|
||||
${whereClause}
|
||||
GROUP BY p.pid
|
||||
${isWildcardSearch ? 'ORDER BY p.datein DESC' : `
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN p.description LIKE ? THEN 1
|
||||
WHEN p.itemnumber = ? THEN 2
|
||||
WHEN p.upc = ? THEN 3
|
||||
WHEN pc1.name LIKE ? THEN 4
|
||||
WHEN s.companyname LIKE ? THEN 5
|
||||
ELSE 6
|
||||
END
|
||||
`}
|
||||
`;
|
||||
|
||||
// Prepare query parameters based on whether it's a wildcard search
|
||||
let queryParams;
|
||||
if (isWildcardSearch) {
|
||||
queryParams = [
|
||||
searchPattern, // LIKE for description
|
||||
searchPattern, // LIKE for itemnumber
|
||||
searchPattern, // LIKE for upc
|
||||
searchPattern, // LIKE for brand name
|
||||
searchPattern // LIKE for company name
|
||||
];
|
||||
} else {
|
||||
queryParams = [
|
||||
searchPattern, // LIKE for description
|
||||
searchPattern, // LIKE for itemnumber
|
||||
searchPattern, // LIKE for upc
|
||||
searchPattern, // LIKE for brand name
|
||||
searchPattern, // LIKE for company name
|
||||
// For ORDER BY clause
|
||||
searchPattern, // LIKE for description
|
||||
exactPattern, // Exact match for itemnumber
|
||||
exactPattern, // Exact match for upc
|
||||
searchPattern, // LIKE for brand name
|
||||
searchPattern // LIKE for company name
|
||||
];
|
||||
}
|
||||
|
||||
const [results] = await connection.query(query, queryParams);
|
||||
|
||||
res.json(results);
|
||||
} catch (error) {
|
||||
console.error('Error searching products:', error);
|
||||
res.status(500).json({ error: 'Failed to search products' });
|
||||
}
|
||||
});
|
||||
|
||||
// Get product categories for a specific product
|
||||
router.get('/product-categories/:pid', async (req, res) => {
|
||||
try {
|
||||
const { pid } = req.params;
|
||||
|
||||
if (!pid || isNaN(parseInt(pid))) {
|
||||
return res.status(400).json({ error: 'Valid product ID is required' });
|
||||
}
|
||||
|
||||
// Use the getDbConnection function instead of getPool
|
||||
const { connection } = await getDbConnection();
|
||||
|
||||
// Query to get categories for a specific product
|
||||
const query = `
|
||||
SELECT pc.cat_id, pc.name, pc.type, pc.combined_name
|
||||
FROM product_category_index pci
|
||||
JOIN product_categories pc ON pci.cat_id = pc.cat_id
|
||||
WHERE pci.pid = ?
|
||||
ORDER BY pc.type, pc.name
|
||||
`;
|
||||
|
||||
const [rows] = await connection.query(query, [pid]);
|
||||
|
||||
// Add debugging to log category types
|
||||
const categoryTypes = rows.map(row => row.type);
|
||||
const uniqueTypes = [...new Set(categoryTypes)];
|
||||
console.log(`Product ${pid} has ${rows.length} categories with types: ${uniqueTypes.join(', ')}`);
|
||||
console.log('Categories:', rows.map(row => ({ id: row.cat_id, name: row.name, type: row.type })));
|
||||
|
||||
// Format the response to match the expected format in the frontend
|
||||
const categories = rows.map(category => ({
|
||||
value: category.cat_id.toString(),
|
||||
label: category.name,
|
||||
type: category.type,
|
||||
combined_name: category.combined_name
|
||||
}));
|
||||
|
||||
res.json(categories);
|
||||
} catch (error) {
|
||||
console.error('Error fetching product categories:', error);
|
||||
res.status(500).json({
|
||||
error: 'Failed to fetch product categories',
|
||||
details: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
260
inventory/package-lock.json
generated
260
inventory/package-lock.json
generated
@@ -42,7 +42,7 @@
|
||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||
"@radix-ui/react-select": "^2.1.4",
|
||||
"@radix-ui/react-separator": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.2",
|
||||
"@radix-ui/react-toast": "^1.2.6",
|
||||
@@ -57,6 +57,7 @@
|
||||
"@tanstack/virtual-core": "^3.11.2",
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"axios": "^1.8.1",
|
||||
"chakra-react-select": "^4.7.5",
|
||||
"chakra-ui-steps": "^2.0.4",
|
||||
"chart.js": "^4.4.7",
|
||||
@@ -5316,6 +5317,12 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"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/attr-accept": {
|
||||
"version": "2.2.5",
|
||||
"resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
|
||||
@@ -5363,6 +5370,17 @@
|
||||
"postcss": "^8.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
|
||||
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-macros": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
|
||||
@@ -5507,6 +5525,19 @@
|
||||
"ieee754": "^1.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind-apply-helpers": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0",
|
||||
"function-bind": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/callsites": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
|
||||
@@ -6185,6 +6216,18 @@
|
||||
"integrity": "sha512-zW190nQTIoXcGCaU08DvVNFTmQhUpnJfVuAKfWqUQkflXKpaDdpaYoM0iluLS9lgJNHyBF58KKA2FBEwkD7wog==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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/commander": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
|
||||
@@ -6491,6 +6534,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"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/detect-node-es": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
|
||||
@@ -6528,6 +6580,20 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dunder-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"gopd": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/eastasianwidth": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
@@ -6556,6 +6622,51 @@
|
||||
"is-arrayish": "^0.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-errors": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"es-errors": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"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.24.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
|
||||
@@ -7041,6 +7152,26 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||
@@ -7069,6 +7200,21 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"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",
|
||||
@@ -7193,6 +7339,30 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-intrinsic": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
|
||||
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"call-bind-apply-helpers": "^1.0.2",
|
||||
"es-define-property": "^1.0.1",
|
||||
"es-errors": "^1.3.0",
|
||||
"es-object-atoms": "^1.1.1",
|
||||
"function-bind": "^1.1.2",
|
||||
"get-proto": "^1.0.1",
|
||||
"gopd": "^1.2.0",
|
||||
"has-symbols": "^1.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"math-intrinsics": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/get-nonce": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||
@@ -7202,6 +7372,19 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-proto": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dunder-proto": "^1.0.1",
|
||||
"es-object-atoms": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
@@ -7283,6 +7466,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/gopd": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
@@ -7306,6 +7501,33 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/has-symbols": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"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",
|
||||
@@ -7770,6 +7992,15 @@
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/math-intrinsics": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
@@ -7804,6 +8035,27 @@
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
@@ -8474,6 +8726,12 @@
|
||||
"react-is": "^16.13.1"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
||||
"@radix-ui/react-select": "^2.1.4",
|
||||
"@radix-ui/react-separator": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.1",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.1.2",
|
||||
"@radix-ui/react-tabs": "^1.1.2",
|
||||
"@radix-ui/react-toast": "^1.2.6",
|
||||
@@ -59,6 +59,7 @@
|
||||
"@tanstack/virtual-core": "^3.11.2",
|
||||
"@types/js-levenshtein": "^1.1.3",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"axios": "^1.8.1",
|
||||
"chakra-react-select": "^4.7.5",
|
||||
"chakra-ui-steps": "^2.0.4",
|
||||
"chart.js": "^4.4.7",
|
||||
|
||||
1569
inventory/src/components/products/ProductSearchDialog.tsx
Normal file
1569
inventory/src/components/products/ProductSearchDialog.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -296,24 +296,54 @@ export const ImageUploadStep = <T extends string>({
|
||||
// Get the current product
|
||||
const product = newData[productIndex];
|
||||
|
||||
// We need to update product_images array directly instead of the image_url field
|
||||
// Initialize product_images array if it doesn't exist
|
||||
if (!product.product_images) {
|
||||
product.product_images = [];
|
||||
} else if (typeof product.product_images === 'string') {
|
||||
// Handle case where it might be a comma-separated string
|
||||
product.product_images = product.product_images.split(',').filter(Boolean);
|
||||
return newData;
|
||||
}
|
||||
|
||||
// Handle different formats of product_images
|
||||
let images: any[] = [];
|
||||
|
||||
if (typeof product.product_images === 'string') {
|
||||
try {
|
||||
// Try to parse as JSON
|
||||
images = JSON.parse(product.product_images);
|
||||
} catch (e) {
|
||||
// If not JSON, split by comma if it's a string
|
||||
images = product.product_images.split(',').filter(Boolean).map(url => ({
|
||||
imageUrl: url.trim(),
|
||||
pid: product.id || 0,
|
||||
iid: 0,
|
||||
type: 0,
|
||||
order: 255,
|
||||
width: 0,
|
||||
height: 0,
|
||||
hidden: 0
|
||||
}));
|
||||
}
|
||||
} else if (Array.isArray(product.product_images)) {
|
||||
// Use the array directly
|
||||
images = product.product_images;
|
||||
} else if (product.product_images) {
|
||||
// Handle case where it might be a single value
|
||||
images = [product.product_images];
|
||||
}
|
||||
|
||||
// Filter out the image URL we're removing
|
||||
if (Array.isArray(product.product_images)) {
|
||||
product.product_images = product.product_images.filter((url: string) => url && url !== imageUrl);
|
||||
}
|
||||
const filteredImages = images.filter(img => {
|
||||
const imgUrl = typeof img === 'string' ? img : img.imageUrl;
|
||||
return imgUrl !== imageUrl;
|
||||
});
|
||||
|
||||
// Update the product_images field
|
||||
product.product_images = filteredImages;
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
// Function to add an image URL to a product
|
||||
const addImageToProduct = (productIndex: number, imageUrl: string) => {
|
||||
const addImageToProduct = (productIndex: number, imageUrl: string, imageData?: any) => {
|
||||
// Create a copy of the data
|
||||
const newData = [...data];
|
||||
|
||||
@@ -323,21 +353,76 @@ export const ImageUploadStep = <T extends string>({
|
||||
// Initialize product_images array if it doesn't exist
|
||||
if (!product.product_images) {
|
||||
product.product_images = [];
|
||||
} else if (typeof product.product_images === 'string') {
|
||||
// Handle case where it might be a comma-separated string
|
||||
product.product_images = product.product_images.split(',').filter(Boolean);
|
||||
}
|
||||
|
||||
// Ensure it's an array
|
||||
if (!Array.isArray(product.product_images)) {
|
||||
product.product_images = [product.product_images].filter(Boolean);
|
||||
// Handle different formats of product_images
|
||||
let images: any[] = [];
|
||||
|
||||
if (typeof product.product_images === 'string') {
|
||||
try {
|
||||
// Try to parse as JSON
|
||||
images = JSON.parse(product.product_images);
|
||||
} catch (e) {
|
||||
// If not JSON, split by comma if it's a string
|
||||
images = product.product_images.split(',').filter(Boolean).map(url => ({
|
||||
imageUrl: url.trim(),
|
||||
pid: product.id || 0,
|
||||
iid: 0,
|
||||
type: 0,
|
||||
order: 255,
|
||||
width: 0,
|
||||
height: 0,
|
||||
hidden: 0
|
||||
}));
|
||||
}
|
||||
} else if (Array.isArray(product.product_images)) {
|
||||
// Use the array directly
|
||||
images = product.product_images;
|
||||
} else if (product.product_images) {
|
||||
// Handle case where it might be a single value
|
||||
images = [product.product_images];
|
||||
}
|
||||
|
||||
// Check if the image URL already exists
|
||||
const exists = images.some(img => {
|
||||
const imgUrl = typeof img === 'string' ? img : img.imageUrl;
|
||||
return imgUrl === imageUrl;
|
||||
});
|
||||
|
||||
// Only add if the URL doesn't already exist
|
||||
if (!product.product_images.includes(imageUrl)) {
|
||||
product.product_images.push(imageUrl);
|
||||
if (!exists) {
|
||||
// Create a new image object with schema fields
|
||||
const newImage = imageData || {
|
||||
imageUrl,
|
||||
pid: product.id || 0,
|
||||
iid: Math.floor(Math.random() * 10000), // Generate a temporary iid
|
||||
type: 0,
|
||||
order: images.length,
|
||||
width: 0,
|
||||
height: 0,
|
||||
hidden: 0
|
||||
};
|
||||
|
||||
// If imageData is a string, convert it to an object
|
||||
if (typeof newImage === 'string') {
|
||||
newImage = {
|
||||
imageUrl: newImage,
|
||||
pid: product.id || 0,
|
||||
iid: Math.floor(Math.random() * 10000),
|
||||
type: 0,
|
||||
order: images.length,
|
||||
width: 0,
|
||||
height: 0,
|
||||
hidden: 0
|
||||
};
|
||||
}
|
||||
|
||||
images.push(newImage);
|
||||
}
|
||||
|
||||
// Update the product_images field
|
||||
product.product_images = images;
|
||||
|
||||
return newData;
|
||||
};
|
||||
|
||||
@@ -530,7 +615,15 @@ export const ImageUploadStep = <T extends string>({
|
||||
productIndex,
|
||||
imageUrl: '',
|
||||
loading: true,
|
||||
fileName: file.name
|
||||
fileName: file.name,
|
||||
// Add schema fields
|
||||
pid: data[productIndex].id || 0,
|
||||
iid: 0, // Will be assigned by server
|
||||
type: 0,
|
||||
order: productImages.filter(img => img.productIndex === productIndex).length + i,
|
||||
width: 0,
|
||||
height: 0,
|
||||
hidden: 0
|
||||
};
|
||||
|
||||
setProductImages(prev => [...prev, newImage]);
|
||||
@@ -559,13 +652,21 @@ export const ImageUploadStep = <T extends string>({
|
||||
setProductImages(prev =>
|
||||
prev.map(img =>
|
||||
(img.loading && img.productIndex === productIndex && img.fileName === file.name)
|
||||
? { ...img, imageUrl: result.imageUrl, loading: false }
|
||||
? {
|
||||
...img,
|
||||
imageUrl: result.imageUrl,
|
||||
loading: false,
|
||||
// Update schema fields if returned from server
|
||||
iid: result.iid || img.iid,
|
||||
width: result.width || img.width,
|
||||
height: result.height || img.height
|
||||
}
|
||||
: img
|
||||
)
|
||||
);
|
||||
|
||||
// Update the product data with the new image URL
|
||||
addImageToProduct(productIndex, result.imageUrl);
|
||||
addImageToProduct(productIndex, result.imageUrl, result);
|
||||
|
||||
toast.success(`Image uploaded for ${data[productIndex].name || `Product #${productIndex + 1}`}`);
|
||||
} catch (error) {
|
||||
@@ -1305,20 +1406,33 @@ export const ImageUploadStep = <T extends string>({
|
||||
// Create a unique ID for this image
|
||||
const imageId = `image-${productIndex}-url-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
||||
|
||||
// Get the next order value for this product
|
||||
const nextOrder = productImages
|
||||
.filter(img => img.productIndex === productIndex)
|
||||
.length;
|
||||
|
||||
// Create the new image object with the URL
|
||||
const newImage: ProductImageSortable = {
|
||||
id: imageId,
|
||||
productIndex,
|
||||
imageUrl: validatedUrl,
|
||||
loading: false, // We're not loading from server, so it's ready immediately
|
||||
fileName: "From URL"
|
||||
fileName: "From URL",
|
||||
// Add schema fields
|
||||
pid: data[productIndex].id || 0,
|
||||
iid: Math.floor(Math.random() * 10000), // Generate a temporary iid
|
||||
type: 0,
|
||||
order: nextOrder,
|
||||
width: 0,
|
||||
height: 0,
|
||||
hidden: 0
|
||||
};
|
||||
|
||||
// Add the image directly to the product images list
|
||||
setProductImages(prev => [...prev, newImage]);
|
||||
|
||||
// Update the product data with the new image URL
|
||||
addImageToProduct(productIndex, validatedUrl);
|
||||
addImageToProduct(productIndex, validatedUrl, newImage);
|
||||
|
||||
// Clear the URL input field on success
|
||||
setUrlInputs(prev => ({ ...prev, [productIndex]: '' }));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -314,7 +314,7 @@ const BASE_IMPORT_FIELDS = [
|
||||
key: "categories",
|
||||
description: "Product categories",
|
||||
fieldType: {
|
||||
type: "select",
|
||||
type: "multi-select",
|
||||
options: [], // Will be populated from API
|
||||
},
|
||||
width: 350,
|
||||
@@ -484,7 +484,7 @@ export function Import() {
|
||||
return {
|
||||
...field,
|
||||
fieldType: {
|
||||
type: "select" as const,
|
||||
type: "multi-select" as const,
|
||||
options: fieldOptions.categories || [],
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user