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
|
// Track item order within each category
|
||||||
categoryItemOrder = {};
|
categoryItemOrder = {};
|
||||||
|
|
||||||
|
// Track item name -> database ID mapping for image uploads
|
||||||
|
itemIdMap = {};
|
||||||
|
|
||||||
for (n = 1; n <= arrayLen(items); n++) {
|
for (n = 1; n <= arrayLen(items); n++) {
|
||||||
item = items[n];
|
item = items[n];
|
||||||
if (!isStruct(item)) continue;
|
if (!isStruct(item)) continue;
|
||||||
|
|
@ -673,6 +676,13 @@ try {
|
||||||
|
|
||||||
totalItems++;
|
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
|
// Link modifier templates to this item
|
||||||
modOrder = 1;
|
modOrder = 1;
|
||||||
for (m = 1; m <= arrayLen(itemModifiers); m++) {
|
for (m = 1; m <= arrayLen(itemModifiers); m++) {
|
||||||
|
|
@ -730,7 +740,8 @@ try {
|
||||||
"templatesProcessed": arrayLen(modTemplates),
|
"templatesProcessed": arrayLen(modTemplates),
|
||||||
"itemsProcessed": totalItems,
|
"itemsProcessed": totalItems,
|
||||||
"linksCreated": totalLinks,
|
"linksCreated": totalLinks,
|
||||||
"imagesDownloaded": totalImages
|
"imagesDownloaded": totalImages,
|
||||||
|
"itemIdMap": itemIdMap
|
||||||
};
|
};
|
||||||
|
|
||||||
} catch (any e) {
|
} catch (any e) {
|
||||||
|
|
|
||||||
|
|
@ -1530,42 +1530,62 @@
|
||||||
return match ? match[1] : null;
|
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) {
|
function isBaseSizeImage(filename) {
|
||||||
return !/_00[234]\.[^.]+$/i.test(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 => {
|
Array.from(files).forEach(file => {
|
||||||
const filename = file.name;
|
const toastId = extractToastId(file.name);
|
||||||
|
if (toastId) {
|
||||||
// Skip smaller size variants - only use base image
|
if (!filesByToastId[toastId]) {
|
||||||
if (!isBaseSizeImage(filename)) {
|
filesByToastId[toastId] = [];
|
||||||
return;
|
}
|
||||||
|
filesByToastId[toastId].push(file);
|
||||||
|
} else {
|
||||||
|
unmatchedFiles.push(file);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Extract Toast item ID from uploaded filename
|
// For each Toast ID, pick the base image (no suffix) or largest file
|
||||||
const uploadedToastId = extractToastId(filename);
|
const bestFiles = {};
|
||||||
|
for (const [toastId, fileGroup] of Object.entries(filesByToastId)) {
|
||||||
// Try to match to an item
|
// Prefer base image (no _002/_003/_004 suffix)
|
||||||
let matchedItem = null;
|
const baseFile = fileGroup.find(f => isBaseSizeImage(f.name));
|
||||||
|
if (baseFile) {
|
||||||
if (uploadedToastId) {
|
bestFiles[toastId] = baseFile;
|
||||||
// Match by Toast ID in imageUrl
|
} else {
|
||||||
matchedItem = items.find(item => {
|
// Fallback: pick largest file
|
||||||
if (!item.imageUrl) return false;
|
bestFiles[toastId] = fileGroup.reduce((a, b) => a.size > b.size ? a : b);
|
||||||
const itemToastId = extractToastId(item.imageUrl);
|
|
||||||
return itemToastId === uploadedToastId;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback: match by filename contained in imageUrl
|
// Now match best files to items
|
||||||
if (!matchedItem) {
|
for (const [toastId, file] of Object.entries(bestFiles)) {
|
||||||
const filenameBase = filename.replace(/\.[^.]+$/, '').toLowerCase();
|
const matchedItem = items.find(item => {
|
||||||
matchedItem = items.find(item => {
|
if (!item.imageUrl) return false;
|
||||||
if (!item.imageUrl) return false;
|
const itemToastId = extractToastId(item.imageUrl);
|
||||||
return item.imageUrl.toLowerCase().includes(filenameBase);
|
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]) {
|
if (matchedItem && !config.itemImages[matchedItem.id]) {
|
||||||
config.itemImages[matchedItem.id] = file;
|
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
|
// Redirect back after a moment
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.location.href = config.menuId ? 'menu-builder.html' : 'index.html#menu';
|
window.location.href = config.menuId ? 'menu-builder.html' : 'index.html#menu';
|
||||||
|
|
|
||||||
Reference in a new issue