lokesh341 commited on
Commit
6ec77cb
·
verified ·
1 Parent(s): 55bb9ef

Update templates/menu.html

Browse files
Files changed (1) hide show
  1. templates/menu.html +352 -288
templates/menu.html CHANGED
@@ -15,6 +15,7 @@
15
  {% endfor %}
16
  {% endfor %}
17
  <style>
 
18
  body {
19
  font-family: Arial, sans-serif;
20
  background-color: #fdf4e3;
@@ -41,7 +42,6 @@
41
  }
42
  .menu-card.visible {
43
  opacity: 1;
44
- /* NEW: Staggered animation for smoother loading */
45
  animation: fadeIn 0.3s ease-in-out;
46
  }
47
  .menu-card.highlighted {
@@ -550,7 +550,6 @@
550
  font-size: 1.2rem;
551
  color: #333;
552
  }
553
- /* NEW: Toast Notification Styling */
554
  .toast-container {
555
  position: fixed;
556
  top: 20px;
@@ -569,7 +568,6 @@
569
  .toast.show {
570
  opacity: 1;
571
  }
572
- /* NEW: Search Suggestions */
573
  .search-suggestions {
574
  position: absolute;
575
  top: 100%;
@@ -680,7 +678,6 @@
680
  height: 40px;
681
  text-align: center;
682
  }
683
- /* NEW: Streamline modal-footer btn-primary */
684
  .modal-footer .btn-primary {
685
  background-color: #0FAA39;
686
  border-color: #0FAA39;
@@ -862,7 +859,6 @@
862
  font-size: 12px;
863
  margin-left: 8px;
864
  }
865
- /* Mobile-Specific Styles */
866
  @media (max-width: 576px) {
867
  .fixed-top-bar {
868
  height: 60px;
@@ -1016,7 +1012,6 @@
1016
  font-size: 10px;
1017
  margin-left: 5px;
1018
  }
1019
- /* Mobile-Specific Addon Styles */
1020
  .addon-section {
1021
  margin-bottom: 10px;
1022
  }
@@ -1045,7 +1040,6 @@
1045
  margin-left: 6px;
1046
  font-size: 0.8rem;
1047
  }
1048
- /* Mobile-Specific Soft Drinks Modal Styles */
1049
  #softDrinkModal .modal-dialog {
1050
  max-width: 90%;
1051
  }
@@ -1097,7 +1091,6 @@
1097
  font-size: 1rem;
1098
  line-height: 28px;
1099
  }
1100
- /* Mobile-Specific Toggle Styles */
1101
  .toggle-container {
1102
  margin: 0 10px;
1103
  gap: 6px;
@@ -1125,7 +1118,6 @@
1125
  </style>
1126
  </head>
1127
  <body>
1128
- <!-- NEW: Toast Container -->
1129
  <div class="toast-container" id="toastContainer"></div>
1130
 
1131
  <div class="fixed-top-bar">
@@ -1143,29 +1135,29 @@
1143
  <input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off" aria-label="Search menu items">
1144
  <i class="bi bi-search search-icon"></i>
1145
  <i class="bi bi-mic mic-icon" id="micIcon" aria-label="Voice search"></i>
1146
- <!-- NEW: Search Suggestions -->
1147
  <div class="search-suggestions" id="searchSuggestions"></div>
1148
  </div>
1149
  </div>
1150
 
 
1151
  <form method="get" action="/menu" class="text-center mb-4" id="filter-form">
1152
  <label class="form-label fw-bold">Filters:</label>
1153
  <div class="toggle-container">
1154
  <input type="checkbox" id="veg-toggle" name="veg"
1155
- {% if selected_category == "Veg" %}checked{% endif %}
1156
  class="custom-toggle" onchange="handleToggle('veg')" aria-label="Filter vegetarian items">
1157
  <label for="veg-toggle">Veg</label>
1158
  </div>
1159
  <div class="toggle-container">
1160
- <input type="radio" id="category-CustomizedDish" name="category" value="Customized Dish"
1161
- {% if selected_category == "Customized Dish" %}checked{% endif %}
1162
  class="custom-toggle" onchange="handleToggle('custom')" aria-label="Filter customized dishes">
1163
  <label for="category-CustomizedDish">Customized Dish</label>
1164
  </div>
1165
  </form>
1166
 
1167
  <div class="container mt-4">
1168
- {% if selected_category == "Customized Dish" %}
1169
  <div id="custom-dish-form" class="mt-4">
1170
  <h3>Create Your Custom Dish</h3>
1171
  <form method="POST" action="/customdish/generate_custom_dish">
@@ -1182,15 +1174,17 @@
1182
  </form>
1183
  </div>
1184
  {% else %}
 
1185
  {% if ordered_menu.items()|length == 0 %}
1186
- <p>No menu items available for this category.</p>
 
1187
  {% else %}
1188
  {% for section, items in ordered_menu.items() %}
1189
  <h3>{{ section }}</h3>
1190
  <div class="row">
1191
  {% for item in items %}
1192
  <div class="col-md-6 mb-4">
1193
- <div class="card menu-card" data-item-name="{{ item.Name | default('Unnamed Item') }}" data-item-section="{{ item.Section__c | default(section) }}">
1194
  <video
1195
  class="card-img-top menu-video"
1196
  muted
@@ -1220,7 +1214,8 @@
1220
  data-item-section="{{ item.Section__c | default(section) }}"
1221
  data-item-category="{{ selected_category }}"
1222
  data-item-description="{{ item.Description__c | default('No description') }}"
1223
- data-item-image2="{{ item.Image2__c | default(item.Image1__c) }}">
 
1224
  {% if item.Section__c == 'Soft Drinks' %}
1225
  <button class="btn btn-primary add-to-cart-btn" onclick="showSoftDrinkModal(this)" aria-label="Add {{ item.Name | default('Unnamed Item') }} to cart">ADD</button>
1226
  {% else %}
@@ -1350,7 +1345,13 @@
1350
  {% for item in items %}
1351
  {
1352
  name: "{{ item.Name | default('Unnamed Item') }}",
1353
- section: "{{ item.Section__c | default(section) }}"
 
 
 
 
 
 
1354
  },
1355
  {% endfor %}
1356
  {% endfor %}
@@ -1382,7 +1383,6 @@
1382
  };
