Dashboard Changes

This commit is contained in:
Utkarsh Khedkar
2025-12-22 22:38:35 +05:30
parent ccce02f43e
commit 72a81fa111
3 changed files with 505 additions and 562 deletions

View File

@@ -10,61 +10,38 @@ use App\Models\MarkList;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use App\Models\User;
use PDF; // barryvdh/laravel-dompdf facade
use PDF;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\OrdersExport;
class AdminOrderController extends Controller
{
// ---------------------------
// LIST / DASHBOARD
// ---------------------------
/* ---------------------------
* LIST / DASHBOARD
* ---------------------------*/
public function index()
{
// raw list for admin dashboard (simple)
$orders = Order::latest()->get();
$markList = MarkList::where('status', 'active')->get();
return view('admin.dashboard', compact('orders', 'markList'));
}
/**
* Orders list (detailed)
*/
// public function orderShow()
// {
// $orders = Order::with(['markList', 'shipments', 'invoice'])
// ->latest('id')
// ->get();
// return view('admin.orders', compact('orders'));
// }
// ---------------------------
// CREATE NEW ORDER (simple DB flow)
// ---------------------------
/**
* Show create form (you can place create UI on separate view or dashboard)
*/
/* ---------------------------
* CREATE NEW ORDER (simple page)
* ---------------------------*/
public function create()
{
// return a dedicated create view - create it at resources/views/admin/orders_create.blade.php
// If you prefer create UI on dashboard, change this to redirect route('admin.orders.index') etc.
$markList = MarkList::where('status', 'active')->get();
return view('admin.orders_create', compact('markList'));
}
/**
* Store a new order and optionally create initial invoice
*/
public function store(Request $request)
{
$data = $request->validate([
'mark_no' => 'required|string',
'origin' => 'nullable|string',
'destination' => 'nullable|string',
// totals optional when creating without items
'ctn' => 'nullable|numeric',
'qty' => 'nullable|numeric',
'ttl_qty' => 'nullable|numeric',
@@ -79,7 +56,7 @@ class AdminOrderController extends Controller
'order_id' => $this->generateOrderId(),
'mark_no' => $data['mark_no'],
'origin' => $data['origin'] ?? null,
'destination' => $data['destination'] ?? null,
'destination'=> $data['destination'] ?? null,
'ctn' => $data['ctn'] ?? 0,
'qty' => $data['qty'] ?? 0,
'ttl_qty' => $data['ttl_qty'] ?? 0,
@@ -91,16 +68,15 @@ class AdminOrderController extends Controller
'status' => 'pending',
]);
//If you want to auto-create an invoice at order creation, uncomment:
$this->createInvoice($order);
return redirect()->route('admin.orders.show', $order->id)
->with('success', 'Order created successfully.');
}
// ---------------------------
// SHOW / POPUP
// ---------------------------
/* ---------------------------
* SHOW / POPUP
* ---------------------------*/
public function show($id)
{
$order = Order::with('items', 'markList')->findOrFail($id);
@@ -109,20 +85,21 @@ class AdminOrderController extends Controller
return view('admin.orders_show', compact('order', 'user'));
}
// public function popup($id)
// {
// $order = Order::with(['items', 'markList'])->findOrFail($id);
// $user = $this->getCustomerFromOrder($order);
public function popup($id)
{
$order = Order::with(['items', 'markList'])->findOrFail($id);
// return view('admin.popup', compact('order', 'user'));
// }
$user = null;
if ($order->markList && $order->markList->customer_id) {
$user = User::where('customer_id', $order->markList->customer_id)->first();
}
// ---------------------------
// ORDER ITEM MANAGEMENT (DB)
// ---------------------------
/**
* Add an item to an existing order
*/
return view('admin.popup', compact('order', 'user'));
}
/* ---------------------------
* ORDER ITEM MANAGEMENT (existing orders)
* ---------------------------*/
public function addItem(Request $request, $orderId)
{
$order = Order::findOrFail($orderId);
@@ -146,34 +123,25 @@ class AdminOrderController extends Controller
OrderItem::create($data);
// recalc totals and save to order
$this->recalcTotals($order);
$this->updateInvoiceFromOrder($order); // <-- NEW
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item added and totals updated.');
}
/**
* Soft-delete an order item and recalc totals
*/
public function deleteItem($id)
{
$item = OrderItem::findOrFail($id);
$order = $item->order;
$item->delete(); // soft delete
$item->delete();
// recalc totals
$this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item deleted and totals updated.');
}
/**
* Restore soft-deleted item and recalc totals
*/
public function restoreItem($id)
{
$item = OrderItem::withTrashed()->findOrFail($id);
@@ -181,17 +149,15 @@ class AdminOrderController extends Controller
$item->restore();
// recalc totals
$this->recalcTotals($order);
$this->updateInvoiceFromOrder($order);
return redirect()->back()->with('success', 'Item restored and totals updated.');
}
// ---------------------------
// ORDER CRUD: update / destroy
// ---------------------------
/* ---------------------------
* ORDER CRUD: update / destroy
* ---------------------------*/
public function update(Request $request, $id)
{
$order = Order::findOrFail($id);
@@ -208,46 +174,35 @@ class AdminOrderController extends Controller
'destination' => $data['destination'] ?? null,
]);
// optionally recalc totals (not necessary unless you change item-level fields here)
$this->recalcTotals($order);
return redirect()->route('admin.orders.show', $order->id)
->with('success', 'Order updated successfully.');
}
/**
* Soft-delete whole order and its items (soft-delete items first then order)
*/
public function destroy($id)
{
$order = Order::findOrFail($id);
// soft-delete items first (so they show up in onlyTrashed for restore)
OrderItem::where('order_id', $order->id)->delete();
// then soft-delete order
$order->delete();
return redirect()->route('admin.orders.index')
->with('success', 'Order deleted successfully.');
}
// ---------------------------
// HELPERS
// ---------------------------
/**
* Recalculate totals for the order from current (non-deleted) items
*/
/* ---------------------------
* HELPERS
* ---------------------------*/
private function recalcTotals(Order $order)
{
// make sure we re-query live items (non-deleted)
$items = $order->items()->get();
$order->update([
'ctn' => (int) $items->sum(fn($i) => (int) ($i->ctn ?? 0)),
'qty' => (int) $items->sum(fn($i) => (int) ($i->qty ?? 0)),
'ttl_qty' => (int) $items->sum(fn($i) => (int) ($i->ttl_qty ?? 0)),
'ttl_amount'=> (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
'ttl_amount' => (float) $items->sum(fn($i) => (float) ($i->ttl_amount ?? 0)),
'cbm' => (float) $items->sum(fn($i) => (float) ($i->cbm ?? 0)),
'ttl_cbm' => (float) $items->sum(fn($i) => (float) ($i->ttl_cbm ?? 0)),
'kg' => (float) $items->sum(fn($i) => (float) ($i->kg ?? 0)),
@@ -255,9 +210,6 @@ class AdminOrderController extends Controller
]);
}
/**
* Generate order id (keeps old format)
*/
private function generateOrderId()
{
$year = date('y');
@@ -269,9 +221,9 @@ class AdminOrderController extends Controller
return $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
}
// ---------------------------
// INVOICE CREATION (optional helper used by store/finish)
// ---------------------------
/* ---------------------------
* INVOICE CREATION HELPERS
* ---------------------------*/
private function createInvoice(Order $order)
{
$invoiceNumber = $this->generateInvoiceNumber();
@@ -302,7 +254,6 @@ class AdminOrderController extends Controller
'pdf_path' => null,
]);
// clone order items into invoice items
foreach ($order->items as $item) {
InvoiceItem::create([
'invoice_id' => $invoice->id,
@@ -350,21 +301,9 @@ class AdminOrderController extends Controller
return null;
}
public function popup($id)
{
// Load order with items + markList
$order = Order::with(['items', 'markList'])->findOrFail($id);
// Fetch user based on markList customer_id (same as show method)
$user = null;
if ($order->markList && $order->markList->customer_id) {
$user = \App\Models\User::where('customer_id', $order->markList->customer_id)->first();
}
return view('admin.popup', compact('order', 'user'));
}
/* ---------------------------
* SEE (detailed)
* ---------------------------*/
public function see($id)
{
$order = Order::with([
@@ -385,7 +324,6 @@ class AdminOrderController extends Controller
}
])->findOrFail($id);
/* ---------------- ORDER DATA ---------------- */
$orderData = [
'order_id' => $order->order_id,
'status' => $order->status,
@@ -402,11 +340,8 @@ class AdminOrderController extends Controller
'items' => $order->items,
];
/* ---------------- SHIPMENTS DATA ---------------- */
$shipmentsData = [];
foreach ($order->shipments as $shipment) {
$shipmentOrders = [];
$totals = [
'ctn' => 0, 'qty' => 0, 'ttl_qty' => 0,
@@ -417,7 +352,6 @@ class AdminOrderController extends Controller
foreach ($shipment->orders as $shipOrder) {
foreach ($shipOrder->items as $item) {
$shipmentOrders[] = [
'order_id' => $shipOrder->order_id,
'origin' => $shipOrder->origin,
@@ -454,7 +388,6 @@ class AdminOrderController extends Controller
];
}
/* ---------------- INVOICE DATA ---------------- */
$invoiceData = null;
if ($order->invoice) {
$invoice = $order->invoice;
@@ -488,89 +421,61 @@ class AdminOrderController extends Controller
));
}
public function resetTemp()
{
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('success', 'Order reset successfully.');
}
/* ---------------------------
* FILTERED LIST + EXPORTS
* ---------------------------*/
public function orderShow()
{
$orders = Order::with([
'markList', // company, customer, origin, destination, date
'shipments', // shipment_id, shipment_date, status
'invoice' // invoice number, dates, amounts, status
])
->latest('id') // show latest orders first
->get();
'markList',
'shipments',
'invoice'
])->latest('id')->get();
return view('admin.orders', compact('orders'));
}
// inside AdminOrderController
private function buildOrdersQueryFromRequest(Request $request)
{
$query = Order::query()
->with(['markList', 'invoice', 'shipments']);
/* ----------------------------------
| SEARCH FILTER
|----------------------------------*/
if ($request->filled('search')) {
$search = trim($request->search);
$query->where(function ($q) use ($search) {
$q->where('orders.order_id', 'like', "%{$search}%")
->orWhereHas('markList', function ($q2) use ($search) {
$q2->where('company_name', 'like', "%{$search}%")
->orWhere('customer_id', 'like', "%{$search}%")
->orWhere('origin', 'like', "%{$search}%")
->orWhere('destination', 'like', "%{$search}%");
})
->orWhereHas('invoice', function ($q3) use ($search) {
$q3->where('invoice_number', 'like', "%{$search}%");
})
->orWhereHas('shipments', function ($q4) use ($search) {
// ✅ VERY IMPORTANT: table name added
$q4->where('shipments.shipment_id', 'like', "%{$search}%");
});
});
}
/* ----------------------------------
| INVOICE STATUS FILTER
|----------------------------------*/
if ($request->filled('status')) {
$query->where(function ($q) use ($request) {
$q->whereHas('invoice', function ($q2) use ($request) {
$q2->where('status', $request->status);
})
->orWhereDoesntHave('invoice');
})->orWhereDoesntHave('invoice');
});
}
/* ----------------------------------
| SHIPMENT STATUS FILTER
|----------------------------------*/
if ($request->filled('shipment')) {
$query->where(function ($q) use ($request) {
$q->whereHas('shipments', function ($q2) use ($request) {
$q2->where('status', $request->shipment);
})
->orWhereDoesntHave('shipments');
})->orWhereDoesntHave('shipments');
});
}
/* ----------------------------------
| DATE RANGE FILTER (🔥 FIXED)
|----------------------------------*/
if ($request->filled('from_date')) {
$query->whereDate('orders.created_at', '>=', $request->from_date);
}
@@ -582,33 +487,9 @@ class AdminOrderController extends Controller
return $query->latest('orders.id');
}
public function downloadPdf(Request $request)
{
// Build same filtered query used for table
// $query = $this->buildOrdersQueryFromRequest($request);
// $orders = $query->get();
// // optional: pass filters to view for header
// $filters = [
// 'search' => $request->search ?? null,
// 'status' => $request->status ?? null,
// 'shipment' => $request->shipment ?? null,
// ];
// $pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters'))
// ->setPaper('a4', 'landscape'); // adjust if needed
// $fileName = 'orders-report'
// . ($filters['status'] ? "-{$filters['status']}" : '')
// . '-' . date('Y-m-d') . '.pdf';
// return $pdf->download($fileName);
$orders = $this->buildOrdersQueryFromRequest($request)->get();
$filters = [
'search' => $request->search,
'status' => $request->status,
@@ -627,97 +508,52 @@ class AdminOrderController extends Controller
public function downloadExcel(Request $request)
{
// pass request to OrdersExport which will build Filtered query internally
// return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx');
return Excel::download(
new OrdersExport($request),
'orders-report-' . now()->format('Y-m-d') . '.xlsx'
);
}
/* --------------------------------------------------
* NEW: Create Order + Invoice directly from popup
* route: admin.orders.temp.add (Create New Order form)
* --------------------------------------------------*/
public function addTempItem(Request $request)
{
// Validate item fields
$item = $request->validate([
'mark_no' => 'required',
'origin' => 'required',
'destination' => 'required',
'description' => 'required|string',
'ctn' => 'nullable|numeric',
'qty' => 'nullable|numeric',
'ttl_qty' => 'nullable|numeric',
'unit' => 'nullable|string',
'price' => 'nullable|numeric',
'ttl_amount' => 'nullable|numeric',
'cbm' => 'nullable|numeric',
'ttl_cbm' => 'nullable|numeric',
'kg' => 'nullable|numeric',
'ttl_kg' => 'nullable|numeric',
'shop_no' => 'nullable|string',
]);
// ❌ Prevent changing mark_no once first item added
if (session()->has('mark_no') && session('mark_no') != $request->mark_no) {
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('error', 'You must finish or clear the current order before changing Mark No.');
}
// Save mark, origin, destination ONLY ONCE
if (!session()->has('mark_no')) {
session([
'mark_no' => $request->mark_no,
'origin' => $request->origin,
'destination' => $request->destination
]);
}
// ❌ DO NOT overwrite these values again
// session(['mark_no' => $request->mark_no]);
// session(['origin' => $request->origin]);
// session(['destination' => $request->destination]);
// Add new sub-item into session
session()->push('temp_order_items', $item);
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('success', 'Item added.');
}
// -------------------------------------------------------------------------
// STEP 3 : FINISH ORDER
// -------------------------------------------------------------------------
public function finishOrder(Request $request)
{
// 1) order-level fields
$request->validate([
'mark_no' => 'required',
'origin' => 'required',
'destination' => 'required',
]);
$items = session('temp_order_items', []);
// 2) multi-row items
$items = $request->validate([
'items' => 'required|array',
'items.*.description' => 'required|string',
'items.*.ctn' => 'nullable|numeric',
'items.*.qty' => 'nullable|numeric',
'items.*.ttl_qty' => 'nullable|numeric',
'items.*.unit' => 'nullable|string',
'items.*.price' => 'nullable|numeric',
'items.*.ttl_amount' => 'nullable|numeric',
'items.*.cbm' => 'nullable|numeric',
'items.*.ttl_cbm' => 'nullable|numeric',
'items.*.kg' => 'nullable|numeric',
'items.*.ttl_kg' => 'nullable|numeric',
'items.*.shop_no' => 'nullable|string',
])['items'];
// रिकामे rows काढा
$items = array_filter($items, function ($row) {
return trim($row['description'] ?? '') !== '';
});
if (empty($items)) {
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('error', 'Add at least one item before finishing.');
return back()->with('error', 'Add at least one item.');
}
// =======================
// GENERATE ORDER ID
// =======================
$year = date('y');
$prefix = "KNT-$year-";
$lastOrder = Order::latest('id')->first();
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
$orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
// =======================
// TOTAL SUMS
// =======================
// 3) totals
$total_ctn = array_sum(array_column($items, 'ctn'));
$total_qty = array_sum(array_column($items, 'qty'));
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
@@ -727,14 +563,15 @@ class AdminOrderController extends Controller
$total_kg = array_sum(array_column($items, 'kg'));
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
// =======================
// CREATE ORDER
// =======================
// 4) order id generate
$orderId = $this->generateOrderId();
// 5) order create
$order = Order::create([
'order_id' => $orderId,
'mark_no' => $request->mark_no,
'origin' => $request->origin,
'destination' => $request->destination,
'destination'=> $request->destination,
'ctn' => $total_ctn,
'qty' => $total_qty,
'ttl_qty' => $total_ttl_qty,
@@ -746,11 +583,11 @@ class AdminOrderController extends Controller
'status' => 'pending',
]);
// SAVE ORDER ITEMS
// 6) order items
foreach ($items as $item) {
OrderItem::create([
'order_id' => $order->id,
'description' => $item['description'],
'description'=> $item['description'],
'ctn' => $item['ctn'],
'qty' => $item['qty'],
'ttl_qty' => $item['ttl_qty'],
@@ -765,56 +602,44 @@ class AdminOrderController extends Controller
]);
}
// =======================
// INVOICE CREATION START
// =======================
// 7) invoice number
$invoiceNumber = $this->generateInvoiceNumber();
// 1. Auto-generate invoice number
$lastInvoice = \App\Models\Invoice::latest()->first();
$nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1;
$invoiceNumber = 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT);
// 2. Fetch customer (using mark list → customer_id)
// 8) customer fetch
$markList = MarkList::where('mark_no', $order->mark_no)->first();
$customer = null;
if ($markList && $markList->customer_id) {
$customer = \App\Models\User::where('customer_id', $markList->customer_id)->first();
$customer = User::where('customer_id', $markList->customer_id)->first();
}
// 3. Create Invoice Record
$invoice = \App\Models\Invoice::create([
// 9) invoice create
$invoice = Invoice::create([
'order_id' => $order->id,
'customer_id' => $customer->id ?? null,
'mark_no' => $order->mark_no,
'invoice_number' => $invoiceNumber,
'invoice_date' => now(),
'due_date' => now()->addDays(10),
'payment_method' => null,
'reference_no' => null,
'status' => 'pending',
'final_amount' => $total_amount,
'gst_percent' => 0,
'gst_amount' => 0,
'final_amount_with_gst' => $total_amount,
// snapshot customer fields
'customer_name' => $customer->customer_name ?? null,
'company_name' => $customer->company_name ?? null,
'customer_email' => $customer->email ?? null,
'customer_mobile' => $customer->mobile_no ?? null,
'customer_address' => $customer->address ?? null,
'pincode' => $customer->pincode ?? null,
'notes' => null,
'pdf_path' => null,
]);
// 4. Clone order items into invoice_items
// 10) invoice items
foreach ($order->items as $item) {
\App\Models\InvoiceItem::create([
InvoiceItem::create([
'invoice_id' => $invoice->id,
'description' => $item->description,
'ctn' => $item->ctn,
@@ -831,24 +656,13 @@ class AdminOrderController extends Controller
]);
}
// 5. TODO: PDF generation (I will add this later)
$invoice->pdf_path = null; // placeholder for now
$invoice->save();
// =======================
// END INVOICE CREATION
// =======================
// CLEAR TEMP DATA
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
return redirect()->route('admin.orders.index')
->with('success', 'Order + Invoice created successfully.');
}
// ---------------------------
// ORDER CRUD: update / destroy
// ---------------------------
/* ---------------------------
* UPDATE ORDER ITEM (existing orders)
* ---------------------------*/
public function updateItem(Request $request, $id)
{
$item = OrderItem::findOrFail($id);
@@ -870,31 +684,27 @@ class AdminOrderController extends Controller
]);
$this->recalcTotals($order);
$this->updateInvoiceFromOrder($order); // <-- NEW
$this->updateInvoiceFromOrder($order);
return back()->with('success', 'Item updated successfully');
}
private function updateInvoiceFromOrder(Order $order)
{
$invoice = Invoice::where('order_id', $order->id)->first();
if (!$invoice) {
return; // No invoice exists (should not happen normally)
return;
}
// Update invoice totals
$invoice->final_amount = $order->ttl_amount;
$invoice->gst_percent = 0;
$invoice->gst_amount = 0;
$invoice->final_amount_with_gst = $order->ttl_amount;
$invoice->save();
// Delete old invoice items
InvoiceItem::where('invoice_id', $invoice->id)->delete();
// Re-create invoice items from updated order items
foreach ($order->items as $item) {
InvoiceItem::create([
'invoice_id' => $invoice->id,
@@ -913,5 +723,4 @@ class AdminOrderController extends Controller
]);
}
}
}

View File

@@ -261,7 +261,6 @@ body, .container-fluid {
font-family: 'Inter', sans-serif;
border-bottom: 2px solid #e9ecef;
}
.table thead th:first-child { border-radius: 9px 0 0 0;}
.table thead th:last-child { border-radius: 0 9px 0 0;}
@@ -466,45 +465,47 @@ body, .container-fluid {
min-width: 2000px;
border-radius: 10px;
}
/* ===== CREATE ORDER MODAL STYLES ===== */
.create-order-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
width: 100vw; /* full screen */
height: 100vh; /* full screen */
background: #f8fafc; /* backdrop काढून direct page सारखा bg */
display: none;
justify-content: center;
align-items: center;
justify-content: flex-start; /* top पासून सुरू कर */
align-items: stretch; /* full height */
z-index: 9999;
overflow-y: auto; /* content scroll */
}
/* JS madhun modal.classList.add('show') already aahe */
.create-order-modal.show {
display: flex;
display: block; /* flex ऐवजी block => full page section सारखा */
}
/* Card ला full-width/height सारखं कर */
.create-order-modal .modal-card {
background: #fff;
border-radius: 16px;
box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
width: 95%;
max-width: 900px;
max-height: 90vh;
overflow-y: auto;
border-radius: 0; /* corner radius काढला => normal page feel */
box-shadow: none; /* popup सारखा shadow काढला */
width: 100%;
max-width: 100%;
min-height: 100vh;
max-height: none;
overflow: visible;
}
.create-order-modal .modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px 25px;
border-radius: 16px 16px 0 0;
border-radius: 0;
display: flex;
justify-content: space-between;
align-items: center;
}
.create-order-modal .modal-title {
font-size: 1.4rem;
font-weight: 700;
@@ -630,6 +631,13 @@ body, .container-fluid {
font-family: 'Inter', sans-serif;
}
/* items table inputs */
.items-input {
width: 100%;
padding: 4px 6px;
font-size: 13px;
}
/* ===== ORDER DETAILS MODAL STYLES ===== */
.modal.fade .modal-dialog {
transition: transform 0.3s ease-out;
@@ -869,6 +877,27 @@ body, .container-fluid {
.pagination-controls {
justify-content: center;
}
.create-order-modal .modal-card {
width: 98%;
margin: 10px;
}
.create-order-modal .modal-body {
padding: 15px;
}
.create-order-modal .modal-header {
padding: 15px 20px;
}
.modal-body {
padding: 20px 15px;
}
.modal-header {
padding: 10px 15px;
}
}
/* Mobile Landscape (768px and below) */
@@ -1131,7 +1160,6 @@ body, .container-fluid {
break-inside: avoid;
}
}
</style>
<div class="container-fluid py-3">
@@ -1169,7 +1197,6 @@ body, .container-fluid {
@endcan
</div>
<div class="order-mgmt-main">
<!-- RECENT ORDERS TABLE -->
<div class="card shadow-sm">
@@ -1348,22 +1375,35 @@ body, .container-fluid {
{{-- ITEM INPUTS --}}
<h6 class="text-primary">Add Item</h6>
<div class="row g-3">
<div class="col-md-4">
<label class="form-label">Description</label>
<input type="text" class="form-control" name="description" id="itemDescription" required placeholder="Enter item description">
{{-- NEW ITEMS TABLE (INSTEAD OF SINGLE-ROW INPUTS) --}}
<div class="table-wrapper mb-3">
<table class="table table-bordered table-sm align-middle text-center" id="itemsTable">
<thead class="table-light">
<tr>
<th>#</th>
<th>Description</th>
<th>CTN</th>
<th>QTY</th>
<th>TTL/QTY</th>
<th>Unit</th>
<th>Price</th>
<th>TTL Amount</th>
<th>CBM</th>
<th>TTL CBM</th>
<th>KG</th>
<th>TTL KG</th>
<th>Shop No</th>
<th>Remove</th>
</tr>
</thead>
<tbody id="itemsTableBody">
{{-- JS will create default 2 blank rows --}}
</tbody>
</table>
</div>
<div class="col-md-2"><label class="form-label">CTN</label><input type="number" name="ctn" id="itemCtn" class="form-control" placeholder="CTN"></div>
<div class="col-md-2"><label class="form-label">QTY</label><input type="number" name="qty" id="itemQty" class="form-control" placeholder="QTY"></div>
<div class="col-md-2"><label class="form-label">TTL/QTY</label><input type="number" name="ttl_qty" id="itemTtlQty" class="form-control" placeholder="TTL/QTY"></div>
<div class="col-md-2"><label class="form-label">Unit</label><input type="text" name="unit" id="itemUnit" class="form-control" placeholder="Unit"></div>
<div class="col-md-2"><label class="form-label">Price</label><input type="number" step="0.01" name="price" id="itemPrice" class="form-control" placeholder="Price"></div>
<div class="col-md-2"><label class="form-label">TTL Amount</label><input type="number" step="0.01" name="ttl_amount" id="itemTtlAmount" class="form-control" placeholder="TTL Amount"></div>
<div class="col-md-2"><label class="form-label">CBM</label><input type="number" step="0.001" name="cbm" id="itemCbm" class="form-control" placeholder="CBM"></div>
<div class="col-md-2"><label class="form-label">TTL CBM</label><input type="number" step="0.001" name="ttl_cbm" id="itemTtlCbm" class="form-control" placeholder="TTL CBM"></div>
<div class="col-md-2"><label class="form-label">KG</label><input type="number" step="0.001" name="kg" id="itemKg" class="form-control" placeholder="KG"></div>
<div class="col-md-2"><label class="form-label">TTL KG</label><input type="number" step="0.001" name="ttl_kg" id="itemTtlKg" class="form-control" placeholder="TTL KG"></div>
<div class="col-md-3"><label class="form-label">Shop No</label><input type="text" name="shop_no" id="itemShopNo" class="form-control" placeholder="Shop No"></div>
<div class="row g-3">
<div class="col-md-12 text-end mt-3">
<button type="button" class="btn btn-secondary clear-form-btn" id="clearForm">
<i class="bi bi-arrow-clockwise"></i> Clear Form
@@ -1485,7 +1525,60 @@ document.addEventListener('DOMContentLoaded', function() {
const ordersPerPage = 10;
let allOrders = @json($orders->values());
// Initialize pagination
// ------- ITEMS TABLE LOGIC (NEW) -------
const itemsTableBody = document.getElementById('itemsTableBody');
function addRow(index) {
const tr = document.createElement('tr');
tr.innerHTML = `
<td class="align-middle fw-bold">${index + 1}</td>
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][description]" data-field="description"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ctn]" data-field="ctn"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][qty]" data-field="qty"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_qty]" data-field="ttl_qty"></td>
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][unit]" data-field="unit"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][price]" data-field="price" step="0.01"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_amount]" data-field="ttl_amount" step="0.01"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][cbm]" data-field="cbm" step="0.001"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_cbm]" data-field="ttl_cbm" step="0.001"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][kg]" data-field="kg" step="0.001"></td>
<td><input type="number" class="form-control form-control-sm items-input" name="items[${index}][ttl_kg]" data-field="ttl_kg" step="0.001"></td>
<td><input type="text" class="form-control form-control-sm items-input" name="items[${index}][shop_no]" data-field="shop_no"></td>
<td><button type="button" class="btn btn-sm btn-danger remove-row-btn">&times;</button></td>
`;
itemsTableBody.appendChild(tr);
}
function generateDefaultRows() {
itemsTableBody.innerHTML = '';
addRow(0);
addRow(1);
focusFirstInput();
}
function reindexRows() {
const rows = itemsTableBody.querySelectorAll('tr');
rows.forEach((tr, idx) => {
tr.querySelector('td:first-child').textContent = idx + 1;
tr.querySelectorAll('input').forEach(input => {
const field = input.getAttribute('data-field');
input.name = `items[${idx}][${field}]`;
});
});
}
function rowHasData(row) {
const inputs = row.querySelectorAll('input');
return Array.from(inputs).some(inp => inp.value.trim() !== '');
}
function focusFirstInput() {
const first = itemsTableBody.querySelector('tr:first-child input');
if (first) first.focus();
}
// ------- EXISTING PAGINATION INITIALIZE -------
initializePagination();
// Reset temp data function
@@ -1504,7 +1597,7 @@ document.addEventListener('DOMContentLoaded', function() {
modal.classList.add('show');
document.body.style.overflow = 'hidden';
document.querySelector('.alert-success')?.remove();
clearForm();
generateDefaultRows();
};
const closeModal = () => {
@@ -1518,11 +1611,9 @@ document.addEventListener('DOMContentLoaded', function() {
@endif
};
// Clear form function
// Clear form -> clear items table
const clearForm = () => {
['itemDescription','itemCtn','itemQty','itemTtlQty','itemUnit','itemPrice','itemTtlAmount','itemCbm','itemTtlCbm','itemKg','itemTtlKg','itemShopNo']
.forEach(id => document.getElementById(id).value = '');
document.getElementById('itemDescription').focus();
generateDefaultRows();
};
// Event listeners
@@ -1533,7 +1624,7 @@ document.addEventListener('DOMContentLoaded', function() {
modal.addEventListener('click', (e) => e.target === modal && closeModal());
document.addEventListener('keydown', (e) => e.key === 'Escape' && modal.classList.contains('show') && closeModal());
// Mark No functionality
// Mark No functionality (unchanged)
const markNoSelect = document.getElementById('markNoSelect');
if (markNoSelect) {
markNoSelect.addEventListener('change', function() {
@@ -1553,7 +1644,7 @@ document.addEventListener('DOMContentLoaded', function() {
@if(session('temp_order_items') && count(session('temp_order_items')) > 0)
modal.classList.add('show');
document.body.style.overflow = 'hidden';
clearForm();
generateDefaultRows();
@endif
// Reset confirmation
@@ -1564,16 +1655,16 @@ document.addEventListener('DOMContentLoaded', function() {
}
}));
// Order details modal functionality
// Order details modal functionality (unchanged)
document.querySelectorAll('.open-order-modal').forEach(button => {
button.addEventListener('click', function() {
let id = this.dataset.id;
let modal = new bootstrap.Modal(document.getElementById('orderDetailsModal'));
let modalInstance = new bootstrap.Modal(document.getElementById('orderDetailsModal'));
document.getElementById('orderDetailsBody').innerHTML =
"<p class='text-center text-muted'>Loading...</p>";
modal.show();
modalInstance.show();
fetch(`/admin/orders/view/${id}`)
.then(response => response.text())
@@ -1587,12 +1678,11 @@ document.addEventListener('DOMContentLoaded', function() {
});
});
/* ---------- Pagination Functions ---------- */
/* ---------- Pagination Functions (unchanged) ---------- */
function initializePagination() {
renderOrdersTable(allOrders);
updatePaginationControls();
// Bind pagination buttons
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
}
@@ -1624,21 +1714,17 @@ document.addEventListener('DOMContentLoaded', function() {
prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
// Update page info text
const startIndex = (currentPage - 1) * ordersPerPage + 1;
const endIndex = Math.min(currentPage * ordersPerPage, allOrders.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${allOrders.length} entries`;
// Generate page numbers
paginationPages.innerHTML = '';
if (totalPages <= 7) {
// Show all pages
for (let i = 1; i <= totalPages; i++) {
addPageButton(i, paginationPages);
}
} else {
// Show first page, current page range, and last page
addPageButton(1, paginationPages);
if (currentPage > 3) {
@@ -1684,7 +1770,6 @@ document.addEventListener('DOMContentLoaded', function() {
return;
}
// Calculate pagination
const startIndex = (currentPage - 1) * ordersPerPage;
const endIndex = startIndex + ordersPerPage;
const paginatedOrders = orders.slice(startIndex, endIndex);
@@ -1729,17 +1814,16 @@ document.addEventListener('DOMContentLoaded', function() {
`;
tbody.appendChild(tr);
// Re-bind order details modal for newly rendered rows
const orderLink = tr.querySelector('.open-order-modal');
if (orderLink) {
orderLink.addEventListener('click', function() {
let id = this.dataset.id;
let modal = new bootstrap.Modal(document.getElementById('orderDetailsModal'));
let modalInstance = new bootstrap.Modal(document.getElementById('orderDetailsModal'));
document.getElementById('orderDetailsBody').innerHTML =
"<p class='text-center text-muted'>Loading...</p>";
modal.show();
modalInstance.show();
fetch(`/admin/orders/view/${id}`)
.then(response => response.text())
@@ -1754,19 +1838,65 @@ document.addEventListener('DOMContentLoaded', function() {
}
});
}
// Enter key behavior for items/table
itemsTableBody.addEventListener('keydown', function(e) {
if (e.key !== 'Enter' || e.target.tagName !== 'INPUT') return;
e.preventDefault();
const currentInput = e.target;
const currentRow = currentInput.closest('tr');
const rows = Array.from(itemsTableBody.querySelectorAll('tr'));
const currentRowIndex = rows.indexOf(currentRow);
const inputs = Array.from(currentRow.querySelectorAll('input'));
const currentInputIndex = inputs.indexOf(currentInput);
const isLastRow = currentRowIndex === rows.length - 1;
const hasData = rowHasData(currentRow);
if (currentInputIndex < inputs.length - 1) {
inputs[currentInputIndex + 1].focus();
return;
}
if (!isLastRow) {
const nextRow = rows[currentRowIndex + 1];
const firstInputNextRow = nextRow.querySelector('input');
if (firstInputNextRow) firstInputNextRow.focus();
return;
}
if (isLastRow && hasData) {
const newIndex = rows.length;
addRow(newIndex);
reindexRows();
const newRow = itemsTableBody.querySelector('tr:last-child');
const firstInput = newRow.querySelector('input');
if (firstInput) firstInput.focus();
}
});
// Remove row
itemsTableBody.addEventListener('click', function(e) {
if (!e.target.classList.contains('remove-row-btn')) return;
const rows = itemsTableBody.querySelectorAll('tr');
if (rows.length <= 1) {
alert('At least one row must exist.');
return;
}
e.target.closest('tr').remove();
reindexRows();
});
});
</script>
<script>
document.addEventListener("hidden.bs.modal", function () {
// Remove Bootstrap backdrops
document.querySelectorAll(".modal-backdrop").forEach(el => el.remove());
// Fix page scroll locking
document.body.classList.remove("modal-open");
document.body.style.overflow = "";
});
</script>
@endsection

View File

@@ -133,6 +133,10 @@ Route::prefix('admin')
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
->name('admin.orders.popup');
Route::post('/admin/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
->name('admin.orders.temp.add');
// Route::get('/orders/{id}', [AdminOrderController::class, 'view'])
// ->name('admin.orders.view');
Route::get('/orders/{order:order_id}/see', [AdminOrderController::class, 'see'])