Add bulk item image upload: accept all sizes, pick best, upload after save
This commit is contained in:
parent
04f65e3495
commit
f8afbb57e9
2 changed files with 115 additions and 27 deletions
|
|
@ -582,6 +582,9 @@ try {
|
|||
// Track item order within each category
|
||||
categoryItemOrder = {};
|
||||
|
||||
// Track item name -> database ID mapping for image uploads
|
||||
itemIdMap = {};
|
||||
|
||||
for (n = 1; n <= arrayLen(items); n++) {
|
||||
item = items[n];
|
||||
if (!isStruct(item)) continue;
|
||||
|
|
@ -673,6 +676,13 @@ try {
|
|||
|
||||
totalItems++;
|
||||
|
||||
// Track mapping for image uploads (use frontend ID if present, else name)
|
||||
frontendId = structKeyExists(item, "id") && isSimpleValue(item.id) ? item.id : "";
|
||||
if (len(frontendId)) {
|
||||
itemIdMap[frontendId] = menuItemID;
|
||||
}
|
||||
itemIdMap[itemName] = menuItemID;
|
||||
|
||||
// Link modifier templates to this item
|
||||
modOrder = 1;
|
||||
for (m = 1; m <= arrayLen(itemModifiers); m++) {
|
||||
|
|
@ -730,7 +740,8 @@ try {
|
|||
"templatesProcessed": arrayLen(modTemplates),
|
||||
"itemsProcessed": totalItems,
|
||||
"linksCreated": totalLinks,
|
||||
"imagesDownloaded": totalImages
|
||||
"imagesDownloaded": totalImages,
|
||||
"itemIdMap": itemIdMap
|
||||
};
|
||||
|
||||
} catch (any e) {
|
||||
|
|
|
|||
|
|
@ -1530,42 +1530,62 @@
|
|||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
// Skip duplicate sizes (_002, _003, _004 suffixes)
|
||||
// Check if this is a base (largest) image vs smaller variant
|
||||
function isBaseSizeImage(filename) {
|
||||
return !/_00[234]\.[^.]+$/i.test(filename);
|
||||
}
|
||||
|
||||
// Group files by Toast ID, then pick best one per ID
|
||||
const filesByToastId = {};
|
||||
const unmatchedFiles = [];
|
||||
|
||||
Array.from(files).forEach(file => {
|
||||
const filename = file.name;
|
||||
|
||||
// Skip smaller size variants - only use base image
|
||||
if (!isBaseSizeImage(filename)) {
|
||||
return;
|
||||
const toastId = extractToastId(file.name);
|
||||
if (toastId) {
|
||||
if (!filesByToastId[toastId]) {
|
||||
filesByToastId[toastId] = [];
|
||||
}
|
||||
filesByToastId[toastId].push(file);
|
||||
} else {
|
||||
unmatchedFiles.push(file);
|
||||
}
|
||||
});
|
||||
|
||||
// Extract Toast item ID from uploaded filename
|
||||
const uploadedToastId = extractToastId(filename);
|
||||
|
||||
// Try to match to an item
|
||||
let matchedItem = null;
|
||||
|
||||
if (uploadedToastId) {
|
||||
// Match by Toast ID in imageUrl
|
||||
matchedItem = items.find(item => {
|
||||
if (!item.imageUrl) return false;
|
||||
const itemToastId = extractToastId(item.imageUrl);
|
||||
return itemToastId === uploadedToastId;
|
||||
});
|
||||
// For each Toast ID, pick the base image (no suffix) or largest file
|
||||
const bestFiles = {};
|
||||
for (const [toastId, fileGroup] of Object.entries(filesByToastId)) {
|
||||
// Prefer base image (no _002/_003/_004 suffix)
|
||||
const baseFile = fileGroup.find(f => isBaseSizeImage(f.name));
|
||||
if (baseFile) {
|
||||
bestFiles[toastId] = baseFile;
|
||||
} else {
|
||||
// Fallback: pick largest file
|
||||
bestFiles[toastId] = fileGroup.reduce((a, b) => a.size > b.size ? a : b);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: match by filename contained in imageUrl
|
||||
if (!matchedItem) {
|
||||
const filenameBase = filename.replace(/\.[^.]+$/, '').toLowerCase();
|
||||
matchedItem = items.find(item => {
|
||||
if (!item.imageUrl) return false;
|
||||
return item.imageUrl.toLowerCase().includes(filenameBase);
|
||||
});
|
||||
// Now match best files to items
|
||||
for (const [toastId, file] of Object.entries(bestFiles)) {
|
||||
const matchedItem = items.find(item => {
|
||||
if (!item.imageUrl) return false;
|
||||
const itemToastId = extractToastId(item.imageUrl);
|
||||
return itemToastId === toastId;
|
||||
});
|
||||
|
||||
if (matchedItem && !config.itemImages[matchedItem.id]) {
|
||||
config.itemImages[matchedItem.id] = file;
|
||||
matchedCount++;
|
||||
matchResults.push({ item: matchedItem.name, file: file.name });
|
||||
}
|
||||
}
|
||||
|
||||
// Try to match unmatched files by filename in imageUrl
|
||||
unmatchedFiles.forEach(file => {
|
||||
const filenameBase = file.name.replace(/\.[^.]+$/, '').toLowerCase();
|
||||
const matchedItem = items.find(item => {
|
||||
if (!item.imageUrl) return false;
|
||||
return item.imageUrl.toLowerCase().includes(filenameBase);
|
||||
});
|
||||
|
||||
if (matchedItem && !config.itemImages[matchedItem.id]) {
|
||||
config.itemImages[matchedItem.id] = file;
|
||||
|
|
@ -2814,6 +2834,63 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Upload item images if any were matched
|
||||
const itemIdMap = summary.itemIdMap || summary.ITEMIDMAP || {};
|
||||
const itemImageEntries = Object.entries(config.itemImages || {});
|
||||
if (itemImageEntries.length > 0) {
|
||||
console.log('Uploading', itemImageEntries.length, 'item images...');
|
||||
saveBtn.innerHTML = '<div class="loading-spinner" style="width:16px;height:16px;border-width:2px;"></div> Uploading images...';
|
||||
|
||||
let uploadedCount = 0;
|
||||
let failedCount = 0;
|
||||
|
||||
for (const [frontendId, file] of itemImageEntries) {
|
||||
// Look up database ID from the map (try frontend ID, then item name)
|
||||
let dbItemId = itemIdMap[frontendId];
|
||||
if (!dbItemId) {
|
||||
// Try to find by item name
|
||||
const item = config.extractedData.items.find(i => i.id === frontendId);
|
||||
if (item && item.name) {
|
||||
dbItemId = itemIdMap[item.name];
|
||||
}
|
||||
}
|
||||
|
||||
if (!dbItemId) {
|
||||
console.warn('No database ID found for frontend ID:', frontendId);
|
||||
failedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const formData = new FormData();
|
||||
formData.append('ItemID', dbItemId);
|
||||
formData.append('photo', file);
|
||||
|
||||
const imgResp = await fetch(`${config.apiBaseUrl}/menu/uploadItemPhoto.cfm`, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
const imgResult = await imgResp.json();
|
||||
if (imgResult.OK) {
|
||||
uploadedCount++;
|
||||
} else {
|
||||
console.error('Item image upload failed:', imgResult.MESSAGE);
|
||||
failedCount++;
|
||||
}
|
||||
} catch (imgErr) {
|
||||
console.error('Item image upload error:', imgErr);
|
||||
failedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Item images: ${uploadedCount} uploaded, ${failedCount} failed`);
|
||||
if (failedCount > 0) {
|
||||
showToast(`${uploadedCount} images uploaded, ${failedCount} failed. You can add images later in Menu Builder.`, 'warning');
|
||||
} else if (uploadedCount > 0) {
|
||||
showToast(`${uploadedCount} item images uploaded!`, 'success');
|
||||
}
|
||||
}
|
||||
|
||||
// Redirect back after a moment
|
||||
setTimeout(() => {
|
||||
window.location.href = config.menuId ? 'menu-builder.html' : 'index.html#menu';
|
||||
|
|
|
|||
Reference in a new issue