1383
  }
1384
 
1385
- // NEW: Show Toast Notification
1386
  function showToast(message) {
1387
  const toastContainer = document.getElementById('toastContainer');
1388
  const toast = document.createElement('div');
@@ -1555,7 +1555,6 @@
1555
  modalSectionEl.setAttribute('data-category', selectedCategory);
1556
  document.getElementById('quantityInput').value = 1;
1557
 
1558
- // NEW: Retry mechanism for add-ons
1559
  let retryCount = 0;
1560
  const maxRetries = 2;
1561
  function fetchAddons() {
@@ -1709,7 +1708,7 @@
1709
  });
1710
  }
1711
 
1712
- // === Filter Form ===
1713
  function handleToggle(source) {
1714
  const form = document.getElementById("filter-form");
1715
  const veg = document.getElementById("veg-toggle");
@@ -1731,299 +1730,364 @@
1731
  }
1732
  }
1733
 
1734
- if (!custom.checked && !veg.checked) {
1735
- custom.checked = false;
1736
- }
 
 
1737
 
1738
  form.submit();
1739
  }
1740
 
1741
- // === Initialization ===
1742
- document.addEventListener('DOMContentLoaded', function () {
1743
- // Avatar Dropdown
1744
- const avatarContainer = document.querySelector('.avatar-dropdown-container');
1745
- const dropdownMenu = document.querySelector('.dropdown-menu');
1746
- avatarContainer.addEventListener('click', function (event) {
1747
- event.stopPropagation();
1748
- dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1749
- });
1750
- document.addEventListener('click', function (event) {
1751
- if (!avatarContainer.contains(event.target)) {
1752
- dropdownMenu.style.display = 'none';
1753
- }
1754
- });
1755
- const dropdownItems = document.querySelectorAll('.dropdown-item');
1756
- dropdownItems.forEach(item => {
1757
- item.addEventListener('click', function () {
1758
- dropdownMenu.style.display = 'none';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1759
  });
1760
- });
1761
 
1762
- // NEW: Search Bar with Client-Side Filtering
1763
- const searchBar = document.getElementById('searchBar');
1764
- const searchSuggestions = document.getElementById('searchSuggestions');
1765
- searchBar.addEventListener('input', debounce(function () {
1766
- const query = searchBar.value.trim().toLowerCase();
1767
- searchSuggestions.innerHTML = '';
1768
- searchSuggestions.style.display = 'none';
1769
- if (query.length > 0) {
1770
- const filteredItems = menuItems.filter(item =>
1771
- item.name.toLowerCase().includes(query) ||
1772
- item.section.toLowerCase().includes(query)
1773
- );
1774
- if (filteredItems.length > 0) {
1775
- filteredItems.forEach(item => {
1776
- const suggestionDiv = document.createElement('div');
1777
- suggestionDiv.classList.add('suggestion-item');
1778
- suggestionDiv.innerText = `${item.name} (${item.section})`;
1779
- suggestionDiv.addEventListener('click', () => {
1780
- localStorage.setItem('selectedItem', JSON.stringify(item));
1781
- window.location.reload();
1782
- });
1783
- searchSuggestions.appendChild(suggestionDiv);
1784
- });
1785
- searchSuggestions.style.display = 'block';
1786
- }
1787
- }
1788
- }, 300));
1789
- searchBar.addEventListener('click', function (e) {
1790
- e.stopPropagation();
1791
- if (searchBar.value.trim()) {
1792
- searchSuggestions.style.display = 'block';
1793
- }
1794
- });
1795
- document.addEventListener('click', function (e) {
1796
- if (!searchBar.contains(e.target) && !searchSuggestions.contains(e.target)) {
1797
- searchSuggestions.style.display = 'none';
1798
- }
1799
- });
1800
 
1801
- // Highlight Selected Item
1802
- const selectedItem = localStorage.getItem('selectedItem');
1803
- if (selectedItem) {
1804
- try {
1805
- const { name, section } = JSON.parse(selectedItem);
1806
- const menuCards = document.querySelectorAll('.menu-card');
1807
- let targetCard = null;
1808
- let buttonContainer = null;
1809
- menuCards.forEach(card => {
1810
- const itemName = card.getAttribute('data-item-name');
1811
- const itemSection = card.getAttribute('data-item-section');
1812
- if (itemName === name && itemSection === section) {
1813
- targetCard = card;
1814
- buttonContainer = card.querySelector('.button-container');
1815
- card.classList.add('highlighted');
1816
- card.scrollIntoView({ behavior: 'smooth', block: 'center' });
1817
- const toggleLink = card.querySelector('.toggle-details');
1818
- if (toggleLink) {
1819
- toggleLink.click();
1820
  }
1821
- }
1822
- });
1823
- if (buttonContainer) {
1824
- if (section === 'Soft Drinks') {
1825
- showSoftDrinkModal(buttonContainer.querySelector('.add-to-cart-btn'));
1826
  } else {
1827
- const name = buttonContainer.getAttribute('data-item-name');
1828
- const price = buttonContainer.getAttribute('data-item-price');
1829
- const image = buttonContainer.getAttribute('data-item-image2');
1830
- const description = buttonContainer.getAttribute('data-item-description');
1831
- const category = buttonContainer.getAttribute('data-item-category');
1832
- showItemDetails(name, price, image, description, section, category);
1833
- const modal = new bootstrap.Modal(document.getElementById('itemModal'));
1834
- modal.show();
1835
  }
1836
- }
1837
- } catch (err) {
1838
- console.error('Error parsing selected item:', err);
1839
- showToast('Error loading selected item');
1840
- }
1841
- localStorage.removeItem('selectedItem');
1842
  }
 
1843
 
1844
- // Intersection Observers
1845
- const menuCards = document.querySelectorAll('.menu-card');
1846
- const menuVideos = document.querySelectorAll('.menu-video');
1847
- const cardObserver = new IntersectionObserver((entries, observer) => {
1848
- entries.forEach(entry => {
1849
- if (entry.isIntersecting) {
1850
- entry.target.classList.add('visible');
1851
- observer.unobserve(entry.target);
1852
  }
1853
- });
1854
- }, {
1855
- root: null,
1856
- rootMargin: '0px',
1857
- threshold: 0.1
1858
- });
1859
- const videoObserver = new IntersectionObserver((entries, observer) => {
1860
- entries.forEach(entry => {
1861
- if (entry.isIntersecting) {
1862
- const video = entry.target;
1863
- const src = video.getAttribute('data-src');
1864
- // NEW: Enhanced error handling for invalid video sources
1865
- if (src && src !== '/static/placeholder.mp4') {
1866
- const source = video.querySelector('source');
1867
- if (source && !source.src) {
1868
- source.src = src;
1869
- video.load().catch(err => {
1870
- console.error('Error loading video:', err);
1871
- video.poster = '/static/placeholder.jpg';
 
 
 
 
 
 
1872
  });
 
1873
  }
1874
- } else {
1875
- video.poster = '/static/placeholder.jpg';
1876
  }
1877
- video.classList.add('loaded');
1878
- observer.unobserve(video);
1879
- }
1880
- });
1881
- }, {
1882
- root: null,
1883
- rootMargin: '200px',
1884
- threshold: 0.01
1885
- });
1886
- menuCards.forEach(card => cardObserver.observe(card));
1887
- menuVideos.forEach(video => videoObserver.observe(video));
1888
 
1889
- // Toggle Details
1890
- const toggleLinks = document.querySelectorAll('.toggle-details');
1891
- toggleLinks.forEach(link => {
1892
- link.addEventListener('click', function () {
1893
- const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1894
- const detailsDiv = document.getElementById(`details-${itemName}`);
1895
- const isCurrentlyShown = detailsDiv.classList.contains('show');
1896
- document.querySelectorAll('.item-details.show').forEach(otherDetails => {
1897
- if (otherDetails !== detailsDiv) {
1898
- otherDetails.classList.remove('show');
1899
- const otherLink = document.querySelector(`.toggle-details[data-item-name="${otherDetails.id.replace('details-', '').replace(/-/g, ' ')}"]`);
1900
- if (otherLink) {
1901
- otherLink.innerText = 'Show Details';
1902
- }
1903
  }
1904
  });
1905
- if (!isCurrentlyShown) {
1906
- detailsDiv.classList.add('show');
1907
- this.innerText = 'Hide Details';
1908
- } else {
1909
- detailsDiv.classList.remove('show');
1910
- this.innerText = 'Show Details';
1911
- }
1912
- });
1913
- });
1914
 
1915
- // Custom Dish Form Suggestions
1916
- const descriptionTextarea = document.getElementById('custom-dish-description');
1917
- const descriptionSuggestions = document.getElementById('descriptionSuggestions');
1918
- if (descriptionTextarea && descriptionSuggestions) {
1919
- let usedIngredients = new Set();
1920
- function updateUsedIngredients() {
1921
- const inputText = descriptionTextarea.value.trim();
1922
- usedIngredients.clear();
1923
- if (inputText) {
1924
- const words = inputText.split(/,\s*/).map(word => word.trim());
1925
- words.forEach(word => {
1926
- if (word && ingredientsList.includes(word)) {
1927
- usedIngredients.add(word);
1928
  }
1929
  });
1930
- }
1931
- }
1932
- // NEW: Debounced input handler for suggestions
1933
- const debouncedSuggestions = debounce(function () {
1934
- const inputText = descriptionTextarea.value.trim();
1935
- const words = inputText.split(/,\s*/);
1936
- const lastWord = words[words.length - 1].trim().toLowerCase();
1937
- descriptionSuggestions.innerHTML = '';
1938
- descriptionSuggestions.style.display = 'none';
1939
- updateUsedIngredients();
1940
- if (lastWord) {
1941
- const filteredIngredients = ingredientsList.filter(ingredient =>
1942
- ingredient.toLowerCase().includes(lastWord) && !usedIngredients.has(ingredient)
1943
- );
1944
- if (filteredIngredients.length > 0) {
1945
- filteredIngredients.forEach(ingredient => {
1946
- const suggestionDiv = document.createElement('div');
1947
- suggestionDiv.classList.add('suggestion-item');
1948
- suggestionDiv.innerText = ingredient;
1949
- suggestionDiv.addEventListener('click', function () {
1950
- const currentValue = descriptionTextarea.value;
1951
- const lastCommaIndex = currentValue.lastIndexOf(',');
1952
- const baseText = lastCommaIndex !== -1 ? currentValue.substring(0, lastCommaIndex + 1) : '';
1953
- descriptionTextarea.value = baseText + (baseText ? ' ' : '') + ingredient + ', ';
1954
- descriptionSuggestions.style.display = 'none';
1955
- descriptionTextarea.focus();
1956
- updateUsedIngredients();
1957
- });
1958
- descriptionSuggestions.appendChild(suggestionDiv);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1959
  });
1960
- descriptionSuggestions.style.display = 'block';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1961
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1962
  }
1963
- }, 300);
1964
- descriptionTextarea.addEventListener('input', debouncedSuggestions);
1965
- document.addEventListener('click', function (event) {
1966
- if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
1967
- descriptionSuggestions.style.display = 'none';
1968
- }
1969
- });
1970
- }
1971
 
1972
- // Fetch Initial Cart
1973
- fetch('/cart/get')
1974
- .then(response => {
1975
- if (!response.ok) {
1976
- throw new Error(`HTTP error! Status: ${response.status}`);
1977
- }
1978
- return response.json();
1979
- })
1980
- .then(data => {
1981
- if (data.success) {
1982
- updateCartUI(data.cart);
1983
- } else {
1984
- console.error('Failed to fetch cart:', data.error);
1985
- showToast('Failed to load cart');
1986
- const cart = getCartLocalStorage();
1987
- updateCartUI(cart);
1988
- }
1989
- })
1990
- .catch(err => {
1991
- console.error('Error fetching cart:', err);
1992
- showToast('Error loading cart');
1993
- const cart = getCartLocalStorage();
1994
- updateCartUI(cart);
1995
- });
1996
-
1997
- // NEW: Remove redundant video preloading (handled by <link rel="preload">)
1998
- // const preloadedVideos = document.querySelectorAll('link[rel="preload"][as="video"]');
1999
- // preloadedVideos.forEach(link => {
2000
- // const video = document.createElement('video');
2001
- // video.src = link.href;
2002
- // video.preload = 'auto';
2003
- // });
2004
 
2005
- // Quantity Controls for Item Modal
2006
- const decreaseBtn = document.getElementById('decreaseQuantity');
2007
- const increaseBtn = document.getElementById('increaseQuantity');
2008
- const quantityInput = document.getElementById('quantityInput');
2009
- decreaseBtn.addEventListener('click', function () {
2010
- let quantity = parseInt(quantityInput.value) || 1;
2011
- quantity = Math.max(1, quantity - 1);
2012
- quantityInput.value = quantity;
2013
- });
2014
- increaseBtn.addEventListener('click', function () {
2015
- let quantity = parseInt(quantityInput.value) || 1;
2016
- quantity += 1;
2017
- quantityInput.value = quantity;
2018
- });
2019
 
2020
- // NEW: Keyboard Accessibility for Search Bar
2021
- searchBar.addEventListener('keydown', function (e) {
2022
- if (e.key === 'Enter' && searchBar.value.trim()) {
2023
- window.location.href = '/search';
2024
- }
2025
- });
2026
- });
2027
- </script>
2028
  </body>
