Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Ghost Burger - Fast Food Ordering 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> | |
| .menu-item:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 10px 20px rgba(0,0,0,0.1); | |
| } | |
| .receipt { | |
| background: repeating-linear-gradient( | |
| to bottom, | |
| white, | |
| white 24px, | |
| #f0f0f0 24px, | |
| #f0f0f0 25px | |
| ); | |
| } | |
| @media print { | |
| .no-print { | |
| display: none ; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 font-sans"> | |
| <!-- Login Page --> | |
| <div id="login-page" class="min-h-screen flex items-center justify-center bg-gray-900"> | |
| <div class="bg-white p-8 rounded-lg shadow-xl w-full max-w-md"> | |
| <div class="text-center mb-8"> | |
| <img src="https://via.placeholder.com/100x100?text=🍔" alt="Ghost Burger Logo" class="mx-auto h-20 w-20"> | |
| <h1 class="text-3xl font-bold text-gray-800 mt-4">Ghost Burger</h1> | |
| <p class="text-gray-600">Fast Food Ordering System</p> | |
| </div> | |
| <form id="login-form" class="space-y-6"> | |
| <div> | |
| <label for="username" class="block text-sm font-medium text-gray-700">Username</label> | |
| <input type="text" id="username" name="username" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="password" class="block text-sm font-medium text-gray-700">Password</label> | |
| <input type="password" id="password" name="password" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <button type="submit" | |
| class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> | |
| Sign in | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Main App Container (hidden initially) --> | |
| <div id="app-container" class="hidden min-h-screen"> | |
| <!-- Header/Navigation --> | |
| <header class="bg-gray-900 text-white shadow-lg"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="flex justify-between h-16 items-center"> | |
| <div class="flex items-center"> | |
| <img src="https://via.placeholder.com/40x40?text=GB" alt="Logo" class="h-8 w-8"> | |
| <span class="ml-2 text-xl font-bold">Ghost Burger</span> | |
| </div> | |
| <nav class="hidden md:flex space-x-8"> | |
| <a href="#" class="nav-link active" data-page="billing">Billing</a> | |
| <a href="#" class="nav-link" data-page="admin-dashboard">Admin Dashboard</a> | |
| <a href="#" class="nav-link" data-page="reports">Reports</a> | |
| </nav> | |
| <div class="flex items-center"> | |
| <span id="current-user" class="mr-4"></span> | |
| <button id="logout-btn" class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-md text-sm font-medium"> | |
| Logout | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content Area --> | |
| <main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> | |
| <!-- Billing Page --> | |
| <div id="billing-page" class="page-content"> | |
| <div class="flex flex-col md:flex-row gap-6"> | |
| <!-- Menu Items --> | |
| <div class="w-full md:w-2/3"> | |
| <h2 class="text-2xl font-bold mb-6">Menu Items</h2> | |
| <div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4"> | |
| <!-- Menu items will be populated here by JavaScript --> | |
| </div> | |
| </div> | |
| <!-- Order Summary --> | |
| <div class="w-full md:w-1/3 bg-white p-6 rounded-lg shadow-md"> | |
| <h2 class="text-2xl font-bold mb-6">Order Summary</h2> | |
| <div id="order-items" class="mb-6 space-y-3"> | |
| <!-- Order items will be populated here --> | |
| <div class="text-gray-500 italic">No items added yet</div> | |
| </div> | |
| <div class="border-t border-gray-200 pt-4 mb-6"> | |
| <div class="flex justify-between font-semibold text-lg"> | |
| <span>Total:</span> | |
| <span id="order-total">KSh 0.00</span> | |
| </div> | |
| </div> | |
| <div class="mb-6"> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Payment Method</label> | |
| <div class="flex space-x-4"> | |
| <label class="inline-flex items-center"> | |
| <input type="radio" name="payment-method" value="cash" checked | |
| class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"> | |
| <span class="ml-2">Cash</span> | |
| </label> | |
| <label class="inline-flex items-center"> | |
| <input type="radio" name="payment-method" value="mpesa" | |
| class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300"> | |
| <span class="ml-2">MPesa</span> | |
| </label> | |
| </div> | |
| </div> | |
| <div id="mpesa-section" class="hidden mb-6"> | |
| <label for="phone-number" class="block text-sm font-medium text-gray-700">Phone Number</label> | |
| <input type="tel" id="phone-number" | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| <button id="process-mpesa" class="mt-2 w-full bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-md"> | |
| Process MPesa Payment | |
| </button> | |
| </div> | |
| <div class="flex space-x-4"> | |
| <button id="print-receipt" class="flex-1 bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-md"> | |
| <i class="fas fa-print mr-2"></i> Print Receipt | |
| </button> | |
| <button id="email-receipt" class="flex-1 bg-purple-600 hover:bg-purple-700 text-white py-2 px-4 rounded-md"> | |
| <i class="fas fa-envelope mr-2"></i> Email Receipt | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Admin Dashboard --> | |
| <div id="admin-dashboard" class="page-content hidden"> | |
| <h1 class="text-3xl font-bold mb-8">Admin Dashboard</h1> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8"> | |
| <div class="bg-white p-6 rounded-lg shadow-md"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Total Sales</h3> | |
| <p class="text-3xl font-bold text-indigo-600">KSh 24,850</p> | |
| <p class="text-sm text-gray-500 mt-1">Today</p> | |
| </div> | |
| <div class="bg-white p-6 rounded-lg shadow-md"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Orders</h3> | |
| <p class="text-3xl font-bold text-green-600">42</p> | |
| <p class="text-sm text-gray-500 mt-1">Today</p> | |
| </div> | |
| <div class="bg-white p-6 rounded-lg shadow-md"> | |
| <h3 class="text-lg font-medium text-gray-900 mb-2">Top Item</h3> | |
| <p class="text-3xl font-bold text-yellow-600">Ghost Burger</p> | |
| <p class="text-sm text-gray-500 mt-1">15 sold today</p> | |
| </div> | |
| </div> | |
| <div class="bg-white p-6 rounded-lg shadow-md mb-8"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-bold">User Management</h2> | |
| <button id="add-user-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md"> | |
| <i class="fas fa-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 class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Name</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Email</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Role</th> | |
| <th 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"> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap">Admin User</td> | |
| <td class="px-6 py-4 whitespace-nowrap">admin@ghostburger.com</td> | |
| <td class="px-6 py-4 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-purple-100 text-purple-800">Admin</span></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
| <button class="text-indigo-600 hover:text-indigo-900 mr-3">Edit</button> | |
| <button class="text-red-600 hover:text-red-900">Delete</button> | |
| </td> | |
| </tr> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap">Staff User</td> | |
| <td class="px-6 py-4 whitespace-nowrap">staff@ghostburger.com</td> | |
| <td class="px-6 py-4 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">Staff</span></td> | |
| <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500"> | |
| <button class="text-indigo-600 hover:text-indigo-900 mr-3">Edit</button> | |
| <button class="text-red-600 hover:text-red-900">Delete</button> | |
| </td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div class="bg-white p-6 rounded-lg shadow-md"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-bold">Add Menu Item</h2> | |
| </div> | |
| <form id="add-item-form" class="space-y-4"> | |
| <div> | |
| <label for="item-name" class="block text-sm font-medium text-gray-700">Item Name</label> | |
| <input type="text" id="item-name" name="item-name" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="item-price" class="block text-sm font-medium text-gray-700">Price</label> | |
| <input type="number" id="item-price" name="item-price" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="item-category" class="block text-sm font-medium text-gray-700">Category</label> | |
| <select id="item-category" name="item-category" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="">Select a category</option> | |
| <option value="burgers">Burgers</option> | |
| <option value="fries">Fries</option> | |
| <option value="drinks">Drinks</option> | |
| <option value="desserts">Desserts</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="item-image" class="block text-sm font-medium text-gray-700">Image</label> | |
| <input type="file" id="item-image" name="item-image" accept="image/*" | |
| class="mt-1 block w-full text-sm text-gray-500 file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-indigo-50 file:text-indigo-700 hover:file:bg-indigo-100"> | |
| </div> | |
| <div> | |
| <button type="submit" | |
| class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md"> | |
| Add Item | |
| </button> | |
| </div> | |
| </form> | |
| </div> | |
| <div class="bg-white p-6 rounded-lg shadow-md"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-xl font-bold">Delete Menu Items</h2> | |
| </div> | |
| <div class="space-y-4"> | |
| <div class="flex items-center justify-between p-3 border border-gray-200 rounded-md"> | |
| <div class="flex items-center"> | |
| <img src="https://via.placeholder.com/50x50?text=🍔" alt="Ghost Burger" class="h-10 w-10 rounded-full"> | |
| <div class="ml-3"> | |
| <p class="font-medium">Ghost Burger</p> | |
| <p class="text-sm text-gray-500">KSh 450</p> | |
| </div> | |
| </div> | |
| <button class="text-red-600 hover:text-red-900"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| <div class="flex items-center justify-between p-3 border border-gray-200 rounded-md"> | |
| <div class="flex items-center"> | |
| <img src="https://via.placeholder.com/50x50?text=🍟" alt="Fries" class="h-10 w-10 rounded-full"> | |
| <div class="ml-3"> | |
| <p class="font-medium">French Fries</p> | |
| <p class="text-sm text-gray-500">KSh 200</p> | |
| </div> | |
| </div> | |
| <button class="text-red-600 hover:text-red-900"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| <div class="flex items-center justify-between p-3 border border-gray-200 rounded-md"> | |
| <div class="flex items-center"> | |
| <img src="https://via.placeholder.com/50x50?text=🥤" alt="Soda" class="h-10 w-10 rounded-full"> | |
| <div class="ml-3"> | |
| <p class="font-medium">Soda</p> | |
| <p class="text-sm text-gray-500">KSh 150</p> | |
| </div> | |
| </div> | |
| <button class="text-red-600 hover:text-red-900"> | |
| <i class="fas fa-trash"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Reports Page --> | |
| <div id="reports-page" class="page-content hidden"> | |
| <h1 class="text-3xl font-bold mb-8">Reports</h1> | |
| <div class="bg-white p-6 rounded-lg shadow-md mb-8"> | |
| <div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-6"> | |
| <h2 class="text-xl font-bold mb-4 md:mb-0">Sales Report</h2> | |
| <div class="flex space-x-4"> | |
| <div> | |
| <label for="report-period" class="block text-sm font-medium text-gray-700">Period</label> | |
| <select id="report-period" | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="today">Today</option> | |
| <option value="week">This Week</option> | |
| <option value="month">This Month</option> | |
| <option value="custom">Custom</option> | |
| </select> | |
| </div> | |
| <div id="custom-date-range" class="hidden"> | |
| <label for="start-date" class="block text-sm font-medium text-gray-700">Date Range</label> | |
| <div class="flex space-x-2"> | |
| <input type="date" id="start-date" | |
| class="mt-1 block px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| <input type="date" id="end-date" | |
| class="mt-1 block px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| </div> | |
| <div class="flex items-end"> | |
| <button id="generate-report" class="bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md"> | |
| Generate | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="min-w-full divide-y divide-gray-200"> | |
| <thead class="bg-gray-50"> | |
| <tr> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Order ID</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Items</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Payment</th> | |
| <th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Amount</th> | |
| </tr> | |
| </thead> | |
| <tbody class="bg-white divide-y divide-gray-200"> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap">2023-06-15</td> | |
| <td class="px-6 py-4 whitespace-nowrap">#GB-00123</td> | |
| <td class="px-6 py-4">Ghost Burger, Fries, Soda</td> | |
| <td class="px-6 py-4 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">MPesa</span></td> | |
| <td class="px-6 py-4 whitespace-nowrap">KSh 800</td> | |
| </tr> | |
| <tr> | |
| <td class="px-6 py-4 whitespace-nowrap">2023-06-15</td> | |
| <td class="px-6 py-4 whitespace-nowrap">#GB-00122</td> | |
| <td class="px-6 py-4">Double Ghost Burger, Large Fries</td> | |
| <td class="px-6 py-4 whitespace-nowrap"><span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800">Cash</span></td> | |
| <td class="px-6 py-4 whitespace-nowrap">KSh 900</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="mt-6 flex justify-between items-center"> | |
| <div class="text-sm text-gray-500"> | |
| Showing <span class="font-medium">1</span> to <span class="font-medium">10</span> of <span class="font-medium">42</span> results | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button class="px-3 py-1 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"> | |
| Previous | |
| </button> | |
| <button class="px-3 py-1 border border-gray-300 rounded-md text-sm font-medium text-gray-700 bg-white hover:bg-gray-50"> | |
| Next | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div class="bg-white p-6 rounded-lg shadow-md"> | |
| <h2 class="text-xl font-bold mb-6">Top Selling Items</h2> | |
| <div class="space-y-4"> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-700">Ghost Burger</span> | |
| <span class="text-sm font-medium text-gray-700">42</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div class="bg-indigo-600 h-2.5 rounded-full" style="width: 75%"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-700">French Fries</span> | |
| <span class="text-sm font-medium text-gray-700">35</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div class="bg-green-600 h-2.5 rounded-full" style="width: 62%"></div> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex justify-between mb-1"> | |
| <span class="text-sm font-medium text-gray-700">Soda</span> | |
| <span class="text-sm font-medium text-gray-700">28</span> | |
| </div> | |
| <div class="w-full bg-gray-200 rounded-full h-2.5"> | |
| <div class="bg-yellow-500 h-2.5 rounded-full" style="width: 50%"></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-white p-6 rounded-lg shadow-md"> | |
| <h2 class="text-xl font-bold mb-6">Payment Methods</h2> | |
| <div class="flex justify-center"> | |
| <div class="w-64 h-64"> | |
| <canvas id="paymentChart"></canvas> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-6 flex justify-end"> | |
| <button id="export-pdf" class="bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded-md mr-3"> | |
| <i class="fas fa-file-pdf mr-2"></i> Export PDF | |
| </button> | |
| <button id="export-csv" class="bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-md"> | |
| <i class="fas fa-file-excel mr-2"></i> Export CSV | |
| </button> | |
| </div> | |
| </div> | |
| </main> | |
| </div> | |
| <!-- Receipt Modal --> | |
| <div id="receipt-modal" class="fixed inset-0 z-50 hidden overflow-y-auto"> | |
| <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | |
| <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | |
| <div class="absolute inset-0 bg-gray-500 opacity-75"></div> | |
| </div> | |
| <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> | |
| <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"> | |
| <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | |
| <div class="receipt p-6 w-full max-w-md mx-auto"> | |
| <div class="text-center mb-4"> | |
| <h2 class="text-2xl font-bold">GHOST BURGER</h2> | |
| <p class="text-sm">123 Fast Food Lane, Nairobi</p> | |
| <p class="text-sm">Tel: 0700 123 456</p> | |
| </div> | |
| <div class="border-t border-b border-gray-300 py-2 my-2"> | |
| <div class="flex justify-between"> | |
| <span>Order #GB-00123</span> | |
| <span id="receipt-date">2023-06-15 14:30</span> | |
| </div> | |
| </div> | |
| <div id="receipt-items" class="mb-4"> | |
| <!-- Receipt items will be populated here --> | |
| </div> | |
| <div class="border-t border-gray-300 pt-2"> | |
| <div class="flex justify-between font-semibold"> | |
| <span>Total:</span> | |
| <span id="receipt-total">KSh 0.00</span> | |
| </div> | |
| <div class="mt-2 text-sm" id="receipt-payment-method"> | |
| Payment Method: Cash | |
| </div> | |
| <div class="mt-4 text-center text-xs"> | |
| Thank you for dining with us! | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse no-print"> | |
| <button type="button" id="print-receipt-btn" | |
| class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 sm:ml-3 sm:w-auto sm:text-sm"> | |
| <i class="fas fa-print mr-2"></i> Print Receipt | |
| </button> | |
| <button type="button" id="close-receipt-modal" | |
| class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Close | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Email Receipt Modal --> | |
| <div id="email-receipt-modal" class="fixed inset-0 z-50 hidden overflow-y-auto"> | |
| <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | |
| <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | |
| <div class="absolute inset-0 bg-gray-500 opacity-75"></div> | |
| </div> | |
| <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> | |
| <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"> | |
| <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | |
| <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Email Receipt</h3> | |
| <div class="mb-4"> | |
| <label for="customer-email" class="block text-sm font-medium text-gray-700">Customer Email</label> | |
| <input type="email" id="customer-email" | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" | |
| placeholder="customer@example.com"> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="receipt-message" class="block text-sm font-medium text-gray-700">Message (optional)</label> | |
| <textarea id="receipt-message" rows="3" | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">Thank you for dining at Ghost Burger! Here's your receipt.</textarea> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | |
| <button type="button" id="send-email-receipt" | |
| class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-purple-600 text-base font-medium text-white hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:ml-3 sm:w-auto sm:text-sm"> | |
| <i class="fas fa-paper-plane mr-2"></i> Send Receipt | |
| </button> | |
| <button type="button" id="close-email-receipt-modal" | |
| class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Add User Modal --> | |
| <div id="add-user-modal" class="fixed inset-0 z-50 hidden overflow-y-auto"> | |
| <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | |
| <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | |
| <div class="absolute inset-0 bg-gray-500 opacity-75"></div> | |
| </div> | |
| <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> | |
| <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full"> | |
| <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | |
| <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">Add New User</h3> | |
| <form id="add-user-form" class="space-y-4"> | |
| <div> | |
| <label for="new-user-name" class="block text-sm font-medium text-gray-700">Full Name</label> | |
| <input type="text" id="new-user-name" name="new-user-name" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="new-user-email" class="block text-sm font-medium text-gray-700">Email</label> | |
| <input type="email" id="new-user-email" name="new-user-email" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="new-user-role" class="block text-sm font-medium text-gray-700">Role</label> | |
| <select id="new-user-role" name="new-user-role" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| <option value="">Select a role</option> | |
| <option value="admin">Admin</option> | |
| <option value="staff">Staff</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="new-user-password" class="block text-sm font-medium text-gray-700">Password</label> | |
| <input type="password" id="new-user-password" name="new-user-password" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <label for="new-user-confirm-password" class="block text-sm font-medium text-gray-700">Confirm Password</label> | |
| <input type="password" id="new-user-confirm-password" name="new-user-confirm-password" required | |
| class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| </form> | |
| </div> | |
| <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | |
| <button type="button" id="save-new-user" | |
| class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Save User | |
| </button> | |
| <button type="button" id="cancel-add-user" | |
| class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- MPesa Processing Modal --> | |
| <div id="mpesa-processing-modal" class="fixed inset-0 z-50 hidden overflow-y-auto"> | |
| <div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> | |
| <div class="fixed inset-0 transition-opacity" aria-hidden="true"> | |
| <div class="absolute inset-0 bg-gray-500 opacity-75"></div> | |
| </div> | |
| <span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span> | |
| <div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-md sm:w-full"> | |
| <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> | |
| <div class="text-center"> | |
| <div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100"> | |
| <i class="fas fa-mobile-alt text-green-600"></i> | |
| </div> | |
| <h3 class="mt-3 text-lg leading-6 font-medium text-gray-900">MPesa Payment Processing</h3> | |
| <div class="mt-2"> | |
| <p class="text-sm text-gray-500">Please check your phone and enter your MPesa PIN to complete the payment of <span id="mpesa-amount" class="font-bold">KSh 0.00</span>.</p> | |
| <div id="mpesa-spinner" class="mt-4"> | |
| <div class="flex justify-center"> | |
| <div class="animate-spin rounded-full h-8 w-8 border-b-2 border-gray-900"></div> | |
| </div> | |
| <p class="mt-2 text-sm text-gray-500">Waiting for payment confirmation...</p> | |
| </div> | |
| <div id="mpesa-success" class="hidden mt-4"> | |
| <div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100"> | |
| <i class="fas fa-check text-green-600"></i> | |
| </div> | |
| <p class="mt-2 text-sm text-green-600">Payment received! Transaction ID: <span id="mpesa-transaction-id" class="font-bold">NCJ892HJK2</span></p> | |
| </div> | |
| <div id="mpesa-failure" class="hidden mt-4"> | |
| <div class="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100"> | |
| <i class="fas fa-times text-red-600"></i> | |
| </div> | |
| <p class="mt-2 text-sm text-red-600">Payment failed. Please try again or use another payment method.</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse"> | |
| <button type="button" id="mpesa-done-btn" class="hidden w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-green-600 text-base font-medium text-white hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Done | |
| </button> | |
| <button type="button" id="mpesa-retry-btn" class="hidden w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Retry | |
| </button> | |
| <button type="button" id="mpesa-cancel-btn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Sample menu data | |
| const menuItems = [ | |
| { id: 1, name: "Ghost Burger", price: 450, category: "burgers", image: "🍔" }, | |
| { id: 2, name: "Double Ghost Burger", price: 650, category: "burgers", image: "🍔🍔" }, | |
| { id: 3, name: "Cheese Ghost Burger", price: 500, category: "burgers", image: "🧀🍔" }, | |
| { id: 4, name: "French Fries", price: 200, category: "fries", image: "🍟" }, | |
| { id: 5, name: "Large Fries", price: 300, category: "fries", image: "🍟🍟" }, | |
| { id: 6, name: "Cheese Fries", price: 350, category: "fries", image: "🧀🍟" }, | |
| { id: 7, name: "Soda", price: 150, category: "drinks", image: "🥤" }, | |
| { id: 8, name: "Milkshake", price: 250, category: "drinks", image: "🥛" }, | |
| { id: 9, name: "Ice Cream", price: 200, category: "desserts", image: "🍦" }, | |
| { id: 10, name: "Chocolate Cake", price: 300, category: "desserts", image: "🍫🍰" } | |
| ]; | |
| // Current order | |
| let currentOrder = []; | |
| let currentUser = null; | |
| // DOM elements | |
| const loginPage = document.getElementById('login-page'); | |
| const appContainer = document.getElementById('app-container'); | |
| const loginForm = document.getElementById('login-form'); | |
| const logoutBtn = document.getElementById('logout-btn'); | |
| const currentUserSpan = document.getElementById('current-user'); | |
| const navLinks = document.querySelectorAll('.nav-link'); | |
| const pageContents = document.querySelectorAll('.page-content'); | |
| const menuContainer = document.querySelector('#billing-page .grid'); | |
| const orderItemsContainer = document.getElementById('order-items'); | |
| const orderTotalSpan = document.getElementById('order-total'); | |
| const paymentMethodRadios = document.querySelectorAll('input[name="payment-method"]'); | |
| const mpesaSection = document.getElementById('mpesa-section'); | |
| const printReceiptBtn = document.getElementById('print-receipt'); | |
| const emailReceiptBtn = document.getElementById('email-receipt'); | |
| const receiptModal = document.getElementById('receipt-modal'); | |
| const closeReceiptModal = document.getElementById('close-receipt-modal'); | |
| const printReceiptBtnModal = document.getElementById('print-receipt-btn'); | |
| const emailReceiptModal = document.getElementById('email-receipt-modal'); | |
| const closeEmailReceiptModal = document.getElementById('close-email-receipt-modal'); | |
| const sendEmailReceiptBtn = document.getElementById('send-email-receipt'); | |
| const addUserBtn = document.getElementById('add-user-btn'); | |
| const addUserModal = document.getElementById('add-user-modal'); | |
| const cancelAddUserBtn = document.getElementById('cancel-add-user'); | |
| const saveNewUserBtn = document.getElementById('save-new-user'); | |
| const processMpesaBtn = document.getElementById('process-mpesa'); | |
| const mpesaProcessingModal = document.getElementById('mpesa-processing-modal'); | |
| const mpesaCancelBtn = document.getElementById('mpesa-cancel-btn'); | |
| const mpesaDoneBtn = document.getElementById('mpesa-done-btn'); | |
| const mpesaRetryBtn = document.getElementById('mpesa-retry-btn'); | |
| const mpesaAmountSpan = document.getElementById('mpesa-amount'); | |
| const reportPeriodSelect = document.getElementById('report-period'); | |
| const customDateRangeDiv = document.getElementById('custom-date-range'); | |
| // Initialize the app | |
| function initApp() { | |
| // Populate menu items | |
| renderMenuItems(); | |
| // Set up event listeners | |
| setupEventListeners(); | |
| } | |
| // Render menu items | |
| function renderMenuItems() { | |
| menuContainer.innerHTML = ''; | |
| menuItems.forEach(item => { | |
| const menuItem = document.createElement('div'); | |
| menuItem.className = 'bg-white p-4 rounded-lg shadow-md cursor-pointer menu-item transition duration-300'; | |
| menuItem.innerHTML = ` | |
| <div class="text-4xl text-center mb-2">${item.image}</div> | |
| <h3 class="font-medium text-center">${item.name}</h3> | |
| <p class="text-center text-gray-600">KSh ${item.price.toFixed(2)}</p> | |
| `; | |
| menuItem.addEventListener('click', () => addToOrder(item)); | |
| menuContainer.appendChild(menuItem); | |
| }); | |
| } | |
| // Add item to order | |
| function addToOrder(item) { | |
| const existingItem = currentOrder.find(orderItem => orderItem.id === item.id); | |
| if (existingItem) { | |
| existingItem.quantity += 1; | |
| } else { | |
| currentOrder.push({ | |
| id: item.id, | |
| name: item.name, | |
| price: item.price, | |
| quantity: 1 | |
| }); | |
| } | |
| renderOrderItems(); | |
| } | |
| // Remove item from order | |
| function removeFromOrder(itemId) { | |
| const itemIndex = currentOrder.findIndex(item => item.id === itemId); | |
| if (itemIndex !== -1) { | |
| if (currentOrder[itemIndex].quantity > 1) { | |
| currentOrder[itemIndex].quantity -= 1; | |
| } else { | |
| currentOrder.splice(itemIndex, 1); | |
| } | |
| renderOrderItems(); | |
| } | |
| } | |
| // Render order items | |
| function renderOrderItems() { | |
| if (currentOrder.length === 0) { | |
| orderItemsContainer.innerHTML = '<div class="text-gray-500 italic">No items added yet</div>'; | |
| orderTotalSpan.textContent = 'KSh 0.00'; | |
| return; | |
| } | |
| let html = ''; | |
| let total = 0; | |
| currentOrder.forEach(item => { | |
| const itemTotal = item.price * item.quantity; | |
| total += itemTotal; | |
| html += ` | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <span class="font-medium">${item.name}</span> | |
| <div class="text-sm text-gray-500"> | |
| KSh ${item.price.toFixed(2)} × ${item.quantity} | |
| </div> | |
| </div> | |
| <div class="flex items-center"> | |
| <span class="font-medium mr-3">KSh ${itemTotal.toFixed(2)}</span> | |
| <button class="text-red-500 hover:text-red-700 remove-item" data-id="${item.id}"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| orderItemsContainer.innerHTML = html; | |
| orderTotalSpan.textContent = `KSh ${total.toFixed(2)}`; | |
| // Add event listeners to remove buttons | |
| document.querySelectorAll('.remove-item').forEach(button => { | |
| button.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| removeFromOrder(parseInt(button.dataset.id)); | |
| }); | |
| }); | |
| } | |
| // Render receipt | |
| function renderReceipt(paymentMethod) { | |
| const receiptItems = document.getElementById('receipt-items'); | |
| const receiptTotal = document.getElementById('receipt-total'); | |
| const receiptPaymentMethod = document.getElementById('receipt-payment-method'); | |
| let html = ''; | |
| let total = 0; | |
| currentOrder.forEach(item => { | |
| const itemTotal = item.price * item.quantity; | |
| total += itemTotal; | |
| html += ` | |
| <div class="flex justify-between mb-1"> | |
| <span>${item.name} × ${item.quantity}</span> | |
| <span>KSh ${itemTotal.toFixed(2)}</span> | |
| </div> | |
| `; | |
| }); | |
| receiptItems.innerHTML = html; | |
| receiptTotal.textContent = `KSh ${total.toFixed(2)}`; | |
| receiptPaymentMethod.textContent = `Payment Method: ${paymentMethod === 'mpesa' ? 'MPesa' : 'Cash'}`; | |
| // Set current date and time | |
| const now = new Date(); | |
| const dateStr = now.toISOString().split('T')[0]; | |
| const timeStr = now.toTimeString().substring(0, 5); | |
| document.getElementById('receipt-date').textContent = `${dateStr} ${timeStr}`; | |
| // Set MPesa transaction details if applicable | |
| if (paymentMethod === 'mpesa') { | |
| const transactionId = `MP${Math.floor(Math.random() * 1000000000).toString().padStart(9, '0')}`; | |
| receiptPaymentMethod.innerHTML += `<div class="mt-1">Transaction ID: ${transactionId}</div>`; | |
| } | |
| } | |
| // Setup event listeners | |
| function setupEventListeners() { | |
| // Login form | |
| loginForm.addEventListener('submit', (e) => { | |
| e.preventDefault(); | |
| const username = document.getElementById('username').value; | |
| const password = document.getElementById('password').value; | |
| // Simple authentication (in a real app, this would be server-side) | |
| if (username && password) { | |
| currentUser = { | |
| name: username === 'admin' ? 'Admin User' : 'Staff User', | |
| role: username === 'admin' ? 'admin' : 'staff' | |
| }; | |
| loginPage.classList.add('hidden'); | |
| appContainer.classList.remove('hidden'); | |
| currentUserSpan.textContent = `Logged in as ${currentUser.name} (${currentUser.role})`; | |
| // Show appropriate pages based on role | |
| if (currentUser.role === 'staff') { | |
| document.querySelector('[data-page="admin-dashboard"]').style.display = 'none'; | |
| document.querySelector('[data-page="reports"]').style.display = 'none'; | |
| showPage('billing'); | |
| } else { | |
| showPage('billing'); | |
| } | |
| } | |
| }); | |
| // Logout button | |
| logoutBtn.addEventListener('click', () => { | |
| currentUser = null; | |
| currentOrder = []; | |
| appContainer.classList.add('hidden'); | |
| loginPage.classList.remove('hidden'); | |
| document.getElementById('username').value = ''; | |
| document.getElementById('password').value = ''; | |
| }); | |
| // Navigation links | |
| navLinks.forEach(link => { | |
| link.addEventListener('click', (e) => { | |
| e.preventDefault(); | |
| showPage(link.dataset.page); | |
| }); | |
| }); | |
| // Payment method radio buttons | |
| paymentMethodRadios.forEach(radio => { | |
| radio.addEventListener('change', (e) => { | |
| if (e.target.value === 'mpesa') { | |
| mpesaSection.classList.remove('hidden'); | |
| } else { | |
| mpesaSection.classList.add('hidden'); | |
| } | |
| }); | |
| }); | |
| // Print receipt button | |
| printReceiptBtn.addEventListener('click', () => { | |
| if (currentOrder.length === 0) { | |
| alert('Please add items to the order first'); | |
| return; | |
| } | |
| const paymentMethod = document.querySelector('input[name="payment-method"]:checked').value; | |
| renderReceipt(paymentMethod); | |
| receiptModal.classList.remove('hidden'); | |
| }); | |
| // Email receipt button | |
| emailReceiptBtn.addEventListener('click', () => { | |
| if (currentOrder.length === 0) { | |
| alert('Please add items to the order first'); | |
| return; | |
| } | |
| emailReceiptModal.classList.remove('hidden'); | |
| }); | |
| // Close receipt modal | |
| closeReceiptModal.addEventListener('click', () => { | |
| receiptModal.classList.add('hidden'); | |
| }); | |
| // Print receipt from modal | |
| printReceiptBtnModal.addEventListener('click', () => { | |
| window.print(); | |
| }); | |
| // Close email receipt modal | |
| closeEmailReceiptModal.addEventListener('click', () => { | |
| emailReceiptModal.classList.add('hidden'); | |
| }); | |
| // Send email receipt | |
| sendEmailReceipt.addEventListener('click', () => { | |
| const email = document.getElementById('customer-email').value; | |
| if (!email) { | |
| alert('Please enter customer email'); | |
| return; | |
| } | |
| alert(`Receipt sent to ${email}`); | |
| emailReceiptModal.classList.add('hidden'); | |
| currentOrder = []; | |
| renderOrderItems(); | |
| }); | |
| // Add user button | |
| addUserBtn.addEventListener('click', () => { | |
| addUserModal.classList.remove('hidden'); | |
| }); | |
| // Cancel add user | |
| cancelAddUserBtn.addEventListener('click', () => { | |
| addUserModal.classList.add('hidden'); | |
| }); | |
| // Save new user | |
| saveNewUserBtn.addEventListener('click', () => { | |
| const name = document.getElementById('new-user-name').value; | |
| const email = document.getElementById('new-user-email').value; | |
| const role = document.getElementById('new-user-role').value; | |
| const password = document.getElementById('new-user-password').value; | |
| const confirmPassword = document.getElementById('new-user-confirm-password').value; | |
| if (!name || !email || !role || !password || !confirmPassword) { | |
| alert('Please fill in all fields'); | |
| return; | |
| } | |
| if (password !== confirmPassword) { | |
| alert('Passwords do not match'); | |
| return; | |
| } | |
| alert(`User ${name} (${role}) added successfully`); | |
| addUserModal.classList.add('hidden'); | |
| document.getElementById('add-user-form').reset(); | |
| }); | |
| // Process MPesa payment | |
| processMpesaBtn.addEventListener('click', () => { | |
| const phoneNumber = document.getElementById('phone-number').value; | |
| if (!phoneNumber || phoneNumber.length < 9) { | |
| alert('Please enter a valid phone number'); | |
| return; | |
| } | |
| if (currentOrder.length === 0) { | |
| alert('Please add items to the order first'); | |
| return; | |
| } | |
| // Calculate total | |
| let total = 0; | |
| currentOrder.forEach(item => { | |
| total += item.price * item.quantity; | |
| }); | |
| mpesaAmountSpan.textContent = `KSh ${total.toFixed(2)}`; | |
| mpesaProcessingModal.classList.remove('hidden'); | |
| // Simulate MPesa processing | |
| const mpesaSpinner = document.getElementById('mpesa-spinner'); | |
| const mpesaSuccess = document.getElementById('mpesa-success'); | |
| const mpesaFailure = document.getElementById('mpesa-failure'); | |
| mpesaSpinner.classList.remove('hidden'); | |
| mpesaSuccess.classList.add('hidden'); | |
| mpesaFailure.classList.add('hidden'); | |
| mpesaDoneBtn.classList.add('hidden'); | |
| mpesaRetryBtn.classList.add('hidden'); | |
| setTimeout(() => { | |
| // Randomly decide if payment succeeds (80% chance) | |
| const success = Math.random() < 0.8; | |
| mpesaSpinner.classList.add('hidden'); | |
| if (success) { | |
| mpesaSuccess.classList.remove('hidden'); | |
| mpesaDoneBtn.classList.remove('hidden'); | |
| // Generate random transaction ID | |
| const transactionId = `MP${Math.floor(Math.random() * 1000000000).toString().padStart(9, '0')}`; | |
| document.getElementById('mpesa-transaction-id').textContent = transactionId; | |
| } else { | |
| mpesaFailure.classList.remove('hidden'); | |
| mpesaRetryBtn.classList.remove('hidden'); | |
| } | |
| }, 3000); | |
| }); | |
| // MPesa modal buttons | |
| mpesaCancelBtn.addEventListener('click', () => { | |
| mpesaProcessingModal.classList.add('hidden'); | |
| }); | |
| mpesaDoneBtn.addEventListener('click', () => { | |
| mpesaProcessingModal.classList.add('hidden'); | |
| const paymentMethod = document.querySelector('input[name="payment-method"]:checked').value; | |
| renderReceipt(paymentMethod); | |
| receiptModal.classList.remove('hidden'); | |
| // Clear order after successful payment | |
| currentOrder = []; | |
| renderOrderItems(); | |
| }); | |
| mpesaRetryBtn.addEventListener('click', () => { | |
| document.getElementById('mpesa-spinner').classList.remove('hidden'); | |
| document.getElementById('mpesa-success').classList.add('hidden'); | |
| document.getElementById('mpesa-failure').classList.add('hidden'); | |
| mpesaDoneBtn.classList.add('hidden'); | |
| mpesaRetryBtn.classList.add('hidden'); | |
| setTimeout(() => { | |
| // Randomly decide if payment succeeds (80% chance) | |
| const success = Math.random() < 0.8; | |
| document.getElementById('mpesa-spinner').classList.add('hidden'); | |
| if (success) { | |
| document.getElementById('mpesa-success').classList.remove('hidden'); | |
| mpesaDoneBtn.classList.remove('hidden'); | |
| // Generate random transaction ID | |
| const transactionId = `MP${Math.floor(Math.random() * 1000000000).toString().padStart(9, '0')}`; | |
| document.getElementById('mpesa-transaction-id').textContent = transactionId; | |
| } else { | |
| document.getElementById('mpesa-failure').classList.remove('hidden'); | |
| mpesaRetryBtn.classList.remove('hidden'); | |
| } | |
| }, 3000); | |
| }); | |
| // Report period selector | |
| reportPeriodSelect.addEventListener('change', (e) => { | |
| if (e.target.value === 'custom') { | |
| customDateRangeDiv.classList.remove('hidden'); | |
| } else { | |
| customDateRangeDiv.classList.add('hidden'); | |
| } | |
| }); | |
| } | |
| // Show page | |
| function showPage(pageName) { | |
| pageContents.forEach(content => { | |
| content.classList.add('hidden'); | |
| }); | |
| navLinks.forEach(link => { | |
| link.classList.remove('active'); | |
| }); | |
| document.getElementById(`${pageName}-page`).classList.remove('hidden'); | |
| document.querySelector(`[data-page="${pageName}"]`).classList.add('active'); | |
| } | |
| // Initialize the app when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', initApp); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=GilbertKambu/webprojects" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |