Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Burger Joint Billing System</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| background-color: #f8f9fa; | |
| } | |
| .menu-item:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1); | |
| } | |
| .menu-item { | |
| transition: all 0.3s ease; | |
| } | |
| .receipt { | |
| background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
| border-left: 5px solid #f59e0b; | |
| } | |
| .print-area { | |
| background-color: white; | |
| box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); | |
| } | |
| .tab-active { | |
| border-bottom: 3px solid #f59e0b; | |
| color: #f59e0b; | |
| font-weight: 600; | |
| } | |
| .floating-btn { | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 1px 3px rgba(0, 0, 0, 0.08); | |
| } | |
| .floating-btn:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 7px 14px rgba(0, 0, 0, 0.1), 0 3px 6px rgba(0, 0, 0, 0.08); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100"> | |
| <!-- Login Modal --> | |
| <div id="loginModal" class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"> | |
| <div class="bg-white rounded-lg p-8 w-full max-w-md"> | |
| <div class="text-center mb-6"> | |
| <img src="https://via.placeholder.com/150x50?text=Burger+Joint" alt="Burger Joint Logo" class="mx-auto h-12"> | |
| <h2 class="text-2xl font-bold text-gray-800 mt-4">Welcome Back!</h2> | |
| <p class="text-gray-600">Please login to your account</p> | |
| </div> | |
| <form id="loginForm"> | |
| <div class="mb-4"> | |
| <label for="username" class="block text-gray-700 text-sm font-medium mb-2">Username</label> | |
| <input type="text" id="username" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" placeholder="Enter your username" required> | |
| </div> | |
| <div class="mb-6"> | |
| <label for="password" class="block text-gray-700 text-sm font-medium mb-2">Password</label> | |
| <input type="password" id="password" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" placeholder="Enter your password" required> | |
| </div> | |
| <button type="submit" class="w-full bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| Login | |
| </button> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Main App Container (hidden until login) --> | |
| <div id="appContainer" class="hidden"> | |
| <!-- Header --> | |
| <header class="bg-white shadow-sm"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 flex justify-between items-center"> | |
| <div class="flex items-center"> | |
| <img src="https://via.placeholder.com/150x50?text=Burger+Joint" alt="Burger Joint Logo" class="h-8"> | |
| <span class="ml-2 text-xl font-bold text-gray-800 hidden md:inline">Burger Joint</span> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="hidden md:block"> | |
| <span id="currentTime" class="text-gray-600"></span> | |
| </div> | |
| <div class="relative"> | |
| <button id="userMenuButton" class="flex items-center space-x-2 focus:outline-none"> | |
| <span id="loggedInUser" class="font-medium text-gray-700"></span> | |
| <div class="w-8 h-8 rounded-full bg-amber-500 flex items-center justify-center text-white"> | |
| <i class="fas fa-user"></i> | |
| </div> | |
| </button> | |
| <div id="userMenu" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50"> | |
| <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Profile</a> | |
| <a href="#" id="logoutButton" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Logout</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> | |
| <!-- Tabs Navigation --> | |
| <div class="border-b border-gray-200 mb-6"> | |
| <nav class="-mb-px flex space-x-8"> | |
| <button id="billingTab" class="tab-active whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"> | |
| <i class="fas fa-cash-register mr-2"></i>Billing | |
| </button> | |
| <button id="menuTab" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"> | |
| <i class="fas fa-utensils mr-2"></i>Menu Management | |
| </button> | |
| <button id="reportsTab" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"> | |
| <i class="fas fa-chart-bar mr-2"></i>Reports | |
| </button> | |
| <button id="usersTab" class="whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300"> | |
| <i class="fas fa-users mr-2"></i>User Management | |
| </button> | |
| </nav> | |
| </div> | |
| <!-- Billing Section --> | |
| <div id="billingSection"> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <!-- Menu Items --> | |
| <div class="lg:col-span-2"> | |
| <div class="bg-white rounded-lg shadow p-4"> | |
| <div class="mb-4"> | |
| <h2 class="text-lg font-medium text-gray-900">Menu Items</h2> | |
| <div class="mt-2 relative"> | |
| <input type="text" id="menuSearch" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" placeholder="Search menu items..."> | |
| <div class="absolute left-3 top-2.5 text-gray-400"> | |
| <i class="fas fa-search"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4" id="menuItemsContainer"> | |
| <!-- Menu items will be populated here by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Order Summary --> | |
| <div class="lg:col-span-1"> | |
| <div class="bg-white rounded-lg shadow p-4 sticky top-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-lg font-medium text-gray-900">Order Summary</h2> | |
| <span id="orderNumber" class="text-sm text-gray-500">Order #0000</span> | |
| </div> | |
| <div class="border-b border-gray-200 mb-4"></div> | |
| <div class="mb-4"> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-gray-600">Staff:</span> | |
| <span id="staffName" class="font-medium">John Doe</span> | |
| </div> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-gray-600">Date:</span> | |
| <span id="orderDate" class="font-medium">May 15, 2023</span> | |
| </div> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-gray-600">Time:</span> | |
| <span id="orderTime" class="font-medium">10:30 AM</span> | |
| </div> | |
| </div> | |
| <div class="border-b border-gray-200 mb-4"></div> | |
| <div class="mb-4 max-h-64 overflow-y-auto" id="orderItemsContainer"> | |
| <!-- Order items will be populated here by JavaScript --> | |
| <div class="text-center text-gray-500 py-4"> | |
| <i class="fas fa-shopping-cart text-2xl mb-2"></i> | |
| <p>Your cart is empty</p> | |
| </div> | |
| </div> | |
| <div class="border-b border-gray-200 mb-4"></div> | |
| <div class="mb-4"> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-gray-600">Subtotal:</span> | |
| <span id="subtotal" class="font-medium">$0.00</span> | |
| </div> | |
| <div class="flex justify-between mb-2"> | |
| <span class="text-gray-600">Tax (10%):</span> | |
| <span id="tax" class="font-medium">$0.00</span> | |
| </div> | |
| <div class="flex justify-between text-lg font-bold"> | |
| <span>Total:</span> | |
| <span id="total" class="text-amber-600">$0.00</span> | |
| </div> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button id="clearOrderBtn" class="flex-1 bg-gray-200 text-gray-800 py-2 px-4 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition duration-150"> | |
| Clear | |
| </button> | |
| <button id="completeOrderBtn" class="flex-1 bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| Complete Order | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Menu Management Section (Hidden by default) --> | |
| <div id="menuManagementSection" class="hidden"> | |
| <div class="bg-white rounded-lg shadow overflow-hidden"> | |
| <div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center"> | |
| <h2 class="text-lg font-medium text-gray-900">Menu Management</h2> | |
| <button id="addMenuItemBtn" class="bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| <i class="fas fa-plus mr-2"></i>Add Item | |
| </button> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Image</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Description</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Price</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200" id="menuItemsTable"> | |
| <!-- Menu items will be populated here by JavaScript --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Reports Section (Hidden by default) --> | |
| <div id="reportsSection" class="hidden"> | |
| <div class="bg-white rounded-lg shadow p-6"> | |
| <h2 class="text-lg font-medium text-gray-900 mb-6">Sales Reports</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <h3 class="text-md font-medium text-gray-700 mb-4">Date Range</h3> | |
| <div class="flex flex-col sm:flex-row space-y-2 sm:space-y-0 sm:space-x-4"> | |
| <div class="flex-1"> | |
| <label for="startDate" class="block text-sm font-medium text-gray-700 mb-1">Start Date</label> | |
| <input type="date" id="startDate" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500"> | |
| </div> | |
| <div class="flex-1"> | |
| <label for="endDate" class="block text-sm font-medium text-gray-700 mb-1">End Date</label> | |
| <input type="date" id="endDate" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500"> | |
| </div> | |
| <div class="flex items-end"> | |
| <button id="generateReportBtn" class="bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| Generate | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-3 gap-4"> | |
| <div class="bg-amber-50 p-4 rounded-lg text-center"> | |
| <p class="text-sm font-medium text-amber-700">Today's Sales</p> | |
| <p class="text-2xl font-bold text-amber-600 mt-2">$<span id="todaySales">0.00</span></p> | |
| </div> | |
| <div class="bg-blue-50 p-4 rounded-lg text-center"> | |
| <p class="text-sm font-medium text-blue-700">This Week</p> | |
| <p class="text-2xl font-bold text-blue-600 mt-2">$<span id="weekSales">0.00</span></p> | |
| </div> | |
| <div class="bg-green-50 p-4 rounded-lg text-center"> | |
| <p class="text-sm font-medium text-green-700">This Month</p> | |
| <p class="text-2xl font-bold text-green-600 mt-2">$<span id="monthSales">0.00</span></p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8"> | |
| <div class="bg-white p-4 rounded-lg border border-gray-200"> | |
| <h3 class="text-md font-medium text-gray-700 mb-4">Daily Sales</h3> | |
| <canvas id="dailySalesChart" height="250"></canvas> | |
| </div> | |
| <div class="bg-white p-4 rounded-lg border border-gray-200"> | |
| <h3 class="text-md font-medium text-gray-700 mb-4">Top Selling Items</h3> | |
| <canvas id="topItemsChart" height="250"></canvas> | |
| </div> | |
| </div> | |
| <div class="bg-white p-4 rounded-lg border border-gray-200"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-md font-medium text-gray-700">Recent Orders</h3> | |
| <button id="exportReportBtn" class="bg-green-500 text-white py-1 px-3 rounded-md text-sm hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition duration-150"> | |
| <i class="fas fa-file-export mr-1"></i>Export | |
| </button> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Order #</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Staff</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200" id="recentOrdersTable"> | |
| <!-- Recent orders will be populated here by JavaScript --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- User Management Section (Hidden by default) --> | |
| <div id="userManagementSection" class="hidden"> | |
| <div class="bg-white rounded-lg shadow overflow-hidden"> | |
| <div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center"> | |
| <h2 class="text-lg font-medium text-gray-900">User Management</h2> | |
| <button id="addUserBtn" class="bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| <i class="fas fa-user-plus mr-2"></i>Add User | |
| </button> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Login</th> | |
| <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200" id="usersTable"> | |
| <!-- Users will be populated here by JavaScript --> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Add/Edit Menu Item Modal --> | |
| <div id="menuItemModal" class="hidden fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-md"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-medium text-gray-900" id="menuItemModalTitle">Add Menu Item</h3> | |
| <button id="closeMenuItemModal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <form id="menuItemForm"> | |
| <input type="hidden" id="menuItemId"> | |
| <div class="mb-4"> | |
| <label for="itemName" class="block text-gray-700 text-sm font-medium mb-2">Item Name</label> | |
| <input type="text" id="itemName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" required> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="itemPrice" class="block text-gray-700 text-sm font-medium mb-2">Price</label> | |
| <input type="number" id="itemPrice" step="0.01" min="0" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" required> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="itemDescription" class="block text-gray-700 text-sm font-medium mb-2">Description</label> | |
| <textarea id="itemDescription" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500"></textarea> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="itemImage" class="block text-gray-700 text-sm font-medium mb-2">Image</label> | |
| <div class="flex items-center"> | |
| <div class="mr-4"> | |
| <img id="itemImagePreview" src="https://via.placeholder.com/100x100?text=No+Image" alt="Item preview" class="w-16 h-16 rounded-md object-cover"> | |
| </div> | |
| <div class="flex-1"> | |
| <input type="file" id="itemImage" class="hidden" accept="image/*"> | |
| <button type="button" id="uploadImageBtn" class="bg-gray-100 text-gray-700 py-2 px-4 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| <i class="fas fa-upload mr-2"></i>Upload Image | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button type="button" id="cancelMenuItemBtn" class="bg-gray-200 text-gray-800 py-2 px-4 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition duration-150"> | |
| Cancel | |
| </button> | |
| <button type="submit" class="bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| Save Item | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Add/Edit User Modal --> | |
| <div id="userModal" class="hidden fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-md"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-medium text-gray-900" id="userModalTitle">Add User</h3> | |
| <button id="closeUserModal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <form id="userForm"> | |
| <input type="hidden" id="userId"> | |
| <div class="mb-4"> | |
| <label for="userName" class="block text-gray-700 text-sm font-medium mb-2">Full Name</label> | |
| <input type="text" id="userName" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" required> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="userEmail" class="block text-gray-700 text-sm font-medium mb-2">Email</label> | |
| <input type="email" id="userEmail" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" required> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="userRole" class="block text-gray-700 text-sm font-medium mb-2">Role</label> | |
| <select id="userRole" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" required> | |
| <option value="admin">Admin</option> | |
| <option value="staff">Staff</option> | |
| </select> | |
| </div> | |
| <div class="mb-4" id="passwordFields"> | |
| <label for="userPassword" class="block text-gray-700 text-sm font-medium mb-2">Password</label> | |
| <input type="password" id="userPassword" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-amber-500" required> | |
| </div> | |
| <div class="flex justify-end space-x-3"> | |
| <button type="button" id="cancelUserBtn" class="bg-gray-200 text-gray-800 py-2 px-4 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition duration-150"> | |
| Cancel | |
| </button> | |
| <button type="submit" class="bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| Save User | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Receipt Modal --> | |
| <div id="receiptModal" class="hidden fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-md"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-medium text-gray-900">Order Receipt</h3> | |
| <div class="flex space-x-2"> | |
| <button id="printReceiptBtn" class="bg-blue-500 text-white py-1 px-3 rounded-md text-sm hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition duration-150"> | |
| <i class="fas fa-print mr-1"></i>Print | |
| </button> | |
| <button id="closeReceiptModal" class="text-gray-500 hover:text-gray-700"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="print-area p-4" id="receiptContent"> | |
| <div class="text-center mb-4"> | |
| <h2 class="text-xl font-bold">Burger Joint</h2> | |
| <p class="text-sm text-gray-600">123 Burger Street, Foodville</p> | |
| <p class="text-sm text-gray-600">Tel: (123) 456-7890</p> | |
| </div> | |
| <div class="border-b border-gray-300 mb-3"></div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm">Order #:</span> | |
| <span class="text-sm font-medium" id="receiptOrderNumber">0000</span> | |
| </div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm">Date:</span> | |
| <span class="text-sm font-medium" id="receiptDate">May 15, 2023</span> | |
| </div> | |
| <div class="flex justify-between mb-3"> | |
| <span class="text-sm">Time:</span> | |
| <span class="text-sm font-medium" id="receiptTime">10:30 AM</span> | |
| </div> | |
| <div class="border-b border-gray-300 mb-3"></div> | |
| <div class="mb-3" id="receiptItems"> | |
| <!-- Receipt items will be populated here by JavaScript --> | |
| </div> | |
| <div class="border-b border-dashed border-gray-300 mb-3"></div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm">Subtotal:</span> | |
| <span class="text-sm font-medium" id="receiptSubtotal">$0.00</span> | |
| </div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm">Tax (10%):</span> | |
| <span class="text-sm font-medium" id="receiptTax">$0.00</span> | |
| </div> | |
| <div class="flex justify-between mb-3 font-bold"> | |
| <span>Total:</span> | |
| <span id="receiptTotal">$0.00</span> | |
| </div> | |
| <div class="border-b border-gray-300 mb-3"></div> | |
| <div class="text-center text-xs text-gray-600"> | |
| <p>Thank you for your order!</p> | |
| <p>Please visit us again</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Confirmation Modal --> | |
| <div id="confirmationModal" class="hidden fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50"> | |
| <div class="bg-white rounded-lg p-6 w-full max-w-sm"> | |
| <div class="text-center mb-4"> | |
| <i class="fas fa-exclamation-circle text-3xl text-amber-500 mb-3"></i> | |
| <h3 class="text-lg font-medium text-gray-900" id="confirmationTitle">Confirm Action</h3> | |
| <p class="text-sm text-gray-500 mt-1" id="confirmationMessage">Are you sure you want to perform this action?</p> | |
| </div> | |
| <div class="flex justify-center space-x-3"> | |
| <button id="cancelConfirmBtn" class="bg-gray-200 text-gray-800 py-2 px-4 rounded-md hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition duration-150"> | |
| Cancel | |
| </button> | |
| <button id="confirmActionBtn" class="bg-amber-500 text-white py-2 px-4 rounded-md hover:bg-amber-600 focus:outline-none focus:ring-2 focus:ring-amber-500 focus:ring-offset-2 transition duration-150"> | |
| Confirm | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <script> | |
| // Database simulation | |
| const database = { | |
| users: [ | |
| { id: 1, name: 'Admin User', email: 'admin@burgerjoint.com', password: 'admin123', role: 'admin', lastLogin: '2023-05-15 10:00' }, | |
| { id: 2, name: 'Staff User', email: 'staff@burgerjoint.com', password: 'staff123', role: 'staff', lastLogin: '2023-05-15 09:30' } | |
| ], | |
| menuItems: [ | |
| { id: 1, name: 'Classic Burger', price: 8.99, description: 'Juicy beef patty with lettuce, tomato, onion, and our special sauce', image: 'https://via.placeholder.com/300x200?text=Classic+Burger' }, | |
| { id: 2, name: 'Cheeseburger', price: 9.99, description: 'Classic burger with a slice of American cheese', image: 'https://via.placeholder.com/300x200?text=Cheeseburger' }, | |
| { id: 3, name: 'Bacon Burger', price: 10.99, description: 'Juicy beef patty with crispy bacon and cheddar cheese', image: 'https://via.placeholder.com/300x200?text=Bacon+Burger' }, | |
| { id: 4, name: 'Veggie Burger', price: 8.99, description: 'Plant-based patty with fresh veggies and vegan mayo', image: 'https://via.placeholder.com/300x200?text=Veggie+Burger' }, | |
| { id: 5, name: 'Chicken Sandwich', price: 7.99, description: 'Grilled chicken breast with lettuce and mayo', image: 'https://via.placeholder.com/300x200?text=Chicken+Sandwich' }, | |
| { id: 6, name: 'French Fries', price: 3.99, description: 'Crispy golden fries with a pinch of salt', image: 'https://via.placeholder.com/300x200?text=French+Fries' }, | |
| { id: 7, name: 'Onion Rings', price: 4.99, description: 'Crispy battered onion rings with dipping sauce', image: 'https://via.placeholder.com/300x200?text=Onion+Rings' }, | |
| { id: 8, name: 'Soda', price: 2.49, description: 'Refreshing soda in various flavors', image: 'https://via.placeholder.com/300x200?text=Soda' } | |
| ], | |
| orders: [ | |
| { id: 1001, userId: 2, totalPrice: 15.98, date: '2023-05-14', time: '12:30 PM', items: [ | |
| { menuItemId: 1, quantity: 1, price: 8.99 }, | |
| { menuItemId: 6, quantity: 1, price: 3.99 }, | |
| { menuItemId: 8, quantity: 2, price: 2.49 } | |
| ]}, | |
| { id: 1002, userId: 2, totalPrice: 24.97, date: '2023-05-14', time: '1:45 PM', items: [ | |
| { menuItemId: 3, quantity: 2, price: 10.99 }, | |
| { menuItemId: 7, quantity: 1, price: 4.99 } | |
| ]}, | |
| { id: 1003, userId: 2, totalPrice: 18.47, date: '2023-05-15', time: '9:15 AM', items: [ | |
| { menuItemId: 2, quantity: 1, price: 9.99 }, | |
| { menuItemId: 5, quantity: 1, price: 7.99 }, | |
| { menuItemId: 8, quantity: 1, price: 2.49 } | |
| ]} | |
| ], | |
| currentOrder: { | |
| id: null, | |
| userId: null, | |
| items: [], | |
| subtotal: 0, | |
| tax: 0, | |
| total: 0 | |
| } | |
| }; | |
| // App state | |
| const state = { | |
| currentUser: null, | |
| currentTab: 'billing', | |
| editingItemId: null, | |
| editingUserId: null, | |
| confirmationAction: null, | |
| confirmationData: null | |
| }; | |
| // DOM Elements | |
| const elements = { | |
| loginModal: document.getElementById('loginModal'), | |
| appContainer: document.getElementById('appContainer'), | |
| loggedInUser: document.getElementById('loggedInUser'), | |
| userMenuButton: document.getElementById('userMenuButton'), | |
| userMenu: document.getElementById('userMenu'), | |
| logoutButton: document.getElementById('logoutButton'), | |
| currentTime: document.getElementById('currentTime'), | |
| // Tabs | |
| billingTab: document.getElementById('billingTab'), | |
| menuTab: document.getElementById('menuTab'), | |
| reportsTab: document.getElementById('reportsTab'), | |
| usersTab: document.getElementById('usersTab'), | |
| // Sections | |
| billingSection: document.getElementById('billingSection'), | |
| menuManagementSection: document.getElementById('menuManagementSection'), | |
| reportsSection: document.getElementById('reportsSection'), | |
| userManagementSection: document.getElementById('userManagementSection'), | |
| // Billing section | |
| menuItemsContainer: document.getElementById('menuItemsContainer'), | |
| menuSearch: document.getElementById('menuSearch'), | |
| orderItemsContainer: document.getElementById('orderItemsContainer'), | |
| orderNumber: document.getElementById('orderNumber'), | |
| staffName: document.getElementById('staffName'), | |
| orderDate: document.getElementById('orderDate'), | |
| orderTime: document.getElementById('orderTime'), | |
| subtotal: document.getElementById('subtotal'), | |
| tax: document.getElementById('tax'), | |
| total: document.getElementById('total'), | |
| clearOrderBtn: document.getElementById('clearOrderBtn'), | |
| completeOrderBtn: document.getElementById('completeOrderBtn'), | |
| // Menu management | |
| menuItemsTable: document.getElementById('menuItemsTable'), | |
| addMenuItemBtn: document.getElementById('addMenuItemBtn'), | |
| // Reports | |
| startDate: document.getElementById('startDate'), | |
| endDate: document.getElementById('endDate'), | |
| generateReportBtn: document.getElementById('generateReportBtn'), | |
| todaySales: document.getElementById('todaySales'), | |
| weekSales: document.getElementById('weekSales'), | |
| monthSales: document.getElementById('monthSales'), | |
| dailySalesChart: document.getElementById('dailySalesChart'), | |
| topItemsChart: document.getElementById('topItemsChart'), | |
| recentOrdersTable: document.getElementById('recentOrdersTable'), | |
| exportReportBtn: document.getElementById('exportReportBtn'), | |
| // User management | |
| usersTable: document.getElementById('usersTable'), | |
| addUserBtn: document.getElementById('addUserBtn'), | |
| // Modals | |
| menuItemModal: document.getElementById('menuItemModal'), | |
| menuItemModalTitle: document.getElementById('menuItemModalTitle'), | |
| menuItemForm: document.getElementById('menuItemForm'), | |
| menuItemId: document.getElementById('menuItemId'), | |
| itemName: document.getElementById('itemName'), | |
| itemPrice: document.getElementById('itemPrice'), | |
| itemDescription: document.getElementById('itemDescription'), | |
| itemImage: document.getElementById('itemImage'), | |
| itemImagePreview: document.getElementById('itemImagePreview'), | |
| uploadImageBtn: document.getElementById('uploadImageBtn'), | |
| cancelMenuItemBtn: document.getElementById('cancelMenuItemBtn'), | |
| closeMenuItemModal: document.getElementById('closeMenuItemModal'), | |
| userModal: document.getElementById('userModal'), | |
| userModalTitle: document.getElementById('userModalTitle'), | |
| userForm: document.getElementById('userForm'), | |
| userId: document.getElementById('userId'), | |
| userName: document.getElementById('userName'), | |
| userEmail: document.getElementById('userEmail'), | |
| userRole: document.getElementById('userRole'), | |
| userPassword: document.getElementById('userPassword'), | |
| passwordFields: document.getElementById('passwordFields'), | |
| cancelUserBtn: document.getElementById('cancelUserBtn'), | |
| closeUserModal: document.getElementById('closeUserModal'), | |
| receiptModal: document.getElementById('receiptModal'), | |
| receiptContent: document.getElementById('receiptContent'), | |
| receiptOrderNumber: document.getElementById('receiptOrderNumber'), | |
| receiptDate: document.getElementById('receiptDate'), | |
| receiptTime: document.getElementById('receiptTime'), | |
| receiptItems: document.getElementById('receiptItems'), | |
| receiptSubtotal: document.getElementById('receiptSubtotal'), | |
| receiptTax: document.getElementById('receiptTax'), | |
| receiptTotal: document.getElementById('receiptTotal'), | |
| printReceiptBtn: document.getElementById('printReceiptBtn'), | |
| closeReceiptModal: document.getElementById('closeReceiptModal'), | |
| confirmationModal: document.getElementById('confirmationModal'), | |
| confirmationTitle: document.getElementById('confirmationTitle'), | |
| confirmationMessage: document.getElementById('confirmationMessage'), | |
| cancelConfirmBtn: document.getElementById('cancelConfirmBtn'), | |
| confirmActionBtn: document.getElementById('confirmActionBtn'), | |
| // Forms | |
| loginForm: document.getElementById('loginForm'), | |
| username: document.getElementById('username'), | |
| password: document.getElementById('password') | |
| }; | |
| // Initialize the app | |
| function init() { | |
| // Set current date for date inputs | |
| const today = new Date().toISOString().split('T')[0]; | |
| elements.startDate.value = today; | |
| elements.endDate.value = today; | |
| // Update clock | |
| updateClock(); | |
| setInterval(updateClock, 1000); | |
| // Generate a random order number | |
| generateOrderNumber(); | |
| // Set current date in order summary | |
| const now = new Date(); | |
| elements.orderDate.textContent = now.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); | |
| elements.orderTime.textContent = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); | |
| // Event listeners | |
| setupEventListeners(); | |
| // Show login modal by default | |
| elements.loginModal.classList.remove('hidden'); | |
| } | |
| // Update clock | |
| function updateClock() { | |
| const now = new Date(); | |
| elements.currentTime.textContent = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', second: '2-digit' }); | |
| } | |
| // Generate a random order number | |
| function generateOrderNumber() { | |
| const orderNumber = Math.floor(1000 + Math.random() * 9000); | |
| database.currentOrder.id = orderNumber; | |
| elements.orderNumber.textContent = `Order #${orderNumber}`; | |
| elements.receiptOrderNumber.textContent = orderNumber; | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Login form | |
| elements.loginForm.addEventListener('submit', handleLogin); | |
| // Logout button | |
| elements.logoutButton.addEventListener('click', handleLogout); | |
| // User menu toggle | |
| elements.userMenuButton.addEventListener('click', () => { | |
| elements.userMenu.classList.toggle('hidden'); | |
| }); | |
| // Tab navigation | |
| elements.billingTab.addEventListener('click', () => switchTab('billing')); | |
| elements.menuTab.addEventListener('click', () => switchTab('menu')); | |
| elements.reportsTab.addEventListener('click', () => switchTab('reports')); | |
| elements.usersTab.addEventListener('click', () => switchTab('users')); | |
| // Menu search | |
| elements.menuSearch.addEventListener('input', filterMenuItems); | |
| // Order buttons | |
| elements.clearOrderBtn.addEventListener('click', clearOrder); | |
| elements.completeOrderBtn.addEventListener('click', completeOrder); | |
| // Menu item modal | |
| elements.addMenuItemBtn.addEventListener('click', () => openMenuItemModal(null)); | |
| elements.uploadImageBtn.addEventListener('click', () => elements.itemImage.click()); | |
| elements.itemImage.addEventListener('change', handleImageUpload); | |
| elements.cancelMenuItemBtn.addEventListener('click', closeMenuItemModal); | |
| elements.closeMenuItemModal.addEventListener('click', closeMenuItemModal); | |
| elements.menuItemForm.addEventListener('submit', saveMenuItem); | |
| // User modal | |
| elements.addUserBtn.addEventListener('click', () => openUserModal(null)); | |
| elements.cancelUserBtn.addEventListener('click', closeUserModal); | |
| elements.closeUserModal.addEventListener('click', closeUserModal); | |
| elements.userForm.addEventListener('submit', saveUser); | |
| // Receipt modal | |
| elements.printReceiptBtn.addEventListener('click', printReceipt); | |
| elements.closeReceiptModal.addEventListener('click', () => { | |
| elements.receiptModal.classList.add('hidden'); | |
| }); | |
| // Confirmation modal | |
| elements.cancelConfirmBtn.addEventListener('click', () => { | |
| elements.confirmationModal.classList.add('hidden'); | |
| }); | |
| elements.confirmActionBtn.addEventListener('click', confirmAction); | |
| // Generate report | |
| elements.generateReportBtn.addEventListener('click', generateReport); | |
| elements.exportReportBtn.addEventListener('click', exportReport); | |
| } | |
| // Handle login | |
| function handleLogin(e) { | |
| e.preventDefault(); | |
| const username = elements.username.value; | |
| const password = elements.password.value; | |
| // Find user in database | |
| const user = database.users.find(u => | |
| (u.email === username || u.name === username) && u.password === password | |
| ); | |
| if (user) { | |
| // Successful login | |
| state.currentUser = user; | |
| elements.loggedInUser.textContent = user.name; | |
| // Hide login modal and show app | |
| elements.loginModal.classList.add('hidden'); | |
| elements.appContainer.classList.remove('hidden'); | |
| // Load appropriate content based on role | |
| if (user.role === 'admin') { | |
| loadMenuItemsTable(); | |
| loadUsersTable(); | |
| loadReports(); | |
| } else { | |
| // Staff can only see billing | |
| elements.menuTab.classList.add('hidden'); | |
| elements.reportsTab.classList.add('hidden'); | |
| elements.usersTab.classList.add('hidden'); | |
| } | |
| // Load menu items for billing | |
| loadMenuItems(); | |
| // Set staff name in order summary | |
| elements.staffName.textContent = user.name; | |
| } else { | |
| // Failed login | |
| alert('Invalid username or password'); | |
| } | |
| } | |
| // Handle logout | |
| function handleLogout() { | |
| state.currentUser = null; | |
| elements.appContainer.classList.add('hidden'); | |
| elements.loginModal.classList.remove('hidden'); | |
| elements.username.value = ''; | |
| elements.password.value = ''; | |
| elements.userMenu.classList.add('hidden'); | |
| } | |
| // Switch between tabs | |
| function switchTab(tab) { | |
| state.currentTab = tab; | |
| // Update tab styling | |
| elements.billingTab.classList.remove('tab-active'); | |
| elements.menuTab.classList.remove('tab-active'); | |
| elements.reportsTab.classList.remove('tab-active'); | |
| elements.usersTab.classList.remove('tab-active'); | |
| // Hide all sections | |
| elements.billingSection.classList.add('hidden'); | |
| elements.menuManagementSection.classList.add('hidden'); | |
| elements.reportsSection.classList.add('hidden'); | |
| elements.userManagementSection.classList.add('hidden'); | |
| // Show selected tab and section | |
| switch (tab) { | |
| case 'billing': | |
| elements.billingTab.classList.add('tab-active'); | |
| elements.billingSection.classList.remove('hidden'); | |
| break; | |
| case 'menu': | |
| elements.menuTab.classList.add('tab-active'); | |
| elements.menuManagementSection.classList.remove('hidden'); | |
| break; | |
| case 'reports': | |
| elements.reportsTab.classList.add('tab-active'); | |
| elements.reportsSection.classList.remove('hidden'); | |
| break; | |
| case 'users': | |
| elements.usersTab.classList.add('tab-active'); | |
| elements.userManagementSection.classList.remove('hidden'); | |
| break; | |
| } | |
| } | |
| // Load menu items for billing | |
| function loadMenuItems() { | |
| elements.menuItemsContainer.innerHTML = ''; | |
| database.menuItems.forEach(item => { | |
| const menuItem = document.createElement('div'); | |
| menuItem.className = 'menu-item bg-white rounded-lg shadow overflow-hidden cursor-pointer'; | |
| menuItem.innerHTML = ` | |
| <div class="h-32 bg-gray-200 overflow-hidden"> | |
| <img src="${item.image}" alt="${item.name}" class="w-full h-full object-cover"> | |
| </div> | |
| <div class="p-3"> | |
| <h3 class="font-medium text-gray-900 truncate">${item.name}</h3> | |
| <p class="text-sm text-gray-500 mb-2 truncate">${item.description}</p> | |
| <div class="flex justify-between items-center"> | |
| <span class="font-bold text-amber-600">$${item.price.toFixed(2)}</span> | |
| <button class="add-to-cart bg-amber-500 text-white rounded-full w-8 h-8 flex items-center justify-center hover:bg-amber-600 focus:outline-none"> | |
| <i class="fas fa-plus"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| // Add event listener to the add to cart button | |
| const addToCartBtn = menuItem.querySelector('.add-to-cart'); | |
| addToCartBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| addToCart(item); | |
| }); | |
| elements.menuItemsContainer.appendChild(menuItem); | |
| }); | |
| } | |
| // Filter menu items based on search input | |
| function filterMenuItems() { | |
| const searchTerm = elements.menuSearch.value.toLowerCase(); | |
| const menuItems = document.querySelectorAll('.menu-item'); | |
| menuItems.forEach(item => { | |
| const name = item.querySelector('h3').textContent.toLowerCase(); | |
| const description = item.querySelector('p').textContent.toLowerCase(); | |
| if (name.includes(searchTerm) || description.includes(searchTerm)) { | |
| item.classList.remove('hidden'); | |
| } else { | |
| item.classList.add('hidden'); | |
| } | |
| }); | |
| } | |
| // Add item to cart | |
| function addToCart(item) { | |
| // Check if item already exists in cart | |
| const existingItem = database.currentOrder.items.find(i => i.menuItemId === item.id); | |
| if (existingItem) { | |
| existingItem.quantity += 1; | |
| } else { | |
| database.currentOrder.items.push({ | |
| menuItemId: item.id, | |
| quantity: 1, | |
| price: item.price | |
| }); | |
| } | |
| // Update order summary | |
| updateOrderSummary(); | |
| } | |
| // Update order summary | |
| function updateOrderSummary() { | |
| // Clear current items | |
| elements.orderItemsContainer.innerHTML = ''; | |
| if (database.currentOrder.items.length === 0) { | |
| elements.orderItemsContainer.innerHTML = ` | |
| <div class="text-center text-gray-500 py-4"> | |
| <i class="fas fa-shopping-cart text-2xl mb-2"></i> | |
| <p>Your cart is empty</p> | |
| </div> | |
| `; | |
| // Update totals | |
| elements.subtotal.textContent = '$0.00'; | |
| elements.tax.textContent = '$0.00'; | |
| elements.total.textContent = '$0.00'; | |
| return; | |
| } | |
| // Calculate totals | |
| let subtotal = 0; | |
| // Add each item to the order summary | |
| database.currentOrder.items.forEach(item => { | |
| const menuItem = database.menuItems.find(m => m.id === item.menuItemId); | |
| const itemTotal = item.quantity * item.price; | |
| subtotal += itemTotal; | |
| const orderItem = document.createElement('div'); | |
| orderItem.className = 'flex justify-between items-center py-2 border-b border-gray-100'; | |
| orderItem.innerHTML = ` | |
| <div class="flex-1"> | |
| <h4 class="text-sm font-medium">${menuItem.name}</h4> | |
| <p class="text-xs text-gray-500">$${item.price.toFixed(2)} x ${item.quantity}</p> | |
| </div> | |
| <div class="flex items-center"> | |
| <span class="text-sm font-medium mr-3">$${itemTotal.toFixed(2)}</span> | |
| <div class="flex items-center space-x-1"> | |
| <button class="decrease-quantity text-xs bg-gray-200 text-gray-700 rounded-full w-5 h-5 flex items-center justify-center hover:bg-gray-300"> | |
| <i class="fas fa-minus"></i> | |
| </button> | |
| <button class="increase-quantity text-xs bg-gray-200 text-gray-700 rounded-full w-5 h-5 flex items-center justify-center hover:bg-gray-300"> | |
| <i class="fas fa-plus"></i> | |
| </button> | |
| <button class="remove-item text-xs bg-red-100 text-red-600 rounded-full w-5 h-5 flex items-center justify-center hover:bg-red-200 ml-1"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| // Add event listeners to quantity buttons | |
| const decreaseBtn = orderItem.querySelector('.decrease-quantity'); | |
| const increaseBtn = orderItem.querySelector('.increase-quantity'); | |
| const removeBtn = orderItem.querySelector('.remove-item'); | |
| decreaseBtn.addEventListener('click', () => updateQuantity(item.menuItemId, -1)); | |
| increaseBtn.addEventListener('click', () => updateQuantity(item.menuItemId, 1)); | |
| removeBtn.addEventListener('click', () => removeItem(item.menuItemId)); | |
| elements.orderItemsContainer.appendChild(orderItem); | |
| }); | |
| // Calculate tax and total | |
| const tax = subtotal * 0.10; // 10% tax | |
| const total = subtotal + tax; | |
| // Update totals in UI | |
| elements.subtotal.textContent = `$${subtotal.toFixed(2)}`; | |
| elements.tax.textContent = `$${tax.toFixed(2)}`; | |
| elements.total.textContent = `$${total.toFixed(2)}`; | |
| // Update totals in database | |
| database.currentOrder.subtotal = subtotal; | |
| database.currentOrder.tax = tax; | |
| database.currentOrder.total = total; | |
| } | |
| // Update item quantity | |
| function updateQuantity(menuItemId, change) { | |
| const item = database.currentOrder.items.find(i => i.menuItemId === menuItemId); | |
| if (item) { | |
| item.quantity += change; | |
| // Remove item if quantity reaches zero | |
| if (item.quantity <= 0) { | |
| database.currentOrder.items = database.currentOrder.items.filter(i => i.menuItemId !== menuItemId); | |
| } | |
| // Update order summary | |
| updateOrderSummary(); | |
| } | |
| } | |
| // Remove item from cart | |
| function removeItem(menuItemId) { | |
| database.currentOrder.items = database.currentOrder.items.filter(i => i.menuItemId !== menuItemId); | |
| updateOrderSummary(); | |
| } | |
| // Clear current order | |
| function clearOrder() { | |
| database.currentOrder.items = []; | |
| updateOrderSummary(); | |
| } | |
| // Complete order and generate receipt | |
| function completeOrder() { | |
| if (database.currentOrder.items.length === 0) { | |
| alert('Cannot complete an empty order'); | |
| return; | |
| } | |
| // Set user ID for the order | |
| database.currentOrder.userId = state.currentUser.id; | |
| // Add current order to orders history | |
| const now = new Date(); | |
| const newOrder = { | |
| id: database.currentOrder.id, | |
| userId: database.currentOrder.userId, | |
| totalPrice: database.currentOrder.total, | |
| date: now.toISOString().split('T')[0], | |
| time: now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }), | |
| items: [...database.currentOrder.items] | |
| }; | |
| database.orders.push(newOrder); | |
| // Generate receipt | |
| generateReceipt(); | |
| // Clear current order and generate a new order number | |
| clearOrder(); | |
| generateOrderNumber(); | |
| } | |
| // Generate receipt | |
| function generateReceipt() { | |
| // Update receipt details | |
| const now = new Date(); | |
| elements.receiptDate.textContent = now.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }); | |
| elements.receiptTime.textContent = now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); | |
| // Clear previous items | |
| elements.receiptItems.innerHTML = ''; | |
| // Add items to receipt | |
| database.currentOrder.items.forEach(item => { | |
| const menuItem = database.menuItems.find(m => m.id === item.menuItemId); | |
| const itemTotal = item.quantity * item.price; | |
| const receiptItem = document.createElement('div'); | |
| receiptItem.className = 'flex justify-between py-1'; | |
| receiptItem.innerHTML = ` | |
| <div> | |
| <span class="text-sm font-medium">${menuItem.name}</span> | |
| <span class="text-xs text-gray-500 block">$${item.price.toFixed(2)} x ${item.quantity}</span> | |
| </div> | |
| <span class="text-sm font-medium">$${itemTotal.toFixed(2)}</span> | |
| `; | |
| elements.receiptItems.appendChild(receiptItem); | |
| }); | |
| // Update totals | |
| elements.receiptSubtotal.textContent = `$${database.currentOrder.subtotal.toFixed(2)}`; | |
| elements.receiptTax.textContent = `$${database.currentOrder.tax.toFixed(2)}`; | |
| elements.receiptTotal.textContent = `$${database.currentOrder.total.toFixed(2)}`; | |
| // Show receipt modal | |
| elements.receiptModal.classList.remove('hidden'); | |
| } | |
| // Print receipt | |
| function printReceipt() { | |
| const printContents = elements.receiptContent.innerHTML; | |
| const originalContents = document.body.innerHTML; | |
| document.body.innerHTML = printContents; | |
| window.print(); | |
| document.body.innerHTML = originalContents; | |
| // Reattach event listeners | |
| setupEventListeners(); | |
| } | |
| // Load menu items for management table | |
| function loadMenuItemsTable() { | |
| elements.menuItemsTable.innerHTML = ''; | |
| database.menuItems.forEach(item => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="flex-shrink-0 h-10 w-10"> | |
| <img class="h-10 w-10 rounded-md object-cover" src="${item.image}" alt="${item.name}"> | |
| </div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="text-sm font-medium text-gray-900">${item.name}</div> | |
| </td> | |
| <td class="px-6 py-4"> | |
| <div class="text-sm text-gray-500 max-w-xs truncate">${item.description}</div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="text-sm text-gray-900">$${item.price.toFixed(2)}</div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | |
| <button class="edit-menu-item text-amber-600 hover:text-amber-900 mr-3" data-id="${item.id}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="delete-menu-item text-red-600 hover:text-red-900" data-id="${item.id}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </td> | |
| `; | |
| // Add event listeners to edit and delete buttons | |
| const editBtn = row.querySelector('.edit-menu-item'); | |
| const deleteBtn = row.querySelector('.delete-menu-item'); | |
| editBtn.addEventListener('click', () => openMenuItemModal(item.id)); | |
| deleteBtn.addEventListener('click', () => confirmDeleteMenuItem(item.id)); | |
| elements.menuItemsTable.appendChild(row); | |
| }); | |
| } | |
| // Open menu item modal for adding/editing | |
| function openMenuItemModal(itemId) { | |
| if (itemId) { | |
| // Editing existing item | |
| state.editingItemId = itemId; | |
| const item = database.menuItems.find(i => i.id === itemId); | |
| elements.menuItemModalTitle.textContent = 'Edit Menu Item'; | |
| elements.menuItemId.value = itemId; | |
| elements.itemName.value = item.name; | |
| elements.itemPrice.value = item.price; | |
| elements.itemDescription.value = item.description; | |
| elements.itemImagePreview.src = item.image; | |
| } else { | |
| // Adding new item | |
| state.editingItemId = null; | |
| elements.menuItemModalTitle.textContent = 'Add Menu Item'; | |
| elements.menuItemId.value = ''; | |
| elements.itemName.value = ''; | |
| elements.itemPrice.value = ''; | |
| elements.itemDescription.value = ''; | |
| elements.itemImagePreview.src = 'https://via.placeholder.com/100x100?text=No+Image'; | |
| } | |
| elements.menuItemModal.classList.remove('hidden'); | |
| } | |
| // Close menu item modal | |
| function closeMenuItemModal() { | |
| elements.menuItemModal.classList.add('hidden'); | |
| } | |
| // Handle image upload | |
| function handleImageUpload(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| elements.itemImagePreview.src = event.target.result; | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| } | |
| // Save menu item (add or update) | |
| function saveMenuItem(e) { | |
| e.preventDefault(); | |
| const itemId = parseInt(elements.menuItemId.value); | |
| const name = elements.itemName.value; | |
| const price = parseFloat(elements.itemPrice.value); | |
| const description = elements.itemDescription.value; | |
| const image = elements.itemImagePreview.src; | |
| if (itemId) { | |
| // Update existing item | |
| const index = database.menuItems.findIndex(i => i.id === itemId); | |
| if (index !== -1) { | |
| database.menuItems[index] = { | |
| id: itemId, | |
| name, | |
| price, | |
| description, | |
| image | |
| }; | |
| } | |
| } else { | |
| // Add new item | |
| const newId = database.menuItems.length > 0 ? | |
| Math.max(...database.menuItems.map(i => i.id)) + 1 : 1; | |
| database.menuItems.push({ | |
| id: newId, | |
| name, | |
| price, | |
| description, | |
| image | |
| }); | |
| } | |
| // Refresh tables | |
| loadMenuItems(); | |
| loadMenuItemsTable(); | |
| // Close modal | |
| closeMenuItemModal(); | |
| } | |
| // Confirm delete menu item | |
| function confirmDeleteMenuItem(itemId) { | |
| state.confirmationAction = 'deleteMenuItem'; | |
| state.confirmationData = itemId; | |
| const item = database.menuItems.find(i => i.id === itemId); | |
| elements.confirmationTitle.textContent = 'Delete Menu Item'; | |
| elements.confirmationMessage.textContent = `Are you sure you want to delete "${item.name}"? This action cannot be undone.`; | |
| elements.confirmationModal.classList.remove('hidden'); | |
| } | |
| // Delete menu item | |
| function deleteMenuItem(itemId) { | |
| database.menuItems = database.menuItems.filter(i => i.id !== itemId); | |
| loadMenuItems(); | |
| loadMenuItemsTable(); | |
| } | |
| // Load users table | |
| function loadUsersTable() { | |
| elements.usersTable.innerHTML = ''; | |
| database.users.forEach(user => { | |
| const row = document.createElement('tr'); | |
| row.innerHTML = ` | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="text-sm font-medium text-gray-900">${user.name}</div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="text-sm text-gray-500">${user.email}</div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full ${user.role === 'admin' ? 'bg-green-100 text-green-800' : 'bg-blue-100 text-blue-800'}"> | |
| ${user.role.charAt(0).toUpperCase() + user.role.slice(1)} | |
| </span> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap"> | |
| <div class="text-sm text-gray-500">${user.lastLogin || 'Never'}</div> | |
| </td> | |
| <td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> | |
| <button class="edit-user text-amber-600 hover:text-amber-900 mr-3" data-id="${user.id}"> | |
| <i class="fas fa-edit"></i> | |
| </button> | |
| <button class="delete-user text-red-600 hover:text-red-900" data-id="${user.id}"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </td> | |
| `; | |
| // Add event listeners to edit and delete buttons | |
| const editBtn = row.querySelector('.edit-user'); | |
| const deleteBtn = row.querySelector('.delete-user'); | |
| editBtn.addEventListener('click', () => openUserModal(user.id)); | |
| deleteBtn.addEventListener('click', () => confirmDeleteUser(user.id)); | |
| elements.usersTable.appendChild(row); | |
| }); | |
| } | |
| // Open user modal for adding/editing | |
| function openUserModal(userId) { | |
| if (userId) { | |
| // Editing existing user | |
| state.editingUserId = userId; | |
| const user = database.users.find(u => u.id === userId); | |
| elements.userModalTitle.textContent = 'Edit User'; | |
| elements.userId.value = userId; | |
| elements.userName.value = user.name; | |
| elements.userEmail.value = user.email; | |
| elements.userRole.value = user.role; | |
| // Hide password field for existing users | |
| elements.passwordFields.classList.add('hidden'); | |
| elements.userPassword.required = false; | |
| } else { | |
| // Adding new user | |
| state.editingUserId = null; | |
| elements.userModalTitle.textContent = 'Add User'; | |
| elements.userId.value = ''; | |
| elements.userName.value = ''; | |
| elements.userEmail.value = ''; | |
| elements.userRole.value = 'staff'; | |
| // Show password field for new users | |
| elements.passwordFields.classList.remove('hidden'); | |
| elements.userPassword.required = true; | |
| elements.userPassword.value = ''; | |
| } | |
| elements.userModal.classList.remove('hidden'); | |
| } | |
| // Close user modal | |
| function closeUserModal() { | |
| elements.userModal.classList.add('hidden'); | |
| } | |
| // Save user (add or update) | |
| function saveUser(e) { | |
| e.preventDefault(); | |
| const userId = parseInt(elements.userId.value); | |
| const name = elements.userName.value; | |
| const email = elements.userEmail.value; | |
| const role = elements.userRole.value; | |
| const password = elements.userPassword.value; | |
| if (userId) { | |
| // Update existing user | |
| const index = database.users.findIndex(u => u.id === userId); | |
| if (index !== -1) { | |
| database.users[index] = { | |
| ...database.users[index], | |
| name, | |
| email, | |
| role | |
| }; | |
| // Update password if provided | |
| if (password) { | |
| database.users[index].password = password; | |
| } | |
| } | |
| } else { | |
| // Add new user | |
| const newId = database.users.length > 0 ? | |
| Math.max(...database.users.map(u => u.id)) + 1 : 1; | |
| database.users.push({ | |
| id: newId, | |
| name, | |
| email, | |
| password, | |
| role, | |
| lastLogin: null | |
| }); | |
| } | |
| // Refresh table | |
| loadUsersTable(); | |
| // Close modal | |
| closeUserModal(); | |
| } | |
| // Confirm delete user | |
| function confirmDeleteUser(userId) { | |
| state.confirmationAction = 'deleteUser'; | |
| state.confirmationData = userId; | |
| const user = database.users.find(u => u.id === userId); | |
| elements.confirmationTitle.textContent = 'Delete User'; | |
| elements.confirmationMessage.textContent = `Are you sure you want to delete "${user.name}"? This action cannot be undone.`; | |
| elements.confirmationModal.classList.remove('hidden'); | |
| } | |
| // Delete user | |
| function deleteUser(userId) { | |
| // Don't allow deleting the current user | |
| if (state.currentUser && state.currentUser.id === userId) { | |
| alert('You cannot delete your own account while logged in.'); | |
| return; | |
| } | |
| database.users = database.users.filter(u => u.id !== userId); | |
| loadUsersTable(); | |
| } | |
| // Handle confirmation actions | |
| function confirmAction() { | |
| switch (state.confirmationAction) { | |
| case 'deleteMenuItem': | |
| deleteMenuItem(state.confirmationData); | |
| break; | |
| case 'deleteUser': | |
| deleteUser(state.confirmationData); | |
| break; | |
| } | |
| elements.confirmationModal.classList.add('hidden'); | |
| } | |
| // Load reports | |
| function loadReports() { | |
| // Calculate today's date | |
| const today = new Date().toISOString().split('T')[0]; | |
| // Calculate sales | |
| const todaySales = database.orders | |
| .filter(order => order.date === today) | |
| .reduce((sum, order) => sum + order.totalPrice, 0); | |
| // Calculate this week's sales (simplified for demo) | |
| const weekSales = database.orders | |
| .reduce((sum, order) => sum + order.totalPrice, 0); | |
| // Calculate this month's sales (simplified for demo) | |
| const monthSales = database.orders | |
| .reduce((sum, order) => sum + order.totalPrice, 0) * 3; | |
| // Update UI | |
| elements.todaySales.textContent = todaySales.toFixed(2); | |
| elements.weekSales.textContent = weekSales.toFixed(2); | |
| elements.monthSales.textContent = monthSales.toFixed(2); | |
| // Load recent orders | |
| loadRecentOrders(); | |
| // Initialize charts | |
| initCharts(); | |
| } | |
| // Initialize charts | |
| function initCharts() { | |
| // Daily sales chart (last 7 days) | |
| const dailySalesCtx = elements.dailySalesChart.getContext('2d'); | |
| new Chart(dailySalesCtx, { | |
| type: 'bar', | |
| data: { | |
| labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], | |
| datasets: [{ | |
| label: 'Daily Sales ($)', | |
| data: [120, 190, 150, 210, 180, 250, 200], | |
| backgroundColor: 'rgba(245, 158, 11, 0.7)', | |
| borderColor: 'rgba(245, 158, 11, 1)', | |
| borderWidth: 1 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| scales: { | |
| y: { | |
| beginAtZero: true | |
| } | |
| } | |
| } | |
| }); | |
| // Top items chart | |
| const topItemsCtx = elements.topItemsChart.getContext('2d'); | |
| new Chart(topItemsCtx, { | |
| type | |
| </html> |