2029
- </html>
 
 
15
  {% endfor %}
16
  {% endfor %}
17
  <style>
18
+ /* Same styles as previous response */
19
  body {
20
  font-family: Arial, sans-serif;
21
  background-color: #fdf4e3;
 
42
  }
43
  .menu-card.visible {
44
  opacity: 1;
 
45
  animation: fadeIn 0.3s ease-in-out;
46
  }
47
  .menu-card.highlighted {
 
550
  font-size: 1.2rem;
551
  color: #333;
552
  }
 
553
  .toast-container {
554
  position: fixed;
555
  top: 20px;
 
568
  .toast.show {
569
  opacity: 1;
570
  }
 
571
  .search-suggestions {
572
  position: absolute;
573
  top: 100%;
 
678
  height: 40px;
679
  text-align: center;
680
  }
 
681
  .modal-footer .btn-primary {
682
  background-color: #0FAA39;
683
  border-color: #0FAA39;
 
859
  font-size: 12px;
860
  margin-left: 8px;
861
  }
 
862
  @media (max-width: 576px) {
863
  .fixed-top-bar {
864
  height: 60px;
 
1012
  font-size: 10px;
1013
  margin-left: 5px;
1014
  }
 
1015
  .addon-section {
1016
  margin-bottom: 10px;
1017
  }
 
1040
  margin-left: 6px;
1041
  font-size: 0.8rem;
1042
  }
 
1043
  #softDrinkModal .modal-dialog {
1044
  max-width: 90%;
1045
  }
 
