undefined / index.html
Kohiii-san's picture
make the code more mobile compatible
f6ed6e5 verified
Raw
History Blame Contribute Delete
139 kB
<!DOCTYPE html>
<html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UniGrub - Campus Canteen System</title>
<style>
/*
===========================================
UNIGRUB CAMPUS CANTEEN ORDERING SYSTEM
===========================================
RESET INSTRUCTIONS:
To reset all data, open browser console (F12) and run:
localStorage.clear(); location.reload();
SECURITY NOTICE:
This is a DEMO application. All data is stored in browser localStorage.
Passwords are NOT encrypted. DO NOT use real passwords.
This app is for educational/prototyping purposes only.
DEFAULT CREDENTIALS:
Student: john / student123
Vendor: andrea / pizza123
Admin: admin / admin123
===========================================
*/
:root {
--primary: #4CAF50; /* CHANGED TO GREEN */
--primary-dark: #388E3C; /* CHANGED TO GREEN */
--primary-glass: rgba(76, 175, 80, 0.15); /* CHANGED TO GREEN */
--primary-dots: rgba(76, 175, 80, 0.08); /* CHANGED TO GREEN */
--primary-shadow: rgba(76, 175, 80, 0.2); /* CHANGED TO GREEN */
--dark-card: rgba(30, 30, 30, 0.55);
--text: #FCFBFC;
--text-secondary: #b0b0b0;
--success: #4CAF50;
--danger: #f44336;
--glass-border: rgba(76, 175, 80, 0.3); /* CHANGED TO GREEN */
}
* { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Segoe UI', sans-serif; }
body {
background: radial-gradient(circle at 20% 30%, var(--primary-glass) 0%, transparent 50%),
linear-gradient(135deg, #1a1a1a 0%, #232323 40%, #1a1a1a 100%);
background-attachment: fixed;
color: var(--text);
min-height: 100vh;
position: relative;
transition: all 0.3s ease;
}
/* Dotty Background Overlay */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
repeating-linear-gradient(90deg, transparent, transparent 2px, var(--primary-dots) 2px, var(--primary-dots) 4px),
repeating-linear-gradient(0deg, transparent, transparent 2px, var(--primary-dots) 2px, var(--primary-dots) 4px);
opacity: 0.6;
pointer-events: none;
z-index: 0;
transition: all 0.3s ease;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 16px;
position: relative;
z-index: 1;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 0;
border-bottom: 1px solid var(--primary);
margin-bottom: 32px;
flex-wrap: wrap;
}
.logo {
display: flex;
align-items: center;
gap: 12px;
font-size: 20px;
font-weight: bold;
color: var(--primary);
margin-bottom: 8px;
}
.logo-img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--primary);
}
nav {
width: 100%;
margin-top: 8px;
}
nav ul {
display: flex;
list-style: none;
gap: 12px;
align-items: center;
flex-wrap: wrap;
justify-content: center;
}
nav a {
color: var(--text);
text-decoration: none;
padding: 8px 12px;
border-radius: 20px;
background: var(--dark-card);
border: 1px solid var(--glass-border);
transition: all 0.3s;
display: inline-block;
font-size: 14px;
}
nav a:hover { background-color: var(--primary); color: #121212; }
.card {
background: var(--dark-card);
backdrop-filter: blur(8px);
border-radius: 12px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
border: 1px solid var(--glass-border);
margin-bottom: 20px;
transition: all 0.3s;
}
.card:hover { transform: translateY(-5px); box-shadow: 0 8px 25px var(--primary-shadow); }
.btn {
padding: 14px 20px;
font-size: 16px;
border: none;
border-radius: 10px;
cursor: pointer;
color: #fff;
background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
transition: all 0.3s;
font-weight: 600;
display: inline-block;
text-align: center;
min-height: 50px;
display: flex;
align-items: center;
justify-content: center;
}
.btn:hover { transform: translateY(-3px); }
.btn-secondary { background-color: transparent; color: var(--text); border: 1px solid var(--primary); }
.btn-danger { background-color: var(--danger); }
.btn-success { background-color: var(--success); }
.btn-small { padding: 10px 16px; font-size: 14px; min-height: 40px; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 8px; color: var(--text-secondary); font-weight: 500; }
input, select, textarea {
width: 100%;
padding: 14px;
background-color: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 8px;
color: var(--text);
font-size: 16px;
-webkit-appearance: none;
}
select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%234CAF50' d='M6 9L1 4h10z'/%3E%3C/svg%3E"); /* CHANGED TO GREEN */
background-repeat: no-repeat;
background-position: right 16px center;
padding-right: 40px;
}
select option {
background-color: #2a2a2a;
color: var(--text);
padding: 12px;
}
textarea { resize: vertical; min-height: 80px; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary-shadow); }
.page { display: none; animation: fadeIn 0.5s; }
.page.active { display: block; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
.role-selection {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
margin: 32px 0;
flex-wrap: wrap;
}
.role-card {
width: 100%;
max-width: 280px;
text-align: center;
padding: 24px;
cursor: pointer;
margin-bottom: 16px;
}
.role-card:hover { transform: translateY(-10px); }
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 20px;
margin-top: 20px;
}
.stall-logo {
width: 100%;
height: 160px;
object-fit: cover;
border-radius: 8px;
margin: 0 auto 16px;
display: block;
}
.stall-logo-placeholder {
width: 100%;
height: 160px;
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
margin: 0 auto 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
font-weight: bold;
color: #1a1a1a;
border-radius: 8px;
}
/* Square logo for stall profile */
.stall-logo-square {
width: 160px;
height: 160px;
object-fit: cover;
border-radius: 8px;
margin: 0 auto 16px;
display: block;
}
.stall-logo-placeholder-square {
width: 160px;
height: 160px;
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
margin: 0 auto 16px;
display: flex;
align-items: center;
justify-content: center;
font-size: 36px;
font-weight: bold;
color: #1a1a1a;
border-radius: 8px;
}
.menu-item {
display: flex;
gap: 16px;
padding: 20px;
border-radius: 8px;
background-color: rgba(255, 255, 255, 0.05);
flex-direction: column;
}
.menu-item-image {
position: relative;
width: 100%;
height: 140px;
border-radius: 8px;
overflow: hidden;
flex-shrink: 0;
}
.menu-item-image img { width: 100%; height: 100%; object-fit: cover; }
/* Left-to-Right Gradient Overlay (90% → 0% opacity) */
.menu-item-image::after {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(to right, rgba(0, 0, 0, 0.9) 0%, rgba(0, 0, 0, 0) 100%);
pointer-events: none;
}
.menu-item-info { flex: 1; }
.menu-item-name { font-weight: bold; margin-bottom: 8px; font-size: 18px; }
.menu-item-price { color: var(--primary); font-weight: bold; font-size: 16px; margin-bottom: 8px; }
.menu-item-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 12px;
}
.section-title { font-size: 22px; color: var(--primary); margin-bottom: 12px; }
.section-subtitle { font-size: 16px; color: var(--text-secondary); margin-bottom: 20px; }
.text-center { text-align: center; }
.vendor-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
margin-bottom: 12px;
background: var(--dark-card);
border-radius: 8px;
border: 1px solid var(--glass-border);
flex-direction: column;
gap: 16px;
}
.vendor-info { width: 100%; }
.vendor-info h4 { color: var(--primary); margin-bottom: 8px; font-size: 18px; }
.vendor-actions { display: flex; gap: 8px; width: 100%; justify-content: center; }
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(5px);
z-index: 1000;
align-items: center;
justify-content: center;
animation: fadeIn 0.3s;
padding: 16px;
box-sizing: border-box;
}
.modal-content {
background-color: var(--dark-card);
backdrop-filter: blur(8px);
border-radius: 12px;
padding: 24px;
width: 100%;
max-width: 500px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
position: relative;
border: 1px solid var(--glass-border);
max-height: 90vh;
overflow-y: auto;
animation: scaleIn 0.3s;
}
@keyframes scaleIn {
from { transform: scale(0.9); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
.close-modal {
position: absolute;
top: 12px;
right: 12px;
font-size: 24px;
cursor: pointer;
color: var(--text-secondary);
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
}
.close-modal:hover { color: var(--text); }
.order-status { padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; }
.status-pending { background: rgba(255, 193, 7, 0.2); color: #ffc107; }
.status-preparing { background: rgba(33, 150, 243, 0.2); color: #2196F3; }
.status-ready { background: rgba(76, 175, 80, 0.2); color: #4CAF50; }
.status-completed { background: rgba(156, 39, 176, 0.2); color: #9C27B0; }
.qr-preview {
width: 160px;
height: 160px;
margin: 0 auto 20px;
background: rgba(255, 255, 255, 0.1);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
border: 2px dashed var(--glass-border);
}
.qr-preview img { max-width: 100%; max-height: 100%; border-radius: 4px; }
.cart-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 14px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
flex-direction: column;
gap: 12px;
}
.cart-total {
font-size: 22px;
font-weight: bold;
color: var(--primary);
margin: 20px 0;
text-align: center;
}
.intro {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #1a1a1a 0%, #232323 40%, #1a1a1a 100%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
animation: fadeOut 1s ease 2s forwards;
padding: 16px;
text-align: center;
}
.intro-logo {
font-size: 36px;
display: flex;
align-items: center;
gap: 12px;
color: var(--primary);
animation: pulse 2s infinite;
flex-direction: column;
}
@keyframes fadeOut { to { opacity: 0; visibility: hidden; pointer-events: none; } }
@keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.05); } }
.toast {
position: fixed;
bottom: 20px;
right: 20px;
left: 20px;
background: var(--dark-card);
backdrop-filter: blur(8px);
border: 1px solid var(--glass-border);
border-radius: 8px;
padding: 14px 20px;
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.5);
z-index: 10000;
animation: slideIn 0.3s;
max-width: 400px;
margin: 0 auto;
}
@keyframes slideIn { from { transform: translateX(400px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
.toast.success { border-left: 4px solid var(--success); }
.toast.error { border-left: 4px solid var(--danger); }
.toast.info { border-left: 4px solid #2196F3; }
.size-price-list {
margin: 14px 0;
padding: 14px;
background: rgba(255, 255, 255, 0.05);
border-radius: 8px;
}
.size-price-item {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.size-price-item:last-child {
border-bottom: none;
}
.item-detail-modal .modal-content {
max-width: 600px;
}
.item-detail-image {
width: 100%;
height: 200px;
object-fit: cover;
border-radius: 8px;
margin-bottom: 20px;
}
.price-display {
font-size: 28px;
color: var(--primary);
font-weight: bold;
text-align: center;
margin: 20px 0;
}
/* Progress tracker styles */
.progress-tracker {
display: flex;
justify-content: space-between;
align-items: center;
margin: 16px 0;
position: relative;
flex-wrap: wrap;
}
.progress-tracker::before {
content: '';
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 4px;
background: rgba(255, 255, 255, 0.1);
transform: translateY(-50%);
z-index: 1;
}
.progress-bar {
position: absolute;
top: 50%;
left: 0;
height: 4px;
background: var(--primary);
transform: translateY(-50%);
z-index: 2;
transition: width 0.5s ease;
}
.progress-step {
position: relative;
z-index: 3;
display: flex;
flex-direction: column;
align-items: center;
flex: 1;
min-width: 70px;
margin-bottom: 8px;
}
.progress-step:last-child {
flex: 0;
}
.step-indicator {
width: 28px;
height: 28px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 6px;
transition: all 0.3s;
font-size: 14px;
}
.step-indicator.active {
background: var(--primary);
color: #1a1a1a;
}
.step-indicator.completed {
background: var(--success);
color: #fff;
}
.step-label {
font-size: 11px;
text-align: center;
color: var(--text-secondary);
}
.step-label.active {
color: var(--primary);
font-weight: bold;
}
.step-label.completed {
color: var(--success);
}
/* Mobile menu styles - NEW FEATURE MERGED */
.mobile-menu-btn {
display: none;
background: none;
border: none;
color: var(--text);
font-size: 24px;
cursor: pointer;
padding: 8px;
}
.mobile-nav {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(26, 26, 26, 0.95);
z-index: 1000;
flex-direction: column;
justify-content: center;
align-items: center;
}
.mobile-nav.active {
display: flex;
}
.mobile-nav ul {
list-style: none;
display: flex;
flex-direction: column;
gap: 20px;
align-items: center;
}
.mobile-nav a {
color: var(--text);
text-decoration: none;
font-size: 18px;
padding: 12px 24px;
border-radius: 30px;
background: var(--dark-card);
border: 1px solid var(--glass-border);
transition: all 0.3s;
display: block;
text-align: center;
min-width: 200px;
}
.mobile-nav a:hover {
background: var(--primary);
color: #1a1a1a;
}
.close-mobile-nav {
position: absolute;
top: 20px;
right: 20px;
background: none;
border: none;
color: var(--text);
font-size: 28px;
cursor: pointer;
}
/* Color scheme styles - NEW FEATURE MERGED */
.color-scheme-selector {
display: flex;
gap: 12px;
margin: 20px 0;
flex-wrap: wrap;
justify-content: center;
}
.color-option {
width: 40px;
height: 40px;
border-radius: 50%;
cursor: pointer;
border: 3px solid transparent;
transition: all 0.3s;
}
.color-option.active {
border-color: white;
transform: scale(1.1);
}
.color-option:hover {
transform: scale(1.1);
}
/* Theme button in navigation - NEW FEATURE MERGED */
.theme-btn {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--dark-card);
border: 1px solid var(--glass-border);
color: var(--primary);
cursor: pointer;
font-size: 18px;
transition: all 0.3s;
}
.theme-btn:hover {
background: var(--primary);
color: #121212;
}
/* Login form button alignment fix */
.login-form-buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.login-form-buttons .btn {
flex: 1;
min-width: 120px;
}
/* Navigation alignment fix */
.nav-buttons {
display: flex;
gap: 12px;
align-items: center;
}
/* Responsive adjustments */
@media (min-width: 768px) {
.container { padding: 24px; }
header {
padding: 24px 0;
margin-bottom: 48px;
flex-wrap: nowrap;
}
.logo {
gap: 16px;
font-size: 24px;
margin-bottom: 0;
}
.logo-img {
width: 50px;
height: 50px;
}
nav {
width: auto;
margin-top: 0;
}
nav ul {
gap: 24px;
justify-content: flex-end;
}
nav a {
padding: 8px 16px;
font-size: 16px;
}
.card {
padding: 32px;
margin-bottom: 24px;
}
.btn {
padding: 16px 24px;
min-height: auto;
}
.btn-small { padding: 8px 16px; font-size: 14px; min-height: auto; }
.role-selection {
gap: 24px;
margin: 48px 0;
}
.role-card {
width: 220px;
padding: 32px;
margin-bottom: 0;
}
.grid {
gap: 24px;
margin-top: 24px;
}
.stall-logo {
height: 200px;
}
.stall-logo-placeholder {
height: 200px;
font-size: 48px;
}
.stall-logo-square {
width: 200px;
height: 200px;
}
.stall-logo-placeholder-square {
width: 200px;
height: 200px;
font-size: 48px;
}
.menu-item {
flex-direction: row;
gap: 24px;
padding: 24px;
}
.menu-item-image {
width: 96px;
height: 120px;
}
.section-header {
margin-bottom: 24px;
gap: 16px;
}
.section-title { font-size: 24px; margin-bottom: 16px; }
.section-subtitle { font-size: 18px; margin-bottom: 24px; }
.vendor-item {
flex-direction: row;
padding: 24px;
margin-bottom: 16px;
gap: 0;
}
.vendor-info { width: auto; }
.vendor-actions { width: auto; justify-content: flex-end; }
.modal-content {
padding: 32px;
}
.close-modal {
top: 15px;
right: 15px;
}
.qr-preview {
width: 200px;
height: 200px;
margin: 0 auto 24px;
}
.cart-item {
flex-direction: row;
padding: 16px 0;
gap: 0;
}
.cart-total {
font-size: 24px;
margin: 24px 0;
text-align: right;
}
.intro-logo {
font-size: 48px;
flex-direction: row;
gap: 15px;
}
.toast {
bottom: 30px;
right: 30px;
left: auto;
max-width: none;
}
.progress-tracker {
margin: 20px 0;
flex-wrap: nowrap;
}
.progress-step {
min-width: auto;
margin-bottom: 0;
}
.step-indicator {
width: 30px;
height: 30px;
margin-bottom: 8px;
font-size: 16px;
}
.step-label {
font-size: 12px;
}
.login-form-buttons {
flex-wrap: nowrap;
}
.nav-buttons {
gap: 16px;
}
}
@media (max-width: 767px) {
.mobile-menu-btn {
display: block;
}
.desktop-nav {
display: none;
}
.nav-buttons {
flex-direction: column;
gap: 8px;
width: 100%;
}
.nav-buttons a {
width: 100%;
text-align: center;
}
}
@media (max-width: 480px) {
.container { padding: 12px; }
.card {
padding: 20px;
margin-bottom: 16px;
}
.role-card {
padding: 20px;
}
.grid {
grid-template-columns: 1fr;
gap: 16px;
margin-top: 16px;
}
.menu-item {
padding: 16px;
}
.section-header {
margin-bottom: 16px;
}
.vendor-item {
padding: 16px;
}
.modal-content {
padding: 20px;
}
.progress-step {
min-width: 60px;
}
.step-indicator {
width: 24px;
height: 24px;
font-size: 12px;
}
.step-label {
font-size: 10px;
}
.login-form-buttons {
flex-direction: column;
}
.login-form-buttons .btn {
width: 100%;
}
}
</style>
</head>
<body>
<div class="intro">
<div class="intro-logo"><span>🍽️</span><h1>UniGrub</h1></div>
<p>Campus Canteen Ordering System</p>
</div>
<div class="container">
<header>
<div class="logo"><span>🍽️</span><h1>UniGrub</h1></div>
<!-- NEW FEATURE MERGED: Mobile menu button -->
<button class="mobile-menu-btn" id="mobile-menu-toggle"></button>
<nav class="desktop-nav">
<ul>
<li><a href="#" id="nav-home">Home</a></li>
<li><a href="#" id="nav-back" style="display: none;">Back</a></li>
<li class="nav-buttons">
<a href="#" id="nav-login">Login</a>
<a href="#" id="nav-logout" style="display: none;">Logout</a>
<!-- NEW FEATURE MERGED: Theme button -->
<div class="theme-btn" id="theme-toggle">🎨</div>
</li>
</ul>
</nav>
</header>
<!-- NEW FEATURE MERGED: Mobile navigation -->
<div class="mobile-nav" id="mobile-nav">
<button class="close-mobile-nav" id="close-mobile-nav"></button>
<ul>
<li><a href="#" id="mobile-nav-home">Home</a></li>
<li><a href="#" id="mobile-nav-back" style="display: none;">Back</a></li>
<li><a href="#" id="mobile-nav-login">Login</a></li>
<li><a href="#" id="mobile-nav-logout" style="display: none;">Logout</a></li>
<li><a href="#" id="mobile-theme-toggle">Change Theme</a></li>
</ul>
</div>
<main>
<div id="home-page" class="page active">
<div class="card text-center">
<h2 class="section-title">Welcome to UniGrub</h2>
<p class="section-subtitle">Your campus canteen ordering solution</p>
<div class="role-selection">
<div class="card role-card" id="student-role">
<div style="font-size: 48px; margin-bottom: 24px;">👨‍🎓</div>
<h3>Student</h3>
<p>Browse menus and place orders</p>
</div>
<div class="card role-card" id="vendor-role">
<div style="font-size: 48px; margin-bottom: 24px;">🏪</div>
<h3>Vendor</h3>
<p>Manage your stall and orders</p>
</div>
<div class="card role-card" id="admin-role">
<div style="font-size: 48px; margin-bottom: 24px;">🧑‍💼</div>
<h3>Admin</h3>
<p>Manage vendors and system</p>
</div>
</div>
</div>
</div>
<!-- STUDENT LOGIN -->
<div id="student-login-page" class="page">
<div class="card">
<h2 class="section-title">Student Login</h2>
<form id="student-login-form">
<div class="form-group"><label>Username</label><input type="text" id="student-username" required></div>
<div class="form-group"><label>Password</label><input type="password" id="student-password" required></div>
<div class="form-group"><label><input type="checkbox" id="student-remember" style="width: auto;"> Remember Me</label></div>
<div class="login-form-buttons">
<button type="submit" class="btn">Login</button>
<button type="button" class="btn btn-secondary" id="student-register">Register</button>
</div>
</form>
</div>
</div>
<!-- STUDENT DASHBOARD -->
<div id="student-dashboard" class="page">
<div class="card">
<h2 class="section-title">Welcome, <span id="student-name"></span>!</h2>
<p class="section-subtitle">Choose a canteen to start ordering</p>
<div class="role-selection">
<div class="card canteen-card" data-canteen="upper" style="text-align: center; cursor: pointer; width: 100%; max-width: 280px;">
<div style="font-size: 48px; margin-bottom: 24px;">🏫</div>
<h3>Upper Campus</h3>
</div>
<div class="card canteen-card" data-canteen="lower" style="text-align: center; cursor: pointer; width: 100%; max-width: 280px;">
<div style="font-size: 48px; margin-bottom: 24px;">🏢</div>
<h3>Lower Campus</h3>
</div>
<div class="card canteen-card" data-canteen="market" style="text-align: center; cursor: pointer; width: 100%; max-width: 280px;">
<div style="font-size: 48px; margin-bottom: 24px;">🏪</div>
<h3>Market</h3>
</div>
</div>
<div class="section-header" style="margin-top: 32px;">
<h3 class="section-title">Your Orders</h3>
<button class="btn btn-small" id="view-all-orders">View All Orders</button>
</div>
</div>
</div>
<!-- NEW FEATURE MERGED: Color Scheme Modal -->
<div id="color-scheme-modal" class="modal">
<div class="modal-content">
<span class="close-modal">&times;</span>
<h2 class="section-title">Choose Color Scheme</h2>
<p>Select your preferred primary color:</p>
<div class="color-scheme-selector">
<div class="color-option active" data-color="#4CAF50" style="background-color: #4CAF50;"></div> <!-- CHANGED TO GREEN -->
<div class="color-option" data-color="#3D52A0" style="background-color: #3D52A0;"></div>
<div class="color-option" data-color="#C41E3A" style="background-color: #C41E3A;"></div>
<div class="color-option" data-color="#00563B" style="background-color: #00563B;"></div>
<div class="color-option" data-color="#F5FEFD" style="background-color: #F5FEFD;"></div>
</div>
<button class="btn" id="save-color-scheme" style="margin-top: 20px;">Save Changes</button>
</div>
</div>
<!-- STUDENT ORDERS PAGE -->
<div id="student-orders-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">Your Orders</h2>
<button class="btn btn-small" id="back-to-student-dashboard">Back to Dashboard</button>
</div>
<div id="student-orders-container"></div>
</div>
</div>
<!-- CANTEEN STALLS -->
<div id="canteen-stalls-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title" id="canteen-title">Stalls</h2>
<button class="btn btn-small" id="back-to-student-dashboard-from-stalls">Back</button>
</div>
<div class="grid" id="stalls-container"></div>
</div>
</div>
<!-- STALL MENU -->
<div id="stall-menu-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title" id="stall-menu-title">Menu</h2>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn btn-small btn-secondary" id="view-cart-btn">Cart (<span id="cart-count">0</span>)</button>
<button class="btn btn-small" id="back-to-stalls">Back</button>
</div>
</div>
<div class="grid" id="stall-menu-container"></div>
</div>
</div>
<!-- CART -->
<div id="student-cart-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">Your Cart</h2>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn btn-small" id="back-to-menu">Continue Shopping</button>
<button class="btn btn-small" id="back-to-stalls-from-cart">Back to Stalls</button>
</div>
</div>
<div id="cart-items-container"></div>
<div class="cart-total" id="cart-total">Total: ₱0.00</div>
<button class="btn" id="checkout-btn">Proceed to Checkout</button>
</div>
</div>
<!-- CHECKOUT -->
<div id="checkout-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">Checkout</h2>
<button class="btn btn-small" id="back-to-cart">Back to Cart</button>
</div>
<div id="checkout-summary"></div>
<div class="form-group"><label>Order Type</label><select id="order-type"><option value="pickup">Pick Up</option><option value="delivery">Delivery</option></select></div>
<div id="delivery-details" style="display: none;">
<div class="form-group"><label>Name</label><input type="text" id="delivery-name"></div>
<div class="form-group"><label>Location</label><input type="text" id="delivery-location"></div>
<div class="form-group"><label>Contact</label><input type="tel" id="delivery-contact" placeholder="09XXXXXXXXX"></div>
</div>
<div class="form-group"><label>Payment</label><select id="payment-method"><option value="cash">Cash</option><option value="gcash">GCash</option></select></div>
<div id="gcash-details" style="display: none; text-align: center;">
<h3>Scan to Pay</h3>
<div class="qr-preview" id="vendor-qr-preview"><span>QR Code will appear here</span></div>
</div>
<button class="btn" id="place-order-btn">Place Order</button>
</div>
</div>
<!-- VENDOR LOGIN -->
<div id="vendor-login-page" class="page">
<div class="card">
<h2 class="section-title">Vendor Login</h2>
<form id="vendor-login-form">
<div class="form-group"><label>Username</label><input type="text" id="vendor-username" required></div>
<div class="form-group"><label>Password</label><input type="password" id="vendor-password" required></div>
<button type="submit" class="btn">Login</button>
</form>
</div>
</div>
<!-- VENDOR DASHBOARD -->
<div id="vendor-dashboard" class="page">
<div class="card">
<div style="display: flex; align-items: center; gap: 16px; margin-bottom: 24px; flex-wrap: wrap;">
<img id="vendor-dash-logo" class="logo-img" style="display: none;">
<div id="vendor-dash-logo-placeholder" class="stall-logo-placeholder" style="display: none; margin: 0; width: 50px; height: 50px; font-size: 24px;"></div>
<div>
<h2 class="section-title" style="margin: 0;">Vendor Dashboard</h2>
<p class="section-subtitle" style="margin: 0;">Welcome, <span id="vendor-stall-name"></span></p>
</div>
</div>
<div class="grid">
<div class="card"><h3>Stall Profile</h3><button class="btn" id="manage-profile">Manage</button></div>
<div class="card"><h3>Menu</h3><button class="btn" id="manage-menu">Manage</button></div>
<div class="card"><h3>GCash QR</h3><button class="btn" id="manage-qr">Upload</button></div>
<div class="card"><h3>Orders</h3><button class="btn" id="view-orders">View</button></div>
</div>
</div>
</div>
<!-- VENDOR PROFILE MANAGEMENT -->
<div id="vendor-profile-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">Stall Profile</h2>
<button class="btn btn-small" id="back-to-vendor-dashboard-profile">Back</button>
</div>
<div style="text-align: center; margin-bottom: 24px;">
<img id="profile-logo-preview" class="stall-logo-square" style="display: none;">
<div id="profile-logo-placeholder" class="stall-logo-placeholder-square"></div>
</div>
<div class="form-group">
<label>Stall Logo (Square image recommended)</label>
<input type="file" id="logo-upload" accept="image/*">
</div>
<button class="btn" id="save-logo">Save Logo</button>
</div>
</div>
<!-- VENDOR MENU MANAGEMENT -->
<div id="vendor-menu-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">Menu Management</h2>
<button class="btn btn-small" id="back-to-vendor-dashboard">Back</button>
</div>
<button class="btn" id="add-menu-item-btn">Add Item</button>
<div class="grid" id="vendor-menu-container"></div>
</div>
</div>
<!-- VENDOR ORDERS -->
<div id="vendor-orders-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">Orders</h2>
<button class="btn btn-small" id="back-to-vendor-dashboard-orders">Back</button>
</div>
<div id="vendor-orders-container"></div>
</div>
</div>
<!-- VENDOR QR -->
<div id="vendor-qr-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">GCash QR</h2>
<button class="btn btn-small" id="back-to-vendor-dashboard-qr">Back</button>
</div>
<div style="text-align: center;">
<div class="qr-preview" id="current-qr-preview"><span>No QR uploaded</span></div>
<div class="form-group"><label>Upload QR</label><input type="file" id="qr-upload" accept="image/*"></div>
<button class="btn" id="save-qr">Save</button>
</div>
</div>
</div>
<!-- ADMIN LOGIN -->
<div id="admin-login-page" class="page">
<div class="card">
<h2 class="section-title">Admin Login</h2>
<form id="admin-login-form">
<div class="form-group"><label>Username</label><input type="text" id="admin-username" required></div>
<div class="form-group"><label>Password</label><input type="password" id="admin-password" required></div>
<button type="submit" class="btn">Login</button>
</form>
</div>
</div>
<!-- ADMIN DASHBOARD -->
<div id="admin-dashboard" class="page">
<div class="card">
<h2 class="section-title">Admin Dashboard</h2>
<div class="grid">
<div class="card"><h3>Vendors</h3><button class="btn" id="manage-vendors">Manage</button></div>
<div class="card"><h3>All Menus</h3><button class="btn" id="view-all-menus">View</button></div>
<div class="card"><h3>Settings</h3><button class="btn" id="system-settings">Configure</button></div>
</div>
</div>
</div>
<!-- VENDOR MANAGEMENT -->
<div id="vendor-management-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">Vendor Management</h2>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button class="btn btn-small" id="back-to-admin-dashboard">Back</button>
<button class="btn btn-small" id="add-vendor-btn">Add Vendor</button>
</div>
</div>
<div id="vendor-list"></div>
</div>
</div>
<!-- ALL MENUS -->
<div id="view-all-menus-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">All Menus</h2>
<button class="btn btn-small" id="back-to-admin-dashboard-menus">Back</button>
</div>
<div id="all-menus-container"></div>
</div>
</div>
<!-- SYSTEM SETTINGS -->
<div id="system-settings-page" class="page">
<div class="card">
<div class="section-header">
<h2 class="section-title">System Settings</h2>
<button class="btn btn-small" id="back-to-admin-dashboard-settings">Back</button>
</div>
<div class="form-group"><label>System Name</label><input type="text" id="system-name" value="UniGrub"></div>
<div class="form-group"><label>Order Timeout (min)</label><input type="number" id="order-timeout" value="30"></div>
<div class="form-group">
<label>Delete Vendor Behavior</label>
<select id="delete-vendor-behavior">
<option value="preserve">Preserve Orders</option>
<option value="remove">Remove Orders</option>
</select>
</div>
<button class="btn" id="save-settings">Save Settings</button>
</div>
</div>
</main>
</div>
<!-- MODALS -->
<div id="order-success-modal" class="modal">
<div class="modal-content" style="text-align: center;">
<span class="close-modal">&times;</span>
<div style="font-size: 64px; color: var(--success); margin-bottom: 24px;"></div>
<h2 style="color: var(--success); margin-bottom: 16px;">Order Placed!</h2>
<p style="margin-bottom: 32px;">Your order is being processed.</p>
<button class="btn" id="close-success-modal">Close</button>
</div>
</div>
<div id="add-vendor-modal" class="modal">
<div class="modal-content">
<span class="close-modal">&times;</span>
<h2 class="section-title">Add Vendor</h2>
<form id="add-vendor-form">
<div class="form-group"><label>Stall Name</label><input type="text" id="new-vendor-stall-name" required></div>
<div class="form-group"><label>Username (lowercase)</label><input type="text" id="new-vendor-username" required></div>
<div class="form-group"><label>Password (lowercase)</label><input type="password" id="new-vendor-password" required></div>
<div class="form-group"><label>Canteen</label><select id="new-vendor-canteen"><option value="upper">Upper</option><option value="lower">Lower</option><option value="market">Market</option></select></div>
<button type="submit" class="btn">Add</button>
</form>
</div>
</div>
<div id="edit-vendor-modal" class="modal">
<div class="modal-content">
<span class="close-modal">&times;</span>
<h2 class="section-title">Edit Vendor</h2>
<form id="edit-vendor-form">
<input type="hidden" id="edit-vendor-id">
<div class="form-group"><label>Stall Name</label><input type="text" id="edit-vendor-stall-name" required></div>
<div class="form-group"><label>Username (lowercase)</label><input type="text" id="edit-vendor-username" required></div>
<div class="form-group"><label>Password (lowercase)</label><input type="password" id="edit-vendor-password" required></div>
<div class="form-group"><label>Canteen</label><select id="edit-vendor-canteen"><option value="upper">Upper</option><option value="lower">Lower</option><option value="market">Market</option></select></div>
<button type="submit" class="btn">Update</button>
</form>
</div>
</div>
<div id="add-menu-item-modal" class="modal">
<div class="modal-content">
<span class="close-modal">&times;</span>
<h2 class="section-title">Add Menu Item</h2>
<form id="add-menu-item-form">
<div class="form-group"><label>Name</label><input type="text" id="item-name" required></div>
<div class="form-group"><label>Description</label><textarea id="item-description"></textarea></div>
<div class="form-group"><label>Flavors (comma separated, optional)</label><input type="text" id="item-flavors" placeholder="e.g., Original, Spicy, Cheese"></div>
<div class="form-group">
<label>Size Prices (format: Size:Price, comma separated)</label>
<input type="text" id="item-size-prices" placeholder="e.g., Small:50, Medium:70, Large:90" required>
<small style="color: var(--text-secondary); display: block; margin-top: 8px;">Example: Small:50, Medium:70, Large:90</small>
</div>
<div class="form-group"><label>Image</label><input type="file" id="item-image" accept="image/*"></div>
<button type="submit" class="btn">Add</button>
</form>
</div>
</div>
<div id="edit-menu-item-modal" class="modal">
<div class="modal-content">
<span class="close-modal">&times;</span>
<h2 class="section-title">Edit Menu Item</h2>
<form id="edit-menu-item-form">
<input type="hidden" id="edit-item-id">
<div class="form-group"><label>Name</label><input type="text" id="edit-item-name" required></div>
<div class="form-group"><label>Description</label><textarea id="edit-item-description"></textarea></div>
<div class="form-group"><label>Flavors (comma separated, optional)</label><input type="text" id="edit-item-flavors"></div>
<div class="form-group">
<label>Size Prices (format: Size:Price, comma separated)</label>
<input type="text" id="edit-item-size-prices" required>
</div>
<div class="form-group"><label>Image</label><input type="file" id="edit-item-image" accept="image/*"></div>
<button type="submit" class="btn">Update</button>
</form>
</div>
</div>
<div id="item-detail-modal" class="modal item-detail-modal">
<div class="modal-content">
<span class="close-modal">&times;</span>
<img id="detail-item-image" class="item-detail-image" style="display: none;">
<h2 id="detail-item-name" class="section-title"></h2>
<p id="detail-item-description" style="color: var(--text-secondary); margin-bottom: 16px;"></p>
<div class="form-group" id="flavor-group" style="display: none;">
<label>Select Flavor</label>
<select id="detail-flavor-select"></select>
</div>
<div class="form-group">
<label>Select Size</label>
<select id="detail-size-select"></select>
</div>
<div class="price-display" id="detail-price-display">₱0.00</div>
<button class="btn" id="confirm-add-to-cart" style="width: 100%;">Add to Cart</button>
</div>
</div>
<div id="cart-switch-modal" class="modal">
<div class="modal-content" style="text-align: center;">
<span class="close-modal">&times;</span>
<h2 style="color: var(--primary);">Switch Vendor?</h2>
<p>Your cart contains items from another vendor. You can only order from one vendor at a time.</p>
<button class="btn" id="clear-cart-switch">Clear Cart & Switch</button>
<button class="btn btn-secondary" id="keep-cart" style="margin-left: 10px;">Keep Current Cart</button>
</div>
</div>
<script>
// ==========================================
// STORAGE & UTILITY HELPERS
// ==========================================
const APP_VERSION = '1.0';
let cart = [];
let pendingSwitchItem = null;
let navigationHistory = [];
// NEW FEATURE MERGED: Color scheme definitions
const colorSchemes = {
'#4CAF50': { // CHANGED TO GREEN
primary: '#4CAF50',
primaryDark: '#388E3C',
primaryGlass: 'rgba(76, 175, 80, 0.15)',
primaryDots: 'rgba(76, 175, 80, 0.08)',
primaryShadow: 'rgba(76, 175, 80, 0.2)',
glassBorder: 'rgba(76, 175, 80, 0.3)'
},
'#3D52A0': {
primary: '#3D52A0',
primaryDark: '#2A3A70',
primaryGlass: 'rgba(61, 82, 160, 0.15)',
primaryDots: 'rgba(61, 82, 160, 0.08)',
primaryShadow: 'rgba(61, 82, 160, 0.2)',
glassBorder: 'rgba(61, 82, 160, 0.3)'
},
'#C41E3A': {
primary: '#C41E3A',
primaryDark: '#8A1529',
primaryGlass: 'rgba(196, 30, 58, 0.15)',
primaryDots: 'rgba(196, 30, 58, 0.08)',
primaryShadow: 'rgba(196, 30, 58, 0.2)',
glassBorder: 'rgba(196, 30, 58, 0.3)'
},
'#00563B': {
primary: '#00563B',
primaryDark: '#003D29',
primaryGlass: 'rgba(0, 86, 59, 0.15)',
primaryDots: 'rgba(0, 86, 59, 0.08)',
primaryShadow: 'rgba(0, 86, 59, 0.2)',
glassBorder: 'rgba(0, 86, 59, 0.3)'
},
'#F5FEFD': {
primary: '#F5FEFD',
primaryDark: '#D9E6E5',
primaryGlass: 'rgba(245, 254, 253, 0.15)',
primaryDots: 'rgba(245, 254, 253, 0.08)',
primaryShadow: 'rgba(245, 254, 253, 0.2)',
glassBorder: 'rgba(245, 254, 253, 0.3)'
}
};
function initData() {
if (!localStorage.getItem('unigrub_init')) {
const students = [
{ id: 1, username: 'john', password: 'student123', name: 'John Doe' },
{ id: 2, username: 'sarah', password: 'student123', name: 'Sarah Smith' }
];
const vendors = [
{
id: 1, username: 'andrea', password: 'pizza123', stallName: "Andrea's Pizza", canteen: 'upper',
logo: '',
menu: [
{
id: 1,
name: 'Margherita Pizza',
description: 'Classic pizza with tomato sauce, mozzarella, and basil',
flavors: ['Original', 'Extra Cheese'],
sizePrices: { Small: 100, Medium: 150, Large: 200 },
available: true,
image: ''
},
{
id: 2,
name: 'Pepperoni Pizza',
description: 'Loaded with pepperoni and cheese',
flavors: ['Classic', 'Spicy'],
sizePrices: { Small: 120, Medium: 170, Large: 220 },
available: true,
image: ''
}
], qrCode: '', orders: []
},
{
id: 2, username: 'donmachiato', password: 'coffee123', stallName: 'Don Machiattos', canteen: 'upper',
logo: '',
menu: [
{
id: 1,
name: 'Espresso',
description: 'Rich and bold espresso shot',
flavors: [],
sizePrices: { Small: 60, Medium: 80, Large: 100 },
available: true,
image: ''
},
{
id: 2,
name: 'Cappuccino',
description: 'Creamy cappuccino with foam art',
flavors: ['Regular', 'Vanilla', 'Caramel'],
sizePrices: { Small: 80, Medium: 100, Large: 120 },
available: true,
image: ''
}
], qrCode: '', orders: []
},
{
id: 3, username: 'carabao', password: 'milk123', stallName: 'Carabao Milk', canteen: 'lower',
logo: '',
menu: [
{
id: 1,
name: 'Fresh Milk',
description: 'Pure and fresh carabao milk',
flavors: ['Plain', 'Chocolate', 'Strawberry'],
sizePrices: { Small: 40, Medium: 50, Large: 60 },
available: true,
image: ''
}
], qrCode: '', orders: []
}
];
localStorage.setItem('students', JSON.stringify(students));
localStorage.setItem('vendors', JSON.stringify(vendors));
localStorage.setItem('orders', JSON.stringify([]));
localStorage.setItem('systemSettings', JSON.stringify({
systemName: 'UniGrub',
orderTimeout: 30,
deleteVendorBehavior: 'preserve'
}));
localStorage.setItem('unigrub_init', 'true');
localStorage.setItem('app_version', APP_VERSION);
}
cart = JSON.parse(localStorage.getItem('currentCart') || '[]');
navigationHistory = JSON.parse(localStorage.getItem('navigationHistory') || '[]');
// NEW FEATURE MERGED: Initialize color scheme
const savedColor = localStorage.getItem('unigrub_color') || '#4CAF50'; // CHANGED TO GREEN
applyColorScheme(savedColor);
}
function getData(key) {
try {
const data = localStorage.getItem(key);
return data ? JSON.parse(data) : null;
} catch (e) {
console.error('Error reading from localStorage:', e);
return null;
}
}
function setData(key, value) {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
if (e.name === 'QuotaExceededError') {
showToast('Storage limit exceeded. Please clear some data.', 'error');
}
console.error('Error writing to localStorage:', e);
}
}
function showToast(message, type = 'info') {
const toast = document.createElement('div');
toast.className = `toast ${type}`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
}
function showPage(id) {
// Add current page to navigation history
const currentActive = document.querySelector('.page.active');
if (currentActive) {
navigationHistory.push(currentActive.id);
// Keep only last 10 pages in history
if (navigationHistory.length > 10) {
navigationHistory.shift();
}
setData('navigationHistory', navigationHistory);
}
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
const page = document.getElementById(id);
if (page) page.classList.add('active');
// Update back button visibility
updateBackButton();
// NEW FEATURE MERGED: Close mobile nav if open
closeMobileNav();
}
function goBack() {
if (navigationHistory.length > 0) {
const previousPage = navigationHistory.pop();
setData('navigationHistory', navigationHistory);
if (previousPage) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
const page = document.getElementById(previousPage);
if (page) {
page.classList.add('active');
updateBackButton();
return;
}
}
}
// If no history, go to appropriate dashboard
if (getData('currentStudent')) {
showPage('student-dashboard');
} else if (getData('currentVendor')) {
showPage('vendor-dashboard');
} else if (getData('currentAdmin')) {
showPage('admin-dashboard');
} else {
showPage('home-page');
}
}
function updateBackButton() {
const backButton = document.getElementById('nav-back');
const mobileBackButton = document.getElementById('mobile-nav-back');
// Show back button if there's navigation history or if user is logged in
const shouldShow = navigationHistory.length > 0 || getData('currentStudent') || getData('currentVendor') || getData('currentAdmin');
if (backButton) backButton.style.display = shouldShow ? 'block' : 'none';
if (mobileBackButton) mobileBackButton.style.display = shouldShow ? 'block' : 'none';
}
function showModal(id) {
const modal = document.getElementById(id);
if (modal) modal.style.display = 'flex';
}
function hideModal(id) {
const modal = document.getElementById(id);
if (modal) modal.style.display = 'none';
}
// NEW FEATURE MERGED: Mobile navigation functions
function toggleMobileNav() {
const mobileNav = document.getElementById('mobile-nav');
if (mobileNav) {
mobileNav.classList.toggle('active');
}
}
function closeMobileNav() {
const mobileNav = document.getElementById('mobile-nav');
if (mobileNav) {
mobileNav.classList.remove('active');
}
}
// NEW FEATURE MERGED: Color scheme functions
function applyColorScheme(color) {
const scheme = colorSchemes[color];
if (!scheme) return;
const root = document.documentElement;
root.style.setProperty('--primary', scheme.primary);
root.style.setProperty('--primary-dark', scheme.primaryDark);
root.style.setProperty('--primary-glass', scheme.primaryGlass);
root.style.setProperty('--primary-dots', scheme.primaryDots);
root.style.setProperty('--primary-shadow', scheme.primaryShadow);
root.style.setProperty('--glass-border', scheme.glassBorder);
// Update select background image
const select = document.querySelector('select');
if (select) {
select.style.backgroundImage = `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%234CAF50' d='M6 9L1 4h10z'/%3E%3C/svg%3E")`; // CHANGED TO GREEN
}
// Update active color option
document.querySelectorAll('.color-option').forEach(option => {
option.classList.remove('active');
if (option.dataset.color === color) {
option.classList.add('active');
}
});
localStorage.setItem('unigrub_color', color);
}
function showColorSchemeModal() {
const savedColor = localStorage.getItem('unigrub_color') || '#4CAF50'; // CHANGED TO GREEN
document.querySelectorAll('.color-option').forEach(option => {
option.classList.remove('active');
if (option.dataset.color === savedColor) {
option.classList.add('active');
}
});
showModal('color-scheme-modal');
}
function sanitizeHTML(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
function validatePhone(phone) {
const pattern = /^09\d{9}$/;
return pattern.test(phone);
}
function compressImage(file, maxSizeMB = 1, maxWidth = 800) {
return new Promise((resolve, reject) => {
if (file.size / 1024 / 1024 > maxSizeMB) {
const img = new Image();
const reader = new FileReader();
reader.onload = (e) => {
img.onload = () => {
const ratio = Math.min(1, maxWidth / img.width);
const canvas = document.createElement('canvas');
canvas.width = Math.round(img.width * ratio);
canvas.height = Math.round(img.height * ratio);
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const dataUrl = canvas.toDataURL('image/jpeg', 0.85);
if ((dataUrl.length * 3 / 4) / 1024 / 1024 > maxSizeMB) {
reject(new Error(`Image still exceeds ${maxSizeMB}MB after compression.`));
} else {
resolve(dataUrl);
}
};
img.onerror = () => reject(new Error('Could not process image'));
img.src = e.target.result;
};
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsDataURL(file);
} else {
const reader = new FileReader();
reader.onload = (e) => resolve(e.target.result);
reader.onerror = () => reject(new Error('Failed to read file'));
reader.readAsDataURL(file);
}
});
}
function getFreshVendor() {
const current = getData('currentVendor');
if (!current) return null;
const vendors = getData('vendors') || [];
return vendors.find(v => v.id === current.id) || null;
}
function cartHasDifferentVendor(vendorId) {
return cart.length > 0 && cart.some(i => i.vendorId !== vendorId);
}
function getStallInitials(name) {
return name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2);
}
function parseSizePrices(input) {
const result = {};
const pairs = input.split(',').map(p => p.trim());
pairs.forEach(pair => {
const [size, price] = pair.split(':').map(s => s.trim());
if (size && price && !isNaN(price)) {
result[size] = parseFloat(price);
}
});
return result;
}
function sizePricesToString(sizePrices) {
return Object.entries(sizePrices).map(([size, price]) => `${size}:${price}`).join(', ');
}
// ==========================================
// AUTHENTICATION
// ==========================================
function loginStudent(u, p) {
const students = getData('students') || [];
const student = students.find(s => s.username === u && s.password === p);
if (student) {
setData('currentStudent', student);
const nameEl = document.getElementById('student-name');
if (nameEl) nameEl.textContent = sanitizeHTML(student.name);
showPage('student-dashboard');
updateNav(true);
showToast('Login successful!', 'success');
return true;
}
return false;
}
function loginVendor(u, p) {
const vendors = getData('vendors') || [];
const vendor = vendors.find(v => v.username === u.toLowerCase() && v.password === p.toLowerCase());
if (vendor) {
setData('currentVendor', vendor);
updateVendorDashboard(vendor);
showPage('vendor-dashboard');
updateNav(true);
showToast('Login successful!', 'success');
return true;
}
return false;
}
function loginAdmin(u, p) {
if (u === 'admin' && p === 'admin123') {
setData('currentAdmin', true);
showPage('admin-dashboard');
updateNav(true);
showToast('Admin access granted', 'success');
return true;
}
return false;
}
function logout() {
localStorage.removeItem('currentStudent');
localStorage.removeItem('currentVendor');
localStorage.removeItem('currentAdmin');
localStorage.removeItem('currentCart');
localStorage.removeItem('navigationHistory');
cart = [];
navigationHistory = [];
showPage('home-page');
updateNav(false);
updateBackButton();
showToast('Logged out successfully', 'info');
}
function updateNav(loggedIn) {
const loginNav = document.getElementById('nav-login');
const logoutNav = document.getElementById('nav-logout');
const mobileLoginNav = document.getElementById('mobile-nav-login');
const mobileLogoutNav = document.getElementById('mobile-nav-logout');
if (loginNav) loginNav.style.display = loggedIn ? 'none' : 'block';
if (logoutNav) logoutNav.style.display = loggedIn ? 'block' : 'none';
if (mobileLoginNav) mobileLoginNav.style.display = loggedIn ? 'none' : 'block';
if (mobileLogoutNav) mobileLogoutNav.style.display = loggedIn ? 'block' : 'none';
updateBackButton();
}
function updateVendorDashboard(vendor) {
const nameEl = document.getElementById('vendor-stall-name');
if (nameEl) nameEl.textContent = sanitizeHTML(vendor.stallName);
const logoImg = document.getElementById('vendor-dash-logo');
const logoPlaceholder = document.getElementById('vendor-dash-logo-placeholder');
if (logoImg && logoPlaceholder) {
if (vendor.logo) {
logoImg.src = vendor.logo;
logoImg.style.display = 'block';
logoPlaceholder.style.display = 'none';
} else {
logoPlaceholder.textContent = getStallInitials(vendor.stallName);
logoPlaceholder.style.display = 'flex';
logoImg.style.display = 'none';
}
}
}
// ==========================================
// STUDENT FUNCTIONS
// ==========================================
function loadStalls(canteen) {
const vendors = (getData('vendors') || []).filter(v => v.canteen === canteen);
const container = document.getElementById('stalls-container');
const titles = { upper: 'Upper Campus', lower: 'Lower Campus', market: 'Market' };
const titleEl = document.getElementById('canteen-title');
if (titleEl) titleEl.textContent = titles[canteen] + ' Stalls';
if (container) container.innerHTML = '';
vendors.forEach(v => {
const card = document.createElement('div');
card.className = 'card';
card.style.cssText = 'text-align:center;cursor:pointer;';
let logoHTML = '';
if (v.logo) {
logoHTML = `<img src="${v.logo}" class="stall-logo">`;
} else {
logoHTML = `<div class="stall-logo-placeholder">${getStallInitials(v.stallName)}</div>`;
}
card.innerHTML = `${logoHTML}<h3>${sanitizeHTML(v.stallName)}</h3>`;
card.onclick = () => loadMenu(v.id);
container.appendChild(card);
});
showPage('canteen-stalls-page');
}
function loadMenu(vendorId) {
const vendors = getData('vendors') || [];
const vendor = vendors.find(v => v.id === vendorId);
if (!vendor) return;
setData('currentVendorId', vendorId);
const titleEl = document.getElementById('stall-menu-title');
if (titleEl) titleEl.textContent = sanitizeHTML(vendor.stallName);
const container = document.getElementById('stall-menu-container');
if (container) container.innerHTML = '';
vendor.menu.filter(i => i.available).forEach(item => {
const div = document.createElement('div');
div.className = 'card menu-item';
const img = item.image
? `<img src="${item.image}">`
: '<div style="width:96px;height:120px;background:rgba(76,175,80,0.2);display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:24px;">🍕</div>'; // CHANGED TO GREEN
const sizes = Object.keys(item.sizePrices || {});
const priceRange = sizes.length > 0
? `₱${Math.min(...Object.values(item.sizePrices))} - ₱${Math.max(...Object.values(item.sizePrices))}`
: '₱0';
div.innerHTML = `
<div class="menu-item-image">${img}</div>
<div class="menu-item-info">
<div class="menu-item-name">${sanitizeHTML(item.name)}</div>
<div class="menu-item-price">${priceRange}</div>
<div style="color: var(--text-secondary); margin-bottom: 8px;">${sanitizeHTML(item.description || '')}</div>
<div class="menu-item-actions">
<button class="btn btn-small" data-item-id="${item.id}">View Details</button>
</div>
</div>
`;
div.querySelector('button').addEventListener('click', () => {
showItemDetailModal(item, vendor.stallName, vendor.id);
});
container.appendChild(div);
});
updateCartCount();
showPage('stall-menu-page');
}
function showItemDetailModal(item, vendorName, vendorId) {
const modal = document.getElementById('item-detail-modal');
const img = document.getElementById('detail-item-image');
const name = document.getElementById('detail-item-name');
const desc = document.getElementById('detail-item-description');
const flavorGroup = document.getElementById('flavor-group');
const flavorSelect = document.getElementById('detail-flavor-select');
const sizeSelect = document.getElementById('detail-size-select');
const priceDisplay = document.getElementById('detail-price-display');
if (name) name.textContent = item.name;
if (desc) desc.textContent = item.description || '';
if (img) {
if (item.image) {
img.src = item.image;
img.style.display = 'block';
} else {
img.style.display = 'none';
}
}
// Setup flavors
if (flavorGroup && flavorSelect) {
if (item.flavors && item.flavors.length > 0) {
flavorGroup.style.display = 'block';
flavorSelect.innerHTML = '';
item.flavors.forEach(flavor => {
const option = document.createElement('option');
option.value = flavor;
option.textContent = flavor;
flavorSelect.appendChild(option);
});
} else {
flavorGroup.style.display = 'none';
}
}
// Setup sizes
if (sizeSelect) {
sizeSelect.innerHTML = '';
Object.entries(item.sizePrices || {}).forEach(([size, price]) => {
const option = document.createElement('option');
option.value = size;
option.textContent = `${size} - ₱${price}`;
option.dataset.price = price;
sizeSelect.appendChild(option);
});
}
// Update price on size change
function updatePrice() {
if (sizeSelect && priceDisplay) {
const selectedOption = sizeSelect.options[sizeSelect.selectedIndex];
if (selectedOption) {
priceDisplay.textContent = `₱${selectedOption.dataset.price}`;
}
}
}
if (sizeSelect) sizeSelect.onchange = updatePrice;
updatePrice();
// Confirm button
const confirmBtn = document.getElementById('confirm-add-to-cart');
if (confirmBtn) {
confirmBtn.onclick = () => {
const selectedFlavor = flavorSelect ? flavorSelect.value : null;
const selectedSize = sizeSelect ? sizeSelect.value : '';
const selectedPrice = sizeSelect ? parseFloat(sizeSelect.options[sizeSelect.selectedIndex].dataset.price) : 0;
addToCart({
...item,
selectedFlavor,
selectedSize,
price: selectedPrice
}, vendorName, vendorId);
hideModal('item-detail-modal');
};
}
showModal('item-detail-modal');
}
function addToCart(item, vendorName, vendorId) {
if (cartHasDifferentVendor(vendorId)) {
pendingSwitchItem = { item, vendorName, vendorId };
showModal('cart-switch-modal');
return;
}
const existing = cart.find(i =>
i.id === item.id &&
i.selectedFlavor === item.selectedFlavor &&
i.selectedSize === item.selectedSize &&
i.vendor === vendorName
);
if (existing) {
existing.quantity++;
} else {
cart.push({
...item,
vendor: vendorName,
vendorId: vendorId,
quantity: 1
});
}
setData('currentCart', cart);
updateCartCount();
showToast('Added to cart!', 'success');
}
function updateCartCount() {
const count = cart.reduce((sum, i) => sum + i.quantity, 0);
const el = document.getElementById('cart-count');
if (el) el.textContent = count;
}
function loadCart() {
const container = document.getElementById('cart-items-container');
const total = document.getElementById('cart-total');
if (container) container.innerHTML = '';
let sum = 0;
cart.forEach((item, idx) => {
const div = document.createElement('div');
div.className = 'cart-item';
const flavorText = item.selectedFlavor ? ` - ${item.selectedFlavor}` : '';
div.innerHTML = `
<div>
<strong>${sanitizeHTML(item.name)}</strong>${flavorText} (${item.selectedSize})<br>
<small>${sanitizeHTML(item.vendor)}</small>
</div>
<div style="text-align: right;">
${item.price} × ${item.quantity} = ₱${(item.price * item.quantity).toFixed(2)}
<br>
<button class="btn btn-small btn-danger" onclick="removeFromCart(${idx})" style="margin-top: 8px;">Remove</button>
</div>
`;
container.appendChild(div);
sum += item.price * item.quantity;
});
if (total) total.textContent = `Total: ₱${sum.toFixed(2)}`;
}
function removeFromCart(idx) {
cart.splice(idx, 1);
setData('currentCart', cart);
loadCart();
updateCartCount();
}
function loadCheckout() {
const container = document.getElementById('checkout-summary');
if (container) {
container.innerHTML = '<h3>Order Summary</h3>';
let total = 0;
cart.forEach(item => {
const div = document.createElement('div');
div.className = 'cart-item';
const flavorText = item.selectedFlavor ? ` - ${item.selectedFlavor}` : '';
div.innerHTML = `
<div>${sanitizeHTML(item.name)}${flavorText} (${item.selectedSize}) - ${sanitizeHTML(item.vendor)}</div>
<div>₱${item.price} × ${item.quantity}</div>
`;
container.appendChild(div);
total += item.price * item.quantity;
});
const totalDiv = document.createElement('div');
totalDiv.className = 'cart-total';
totalDiv.textContent = `Total: ₱${total.toFixed(2)}`;
container.appendChild(totalDiv);
}
if (cart.length > 0) {
const vendorId = cart[0].vendorId;
const vendors = getData('vendors') || [];
const vendor = vendors.find(v => v.id == vendorId);
const qrPreview = document.getElementById('vendor-qr-preview');
if (vendor && vendor.qrCode && qrPreview) {
qrPreview.innerHTML = `<img src="${vendor.qrCode}">`;
}
}
}
function placeOrder() {
const student = getData('currentStudent');
if (!student) {
showToast('Please log in as a student before placing an order.', 'error');
showPage('student-login-page');
return;
}
if (cart.length === 0) {
showToast('Cart is empty!', 'error');
return;
}
const orderType = document.getElementById('order-type').value;
const paymentMethod = document.getElementById('payment-method').value;
if (orderType === 'delivery') {
const name = document.getElementById('delivery-name').value;
const location = document.getElementById('delivery-location').value;
const contact = document.getElementById('delivery-contact').value;
if (!name || !location || !contact) {
showToast('Please fill in all delivery details', 'error');
return;
}
if (!validatePhone(contact)) {
showToast('Invalid phone number. Use format: 09XXXXXXXXX', 'error');
return;
}
}
const orders = getData('orders') || [];
const newOrder = {
id: Date.now(),
student: student,
items: [...cart],
orderType: orderType,
paymentMethod: paymentMethod,
status: 'pending',
timestamp: new Date().toISOString(),
total: cart.reduce((sum, item) => sum + (item.price * item.quantity), 0),
vendorId: cart[0].vendorId
};
if (orderType === 'delivery') {
newOrder.deliveryDetails = {
name: document.getElementById('delivery-name').value,
location: document.getElementById('delivery-location').value,
contact: document.getElementById('delivery-contact').value
};
}
orders.push(newOrder);
setData('orders', orders);
const vendors = getData('vendors') || [];
const vendorIdx = vendors.findIndex(v => v.id === newOrder.vendorId);
if (vendorIdx !== -1) {
vendors[vendorIdx].orders = vendors[vendorIdx].orders || [];
vendors[vendorIdx].orders.push(newOrder.id);
setData('vendors', vendors);
}
cart = [];
setData('currentCart', cart);
updateCartCount();
showModal('order-success-modal');
}
// Load student orders with progress tracker
function loadStudentOrders() {
const student = getData('currentStudent');
if (!student) return;
const orders = getData('orders') || [];
const studentOrders = orders.filter(o => o.student.id === student.id);
const container = document.getElementById('student-orders-container');
if (container) {
container.innerHTML = '';
if (studentOrders.length === 0) {
container.innerHTML = '<p>No orders yet.</p>';
return;
}
// Sort by timestamp (newest first)
studentOrders.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
studentOrders.forEach(order => {
const card = document.createElement('div');
card.className = 'card';
card.style.marginBottom = '20px';
// Get vendor name
const vendors = getData('vendors') || [];
const vendor = vendors.find(v => v.id === order.vendorId);
const vendorName = vendor ? vendor.stallName : 'Unknown Vendor';
// Create progress tracker
const statusOrder = ['pending', 'preparing', 'ready', 'completed'];
const currentStatusIndex = statusOrder.indexOf(order.status);
let progressHTML = `
<div class="progress-tracker">
<div class="progress-bar" style="width: ${(currentStatusIndex / (statusOrder.length - 1)) * 100}%"></div>
`;
statusOrder.forEach((status, index) => {
const stepClass = index < currentStatusIndex ? 'completed' :
index === currentStatusIndex ? 'active' : '';
progressHTML += `
<div class="progress-step">
<div class="step-indicator ${stepClass}">${index + 1}</div>
<div class="step-label ${stepClass}">${status.charAt(0).toUpperCase() + status.slice(1)}</div>
</div>
`;
});
progressHTML += `</div>`;
let itemsHTML = '';
order.items.forEach(i => {
const flavorText = i.selectedFlavor ? ` - ${i.selectedFlavor}` : '';
itemsHTML += `<div>${sanitizeHTML(i.name)}${flavorText} (${i.selectedSize}) x ${i.quantity} - ₱${i.price * i.quantity}</div>`;
});
const delivery = order.orderType === 'delivery' && order.deliveryDetails ?
`<p><strong>Delivery:</strong> ${sanitizeHTML(order.deliveryDetails.name)}, ${sanitizeHTML(order.deliveryDetails.location)}, ${sanitizeHTML(order.deliveryDetails.contact)}</p>` : '';
card.innerHTML = `
<h4>Order #${order.id}</h4>
<p><strong>Vendor:</strong> ${sanitizeHTML(vendorName)}</p>
<p><strong>Type:</strong> ${order.orderType}</p>
${delivery}
<p><strong>Payment:</strong> ${order.paymentMethod}</p>
<p><strong>Total:</strong> ₱${order.total.toFixed(2)}</p>
<p><strong>Time:</strong> ${new Date(order.timestamp).toLocaleString()}</p>
<div style="margin:10px 0;"><strong>Items:</strong>${itemsHTML}</div>
${progressHTML}
`;
container.appendChild(card);
});
}
}
// ==========================================
// VENDOR FUNCTIONS
// ==========================================
function loadVendorMenu() {
const vendor = getFreshVendor();
if (!vendor) return;
setData('currentVendor', vendor);
const container = document.getElementById('vendor-menu-container');
if (container) container.innerHTML = '';
vendor.menu.forEach(item => {
const div = document.createElement('div');
div.className = 'card menu-item';
const img = item.image ? `<img src="${item.image}">` : '<div style="width:96px;height:120px;background:rgba(76,175,80,0.2);display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:24px;">🍕</div>'; // CHANGED TO GREEN
const priceRange = item.sizePrices
? `₱${Math.min(...Object.values(item.sizePrices))} - ₱${Math.max(...Object.values(item.sizePrices))}`
: '₱0';
div.innerHTML = `
<div class="menu-item-image">${img}</div>
<div class="menu-item-info">
<div class="menu-item-name">${sanitizeHTML(item.name)}</div>
<div class="menu-item-price">${priceRange}</div>
<div style="color: var(--text-secondary); font-size: 14px; margin-bottom: 8px;">${sanitizeHTML(item.description || '')}</div>
<div class="menu-item-actions">
<button class="btn btn-small btn-secondary" data-action="edit" data-id="${item.id}">Edit</button>
<button class="btn btn-small btn-danger" data-action="delete" data-id="${item.id}">Delete</button>
<button class="btn btn-small ${item.available ? 'btn-success' : 'btn-secondary'}" data-action="toggle" data-id="${item.id}">${item.available ? 'Available' : 'Unavailable'}</button>
</div>
</div>
`;
div.querySelectorAll('button').forEach(btn => {
btn.addEventListener('click', () => {
const action = btn.dataset.action;
const itemId = parseInt(btn.dataset.id);
if (action === 'edit') editMenuItem(itemId);
else if (action === 'delete') deleteMenuItem(itemId);
else if (action === 'toggle') toggleAvailability(itemId);
});
});
container.appendChild(div);
});
}
function editMenuItem(itemId) {
const vendor = getData('currentVendor') || {};
const item = vendor.menu.find(i => i.id === itemId);
if (item) {
document.getElementById('edit-item-id').value = item.id;
document.getElementById('edit-item-name').value = item.name;
document.getElementById('edit-item-description').value = item.description || '';
document.getElementById('edit-item-flavors').value = (item.flavors || []).join(', ');
document.getElementById('edit-item-size-prices').value = sizePricesToString(item.sizePrices || {});
showModal('edit-menu-item-modal');
}
}
function deleteMenuItem(itemId) {
if (!confirm('Delete this item?')) return;
const vendor = getFreshVendor();
if (vendor) {
vendor.menu = vendor.menu.filter(i => i.id !== itemId);
setData('currentVendor', vendor);
updateVendorInStorage(vendor);
loadVendorMenu();
showToast('Item deleted', 'info');
}
}
function toggleAvailability(itemId) {
const vendor = getFreshVendor();
if (vendor) {
const item = vendor.menu.find(i => i.id === itemId);
if (item) {
item.available = !item.available;
setData('currentVendor', vendor);
updateVendorInStorage(vendor);
loadVendorMenu();
}
}
}
function updateVendorInStorage(vendor) {
const vendors = getData('vendors') || [];
const idx = vendors.findIndex(v => v.id === vendor.id);
if (idx !== -1) {
vendors[idx] = vendor;
setData('vendors', vendors);
}
}
function loadCurrentQR() {
const vendor = getFreshVendor();
if (!vendor) return;
setData('currentVendor', vendor);
const preview = document.getElementById('current-qr-preview');
if (preview) {
if (vendor.qrCode) {
preview.innerHTML = `<img src="${vendor.qrCode}">`;
preview.dataset.qrCode = vendor.qrCode;
} else {
preview.innerHTML = '<span>No QR uploaded</span>';
}
}
}
function loadVendorProfile() {
const vendor = getFreshVendor();
if (!vendor) return;
const logoPreview = document.getElementById('profile-logo-preview');
const logoPlaceholder = document.getElementById('profile-logo-placeholder');
if (logoPreview && logoPlaceholder) {
if (vendor.logo) {
logoPreview.src = vendor.logo;
logoPreview.style.display = 'block';
logoPlaceholder.style.display = 'none';
} else {
logoPlaceholder.textContent = getStallInitials(vendor.stallName);
logoPlaceholder.style.display = 'flex';
logoPreview.style.display = 'none';
}
}
}
function loadVendorOrders() {
const vendor = getData('currentVendor') || {};
const orders = getData('orders') || [];
const vendorOrders = orders.filter(o => o.vendorId == vendor.id);
const container = document.getElementById('vendor-orders-container');
if (container) {
container.innerHTML = '';
if (vendorOrders.length === 0) {
container.innerHTML = '<p>No orders yet.</p>';
return;
}
vendorOrders.forEach(order => {
const card = document.createElement('div');
card.className = 'card';
let itemsHTML = '';
order.items.forEach(i => {
const flavorText = i.selectedFlavor ? ` - ${i.selectedFlavor}` : '';
itemsHTML += `<div>${sanitizeHTML(i.name)}${flavorText} (${i.selectedSize}) x ${i.quantity} - ₱${i.price * i.quantity}</div>`;
});
const delivery = order.orderType === 'delivery' && order.deliveryDetails ?
`<p><strong>Delivery:</strong> ${sanitizeHTML(order.deliveryDetails.name)}, ${sanitizeHTML(order.deliveryDetails.location)}, ${sanitizeHTML(order.deliveryDetails.contact)}</p>` : '';
const statusButtons = ['preparing', 'ready', 'completed']
.filter(s => s !== order.status)
.map(s => `<button class="btn btn-small" onclick="updateOrderStatus(${order.id}, '${s}')">${s.charAt(0).toUpperCase() + s.slice(1)}</button>`)
.join(' ');
card.innerHTML = `
<h4>Order #${order.id}</h4>
<p><strong>Student:</strong> ${sanitizeHTML(order.student.name)}</p>
<p><strong>Type:</strong> ${order.orderType}</p>
${delivery}
<p><strong>Payment:</strong> ${order.paymentMethod}</p>
<p><strong>Status:</strong> <span class="order-status status-${order.status}">${order.status}</span></p>
<div style="margin:10px 0;"><strong>Items:</strong>${itemsHTML}</div>
<p><strong>Total:</strong> ₱${order.total.toFixed(2)}</p>
<p><strong>Time:</strong> ${new Date(order.timestamp).toLocaleString()}</p>
<div style="margin-top:10px;">${statusButtons}</div>
`;
container.appendChild(card);
});
}
}
function updateOrderStatus(orderId, newStatus) {
const orders = getData('orders') || [];
const order = orders.find(o => o.id === orderId);
if (order) {
order.status = newStatus;
setData('orders', orders);
loadVendorOrders();
showToast(`Order updated to ${newStatus}`, 'success');
}
}
// ==========================================
// ADMIN FUNCTIONS
// ==========================================
function loadVendors() {
const vendors = getData('vendors') || [];
const list = document.getElementById('vendor-list');
if (list) {
list.innerHTML = '';
vendors.forEach(v => {
const div = document.createElement('div');
div.className = 'vendor-item';
div.innerHTML = `
<div class="vendor-info">
<h4>${sanitizeHTML(v.stallName)}</h4>
<p>Username: ${sanitizeHTML(v.username)} | Canteen: ${v.canteen}</p>
<p>Menu Items: ${v.menu.length}</p>
</div>
<div class="vendor-actions">
<button class="btn btn-small btn-secondary" onclick="editVendor(${v.id})">Edit</button>
<button class="btn btn-small btn-danger" onclick="deleteVendor(${v.id})">Delete</button>
</div>
`;
list.appendChild(div);
});
}
}
function editVendor(vendorId) {
const vendors = getData('vendors') || [];
const vendor = vendors.find(v => v.id === vendorId);
if (vendor) {
document.getElementById('edit-vendor-id').value = vendor.id;
document.getElementById('edit-vendor-stall-name').value = vendor.stallName;
document.getElementById('edit-vendor-username').value = vendor.username;
document.getElementById('edit-vendor-password').value = vendor.password;
document.getElementById('edit-vendor-canteen').value = vendor.canteen;
showModal('edit-vendor-modal');
}
}
function deleteVendor(vendorId) {
if (!confirm('Delete this vendor?')) return;
const vendors = getData('vendors') || [];
const updated = vendors.filter(v => v.id !== vendorId);
setData('vendors', updated);
const settings = getData('systemSettings') || {};
if (settings.deleteVendorBehavior === 'remove') {
const orders = getData('orders') || [];
const filteredOrders = orders.filter(o => o.vendorId !== vendorId);
setData('orders', filteredOrders);
}
const currentVendor = getData('currentVendor');
if (currentVendor && currentVendor.id === vendorId) {
localStorage.removeItem('currentVendor');
showPage('home-page');
updateNav(false);
}
loadVendors();
showToast('Vendor deleted', 'info');
}
function loadAllMenus() {
const vendors = getData('vendors') || [];
const container = document.getElementById('all-menus-container');
if (container) {
container.innerHTML = '';
vendors.forEach(vendor => {
const section = document.createElement('div');
section.className = 'card';
section.style.marginBottom = '30px';
let menuHTML = '<div class="grid" style="margin-top:20px;">';
vendor.menu.forEach(item => {
const img = item.image ? `<img src="${item.image}">` : '<div style="width:96px;height:120px;background:rgba(76,175,80,0.2);display:flex;align-items:center;justify-content:center;border-radius:8px;font-size:24px;">🍕</div>'; // CHANGED TO GREEN
const priceRange = item.sizePrices
? `₱${Math.min(...Object.values(item.sizePrices))} - ₱${Math.max(...Object.values(item.sizePrices))}`
: '₱0';
menuHTML += `
<div class="card menu-item">
<div class="menu-item-image">${img}</div>
<div class="menu-item-info">
<div class="menu-item-name">${sanitizeHTML(item.name)}</div>
<div class="menu-item-price">${priceRange}</div>
<div style="color: var(--text-secondary); font-size: 14px;">${sanitizeHTML(item.description || '')}</div>
<div style="margin-top: 8px;">${item.available ? '✓ Available' : '✗ Unavailable'}</div>
</div>
</div>
`;
});
menuHTML += '</div>';
section.innerHTML = `
<h3 style="color:var(--primary);">${sanitizeHTML(vendor.stallName)}</h3>
<p style="color:var(--text-secondary);">Canteen: ${vendor.canteen} | Username: ${sanitizeHTML(vendor.username)}</p>
${menuHTML}
`;
container.appendChild(section);
});
}
}
function loadSystemSettings() {
const settings = getData('systemSettings') || {};
if (settings.systemName) document.getElementById('system-name').value = settings.systemName;
if (settings.orderTimeout) document.getElementById('order-timeout').value = settings.orderTimeout;
if (settings.deleteVendorBehavior) document.getElementById('delete-vendor-behavior').value = settings.deleteVendorBehavior;
}
// ==========================================
// EVENT LISTENERS
// ==========================================
document.addEventListener('DOMContentLoaded', function() {
initData();
// NEW FEATURE MERGED: Mobile navigation
const mobileMenuToggle = document.getElementById('mobile-menu-toggle');
const closeMobileNavBtn = document.getElementById('close-mobile-nav'); // FIXED: Changed variable name
if (mobileMenuToggle) {
mobileMenuToggle.addEventListener('click', toggleMobileNav);
}
if (closeMobileNavBtn) {
closeMobileNavBtn.addEventListener('click', closeMobileNav);
}
// NEW FEATURE MERGED: Color scheme functionality
const themeToggle = document.getElementById('theme-toggle');
const mobileThemeToggle = document.getElementById('mobile-theme-toggle');
const saveColorScheme = document.getElementById('save-color-scheme');
if (themeToggle) {
themeToggle.addEventListener('click', showColorSchemeModal);
}
if (mobileThemeToggle) {
mobileThemeToggle.addEventListener('click', () => {
showColorSchemeModal();
closeMobileNav();
});
}
if (saveColorScheme) {
saveColorScheme.addEventListener('click', () => {
const activeColor = document.querySelector('.color-option.active').dataset.color;
applyColorScheme(activeColor);
hideModal('color-scheme-modal');
showToast('Color scheme updated!', 'success');
});
}
// NEW FEATURE MERGED: Color option selection
document.querySelectorAll('.color-option').forEach(option => {
option.addEventListener('click', function() {
document.querySelectorAll('.color-option').forEach(opt => opt.classList.remove('active'));
this.classList.add('active');
});
});
// Navigation
const navHome = document.getElementById('nav-home');
const mobileNavHome = document.getElementById('mobile-nav-home');
const navBack = document.getElementById('nav-back');
const mobileNavBack = document.getElementById('mobile-nav-back');
const navLogin = document.getElementById('nav-login');
const mobileNavLogin = document.getElementById('mobile-nav-login');
const navLogout = document.getElementById('nav-logout');
const mobileNavLogout = document.getElementById('mobile-nav-logout');
if (navHome) {
navHome.onclick = (e) => {
e.preventDefault();
if (getData('currentStudent')) {
showPage('student-dashboard');
} else if (getData('currentVendor')) {
showPage('vendor-dashboard');
} else if (getData('currentAdmin')) {
showPage('admin-dashboard');
} else {
showPage('home-page');
}
};
}
if (mobileNavHome) {
mobileNavHome.onclick = (e) => {
e.preventDefault();
if (getData('currentStudent')) {
showPage('student-dashboard');
} else if (getData('currentVendor')) {
showPage('vendor-dashboard');
} else if (getData('currentAdmin')) {
showPage('admin-dashboard');
} else {
showPage('home-page');
}
};
}
if (navBack) {
navBack.onclick = (e) => {
e.preventDefault();
goBack();
};
}
if (mobileNavBack) {
mobileNavBack.onclick = (e) => {
e.preventDefault();
goBack();
};
}
if (navLogin) navLogin.onclick = (e) => { e.preventDefault(); showPage('home-page'); };
if (mobileNavLogin) mobileNavLogin.onclick = (e) => { e.preventDefault(); showPage('home-page'); };
if (navLogout) navLogout.onclick = (e) => { e.preventDefault(); logout(); };
if (mobileNavLogout) mobileNavLogout.onclick = (e) => { e.preventDefault(); logout(); };
// Role selection
const studentRole = document.getElementById('student-role');
const vendorRole = document.getElementById('vendor-role');
const adminRole = document.getElementById('admin-role');
if (studentRole) {
studentRole.addEventListener('click', function() {
showPage('student-login-page');
});
}
if (vendorRole) {
vendorRole.addEventListener('click', function() {
showPage('vendor-login-page');
});
}
if (adminRole) {
adminRole.addEventListener('click', function() {
showPage('admin-login-page');
});
}
// Student authentication
const studentLoginForm = document.getElementById('student-login-form');
if (studentLoginForm) {
studentLoginForm.onsubmit = (e) => {
e.preventDefault();
const u = document.getElementById('student-username').value;
const p = document.getElementById('student-password').value;
const remember = document.getElementById('student-remember').checked;
if (loginStudent(u, p)) {
if (remember) setData('rememberedStudent', { username: u, password: p });
else localStorage.removeItem('rememberedStudent');
} else showToast('Invalid credentials', 'error');
};
}
const vendorLoginForm = document.getElementById('vendor-login-form');
if (vendorLoginForm) {
vendorLoginForm.onsubmit = (e) => {
e.preventDefault();
if (!loginVendor(document.getElementById('vendor-username').value, document.getElementById('vendor-password').value)) {
showToast('Invalid credentials', 'error');
}
};
}
const adminLoginForm = document.getElementById('admin-login-form');
if (adminLoginForm) {
adminLoginForm.onsubmit = (e) => {
e.preventDefault();
if (!loginAdmin(document.getElementById('admin-username').value, document.getElementById('admin-password').value)) {
showToast('Invalid credentials', 'error');
}
};
}
const studentRegister = document.getElementById('student-register');
if (studentRegister) {
studentRegister.onclick = () => {
const u = document.getElementById('student-username').value;
const p = document.getElementById('student-password').value;
if (!u || !p) { showToast('Enter username and password', 'error'); return; }
const students = getData('students') || [];
if (students.find(s => s.username === u)) { showToast('Username exists', 'error'); return; }
students.push({ id: Date.now(), username: u, password: p, name: u });
setData('students', students);
showToast('Registration successful!', 'success');
};
}
// Student navigation
document.querySelectorAll('.canteen-card').forEach(card => {
card.onclick = function() { loadStalls(this.dataset.canteen); };
});
const backToStalls = document.getElementById('back-to-stalls');
const viewCartBtn = document.getElementById('view-cart-btn');
const backToMenu = document.getElementById('back-to-menu');
const checkoutBtn = document.getElementById('checkout-btn');
const placeOrderBtn = document.getElementById('place-order-btn');
const viewAllOrders = document.getElementById('view-all-orders');
const backToStudentDashboard = document.getElementById('back-to-student-dashboard');
const backToStudentDashboardFromStalls = document.getElementById('back-to-student-dashboard-from-stalls');
const backToStallsFromCart = document.getElementById('back-to-stalls-from-cart');
const backToCart = document.getElementById('back-to-cart');
if (backToStalls) backToStalls.onclick = () => showPage('canteen-stalls-page');
if (viewCartBtn) viewCartBtn.onclick = () => { loadCart(); showPage('student-cart-page'); };
if (backToMenu) backToMenu.onclick = () => showPage('stall-menu-page');
if (checkoutBtn) {
checkoutBtn.onclick = () => {
if (cart.length === 0) { showToast('Cart is empty!', 'error'); return; }
loadCheckout();
showPage('checkout-page');
};
}
if (placeOrderBtn) placeOrderBtn.onclick = placeOrder;
if (viewAllOrders) viewAllOrders.onclick = () => { loadStudentOrders(); showPage('student-orders-page'); };
if (backToStudentDashboard) backToStudentDashboard.onclick = () => showPage('student-dashboard');
if (backToStudentDashboardFromStalls) backToStudentDashboardFromStalls.onclick = () => showPage('student-dashboard');
if (backToStallsFromCart) backToStallsFromCart.onclick = () => showPage('canteen-stalls-page');
if (backToCart) backToCart.onclick = () => { loadCart(); showPage('student-cart-page'); };
const orderType = document.getElementById('order-type');
const paymentMethod = document.getElementById('payment-method');
if (orderType) {
orderType.onchange = function() {
const deliveryDetails = document.getElementById('delivery-details');
if (deliveryDetails) deliveryDetails.style.display = this.value === 'delivery' ? 'block' : 'none';
};
}
if (paymentMethod) {
paymentMethod.onchange = function() {
const gcashDetails = document.getElementById('gcash-details');
if (gcashDetails) gcashDetails.style.display = this.value === 'gcash' ? 'block' : 'none';
};
}
// Vendor navigation
const manageProfile = document.getElementById('manage-profile');
const manageMenu = document.getElementById('manage-menu');
const manageQR = document.getElementById('manage-qr');
const viewOrders = document.getElementById('view-orders');
if (manageProfile) manageProfile.onclick = () => { loadVendorProfile(); showPage('vendor-profile-page'); };
if (manageMenu) manageMenu.onclick = () => { loadVendorMenu(); showPage('vendor-menu-page'); };
if (manageQR) manageQR.onclick = () => { loadCurrentQR(); showPage('vendor-qr-page'); };
if (viewOrders) viewOrders.onclick = () => { loadVendorOrders(); showPage('vendor-orders-page'); };
const logoUpload = document.getElementById('logo-upload');
if (logoUpload) {
logoUpload.onchange = async function(e) {
const file = e.target.files[0];
if (!file) {
showToast('Please select a file first', 'error');
return;
}
try {
const logo = await compressImage(file);
const preview = document.getElementById('profile-logo-preview');
const placeholder = document.getElementById('profile-logo-placeholder');
if (preview && placeholder) {
preview.src = logo;
preview.style.display = 'block';
placeholder.style.display = 'none';
preview.dataset.logo = logo;
}
} catch (error) {
showToast(error.message, 'error');
}
};
}
const saveLogo = document.getElementById('save-logo');
if (saveLogo) {
saveLogo.onclick = () => {
const preview = document.getElementById('profile-logo-preview');
const logo = preview ? preview.dataset.logo : null;
if (logo) {
const vendor = getFreshVendor();
if (vendor) {
vendor.logo = logo;
setData('currentVendor', vendor);
updateVendorInStorage(vendor);
updateVendorDashboard(vendor);
showToast('Logo saved!', 'success');
}
} else {
showToast('Upload a logo first', 'error');
}
};
}
const qrUpload = document.getElementById('qr-upload');
if (qrUpload) {
qrUpload.onchange = async function(e) {
const file = e.target.files[0];
if (!file) {
showToast('Please select a file first', 'error');
return;
}
try {
const qrCode = await compressImage(file);
const preview = document.getElementById('current-qr-preview');
if (preview) {
preview.innerHTML = `<img src="${qrCode}">`;
preview.dataset.qrCode = qrCode;
}
} catch (error) {
showToast(error.message, 'error');
}
};
}
const saveQR = document.getElementById('save-qr');
if (saveQR) {
saveQR.onclick = () => {
const preview = document.getElementById('current-qr-preview');
const qrCode = preview ? preview.dataset.qrCode : null;
if (qrCode) {
const vendor = getFreshVendor();
if (vendor) {
vendor.qrCode = qrCode;
setData('currentVendor', vendor);
updateVendorInStorage(vendor);
showToast('QR Code saved!', 'success');
}
} else showToast('Upload a QR code first', 'error');
};
}
const backBtns = [
'back-to-vendor-dashboard',
'back-to-vendor-dashboard-profile',
'back-to-vendor-dashboard-orders',
'back-to-vendor-dashboard-qr'
];
backBtns.forEach(id => {
const btn = document.getElementById(id);
if (btn) btn.onclick = () => showPage('vendor-dashboard');
});
// Admin navigation
const manageVendors = document.getElementById('manage-vendors');
const viewAllMenus = document.getElementById('view-all-menus');
const systemSettings = document.getElementById('system-settings');
const backToAdminDashboard = document.getElementById('back-to-admin-dashboard');
const backToAdminDashboardMenus = document.getElementById('back-to-admin-dashboard-menus');
const backToAdminDashboardSettings = document.getElementById('back-to-admin-dashboard-settings');
if (manageVendors) manageVendors.onclick = () => { loadVendors(); showPage('vendor-management-page'); };
if (viewAllMenus) viewAllMenus.onclick = () => { loadAllMenus(); showPage('view-all-menus-page'); };
if (systemSettings) systemSettings.onclick = () => { loadSystemSettings(); showPage('system-settings-page'); };
if (backToAdminDashboard) backToAdminDashboard.onclick = () => showPage('admin-dashboard');
if (backToAdminDashboardMenus) backToAdminDashboardMenus.onclick = () => showPage('admin-dashboard');
if (backToAdminDashboardSettings) backToAdminDashboardSettings.onclick = () => showPage('admin-dashboard');
const addVendorBtn = document.getElementById('add-vendor-btn');
const addMenuItemBtn = document.getElementById('add-menu-item-btn');
if (addVendorBtn) addVendorBtn.onclick = () => showModal('add-vendor-modal');
if (addMenuItemBtn) addMenuItemBtn.onclick = () => showModal('add-menu-item-modal');
const addVendorForm = document.getElementById('add-vendor-form');
if (addVendorForm) {
addVendorForm.onsubmit = (e) => {
e.preventDefault();
const username = document.getElementById('new-vendor-username').value.trim();
const password = document.getElementById('new-vendor-password').value.trim();
if (username !== username.toLowerCase() || password !== password.toLowerCase()) {
showToast('Username and password must be lowercase', 'error');
return;
}
const vendors = getData('vendors') || [];
if (vendors.find(v => v.username === username)) {
showToast('Username already exists', 'error');
return;
}
vendors.push({
id: Date.now(),
username: username,
password: password,
stallName: document.getElementById('new-vendor-stall-name').value,
canteen: document.getElementById('new-vendor-canteen').value,
logo: '',
menu: [], qrCode: '', orders: []
});
setData('vendors', vendors);
loadVendors();
hideModal('add-vendor-modal');
e.target.reset();
showToast('Vendor added!', 'success');
};
}
const editVendorForm = document.getElementById('edit-vendor-form');
if (editVendorForm) {
editVendorForm.onsubmit = (e) => {
e.preventDefault();
const vendorId = parseInt(document.getElementById('edit-vendor-id').value);
const username = document.getElementById('edit-vendor-username').value.trim();
const password = document.getElementById('edit-vendor-password').value.trim();
if (username !== username.toLowerCase() || password !== password.toLowerCase()) {
showToast('Username and password must be lowercase', 'error');
return;
}
const vendors = getData('vendors') || [];
const vendor = vendors.find(v => v.id === vendorId);
if (vendor) {
if (vendors.find(v => v.username === username && v.id !== vendorId)) {
showToast('Username already exists', 'error');
return;
}
vendor.stallName = document.getElementById('edit-vendor-stall-name').value;
vendor.username = username;
vendor.password = password;
vendor.canteen = document.getElementById('edit-vendor-canteen').value;
setData('vendors', vendors);
loadVendors();
hideModal('edit-vendor-modal');
showToast('Vendor updated!', 'success');
}
};
}
const addMenuItemForm = document.getElementById('add-menu-item-form');
if (addMenuItemForm) {
addMenuItemForm.onsubmit = async (e) => {
e.preventDefault();
const vendor = getFreshVendor();
if (!vendor) return;
const sizePricesInput = document.getElementById('item-size-prices').value;
const sizePrices = parseSizePrices(sizePricesInput);
if (Object.keys(sizePrices).length === 0) {
showToast('Please enter at least one valid size:price pair', 'error');
return;
}
const flavorsInput = document.getElementById('item-flavors').value;
const flavors = flavorsInput ? flavorsInput.split(',').map(f => f.trim()).filter(f => f) : [];
const newItem = {
id: Date.now(),
name: document.getElementById('item-name').value,
description: document.getElementById('item-description').value,
flavors: flavors,
sizePrices: sizePrices,
available: true,
image: ''
};
const file = document.getElementById('item-image').files[0];
if (file) {
try {
newItem.image = await compressImage(file);
} catch (error) {
showToast(error.message, 'error');
return;
}
}
vendor.menu.push(newItem);
setData('currentVendor', vendor);
updateVendorInStorage(vendor);
loadVendorMenu();
hideModal('add-menu-item-modal');
e.target.reset();
showToast('Item added!', 'success');
};
}
const editMenuItemForm = document.getElementById('edit-menu-item-form');
if (editMenuItemForm) {
editMenuItemForm.onsubmit = async (e) => {
e.preventDefault();
const itemId = parseInt(document.getElementById('edit-item-id').value);
const vendor = getFreshVendor();
if (!vendor) return;
const item = vendor.menu.find(i => i.id === itemId);
if (item) {
const sizePricesInput = document.getElementById('edit-item-size-prices').value;
const sizePrices = parseSizePrices(sizePricesInput);
if (Object.keys(sizePrices).length === 0) {
showToast('Please enter at least one valid size:price pair', 'error');
return;
}
const flavorsInput = document.getElementById('edit-item-flavors').value;
const flavors = flavorsInput ? flavorsInput.split(',').map(f => f.trim()).filter(f => f) : [];
item.name = document.getElementById('edit-item-name').value;
item.description = document.getElementById('edit-item-description').value;
item.flavors = flavors;
item.sizePrices = sizePrices;
const file = document.getElementById('edit-item-image').files[0];
if (file) {
try {
item.image = await compressImage(file);
} catch (error) {
showToast(error.message, 'error');
return;
}
}
setData('currentVendor', vendor);
updateVendorInStorage(vendor);
loadVendorMenu();
hideModal('edit-menu-item-modal');
showToast('Item updated!', 'success');
}
};
}
const saveSettingsBtn = document.getElementById('save-settings');
if (saveSettingsBtn) {
saveSettingsBtn.onclick = () => {
const settings = {
systemName: document.getElementById('system-name').value,
orderTimeout: parseInt(document.getElementById('order-timeout').value),
deleteVendorBehavior: document.getElementById('delete-vendor-behavior').value
};
setData('systemSettings', settings);
showToast('Settings saved!', 'success');
};
}
document.querySelectorAll('.close-modal').forEach(btn => {
btn.onclick = function() { this.closest('.modal').style.display = 'none'; };
});
const closeSuccessModal = document.getElementById('close-success-modal');
if (closeSuccessModal) {
closeSuccessModal.onclick = () => {
hideModal('order-success-modal');
showPage('student-dashboard');
};
}
const clearCartSwitch = document.getElementById('clear-cart-switch');
if (clearCartSwitch) {
clearCartSwitch.onclick = () => {
if (pendingSwitchItem) {
cart = [];
setData('currentCart', cart);
updateCartCount();
addToCart(pendingSwitchItem.item, pendingSwitchItem.vendorName, pendingSwitchItem.vendorId);
pendingSwitchItem = null;
}
hideModal('cart-switch-modal');
};
}
const keepCart = document.getElementById('keep-cart');
if (keepCart) {
keepCart.onclick = () => {
pendingSwitchItem = null;
hideModal('cart-switch-modal');
};
}
window.onclick = (e) => {
if (e.target.classList.contains('modal')) e.target.style.display = 'none';
};
// Auto-login if remembered
const remembered = getData('rememberedStudent');
if (remembered) {
const usernameInput = document.getElementById('student-username');
const passwordInput = document.getElementById('student-password');
const rememberCheck = document.getElementById('student-remember');
if (usernameInput) usernameInput.value = remembered.username;
if (passwordInput) passwordInput.value = remembered.password;
if (rememberCheck) rememberCheck.checked = true;
}
// Check if already logged in
if (getData('currentStudent')) {
const student = getData('currentStudent');
const nameEl = document.getElementById('student-name');
if (nameEl) nameEl.textContent = student.name;
showPage('student-dashboard');
updateNav(true);
} else if (getData('currentVendor')) {
const vendor = getFreshVendor();
if (vendor) {
updateVendorDashboard(vendor);
showPage('vendor-dashboard');
updateNav(true);
}
} else if (getData('currentAdmin')) {
showPage('admin-dashboard');
updateNav(true);
}
updateCartCount();
updateBackButton();
});
</script>
</body>
</html>