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
+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>