1091
  font-size: 1rem;
1092
  line-height: 28px;
1093
  }
 
1094
  .toggle-container {
1095
  margin: 0 10px;
1096
  gap: 6px;
 
1118
  </style>
1119
  </head>
1120
  <body>
 
1121
  <div class="toast-container" id="toastContainer"></div>
1122
 
1123
  <div class="fixed-top-bar">
 
1135
  <input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off" aria-label="Search menu items">
1136
  <i class="bi bi-search search-icon"></i>
1137
  <i class="bi bi-mic mic-icon" id="micIcon" aria-label="Voice search"></i>
 
1138
  <div class="search-suggestions" id="searchSuggestions"></div>
1139
  </div>
1140
  </div>
1141
 
1142
+ <!-- NEW: Updated Filter Form -->
1143
  <form method="get" action="/menu" class="text-center mb-4" id="filter-form">
1144
  <label class="form-label fw-bold">Filters:</label>
1145
  <div class="toggle-container">
1146
  <input type="checkbox" id="veg-toggle" name="veg"
1147
+ {% if veg_filter %}checked{% endif %}
1148
  class="custom-toggle" onchange="handleToggle('veg')" aria-label="Filter vegetarian items">
1149
  <label for="veg-toggle">Veg</label>
1150
  </div>
1151
  <div class="toggle-container">
1152
+ <input type="checkbox" id="category-CustomizedDish" name="custom"
1153
+ {% if custom_filter %}checked{% endif %}
1154
  class="custom-toggle" onchange="handleToggle('custom')" aria-label="Filter customized dishes">
1155
  <label for="category-CustomizedDish">Customized Dish</label>
1156
  </div>
1157
  </form>
1158
 
1159
  <div class="container mt-4">
1160
+ {% if custom_filter %}
1161
  <div id="custom-dish-form" class="mt-4">
1162
  <h3>Create Your Custom Dish</h3>
1163
  <form method="POST" action="/customdish/generate_custom_dish">
 
1174
  </form>
1175
  </div>
1176
  {% else %}
1177
+ <!-- NEW: Enhanced Empty State Handling -->
1178
  {% if ordered_menu.items()|length == 0 %}
1179
+ <p id="no-items-message">No menu items available for this filter. Please try adjusting the filters or contact support.</p>
1180
+ <div id="client-side-menu" class="row"></div>
1181
  {% else %}
1182
  {% for section, items in ordered_menu.items() %}
1183
  <h3>{{ section }}</h3>
1184
  <div class="row">
1185
  {% for item in items %}
1186
  <div class="col-md-6 mb-4">
1187
+ <div class="card menu-card" data-item-name="{{ item.Name | default('Unnamed Item') }}" data-item-section="{{ item.Section__c | default(section) }}" data-is-veg="{{ item.IsVeg__c | default('false') | lower }}">
1188
  <video
1189
  class="card-img-top menu-video"
1190
  muted
 
1214
  data-item-section="{{ item.Section__c | default(section) }}"
1215
  data-item-category="{{ selected_category }}"
1216
  data-item-description="{{ item.Description__c | default('No description') }}"
1217
+ data-item-image2="{{ item.Image2__c | default(item.Image1__c) }}"
1218
+ data-is-veg="{{ item.IsVeg__c | default('false') | lower }}">
1219
  {% if item.Section__c == 'Soft Drinks' %}
1220
  <button class="btn btn-primary add-to-cart-btn" onclick="showSoftDrinkModal(this)" aria-label="Add {{ item.Name | default('Unnamed Item') }} to cart">ADD</button>
1221
  {% else %}
 
1345
  {% for item in items %}
1346
  {
1347
  name: "{{ item.Name | default('Unnamed Item') }}",
1348
+ section: "{{ item.Section__c | default(section) }}",
1349
+ isVeg: {{ item.IsVeg__c | default('false') | lower }},
1350
+ price: "{{ item.Price__c | default('0.00') }}",
1351
+ image: "{{ item.Image1__c | default('/static/placeholder.jpg') }}",
1352
+ image2: "{{ item.Image2__c | default(item.Image1__c) }}",
1353
+ description: "{{ item.Description__c | default('No description') }}",
1354
+ video: "{{ item.Video1__c | default('/static/placeholder.mp4') }}"
1355
  },
1356
  {% endfor %}
1357
  {% endfor %}
 
1383
  };
1384
  }
1385
 
 
1386
  function showToast(message) {
1387
  const toastContainer = document.getElementById('toastContainer');
1388
  const toast = document.createElement('div');
 
1555
  modalSectionEl.setAttribute('data-category', selectedCategory);
1556
  document.getElementById('quantityInput').value = 1;
1557
 
 
1558
  let retryCount = 0;
1559
  const maxRetries = 2;
1560
  function fetchAddons() {
 
1708
  });
1709
  }
1710
 
1711
+ // NEW: Updated Filter Form Logic
1712
  function handleToggle(source) {
1713
  const form = document.getElementById("filter-form");
1714
  const veg = document.getElementById("veg-toggle");
 
1730
  }
1731
  }
1732
 
1733
+ // NEW: Log query parameters for debugging
1734
+ const params = new URLSearchParams();
1735
+ if (veg.checked) params.set('veg', 'on');
1736
+ if (custom.checked) params.set('custom', 'on');
1737
+ console.log('Filter form submitting with:', params.toString());
1738
 
1739
  form.submit();
1740
  }
1741
 
1742
+ // NEW: Client-Side Filtering for Veg
1743
+ function applyClientSideVegFilter() {
1744
+ const noItemsMessage = document.getElementById('no-items-message');
1745
+ const clientSideMenu = document.getElementById('client-side-menu');
1746
+ if (!noItemsMessage || !clientSideMenu) return;
1747
+
1748
+ const vegItems = menuItems.filter(item => item.isVeg === true);
1749
+ if (vegItems.length > 0) {
1750
+ noItemsMessage.innerText = 'Showing vegetarian items (client-side)';
1751
+ clientSideMenu.innerHTML = '';
1752
+
1753
+ const sections = [...new Set(vegItems.map(item => item.section))];
1754
+ sections.forEach(section => {
1755
+ const sectionItems = vegItems.filter(item => item.section === section);
1756
+ const sectionDiv = document.createElement('div');
1757
+ sectionDiv.innerHTML = `<h3>${section}</h3>`;
1758
+ const rowDiv = document.createElement('div');
1759
+ rowDiv.classList.add('row');
1760
+
1761
+ sectionItems.forEach(item => {
1762
+ const itemDiv = document.createElement('div');
1763
+ itemDiv.classList.add('col-md-6', 'mb-4');
1764
+ itemDiv.innerHTML = `
1765
+ <div class="card menu-card" data-item-name="${item.name}" data-item-section="${item.section}" data-is-veg="${item.isVeg}">
1766
+ <video
1767
+ class="card-img-top menu-video"
1768
+ muted
1769
+ loop
1770
+ preload="auto"
1771
+ data-src="${item.video}"
1772
+ poster="${item.image}"
1773
+ width="350"
1774
+ height="200"
1775
+ onmouseover="this.play()"
1776
+ onmouseout="this.pause(); this.currentTime = 0;"
1777
+ onerror="this.poster='/static/placeholder.jpg';">
1778
+ <source src="${item.video}" type="video/mp4">
1779
+ Your browser does not support the video tag.
1780
+ </video>
1781
+ <div class="addbutton">
1782
+ <div class="card-body d-flex align-items-center justify-content-between">
1783
+ <div>
1784
+ <h5 class="card-title">${item.name}</h5>
1785
+ <p class="card-text price">$${item.price}</p>
1786
+ </div>
1787
+ <div class="d-flex flex-column align-items-center justify-content-center
1788
+ <div class="button-container"
1789
+ data-item-name="${item.name}"
1790
+ data-item-price="${item.price}"
1791
+ data-item-image="${item.image}"
1792
+ data-item-section="${item.section}"
1793
+ data-item-category="Veg"
1794
+ data-item-description="${item.description}"
1795
+ data-item-image2="${item.image2}"
1796
+ data-is-veg="${item.isVeg}">
1797
+ <button class="btn btn-primary"
1798
+ data-bs-toggle="modal"
1799
+ data-bs-target="#itemModal"
1800
+ onclick="showItemDetails('${item.name}', '${item.price}', '${item.image2}', '${item.description}', '${item.section}', 'Veg')"
1801
+ aria-label="Customize ${item.name}">
1802
+ ADD
1803
+ </button>
1804
+ ${item.section !== 'Apetizer' && item.section !== 'Customized Dish' ? '<span class="customisable-text">Customisable</span>' : ''}
1805
+ </div>
1806
+ </div>
1807
+ </div>
1808
+ <div class="toggle-details" data-item-name="${item.name}" role="button" aria-label="Toggle details for ${item.name}">Show Details</div>
1809
+ <div class="item-details" id="details-${item.name.replace(/ /g, '-')}">
1810
+ <h6>Description</h6>
1811
+ <p>${item.description}</p>
1812
+ <!-- NEW: Placeholder for ingredients and nutritional info -->
1813
+ <h6>Ingredients</h6>
1814
+ <p>Not specified</p>
1815
+ <h6>Nutritional Info</h6>
1816
+ <p class="nutritional-info">Not available</p>
1817
+ <h6>Allergens</h6>
1818
+ <p>None listed</p>
1819
+ </div>
1820
+ </div>
1821
+ `;
1822
+ rowDiv.appendChild(itemDiv);
1823
+ });
1824
+ sectionDiv.appendChild(rowDiv);
1825
+ clientSideMenu.appendChild(sectionDiv);
1826
  });
 
1827
 
1828
+ // NEW: Re-apply Intersection Observers for client-side rendered cards
1829
+ const newMenuCards = clientSideMenu.querySelectorAll('.menu-card');
1830
+ const newMenuVideos = clientSideMenu.querySelectorAll('.menu-video');
1831
+ newMenuCards.forEach(card => cardObserver.observe(card));
1832
+ newMenuVideos.forEach(video => videoObserver.observe(video));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1833
 
1834
+ // NEW: Re-apply toggle details listeners
1835
+ const newToggleLinks = clientSideMenu.querySelectorAll('.toggle-details');
1836
+ newToggleLinks.forEach(link => {
1837
+ link.addEventListener('click', function () {
1838
+ const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1839
+ const detailsDiv = document.getElementById(`details-${itemName}`);
1840
+ const isCurrentlyShown = detailsDiv.classList.contains('show');
1841
+ document.querySelectorAll('.item-details.show').forEach(otherDetails => {
1842
+ if (otherDetails !== detailsDiv) {
1843
+ otherDetails.classList.remove('show');
1844
+ const otherLink = document.querySelector(`.toggle-details[data-item-name="${otherDetails.id.replace('details-', '').replace(/-/g, ' ')}"]`);
1845
+ if (otherLink) {
1846
+ otherLink.innerText = 'Show Details';
1847
+ }
 
 
 
 
 
1848
  }
1849
+ });
1850
+ if (!isCurrentlyShown) {
1851
+ detailsDiv.classList.add('show');
1852
+ this.innerText = 'Hide Details';
 
1853
  } else {
1854
+ detailsDiv.classList.remove('show');
1855
+ this.innerText = 'Show Details';
 
 
 
 
 
 
1856
  }
1857
+ });
1858
+ });
1859
+ } else {
1860
+ noItemsMessage.innerText = 'No vegetarian items available. Please try adjusting the filters or contact support.';
 
 
1861
  }
1862
+ }
1863
 
