subbuok / templates /search.html
lokeshloki143's picture
Update templates/search.html
c307077 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Search 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 Placeholder Image -->
<link rel="preload" href="/static/placeholder.jpg" as="image">
<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-heading {
font-size: 2rem;
font-weight: 700;
color: #fff;
text-align: center;
padding: 15px 20px;
margin: 20px 0;
background: linear-gradient(45deg, #FFA07A, #FFB347);
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.2);
width: 100%;
max-width: 900px;
margin-left: auto;
margin-right: auto;
}
.menu-card {
max-width: 350px;
border-radius: 15px;
overflow: hidden;
background-color: #fff;
margin: auto;
display: flex;
flex-direction: column;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
cursor: pointer;
}
.card-img-container {
position: relative;
width: 100%;
height: 200px;
}
.card-img {
height: 100%;
width: 100%;
object-fit: cover;
border-radius: 15px 15px 0 0;
display: block;
}
.card-title {
position: absolute;
top: 10px;
left: 50%;
transform: translateX(-50%);
font-size: 1.2rem;
font-weight: 600;
color: #fff;
text-align: center;
margin: 0;
padding: 5px 10px;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 5px;
width: 90%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
z-index: 1;
}
.menu-card .card-body {
padding: 10px;
text-align: center;
}
.menu-card .card-body .card-text.section {
font-size: 0.9rem;
color: #6c757d;
text-align: center;
margin-bottom: 10px;
}
.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: linear-gradient(45deg, #FF4500, #000000, #1E90FF);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
font-weight: bold;
}
.dropdown-menu {
position: absolute;
right: 0;
top: 100%;
background-color: #fff8f0;
border-radius: 5px;
width: 220px;
box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
display: none;
border: 1px solid #ffd8b1;
z-index: 1000;
}
.dropdown-menu .dropdown-item {
padding: 12px 16px;
text-decoration: none;
color: #333;
border-bottom: 1px solid #ffd8b1;
display: flex;
align-items: center;
font-size: 15px;
transition: background-color 0.2s ease;
background: linear-gradient(45deg, #FF4500, #000000, #1E90FF);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.dropdown-menu .dropdown-item i {
margin-right: 10px;
font-size: 16px;
}
.dropdown-menu .dropdown-item:last-child {
border-bottom: none;
}
.dropdown-menu .dropdown-item:hover {
background-color: #ffe4c4;
color: #333;
-webkit-text-fill-color: #333;
}
.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;
}
.back-arrow-container {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
justify-content: center;
}
.back-arrow {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(45deg, #FFA07A, #FFB347);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 20px;
cursor: pointer;
transition: transform 0.2s ease, background-color 0.2s ease;
text-decoration: none;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.back-arrow:hover {
background: linear-gradient(45deg, #FF8C61, #FF9E2C);
transform: scale(1.1);
}
.back-arrow:active {
transform: scale(0.95);
}
.search-bar-container {
position: absolute;
left: 60px;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
width: 300px;
max-width: calc(90% - 60px);
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;
transition: border-bottom 0.3s ease;
}
.search-bar-container input:focus {
border-bottom: 2px solid #FFA07A;
}
.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;
}
.search-popup {
position: absolute;
top: 100%;
left: 0;
right: 0;
background-color: #fff;
border-radius: 5px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
max-height: 300px;
overflow-y: auto;
z-index: 1000;
display: none;
margin-top: 5px;
}
.search-popup-item {
display: flex;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
cursor: pointer;
transition: background-color 0.2s ease;
}
.search-popup-item:hover {
background-color: #f8f9fa;
}
.search-popup-item img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 5px;
margin-right: 10px;
}
.search-popup-item span {
font-size: 14px;
color: #333;
}
.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;
}
.no-results {
text-align: center;
font-size: 1.2rem;
color: #6c757d;
margin-top: 20px;
}
@media (max-width: 576px) {
.fixed-top-bar {
height: 60px;
padding: 10px;
}
.back-arrow-container {
left: 8px;
}
.back-arrow {
width: 32px;
height: 32px;
font-size: 18px;
}
.search-bar-container {
width: calc(80% - 50px);
max-width: calc(100% - 50px);
left: 50px;
}
.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: 220px;
}
.dropdown-menu .dropdown-item {
padding: 12px 16px;
font-size: 15px;
}
.menu-heading {
font-size: 1.5rem;
padding: 10px 15px;
margin: 15px 0;
}
.menu-card {
max-width: 100%;
}
.card-img-container {
height: 150px;
}
.card-title {
font-size: 1rem;
top: 8px;
padding: 4px 8px;
}
.menu-card .card-body .card-text.section {
font-size: 0.8rem;
}
.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;
}
.search-popup {
width: 100%;
}
.search-popup-item img {
width: 40px;
height: 40px;
}
.search-popup-item span {
font-size: 12px;
}
}
</style>
</head>
<body>
<div class="fixed-top-bar">
<div class="back-arrow-container">
<a href="{{ url_for('menu.menu') }}" class="back-arrow" aria-label="Back to Menu">
<i class="bi bi-arrow-left"></i>
</a>
</div>
<div class="avatar-dropdown-container">
<div class="avatar-icon">
<span>{{ first_letter }}</span>
</div>
<div class="dropdown-menu">
<a href="{{ url_for('user_details.customer_details') }}" class="dropdown-item"><i class="bi bi-person"></i> View Profile</a>
<a href="{{ url_for('orderhistory.order_history') }}" class="dropdown-item"><i class="bi bi-clock-history"></i> Order History</a>
<a href="{{ url_for('combined_summary.combined_summary') }}" class="dropdown-item"><i class="bi bi-file-earmark-text"></i> MY Summary</a>
<a href="{{ url_for('logout') }}" class="dropdown-item"><i class="bi bi-box-arrow-right"></i> 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>
<div class="search-popup" id="searchPopup"></div>
</div>
</div>
<!-- Timer and Success Message Container -->
<div id="orderTimerContainer" style="display: none; text-align: center; margin-top: 20px;">
<div id="orderTimer" style="font-size: 1.5rem; font-weight: bold; color: #FFA07A;"></div>
<div id="orderSuccessMessage" style="font-size: 1.5rem; font-weight: bold; color: #0FAA39; display: none;">Order Successfully Placed!</div>
</div>
<div class="container mt-4">
<h1 class="menu-heading">Search Menu Items</h1>
<div class="row" id="menuItems">
{% for section, items in ordered_menu.items() %}
{% for item in items %}
<div class="col-md-6 mb-4 menu-item" data-name="{{ item.Name | default('Unnamed Item') }}" data-section="{{ item.Section__c | default(section) }}">
<div class="card menu-card" onclick="selectItem('{{ item.Name | default('Unnamed Item') }}', '{{ item.Section__c | default(section) }}')">
<div class="card-img-container">
<img src="{{ item.Image1__c | default('/static/placeholder.jpg') }}" alt="{{ item.Name | default('Unnamed Item') }}" class="card-img" loading="eager" decoding="async" width="350" height="200">
<h5 class="card-title">{{ item.Name | default('Unnamed Item') }}</h5>
</div>
<div class="card-body">
<p class="card-text section">{{ item.Section__c | default(section) }}</p>
</div>
</div>
</div>
{% endfor %}
{% endfor %}
</div>
<div class="no-results" id="noResults" style="display: none;">
No items found matching your search.
</div>
</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 id="cart-item-count" class="cart-icon-badge" style="display: none;">0</span>
</a>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
const menuItems = [
{% for section, items in ordered_menu.items() %}
{% for item in items %}
{
name: "{{ item.Name | default('Unnamed Item') }}",
section: "{{ item.Section__c | default(section) }}",
image: "{{ item.Image1__c | default('/static/placeholder.jpg') }}"
},
{% endfor %}
{% endfor %}
];
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 getCartLocalStorage() {
return JSON.parse(localStorage.getItem('cart')) || [];
}
function selectItem(itemName, section) {
localStorage.setItem('selectedItem', JSON.stringify({ name: itemName, section: section }));
window.location.href = '/menu';
}
function filterMenuItems(query) {
const menuItemElements = document.querySelectorAll('.menu-item');
const noResults = document.getElementById('noResults');
let hasResults = false;
menuItemElements.forEach(item => {
const name = item.getAttribute('data-name').toLowerCase();
const section = item.getAttribute('data-section').toLowerCase();
const matches = name.includes(query.toLowerCase()) || section.includes(query.toLowerCase());
item.style.display = matches ? '' : 'none';
if (matches) hasResults = true;
});
noResults.style.display = hasResults ? 'none' : 'block';
}
function showSearchPopup(query) {
const searchPopup = document.getElementById('searchPopup');
searchPopup.innerHTML = '';
if (!query) {
searchPopup.style.display = 'none';
return;
}
const filteredItems = menuItems.filter(item =>
item.name.toLowerCase().includes(query.toLowerCase()) ||
item.section.toLowerCase().includes(query.toLowerCase())
);
if (filteredItems.length === 0) {
searchPopup.style.display = 'none';
return;
}
filteredItems.forEach(item => {
const popupItem = document.createElement('div');
popupItem.className = 'search-popup-item';
popupItem.innerHTML = `
<img src="${item.image}" alt="${item.name}">
<span>${item.name}</span>
`;
popupItem.addEventListener('click', () => {
selectItem(item.name, item.section);
});
searchPopup.appendChild(popupItem);
});
searchPopup.style.display = 'block';
}
// Timer and Success Message Functions
let orderTimerInterval;
let orderDurationInSeconds = 0; // Set your desired order completion time in seconds
function startOrderTimer(durationInSeconds) {
orderDurationInSeconds = durationInSeconds;
const timerElement = document.getElementById('orderTimer');
const successMessageElement = document.getElementById('orderSuccessMessage');
const timerContainer = document.getElementById('orderTimerContainer');
if (!timerElement || !successMessageElement || !timerContainer) {
console.error("Timer elements not found.");
return;
}
timerContainer.style.display = 'block';
successMessageElement.style.display = 'none';
let secondsRemaining = orderDurationInSeconds;
timerElement.innerText = formatTime(secondsRemaining);
timerElement.style.display = 'block'; // Ensure timer is visible when starting
orderTimerInterval = setInterval(() => {
secondsRemaining--;
if (secondsRemaining <= 0) {
clearInterval(orderTimerInterval);
timerElement.style.display = 'none';
successMessageElement.style.display = 'block';
// Optionally hide the container after a few seconds
setTimeout(() => {
timerContainer.style.display = 'none';
successMessageElement.style.display = 'none';
timerElement.style.display = 'block'; // Reset for next order
}, 5000); // Hide after 5 seconds
} else {
timerElement.innerText = formatTime(secondsRemaining);
}
}, 1000);
}
function formatTime(seconds) {
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
const formattedMinutes = String(minutes).padStart(2, '0');
const formattedSeconds = String(remainingSeconds).padStart(2, '0');
return `${formattedMinutes}:${formattedSeconds}`;
}
// Function to simulate placing a new order (replace with your actual order logic)
// This function should be called when a new order is successfully processed.
function handleNewOrderSuccess(estimatedCompletionTimeInSeconds) {
console.log("New order successful, starting timer...");
startOrderTimer(estimatedCompletionTimeInSeconds);
}
document.addEventListener('DOMContentLoaded', function () {
// Avatar Dropdown
const avatarContainer = document.querySelector('.avatar-dropdown-container');
const dropdownMenu = document.querySelector('.dropdown-menu');
avatarContainer.addEventListener('click', function (event) {
event.stopPropagation();
dropdownMenu.style.display = dropdownMenu.style.display === 'block' ? 'none' : 'block';
});
document.addEventListener('click', function (event) {
if (!avatarContainer.contains(event.target)) {
dropdownMenu.style.display = 'none';
}
});
const dropdownItems = document.querySelectorAll('.dropdown-item');
dropdownItems.forEach(item => {
item.addEventListener('click', function () {
dropdownMenu.style.display = 'none';
});
});
// Search Bar Functionality
const searchBar = document.getElementById('searchBar');
const searchPopup = document.getElementById('searchPopup');
const searchQuery = localStorage.getItem('searchQuery');
if (searchQuery) {
searchBar.value = searchQuery;
filterMenuItems(searchQuery);
showSearchPopup(searchQuery);
localStorage.removeItem('searchQuery');
}
searchBar.addEventListener('input', function () {
filterMenuItems(this.value);
showSearchPopup(this.value);
});
document.addEventListener('click', function (event) {
if (!searchBar.contains(event.target) && !searchPopup.contains(event.target)) {
searchPopup.style.display = 'none';
}
});
// Voice Recognition
const micIcon = document.getElementById('micIcon');
if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.lang = 'en-US';
recognition.onstart = () => micIcon.classList.add('active');
recognition.onresult = (event) => {
const query = event.results[0][0].transcript.trim();
searchBar.value = query;
filterMenuItems(query);
showSearchPopup(query);
};
recognition.onend = () => micIcon.classList.remove('active');
recognition.onerror = (event) => {
micIcon.classList.remove('active');
console.error('Speech error:', event.error);
};
micIcon.addEventListener('click', () => {
recognition.start();
});
} else {
micIcon.style.display = 'none';
}
// Fetch Cart
fetch('/cart/get')
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.success) {
updateCartUI(data.cart);
} else {
console.error('Failed to fetch cart:', data.error);
const cart = getCartLocalStorage();
updateCartUI(cart);
}
})
.catch(err => {
console.error('Error fetching cart:', err);
const cart = getCartLocalStorage();
updateCartUI(cart);
});
// --- Timer Logic Integration ---
// You need to call handleNewOrderSuccess() when a new order is successfully placed.
// This is where you'll connect this frontend timer to your backend order process.
// For demonstration purposes, let's simulate a new order after the page loads
// with a fixed duration (e.g., 30 seconds).
// In a real application, this call would happen after the user confirms an order
// on a different page (like a checkout or confirmation page).
// Example:
// handleNewOrderSuccess(30); // Simulate a new order with a 30-second timer
// You would likely pass the estimated time from your backend here.
// --- Persistence (Optional but Recommended) ---
// To make the timer persist across page loads, you can store the order's
// start time and estimated duration in local storage.
// When the page loads, check local storage for timer data and resume the timer
// if an active order exists.
const savedOrderTimer = localStorage.getItem('orderTimer');
if (savedOrderTimer) {
const timerData = JSON.parse(savedOrderTimer);
const now = Date.now();
const elapsed = Math.floor((now - timerData.startTime) / 1000);
const remaining = timerData.duration - elapsed;
if (remaining > 0) {
startOrderTimer(remaining);
} else {
// Timer has already finished, show success message briefly
const successMessageElement = document.getElementById('orderSuccessMessage');
const timerContainer = document.getElementById('orderTimerContainer');
timerContainer.style.display = 'block';
successMessageElement.style.display = 'block';
setTimeout(() => {
timerContainer.style.display = 'none';
successMessageElement.style.display = 'none';
}, 3000); // Show success for 3 seconds
localStorage.removeItem('orderTimer'); // Clear finished timer data
}
}
});
// --- Add this to your order placement success logic ---
// When you successfully place a new order (e.g., after a fetch/API call),
// call this function to start the timer and save its state.
function handleOrderPlacedAndStartTimer(estimatedCompletionTimeInSeconds) {
const startTime = Date.now();
localStorage.setItem('orderTimer', JSON.stringify({
startTime: startTime,
duration: estimatedCompletionTimeInSeconds
}));
startOrderTimer(estimatedCompletionTimeInSeconds);
}
</script>
</body>
</html>