feat: initial foundation for collaborative canteen ordering system in vanilla PHP

This commit is contained in:
2026-08-27 17:29:13 +07:00
commit 5effce30cb
52 changed files with 4915 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
<div style="max-width: 440px; margin: 3rem auto;">
<div class="card" style="box-shadow: var(--shadow-lg);">
<div class="card-header" style="text-align: center; display: block; padding: 2rem 1.5rem 1rem;">
<div style="font-size: 2.5rem; margin-bottom: 0.5rem;">🍱</div>
<h1 style="font-size: 1.5rem; font-weight: 700; color: var(--primary);">Login to CanteenHub</h1>
<p style="font-size: 0.875rem; color: var(--text-muted); margin-top: 0.25rem;">Order lunch or host food sessions with your team</p>
</div>
<div class="card-body">
<form method="POST" action="/login">
<?= \App\Core\View::csrfField() ?>
<div class="form-group">
<label class="form-label">Username or Email</label>
<input type="text" name="identifier" class="form-control" placeholder="e.g. dea or alex@company.com" required autofocus>
</div>
<div class="form-group">
<label class="form-label">Password</label>
<input type="password" name="password" class="form-control" placeholder="••••••••" required>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; padding: 0.75rem; font-size: 1rem; margin-top: 0.5rem;">
Sign In &rarr;
</button>
</form>
<div style="text-align: center; margin-top: 1.25rem; font-size: 0.875rem; color: var(--text-muted);">
Don't have an account? <a href="/register" style="color: var(--primary); font-weight: 600;">Register here</a>
</div>
</div>
<!-- Quick Switch Demo Account helper -->
<?php if (!empty($users)): ?>
<div class="card-footer" style="background: #f8fafc;">
<div style="font-size: 0.75rem; text-transform: uppercase; font-weight: 600; color: var(--text-muted); margin-bottom: 0.5rem; text-align: center;">
⚡ 1-Click Quick Demo Login:
</div>
<div style="display: flex; gap: 0.4rem; flex-wrap: wrap; justify-content: center;">
<?php foreach ($users as $u): ?>
<form method="POST" action="/quick-login" style="display: inline;">
<?= \App\Core\View::csrfField() ?>
<input type="hidden" name="user_id" value="<?= e($u['id']) ?>">
<button type="submit" class="btn btn-secondary btn-sm" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;">
<?= e($u['name']) ?>
</button>
</form>
<?php endforeach; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
+48
View File
@@ -0,0 +1,48 @@
<div style="max-width: 440px; margin: 3rem auto;">
<div class="card" style="box-shadow: var(--shadow-lg);">
<div class="card-header" style="text-align: center; display: block; padding: 2rem 1.5rem 1rem;">
<div style="font-size: 2.5rem; margin-bottom: 0.5rem;">🍱</div>
<h1 style="font-size: 1.5rem; font-weight: 700; color: var(--primary);">Join CanteenHub</h1>
<p style="font-size: 0.875rem; color: var(--text-muted); margin-top: 0.25rem;">Create your coworker account to order lunch</p>
</div>
<div class="card-body">
<form method="POST" action="/register">
<?= \App\Core\View::csrfField() ?>
<div class="form-group">
<label class="form-label">Full Name <span style="color: var(--danger);">*</span></label>
<input type="text" name="name" class="form-control" placeholder="e.g. Andy Pratama" required autofocus>
</div>
<div class="form-group">
<label class="form-label">Mattermost Username / Handle <span style="color: var(--danger);">*</span></label>
<div style="display: flex; align-items: center; gap: 0.25rem;">
<span style="color: var(--text-muted); font-weight: 600;">@</span>
<input type="text" name="username" class="form-control" placeholder="andy" required>
</div>
</div>
<div class="form-group">
<label class="form-label">Email Address <span style="color: var(--danger);">*</span></label>
<input type="email" name="email" class="form-control" placeholder="andy@company.com" required>
</div>
<div class="form-group">
<label class="form-label">Password <span style="color: var(--danger);">*</span></label>
<input type="password" name="password" class="form-control" placeholder="At least 4 characters" required minlength="4">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; padding: 0.75rem; font-size: 1rem; margin-top: 0.5rem;">
Create Account &rarr;
</button>
</form>
<div style="text-align: center; margin-top: 1.25rem; font-size: 0.875rem; color: var(--text-muted);">
Already have an account? <a href="/login" style="color: var(--primary); font-weight: 600;">Login here</a>
</div>
</div>
</div>
</div>
+88
View File
@@ -0,0 +1,88 @@
<div class="page-header">
<div>
<h1 class="page-title">Customer Directory</h1>
<p class="page-subtitle">Manage customer profiles and contact details</p>
</div>
</div>
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; align-items: start;">
<!-- Customers Table -->
<div class="card">
<div class="card-header">
<h2 class="card-title">Registered Customers (<?= count($customers) ?>)</h2>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Customer</th>
<th>Phone</th>
<th>Address</th>
<th>Registered</th>
</tr>
</thead>
<tbody>
<?php if (empty($customers)): ?>
<tr>
<td colspan="4" style="text-align: center; color: var(--text-muted); padding: 2rem;">
No customers registered yet.
</td>
</tr>
<?php else: ?>
<?php foreach ($customers as $cust): ?>
<tr>
<td>
<div style="font-weight: 600;"><?= e($cust['name']) ?></div>
<div style="font-size: 0.8rem; color: var(--text-muted);"><?= e($cust['email']) ?></div>
</td>
<td><?= e($cust['phone'] ?: '—') ?></td>
<td style="font-size: 0.85rem; color: var(--text-muted); max-width: 250px;">
<?= e($cust['address'] ?: '—') ?>
</td>
<td style="color: var(--text-muted); font-size: 0.85rem;">
<?= date('M d, Y', strtotime($cust['created_at'])) ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Add Customer Form -->
<div class="card">
<div class="card-header">
<h2 class="card-title">Register Customer</h2>
</div>
<div class="card-body">
<form method="POST" action="/customers">
<?= \App\Core\View::csrfField() ?>
<div class="form-group">
<label class="form-label">Full Name <span style="color: var(--danger);">*</span></label>
<input type="text" name="name" class="form-control" placeholder="e.g. Sarah Connor" required>
</div>
<div class="form-group">
<label class="form-label">Email Address <span style="color: var(--danger);">*</span></label>
<input type="email" name="email" class="form-control" placeholder="sarah@example.com" required>
</div>
<div class="form-group">
<label class="form-label">Phone Number</label>
<input type="tel" name="phone" class="form-control" placeholder="+1-555-0199">
</div>
<div class="form-group">
<label class="form-label">Shipping / Billing Address</label>
<textarea name="address" class="form-textarea" rows="3" placeholder="Street, City, State, ZIP..."></textarea>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%;">Add Customer</button>
</form>
</div>
</div>
</div>
+98
View File
@@ -0,0 +1,98 @@
<div class="page-header">
<div>
<h1 class="page-title">Dashboard Overview</h1>
<p class="page-subtitle">Real-time summary of sales, orders, and inventory status</p>
</div>
<div>
<a href="/orders/create" class="btn btn-primary">+ Create New Order</a>
</div>
</div>
<!-- Metrics Cards -->
<div class="grid-stats">
<div class="stat-card">
<div>
<div class="stat-label">Total Revenue</div>
<div class="stat-value"><?= money($stats['total_revenue']) ?></div>
</div>
<div class="stat-icon" style="background: #ecfdf5; color: #10b981;">💰</div>
</div>
<div class="stat-card">
<div>
<div class="stat-label">Total Orders</div>
<div class="stat-value"><?= e($stats['total_orders']) ?></div>
</div>
<div class="stat-icon" style="background: #eef2ff; color: #4f46e5;">📑</div>
</div>
<div class="stat-card">
<div>
<div class="stat-label">Pending / Processing</div>
<div class="stat-value"><?= e($stats['pending_orders'] + $stats['processing_orders']) ?></div>
</div>
<div class="stat-icon" style="background: #fffbeb; color: #f59e0b;">⏳</div>
</div>
<div class="stat-card">
<div>
<div class="stat-label">Total Products</div>
<div class="stat-value"><?= e($stats['total_products']) ?></div>
</div>
<div class="stat-icon" style="background: #f0f9ff; color: #0ea5e9;">🏷️</div>
</div>
</div>
<!-- Recent Orders Section -->
<div class="card">
<div class="card-header">
<h2 class="card-title">Recent Orders</h2>
<a href="/orders" class="btn btn-secondary btn-sm">View All Orders &rarr;</a>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Order #</th>
<th>Customer</th>
<th>Items</th>
<th>Total</th>
<th>Status</th>
<th>Date</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php if (empty($recentOrders)): ?>
<tr>
<td colspan="7" style="text-align: center; color: var(--text-muted); padding: 2rem;">
No orders found yet. <a href="/orders/create" style="color: var(--primary); font-weight: 600;">Create the first order</a>.
</td>
</tr>
<?php else: ?>
<?php foreach ($recentOrders as $order): ?>
<tr>
<td>
<a href="/orders/<?= e($order['id']) ?>" style="font-weight: 600; color: var(--primary); text-decoration: none;">
<?= e($order['order_number']) ?>
</a>
</td>
<td><?= e($order['customer_name']) ?></td>
<td><?= e($order['item_count']) ?> item(s)</td>
<td style="font-weight: 600;"><?= money($order['total']) ?></td>
<td>
<span class="badge badge-<?= strtolower(e($order['status'])) ?>">
<?= e($order['status']) ?>
</span>
</td>
<td style="color: var(--text-muted); font-size: 0.85rem;"><?= date('M d, Y H:i', strtotime($order['created_at'])) ?></td>
<td>
<a href="/orders/<?= e($order['id']) ?>" class="btn btn-secondary btn-sm">View Invoice</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
+56
View File
@@ -0,0 +1,56 @@
<div class="page-header">
<div>
<h1 class="page-title">Coworkers &amp; Balance Tabs</h1>
<p class="page-subtitle">Directory of team members, total food orders, and unpaid balance tabs</p>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Team Members (<?= count($employees) ?>)</h2>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Mattermost Tag</th>
<th>Email</th>
<th>Total Orders Placed</th>
<th>Unpaid Balance Tab</th>
</tr>
</thead>
<tbody>
<?php if (empty($employees)): ?>
<tr>
<td colspan="5" style="text-align: center; color: var(--text-muted); padding: 2rem;">
No coworkers registered yet.
</td>
</tr>
<?php else: ?>
<?php foreach ($employees as $emp): ?>
<tr>
<td>
<div style="font-weight: 600;"><?= e($emp['name']) ?></div>
</td>
<td>
<span class="badge badge-neutral">@<?= e($emp['username']) ?></span>
</td>
<td style="color: var(--text-muted); font-size: 0.85rem;"><?= e($emp['email']) ?></td>
<td><?= e($emp['total_orders']) ?> orders</td>
<td>
<?php if ((float)$emp['unpaid_balance'] > 0): ?>
<span class="badge badge-pending" style="font-weight: 700;">
<?= money($emp['unpaid_balance']) ?> unpaid
</span>
<?php else: ?>
<span class="badge badge-completed">All Settled</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
+76
View File
@@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= e($title ?? 'Office Canteen Lunch Hub') ?> - CanteenHub</title>
<link rel="stylesheet" href="/assets/css/style.css">
<!-- Alpine.js -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.14.8/dist/cdn.min.js"></script>
</head>
<body>
<!-- Header Navigation -->
<header class="navbar">
<div class="navbar-container">
<a href="/" class="navbar-brand">
<div class="navbar-brand-icon">🍱</div>
<span>CanteenHub</span>
</a>
<?php if (\App\Core\Auth::check()): ?>
<?php $loggedUser = \App\Core\Auth::user(); ?>
<nav>
<ul class="nav-links">
<li><a href="/" class="nav-link <?= ($_SERVER['REQUEST_URI'] ?? '') === '/' ? 'active' : '' ?>">🍱 Active Sessions</a></li>
<li><a href="/sessions/create" class="nav-link <?= str_starts_with($_SERVER['REQUEST_URI'] ?? '', '/sessions/create') ? 'active' : '' ?>"> Start Session</a></li>
<li><a href="/sessions/history" class="nav-link <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/history') ? 'active' : '' ?>">📅 Archive</a></li>
<li><a href="/employees" class="nav-link <?= str_starts_with($_SERVER['REQUEST_URI'] ?? '', '/employees') ? 'active' : '' ?>">👥 Coworkers</a></li>
</ul>
</nav>
<div class="nav-actions">
<div style="display: flex; align-items: center; gap: 0.75rem;">
<div style="text-align: right; font-size: 0.85rem;">
<strong><?= e($loggedUser['name']) ?></strong>
<div style="color: var(--text-muted); font-size: 0.75rem;">@<?= e($loggedUser['username']) ?></div>
</div>
<form method="POST" action="/logout" style="display: inline;">
<?= \App\Core\View::csrfField() ?>
<button type="submit" class="btn btn-secondary btn-sm" style="color: var(--danger);">Logout</button>
</form>
</div>
</div>
<?php else: ?>
<div class="nav-actions">
<a href="/login" class="btn btn-secondary btn-sm">Login</a>
<a href="/register" class="btn btn-primary btn-sm">Register</a>
</div>
<?php endif; ?>
</div>
</header>
<!-- Main Content Body -->
<main class="main-container">
<!-- Flash Messages -->
<?php foreach (\App\Core\View::getFlashes() as $type => $messages): ?>
<?php foreach ($messages as $msg): ?>
<div class="alert alert-<?= $type === 'error' ? 'error' : 'success' ?>" x-data="{ show: true }" x-show="show">
<span><?= e($msg) ?></span>
<button type="button" @click="show = false" style="background:none; border:none; cursor:pointer; font-size:1.1rem; color:inherit;">&times;</button>
</div>
<?php endforeach; ?>
<?php endforeach; ?>
<?= $content ?>
</main>
<!-- Footer -->
<footer class="footer">
<p>&copy; <?= date('Y') ?> CanteenHub &bull; Zero Framework PHP &bull; Native CSS &amp; Alpine.js</p>
</footer>
</body>
</html>
+221
View File
@@ -0,0 +1,221 @@
<div class="page-header">
<div>
<h1 class="page-title">Create New Order</h1>
<p class="page-subtitle">Select customer, add products, and calculate order totals</p>
</div>
<div>
<a href="/orders" class="btn btn-secondary">&larr; Back to Orders</a>
</div>
</div>
<form method="POST" action="/orders" x-data="orderForm()">
<?= \App\Core\View::csrfField() ?>
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; align-items: start;">
<!-- Left Column: Customer & Order Items -->
<div>
<!-- Customer Information Card -->
<div class="card">
<div class="card-header">
<h2 class="card-title">1. Customer Selection</h2>
<a href="/customers" target="_blank" style="font-size: 0.85rem; color: var(--primary); text-decoration: none;">+ Add Customer</a>
</div>
<div class="card-body">
<div class="form-group">
<label class="form-label">Customer <span style="color: var(--danger);">*</span></label>
<select name="customer_id" class="form-select" required>
<option value="">-- Choose Customer --</option>
<?php foreach ($customers as $customer): ?>
<option value="<?= e($customer['id']) ?>">
<?= e($customer['name']) ?> (<?= e($customer['email']) ?>)
</option>
<?php endforeach; ?>
</select>
</div>
</div>
</div>
<!-- Products / Items Card -->
<div class="card">
<div class="card-header">
<h2 class="card-title">2. Order Line Items</h2>
<button type="button" class="btn btn-secondary btn-sm" @click="addItem()">+ Add Product</button>
</div>
<div class="card-body" style="padding: 0;">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th style="width: 45%;">Product</th>
<th style="width: 18%;">Price ($)</th>
<th style="width: 15%;">Qty</th>
<th style="width: 17%; text-align: right;">Subtotal ($)</th>
<th style="width: 5%;"></th>
</tr>
</thead>
<tbody>
<template x-for="(item, index) in items" :key="index">
<tr>
<td>
<select :name="`items[${index}][product_id]`"
class="form-select"
x-model="item.product_id"
@change="onProductSelect(index)"
required>
<option value="">-- Select Product --</option>
<?php foreach ($products as $prod): ?>
<option value="<?= e($prod['id']) ?>"
data-price="<?= e($prod['price']) ?>"
data-name="<?= e($prod['name']) ?>"
data-stock="<?= e($prod['stock']) ?>">
<?= e($prod['name']) ?> ($<?= number_format((float)$prod['price'], 2) ?> | Stock: <?= e($prod['stock']) ?>)
</option>
<?php endforeach; ?>
</select>
<input type="hidden" :name="`items[${index}][product_name]`" :value="item.product_name">
</td>
<td>
<input type="number"
step="0.01"
:name="`items[${index}][unit_price]`"
class="form-control"
x-model.number="item.unit_price"
readonly
style="background-color: #f8fafc;">
</td>
<td>
<input type="number"
min="1"
:name="`items[${index}][quantity]`"
class="form-control"
x-model.number="item.quantity"
required>
</td>
<td style="text-align: right; font-weight: 600;" x-text="'$' + (item.quantity * item.unit_price).toFixed(2)">
</td>
<td style="text-align: center;">
<button type="button"
@click="removeItem(index)"
class="btn btn-sm"
style="color: var(--danger); background: none; border: none; font-size: 1.25rem;"
:disabled="items.length <= 1">&times;</button>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<div class="card-footer" style="display: flex; justify-content: flex-end;">
<button type="button" class="btn btn-secondary btn-sm" @click="addItem()">+ Add Another Line</button>
</div>
</div>
<!-- Notes Card -->
<div class="card">
<div class="card-header">
<h2 class="card-title">3. Order Notes / Special Instructions</h2>
</div>
<div class="card-body">
<textarea name="notes" class="form-textarea" rows="3" placeholder="Optional notes for delivery, customer requests, or reference..."></textarea>
</div>
</div>
</div>
<!-- Right Column: Summary & Actions -->
<div>
<div class="card" style="position: sticky; top: 5.5rem;">
<div class="card-header">
<h2 class="card-title">Order Summary</h2>
</div>
<div class="card-body">
<div class="form-group">
<label class="form-label">Initial Status</label>
<select name="status" class="form-select">
<option value="Pending">Pending</option>
<option value="Processing">Processing</option>
<option value="Completed">Completed</option>
</select>
</div>
<div style="border-top: 1px solid var(--border-color); padding-top: 1rem; margin-top: 1rem;">
<div class="summary-row">
<span style="color: var(--text-muted);">Items Subtotal:</span>
<span style="font-weight: 600;" x-text="'$' + calculateSubtotal().toFixed(2)">$0.00</span>
</div>
<div class="summary-row" style="align-items: center; margin-top: 0.5rem;">
<span style="color: var(--text-muted);">Tax Amount ($):</span>
<input type="number" step="0.01" min="0" name="tax" class="form-control" style="width: 100px; padding: 0.35rem 0.5rem; text-align: right;" x-model.number="tax">
</div>
<div class="summary-row" style="align-items: center; margin-top: 0.5rem;">
<span style="color: var(--text-muted);">Discount ($):</span>
<input type="number" step="0.01" min="0" name="discount" class="form-control" style="width: 100px; padding: 0.35rem 0.5rem; text-align: right; color: var(--danger);" x-model.number="discount">
</div>
<div class="summary-row summary-total">
<span>Grand Total:</span>
<span x-text="'$' + calculateGrandTotal().toFixed(2)">$0.00</span>
</div>
</div>
</div>
<div class="card-footer">
<button type="submit" class="btn btn-primary" style="width: 100%; padding: 0.85rem; font-size: 1rem;">
Place Order
</button>
</div>
</div>
</div>
</div>
</form>
<script>
function orderForm() {
return {
tax: 0.00,
discount: 0.00,
items: [
{ product_id: '', product_name: '', unit_price: 0, quantity: 1 }
],
addItem() {
this.items.push({ product_id: '', product_name: '', unit_price: 0, quantity: 1 });
},
removeItem(index) {
if (this.items.length > 1) {
this.items.splice(index, 1);
}
},
onProductSelect(index) {
const item = this.items[index];
if (!item.product_id) {
item.unit_price = 0;
item.product_name = '';
return;
}
// Find option in DOM to grab data attributes
const selectEl = document.querySelectorAll('select[name^="items"]')[index];
if (selectEl) {
const selectedOption = selectEl.options[selectEl.selectedIndex];
if (selectedOption) {
item.unit_price = parseFloat(selectedOption.getAttribute('data-price') || 0);
item.product_name = selectedOption.getAttribute('data-name') || '';
}
}
},
calculateSubtotal() {
return this.items.reduce((sum, item) => {
return sum + ((item.unit_price || 0) * (item.quantity || 0));
}, 0);
},
calculateGrandTotal() {
const subtotal = this.calculateSubtotal();
const total = subtotal + (Number(this.tax) || 0) - (Number(this.discount) || 0);
return Math.max(0, total);
}
};
}
</script>
+92
View File
@@ -0,0 +1,92 @@
<div class="page-header">
<div>
<h1 class="page-title">Orders</h1>
<p class="page-subtitle">Track, filter, and manage customer orders</p>
</div>
<div>
<a href="/orders/create" class="btn btn-primary">+ Create New Order</a>
</div>
</div>
<!-- Filters & Search -->
<div class="card" style="margin-bottom: 1.25rem;">
<div class="card-body" style="padding: 1rem 1.25rem;">
<form method="GET" action="/orders" class="filter-bar" style="margin-bottom: 0;">
<div style="flex: 1; min-width: 240px;">
<input type="text" name="search" class="form-control" placeholder="Search by Order #, Customer name or email..." value="<?= e($search ?? '') ?>">
</div>
<div style="min-width: 180px;">
<select name="status" class="form-select" onchange="this.form.submit()">
<option value="all" <?= ($currentStatus ?? 'all') === 'all' ? 'selected' : '' ?>>All Statuses</option>
<option value="Pending" <?= ($currentStatus ?? '') === 'Pending' ? 'selected' : '' ?>>Pending</option>
<option value="Processing" <?= ($currentStatus ?? '') === 'Processing' ? 'selected' : '' ?>>Processing</option>
<option value="Completed" <?= ($currentStatus ?? '') === 'Completed' ? 'selected' : '' ?>>Completed</option>
<option value="Cancelled" <?= ($currentStatus ?? '') === 'Cancelled' ? 'selected' : '' ?>>Cancelled</option>
</select>
</div>
<button type="submit" class="btn btn-secondary">Search</button>
<?php if (!empty($search) || ($currentStatus ?? 'all') !== 'all'): ?>
<a href="/orders" class="btn btn-secondary" style="color: var(--danger);">Reset</a>
<?php endif; ?>
</form>
</div>
</div>
<!-- Orders Table -->
<div class="card">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Order Number</th>
<th>Customer</th>
<th>Subtotal</th>
<th>Tax</th>
<th>Discount</th>
<th>Total</th>
<th>Status</th>
<th>Date</th>
<th style="text-align: right;">Action</th>
</tr>
</thead>
<tbody>
<?php if (empty($orders)): ?>
<tr>
<td colspan="9" style="text-align: center; color: var(--text-muted); padding: 3rem;">
No orders found matching your filter criteria.
</td>
</tr>
<?php else: ?>
<?php foreach ($orders as $order): ?>
<tr>
<td>
<a href="/orders/<?= e($order['id']) ?>" style="font-weight: 600; color: var(--primary); text-decoration: none;">
<?= e($order['order_number']) ?>
</a>
</td>
<td>
<div style="font-weight: 500;"><?= e($order['customer_name']) ?></div>
<div style="font-size: 0.8rem; color: var(--text-muted);"><?= e($order['customer_email']) ?></div>
</td>
<td><?= money($order['subtotal']) ?></td>
<td><?= money($order['tax']) ?></td>
<td style="color: var(--danger);">-<?= money($order['discount']) ?></td>
<td style="font-weight: 700; color: var(--primary); font-size: 1rem;"><?= money($order['total']) ?></td>
<td>
<span class="badge badge-<?= strtolower(e($order['status'])) ?>">
<?= e($order['status']) ?>
</span>
</td>
<td style="color: var(--text-muted); font-size: 0.85rem;"><?= date('Y-m-d H:i', strtotime($order['created_at'])) ?></td>
<td style="text-align: right;">
<a href="/orders/<?= e($order['id']) ?>" class="btn btn-secondary btn-sm">Details</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
+146
View File
@@ -0,0 +1,146 @@
<div class="page-header">
<div>
<h1 class="page-title">Order <?= e($order['order_number']) ?></h1>
<p class="page-subtitle">Created on <?= date('F j, Y, g:i a', strtotime($order['created_at'])) ?></p>
</div>
<div style="display: flex; gap: 0.75rem;">
<button type="button" class="btn btn-secondary" onclick="window.print()">🖨️ Print Invoice</button>
<a href="/orders" class="btn btn-secondary">&larr; Back to Orders</a>
</div>
</div>
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; align-items: start;">
<!-- Left Column: Detailed Invoice -->
<div class="invoice-card">
<div class="invoice-header">
<div>
<h2 style="font-size: 1.5rem; font-weight: 700; color: var(--primary);">SimpleOrder</h2>
<p style="color: var(--text-muted); font-size: 0.875rem;">Official Order Invoice</p>
</div>
<div class="invoice-meta">
<div style="font-size: 1.1rem; font-weight: 700;"><?= e($order['order_number']) ?></div>
<div style="margin-top: 0.25rem;">
<span class="badge badge-<?= strtolower(e($order['status'])) ?>">
Status: <?= e($order['status']) ?>
</span>
</div>
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 2rem;">
<div>
<h4 style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.5rem;">Billed To:</h4>
<div style="font-weight: 600; font-size: 1.05rem;"><?= e($order['customer_name']) ?></div>
<div style="color: var(--text-muted); font-size: 0.9rem;"><?= e($order['customer_email']) ?></div>
<?php if (!empty($order['customer_phone'])): ?>
<div style="color: var(--text-muted); font-size: 0.9rem;"><?= e($order['customer_phone']) ?></div>
<?php endif; ?>
<?php if (!empty($order['customer_address'])): ?>
<div style="color: var(--text-muted); font-size: 0.9rem; margin-top: 0.25rem;"><?= nl2br(e($order['customer_address'])) ?></div>
<?php endif; ?>
</div>
<div>
<h4 style="font-size: 0.8rem; text-transform: uppercase; color: var(--text-muted); margin-bottom: 0.5rem;">Order Details:</h4>
<div style="font-size: 0.9rem; margin-bottom: 0.25rem;"><strong>Date:</strong> <?= date('M d, Y', strtotime($order['created_at'])) ?></div>
<div style="font-size: 0.9rem; margin-bottom: 0.25rem;"><strong>Payment Status:</strong> Paid / Pending</div>
<div style="font-size: 0.9rem;"><strong>Last Updated:</strong> <?= date('M d, Y H:i', strtotime($order['updated_at'] ?? $order['created_at'])) ?></div>
</div>
</div>
<!-- Items Table -->
<table class="table" style="margin-bottom: 1.5rem;">
<thead>
<tr>
<th>Item Description</th>
<th>SKU</th>
<th style="text-align: right;">Unit Price</th>
<th style="text-align: center;">Qty</th>
<th style="text-align: right;">Amount</th>
</tr>
</thead>
<tbody>
<?php foreach ($order['items'] as $item): ?>
<tr>
<td>
<div style="font-weight: 600;"><?= e($item['product_name']) ?></div>
<div style="font-size: 0.8rem; color: var(--text-muted);"><?= e($item['category'] ?? 'Product') ?></div>
</td>
<td style="font-family: monospace; font-size: 0.85rem;"><?= e($item['sku'] ?? 'N/A') ?></td>
<td style="text-align: right;"><?= money($item['unit_price']) ?></td>
<td style="text-align: center; font-weight: 600;"><?= e($item['quantity']) ?></td>
<td style="text-align: right; font-weight: 600;"><?= money($item['total_price']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<!-- Totals Breakdown -->
<div class="order-summary-box">
<div class="summary-row">
<span style="color: var(--text-muted);">Subtotal:</span>
<span><?= money($order['subtotal']) ?></span>
</div>
<div class="summary-row">
<span style="color: var(--text-muted);">Tax:</span>
<span><?= money($order['tax']) ?></span>
</div>
<?php if ((float)$order['discount'] > 0): ?>
<div class="summary-row" style="color: var(--danger);">
<span>Discount:</span>
<span>-<?= money($order['discount']) ?></span>
</div>
<?php endif; ?>
<div class="summary-row summary-total">
<span>Grand Total:</span>
<span><?= money($order['total']) ?></span>
</div>
</div>
<?php if (!empty($order['notes'])): ?>
<div style="margin-top: 2rem; padding: 1rem; background-color: #f8fafc; border-radius: var(--radius-sm); border-left: 3px solid var(--primary);">
<div style="font-weight: 600; font-size: 0.85rem; margin-bottom: 0.25rem;">Order Notes:</div>
<div style="font-size: 0.9rem; color: var(--text-muted);"><?= nl2br(e($order['notes'])) ?></div>
</div>
<?php endif; ?>
</div>
<!-- Right Column: Status Controls & Actions -->
<div>
<div class="card">
<div class="card-header">
<h3 class="card-title">Update Status</h3>
</div>
<div class="card-body">
<form method="POST" action="/orders/<?= e($order['id']) ?>/status">
<?= \App\Core\View::csrfField() ?>
<div class="form-group">
<label class="form-label">Current Status</label>
<select name="status" class="form-select">
<option value="Pending" <?= $order['status'] === 'Pending' ? 'selected' : '' ?>>Pending</option>
<option value="Processing" <?= $order['status'] === 'Processing' ? 'selected' : '' ?>>Processing</option>
<option value="Completed" <?= $order['status'] === 'Completed' ? 'selected' : '' ?>>Completed</option>
<option value="Cancelled" <?= $order['status'] === 'Cancelled' ? 'selected' : '' ?>>Cancelled</option>
</select>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%;">Update Order Status</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h3 class="card-title" style="color: var(--danger);">Danger Zone</h3>
</div>
<div class="card-body">
<p style="font-size: 0.85rem; color: var(--text-muted); margin-bottom: 1rem;">Permanently remove this order and its associated records.</p>
<form method="POST" action="/orders/<?= e($order['id']) ?>/delete" onsubmit="return confirm('Are you sure you want to permanently delete this order?');">
<?= \App\Core\View::csrfField() ?>
<button type="submit" class="btn btn-danger" style="width: 100%;">Delete Order</button>
</form>
</div>
</div>
</div>
</div>
+110
View File
@@ -0,0 +1,110 @@
<div class="page-header">
<div>
<h1 class="page-title">Products &amp; Inventory</h1>
<p class="page-subtitle">Manage catalog items, pricing, and stock levels</p>
</div>
</div>
<div style="display: grid; grid-template-columns: 2fr 1fr; gap: 1.5rem; align-items: start;">
<!-- Products Table -->
<div class="card">
<div class="card-header">
<h2 class="card-title">Available Products (<?= count($products) ?>)</h2>
</div>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>SKU</th>
<th>Product Name</th>
<th>Category</th>
<th>Price</th>
<th>Stock</th>
</tr>
</thead>
<tbody>
<?php if (empty($products)): ?>
<tr>
<td colspan="5" style="text-align: center; color: var(--text-muted); padding: 2rem;">
No products found in the catalog.
</td>
</tr>
<?php else: ?>
<?php foreach ($products as $prod): ?>
<tr>
<td style="font-family: monospace; font-weight: 600; font-size: 0.85rem;"><?= e($prod['sku']) ?></td>
<td>
<div style="font-weight: 600;"><?= e($prod['name']) ?></div>
<?php if (!empty($prod['description'])): ?>
<div style="font-size: 0.8rem; color: var(--text-muted); max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<?= e($prod['description']) ?>
</div>
<?php endif; ?>
</td>
<td><span class="badge badge-neutral"><?= e($prod['category']) ?></span></td>
<td style="font-weight: 600;"><?= money($prod['price']) ?></td>
<td>
<?php if ($prod['stock'] <= 5): ?>
<span class="badge badge-cancelled"><?= e($prod['stock']) ?> (Low)</span>
<?php elseif ($prod['stock'] <= 25): ?>
<span class="badge badge-pending"><?= e($prod['stock']) ?></span>
<?php else: ?>
<span class="badge badge-completed"><?= e($prod['stock']) ?></span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
<!-- Add Product Form -->
<div class="card">
<div class="card-header">
<h2 class="card-title">Add New Product</h2>
</div>
<div class="card-body">
<form method="POST" action="/products">
<?= \App\Core\View::csrfField() ?>
<div class="form-group">
<label class="form-label">SKU / Code <span style="color: var(--danger);">*</span></label>
<input type="text" name="sku" class="form-control" placeholder="e.g. PROD-007" required>
</div>
<div class="form-group">
<label class="form-label">Product Name <span style="color: var(--danger);">*</span></label>
<input type="text" name="name" class="form-control" placeholder="e.g. Ergonomic Keyboard" required>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Price ($) <span style="color: var(--danger);">*</span></label>
<input type="number" step="0.01" min="0.01" name="price" class="form-control" placeholder="49.99" required>
</div>
<div class="form-group">
<label class="form-label">Initial Stock <span style="color: var(--danger);">*</span></label>
<input type="number" min="0" name="stock" class="form-control" placeholder="50" required>
</div>
</div>
<div class="form-group">
<label class="form-label">Category</label>
<input type="text" name="category" class="form-control" placeholder="Electronics, Accessories, etc.">
</div>
<div class="form-group">
<label class="form-label">Description</label>
<textarea name="description" class="form-textarea" rows="2" placeholder="Brief product overview..."></textarea>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%;">Create Product</button>
</form>
</div>
</div>
</div>
+58
View File
@@ -0,0 +1,58 @@
<div class="page-header">
<div>
<h1 class="page-title">Start a Food / Canteen Session</h1>
<p class="page-subtitle">Host a lunch order, post the available menu, and collect orders from your team</p>
</div>
<div>
<a href="/" class="btn btn-secondary">&larr; Back to Active Sessions</a>
</div>
</div>
<div style="max-width: 680px; margin: 0 auto;">
<div class="card">
<div class="card-header">
<h2 class="card-title">Session Details</h2>
</div>
<div class="card-body">
<form method="POST" action="/sessions">
<?= \App\Core\View::csrfField() ?>
<div class="form-group">
<label class="form-label">Session Title <span style="color: var(--danger);">*</span></label>
<input type="text" name="title" class="form-control" placeholder="e.g. Dea's Kantin Bu Siti Lunch Run" value="<?= e($currentUser['name']) ?>'s Lunch Run" required autofocus>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">Canteen / Restaurant Name <span style="color: var(--danger);">*</span></label>
<input type="text" name="canteen_name" class="form-control" placeholder="e.g. Kantin Bu Siti / Nasi Padang Sederhana" required>
</div>
<div class="form-group">
<label class="form-label">Date <span style="color: var(--danger);">*</span></label>
<input type="date" name="session_date" class="form-control" value="<?= date('Y-m-d') ?>" required>
</div>
</div>
<div class="form-group">
<label class="form-label">Notice / Cut-off Instructions</label>
<input type="text" name="notes" class="form-control" placeholder="e.g. Please place your order before 11:30 AM! Delivery around 12:15 PM.">
</div>
<div class="form-group" style="margin-top: 1.5rem;">
<label class="form-label">
<span>Today's Available Menu Options (Paste line by line from chat):</span>
</label>
<textarea name="menu_text" class="form-textarea" rows="5" placeholder="1. Ayam Bakar Madu + Nasi&#10;2. Ayam Geprek Sambal Bawang&#10;3. Nasi Goreng Spesial&#10;4. Soto Ayam Lamongan&#10;5. Es Teh Manis&#10;6. Es Jeruk Segar"></textarea>
<small style="color: var(--text-muted); font-size: 0.8rem; margin-top: 0.25rem; display: block;">
💡 Prices are not needed yet! You can settle final prices after the food arrives with the receipt.
</small>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; padding: 0.85rem; font-size: 1rem; margin-top: 1rem;">
🚀 Publish Food Session &amp; Open for Orders
</button>
</form>
</div>
</div>
</div>
+65
View File
@@ -0,0 +1,65 @@
<div class="page-header">
<div>
<h1 class="page-title">Lunch Sessions History</h1>
<p class="page-subtitle">Archive of past daily canteen lunch orders and billing</p>
</div>
<div>
<a href="/" class="btn btn-primary">&larr; Back to Today's Session</a>
</div>
</div>
<div class="card">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Session Title</th>
<th>Canteen</th>
<th>Orders</th>
<th>Menu Items</th>
<th>Total Billed</th>
<th>Status</th>
<th style="text-align: right;">Action</th>
</tr>
</thead>
<tbody>
<?php if (empty($sessions)): ?>
<tr>
<td colspan="8" style="text-align: center; color: var(--text-muted); padding: 3rem;">
No past sessions found.
</td>
</tr>
<?php else: ?>
<?php foreach ($sessions as $s): ?>
<tr>
<td style="font-weight: 600;"><?= date('M d, Y', strtotime($s['session_date'])) ?></td>
<td>
<a href="/sessions/<?= e($s['id']) ?>" style="font-weight: 600; color: var(--primary); text-decoration: none;">
<?= e($s['title']) ?>
</a>
</td>
<td><?= e($s['canteen_name']) ?></td>
<td><?= e($s['order_count']) ?> people</td>
<td><?= e($s['menu_count']) ?> items</td>
<td style="font-weight: 700; color: var(--primary);"><?= money($s['total_session_amount']) ?></td>
<td>
<span class="badge badge-<?= match($s['status']) {
'open' => 'completed',
'closed' => 'warning',
'arrived' => 'processing',
default => 'neutral'
} ?>">
<?= strtoupper(e($s['status'])) ?>
</span>
</td>
<td style="text-align: right;">
<a href="/sessions/<?= e($s['id']) ?>" class="btn btn-secondary btn-sm">View Board</a>
</td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
</div>
</div>
+76
View File
@@ -0,0 +1,76 @@
<div class="page-header">
<div>
<h1 class="page-title">Active Food &amp; Canteen Sessions</h1>
<p class="page-subtitle">Join an active lunch session or host your own food order for the team</p>
</div>
<div>
<a href="/sessions/create" class="btn btn-primary"> Start a Food Session</a>
</div>
</div>
<?php if (empty($activeSessions)): ?>
<div class="card" style="text-align: center; padding: 4rem 2rem;">
<div style="font-size: 3rem; margin-bottom: 0.75rem;">🍱</div>
<h2 style="font-size: 1.25rem; font-weight: 700; color: var(--text-main);">No Active Food Sessions Right Now</h2>
<p style="color: var(--text-muted); margin-top: 0.5rem; max-width: 400px; margin-left: auto; margin-right: auto;">
Anyone can start a session! Click below to post today's canteen menu or coordinate a team food run.
</p>
<div style="margin-top: 1.5rem;">
<a href="/sessions/create" class="btn btn-primary"> Start New Food Session</a>
</div>
</div>
<?php else: ?>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 1.5rem; margin-bottom: 2.5rem;">
<?php foreach ($activeSessions as $session): ?>
<div class="card" style="display: flex; flex-direction: column; height: 100%; border: 1px solid var(--border-color); transition: transform 0.15s ease, box-shadow 0.15s ease;">
<div class="card-header" style="background: white; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: flex-start;">
<div>
<span class="badge badge-<?= match($session['status']) {
'open' => 'completed',
'closed' => 'warning',
'arrived' => 'processing',
default => 'neutral'
} ?>" style="font-size: 0.75rem; margin-bottom: 0.4rem;">
<?= strtoupper(e($session['status'])) ?>
</span>
<h2 style="font-size: 1.15rem; font-weight: 700; color: var(--text-main); margin-top: 0.2rem;">
<a href="/sessions/<?= e($session['id']) ?>" style="text-decoration: none; color: inherit;">
<?= e($session['title']) ?>
</a>
</h2>
</div>
</div>
<div class="card-body" style="flex: 1; display: flex; flex-direction: column; gap: 0.75rem;">
<div style="font-size: 0.9rem; color: var(--text-muted);">
🏢 Canteen: <strong><?= e($session['canteen_name']) ?></strong>
</div>
<div style="font-size: 0.85rem; color: var(--text-muted); display: flex; align-items: center; gap: 0.4rem;">
<span>👤 Hosted by:</span>
<strong><?= e($session['creator_name']) ?></strong>
<span style="font-size: 0.75rem; background: #f1f5f9; padding: 0.1rem 0.35rem; border-radius: var(--radius-sm);">@<?= e($session['creator_username']) ?></span>
</div>
<?php if (!empty($session['notes'])): ?>
<div style="font-size: 0.825rem; color: #1e40af; background: #eff6ff; padding: 0.5rem 0.75rem; border-radius: var(--radius-sm); border-left: 3px solid #3b82f6;">
📢 <?= e($session['notes']) ?>
</div>
<?php endif; ?>
<div style="display: flex; gap: 1rem; margin-top: auto; padding-top: 0.75rem; border-top: 1px dashed var(--border-color); font-size: 0.85rem; color: var(--text-muted);">
<div>👥 <strong><?= e($session['order_count']) ?></strong> ordered</div>
<div>🍱 <strong><?= e($session['menu_count']) ?></strong> menu items</div>
</div>
</div>
<div class="card-footer" style="background: #f8fafc; display: flex; justify-content: space-between; align-items: center;">
<span style="font-size: 0.8rem; color: var(--text-muted);"><?= date('M d, Y', strtotime($session['session_date'])) ?></span>
<a href="/sessions/<?= e($session['id']) ?>" class="btn btn-primary btn-sm">
<?= $session['status'] === 'open' ? '🍱 View & Order &rarr;' : '📋 View Live Board &rarr;' ?>
</a>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
+533
View File
@@ -0,0 +1,533 @@
<div class="page-header">
<div>
<div style="display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;">
<h1 class="page-title"><?= e($session['title']) ?></h1>
<span class="badge badge-<?= match($session['status']) {
'open' => 'completed',
'closed' => 'warning',
'arrived' => 'processing',
default => 'neutral'
} ?>" style="font-size: 0.85rem; padding: 0.35rem 0.75rem;">
<?= strtoupper(e($session['status'])) ?>
</span>
</div>
<p class="page-subtitle">
🏢 Canteen: <strong><?= e($session['canteen_name']) ?></strong> &bull;
👤 Hosted by: <strong><?= e($session['creator_name']) ?></strong> (@<?= e($session['creator_username']) ?>) &bull;
📅 Date: <strong><?= date('F j, Y', strtotime($session['session_date'])) ?></strong>
</p>
</div>
<!-- Status Controls for Session Creator -->
<?php if ($isCreator): ?>
<div style="display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap;">
<form method="POST" action="/sessions/<?= e($session['id']) ?>/status" style="display: inline-flex; gap: 0.5rem;">
<?= \App\Core\View::csrfField() ?>
<select name="status" class="form-select" onchange="this.form.submit()" style="font-size: 0.85rem; padding: 0.4rem 0.75rem;">
<option value="open" <?= $session['status'] === 'open' ? 'selected' : '' ?>>🟢 Status: Open (Taking Orders)</option>
<option value="closed" <?= $session['status'] === 'closed' ? 'selected' : '' ?>>🟡 Status: Closed (Sent to Canteen)</option>
<option value="arrived" <?= $session['status'] === 'arrived' ? 'selected' : '' ?>>🔵 Status: Food Arrived (Billing)</option>
<option value="completed" <?= $session['status'] === 'completed' ? 'selected' : '' ?>>⚪ Status: Completed / Settled</option>
</select>
</form>
</div>
<?php endif; ?>
</div>
<?php if (!empty($session['notes'])): ?>
<div class="alert alert-info" style="background-color: #eff6ff; border: 1px solid #bfdbfe; color: #1e40af; margin-bottom: 1.5rem;">
<span>📢 <strong>Host Note:</strong> <?= e($session['notes']) ?></span>
</div>
<?php endif; ?>
<!-- Quick Stats Bar -->
<div class="grid-stats" style="margin-bottom: 1.5rem;">
<div class="stat-card">
<div>
<div class="stat-label">Coworkers Ordered</div>
<div class="stat-value"><?= count($session['orders']) ?></div>
</div>
<div class="stat-icon" style="background: #eef2ff; color: #4f46e5;">👥</div>
</div>
<div class="stat-card">
<div>
<div class="stat-label">Total Food Items</div>
<div class="stat-value"><?= array_sum(array_column($session['recap'], 'total_quantity')) ?></div>
</div>
<div class="stat-icon" style="background: #ecfdf5; color: #10b981;">🍱</div>
</div>
<div class="stat-card">
<div>
<div class="stat-label">Total Session Bill</div>
<div class="stat-value"><?= money(array_sum(array_column($session['orders'], 'total_bill'))) ?></div>
</div>
<div class="stat-icon" style="background: #fffbeb; color: #f59e0b;">💵</div>
</div>
</div>
<!-- Main 2-Column Hub Layout -->
<div style="display: grid; grid-template-columns: 1.2fr 1fr; gap: 1.5rem; align-items: start;">
<!-- Left Column: Self-Ordering Widget & Live Coworker Orders Feed -->
<div>
<!-- Interactive Self-Order Form (Alpine.js) -->
<?php if ($session['status'] === 'open'): ?>
<div class="card" x-data="canteenOrderForm()" style="border: 2px solid var(--primary);">
<div class="card-header" style="background: var(--primary-light); display: flex; justify-content: space-between; align-items: center;">
<div>
<h2 class="card-title" style="color: var(--primary);">
<?= $myOrder ? '✏️ Update Your Lunch Order' : ' Place Your Lunch Order' ?>
</h2>
<p style="font-size: 0.8rem; color: var(--text-muted); margin-top: 0.15rem;">
Ordering as: <strong><?= e($currentUser['name']) ?></strong> (@<?= e($currentUser['username']) ?>)
</p>
</div>
<?php if ($myOrder): ?>
<span class="badge badge-completed">Already Ordered</span>
<?php endif; ?>
</div>
<div class="card-body">
<form method="POST" action="/employee-orders" @submit="validateSubmit($event)">
<?= \App\Core\View::csrfField() ?>
<input type="hidden" name="session_id" value="<?= e($session['id']) ?>">
<!-- Multi-Select Menu Selection -->
<div class="form-group">
<label class="form-label" style="display: flex; justify-content: space-between; align-items: center;">
<span>Click items to Multi-Select:</span>
<span style="font-size: 0.75rem; font-weight: normal; color: var(--text-muted);">
<span x-text="selectedItems.length">0</span> item(s) selected
</span>
</label>
<?php if (empty($session['menus'])): ?>
<div style="padding: 1.5rem; text-align: center; background: #f8fafc; border-radius: var(--radius-sm); border: 1px dashed var(--border-color);">
<p style="color: var(--text-muted); font-size: 0.9rem;">No menu listed yet for today.</p>
</div>
<?php else: ?>
<div class="menu-grid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 0.6rem;">
<?php foreach ($session['menus'] as $menu): ?>
<div class="menu-select-card"
:class="{ 'is-selected': isSelected(<?= e($menu['id']) ?>) }"
@click="toggleItem(<?= e($menu['id']) ?>, '<?= addslashes(e($menu['name'])) ?>')">
<div style="display: flex; align-items: flex-start; gap: 0.5rem;">
<input type="checkbox"
:checked="isSelected(<?= e($menu['id']) ?>)"
style="margin-top: 0.2rem; pointer-events: none;">
<div style="flex: 1;">
<div style="font-weight: 600; font-size: 0.875rem; line-height: 1.3;">
<?= e($menu['name']) ?>
</div>
<?php if ((float)$menu['final_price'] > 0): ?>
<div style="font-size: 0.775rem; color: var(--success); font-weight: 600; margin-top: 0.2rem;">
<?= money($menu['final_price']) ?>
</div>
<?php else: ?>
<div style="font-size: 0.725rem; color: var(--text-muted); margin-top: 0.2rem;">
(Price settled on arrival)
</div>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<!-- Selected Items Customizer (Quantity & Notes) -->
<template x-if="selectedItems.length > 0">
<div style="margin-top: 1.25rem; background: #f8fafc; border: 1px solid var(--border-color); border-radius: var(--radius-sm); padding: 1rem;">
<div style="font-weight: 600; font-size: 0.85rem; margin-bottom: 0.75rem; color: var(--text-main);">
📝 Customize Quantities &amp; Requests:
</div>
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
<template x-for="(item, idx) in selectedItems" :key="idx">
<div style="display: flex; align-items: center; gap: 0.75rem; background: white; padding: 0.6rem 0.75rem; border-radius: var(--radius-sm); border: 1px solid var(--border-color); flex-wrap: wrap;">
<input type="hidden" :name="`items[${idx}][session_menu_id]`" :value="item.id">
<input type="hidden" :name="`items[${idx}][item_name]`" :value="item.name">
<div style="flex: 2; min-width: 140px; font-weight: 600; font-size: 0.85rem;" x-text="item.name"></div>
<div style="display: flex; align-items: center; gap: 0.35rem;">
<button type="button" class="btn btn-secondary btn-sm" style="padding: 0.15rem 0.5rem; height: 28px;" @click="item.quantity = Math.max(1, item.quantity - 1)">-</button>
<input type="number" min="1" :name="`items[${idx}][quantity]`" class="form-control" style="width: 50px; text-align: center; padding: 0.2rem;" x-model.number="item.quantity">
<button type="button" class="btn btn-secondary btn-sm" style="padding: 0.15rem 0.5rem; height: 28px;" @click="item.quantity++">+</button>
</div>
<div style="flex: 3; min-width: 160px;">
<input type="text" :name="`items[${idx}][notes]`" class="form-control" placeholder="Notes (e.g. no spicy, extra egg)" x-model="item.notes" style="font-size: 0.825rem; padding: 0.3rem 0.5rem;">
</div>
<button type="button" @click="removeItem(idx)" style="background: none; border: none; color: var(--danger); font-size: 1.25rem; cursor: pointer; padding: 0 0.25rem;">&times;</button>
</div>
</template>
</div>
</div>
</template>
<!-- General Order Note -->
<div class="form-group" style="margin-top: 1rem;">
<label class="form-label">General Note (Optional)</label>
<input type="text" name="notes" class="form-control" placeholder="e.g. Please put in plastic bag" value="<?= e($myOrder['notes'] ?? '') ?>">
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; padding: 0.75rem; margin-top: 0.5rem; font-size: 1rem;">
🚀 <?= $myOrder ? 'Update My Order' : 'Submit My Lunch Order' ?>
</button>
</form>
</div>
</div>
<?php else: ?>
<div class="card" style="background: #f8fafc; border: 1px dashed var(--border-color);">
<div class="card-body" style="text-align: center; padding: 2rem;">
<div style="font-size: 2rem; margin-bottom: 0.5rem;">🔒</div>
<h3 style="font-size: 1.1rem; font-weight: 600;">Orders Closed for this Session</h3>
<p style="color: var(--text-muted); font-size: 0.875rem; margin-top: 0.25rem;">
This order session is no longer taking new submissions. Please contact the host (<strong><?= e($session['creator_name']) ?></strong>) directly.
</p>
</div>
</div>
<?php endif; ?>
<!-- Live Coworker Orders Feed -->
<div class="card" style="margin-top: 1.5rem;">
<div class="card-header">
<div>
<h2 class="card-title">📋 Orders in this Session (<?= count($session['orders']) ?> people)</h2>
<p style="font-size: 0.8rem; color: var(--text-muted);">Real-time feed of all team members who joined</p>
</div>
</div>
<?php if (empty($session['orders'])): ?>
<div class="card-body" style="text-align: center; padding: 3rem; color: var(--text-muted);">
No orders placed yet. Be the first to order above!
</div>
<?php else: ?>
<div style="display: flex; flex-direction: column;">
<?php foreach ($session['orders'] as $order): ?>
<div style="padding: 1.25rem 1.5rem; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; flex-wrap: wrap; <?= (int)$order['user_id'] === (int)$currentUser['id'] ? 'background-color: #faf5ff;' : '' ?>">
<div style="flex: 1; min-width: 250px;">
<div style="display: flex; align-items: center; gap: 0.5rem;">
<strong style="font-size: 1rem; color: var(--text-main);">
<?= e($order['user_name']) ?>
<?= (int)$order['user_id'] === (int)$currentUser['id'] ? ' <span style="font-size: 0.75rem; color: var(--primary); font-weight: normal;">(You)</span>' : '' ?>
</strong>
<?php if (!empty($order['mattermost_username'])): ?>
<span style="font-size: 0.775rem; color: var(--text-muted); background: #f1f5f9; padding: 0.15rem 0.4rem; border-radius: var(--radius-sm);">@<?= e($order['mattermost_username']) ?></span>
<?php endif; ?>
<span style="font-size: 0.75rem; color: var(--text-muted); margin-left: auto;"><?= date('H:i', strtotime($order['created_at'])) ?></span>
</div>
<ul style="list-style: none; margin-top: 0.5rem; display: flex; flex-direction: column; gap: 0.35rem;">
<?php foreach ($order['items'] as $item): ?>
<li style="font-size: 0.9rem; display: flex; align-items: center; gap: 0.5rem;">
<span style="font-weight: 700; color: var(--primary); background: var(--primary-light); padding: 0.1rem 0.45rem; border-radius: var(--radius-sm); font-size: 0.8rem;"><?= e($item['quantity']) ?>x</span>
<span><?= e($item['item_name']) ?></span>
<?php if (!empty($item['notes'])): ?>
<em style="color: var(--text-muted); font-size: 0.8rem;">(<?= e($item['notes']) ?>)</em>
<?php endif; ?>
<?php if ((float)$item['total_price'] > 0): ?>
<span style="margin-left: auto; font-weight: 600; font-size: 0.85rem;"><?= money($item['total_price']) ?></span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php if (!empty($order['notes'])): ?>
<div style="margin-top: 0.5rem; font-size: 0.8rem; color: var(--text-muted); background: #f8fafc; padding: 0.35rem 0.6rem; border-radius: var(--radius-sm);">
💬 Note: <?= e($order['notes']) ?>
</div>
<?php endif; ?>
</div>
<!-- Personal Bill & Payment Status -->
<div style="text-align: right; min-width: 140px;">
<div style="font-size: 1.15rem; font-weight: 700; color: var(--text-main);">
<?= (float)$order['total_bill'] > 0 ? money($order['total_bill']) : '<span style="font-size: 0.8rem; color: var(--text-muted); font-weight: normal;">Pending Price</span>' ?>
</div>
<div style="margin-top: 0.35rem;">
<?php if ($isCreator): ?>
<form method="POST" action="/employee-orders/<?= e($order['id']) ?>/toggle-paid" style="display: inline;">
<?= \App\Core\View::csrfField() ?>
<input type="hidden" name="session_id" value="<?= e($session['id']) ?>">
<button type="submit" class="badge badge-<?= $order['status'] === 'paid' ? 'completed' : 'pending' ?>" style="cursor: pointer; border: none;">
<?= $order['status'] === 'paid' ? '✓ PAID' : '⌛ UNPAID' ?>
</button>
</form>
<?php else: ?>
<span class="badge badge-<?= $order['status'] === 'paid' ? 'completed' : 'pending' ?>">
<?= $order['status'] === 'paid' ? '✓ PAID' : '⌛ UNPAID' ?>
</span>
<?php endif; ?>
<?php if ($isCreator || (int)$order['user_id'] === (int)$currentUser['id']): ?>
<form method="POST" action="/employee-orders/<?= e($order['id']) ?>/delete" style="display: inline; margin-left: 0.35rem;" onsubmit="return confirm('Cancel this order?');">
<?= \App\Core\View::csrfField() ?>
<input type="hidden" name="session_id" value="<?= e($session['id']) ?>">
<button type="submit" style="background: none; border: none; color: var(--danger); font-size: 0.8rem; cursor: pointer;">✕</button>
</form>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- Right Column: Host Tools (Recap, Settlement, Menu Management) -->
<div>
<!-- 1. Canteen Recap (1-Click Copy for Canteen Placement) -->
<div class="card" x-data="canteenRecapHelper()">
<div class="card-header" style="background: #f0fdf4; border-bottom: 1px solid #bbf7d0;">
<div>
<h2 class="card-title" style="color: #166534;">📦 Canteen Order Summary</h2>
<p style="font-size: 0.8rem; color: #15803d;">Grouped quantities for ordering</p>
</div>
<button type="button" class="btn btn-sm btn-success" @click="copyCanteenText()">
📋 <span x-text="copyBtnText">Copy Canteen Text</span>
</button>
</div>
<div class="card-body">
<?php if (empty($session['recap'])): ?>
<p style="color: var(--text-muted); font-size: 0.875rem; text-align: center; padding: 1rem;">
No items ordered yet.
</p>
<?php else: ?>
<div style="display: flex; flex-direction: column; gap: 0.5rem; margin-bottom: 1rem;">
<?php foreach ($session['recap'] as $recap): ?>
<div style="display: flex; justify-content: space-between; align-items: flex-start; padding: 0.4rem 0; border-bottom: 1px dashed var(--border-color);">
<div>
<span style="font-weight: 700; color: var(--primary); font-size: 0.95rem;"><?= e($recap['total_quantity']) ?>x</span>
<strong style="margin-left: 0.35rem; font-size: 0.9rem;"><?= e($recap['item_name']) ?></strong>
<?php if (!empty($recap['combined_notes'])): ?>
<div style="font-size: 0.775rem; color: var(--text-muted); margin-left: 1.5rem;">
Note: <?= e($recap['combined_notes']) ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<textarea x-ref="recapText" style="display: none;"><?= e(generateCanteenCopyText($session)) ?></textarea>
</div>
</div>
<!-- 2. End-of-Day Food Arrival & Bill Settlement (Available for Host) -->
<?php if ($isCreator): ?>
<div class="card" style="border: 2px solid #3b82f6;" x-data="mattermostRecapHelper()">
<div class="card-header" style="background: #eff6ff; border-bottom: 1px solid #bfdbfe;">
<div>
<h2 class="card-title" style="color: #1e40af;">💰 Settle Canteen Bill (Arrival)</h2>
<p style="font-size: 0.8rem; color: #1e3a8a;">Enter final receipt prices to compute everyone's bill</p>
</div>
</div>
<div class="card-body">
<form method="POST" action="/sessions/<?= e($session['id']) ?>/settle-prices">
<?= \App\Core\View::csrfField() ?>
<div style="display: flex; flex-direction: column; gap: 0.6rem; margin-bottom: 1rem;">
<?php foreach ($session['menus'] as $menu): ?>
<div style="display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; font-size: 0.875rem;">
<span style="flex: 1; font-weight: 500;"><?= e($menu['name']) ?>:</span>
<div style="display: flex; align-items: center; gap: 0.25rem;">
<span style="color: var(--text-muted);">$</span>
<input type="number"
step="0.01"
min="0"
name="prices[<?= e($menu['id']) ?>]"
class="form-control"
style="width: 90px; padding: 0.25rem 0.5rem; text-align: right;"
value="<?= (float)$menu['final_price'] > 0 ? e($menu['final_price']) : '' ?>"
placeholder="0.00">
</div>
</div>
<?php endforeach; ?>
</div>
<button type="submit" class="btn btn-primary" style="width: 100%; font-size: 0.9rem;">
⚡ Save Prices &amp; Compute All Bills
</button>
</form>
<?php if (array_sum(array_column($session['orders'], 'total_bill')) > 0): ?>
<div style="margin-top: 1.25rem; border-top: 1px solid var(--border-color); padding-top: 1rem;">
<button type="button" class="btn btn-secondary" style="width: 100%; font-size: 0.85rem;" @click="copyMattermostText()">
📢 <span x-text="mmBtnText">Copy Mattermost Billing Breakdown</span>
</button>
<textarea x-ref="mattermostText" style="display: none;"><?= e(generateMattermostBillText($session)) ?></textarea>
</div>
<?php endif; ?>
</div>
</div>
<!-- 3. Host Menu Manager -->
<div class="card">
<div class="card-header">
<h2 class="card-title">📝 Menu Options Manager</h2>
</div>
<div class="card-body">
<form method="POST" action="/sessions/<?= e($session['id']) ?>/add-menu">
<?= \App\Core\View::csrfField() ?>
<div class="form-group">
<label class="form-label" style="font-size: 0.85rem;">Add More Items (Paste line by line):</label>
<textarea name="menu_text" class="form-textarea" rows="2" placeholder="e.g. Extra Kerupuk&#10;Es Kopi Susu" required></textarea>
</div>
<button type="submit" class="btn btn-secondary btn-sm" style="width: 100%;">+ Add to Menu</button>
</form>
<?php if (!empty($session['menus'])): ?>
<div style="margin-top: 1rem; border-top: 1px solid var(--border-color); padding-top: 0.75rem;">
<ul style="list-style: none; display: flex; flex-direction: column; gap: 0.35rem;">
<?php foreach ($session['menus'] as $m): ?>
<li style="display: flex; justify-content: space-between; align-items: center; font-size: 0.85rem; padding: 0.25rem 0;">
<span>&bull; <?= e($m['name']) ?></span>
<form method="POST" action="/sessions/<?= e($session['id']) ?>/delete-menu" style="display: inline;">
<?= \App\Core\View::csrfField() ?>
<input type="hidden" name="menu_id" value="<?= e($m['id']) ?>">
<button type="submit" style="background: none; border: none; color: var(--text-muted); cursor: pointer; font-size: 0.9rem;" title="Delete item">&times;</button>
</form>
</li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
</div>
</div>
<!-- Alpine.js Multi-Select Form Logic -->
<script>
function canteenOrderForm() {
<?php
$initialItems = [];
if (!empty($myOrder['items'])) {
foreach ($myOrder['items'] as $item) {
$initialItems[] = [
'id' => $item['session_menu_id'] ? (int)$item['session_menu_id'] : null,
'name' => $item['item_name'],
'quantity' => (int)$item['quantity'],
'notes' => $item['notes'] ?? '',
];
}
}
?>
return {
selectedItems: <?= json_encode($initialItems) ?>,
isSelected(menuId) {
return this.selectedItems.some(i => i.id === menuId);
},
toggleItem(menuId, menuName) {
const idx = this.selectedItems.findIndex(i => i.id === menuId);
if (idx >= 0) {
this.selectedItems.splice(idx, 1);
} else {
this.selectedItems.push({
id: menuId,
name: menuName,
quantity: 1,
notes: ''
});
}
},
removeItem(index) {
this.selectedItems.splice(index, 1);
},
validateSubmit(e) {
if (this.selectedItems.length === 0) {
alert('Please click on at least one menu item to select it!');
e.preventDefault();
return;
}
}
};
}
function canteenRecapHelper() {
return {
copyBtnText: 'Copy Canteen Text',
copyCanteenText() {
const text = this.$refs.recapText.value;
navigator.clipboard.writeText(text).then(() => {
this.copyBtnText = '✓ Copied to Clipboard!';
setTimeout(() => this.copyBtnText = 'Copy Canteen Text', 2500);
});
}
};
}
function mattermostRecapHelper() {
return {
mmBtnText: 'Copy Mattermost Breakdown',
copyMattermostText() {
const text = this.$refs.mattermostText.value;
navigator.clipboard.writeText(text).then(() => {
this.mmBtnText = '✓ Mattermost Summary Copied!';
setTimeout(() => this.mmBtnText = 'Copy Mattermost Breakdown', 2500);
});
}
};
}
</script>
<?php
function generateCanteenCopyText(array $session): string {
$out = "🍱 *Order Makan Siang - " . $session['title'] . "*\n";
$out .= "Canteen: " . $session['canteen_name'] . " (" . $session['session_date'] . ")\n\n";
$out .= "Rekap Pesanan:\n";
foreach ($session['recap'] as $r) {
$out .= "- " . $r['total_quantity'] . "x " . $r['item_name'];
if (!empty($r['combined_notes'])) {
$out .= " (" . $r['combined_notes'] . ")";
}
$out .= "\n";
}
$totalQty = array_sum(array_column($session['recap'], 'total_quantity'));
$out .= "\nTotal: " . $totalQty . " item(s). Terima kasih!";
return $out;
}
function generateMattermostBillText(array $session): string {
$out = "📢 **Tagihan Makan Siang (" . $session['title'] . ")**\n\n";
$out .= "| Nama | Pesanan | Total Tagihan | Status |\n";
$out .= "|:---|:---|:---|:---|\n";
foreach ($session['orders'] as $o) {
$itemList = [];
foreach ($o['items'] as $i) {
$itemList[] = $i['quantity'] . "x " . $i['item_name'];
}
$tag = !empty($o['mattermost_username']) ? "@" . $o['mattermost_username'] : $o['user_name'];
$status = $o['status'] === 'paid' ? "✅ Lunas" : "⏳ Belum";
$out .= "| " . $tag . " | " . implode(', ', $itemList) . " | " . money($o['total_bill']) . " | " . $status . " |\n";
}
$totalBill = array_sum(array_column($session['orders'], 'total_bill'));
$out .= "\n**Total Keseluruhan:** " . money($totalBill) . "\n";
$out .= "Mohon transfer/bayar ke " . $session['creator_name'] . " ya. Terima kasih! 🙏";
return $out;
}
?>