1864
+ // NEW: Apply client-side filter if no items are returned
1865
+ if (document.getElementById('no-items-message')) {
1866
+ applyClientSideVegFilter();
 
 
 
 
 
1867
  }
1868
+
1869
+ // === Search Functionality ===
1870
+ const searchBar = document.getElementById('searchBar');
1871
+ const searchSuggestions = document.getElementById('searchSuggestions');
1872
+ const debouncedSearch = debounce(function () {
1873
+ const query = searchBar.value.trim().toLowerCase();
1874
+ searchSuggestions.innerHTML = '';
1875
+ searchSuggestions.style.display = 'none';
1876
+ if (query.length >= 2) {
1877
+ const filteredItems = menuItems.filter(item =>
1878
+ item.name.toLowerCase().includes(query) ||
1879
+ item.section.toLowerCase().includes(query) ||
1880
+ item.description.toLowerCase().includes(query)
1881
+ );
1882
+ if (filteredItems.length > 0) {
1883
+ filteredItems.forEach(item => {
1884
+ const suggestionDiv = document.createElement('div');
1885
+ suggestionDiv.classList.add('suggestion-item');
1886
+ suggestionDiv.innerText = item.name;
1887
+ suggestionDiv.addEventListener('click', () => {
1888
+ searchBar.value = item.name;
1889
+ searchSuggestions.style.display = 'none';
1890
+ window.location.href = '/search';
1891
+ });
1892
+ searchSuggestions.appendChild(suggestionDiv);
1893
  });
1894
+ searchSuggestions.style.display = 'block';
1895
  }
 
 
1896
  }
1897
+ }, 300);
1898
+ searchBar.addEventListener('input', debouncedSearch);
1899
+ document.addEventListener('click', function (event) {
1900
+ if (!searchBar.contains(event.target) && !searchSuggestions.contains(event.target)) {
1901
+ searchSuggestions.style.display = 'none';
1902
+ }
1903
+ });
 
 
 
 
1904
 
