subbuok / templates /menu.html
lokesh341's picture
Update templates/menu.html
0590c97 verified
raw
history blame
87.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Menu</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.css" rel="stylesheet">
<!-- Preload Critical Resources -->
<link rel="preload" href="/static/placeholder.mp4" as="video">
{% for section, items in ordered_menu.items() %}
{% for item in items[:1] %}
<link rel="preload" href="{{ item.Video1__c | default('/static/placeholder.mp4') }}" as="video" fetchpriority="high">
{% endfor %}
{% endfor %}
<style>
body {
font-family: Arial, sans-serif;
background-color: #fdf4e3;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
padding-bottom: 70px;
}
.container {
max-width: 900px;
}
.menu-card {
max-width: 350px;
border-radius: 15px;
overflow: hidden;
background-color: #fff;
margin: auto;
display: flex;
flex-direction: column;
opacity: 0;
transition: opacity 0.3s ease-in-out;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.menu-card.visible {
opacity: 1;
}
.menu-video {
height: 200px;
width: 100%;
object-fit: cover;
border-radius: 15px 15px 0 0;
opacity: 0;
transition: opacity 0.5s ease-in-out;
background-color: #000;
}
.menu-video.loaded {
opacity: 1;
}
.menu-card:hover .menu-video {
opacity: 1;
transform: scale(1.05);
}
.menu-card .card-body .card-title {
font-size: 1.2rem;
font-weight: 600;
margin: 10px 0;
color: #333333;
}
.menu-card .card-body .card-text.price {
font-size: 1rem;
font-weight: 500;
color: #000000;
margin-bottom: 5px;
}
.addbutton .btn {
background-color: #28a745;
color: white;
padding: 10px 20px;
font-size: 16px;
font-weight: bold;
border-radius: 5px;
border: none;
transition: background-color 0.3s ease;
margin-left: 13px;
}
.addbutton .btn:hover {
background-color: #218838;
}
.button-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 6px;
}
.customisable-text {
color: #0FAA39;
font-size: 10px;
font-weight: 500;
margin: 0;
text-align: center;
line-height: 1;
}
.btn-primary {
font-size: 12px;
font-weight: bold;
border-radius: 8px;
width: 70px;
height: 35px;
background-color: #0FAA39;
border-color: #0FAA39;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
transition: background-color 0.3s ease, transform 0.1s ease;
}
.btn-primary:hover {
background-color: #0D9232;
border-color: #0D9232;
transform: scale(1.05);
}
.btn-primary:active,
.btn-primary:focus {
background-color: #0B7A29;
border-color: #0B7A29;
box-shadow: none;
transform: scale(0.98);
}
.avatar-dropdown-container {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
}
.avatar-icon {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #007bff;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
font-weight: bold;
position: relative;
transition: transform 0.2s ease;
}
.avatar-icon:hover {
transform: scale(1.1);
}
.avatar-icon img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 50%;
transition: transform 0.2s ease;
}
.avatar-icon img:hover {
transform: scale(1.1);
}
.dropdown-menu {
position: absolute;
right: 0;
top: 100%;
background-color: #fff8f0;
border-radius: 8px;
width: 200px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
display: none;
border: 1px solid #ffd8b1;
padding: 5px 0;
z-index: 1000;
}
.dropdown-menu .dropdown-item {
padding: 10px 16px;
text-decoration: none;
color: #333;
font-size: 14px;
transition: background-color 0.2s ease, color 0.2s ease;
cursor: pointer;
}
.dropdown-menu .dropdown-item:hover {
background-color: #ffe4c4;
color: #2c2c2c;
}
.upload-item,
.delete-item,
.view-item {
padding: 10px 16px;
text-decoration: none;
color: #333;
font-size: 14px;
transition: background-color 0.2s ease;
cursor: pointer;
}
.upload-item:hover,
.delete-item:hover,
.view-item:hover {
background-color: #ffe4c4;
color: #2c2c2c;
}
.fixed-top-bar {
position: relative;
top: 0;
left: 0;
width: 100%;
height: 54px;
background: linear-gradient(45deg, #FFA07A, #FFB347);
color: white;
padding: 15px;
display: flex;
justify-content: space-between;
align-items: center;
z-index: 1000;
}
.search-bar-container {
position: absolute;
left: 20px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
width: 300px;
max-width: 90%;
position: relative;
}
.search-bar-container input {
width: 100%;
padding: 8px 40px 8px 40px;
font-size: 16px;
border-radius: 25px;
border: none;
background-color: #fff;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
outline: none;
}
.search-bar-container input::placeholder {
color: #888;
}
.search-icon {
position: absolute;
left: 15px;
font-size: 18px;
color: #888;
}
.mic-icon {
position: absolute;
right: 15px;
font-size: 18px;
color: #888;
cursor: pointer;
transition: color 0.3s ease;
}
.mic-icon.active {
color: #007bff;
}
.mic-unsupported {
display: none;
position: absolute;
right: 15px;
font-size: 14px;
color: #888;
background-color: #fff;
padding: 2px 8px;
border-radius: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.autocomplete-suggestions {
position: absolute;
top: 100%;
left: 0;
width: 100%;
max-height: 200px;
overflow-y: auto;
background-color: #fff;
border: 1px solid #ddd;
border-radius: 5px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
display: none;
}
.autocomplete-suggestions .suggestion-item {
padding: 8px 15px;
cursor: pointer;
font-size: 14px;
color: #333;
}
.autocomplete-suggestions .suggestion-item:hover {
background-color: #f1f1f1;
}
.addon-section {
background-color: #fff;
border: 2px solid #ffa500;
border-radius: 8px;
padding: 12px;
margin-bottom: 10px;
}
.addon-section h6 {
margin-bottom: 10px;
font-size: 1.1rem;
font-weight: bold;
color: #343a40;
}
.addon-section .form-check {
display: inline-flex;
align-items: center;
margin-left: 10px;
color: #343a40;
}
.addon-section .form-check-input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border: 2px solid #343a40;
border-radius: 5px;
background-color: #f0f0f0;
position: relative;
margin-right: 10px;
}
.addon-section .form-check-input:checked {
background-color: #006400;
border-color: #006400;
}
.addon-section .form-check-input:checked::before {
content: '\2713';
font-size: 14px;
position: absolute;
top: 3px;
left: 4px;
color: white;
}
.addon-section .form-check-label {
font-size: 16px;
margin-left: 5px;
margin-right: 15px;
cursor: pointer;
display: inline-block;
vertical-align: middle;
}
form.text-center.mb-4 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 5px;
}
.modal-header {
padding: 10px 15px;
}
.modal-title {
font-size: 16px;
font-weight: bold;
}
.modal-body {
max-height: 60vh;
overflow-y: auto;
padding: 15px;
text-align: center;
}
.modal-body #avatar-modal-img {
max-width: 100%;
max-height: 400px;
object-fit: contain;
border-radius: 8px;
margin: 0 auto;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.modal-body #modal-img {
max-height: 200px;
width: 100%;
object-fit: cover;
border-radius: 8px;
margin-bottom: 10px;
}
.modal-body #modal-name {
font-size: 20px;
font-weight: bold;
text-align: center;
margin-bottom: 5px;
color: #333333;
}
.modal-body #modal-price {
font-size: 16px;
font-weight: 500;
color: #000000;
text-align: center;
margin-bottom: 10px;
}
.modal-body #modal-description {
font-size: 14px;
color: #6c757d;
margin-bottom: 10px;
}
.modal-body .nutritional-info {
font-size: 12px;
color: #6c757d;
margin-bottom: 10px;
}
.modal-body #modal-addons h6,
.modal-body #first-row h6 {
font-size: 14px;
font-weight: bold;
margin-bottom: 10px;
}
.modal-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px;
}
.modal-footer .d-flex {
display: flex;
align-items: center;
gap: 10px;
}
.modal-footer .btn {
height: 40px;
padding: 0 15px;
}
.modal-footer .form-control {
width: 50px;
height: 40px;
text-align: center;
}
.modal-footer .btn-primary {
background-color: #0FAA39;
border-color: #0FAA39;
font-weight: bold;
padding: 10px 20px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
width: auto;
}
.modal-footer .btn-outline-secondary {
height: 40px;
width: 40px;
}
.item-details {
display: none;
padding: 15px;
background-color: #f8f9fa;
border-radius: 8px;
margin-top: 10px;
margin: 10px 15px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.item-details.show {
display: block;
}
.item-details h6 {
color: #0FAA39;
margin-bottom: 10px;
font-size: 1.1rem;
font-weight: bold;
}
.item-details p {
margin-bottom: 15px;
color: #555;
line-height: 1.5;
font-size: 0.9rem;
}
.toggle-details {
cursor: pointer;
color: #0FAA39;
font-size: 0.9rem;
text-align: left;
padding: 5px 0;
transition: color 0.3s ease;
display: block;
width: 100%;
margin-top: 5px;
}
.toggle-details:hover {
color: #0D9232;
text-decoration: underline;
}
.category-buttons {
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: center;
margin-top: 10px;
}
.category-button {
background-color: #fff;
border: 2px solid #0FAA39;
color: #0FAA39;
padding: 5px 15px;
border-radius: 20px;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
}
.category-button.selected {
background-color: #0FAA39;
color: #fff;
border-color: #0FAA39;
}
.category-button:hover {
background-color: #e6f4ea;
}
.quantity-selector {
display: flex;
align-items: center;
gap: 5px;
}
.quantity-selector .btn {
width: 25px;
height: 25px;
padding: 0;
font-size: 12px;
line-height: 25px;
text-align: center;
}
.quantity-selector .quantity-display {
width: 25px;
text-align: center;
font-size: 12px;
font-weight: bold;
line-height: 25px;
}
.quantity-selector .quantity-to-add,
.quantity-selector .quantity-to-remove {
width: 45px;
height: 25px;
font-size: 12px;
padding: 0 5px;
}
.modal-dialog {
max-height: 90vh;
}
.modal-body::-webkit-scrollbar {
width: 8px;
}
.modal-body::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.modal-body::-webkit-scrollbar-thumb {
background: #0FAA39;
border-radius: 10px;
}
.modal-body::-webkit-scrollbar-thumb:hover {
background: #0D9232;
}
.btn-primary:disabled {
opacity: 0.65;
cursor: not-allowed;
}
.quantity-selector select {
width: 60px;
height: 35px;
padding: 5px;
border-radius: 5px;
border: 1px solid #ced4da;
}
#custom-dish-form {
position: relative;
padding-bottom: 80px;
}
#custom-dish-form .btn-primary {
position: absolute;
right: 15px;
bottom: 15px;
width: auto;
padding: 10px 20px;
}
.bottom-action-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: white;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
max-width: 900px;
margin: 0 auto;
}
.bottom-action-bar .btn {
flex: 1;
margin: 0 5px;
padding: 10px 15px;
border-radius: 8px;
font-weight: bold;
font-size: 16px;
color: white;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
min-width: 0;
white-space: nowrap;
}
.bottom-action-bar .btn-order-history {
background-color: #FFA07A;
border-color: #FFA07A;
}
.bottom-action-bar .btn-order-history:hover {
background-color: #FF8C61;
border-color: #FF8C61;
}
.bottom-action-bar .btn-view-cart {
background-color: #0FAA39;
border-color: #0FAA39;
}
.bottom-action-bar .btn-view-cart:hover {
background-color: #0D9232;
border-color: #0D9232;
}
.cart-icon-badge {
background-color: white;
color: #0FAA39;
border-radius: 50%;
width: 20px;
height: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 12px;
margin-left: 8px;
}
.mic-popup {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 30px;
border-radius: 15px;
text-align: center;
z-index: 2000;
display: none;
width: 300px;
max-width: 90%;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.mic-popup.active {
display: block;
}
.mic-popup-icon {
font-size: 48px;
margin-bottom: 20px;
color: #ff4444;
animation: pulse 1.5s infinite;
}
.mic-popup-text {
font-size: 18px;
margin-bottom: 15px;
min-height: 24px;
}
.mic-popup-cancel {
background-color: #ff4444;
color: white;
border: none;
padding: 8px 20px;
border-radius: 20px;
cursor: pointer;
font-weight: bold;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}
.highlight-item {
animation: highlight 2s ease-out;
}
@keyframes highlight {
0% { background-color: #FFFFE0; }
100% { background-color: transparent; }
}
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255,255,255,0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
display: none;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #0FAA39;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@media (max-width: 576px) {
.fixed-top-bar {
height: 60px;
padding: 10px;
}
.search-bar-container {
width: 80%;
max-width: 100%;
left: 10px;
top: 50%;
transform: translateY(-50%);
}
.search-bar-container input {
padding: 6px 35px 6px 35px;
font-size: 14px;
border-radius: 20px;
}
.search-icon {
left: 12px;
font-size: 16px;
}
.mic-icon {
right: 12px;
font-size: 16px;
}
.avatar-dropdown-container {
right: 10px;
}
.avatar-icon {
width: 40px;
height: 40px;
font-size: 20px;
}
.dropdown-menu {
width: 180px;
}
.dropdown-menu .dropdown-item {
padding: 8px 12px;
font-size: 13px;
}
.upload-item,
.delete-item,
.view-item {
padding: 8px 12px;
font-size: 13px;
}
.category-buttons {
gap: 8px;
}
.category-button {
padding: 4px 12px;
font-size: 0.85rem;
}
.modal-dialog {
max-width: 96%;
margin: 5px auto;
}
.modal-header {
padding: 5px 10px;
}
.modal-title {
font-size: 14px;
}
.modal-body {
max-height: 50vh;
padding: 8px;
}
.modal-body #avatar-modal-img {
max-height: 300px;
}
.modal-body #modal-img {
max-height: 150px;
width: 100%;
max-width: 150px;
margin: 0 auto 5px;
display: block;
}
.modal-body #modal-name {
font-size: 18px;
margin-bottom: 3px;
}
.modal-body #modal-price {
font-size: 14px;
margin-bottom: 5px;
}
.modal-body #modal-description {
font-size: 12px;
margin-bottom: 5px;
}
.modal-body .nutritional-info {
font-size: 10px;
margin-bottom: 5px;
}
.modal-body #modal-addons h6,
.modal-body #first-row h6 {
font-size: 12px;
margin-bottom: 5px;
}
.modal-footer {
padding: 5px;
}
.modal-footer .btn {
height: 30px;
padding: 0 10px;
}
.modal-footer .form-control {
width: 30px;
height: 30px;
font-size: 12px;
font-weight: bold;
}
.modal-footer .btn-outline-secondary {
width: 25px;
height: 25px;
font-size: 12px;
line-height: 25px;
}
.modal-footer .btn-primary {
font-size: 12px;
height: 30px;
padding: 0 15px;
border-radius: 5px;
}
.btn-primary {
font-size: 10px;
width: 50px;
height: 25px;
}
.customisable-text {
font-size: 8px;
}
.button-container {
gap: 3px;
}
.quantity-selector .btn {
width: 18px;
height: 18px;
font-size: 9px;
line-height: 18px;
}
.quantity-selector .quantity-display {
width: 18px;
font-size: 9px;
line-height: 18px;
}
.quantity-selector .quantity-to-add,
.quantity-selector .quantity-to-remove {
width: 35px;
height: 18px;
font-size: 9px;
}
.quantity-selector select {
width: 50px;
height: 30px;
font-size: 12px;
}
.bottom-action-bar {
padding: 8px 10px;
}
.bottom-action-bar .btn {
padding: 8px 10px;
font-size: 14px;
}
.cart-icon-badge {
width: 18px;
height: 18px;
font-size: 10px;
margin-left: 5px;
}
.item-details {
padding: 10px;
margin: 5px 10px;
}
.item-details h6 {
font-size: 0.95rem;
}
.item-details p {
font-size: 0.8rem;
}
.toggle-details {
font-size: 0.8rem;
}
.mic-popup {
padding: 20px;
width: 280px;
}
.mic-popup-icon {
font-size: 36px;
margin-bottom: 15px;
}
.mic-popup-text {
font-size: 16px;
}
.mic-popup-cancel {
padding: 6px 16px;
font-size: 14px;
}
}
</style>
</head>
<body>
<div class="fixed-top-bar">
<div class="avatar-dropdown-container">
<div class="avatar-icon" id="avatarIcon">
{% if user_image %}
<img src="{{ user_image }}" alt="User Avatar" class="avatar-image">
{% else %}
<span>{{ first_letter }}</span>
{% endif %}
</div>
<div class="dropdown-menu" id="avatarDropdown">
{% if user_image %}
<div class="dropdown-item delete-item" id="deleteAvatar">Delete Image</div>
<div class="dropdown-item view-item" id="viewAvatar">View Avatar</div>
{% endif %}
<a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item">Order History</a>
<a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item">View Profile</a>
<div class="dropdown-item upload-item">
<label for="avatarUpload" style="cursor: pointer; margin: 0; width: 100%;">
Upload Image
</label>
<input type="file" id="avatarUpload" accept="image/*" style="display: none;">
</div>
<a href="{{ url_for('logout') }}" class="dropdown-item">Logout</a>
</div>
</div>
<div class="search-bar-container">
<input type="text" id="searchBar" class="form-control" placeholder="Search items or sections..." autocomplete="off">
<i class="bi bi-search search-icon"></i>
<i class="bi bi-mic mic-icon" id="micIcon"></i>
<span class="mic-unsupported" id="micUnsupported">Mic not supported</span>
<div id="autocompleteSuggestions" class="autocomplete-suggestions"></div>
</div>
</div>
<form method="get" action="/menu" class="text-center mb-4" id="categoryForm">
<label class="form-label fw-bold">Select a Category:</label>
<div class="category-buttons">
{% for category in categories %}
<button type="button" class="category-button {% if selected_category == category %}selected{% endif %}" data-category="{{ category }}">{{ category }}</button>
{% endfor %}
<button type="button" class="category-button {% if selected_category == 'Customized Dish' %}selected{% endif %}" data-category="Customized Dish">Customized Dish</button>
</div>
<input type="hidden" name="category" id="selectedCategoryInput" value="{{ selected_category }}">
</form>
<div class="container mt-4">
{% if selected_category == "Customized Dish" %}
<div id="custom-dish-form" class="mt-4">
<h3>Create Your Custom Dish</h3>
<form method="POST" action="/customdish/generate_custom_dish" id="customDishForm">
<div class="mb-3">
<label for="custom-dish-name" class="form-label">Dish Name</label>
<input type="text" class="form-control" id="custom-dish-name" name="name" required>
</div>
<div class="mb-3 position-relative">
<label for="custom-dish-description" class="form-label">Dish Description</label>
<textarea class="form-control" id="custom-dish-description" name="description" required></textarea>
<div id="descriptionSuggestions" class="autocomplete-suggestions"></div>
</div>
<button type="submit" class="btn btn-primary">Submit Custom Dish</button>
</form>
</div>
{% else %}
{% if ordered_menu.items()|length == 0 %}
<p>No menu items available for this category.</p>
{% else %}
{% for section, items in ordered_menu.items() %}
<h3>{{ section }}</h3>
<div class="row">
{% for item in items %}
<div class="col-md-6 mb-4" data-item-name="{{ item.Name | default('Unnamed Item') | e }}">
<div class="card menu-card">
<video
class="card-img-top menu-video"
muted
loop
preload="auto"
data-src="{{ item.Video1__c | default('/static/placeholder.mp4') }}"
poster="{{ item.Image1__c | default('/static/placeholder.jpg') }}"
width="350"
height="200"
onmouseover="this.play()"
onmouseout="this.pause(); this.currentTime = 0;"
onerror="this.poster='/static/placeholder.jpg';">
<source src="{{ item.Video1__c | default('/static/placeholder.mp4') }}" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="addbutton">
<div class="card-body d-flex align-items-center justify-content-between">
<div>
<h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
<p class="card-text price">${{ item.Price__c | default('0.00') }}</p>
{% if item.Section__c != 'Soft Drinks' %}
<div class="toggle-details" data-item-name="{{ item.Name | default('Unnamed Item') }}">Show Details</div>
{% endif %}
</div>
<div class="d-flex flex-column align-item-center justify-content-center">
<div class="button-container"
data-item-name="{{ item.Name | default('Unnamed Item') }}"
data-item-price="{{ item.Price__c | default('0.00') }}"
data-item-image="{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}"
data-item-section="{{ item.Section__c | default(section) }}"
data-item-category="{{ selected_category }}">
{% if item.Section__c == 'Soft Drinks' %}
<button class="btn btn-primary add-to-cart-btn" onclick="showSoftDrinkModal(this)">ADD</button>
{% else %}
<button class="btn btn-primary"
data-bs-toggle="modal"
data-bs-target="#itemModal"
onclick="showItemDetails('{{ item.Name | default('Unnamed Item') | e }}', '{{ item.Price__c | default('0.00') }}', '{{ item.Image2__c | default(item.Image1__c) | default('/static/placeholder.jpg') }}', '{{ item.Description__c | default('No description') | e }}', '{{ item.IngredientsInfo__c | default('Not specified') | e }}', '{{ item.NutritionalInfo__c | default('Not available') | e }}', '{{ item.Allergens__c | default('None listed') | e }}', '{{ item.Section__c | default(section) | e }}', '{{ selected_category | e }}')">
ADD
</button>
{% endif %}
{% if item.Section__c != 'Apetizer' and item.Section__c != 'Customized dish' and item.Section__c != 'Soft Drinks' %}
<span class="customisable-text">Customisable</span>
{% endif %}
</div>
</div>
</div>
</div>
{% if item.Section__c != 'Soft Drinks' %}
<div class="item-details" id="details-{{ item.Name | default('unnamed-item') | replace(' ', '-') }}">
<h6>Description</h6>
<p>{{ item.Description__c | default('No description available') }}</p>
<h6>Ingredients Info</h6>
<p>{{ item.IngredientsInfo__c | default('Not specified') }}</p>
<h6>Nutritional Info</h6>
север <p>{{ item.NutritionalInfo__c | default('Not available') }}</p>
<h6>Allergens</h6>
<p>{{ item.Allergens__c | default('None listed') }}</p>
</div>
{% endif %}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
{% endif %}
{% endif %}
</div>
<div class="bottom-action-bar">
<a href="{{ url_for('orderhistory.order_history') }}" class="btn btn-order-history">
<i class="bi bi-clock-history"></i> Order History
</a>
<a href="{{ url_for('cart.cart') }}" class="btn btn-view-cart">
<i class="bi bi-cart"></i> View Cart
<span class="cart-icon-badge" id="cart-item-count">{{ cart_item_count }}</span>
</a>
</div>
<!-- Modal for Item Details -->
<div class="modal fade" id="itemModal" tabindex="-1" aria-labelledby="itemModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="itemModalLabel">Item Details</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<img id="modal-img" class="img-fluid rounded mb-3 d-block mx-auto" alt="Item Image" style="max-height: 200px; object-fit: cover;">
<h5 id="modal-name" class="fw-bold text-center"></h5>
<p id="modal-price" class="text-muted text-center"></p>
<p id="modal-description" class="text-secondary"></p>
<div class="nutritional-info">
<strong>Ingredients:</strong> <span id="modal-ingredients"></span><br>
<strong>Nutrition:</strong> <span id="modal-nutrition"></span><br>
<strong>Allergens:</strong> <span id="modal-allergens"></span>
</div>
<div id="modal-addons" class="modal-addons mt-4">
<h6>Customization Options</h6>
<div id="addons-list" class="addons-container">Loading customization options...</div>
</div>
<div class="mt-4">
<h6>Custom Request</h6>
<textarea id="modal-instructions" class="form-control" placeholder="Enter any special instructions here..."></textarea>
</div>
<span id="modal-section" data-section="" data-category="" style="display: none;"></span>
</div>
<div class="modal-footer d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center gap-2">
<button type="button" class="btn btn-outline-secondary" id="decreaseQuantity">-</button>
<input type="text" class="form-control text-center" id="quantityInput" value="1" readonly style="width: 50px;"/>
<button type="button" class="btn btn-outline-secondary" id="increaseQuantity">+</button>
</div>
<button type="button" class="btn btn-primary" onclick="addToCartFromModal()">Add to Cart</button>
</div>
</div>
</div>
</div>
<!-- Modal for Avatar View -->
<div class="modal fade" id="avatarModal" tabindex="-1" aria-labelledby="avatarModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="avatarModalLabel">View Avatar</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<img id="avatar-modal-img" class="img-fluid rounded mx-auto d-block" alt="Avatar Image" style="max-height: 400px; object-fit: contain;">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- Modal for Soft Drinks Quantity Selection -->
<div class="modal fade" id="softDrinkModal" tabindex="-1" aria-labelledby="softDrinkModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content" style="border-radius: 15px; box-shadow: 0 4px 15px rgba(0,0,0,0.2);">
<div class="modal-header" style="background: linear-gradient(45deg, #0FAA39, #0D9232); color: white; border-radius: 15px 15px 0 0;">
<h5 class="modal-title" id="softDrinkModalLabel">Select Your Drink</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" style="padding: 20px;">
<div class="text-center mb-4">
<img id="soft-drink-image" class="img-fluid rounded mb-3" alt="Soft Drink Image" style="max-height: 150px; width: auto; object-fit: contain;">
<h5 id="soft-drink-name" class="fw-bold" style="color: #333;"></h5>
<p id="soft-drink-price" class="text-muted" style="font-size: 1.1rem;"></p>
</div>
<div class="d-flex justify-content-center align-items-center mb-4">
<div class="quantity-selector" style="background-color: #f8f9fa; padding: 10px; border-radius: 10px;">
<button type="button" class="btn btn-outline-secondary" id="soft-drink-decrease" style="width: 40px; height: 40px;">-</button>
<input type="text" class="form-control text-center mx-2" id="soft-drink-quantity" value="1" readonly style="width: 60px; font-weight: bold; font-size: 1.1rem;">
<button type="button" class="btn btn-outline-secondary" id="soft-drink-increase" style="width: 40px; height: 40px;">+</button>
</div>
</div>
</div>
<div class="modal-footer" style="border-top: none; padding: 0 20px 20px 20px; justify-content: center;">
<button type="button" class="btn btn-primary" onclick="addSoftDrinkToCart()" style="width: 200px; padding: 12px; font-size: 1.1rem; background-color: #0FAA39; border-color: #0FAA39;">Add to Cart</button>
</div>
</div>
</div>
</div>
<!-- Mic Popup -->
<div class="mic-popup" id="micPopup">
<div class="mic-popup-icon">
<i class="bi bi-mic-fill"></i>
</div>
<div class="mic-popup-text" id="micPopupText">Listening...</div>
<button class="mic-popup-cancel" id="micPopupCancel">Cancel</button>
</div>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="spinner"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
let isProcessingRequest = false;
let currentSoftDrinkButton = null;
const menuItems = [
{% for section, items in ordered_menu.items() %}
{% for item in items %}
"{{ item.Name | default('Unnamed Item') | e }}",
{% endfor %}
{% endfor %}
];
const ingredientsList = [
"Basmati Rice", "Bell Pepper", "Biryani Masala", "Butter", "Capsicum", "Cauliflower",
"Chickpea Flour (Besan)", "Chickpea Flour (for batter)", "Chickpeas (Channa)", "Chili Powder",
"Chili Sauce", "Coconut Milk", "Coriander Powder", "Cornflour", "Cream", "Cumin Powder",
"Cumin Seeds", "Curd (Yogurt)", "Curry Leaves", "Fish (e.g., King Fish or Salmon)",
"Fresh Coriander Leaves", "Garam Masala", "Garlic", "Ghee (Clarified Butter)", "Ginger",
"Ginger-Garlic Paste", "Goat Meat (Mutton)", "Green Chilies", "Honey",
"Kasuri Methi (dried fenugreek leaves)", "Lemon Juice", "Mango Puree", "Mint Leaves",
"Mixed Vegetables (Carrot, Peas, Potato, Cauliflower)", "Mixed Vegetables (Carrot, Peas, Potato)",
"Mustard Seeds", "Mutton (Goat Meat)", "Oil", "Oil (for frying)", "Onion",
"Paneer (Indian Cottage Cheese)", "Peas", "Potatoes", "Prawns", "Red Chili Powder",
"Rice Flour", "Saffron", "Salt", "Soy Sauce", "Spring Onion", "Tamarind (for sourness)",
"Tomato Ketchup", "Tomatoes", "Turmeric Powder", "Vinegar", "Water", "Wheat Flour (for dough)",
"Whole Wheat Flour", "Yogurt (Curd)"
];
function sanitizeInput(input) {
const div = document.createElement('div');
div.textContent = input;
return div.innerHTML;
}
function addToCartLocalStorage(payload) {
let cart = JSON.parse(localStorage.getItem('cart')) || [];
const existingItem = cart.find(item =>
item.itemName === payload.itemName &&
item.instructions === payload.instructions &&
JSON.stringify(item.addons) === JSON.stringify(payload.addons)
);
if (existingItem) {
existingItem.quantity = payload.quantity;
} else {
cart.push(payload);
}
localStorage.setItem('cart', JSON.stringify(cart));
return cart;
}
function removeFromCartLocalStorage(itemName, quantityToRemove, instructions, addons) {
let cart = JSON.parse(localStorage.getItem('cart')) || [];
const itemIndex = cart.findIndex(item =>
item.itemName === itemName &&
item.instructions === instructions &&
JSON.stringify(item.addons) === JSON.stringify(addons)
);
if (itemIndex !== -1) {
if (quantityToRemove >= cart[itemIndex].quantity) {
cart.splice(itemIndex, 1);
} else {
cart[itemIndex].quantity -= quantityToRemove;
}
}
localStorage.setItem('cart', JSON.stringify(cart));
return cart;
}
function getCartLocalStorage() {
return JSON.parse(localStorage.getItem('cart')) || [];
}
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function showSoftDrinkModal(button) {
currentSoftDrinkButton = button;
const buttonContainer = button.closest('.button-container');
const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
const itemPrice = buttonContainer.getAttribute('data-item-price');
const itemImage = buttonContainer.getAttribute('data-item-image');
document.getElementById('soft-drink-name').textContent = itemName;
document.getElementById('soft-drink-price').textContent = `$${itemPrice}`;
document.getElementById('soft-drink-quantity').value = '1';
const softDrinkImage = document.getElementById('soft-drink-image');
softDrinkImage.src = itemImage || '/static/placeholder.jpg';
const modal = new bootstrap.Modal(document.getElementById('softDrinkModal'));
modal.show();
}
function addSoftDrinkToCart() {
if (!currentSoftDrinkButton) return;
const buttonContainer = currentSoftDrinkButton.closest('.button-container');
const quantity = parseInt(document.getElementById('soft-drink-quantity').value) || 1;
const itemName = sanitizeInput(buttonContainer.getAttribute('data-item-name'));
const itemPrice = parseFloat(buttonContainer.getAttribute('data-item-price'));
const itemImage = buttonContainer.getAttribute('data-item-image');
const section = sanitizeInput(buttonContainer.getAttribute('data-item-section'));
const selectedCategory = sanitizeInput(buttonContainer.getAttribute('data-item-category'));
const cartPayload = {
itemName: itemName,
itemPrice: itemPrice,
itemImage: itemImage,
section: section,
category: selectedCategory,
addons: [],
instructions: '',
quantity: quantity
};
fetch('/cart/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(cartPayload)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Item added to cart successfully!');
updateCartUI(data.cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
modal.hide();
} else {
console.error('Failed to add item to cart:', data.error);
alert(data.error || 'Failed to add item to cart. Using local storage as fallback.');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
modal.hide();
}
})
.catch(err => {
console.error('Error adding item to cart:', err);
alert('Error adding item to cart. Using local storage as fallback.');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('softDrinkModal'));
modal.hide();
});
}
function updateCartUI(cart) {
if (!Array.isArray(cart)) {
console.error('Invalid cart data:', cart);
return;
}
let totalQuantity = 0;
cart.forEach(item => {
totalQuantity += item.quantity;
});
const cartItemCount = document.getElementById('cart-item-count');
if (cartItemCount) {
cartItemCount.innerText = totalQuantity;
cartItemCount.style.display = totalQuantity > 0 ? 'inline-flex' : 'none';
}
}
function handleHighlightedItem() {
const urlParams = new URLSearchParams(window.location.search);
const highlightItemName = urlParams.get('highlight');
if (highlightItemName) {
const itemData = JSON.parse(sessionStorage.getItem('highlightItem'));
if (itemData) {
setTimeout(() => {
const itemElement = document.querySelector(`[data-item-name="${itemData.name}"]`);
if (itemElement) {
itemElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
itemElement.classList.add('highlight-item');
setTimeout(() => {
itemElement.classList.remove('highlight-item');
}, 2000);
const addButton = itemElement.querySelector('.btn-primary');
const buttonContainer = addButton.closest('.button-container');
const section = buttonContainer.getAttribute('data-item-section');
if (section === 'Soft Drinks') {
showSoftDrinkModal(addButton);
} else {
const name = buttonContainer.getAttribute('data-item-name');
const price = buttonContainer.getAttribute('data-item-price');
const image = buttonContainer.getAttribute('data-item-image');
const category = buttonContainer.getAttribute('data-item-category');
const description = itemElement.querySelector('.item-details h6:contains("Description") + p')?.textContent || 'No description available';
const ingredients = itemElement.querySelector('.item-details h6:contains("Ingredients Info") + p')?.textContent || 'Not specified';
const nutrition = itemElement.querySelector('.item-details h6:contains("Nutritional Info") + p')?.textContent || 'Not available';
const allergens = itemElement.querySelector('.item-details h6:contains("Allergens") + p')?.textContent || 'None listed';
showItemDetails(
name,
price,
image,
description,
ingredients,
nutrition,
allergens,
section,
category
);
const modal = new bootstrap.Modal(document.getElementById('itemModal'));
modal.show();
}
}
sessionStorage.removeItem('highlightItem');
window.history.replaceState({}, document.title, window.location.pathname);
}, 500);
}
}
}
document.addEventListener('DOMContentLoaded', function () {
handleHighlightedItem();
const avatarContainer = document.querySelector('.avatar-dropdown-container');
const dropdownMenu = document.querySelector('.dropdown-menu');
const avatarIcon = document.getElementById('avatarIcon');
const avatarUpload = document.getElementById('avatarUpload');
const deleteAvatar = document.getElementById('deleteAvatar');
const viewAvatar = document.getElementById('viewAvatar');
function loadAvatar() {
const savedAvatar = localStorage.getItem('userAvatar');
const avatarImage = avatarIcon.querySelector('.avatar-image');
if (savedAvatar && !avatarImage) {
const img = document.createElement('img');
img.src = savedAvatar;
img.alt = 'User Avatar';
img.className = 'avatar-image';
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
avatarIcon.innerHTML = '';
avatarIcon.appendChild(img);
ensureAvatarOptions();
} else if (!savedAvatar && avatarImage) {
avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
removeAvatarOptions();
}
}
function ensureAvatarOptions() {
if (!document.querySelector('#deleteAvatar')) {
const deleteItem = document.createElement('div');
deleteItem.className = 'dropdown-item delete-item';
deleteItem.id = 'deleteAvatar';
deleteItem.innerText = 'Delete Image';
dropdownMenu.insertBefore(deleteItem, dropdownMenu.firstChild);
addDeleteListener(deleteItem);
}
if (!document.querySelector('#viewAvatar')) {
const viewItem = document.createElement('div');
viewItem.className = 'dropdown-item view-item';
viewItem.id = 'viewAvatar';
viewItem.innerText = 'View Avatar';
const firstChild = dropdownMenu.firstChild;
if (firstChild) dropdownMenu.insertBefore(viewItem, firstChild.nextSibling);
addViewListener(viewItem);
}
}
function removeAvatarOptions() {
const deleteItem = document.getElementById('deleteAvatar');
const viewItem = document.getElementById('viewAvatar');
if (deleteItem) deleteItem.remove();
if (viewItem) viewItem.remove();
}
function saveAvatar(imageUrl) {
localStorage.setItem('userAvatar', imageUrl);
}
function clearAvatar() {
localStorage.removeItem('userAvatar');
avatarIcon.innerHTML = `<span>{{ first_letter }}</span>`;
removeAvatarOptions();
}
avatarIcon.addEventListener('click', function(event) {
event.stopPropagation();
dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
});
avatarUpload.addEventListener('change', function(event) {
const file = event.target.files[0];
if (file) {
const maxSize = 15 * 1024 * 1024;
if (file.size > maxSize) {
alert('Image size must not exceed 15MB.');
this.value = '';
return;
}
const reader = new FileReader();
reader.onload = function(e) {
const base64Image = e.target.result;
fetch('/upload_avatar', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ image: base64Image })
})
.then(response => {
if (!response.ok) throw new Error('Upload failed');
return response.json();
})
.then(data => {
if (data.success) {
const img = document.createElement('img');
img.src = data.image || '/static/default-avatar.jpg';
img.alt = 'User Avatar';
img.className = 'avatar-image';
img.style.cssText = 'width: 100%; height: 100%; object-fit: cover; border-radius: 50%;';
avatarIcon.innerHTML = '';
avatarIcon.appendChild(img);
saveAvatar(data.image);
ensureAvatarOptions();
dropdownMenu.style.display = 'none';
} else {
alert('Failed to upload image: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Upload error:', error);
alert('Error uploading image.');
});
};
reader.readAsDataURL(file);
}
});
function addDeleteListener(deleteElement) {
deleteElement.addEventListener('click', function() {
fetch('/delete_avatar', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
if (data.success) {
clearAvatar();
dropdownMenu.style.display = 'none';
} else {
alert('Failed to delete image: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Delete error:', error);
alert('Error deleting image.');
});
});
}
function addViewListener(viewElement) {
viewElement.addEventListener('click', function() {
const avatarImage = avatarIcon.querySelector('.avatar-image');
if (avatarImage) {
const modalImg = document.getElementById('avatar-modal-img');
modalImg.src = avatarImage.src;
const modal = new bootstrap.Modal(document.getElementById('avatarModal'));
modal.show();
}
dropdownMenu.style.display = 'none';
});
}
if (deleteAvatar) addDeleteListener(deleteAvatar);
if (viewAvatar) addViewListener(viewAvatar);
loadAvatar();
document.addEventListener('click', function(event) {
if (!avatarContainer.contains(event.target)) {
dropdownMenu.style.display = 'none';
}
});
const menuCards = document.querySelectorAll('.menu-card');
const menuVideos = document.querySelectorAll('.menu-video');
const cardObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1 });
const videoObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const video = entry.target;
const src = video.getAttribute('data-src');
if (src) {
const source = video.querySelector('source');
source.src = src;
video.load();
}
video.classList.add('loaded');
}
});
}, { rootMargin: '200px' });
menuCards.forEach(card => cardObserver.observe(card));
menuVideos.forEach(video => videoObserver.observe(video));
const toggleLinks = document.querySelectorAll('.toggle-details');
toggleLinks.forEach(link => {
link.addEventListener('click', function () {
const itemName = this.getAttribute('data-item-name').replace(/ /g, '-');
const detailsDiv = document.getElementById(`details-${itemName}`);
const isShown = detailsDiv.classList.contains('show');
document.querySelectorAll('.item-details.show').forEach(other => {
if (other !== detailsDiv) {
other.classList.remove('show');
other.previousElementSibling.querySelector('.toggle-details').innerText = 'Show Details';
}
});
detailsDiv.classList.toggle('show');
this.innerText = isShown ? 'Show Details' : 'Hide Details';
});
});
const categoryButtons = document.querySelectorAll('.category-button');
const categoryForm = document.getElementById('categoryForm');
const selectedCategoryInput = document.getElementById('selectedCategoryInput');
if (!selectedCategoryInput.value) {
selectedCategoryInput.value = "All";
document.querySelector('.category-button[data-category="All"]').classList.add('selected');
}
categoryButtons.forEach(button => {
button.addEventListener('click', function () {
categoryButtons.forEach(btn => btn.classList.remove('selected'));
this.classList.add('selected');
selectedCategoryInput.value = this.getAttribute('data-category');
categoryForm.submit();
});
});
const searchBar = document.getElementById('searchBar');
const suggestionsContainer = document.getElementById('autocompleteSuggestions');
const debouncedFilterMenu = debounce(filterMenu, 300);
searchBar.addEventListener('click', function (event) {
event.stopPropagation();
document.getElementById('loadingOverlay').style.display = 'flex';
window.location.href = '/search';
});
searchBar.addEventListener('input', function () {
const input = sanitizeInput(this.value.trim().toLowerCase());
suggestionsContainer.innerHTML = '';
suggestionsContainer.style.display = 'none';
if (input) {
const filteredItems = menuItems.filter(item => item.toLowerCase().includes(input));
if (filteredItems.length > 0) {
filteredItems.forEach(item => {
const suggestionDiv = document.createElement('div');
suggestionDiv.classList.add('suggestion-item');
suggestionDiv.innerText = item;
suggestionDiv.addEventListener('click', function () {
searchBar.value = item;
suggestionsContainer.style.display = 'none';
debouncedFilterMenu();
});
suggestionsContainer.appendChild(suggestionDiv);
});
suggestionsContainer.style.display = 'block';
}
}
debouncedFilterMenu();
});
document.addEventListener('click', function (event) {
if (!searchBar.contains(event.target) && !suggestionsContainer.contains(event.target)) {
suggestionsContainer.style.display = 'none';
}
});
const descriptionTextarea = document.getElementById('custom-dish-description');
const descriptionSuggestions = document.getElementById('descriptionSuggestions');
if (descriptionTextarea && descriptionSuggestions) {
let usedIngredients = new Set();
function updateUsedIngredients() {
const inputText = descriptionTextarea.value.trim();
usedIngredients.clear();
if (inputText) {
const words = inputText.split(/,\s*/).map(word => word.trim());
words.forEach(word => {
if (word && ingredientsList.includes(word)) {
usedIngredients.add(word);
}
});
}
}
descriptionTextarea.addEventListener('input', function () {
const inputText = this.value.trim();
const words = inputText.split(/,\s*/);
const lastWord = words[words.length - 1].trim().toLowerCase();
descriptionSuggestions.innerHTML = '';
descriptionSuggestions.style.display = 'none';
updateUsedIngredients();
if (lastWord) {
const filteredIngredients = ingredientsList.filter(ingredient =>
ingredient.toLowerCase().includes(lastWord) && !usedIngredients.has(ingredient)
);
if (filteredIngredients.length > 0) {
filteredIngredients.forEach(ingredient => {
const suggestionDiv = document.createElement('div');
suggestionDiv.classList.add('suggestion-item');
suggestionDiv.innerText = ingredient;
suggestionDiv.addEventListener('click', function () {
const currentValue = descriptionTextarea.value;
const lastCommaIndex = currentValue.lastIndexOf(',');
const baseText = lastCommaIndex !== -1 ? currentValue.substring(0, lastCommaIndex + 1) : '';
descriptionTextarea.value = baseText + (baseText ? ' ' : '') + ingredient + ', ';
descriptionSuggestions.style.display = 'none';
descriptionTextarea.focus();
updateUsedIngredients();
});
descriptionSuggestions.appendChild(suggestionDiv);
});
descriptionSuggestions.style.display = 'block';
}
}
});
document.addEventListener('click', function (event) {
if (!descriptionTextarea.contains(event.target) && !descriptionSuggestions.contains(event.target)) {
descriptionSuggestions.style.display = 'none';
}
});
}
const customDishForm = document.getElementById('customDishForm');
if (customDishForm) {
customDishForm.addEventListener('submit', function(event) {
event.preventDefault();
const dishName = document.getElementById('custom-dish-name').value.trim();
const description = document.getElementById('custom-dish-description').value.trim();
if (!dishName || !description) {
alert('Please fill in both fields.');
return;
}
fetch('/customdish/generate_custom_dish', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
'name': dishName,
'description': description
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Custom dish submitted successfully!');
window.location.reload();
} else {
alert('Failed to submit: ' + (data.error || 'Unknown error'));
}
})
.catch(error => {
console.error('Error:', error);
alert('Error submitting custom dish.');
});
});
}
fetch('/cart/get')
.then(response => response.json())
.then(data => {
if (data.success) {
updateCartUI(data.cart);
} else {
updateCartUI(getCartLocalStorage());
}
})
.catch(err => {
console.error('Error fetching cart:', err);
updateCartUI(getCartLocalStorage());
});
const decreaseBtn = document.getElementById('decreaseQuantity');
const increaseBtn = document.getElementById('increaseQuantity');
const quantityInput = document.getElementById('quantityInput');
decreaseBtn.addEventListener('click', function () {
let qty = parseInt(quantityInput.value);
if (qty > 1) quantityInput.value = qty - 1;
});
increaseBtn.addEventListener('click', function () {
let qty = parseInt(quantityInput.value);
quantityInput.value = qty + 1;
});
const softDrinkDecreaseBtn = document.getElementById('soft-drink-decrease');
const softDrinkIncreaseBtn = document.getElementById('soft-drink-increase');
const softDrinkQuantityInput = document.getElementById('soft-drink-quantity');
softDrinkDecreaseBtn.addEventListener('click', function() {
let qty = parseInt(softDrinkQuantityInput.value);
if (qty > 1) softDrinkQuantityInput.value = qty - 1;
});
softDrinkIncreaseBtn.addEventListener('click', function() {
let qty = parseInt(softDrinkQuantityInput.value);
if (qty < 1000) softDrinkQuantityInput.value = qty + 1;
});
const micIcon = document.getElementById('micIcon');
const micUnsupported = document.getElementById('micUnsupported');
const micPopup = document.getElementById('micPopup');
const micPopupText = document.getElementById('micPopupText');
const micPopupCancel = document.getElementById('micPopupCancel');
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.continuous = false;
recognition.interimResults = true;
recognition.lang = 'en-US';
recognition.onstart = () => {
micIcon.classList.add('active');
micPopup.classList.add('active');
micPopupText.textContent = 'Listening...';
};
recognition.onresult = (event) => {
let interimTranscript = '';
let finalTranscript = '';
for (let i = event.resultIndex; i < event.results.length; i++) {
const transcript = event.results[i][0].transcript;
if (event.results[i].isFinal) finalTranscript += transcript;
else interimTranscript += transcript;
}
if (interimTranscript) micPopupText.textContent = interimTranscript;
if (finalTranscript) {
searchBar.value = sanitizeInput(finalTranscript.trim());
debouncedFilterMenu();
micPopup.classList.remove('active');
}
};
recognition.onend = () => {
micIcon.classList.remove('active');
setTimeout(() => micPopup.classList.remove('active'), 1000);
};
recognition.onerror = (event) => {
micIcon.classList.remove('active');
micPopupText.textContent = 'Error: ' + event.error;
setTimeout(() => micPopup.classList.remove('active'), 2000);
console.error('Speech error:', event.error);
};
micIcon.addEventListener('click', () => {
try {
recognition.start();
} catch (e) {
micPopupText.textContent = 'Error starting microphone';
setTimeout(() => micPopup.classList.remove('active'), 2000);
console.error('Error:', e);
}
});
micPopupCancel.addEventListener('click', () => {
recognition.stop();
micPopup.classList.remove('active');
micIcon.classList.remove('active');
});
} else {
micIcon.style.display = 'none';
micUnsupported.style.display = 'block';
}
});
window.addEventListener('load', function() {
document.getElementById('loadingOverlay').style.display = 'none';
});
function filterMenu() {
const input = sanitizeInput(document.getElementById('searchBar').value.trim().toLowerCase());
const sections = document.querySelectorAll('h3');
const items = document.querySelectorAll('.menu-card');
let matchedSections = new Set();
items.forEach(item => {
const itemName = item.querySelector('.card-title').innerText.toLowerCase();
const itemSection = item.closest('.row').previousElementSibling.innerText.toLowerCase();
if (itemName.includes(input) || (itemSection && itemSection.includes(input))) {
item.style.display = 'block';
item.classList.add('visible');
matchedSections.add(item.closest('.row'));
} else {
item.style.display = 'none';
}
});
sections.forEach(section => {
const sectionRow = section.nextElementSibling;
if (matchedSections.has(sectionRow)) {
section.style.display = 'block';
sectionRow.style.display = 'flex';
} else {
section.style.display = 'none';
sectionRow.style.display = 'none';
}
});
if (!input) {
sections.forEach(section => {
section.style.display = 'block';
section.nextElementSibling.style.display = 'flex';
});
items.forEach(item => {
item.style.display = 'block';
item.classList.add('visible');
});
}
}
function showItemDetails(name, price, image, description, ingredients, nutrition, allergens, section, selectedCategory) {
document.getElementById('modal-name').innerText = name;
document.getElementById('modal-price').innerText = `$${price}`;
document.getElementById('modal-img').src = image || '/static/placeholder.jpg';
document.getElementById('modal-description').innerText = description || 'No description available.';
document.getElementById('modal-ingredients').innerText = ingredients || 'Not specified';
document.getElementById('modal-nutrition').innerText = nutrition || 'Not available';
document.getElementById('modal-allergens').innerText = allergens || 'None listed';
document.getElementById('addons-list').innerHTML = 'Loading customization options...';
document.getElementById('modal-instructions').value = '';
const modalSectionEl = document.getElementById('modal-section');
modalSectionEl.setAttribute('data-section', section);
modalSectionEl.setAttribute('data-category', selectedCategory);
document.getElementById('quantityInput').value = 1;
fetch(`/api/addons?item_name=${encodeURIComponent(name)}&item_section=${encodeURIComponent(section)}`)
.then(response => response.json())
.then(data => {
const addonsList = document.getElementById('addons-list');
addonsList.innerHTML = '';
if (!data.success || !data.addons || data.addons.length === 0) {
addonsList.innerHTML = '<p>No customization options available.</p>';
return;
}
data.addons.forEach(addon => {
const sectionDiv = document.createElement('div');
sectionDiv.classList.add('addon-section');
const title = document.createElement('h6');
title.innerText = addon.name;
sectionDiv.appendChild(title);
const optionsContainer = document.createElement('div');
addon.options.forEach((option, index) => {
const optionId = `addon-${addon.name.replace(/\s+/g, '')}-${index}`;
const listItem = document.createElement('div');
listItem.classList.add('form-check');
listItem.innerHTML = `
<input type="checkbox" class="form-check-input addon-option" id="${optionId}" value="${option}"
data-name="${option}" data-group="${addon.name}" data-price="${addon.extra_charge ? addon.extra_charge_amount : 0}">
<label class="form-check-label" for="${optionId}">
${option} ${addon.extra_charge ? `($${addon.extra_charge_amount})` : ''}
</label>
`;
optionsContainer.appendChild(listItem);
});
sectionDiv.appendChild(optionsContainer);
addonsList.appendChild(sectionDiv);
});
})
.catch(err => {
console.error('Error fetching add-ons:', err);
document.getElementById('addons-list').innerHTML = '<p>Error loading customization options.</p>';
});
}
document.addEventListener('click', function(event) {
if (event.target.classList.contains('addon-option')) {
handleAddonClick(event.target);
}
});
function handleAddonClick(checkbox) {
const groupName = checkbox.getAttribute('data-group');
const isMultiSelectGroup = ["Extra Toppings", "Choose Raita/Sides", "Select Dip/Sauce", "Extra Add-ons", "Make it"].includes(groupName);
const checkboxes = document.querySelectorAll(`.addon-option[data-group="${groupName}"]`);
if (!isMultiSelectGroup) {
checkboxes.forEach(cb => {
if (cb !== checkbox) cb.checked = false;
});
}
}
function addToCartFromModal() {
const itemName = document.getElementById('modal-name').innerText;
const itemPrice = parseFloat(document.getElementById('modal-price').innerText.replace('$', ''));
const itemImage = document.getElementById('modal-img').src;
const instructions = document.getElementById('modal-instructions').value.trim();
const quantity = parseInt(document.getElementById('quantityInput').value);
const section = document.getElementById('modal-section').getAttribute('data-section');
const selectedCategory = document.getElementById('modal-section').getAttribute('data-category');
const addons = Array.from(document.querySelectorAll('.addon-option:checked')).map(cb => ({
name: cb.getAttribute('data-name'),
price: parseFloat(cb.getAttribute('data-price') || 0)
}));
const cartPayload = {
itemName: itemName,
itemPrice: itemPrice,
itemImage: itemImage,
section: section,
category: selectedCategory,
addons: addons,
instructions: instructions,
quantity: quantity
};
fetch('/cart/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(cartPayload)
})
.then(response => response.json())
.then(data => {
if (data.success) {
alert('Item added to cart successfully!');
updateCartUI(data.cart);
const modal = bootstrap.Modal.getInstance(document.getElementById('itemModal'));
modal.hide();
} else {
console.error('Failed to add item to cart:', data.error);
alert(data.error || 'Failed to add item to cart. Using local storage as fallback.');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = document.getElementById('itemModal');
const modalInstance = bootstrap.Modal.getInstance(modal);
modalInstance.hide();
}
})
.catch(err => {
console.error('Error adding item to cart:', err);
alert('Error adding item to cart. Using local storage as fallback.');
const cart = addToCartLocalStorage(cartPayload);
updateCartUI(cart);
const modal = document.getElementById('itemModal');
const modalInstance = bootstrap.Modal.getInstance(modal);
modalInstance.hide();
});
}
</script>
</body>
</html>