lokesh341 commited on
Commit
ea48762
·
verified ·
1 Parent(s): b9e5f4c

Update static/script.js

Browse files
Files changed (1) hide show
  1. static/script.js +151 -35
static/script.js CHANGED
@@ -3,8 +3,10 @@ let conversation = [
3
  ];
4
  let selectedItems = [];
5
  let selectionBoxVisible = false;
 
 
6
 
7
- function addMessage(role, message) {
8
  const chatMessages = document.getElementById('chatMessages');
9
  if (!chatMessages) {
10
  console.error('Chat messages container not found!');
@@ -15,7 +17,7 @@ function addMessage(role, message) {
15
  messageDiv.textContent = message;
16
  chatMessages.appendChild(messageDiv);
17
  chatMessages.scrollTop = chatMessages.scrollHeight;
18
- console.log(`Added ${role} message: ${message}`);
19
  }
20
 
21
  function showToast(message) {
@@ -34,7 +36,7 @@ function sendMessage() {
34
  }
35
  const message = userInput.value.trim();
36
  if (message) {
37
- addMessage('user', message);
38
  conversation.push({ role: 'user', message: message });
39
  selectionBoxVisible = true;
40
  handleResponse(message);
@@ -93,6 +95,8 @@ function updateSelectionBox() {
93
  const clearAllButton = document.createElement('button');
94
  clearAllButton.textContent = 'Clear All';
95
  clearAllButton.className = 'remove-button';
 
 
96
  clearAllButton.onclick = () => {
97
  selectedItems = [];
98
  addMessage('bot', 'Cleared all items.');
@@ -104,8 +108,10 @@ function updateSelectionBox() {
104
  const vegButton = document.createElement('button');
105
  vegButton.textContent = 'Veg';
106
  vegButton.className = 'option-button green';
 
 
107
  vegButton.onclick = () => {
108
- addMessage('user', 'Vegetarian');
109
  conversation.push({ role: 'user', message: 'Vegetarian' });
110
  handleResponse('vegetarian');
111
  showToast('Selected Vegetarian');
@@ -115,8 +121,10 @@ function updateSelectionBox() {
115
  const nonVegButton = document.createElement('button');
116
  nonVegButton.textContent = 'Non-Veg';
117
  nonVegButton.className = 'option-button red';
 
 
118
  nonVegButton.onclick = () => {
119
- addMessage('user', 'Non-Vegetarian');
120
  conversation.push({ role: 'user', message: 'Non-Vegetarian' });
121
  handleResponse('non-vegetarian');
122
  showToast('Selected Non-Vegetarian');
@@ -126,8 +134,10 @@ function updateSelectionBox() {
126
  const bothButton = document.createElement('button');
127
  bothButton.textContent = 'Both';
128
  bothButton.className = 'option-button gray';
 
 
129
  bothButton.onclick = () => {
130
- addMessage('user', 'Both');
131
  conversation.push({ role: 'user', message: 'Both' });
132
  handleResponse('both');
133
  showToast('Selected Both');
@@ -144,7 +154,7 @@ function updateSelectionBox() {
144
  itemContainer.dataset.hidden = item.source === 'Sector_Detail__c' ? 'true' : 'false';
145
 
146
  const img = document.createElement('img');
147
- img.src = item.image_url || 'https://via.placeholder.com/30';
148
  img.alt = item.name;
149
  img.className = 'selected-item-image';
150
  itemContainer.appendChild(img);
@@ -156,10 +166,41 @@ function updateSelectionBox() {
156
  itemSpan.textContent = `${item.name} (Qty: ${item.quantity || 1})`;
157
  contentDiv.appendChild(itemSpan);
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  if (item.source === 'Sector_Detail__c') {
160
  const showButton = document.createElement('button');
161
  showButton.textContent = 'Show';
162
  showButton.className = 'show-button';
 
 
163
  showButton.onclick = () => toggleDescription(itemContainer, item.description, item.name);
164
  contentDiv.appendChild(showButton);
165
  }
@@ -169,6 +210,8 @@ function updateSelectionBox() {
169
  const removeButton = document.createElement('button');
170
  removeButton.textContent = 'X';
171
  removeButton.className = 'remove-button';
 
 
172
  removeButton.onclick = () => {
173
  selectedItems.splice(index, 1);
174
  addMessage('bot', `Removed "${item.name}".`);
@@ -184,14 +227,17 @@ function updateSelectionBox() {
184
  textInput.type = 'text';
185
  textInput.placeholder = 'Add item...';
186
  textInput.className = 'manual-input';
187
- textInput.addEventListener('keypress', debounce((e) => {
 
 
188
  if (e.key === 'Enter' && textInput.value.trim()) {
189
  const itemName = textInput.value.trim();
190
  fetchSectorItemDetails(itemName);
191
  textInput.value = '';
192
  showToast(`Searching for "${itemName}"...`);
193
  }
194
- }, 300));
 
195
  selectionBox.appendChild(textInput);
196
 
197
  if (selectedItems.length > 0) {
@@ -201,11 +247,15 @@ function updateSelectionBox() {
201
  quantityInput.value = '1';
202
  quantityInput.placeholder = 'Qty';
203
  quantityInput.className = 'quantity-input';
 
 
204
  selectionBox.appendChild(quantityInput);
205
 
206
  const submitButton = document.createElement('button');
207
  submitButton.textContent = 'Submit';
208
  submitButton.className = 'submit-button';
 
 
209
  submitButton.onclick = () => promptAndSubmit(quantityInput.value);
210
  selectionBox.appendChild(submitButton);
211
 
@@ -213,15 +263,38 @@ function updateSelectionBox() {
213
  orderNameInput.type = 'text';
214
  orderNameInput.placeholder = 'Order Name';
215
  orderNameInput.className = 'order-name-input';
 
 
216
  selectionBox.appendChild(orderNameInput);
217
  }
218
 
219
  chatMessages.appendChild(selectionBox);
220
  chatMessages.scrollTop = chatMessages.scrollHeight;
221
- console.log('Updated selection box:', selectedItems.map(item => ({ name: item.name, category: item.category })));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
 
224
  function fetchMenuItems(dietaryPreference = '', searchTerm = '') {
 
 
 
 
 
 
 
225
  const payload = {};
226
  if (dietaryPreference) payload.dietary_preference = dietaryPreference;
227
  if (searchTerm) payload.search_term = searchTerm;
@@ -231,46 +304,64 @@ function fetchMenuItems(dietaryPreference = '', searchTerm = '') {
231
  spinner.className = 'spinner';
232
  chatMessages.appendChild(spinner);
233
 
234
- fetch('/get_menu_items', {
235
  method: 'POST',
236
  headers: { 'Content-Type': 'application/json' },
237
  body: JSON.stringify(payload)
238
  })
239
- .then(response => response.json())
240
  .then(data => {
241
  spinner.remove();
242
  if (data.error) {
243
  addMessage('bot', `Error: ${data.error}. Try again!`);
244
  showToast(`Error: ${data.error}`);
245
  } else if (data.menu_items.length > 0) {
 
246
  addMessage('bot', `--- Found ${data.menu_items.length} item${data.menu_items.length > 1 ? 's' : ''} ---`);
247
  displayItemsList(data.menu_items);
248
  } else {
249
  addMessage('bot', `No matches for "${searchTerm || dietaryPreference}". Try "paneer" or "chicken curry"!`);
250
  showToast('No items found!');
251
  }
252
- console.log(`Fetched items for ${searchTerm || dietaryPreference}:`, data.menu_items);
253
  })
254
  .catch(error => {
255
  spinner.remove();
256
- addMessage('bot', `Connection issue: ${error.message}. Retrying...`);
257
  showToast('Connection issue!');
258
- setTimeout(() => fetchMenuItems(dietaryPreference, searchTerm), 2000);
259
  });
260
  }
261
 
 
 
 
 
 
 
 
 
 
 
 
 
 
262
  function fetchSectorItemDetails(itemName) {
263
- fetch('/get_sector_item_details', {
 
 
 
 
 
 
 
264
  method: 'POST',
265
  headers: { 'Content-Type': 'application/json' },
266
  body: JSON.stringify({ item_name: itemName })
267
  })
268
- .then(response => response.json())
269
  .then(data => {
270
  if (data.error) {
271
  addMessage('bot', `No "${itemName}" found. Try another!`);
272
  showToast(`No "${itemName}" found`);
273
  } else {
 
274
  const details = data.item_details;
275
  if (selectedItems.some(item => item.name === details.name)) {
276
  addMessage('bot', `"${details.name}" already selected!`);
@@ -284,12 +375,29 @@ function fetchSectorItemDetails(itemName) {
284
  }
285
  })
286
  .catch(error => {
287
- addMessage('bot', `Error for "${itemName}". Retrying...`);
288
  showToast(`Error fetching "${itemName}"`);
289
- setTimeout(() => fetchSectorItemDetails(itemName), 2000);
290
  });
291
  }
292
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  function toggleDescription(itemContainer, description, itemName) {
294
  let descElement = itemContainer.querySelector('.item-description');
295
  if (!descElement) {
@@ -298,12 +406,10 @@ function toggleDescription(itemContainer, description, itemName) {
298
  descElement.textContent = description;
299
  itemContainer.querySelector('.selected-item-content').appendChild(descElement);
300
  itemContainer.dataset.hidden = 'false';
301
- console.log(`Showed description for ${itemName}`);
302
  showToast(`Showing details for ${itemName}`);
303
  } else {
304
  descElement.remove();
305
  itemContainer.dataset.hidden = 'true';
306
- console.log(`Hid description for ${itemName}`);
307
  showToast(`Hid details for ${itemName}`);
308
  }
309
  }
@@ -311,7 +417,6 @@ function toggleDescription(itemContainer, description, itemName) {
311
  function displayItemsList(items) {
312
  const chatMessages = document.getElementById('chatMessages');
313
  if (!chatMessages) {
314
- console.error('Chat messages container not found!');
315
  addMessage('bot', 'Display issue. Try again?');
316
  showToast('Display issue!');
317
  return;
@@ -320,14 +425,15 @@ function displayItemsList(items) {
320
  const itemsGrid = document.createElement('div');
321
  itemsGrid.className = 'items-grid';
322
 
323
- items.forEach(item => {
324
- const itemDiv = document.createElement('div');
325
  itemDiv.className = 'item-card';
326
 
327
  const img = document.createElement('img');
328
- img.src = item.image_url || 'https://via.placeholder.com/120';
329
  img.alt = item.name;
330
  img.className = 'item-image';
 
331
  itemDiv.appendChild(img);
332
 
333
  const contentDiv = document.createElement('div');
@@ -365,6 +471,8 @@ function displayItemsList(items) {
365
  const addButton = document.createElement('button');
366
  addButton.textContent = 'Add';
367
  addButton.className = 'add-button';
 
 
368
  addButton.onclick = () => {
369
  const selectedItem = {
370
  name: item.name,
@@ -414,8 +522,10 @@ function displayOptions(options) {
414
  const button = document.createElement('button');
415
  button.textContent = opt.text;
416
  button.className = `option-button ${opt.class}`;
 
 
417
  button.onclick = () => {
418
- addMessage('user', opt.text);
419
  conversation.push({ role: 'user', message: opt.text });
420
  selectionBoxVisible = true;
421
  handleResponse(opt.text);
@@ -428,6 +538,8 @@ function displayOptions(options) {
428
  const backButton = document.createElement('button');
429
  backButton.textContent = 'Back';
430
  backButton.className = 'option-button';
 
 
431
  backButton.onclick = () => resetConversation();
432
  optionsDiv.appendChild(backButton);
433
 
@@ -437,8 +549,14 @@ function displayOptions(options) {
437
  function promptAndSubmit(quantity) {
438
  const orderNameInput = document.querySelector('.order-name-input');
439
  const customOrderName = orderNameInput ? orderNameInput.value.trim() : '';
440
- if (confirm(`Submit ${selectedItems.length} items (Qty: ${quantity})?`)) {
441
- submitToSalesforce(customOrderName, quantity);
 
 
 
 
 
 
442
  showToast('Submitting order...');
443
  } else {
444
  addMessage('bot', 'Cancelled. Add more items?');
@@ -458,30 +576,28 @@ function submitToSalesforce(customOrderName, quantity) {
458
  category: item.category || 'Not specified',
459
  description: item.description || 'No description available',
460
  image_url: item.image_url || '',
461
- quantity: parseInt(quantity) || 1
462
  }));
463
 
464
- fetch('/submit_items', {
465
  method: 'POST',
466
  headers: { 'Content-Type': 'application/json' },
467
  body: JSON.stringify({ items: itemsToSubmit, custom_order_name: customOrderName })
468
  })
469
- .then(response => response.json())
470
  .then(data => {
471
  if (data.error) {
472
  addMessage('bot', `Submission failed: ${data.error}. Try again?`);
473
  showToast(`Submission failed: ${data.error}`);
474
  } else {
475
- addMessage('bot', `Submitted ${data.ingredient_name}! What's next?`);
476
  selectedItems = [];
477
  updateSelectionBox();
478
- showToast('Order submitted!');
479
  }
480
  })
481
  .catch(error => {
482
- addMessage('bot', `Submission error: ${error.message}. Retrying...`);
483
  showToast('Submission error!');
484
- setTimeout(() => submitToSalesforce(customOrderName, quantity), 2000);
485
  });
486
  }
487
 
 
3
  ];
4
  let selectedItems = [];
5
  let selectionBoxVisible = false;
6
+ const searchCache = new Map();
7
+ const MAX_RETRIES = 3;
8
 
9
+ function addMessage(role, message, isDebug = false) {
10
  const chatMessages = document.getElementById('chatMessages');
11
  if (!chatMessages) {
12
  console.error('Chat messages container not found!');
 
17
  messageDiv.textContent = message;
18
  chatMessages.appendChild(messageDiv);
19
  chatMessages.scrollTop = chatMessages.scrollHeight;
20
+ if (isDebug) console.log(`[${role.toUpperCase()}] ${message}`);
21
  }
22
 
23
  function showToast(message) {
 
36
  }
37
  const message = userInput.value.trim();
38
  if (message) {
39
+ addMessage('user', message, true);
40
  conversation.push({ role: 'user', message: message });
41
  selectionBoxVisible = true;
42
  handleResponse(message);
 
95
  const clearAllButton = document.createElement('button');
96
  clearAllButton.textContent = 'Clear All';
97
  clearAllButton.className = 'remove-button';
98
+ clearAllButton.setAttribute('aria-label', 'Clear all selected items');
99
+ clearAllButton.tabIndex = 0;
100
  clearAllButton.onclick = () => {
101
  selectedItems = [];
102
  addMessage('bot', 'Cleared all items.');
 
108
  const vegButton = document.createElement('button');
109
  vegButton.textContent = 'Veg';
110
  vegButton.className = 'option-button green';
111
+ vegButton.setAttribute('aria-label', 'Select vegetarian dishes');
112
+ vegButton.tabIndex = 0;
113
  vegButton.onclick = () => {
114
+ addMessage('user', 'Vegetarian', true);
115
  conversation.push({ role: 'user', message: 'Vegetarian' });
116
  handleResponse('vegetarian');
117
  showToast('Selected Vegetarian');
 
121
  const nonVegButton = document.createElement('button');
122
  nonVegButton.textContent = 'Non-Veg';
123
  nonVegButton.className = 'option-button red';
124
+ nonVegButton.setAttribute('aria-label', 'Select non-vegetarian dishes');
125
+ nonVegButton.tabIndex = 0;
126
  nonVegButton.onclick = () => {
127
+ addMessage('user', 'Non-Vegetarian', true);
128
  conversation.push({ role: 'user', message: 'Non-Vegetarian' });
129
  handleResponse('non-vegetarian');
130
  showToast('Selected Non-Vegetarian');
 
134
  const bothButton = document.createElement('button');
135
  bothButton.textContent = 'Both';
136
  bothButton.className = 'option-button gray';
137
+ bothButton.setAttribute('aria-label', 'Select both vegetarian and non-vegetarian dishes');
138
+ bothButton.tabIndex = 0;
139
  bothButton.onclick = () => {
140
+ addMessage('user', 'Both', true);
141
  conversation.push({ role: 'user', message: 'Both' });
142
  handleResponse('both');
143
  showToast('Selected Both');
 
154
  itemContainer.dataset.hidden = item.source === 'Sector_Detail__c' ? 'true' : 'false';
155
 
156
  const img = document.createElement('img');
157
+ img.src = item.image_url || 'https://via.placeholder.com/30?text=Item';
158
  img.alt = item.name;
159
  img.className = 'selected-item-image';
160
  itemContainer.appendChild(img);
 
166
  itemSpan.textContent = `${item.name} (Qty: ${item.quantity || 1})`;
167
  contentDiv.appendChild(itemSpan);
168
 
169
+ const quantityDiv = document.createElement('div');
170
+ quantityDiv.className = 'quantity-controls';
171
+ const decButton = document.createElement('button');
172
+ decButton.textContent = '-';
173
+ decButton.className = 'quantity-button';
174
+ decButton.setAttribute('aria-label', `Decrease quantity of ${item.name}`);
175
+ decButton.tabIndex = 0;
176
+ decButton.onclick = () => {
177
+ if (item.quantity > 1) {
178
+ item.quantity -= 1;
179
+ updateSelectionBox();
180
+ showToast(`Decreased quantity of ${item.name}`);
181
+ }
182
+ };
183
+ quantityDiv.appendChild(decButton);
184
+
185
+ const incButton = document.createElement('button');
186
+ incButton.textContent = '+';
187
+ incButton.className = 'quantity-button';
188
+ incButton.setAttribute('aria-label', `Increase quantity of ${item.name}`);
189
+ incButton.tabIndex = 0;
190
+ incButton.onclick = () => {
191
+ item.quantity += 1;
192
+ updateSelectionBox();
193
+ showToast(`Increased quantity of ${item.name}`);
194
+ };
195
+ quantityDiv.appendChild(incButton);
196
+ contentDiv.appendChild(quantityDiv);
197
+
198
  if (item.source === 'Sector_Detail__c') {
199
  const showButton = document.createElement('button');
200
  showButton.textContent = 'Show';
201
  showButton.className = 'show-button';
202
+ showButton.setAttribute('aria-label', `Show details for ${item.name}`);
203
+ showButton.tabIndex = 0;
204
  showButton.onclick = () => toggleDescription(itemContainer, item.description, item.name);
205
  contentDiv.appendChild(showButton);
206
  }
 
210
  const removeButton = document.createElement('button');
211
  removeButton.textContent = 'X';
212
  removeButton.className = 'remove-button';
213
+ removeButton.setAttribute('aria-label', `Remove ${item.name} from selection`);
214
+ removeButton.tabIndex = 0;
215
  removeButton.onclick = () => {
216
  selectedItems.splice(index, 1);
217
  addMessage('bot', `Removed "${item.name}".`);
 
227
  textInput.type = 'text';
228
  textInput.placeholder = 'Add item...';
229
  textInput.className = 'manual-input';
230
+ textInput.setAttribute('aria-label', 'Add item manually');
231
+ textInput.tabIndex = 0;
232
+ const debouncedFetch = debounce((e) => {
233
  if (e.key === 'Enter' && textInput.value.trim()) {
234
  const itemName = textInput.value.trim();
235
  fetchSectorItemDetails(itemName);
236
  textInput.value = '';
237
  showToast(`Searching for "${itemName}"...`);
238
  }
239
+ }, 300);
240
+ textInput.addEventListener('keypress', debouncedFetch);
241
  selectionBox.appendChild(textInput);
242
 
243
  if (selectedItems.length > 0) {
 
247
  quantityInput.value = '1';
248
  quantityInput.placeholder = 'Qty';
249
  quantityInput.className = 'quantity-input';
250
+ quantityInput.setAttribute('aria-label', 'Set default quantity for all items');
251
+ quantityInput.tabIndex = 0;
252
  selectionBox.appendChild(quantityInput);
253
 
254
  const submitButton = document.createElement('button');
255
  submitButton.textContent = 'Submit';
256
  submitButton.className = 'submit-button';
257
+ submitButton.setAttribute('aria-label', 'Submit selected items');
258
+ submitButton.tabIndex = 0;
259
  submitButton.onclick = () => promptAndSubmit(quantityInput.value);
260
  selectionBox.appendChild(submitButton);
261
 
 
263
  orderNameInput.type = 'text';
264
  orderNameInput.placeholder = 'Order Name';
265
  orderNameInput.className = 'order-name-input';
266
+ orderNameInput.setAttribute('aria-label', 'Enter order name');
267
+ orderNameInput.tabIndex = 0;
268
  selectionBox.appendChild(orderNameInput);
269
  }
270
 
271
  chatMessages.appendChild(selectionBox);
272
  chatMessages.scrollTop = chatMessages.scrollHeight;
273
+ }
274
+
275
+ async function fetchWithRetry(url, options, retries = MAX_RETRIES) {
276
+ for (let i = 0; i < retries; i++) {
277
+ try {
278
+ const response = await fetch(url, options);
279
+ if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
280
+ return await response.json();
281
+ } catch (error) {
282
+ if (i === retries - 1) {
283
+ throw error;
284
+ }
285
+ await new Promise(resolve => setTimeout(resolve, 2000));
286
+ }
287
+ }
288
  }
289
 
290
  function fetchMenuItems(dietaryPreference = '', searchTerm = '') {
291
+ const cacheKey = `${dietaryPreference}:${searchTerm}`;
292
+ if (searchCache.has(cacheKey)) {
293
+ const data = searchCache.get(cacheKey);
294
+ displayCachedMenuItems(data, searchTerm, dietaryPreference);
295
+ return;
296
+ }
297
+
298
  const payload = {};
299
  if (dietaryPreference) payload.dietary_preference = dietaryPreference;
300
  if (searchTerm) payload.search_term = searchTerm;
 
304
  spinner.className = 'spinner';
305
  chatMessages.appendChild(spinner);
306
 
307
+ fetchWithRetry('/get_menu_items', {
308
  method: 'POST',
309
  headers: { 'Content-Type': 'application/json' },
310
  body: JSON.stringify(payload)
311
  })
 
312
  .then(data => {
313
  spinner.remove();
314
  if (data.error) {
315
  addMessage('bot', `Error: ${data.error}. Try again!`);
316
  showToast(`Error: ${data.error}`);
317
  } else if (data.menu_items.length > 0) {
318
+ searchCache.set(cacheKey, data);
319
  addMessage('bot', `--- Found ${data.menu_items.length} item${data.menu_items.length > 1 ? 's' : ''} ---`);
320
  displayItemsList(data.menu_items);
321
  } else {
322
  addMessage('bot', `No matches for "${searchTerm || dietaryPreference}". Try "paneer" or "chicken curry"!`);
323
  showToast('No items found!');
324
  }
 
325
  })
326
  .catch(error => {
327
  spinner.remove();
328
+ addMessage('bot', `Connection issue: ${error.message}. Please try again later.`);
329
  showToast('Connection issue!');
 
330
  });
331
  }
332
 
333
+ function displayCachedMenuItems(data, searchTerm, dietaryPreference) {
334
+ if (data.error) {
335
+ addMessage('bot', `Error: ${data.error}. Try again!`);
336
+ showToast(`Error: ${data.error}`);
337
+ } else if (data.menu_items.length > 0) {
338
+ addMessage('bot', `--- Found ${data.menu_items.length} item${data.menu_items.length > 1 ? 's' : ''} ---`);
339
+ displayItemsList(data.menu_items);
340
+ } else {
341
+ addMessage('bot', `No matches for "${searchTerm || dietaryPreference}". Try "paneer" or "chicken curry"!`);
342
+ showToast('No items found!');
343
+ }
344
+ }
345
+
346
  function fetchSectorItemDetails(itemName) {
347
+ const cacheKey = `sector:${itemName}`;
348
+ if (searchCache.has(cacheKey)) {
349
+ const data = searchCache.get(cacheKey);
350
+ displayCachedSectorItems(data, itemName);
351
+ return;
352
+ }
353
+
354
+ fetchWithRetry('/get_sector_item_details', {
355
  method: 'POST',
356
  headers: { 'Content-Type': 'application/json' },
357
  body: JSON.stringify({ item_name: itemName })
358
  })
 
359
  .then(data => {
360
  if (data.error) {
361
  addMessage('bot', `No "${itemName}" found. Try another!`);
362
  showToast(`No "${itemName}" found`);
363
  } else {
364
+ searchCache.set(cacheKey, data);
365
  const details = data.item_details;
366
  if (selectedItems.some(item => item.name === details.name)) {
367
  addMessage('bot', `"${details.name}" already selected!`);
 
375
  }
376
  })
377
  .catch(error => {
378
+ addMessage('bot', `Error for "${itemName}". Please try again later.`);
379
  showToast(`Error fetching "${itemName}"`);
 
380
  });
381
  }
382
 
383
+ function displayCachedSectorItems(data, itemName) {
384
+ if (data.error) {
385
+ addMessage('bot', `No "${itemName}" found. Try another!`);
386
+ showToast(`No "${itemName}" found`);
387
+ } else {
388
+ const details = data.item_details;
389
+ if (selectedItems.some(item => item.name === details.name)) {
390
+ addMessage('bot', `"${details.name}" already selected!`);
391
+ showToast(`"${details.name}" already selected`);
392
+ } else {
393
+ selectedItems.push({ ...details, quantity: 1 });
394
+ addMessage('bot', `Added "${details.name}"!`);
395
+ showToast(`Added "${details.name}"`);
396
+ updateSelectionBox();
397
+ }
398
+ }
399
+ }
400
+
401
  function toggleDescription(itemContainer, description, itemName) {
402
  let descElement = itemContainer.querySelector('.item-description');
403
  if (!descElement) {
 
406
  descElement.textContent = description;
407
  itemContainer.querySelector('.selected-item-content').appendChild(descElement);
408
  itemContainer.dataset.hidden = 'false';
 
409
  showToast(`Showing details for ${itemName}`);
410
  } else {
411
  descElement.remove();
412
  itemContainer.dataset.hidden = 'true';
 
413
  showToast(`Hid details for ${itemName}`);
414
  }
415
  }
 
417
  function displayItemsList(items) {
418
  const chatMessages = document.getElementById('chatMessages');
419
  if (!chatMessages) {
 
420
  addMessage('bot', 'Display issue. Try again?');
421
  showToast('Display issue!');
422
  return;
 
425
  const itemsGrid = document.createElement('div');
426
  itemsGrid.className = 'items-grid';
427
 
428
+ items.forEach((item, index) => {
429
+ const itemDiv = document.createElement('article');
430
  itemDiv.className = 'item-card';
431
 
432
  const img = document.createElement('img');
433
+ img.src = item.image_url || 'https://via.placeholder.com/120?text=Item';
434
  img.alt = item.name;
435
  img.className = 'item-image';
436
+ if (index < 3) img.loading = 'eager'; // Preload first 3 images
437
  itemDiv.appendChild(img);
438
 
439
  const contentDiv = document.createElement('div');
 
471
  const addButton = document.createElement('button');
472
  addButton.textContent = 'Add';
473
  addButton.className = 'add-button';
474
+ addButton.setAttribute('aria-label', `Add ${item.name} to selection`);
475
+ addButton.tabIndex = 0;
476
  addButton.onclick = () => {
477
  const selectedItem = {
478
  name: item.name,
 
522
  const button = document.createElement('button');
523
  button.textContent = opt.text;
524
  button.className = `option-button ${opt.class}`;
525
+ button.setAttribute('aria-label', `Select ${opt.text} dishes`);
526
+ button.tabIndex = 0;
527
  button.onclick = () => {
528
+ addMessage('user', opt.text, true);
529
  conversation.push({ role: 'user', message: opt.text });
530
  selectionBoxVisible = true;
531
  handleResponse(opt.text);
 
538
  const backButton = document.createElement('button');
539
  backButton.textContent = 'Back';
540
  backButton.className = 'option-button';
541
+ backButton.setAttribute('aria-label', 'Reset conversation');
542
+ backButton.tabIndex = 0;
543
  backButton.onclick = () => resetConversation();
544
  optionsDiv.appendChild(backButton);
545
 
 
549
  function promptAndSubmit(quantity) {
550
  const orderNameInput = document.querySelector('.order-name-input');
551
  const customOrderName = orderNameInput ? orderNameInput.value.trim() : '';
552
+ const qty = parseInt(quantity);
553
+ if (isNaN(qty) || qty <= 0) {
554
+ addMessage('bot', 'Please enter a valid quantity (1 or more)!');
555
+ showToast('Invalid quantity!');
556
+ return;
557
+ }
558
+ if (confirm(`Submit ${selectedItems.length} items with default quantity ${qty}?`)) {
559
+ submitToSalesforce(customOrderName, qty);
560
  showToast('Submitting order...');
561
  } else {
562
  addMessage('bot', 'Cancelled. Add more items?');
 
576
  category: item.category || 'Not specified',
577
  description: item.description || 'No description available',
578
  image_url: item.image_url || '',
579
+ quantity: item.quantity || parseInt(quantity) || 1
580
  }));
581
 
582
+ fetchWithRetry('/submit_items', {
583
  method: 'POST',
584
  headers: { 'Content-Type': 'application/json' },
585
  body: JSON.stringify({ items: itemsToSubmit, custom_order_name: customOrderName })
586
  })
 
587
  .then(data => {
588
  if (data.error) {
589
  addMessage('bot', `Submission failed: ${data.error}. Try again?`);
590
  showToast(`Submission failed: ${data.error}`);
591
  } else {
592
+ addMessage('bot', `Order "${customOrderName || 'Order'}" with ${itemsToSubmit.length} items submitted! What's next?`);
593
  selectedItems = [];
594
  updateSelectionBox();
595
+ showToast('Order submitted successfully!');
596
  }
597
  })
598
  .catch(error => {
599
+ addMessage('bot', `Submission error: ${error.message}. Please try again later.`);
600
  showToast('Submission error!');
 
601
  });
602
  }
603