1905
+ // === Dropdown Menu ===
1906
+ const avatarIcon = document.querySelector('.avatar-icon');
1907
+ const dropdownMenu = document.querySelector('.dropdown-menu');
1908
+ avatarIcon.addEventListener('click', function () {
1909
+ dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
1910
+ });
1911
+ document.addEventListener('click', function (event) {
1912
+ if (!avatarIcon.contains(event.target) && !dropdownMenu.contains(event.target)) {
1913
+ dropdownMenu.style.display = 'none';
 
 
 
 
 
1914
  }
1915
  });
 
 
 
 
 
 
 
 
 
1916
 
1917
+ // === Intersection Observers ===
1918
+ const menuCards = document.querySelectorAll('.menu-card');
1919
+ const menuVideos = document.querySelectorAll('.menu-video');
1920
+ const cardObserver = new IntersectionObserver((entries, observer) => {
1921
+ entries.forEach(entry => {
1922
+ if (entry.isIntersecting) {
1923
+ entry.target.classList.add('visible');
1924
+ observer.unobserve(entry.target);
 
 
 
 
 
1925
  }
1926
  });
1927
+ }, {
1928
+ root: null,
1929
+ rootMargin: '0px',
1930
+ threshold: 0.1
1931
+ });
1932
+ const videoObserver = new IntersectionObserver((entries, observer) => {
1933
+ entries.forEach(entry => {
1934
+ if (entry.isIntersecting) {
1935
+ const video = entry.target;
1936
+ const src = video.getAttribute('data-src');
1937
+ if (src && src !== '/static/placeholder.mp4') {
1938
+ const source = video.querySelector('source');
1939
+ if (source && !source.src) {
1940
+ source.src = src;
1941
+ video.load().catch(err => {
1942
+ console.error('Error loading video:', err);
1943
+ video.poster = '/static/placeholder.jpg';
1944
+ });
1945
+ }
1946
+ } else {
1947
+ video.poster = '/static/placeholder.jpg';
1948
+ }
1949
+ video.classList.add('loaded');
1950
+ observer.unobserve(video);
1951
+ }
1952
+ });
1953
+ }, {
1954
+ root: null,
1955
+ rootMargin: '200px',
1956
+ threshold: 0.01
1957
+ });
1958
+ menuCards.forEach(card => cardObserver.observe(card));
1959
+ menuVideos.forEach(video => videoObserver.observe(video));
1960
+
1961
+ // === Toggle Details ===
1962
+ const toggleLinks = document.querySelectorAll('.toggle-details');
1963
+ toggleLinks.forEach(link => {
1964
+ link.addEventListener('click', function () {
1965
+ const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
1966
+ const detailsDiv = document.getElementById(`details-${itemName}`);
1967
+ const isCurrentlyShown = detailsDiv.classList.contains('show');
1968
+ document.querySelectorAll('.item-details.show').forEach(otherDetails => {
1969
+ if (otherDetails !== detailsDiv) {
1970
+ otherDetails.classList.remove('show');
1971
+ const otherLink = document.querySelector(`.toggle-details[data-item-name="${otherDetails.id.replace('details-', '').replace(/-/g, ' ')}"]`);
1972
+ if (otherLink) {
1973
+ otherLink.innerText = 'Show Details';
1974
+ }
1975
+ }
1976
  });
1977
+ if (!isCurrentlyShown) {
1978
+ detailsDiv.classList.add('show');
1979
+ this.innerText = 'Hide Details';
1980
+ } else {
1981
+ detailsDiv.classList.remove('show');
1982
+ this.innerText = 'Show Details';
1983
+ }
1984
+ });
1985
+ });
1986
+
1987
+ // === Custom Dish Form Suggestions ===
1988
+ const descriptionTextarea = document.getElementById('custom-dish-description');
1989
+ const descriptionSuggestions = document.getElementById('descriptionSuggestions');
1990
+ if (descriptionTextarea && descriptionSuggestions) {
1991
+ let usedIngredients = new Set();
1992
+ function updateUsedIngredients() {
1993
+ const inputText = descriptionTextarea.value.trim();
1994
+ usedIngredients.clear();
1995
+ if (inputText) {
1996
+ const words = inputText.split(/,\s*/).map(word => word.trim());
1997
+ words.forEach(word => {
1998
+ if (word && ingredientsList.includes(word)) {
1999
+ usedIngredients.add(word);
2000
+ }
2001
+ });
2002
+ }
2003
  }
2004
+ const debouncedSuggestions = debounce(function () {
2005
+ const inputText = descriptionTextarea.value.trim();
2006
+ const words = inputText.split(/,\s*/);
2007
+ const lastWord = words[words.length - 1].trim().toLowerCase();
2008
+ descriptionSuggestions.innerHTML = '';
2009
+ descriptionSuggestions.style.display = 'none';
2010
+ updateUsedIngredients();
2011
+ if (lastWord) {
2012
+ const filteredIngredients = ingredientsList.filter(ingredient =>
2013
+ ingredient.toLowerCase().includes(lastWord) && !usedIngredients.has(ingredient)
2014
+ );
2015
+ if (filteredIngredients.length > 0) {
2016
+ filteredIngredients.forEach(ingredient => {
2017
+ const suggestionDiv = document.createElement('div');
2018
+ suggestionDiv.classList.add('suggestion-item');
2019
+ suggestionDiv.innerText = ingredient;
2020
+ suggestionDiv.addEventListener('click', function () {
2021
+ const currentValue = descriptionTextarea.value;
2022
+ const lastCommaIndex = currentValue.lastIndexOf(',');
2023
+ const baseText = lastCommaIndex !== -1 ? currentValue.substring(0, lastCommaIndex + 1) : '';
2024
+ descriptionTextarea.value = baseText + (baseText ? ' ' : '') + ingredient + ', ';
2025
+ descriptionSuggestions.style.display = 'none';
2026
+ descriptionTextarea.focus();
2027
+ updateUsedIngredients();
2028
+ });
2029
+ descriptionSuggestions.appendChild(suggestionDiv);
2030
+ });
2031
+ descriptionSuggestions.style.display = 'block';
2032
+ }
2033
+ }
2034
+ }, 300);
2035
+ descriptionTextarea.addEventListener('input', debouncedSuggestions);
2036
+ document.addEventListener('click', function (event) {
2037
+ if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
2038
+ descriptionSuggestions.style.display = 'none';
2039
+ }
2040
+ });
2041
  }
 
 
 
 
 
 
 
 
