feat: initial foundation for collaborative canteen ordering system in vanilla PHP
This commit is contained in:
@@ -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">← 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">×</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>
|
||||
Reference in New Issue
Block a user