2042
 
2043
+ // === Fetch Initial Cart ===
2044
+ fetch('/cart/get')
2045
+ .then(response => {
2046
+ if (!response.ok) {
2047
+ throw new Error(`HTTP error! Status: ${response.status}`);
2048
+ }
2049
+ return response.json();
2050
+ })
2051
+ .then(data => {
2052
+ if (data.success) {
2053
+ updateCartUI(data.cart);
2054
+ } else {
2055
+ console.error('Failed to fetch cart:', data.error);
2056
+ showToast('Failed to load cart');
2057
+ const cart = getCartLocalStorage();
2058
+ updateCartUI(cart);
2059
+ }
2060
+ })
2061
+ .catch(err => {
2062
+ console.error('Error fetching cart:', err);
2063
+ showToast('Error loading cart');
2064
+ const cart = getCartLocalStorage();
2065
+ updateCartUI(cart);
2066
+ });
 
 
 
 
 
 
 
 
2067
 
2068
+ // === Quantity Controls for Item Modal ===
2069
+ const decreaseBtn = document.getElementById('decreaseQuantity');
2070
+ const increaseBtn = document.getElementById('increaseQuantity');
2071
+ const quantityInput = document.getElementById('quantityInput');
2072
+ decreaseBtn.addEventListener('click', function () {
2073
+ let quantity = parseInt(quantityInput.value) || 1;
2074
+ quantity = Math.max(1, quantity - 1);
2075
+ quantityInput.value = quantity;
2076
+ });
2077
+ increaseBtn.addEventListener('click', function () {
2078
+ let quantity = parseInt(quantityInput.value) || 1;
2079
+ quantity += 1;
2080
+ quantityInput.value = quantity;
2081
+ });
2082
 
2083
+ // === Keyboard Accessibility for Search Bar ===
2084
+ searchBar.addEventListener('keydown', function (e) {
2085
+ if (e.key === 'Enter' && searchBar.value.trim()) {
2086
+ window.location.href = '/search';
2087
+ }
2088
+ });
2089
+ });
2090
+ </script>
2091
  </body>
2092
+ </html>
2093
+