Shipment dashboard changes

This commit is contained in:
divya abdar
2025-12-03 15:36:04 +05:30
parent 3845972c5c
commit 340c2b2132
15 changed files with 2706 additions and 756 deletions

View File

@@ -164,46 +164,30 @@ class AdminOrderController extends Controller
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);
$order = Order::findOrFail($item->order_id);
$item->restore();
// recalc totals
$this->recalcTotals($order);
return redirect()->back()->with('success', 'Item restored and totals updated.');
}
// ---------------------------
// ORDER CRUD: update / destroy
// ---------------------------
public function update(Request $request, $id)
public function updateItem(Request $request, $id)
{
$order = Order::findOrFail($id);
$item = OrderItem::findOrFail($id);
$data = $request->validate([
'mark_no' => 'required|string',
'origin' => 'nullable|string',
'destination' => 'nullable|string',
$item->update([
'description' => $request->description,
'ctn' => $request->ctn,
'qty' => $request->qty,
'ttl_qty' => $request->ttl_qty,
'unit' => $request->unit,
'price' => $request->price,
'ttl_amount' => $request->ttl_amount,
'cbm' => $request->cbm,
'ttl_cbm' => $request->ttl_cbm,
'kg' => $request->kg,
'ttl_kg' => $request->ttl_kg,
'shop_no' => $request->shop_no,
]);
$order->update([
'mark_no' => $data['mark_no'],
'origin' => $data['origin'] ?? null,
'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.');
return back()->with('success', 'Item updated successfully!');
}
/**
@@ -341,6 +325,10 @@ class AdminOrderController extends Controller
return null;
}
// -------------------------------------------------------------------------
// Popup function
// -------------------------------------------------------------------------
public function popup($id)
{
// Load order with items + markList
@@ -354,8 +342,27 @@ class AdminOrderController extends Controller
return view('admin.popup', compact('order', 'user'));
}
// -------------------------------------------------------------------------
// Restore Item
// -------------------------------------------------------------------------
public function restoreItem($id)
{
$item = OrderItem::onlyTrashed()->findOrFail($id);
$item->restore();
if (request()->ajax()) {
return response()->json([
'status' => 'ok',
'message' => 'Item restored.'
]);
}
return back()->belongsTo(Order::class)->with('success', 'Item restored successfully.');
}
// -------------------------------------------------------------------------
// Reset temp
// -------------------------------------------------------------------------
public function resetTemp()
@@ -366,6 +373,11 @@ class AdminOrderController extends Controller
->with('success', 'Order reset successfully.');
}
// -------------------------------------------------------------------------
// ORDER SHOW PAGE
// -------------------------------------------------------------------------
public function orderShow()
{
$orders = Order::with([
@@ -379,48 +391,55 @@ class AdminOrderController extends Controller
return view('admin.orders', compact('orders'));
}
public function downloadPdf(Request $request)
{
$query = Order::with(['markList', 'invoice', 'shipments']);
//====================================================
// Download Pdf
//=================================================
public function downloadPdf(Request $request)
{
$query = Order::with(['markList', 'invoice', 'shipments']);
// Apply filters
if ($request->has('search') && $request->search) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('order_id', 'like', "%{$search}%")
->orWhereHas('markList', function($q) use ($search) {
$q->where('company_name', 'like', "%{$search}%");
})
->orWhereHas('invoice', function($q) use ($search) {
$q->where('invoice_number', 'like', "%{$search}%");
});
});
}
// Apply filters
if ($request->has('search') && $request->search) {
$search = $request->search;
$query->where(function($q) use ($search) {
$q->where('order_id', 'like', "%{$search}%")
->orWhereHas('markList', function($q) use ($search) {
$q->where('company_name', 'like', "%{$search}%");
})
->orWhereHas('invoice', function($q) use ($search) {
$q->where('invoice_number', 'like', "%{$search}%");
});
});
}
if ($request->has('status') && $request->status) {
$query->whereHas('invoice', function($q) use ($request) {
$q->where('status', $request->status);
});
}
if ($request->has('status') && $request->status) {
$query->whereHas('invoice', function($q) use ($request) {
$q->where('status', $request->status);
});
}
if ($request->has('shipment') && $request->shipment) {
$query->whereHas('shipments', function($q) use ($request) {
$q->where('status', $request->shipment);
});
}
if ($request->has('shipment') && $request->shipment) {
$query->whereHas('shipments', function($q) use ($request) {
$q->where('status', $request->shipment);
});
}
$orders = $query->get();
$orders = $query->get();
$pdf = PDF::loadView('admin.orders.pdf', compact('orders'));
return $pdf->download('orders-report-' . date('Y-m-d') . '.pdf');
}
$pdf = PDF::loadView('admin.orders.pdf', compact('orders'));
return $pdf->download('orders-report-' . date('Y-m-d') . '.pdf');
}
public function downloadExcel(Request $request)
{
return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx');
}
public function downloadExcel(Request $request)
{
return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx');
}
public function addTempItem(Request $request)
//====================================================
// add Temp Item
//=================================================
public function addTempItem(Request $request)
{
// Validate item fields
$item = $request->validate([
@@ -468,81 +487,162 @@ public function addTempItem(Request $request)
->with('success', 'Item added.');
}
public function finishOrder(Request $request)
{
$request->validate([
'mark_no' => 'required',
'origin' => 'required',
'destination' => 'required',
]);
$items = session('temp_order_items', []);
public function finishOrder(Request $request)
{
$request->validate([
'mark_no' => 'required',
'origin' => 'required',
'destination' => 'required',
]);
if (empty($items)) {
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('error', 'Add at least one item before finishing.');
}
$items = session('temp_order_items', []);
// 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
$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'));
$total_amount = array_sum(array_column($items, 'ttl_amount'));
$total_cbm = array_sum(array_column($items, 'cbm'));
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
$total_kg = array_sum(array_column($items, 'kg'));
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
// CREATE ORDER
$order = Order::create([
'order_id' => $orderId,
'mark_no' => $request->mark_no,
'origin' => $request->origin,
'destination' => $request->destination,
'ctn' => $total_ctn,
'qty' => $total_qty,
'ttl_qty' => $total_ttl_qty,
'ttl_amount' => $total_amount,
'cbm' => $total_cbm,
'ttl_cbm' => $total_ttl_cbm,
'kg' => $total_kg,
'ttl_kg' => $total_ttl_kg,
'status' => 'pending',
]);
// SAVE ALL SUB-ITEMS
foreach ($items as $item) {
OrderItem::create([
'order_id' => $order->id,
'description' => $item['description'],
'ctn' => $item['ctn'],
'qty' => $item['qty'],
'ttl_qty' => $item['ttl_qty'],
'unit' => $item['unit'],
'price' => $item['price'],
'ttl_amount' => $item['ttl_amount'],
'cbm' => $item['cbm'],
'ttl_cbm' => $item['ttl_cbm'],
'kg' => $item['kg'],
'ttl_kg' => $item['ttl_kg'],
'shop_no' => $item['shop_no'],
]);
}
// CLEAR TEMP DATA
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
return redirect()->route('admin.orders.index')
->with('success', 'Order saved successfully.');
if (empty($items)) {
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('error', 'Add at least one item before finishing.');
}
// =======================
// 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
// =======================
$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'));
$total_amount = array_sum(array_column($items, 'ttl_amount'));
$total_cbm = array_sum(array_column($items, 'cbm'));
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
$total_kg = array_sum(array_column($items, 'kg'));
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
// =======================
// CREATE ORDER
// =======================
$order = Order::create([
'order_id' => $orderId,
'mark_no' => $request->mark_no,
'origin' => $request->origin,
'destination' => $request->destination,
'ctn' => $total_ctn,
'qty' => $total_qty,
'ttl_qty' => $total_ttl_qty,
'ttl_amount' => $total_amount,
'cbm' => $total_cbm,
'ttl_cbm' => $total_ttl_cbm,
'kg' => $total_kg,
'ttl_kg' => $total_ttl_kg,
'status' => 'pending',
]);
// SAVE ORDER ITEMS
foreach ($items as $item) {
OrderItem::create([
'order_id' => $order->id,
'description' => $item['description'],
'ctn' => $item['ctn'],
'qty' => $item['qty'],
'ttl_qty' => $item['ttl_qty'],
'unit' => $item['unit'],
'price' => $item['price'],
'ttl_amount' => $item['ttl_amount'],
'cbm' => $item['cbm'],
'ttl_cbm' => $item['ttl_cbm'],
'kg' => $item['kg'],
'ttl_kg' => $item['ttl_kg'],
'shop_no' => $item['shop_no'],
]);
}
// =======================
// INVOICE CREATION START
// =======================
// 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)
$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();
}
// 3. Create Invoice Record
$invoice = \App\Models\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,
]);
// 4. Clone order items into invoice_items
foreach ($order->items as $item) {
\App\Models\InvoiceItem::create([
'invoice_id' => $invoice->id,
'description' => $item->description,
'ctn' => $item->ctn,
'qty' => $item->qty,
'ttl_qty' => $item->ttl_qty,
'unit' => $item->unit,
'price' => $item->price,
'ttl_amount' => $item->ttl_amount,
'cbm' => $item->cbm,
'ttl_cbm' => $item->ttl_cbm,
'kg' => $item->kg,
'ttl_kg' => $item->ttl_kg,
'shop_no' => $item->shop_no,
]);
}
// 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.');
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Models\Shipment;
@@ -106,6 +107,9 @@ class ShipmentController extends Controller
'order_qty' => $order->qty,
'order_ttl_qty' => $order->ttl_qty,
'order_ttl_amount' => $order->ttl_amount,
'order_cbm' => $order->cbm,
'order_ttl_cbm' => $order->ttl_cbm,
'order_kg' => $order->kg,
'order_ttl_kg' => $order->ttl_kg,
]);
}
@@ -113,24 +117,112 @@ class ShipmentController extends Controller
return redirect()->back()->with('success', "Shipment $newShipmentId created successfully!");
}
/**
* Show shipment details (for modal popup)
*/
public function edit($id)
{
$shipment = Shipment::with('orders')->findOrFail($id);
return view('admin.shipments.show', [
'shipment' => $shipment,
'orders' => $shipment->orders,
'isViewMode' => false // This is the edit mode
]);
}
public function show($id)
{
$shipment = Shipment::findOrFail($id);
$mode = request()->get('mode', 'view');
// Load full order data from orders table
$orders = Order::whereIn('id',
ShipmentItem::where('shipment_id', $id)->pluck('order_id')
)->get();
$shipment = Shipment::with(['items.order'])->findOrFail($id);
return response()->json([
// Get orders from shipment items
$orders = collect();
foreach ($shipment->items as $item) {
if ($item->order) {
$orders->push($item->order);
}
}
// Get orders not assigned to any shipment (available orders)
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
$availableOrders = Order::whereNotIn('id', $usedOrderIds)->get();
return view('admin.view-shipment', [
'shipment' => $shipment,
'orders' => $orders
'orders' => $orders,
'mode' => $mode,
'availableOrders' => $availableOrders
]);
}
public function addOrders(Request $request, $id)
{
$request->validate([
'order_ids' => 'required|array|min:1'
]);
$shipment = Shipment::findOrFail($id);
$orderIds = $request->order_ids;
DB::beginTransaction();
try {
$orders = Order::whereIn('id', $orderIds)->get();
$addedOrders = [];
foreach ($orders as $order) {
// Prevent duplicates
if (ShipmentItem::where('shipment_id', $shipment->id)->where('order_id', $order->id)->exists()) {
continue;
}
ShipmentItem::create([
'shipment_id' => $shipment->id,
'order_id' => $order->id,
'order_ctn' => $order->ctn,
'order_qty' => $order->qty,
'order_ttl_qty' => $order->ttl_qty,
'order_ttl_amount' => $order->ttl_amount,
'order_cbm' => $order->cbm,
'order_ttl_cbm' => $order->ttl_cbm,
'order_kg' => $order->kg,
'order_ttl_kg' => $order->ttl_kg,
]);
$addedOrders[] = $order;
}
// Recalculate totals
$this->recalculateShipmentTotals($shipment->id);
DB::commit();
$shipment->refresh();
$shipment->load('items.order');
// Get updated orders list
$updatedOrders = collect();
foreach ($shipment->items as $item) {
if ($item->order) {
$updatedOrders->push($item->order);
}
}
return response()->json([
'success' => true,
'message' => 'Orders added to shipment successfully.',
'shipment' => $shipment,
'orders' => $addedOrders
]);
} catch (\Exception $e) {
DB::rollBack();
return response()->json([
'success' => false,
'message' => 'Failed to add orders: ' . $e->getMessage()
], 500);
}
}
/**
* Update Shipment status from action button
*/
@@ -209,4 +301,77 @@ class ShipmentController extends Controller
return redirect()->route('admin.shipments')
->with('success', 'Shipment deleted successfully.');
}
public function removeOrder(Request $request)
{
$request->validate([
'shipment_id' => 'required|exists:shipments,id',
'order_id' => 'required|exists:orders,id'
]);
$shipmentId = $request->shipment_id;
$orderId = $request->order_id;
// Get order data before deletion
$order = Order::findOrFail($orderId);
// Delete pivot entry
ShipmentItem::where('shipment_id', $shipmentId)
->where('order_id', $orderId)
->delete();
// Recalculate totals
$shipment = $this->recalculateShipmentTotals($shipmentId);
if ($request->ajax() || $request->wantsJson()) {
return response()->json([
'success' => true,
'message' => 'Order removed successfully.',
'order' => $order,
'shipment' => $shipment
]);
}
return back()->with('success', 'Order removed successfully.');
}
private function recalculateShipmentTotals($shipmentId)
{
$shipment = Shipment::with('items')->findOrFail($shipmentId);
// Use the shipment items to calculate totals
$shipment->total_ctn = $shipment->items->sum('order_ctn');
$shipment->total_qty = $shipment->items->sum('order_qty');
$shipment->total_ttl_qty = $shipment->items->sum('order_ttl_qty');
$shipment->total_amount = $shipment->items->sum('order_ttl_amount');
$shipment->total_cbm = $shipment->items->sum('order_cbm');
$shipment->total_ttl_cbm = $shipment->items->sum('order_ttl_cbm');
$shipment->total_kg = $shipment->items->sum('order_kg');
$shipment->total_ttl_kg = $shipment->items->sum('order_ttl_kg');
$shipment->save();
$shipment->refresh(); // Refresh to get updated data
return $shipment;
}
// Helper method to get available orders for a shipment
public function getAvailableOrders($shipmentId)
{
$shipment = Shipment::findOrFail($shipmentId);
// Get all used order IDs
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
// Remove orders that are already in this shipment
$shipmentOrderIds = $shipment->items->pluck('order_id')->toArray();
$availableOrderIds = array_diff($usedOrderIds, $shipmentOrderIds);
$availableOrders = Order::whereNotIn('id', $availableOrderIds)->get();
return response()->json([
'success' => true,
'availableOrders' => $availableOrders
]);
}
}

View File

@@ -11,12 +11,27 @@ class ShipmentItem extends Model
protected $fillable = [
'shipment_id',
'order_id',
'order_ctn',
'order_qty',
'order_ttl_qty',
'order_ttl_amount',
'order_ttl_kg',
'order_id',
// OLD fields (keep them if old data exists)
'order_ctn',
'order_qty',
'order_ttl_qty',
'order_ttl_amount',
'order_ttl_kg',
// NEW fields (added for correct shipments)
'ctn',
'qty',
'ttl_qty',
'cbm',
'ttl_cbm',
'kg',
'ttl_kg',
'ttl_amount',
];
// ---------------------------
@@ -36,11 +51,11 @@ class ShipmentItem extends Model
// Helper: return order data with fallback to snapshot
public function getDisplayQty()
{
return $this->order->qty ?? $this->order_qty;
return $this->qty;
}
public function getDisplayAmount()
{
return $this->order->ttl_amount ?? $this->order_ttl_amount;
return $this->ttl_amount;
}
}

View File

@@ -7,8 +7,10 @@
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"maatwebsite/excel": "^1.1",
"mpdf/mpdf": "^8.2",
"php-open-source-saver/jwt-auth": "2.8"
},

488
composer.lock generated
View File

@@ -4,8 +4,85 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "fa680e1e8b3550d710849bbd820e3626",
"content-hash": "6a2ec43d7e96d38cacfc2f9e1ae5cb9e",
"packages": [
{
"name": "barryvdh/laravel-dompdf",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-dompdf.git",
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
"reference": "8e71b99fc53bb8eb77f316c3c452dd74ab7cb25d",
"shasum": ""
},
"require": {
"dompdf/dompdf": "^3.0",
"illuminate/support": "^9|^10|^11|^12",
"php": "^8.1"
},
"require-dev": {
"larastan/larastan": "^2.7|^3.0",
"orchestra/testbench": "^7|^8|^9|^10",
"phpro/grumphp": "^2.5",
"squizlabs/php_codesniffer": "^3.5"
},
"type": "library",
"extra": {
"laravel": {
"aliases": {
"PDF": "Barryvdh\\DomPDF\\Facade\\Pdf",
"Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf"
},
"providers": [
"Barryvdh\\DomPDF\\ServiceProvider"
]
},
"branch-alias": {
"dev-master": "3.0-dev"
}
},
"autoload": {
"psr-4": {
"Barryvdh\\DomPDF\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "A DOMPDF Wrapper for Laravel",
"keywords": [
"dompdf",
"laravel",
"pdf"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-dompdf/issues",
"source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.1"
},
"funding": [
{
"url": "https://fruitcake.nl",
"type": "custom"
},
{
"url": "https://github.com/barryvdh",
"type": "github"
}
],
"time": "2025-02-13T15:07:54+00:00"
},
{
"name": "brick/math",
"version": "0.14.0",
@@ -377,6 +454,161 @@
],
"time": "2024-02-05T11:56:58+00:00"
},
{
"name": "dompdf/dompdf",
"version": "v3.1.4",
"source": {
"type": "git",
"url": "https://github.com/dompdf/dompdf.git",
"reference": "db712c90c5b9868df3600e64e68da62e78a34623"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/dompdf/zipball/db712c90c5b9868df3600e64e68da62e78a34623",
"reference": "db712c90c5b9868df3600e64e68da62e78a34623",
"shasum": ""
},
"require": {
"dompdf/php-font-lib": "^1.0.0",
"dompdf/php-svg-lib": "^1.0.0",
"ext-dom": "*",
"ext-mbstring": "*",
"masterminds/html5": "^2.0",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"ext-gd": "*",
"ext-json": "*",
"ext-zip": "*",
"mockery/mockery": "^1.3",
"phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11",
"squizlabs/php_codesniffer": "^3.5",
"symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0"
},
"suggest": {
"ext-gd": "Needed to process images",
"ext-gmagick": "Improves image processing performance",
"ext-imagick": "Improves image processing performance",
"ext-zlib": "Needed for pdf stream compression"
},
"type": "library",
"autoload": {
"psr-4": {
"Dompdf\\": "src/"
},
"classmap": [
"lib/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1"
],
"authors": [
{
"name": "The Dompdf Community",
"homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md"
}
],
"description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
"homepage": "https://github.com/dompdf/dompdf",
"support": {
"issues": "https://github.com/dompdf/dompdf/issues",
"source": "https://github.com/dompdf/dompdf/tree/v3.1.4"
},
"time": "2025-10-29T12:43:30+00:00"
},
{
"name": "dompdf/php-font-lib",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-font-lib.git",
"reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
"reference": "6137b7d4232b7f16c882c75e4ca3991dbcf6fe2d",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6"
},
"type": "library",
"autoload": {
"psr-4": {
"FontLib\\": "src/FontLib"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-2.1-or-later"
],
"authors": [
{
"name": "The FontLib Community",
"homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse, export and make subsets of different types of font files.",
"homepage": "https://github.com/dompdf/php-font-lib",
"support": {
"issues": "https://github.com/dompdf/php-font-lib/issues",
"source": "https://github.com/dompdf/php-font-lib/tree/1.0.1"
},
"time": "2024-12-02T14:37:59+00:00"
},
{
"name": "dompdf/php-svg-lib",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/dompdf/php-svg-lib.git",
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
"reference": "eb045e518185298eb6ff8d80d0d0c6b17aecd9af",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": "^7.1 || ^8.0",
"sabberworm/php-css-parser": "^8.4"
},
"require-dev": {
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5"
},
"type": "library",
"autoload": {
"psr-4": {
"Svg\\": "src/Svg"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-or-later"
],
"authors": [
{
"name": "The SvgLib Community",
"homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md"
}
],
"description": "A library to read, parse and export to PDF SVG files.",
"homepage": "https://github.com/dompdf/php-svg-lib",
"support": {
"issues": "https://github.com/dompdf/php-svg-lib/issues",
"source": "https://github.com/dompdf/php-svg-lib/tree/1.0.0"
},
"time": "2024-04-29T13:26:35+00:00"
},
{
"name": "dragonmantank/cron-expression",
"version": "v3.6.0",
@@ -2081,6 +2313,132 @@
],
"time": "2024-12-08T08:18:47+00:00"
},
{
"name": "maatwebsite/excel",
"version": "v1.1.5",
"source": {
"type": "git",
"url": "https://github.com/Maatwebsite/Laravel-Excel.git",
"reference": "0c67aba8387726458d42461eae91a3415593bbc4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Maatwebsite/Laravel-Excel/zipball/0c67aba8387726458d42461eae91a3415593bbc4",
"reference": "0c67aba8387726458d42461eae91a3415593bbc4",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"phpoffice/phpexcel": "~1.8.0"
},
"require-dev": {
"mockery/mockery": "~0.9",
"orchestra/testbench": "~2.2.0@dev",
"phpunit/phpunit": "~4.0"
},
"type": "library",
"autoload": {
"psr-0": {
"Maatwebsite\\Excel\\": "src/"
},
"classmap": [
"src/Maatwebsite/Excel",
"tests/TestCase.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL"
],
"authors": [
{
"name": "Maatwebsite.nl",
"email": "patrick@maatwebsite.nl"
}
],
"description": "An eloquent way of importing and exporting Excel and CSV in Laravel 4 with the power of PHPExcel",
"keywords": [
"PHPExcel",
"batch",
"csv",
"excel",
"export",
"import",
"laravel"
],
"support": {
"issues": "https://github.com/Maatwebsite/Laravel-Excel/issues",
"source": "https://github.com/Maatwebsite/Laravel-Excel/tree/master"
},
"time": "2014-07-10T09:06:07+00:00"
},
{
"name": "masterminds/html5",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/Masterminds/html5-php.git",
"reference": "fcf91eb64359852f00d921887b219479b4f21251"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251",
"reference": "fcf91eb64359852f00d921887b219479b4f21251",
"shasum": ""
},
"require": {
"ext-dom": "*",
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Masterminds\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Matt Butcher",
"email": "technosophos@gmail.com"
},
{
"name": "Matt Farina",
"email": "matt@mattfarina.com"
},
{
"name": "Asmir Mustafic",
"email": "goetas@gmail.com"
}
],
"description": "An HTML5 parser and serializer.",
"homepage": "http://masterminds.github.io/html5-php",
"keywords": [
"HTML5",
"dom",
"html",
"parser",
"querypath",
"serializer",
"xml"
],
"support": {
"issues": "https://github.com/Masterminds/html5-php/issues",
"source": "https://github.com/Masterminds/html5-php/tree/2.10.0"
},
"time": "2025-07-25T09:04:22+00:00"
},
{
"name": "monolog/monolog",
"version": "3.9.0",
@@ -3034,6 +3392,68 @@
},
"time": "2025-02-10T21:11:16+00:00"
},
{
"name": "phpoffice/phpexcel",
"version": "1.8.1",
"source": {
"type": "git",
"url": "https://github.com/PHPOffice/PHPExcel.git",
"reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPOffice/PHPExcel/zipball/372c7cbb695a6f6f1e62649381aeaa37e7e70b32",
"reference": "372c7cbb695a6f6f1e62649381aeaa37e7e70b32",
"shasum": ""
},
"require": {
"ext-xml": "*",
"ext-xmlwriter": "*",
"php": ">=5.2.0"
},
"type": "library",
"autoload": {
"psr-0": {
"PHPExcel": "Classes/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL"
],
"authors": [
{
"name": "Maarten Balliauw",
"homepage": "http://blog.maartenballiauw.be"
},
{
"name": "Mark Baker"
},
{
"name": "Franck Lefevre",
"homepage": "http://blog.rootslabs.net"
},
{
"name": "Erik Tilt"
}
],
"description": "PHPExcel - OpenXML - Read, Create and Write Spreadsheet documents in PHP - Spreadsheet engine",
"homepage": "http://phpexcel.codeplex.com",
"keywords": [
"OpenXML",
"excel",
"php",
"spreadsheet",
"xls",
"xlsx"
],
"support": {
"issues": "https://github.com/PHPOffice/PHPExcel/issues",
"source": "https://github.com/PHPOffice/PHPExcel/tree/master"
},
"abandoned": "phpoffice/phpspreadsheet",
"time": "2015-05-01T07:00:55+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.4",
@@ -3798,6 +4218,72 @@
},
"time": "2025-09-04T20:59:21+00:00"
},
{
"name": "sabberworm/php-css-parser",
"version": "v8.9.0",
"source": {
"type": "git",
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
"reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/d8e916507b88e389e26d4ab03c904a082aa66bb9",
"reference": "d8e916507b88e389e26d4ab03c904a082aa66bb9",
"shasum": ""
},
"require": {
"ext-iconv": "*",
"php": "^5.6.20 || ^7.0.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"require-dev": {
"phpunit/phpunit": "5.7.27 || 6.5.14 || 7.5.20 || 8.5.41",
"rawr/cross-data-providers": "^2.0.0"
},
"suggest": {
"ext-mbstring": "for parsing UTF-8 CSS"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Sabberworm\\CSS\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Raphael Schweikert"
},
{
"name": "Oliver Klee",
"email": "github@oliverklee.de"
},
{
"name": "Jake Hotson",
"email": "jake.github@qzdesign.co.uk"
}
],
"description": "Parser for CSS Files written in PHP",
"homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser",
"keywords": [
"css",
"parser",
"stylesheet"
],
"support": {
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v8.9.0"
},
"time": "2025-07-11T13:20:48+00:00"
},
{
"name": "setasign/fpdi",
"version": "v2.6.4",

View File

@@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('shipment_items', function (Blueprint $table) {
if (!Schema::hasColumn('shipment_items', 'ctn')) {
$table->integer('ctn')->default(0);
}
if (!Schema::hasColumn('shipment_items', 'qty')) {
$table->integer('qty')->default(0);
}
if (!Schema::hasColumn('shipment_items', 'ttl_qty')) {
$table->integer('ttl_qty')->default(0);
}
if (!Schema::hasColumn('shipment_items', 'cbm')) {
$table->double('cbm')->default(0);
}
if (!Schema::hasColumn('shipment_items', 'ttl_cbm')) {
$table->double('ttl_cbm')->default(0);
}
if (!Schema::hasColumn('shipment_items', 'kg')) {
$table->double('kg')->default(0);
}
if (!Schema::hasColumn('shipment_items', 'ttl_kg')) {
$table->double('ttl_kg')->default(0);
}
if (!Schema::hasColumn('shipment_items', 'ttl_amount')) {
$table->double('ttl_amount')->default(0);
}
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('shipment_items', function (Blueprint $table) {
// safely remove columns (optional)
$columns = [
'ctn', 'qty', 'ttl_qty', 'cbm',
'ttl_cbm', 'kg', 'ttl_kg', 'ttl_amount'
];
foreach ($columns as $col) {
if (Schema::hasColumn('shipment_items', $col)) {
$table->dropColumn($col);
}
}
});
}
};

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -15,27 +15,30 @@
<h4 class="fw-bold mb-0">Order Details</h4>
<small class="text-muted">Detailed view of this shipment order</small>
</div>
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
</div>
{{-- ACTION BUTTONS --}}
<div class="mt-3 d-flex gap-2">
{{-- ADD ITEM --}}
<button class="btn btn-add-item" data-bs-toggle="modal" data-bs-target="#addItemModal">
<i class="fas fa-plus-circle me-2"></i>Add New Item
</button>
{{-- EDIT ORDER --}}
@if($order->status === 'pending')
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
</div>
<!-- {{-- ACTION BUTTONS --}}
<div class="mt-3 d-flex gap-2">
{{-- EDIT ORDER --}} -->
<!-- @if($order->status === 'pending')
<button class="btn btn-edit-order" onclick="document.getElementById('editOrderForm').style.display='block'">
<i class="fas fa-edit me-2"></i>Edit Order
</button>
@else
<button class="btn btn-edit-order" disabled><i class="fas fa-edit me-2"></i>Edit Order</button>
@endif
@endif -->
{{-- DELETE ORDER --}}
<!-- {{-- DELETE ORDER --}}
@if($order->status === 'pending')
<form action="{{ route('admin.orders.destroy', $order->id) }}"
method="POST"
@@ -46,9 +49,9 @@
<i class="fas fa-trash-alt me-2"></i>Delete Order
</button>
</form>
@endif
@endif -->
</div>
<!-- </div> -->
<hr>
@@ -56,7 +59,7 @@
<div id="editOrderForm" class="p-3 bg-light rounded mb-4 shadow-sm" style="display:none;">
<h5 class="fw-bold mb-3">Edit Order</h5>
<form action="{{ route('admin.orders.update', $order->id) }}" method="POST">
<form action="{{ route('admin.orders.updateItem', $order->id) }}" method="POST">
@csrf
<div class="row">
@@ -185,23 +188,157 @@
<td>{{ $item->ttl_kg }}</td>
<td>{{ $item->shop_no }}</td>
<td>
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
method="POST"
onsubmit="return confirm('Delete this item?')">
@csrf
@method('DELETE')
<button class="btn btn-sm btn-delete-item">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
<td class="d-flex justify-content-center gap-2">
{{-- EDIT BUTTON --}}
<button
type="button"
class="btn btn-sm btn-edit-item"
data-bs-toggle="modal"
data-bs-target="#editItemModal{{ $item->id }}">
<i class="fas fa-edit"></i>
</button>
{{-- DELETE BUTTON --}}
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
method="POST"
onsubmit="return confirm('Delete this item?')">
@csrf
@method('DELETE')
<button class="btn btn-sm btn-delete-item">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@foreach($order->items as $item)
<div class="modal fade" id="editItemModal{{ $item->id }}" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content custom-modal">
<div class="modal-header modal-gradient-header">
<h5 class="modal-title text-white">
<i class="fas fa-edit me-2"></i>Edit Item
</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="{{ route('admin.orders.updateItem', $item->id) }}" method="POST">
@csrf
@method('PUT')
<div class="modal-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Description</label>
<input type="text" name="description"
value="{{ $item->description }}"
class="form-control custom-input" required>
</div>
<div class="col-md-3">
<label class="form-label">CTN</label>
<input type="number" name="ctn"
value="{{ $item->ctn }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">QTY</label>
<input type="number" name="qty"
value="{{ $item->qty }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">TTL QTY</label>
<input type="number" name="ttl_qty"
value="{{ $item->ttl_qty }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">Unit</label>
<input type="text" name="unit"
value="{{ $item->unit }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">Price</label>
<input type="number" name="price"
value="{{ $item->price }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">Total Amount</label>
<input type="number" name="ttl_amount"
value="{{ $item->ttl_amount }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">CBM</label>
<input type="number" name="cbm"
value="{{ $item->cbm }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">TTL CBM</label>
<input type="number" name="ttl_cbm"
value="{{ $item->ttl_cbm }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">KG</label>
<input type="number" name="kg"
value="{{ $item->kg }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">TTL KG</label>
<input type="number" name="ttl_kg"
value="{{ $item->ttl_kg }}"
class="form-control custom-input">
</div>
<div class="col-md-3">
<label class="form-label">Shop No</label>
<input type="text" name="shop_no"
value="{{ $item->shop_no }}"
class="form-control custom-input">
</div>
</div>
</div>
<div class="modal-footer">
<div class="modal-footer">
<button type="button" class="btn btn-modal-close" data-bs-dismiss="modal">Close</button>
<button type="submit" class="btn btn-modal-add">Add Item</button>
</div>
</div>
</form>
</div>
</div>
</div>
@endforeach
{{-- TOTALS --}}
<div class="row text-center mt-4">
<div class="col-md-3">
@@ -239,7 +376,7 @@
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
</div>
<form action="{{ route('admin.orders.addItem', $order->id) }}" method="POST">
<form id="addItemForm" action="{{ route('admin.orders.addItem', $order->id) }}" method="POST">
@csrf
<div class="modal-body">
@@ -253,54 +390,75 @@
<h5 class="section-title">Deleted Items (Use / Restore)</h5>
<ul class="list-group mb-3">
<div class="table-responsive mb-4">
<table class="table table-bordered align-middle text-center">
<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>Actions</th>
</tr>
</thead>
@forelse($deletedItems as $deleted)
<li class="list-group-item">
<tbody>
@forelse($deletedItems as $key => $deleted)
<tr>
<td>{{ $key + 1 }}</td>
<td>{{ $deleted->description }}</td>
<td>{{ $deleted->ctn }}</td>
<td>{{ $deleted->qty }}</td>
<td>{{ $deleted->ttl_qty }}</td>
<td>{{ $deleted->unit }}</td>
<td>{{ number_format($deleted->price, 2) }}</td>
<td>{{ number_format($deleted->ttl_amount, 2) }}</td>
<td>{{ $deleted->cbm }}</td>
<td>{{ $deleted->ttl_cbm }}</td>
<td>{{ $deleted->kg }}</td>
<td>{{ $deleted->ttl_kg }}</td>
<td>{{ $deleted->shop_no }}</td>
<div class="d-flex justify-content-between">
<td class="d-flex justify-content-center gap-2">
<div>
<strong>{{ $deleted->description }}</strong><br>
<small>
CTN: {{ $deleted->ctn }},
QTY: {{ $deleted->qty }},
TTL/QTY: {{ $deleted->ttl_qty }},
Unit: {{ $deleted->unit }},
Price: {{ $deleted->price }},
TTL Amt: {{ $deleted->ttl_amount }},
CBM: {{ $deleted->cbm }},
TTL CBM: {{ $deleted->ttl_cbm }},
KG: {{ $deleted->kg }},
TTL KG: {{ $deleted->ttl_kg }},
Shop: {{ $deleted->shop_no }}
</small>
</div>
{{-- USE THIS ITEM --}}
<button type="button"
class="btn btn-sm btn-use-item"
onclick="fillFormFromDeleted({{ json_encode($deleted) }})">
Use
</button>
<div class="d-flex gap-2">
{{-- Auto-fill button --}}
<button type="button" class="btn btn-sm btn-use-item"
onclick="fillFormFromDeleted({{ json_encode($deleted) }})">
Use This
{{-- RESTORE ITEM --}}
<form action="{{ route('admin.orders.restoreItem', $deleted->id) }}" method="POST">
@csrf
<button type="button"
class="btn btn-sm btn-restore js-restore-item"
data-id="{{ $deleted->id }}"
data-url="{{ route('admin.orders.restoreItem', $deleted->id) }}">
Restore
</button>
</form>
{{-- Restore --}}
<form action="{{ route('admin.orders.restoreItem', $deleted->id) }}" method="POST">
@csrf
<button class="btn btn-sm btn-restore">Restore</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="14" class="text-muted">No deleted items.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</li>
@empty
<li class="list-group-item text-muted">No deleted items.</li>
@endforelse
</ul>
{{-- ADD FORM --}}
<div class="row g-3">
@@ -380,21 +538,62 @@
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const token = '{{ csrf_token() }}';
document.querySelectorAll('.js-restore-item').forEach(function (btn) {
btn.addEventListener('click', function () {
if (!confirm('Restore this item?')) return;
const url = this.dataset.url;
const row = this.closest('tr');
fetch(url, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': token,
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({})
})
.then(response => {
if (!response.ok) throw new Error('Restore failed');
return response.json().catch(() => ({}));
})
.then(() => {
row.remove();
location.reload(); // 🔥 refresh UI so restored item appears
})
.catch(() => {
alert('Could not restore item. Please try again.');
});
});
});
});
</script>
{{-- AUTO-FILL SCRIPT --}}
<script>
function fillFormFromDeleted(item) {
document.querySelector('input[name="description"]').value = item.description;
document.querySelector('input[name="ctn"]').value = item.ctn;
document.querySelector('input[name="qty"]').value = item.qty;
document.querySelector('input[name="ttl_qty"]').value = item.ttl_qty;
document.querySelector('input[name="unit"]').value = item.unit;
document.querySelector('input[name="price"]').value = item.price;
document.querySelector('input[name="ttl_amount"]').value = item.ttl_amount;
document.querySelector('input[name="cbm"]').value = item.cbm;
document.querySelector('input[name="ttl_cbm"]').value = item.ttl_cbm;
document.querySelector('input[name="kg"]').value = item.kg;
document.querySelector('input[name="ttl_kg"]').value = item.ttl_kg;
document.querySelector('input[name="shop_no"]').value = item.shop_no;
let form = document.getElementById('addItemForm');
form.querySelector('input[name="description"]').value = item.description;
form.querySelector('input[name="ctn"]').value = item.ctn;
form.querySelector('input[name="qty"]').value = item.qty;
form.querySelector('input[name="ttl_qty"]').value = item.ttl_qty;
form.querySelector('input[name="unit"]').value = item.unit;
form.querySelector('input[name="price"]').value = item.price;
form.querySelector('input[name="ttl_amount"]').value = item.ttl_amount;
form.querySelector('input[name="cbm"]').value = item.cbm;
form.querySelector('input[name="ttl_cbm"]').value = item.ttl_cbm;
form.querySelector('input[name="kg"]').value = item.kg;
form.querySelector('input[name="ttl_kg"]').value = item.ttl_kg;
form.querySelector('input[name="shop_no"]').value = item.shop_no;
}
</script>
@@ -404,13 +603,15 @@ function fillFormFromDeleted(item) {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: white;
padding: 12px 24px;
padding: 6px 14px;
border-radius: 10px;
font-weight: 600;
font-size: 0.85rem;
transition: all 0.3s ease;
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
margin-right: -800px;
}
.btn-add-item:hover {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,975 @@
@extends('admin.layouts.app')
@section('page-title', 'Shipment Details')
@section('content')
<style>
/* ===== ANIMATIONS ===== */
.fade-in {
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0px); }
}
/* ===== CARD STYLES ===== */
.card {
border-radius: 20px;
background: #ffffff;
box-shadow: 0 15px 35px rgba(13, 38, 76, 0.08);
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
overflow: hidden;
}
.card:hover {
transform: translateY(-6px);
box-shadow: 0 20px 40px rgba(103, 126, 234, 0.15);
}
.card-header h5 {
margin: 0;
font-size: 20px;
font-weight: 800;
display: flex;
align-items: center;
gap: 10px;
}
/* ===== GLASSMORPHISM BUTTON ===== */
.glass-btn {
background: linear-gradient(
135deg,
rgba(103, 126, 234, 0.25) 0%,
rgba(118, 75, 162, 0.25) 100%
);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1.5px solid rgba(255, 255, 255, 0.4);
color: #ffffff !important;
padding: 12px 24px;
border-radius: 14px;
font-weight: 700;
display: inline-flex;
align-items: center;
gap: 10px;
text-decoration: none;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow:
0 8px 32px rgba(103, 126, 234, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.3),
inset 0 -1px 0 rgba(0, 0, 0, 0.1);
position: relative;
overflow: hidden;
z-index: 1;
}
.glass-btn::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
135deg,
rgba(103, 126, 234, 0.4) 0%,
rgba(118, 75, 162, 0.4) 100%
);
z-index: -1;
transition: opacity 0.4s ease;
opacity: 0;
}
.glass-btn:hover {
transform: translateY(-4px) scale(1.05);
box-shadow:
0 15px 40px rgba(103, 126, 234, 0.35),
inset 0 1px 0 rgba(255, 255, 255, 0.4),
inset 0 -1px 0 rgba(0, 0, 0, 0.1);
border-color: rgba(255, 255, 255, 0.6);
background: linear-gradient(
135deg,
rgba(103, 126, 234, 0.15) 0%,
rgba(118, 75, 162, 0.15) 100%
);
}
.glass-btn:hover::before {
opacity: 1;
}
.glass-btn:active {
transform: translateY(-1px) scale(0.98);
transition: transform 0.1s ease;
}
.glass-btn i {
font-size: 18px;
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
}
/* ===== HEADER STYLES ===== */
.card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: #fff;
padding: 22px 28px;
border-radius: 20px 20px 0 0 !important;
position: relative;
overflow: hidden;
}
.card-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
to right,
rgba(255, 255, 255, 0.1) 0%,
rgba(255, 255, 255, 0.05) 100%
);
pointer-events: none;
}
.title-row {
display: flex;
align-items: center;
gap: 12px;
z-index: 1;
}
.header-controls {
display: flex;
align-items: center;
gap: 12px;
z-index: 1;
}
.header-cancel-btn {
background: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
color: rgba(255, 255, 255, 0.95);
border: 1.5px solid rgba(255, 255, 255, 0.3);
padding: 10px 14px;
border-radius: 12px;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
.header-cancel-btn:hover {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.5);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
}
/* ===== INFO BLOCKS ===== */
.shipment-info-box {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
gap: 24px;
margin-bottom: 32px;
}
.shipment-info-item {
flex: 1;
min-width: 220px;
background: linear-gradient(145deg, #ffffff, #f8fafc);
padding: 24px;
border-radius: 16px;
box-shadow:
0 5px 20px rgba(0, 0, 0, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
text-align: center;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(255, 255, 255, 0.8);
position: relative;
overflow: hidden;
}
.shipment-info-item::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #667eea, #764ba2);
opacity: 0;
transition: opacity 0.4s ease;
}
.shipment-info-item:hover {
transform: translateY(-6px);
box-shadow:
0 15px 35px rgba(103, 126, 234, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.shipment-info-item:hover::before {
opacity: 1;
}
.shipment-info-label {
color: #64748b;
font-weight: 600;
margin-bottom: 8px;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.shipment-info-value {
font-size: 24px;
font-weight: 800;
color: #1e293b;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
/* ===== TOTAL BOXES ===== */
.total-box {
background: linear-gradient(145deg, #ffffff, #f8fafc);
padding: 20px;
border-radius: 16px;
box-shadow:
0 5px 20px rgba(0, 0, 0, 0.05),
inset 0 1px 0 rgba(255, 255, 255, 0.8);
text-align: center;
min-width: 160px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
border: 1px solid rgba(255, 255, 255, 0.8);
position: relative;
overflow: hidden;
}
.total-box::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, #667eea, #764ba2);
transform: translateX(-100%);
transition: transform 0.4s ease;
}
.total-box:hover {
transform: translateY(-5px) scale(1.03);
box-shadow:
0 15px 35px rgba(103, 126, 234, 0.15),
inset 0 1px 0 rgba(255, 255, 255, 0.9);
}
.total-box:hover::after {
transform: translateX(0);
}
.total-title {
font-size: 13px;
font-weight: 700;
color: #64748b;
margin-bottom: 10px;
text-transform: uppercase;
letter-spacing: 1px;
}
.total-value {
font-size: 22px;
font-weight: 900;
color: #1e293b;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
/* ===== TABLE STYLES ===== */
.table {
border-collapse: separate;
border-spacing: 0;
border-radius: 16px;
overflow: hidden;
background: #ffffff;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05);
}
.table thead th {
background: #667eea ;
color: #ffffff;
font-weight: 700;
padding: 18px;
white-space: nowrap;
border: none;
position: relative;
}
.table thead th::after {
content: '';
position: absolute;
bottom: 0;
left: 15px;
right: 15px;
height: 1px;
background: rgba(255, 255, 255, 0.3);
}
.table tbody tr {
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.table tbody tr:hover {
background: linear-gradient(90deg, rgba(103, 126, 234, 0.05), rgba(118, 75, 162, 0.05));
transform: translateX(4px);
border-left: 3px solid #667eea;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
}
.table tbody td {
padding: 16px;
vertical-align: middle;
white-space: nowrap;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
font-weight: 500;
}
/* ===== GLASSMORPHISM MODAL ===== */
#newOrderModal .modal-content {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border-radius: 24px;
border: 1.5px solid rgba(255, 255, 255, 0.3);
box-shadow:
0 25px 50px -12px rgba(0, 0, 0, 0.25),
inset 0 1px 0 rgba(255, 255, 255, 0.5);
overflow: hidden;
}
#newOrderModal .modal-header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-bottom: 1px solid rgba(255, 255, 255, 0.3);
padding: 24px 32px;
border-radius: 24px 24px 0 0;
position: relative;
overflow: hidden;
}
#newOrderModal .modal-header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
to right,
rgba(255, 255, 255, 0.1) 0%,
rgba(255, 255, 255, 0.05) 100%
);
}
#newOrderModal .modal-title {
color: #ffffff;
font-weight: 800;
font-size: 22px;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
position: relative;
z-index: 1;
}
#newOrderModal .btn-close {
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 10px;
padding: 8px;
opacity: 0.9;
transition: all 0.3s ease;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
#newOrderModal .btn-close:hover {
background: rgba(255, 255, 255, 0.3);
opacity: 1;
transform: rotate(90deg);
}
#newOrderModal .modal-body {
background: rgba(248, 250, 252, 0.7);
padding: 32px;
backdrop-filter: blur(10px);
}
#newOrderModal .modal-footer {
background: rgba(255, 255, 255, 0.8);
border-top: 1px solid rgba(255, 255, 255, 0.4);
padding: 24px 32px;
border-radius: 0 0 24px 24px;
backdrop-filter: blur(10px);
}
#newOrderModal h6 {
color: #1e293b;
font-weight: 700;
margin-bottom: 16px;
font-size: 16px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* ===== ORDER LIST ITEMS ===== */
#availableOrdersList,
#deletedOrdersList {
max-height: 300px;
overflow-y: auto;
border-radius: 16px;
background: rgba(255, 255, 255, 0.6);
backdrop-filter: blur(10px);
border: 1.5px solid rgba(255, 255, 255, 0.5);
}
.list-group-item {
background: rgba(255, 255, 255, 0.7);
border: 1.5px solid rgba(255, 255, 255, 0.4);
margin-bottom: 8px;
border-radius: 12px !important;
padding: 16px;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
}
.list-group-item:hover {
background: rgba(103, 126, 234, 0.1);
border-color: rgba(103, 126, 234, 0.3);
transform: translateX(4px);
box-shadow: 0 5px 15px rgba(103, 126, 234, 0.15);
}
.list-group-item strong {
color: #1e293b;
font-weight: 700;
}
.list-group-item .small {
color: #64748b;
font-size: 13px;
}
.order-checkbox {
width: 18px;
height: 18px;
border-radius: 6px;
border: 2px solid #cbd5e1;
cursor: pointer;
transition: all 0.3s ease;
}
.order-checkbox:checked {
background: linear-gradient(135deg, #667eea, #764ba2);
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(103, 126, 234, 0.2);
}
/* ===== MODAL BUTTONS ===== */
#newOrderModal .btn-secondary {
background: rgba(100, 116, 139, 0.2);
backdrop-filter: blur(10px);
border: 1.5px solid rgba(100, 116, 139, 0.3);
color: #64748b;
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
transition: all 0.3s ease;
}
#newOrderModal .btn-secondary:hover {
background: rgba(100, 116, 139, 0.3);
border-color: rgba(100, 116, 139, 0.5);
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(100, 116, 139, 0.2);
}
#newOrderModal .btn-success {
background: linear-gradient(
135deg,
rgba(34, 197, 94, 0.25) 0%,
rgba(21, 128, 61, 0.25) 100%
);
backdrop-filter: blur(12px);
border: 1.5px solid rgba(34, 197, 94, 0.4);
color: #166534;
padding: 12px 28px;
border-radius: 12px;
font-weight: 700;
transition: all 0.3s ease;
box-shadow:
0 8px 25px rgba(34, 197, 94, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
}
#newOrderModal .btn-success:hover {
background: linear-gradient(
135deg,
rgba(34, 197, 94, 0.35) 0%,
rgba(21, 128, 61, 0.35) 100%
);
border-color: rgba(34, 197, 94, 0.6);
color: #14532d;
transform: translateY(-2px);
box-shadow:
0 12px 30px rgba(34, 197, 94, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.5);
}
/* ===== SCROLLBAR STYLING ===== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.05);
border-radius: 10px;
}
::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #667eea, #764ba2);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #5a6fe8, #6a42a0);
}
/* ===== ACTION BUTTONS ===== */
.btn-secondary {
background: linear-gradient(135deg, #94a3b8, #64748b);
border: none;
color: white;
padding: 12px 28px;
border-radius: 12px;
font-weight: 600;
transition: all 0.3s ease;
box-shadow: 0 6px 20px rgba(100, 116, 139, 0.2);
}
.btn-secondary:hover {
background: linear-gradient(135deg, #8492a6, #565e6e);
transform: translateY(-2px);
box-shadow: 0 10px 25px rgba(100, 116, 139, 0.3);
color: white;
}
/* ===== RESPONSIVE ADJUSTMENTS ===== */
@media (max-width: 768px) {
.shipment-info-item,
.total-box {
min-width: 100%;
}
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 16px;
}
.header-controls {
width: 100%;
justify-content: space-between;
}
.glass-btn {
padding: 10px 20px;
font-size: 14px;
}
}
</style>
<div class="container-fluid py-4">
<div class="card mb-4">
<div class="card-header">
<div class="title-row">
<h5 class="mb-0"><i class="bi bi-truck me-2"></i>Shipment Details</h5>
<!-- <small class="text-white-50" style="margin-left:8px; font-weight:600;">{{ $shipment->shipment_id ?? '' }}</small> -->
</div>
<div class="header-controls">
<!-- Add Order button (top-right) with glass effect -->
@if($mode === 'edit' && $shipment->status === 'pending')
<a href="#" data-bs-toggle="modal" data-bs-target="#newOrderModal" class="glass-btn">
<i class="bi bi-plus-circle"></i> Add Order
</a>
@endif
<!-- Cancel/close: goes back to shipments list -->
<a href="{{ route('admin.shipments') }}" class="btn header-cancel-btn" title="Cancel / Back">
<i class="bi bi-x-lg"></i>
</a>
</div>
</div>
<div class="card-body">
<!-- ================================ -->
<!-- SHIPMENT MAIN DETAILS -->
<!-- ================================ -->
<div class="shipment-info-box">
<div class="shipment-info-item">
<div class="shipment-info-label">Shipment ID</div>
<div class="shipment-info-value">{{ $shipment->shipment_id }}</div>
</div>
<div class="shipment-info-item">
<div class="shipment-info-label">Status</div>
<div class="shipment-info-value text-capitalize">
{{ str_replace('_', ' ', $shipment->status) }}
</div>
</div>
<div class="shipment-info-item">
<div class="shipment-info-label">Shipment Date</div>
<div class="shipment-info-value">
{{ \Carbon\Carbon::parse($shipment->shipment_date)->format('d M Y') }}
</div>
</div>
<div class="shipment-info-item">
<div class="shipment-info-label">Total Orders</div>
<div class="shipment-info-value">{{ $orders->count() }}</div>
</div>
</div>
<!-- ================================ -->
<!-- SHIPMENT TOTAL BLOCKS -->
<!-- ================================ -->
<h5 class="fw-bold mb-3 mt-4" style="color: #1e293b;">Shipment Summary</h5>
<div class="d-flex flex-wrap gap-3">
<div class="total-box">
<div class="total-title">TOTAL CTN</div>
<div class="total-value">{{ $shipment->total_ctn }}</div>
</div>
<div class="total-box">
<div class="total-title">TOTAL QTY</div>
<div class="total-value">{{ $shipment->total_qty }}</div>
</div>
<div class="total-box">
<div class="total-title">TOTAL CBM</div>
<div class="total-value">{{ $shipment->total_cbm }}</div>
</div>
<div class="total-box">
<div class="total-title">TOTAL KG</div>
<div class="total-value">{{ $shipment->total_kg }}</div>
</div>
<div class="total-box">
<div class="total-title">TOTAL TTL QTY</div>
<div class="total-value">{{ $shipment->total_ttl_qty }}</div>
</div>
<div class="total-box">
<div class="total-title">TOTAL TTL CBM</div>
<div class="total-value">{{ $shipment->total_ttl_cbm }}</div>
</div>
<div class="total-box">
<div class="total-title">TOTAL TTL KG</div>
<div class="total-value">{{ $shipment->total_ttl_kg }}</div>
</div>
<div class="total-box">
<div class="total-title">TOTAL AMOUNT</div>
<div class="total-value text-success">
{{ number_format($shipment->total_amount, 2) }}
</div>
</div>
</div>
<!-- ================================ -->
<!-- ORDERS TABLE LIST -->
<!-- ================================ -->
<h5 class="fw-bold mb-3 mt-5" style="color: #1e293b;">Orders in This Shipment</h5>
<div class="table-responsive">
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Order ID</th>
<th>Origin</th>
<th>Destination</th>
<th>CTN</th>
<th>QTY</th>
<th>TTL/QTY</th>
<th>CBM</th>
<th>TTL CBM</th>
<th>KG</th>
<th>TTL KG</th>
<th>Amount ()</th>
<!-- Conditionally show Delete column header -->
@if($mode === 'edit' && $shipment->status === 'pending')
<th>Delete</th>
@endif
</tr>
</thead>
<tbody>
@foreach($orders as $order)
<tr>
<td class="fw-bold text-primary">{{ $order->order_id }}</td>
<td>{{ $order->origin }}</td>
<td>{{ $order->destination }}</td>
<td>{{ $order->ctn }}</td>
<td>{{ $order->qty }}</td>
<td>{{ $order->ttl_qty }}</td>
<td>{{ $order->cbm }}</td>
<td>{{ $order->ttl_cbm }}</td>
<td>{{ $order->kg }}</td>
<td>{{ $order->ttl_kg }}</td>
<td class="fw-bold text-success">
{{ number_format($order->ttl_amount, 2) }}
</td>
<!-- Conditionally show Delete button -->
@if($mode === 'edit' && $shipment->status === 'pending')
<td>
<form method="POST" action="{{ route('admin.shipments.removeOrder') }}" onsubmit="return confirmDelete()">
@csrf
<input type="hidden" name="shipment_id" value="{{ $shipment->id }}">
<input type="hidden" name="order_id" value="{{ $order->id }}">
<button type="submit" class="btn btn-danger btn-sm" style="border-radius: 10px; padding: 8px 16px;">
<i class="bi bi-trash"></i> Delete
</button>
</form>
</td>
@endif
</tr>
@endforeach
</tbody>
</table>
</div>
<!-- Action Buttons -->
<div class="d-flex justify-content-between mt-4">
<!-- <a href="{{ route('admin.shipments') }}" class="btn btn-secondary">
<i class="bi bi-arrow-left"></i> Back to Shipments
</a>
-->
@if($mode === 'edit' && $shipment->status === 'pending')
<div>
<!-- <a href="{{ route('admin.shipments.view', ['id' => $shipment->id, 'mode' => 'edit']) }}"
class="btn btn-warning me-2">
<i class="bi bi-pencil"></i> Edit Shipment
</a> -->
<!-- <a href="{{ route('admin.shipments.view', ['id' => $shipment->id, 'mode' => 'view']) }}"
class="btn btn-info">
<i class="bi bi-eye"></i> View Mode
</a> -->
</div>
@elseif($shipment->status === 'pending')
<div>
<!-- <a href="{{ route('admin.shipments.view', ['id' => $shipment->id, 'mode' => 'edit']) }}"
class="btn btn-primary">
<i class="bi bi-pencil"></i> Edit Shipment
</a> -->
</div>
@endif
</div>
</div>
</div>
<!-- New Order Modal -->
<div class="modal fade" id="newOrderModal" tabindex="-1" aria-labelledby="newOrderModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="newOrderModalLabel">Add Orders to Shipment</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="row mb-3">
<div class="col-md-6">
<h6>Deleted Orders (recently removed)</h6>
<div id="deletedOrdersList" class="list-group"></div>
</div>
<div class="col-md-6">
<h6>Available Orders</h6>
<div id="availableOrdersList" class="list-group"></div>
</div>
</div>
<div class="mt-3">
<small class="text-muted">Select orders to add back to this shipment, then click "Add Selected".</small>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button id="addSelectedOrdersBtn" type="button" class="btn btn-success">Add Selected</button>
</div>
</div>
</div>
</div>
</div>
<script>
function confirmDelete() {
return confirm('Are you sure you want to remove this order from the shipment?');
}
</script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const csrfToken = '{{ csrf_token() }}';
const shipmentId = {{ $shipment->id }};
let availableOrders = @json($availableOrders ?? []);
let deletedOrders = [];
function renderOrdersList(containerId, orders) {
const container = document.getElementById(containerId);
container.innerHTML = '';
if (!orders.length) {
container.innerHTML = '<div class="text-muted py-3">No orders</div>';
return;
}
orders.forEach(order => {
const item = document.createElement('label');
item.className = 'list-group-item d-flex justify-content-between align-items-center';
item.innerHTML = `
<div>
<input type="checkbox" class="me-2 order-checkbox" data-order='${JSON.stringify(order)}'>
<strong>${order.order_id}</strong>
<div class="small text-muted"> ${order.origin} ${order.destination} ${order.qty} qty ${parseFloat(order.ttl_amount).toLocaleString('en-IN', {minimumFractionDigits:2})}</div>
</div>
<div class="small text-muted">ID: ${order.id}</div>
`;
container.appendChild(item);
});
}
renderOrdersList('availableOrdersList', availableOrders);
renderOrdersList('deletedOrdersList', deletedOrders);
// Intercept remove-order forms (update selector if form action different)
document.querySelectorAll('form[action="{{ route('admin.shipments.removeOrder') }}"]').forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
if (!confirm('Are you sure you want to remove this order from the shipment?')) return;
const formData = new FormData(this);
fetch(this.action, {
method: 'POST',
headers: {'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json'},
body: formData
})
.then(res => res.json())
.then(data => {
if (!data.success) { alert(data.message || 'Failed'); return; }
// Remove row from DOM
const row = this.closest('tr');
// Build deleted order object from row cells if present
const orderObj = {
id: formData.get('order_id'),
order_id: row ? row.querySelector('td:first-child')?.innerText.trim() : ('Order-'+formData.get('order_id')),
origin: row ? row.cells[1]?.innerText.trim() : '',
destination: row ? row.cells[2]?.innerText.trim() : '',
qty: row ? row.cells[4]?.innerText.trim() : '',
ttl_amount: row ? row.cells[10]?.innerText.replace('₹','').replace(',','').trim() : 0
};
if (row) row.remove();
deletedOrders.unshift(orderObj);
renderOrdersList('deletedOrdersList', deletedOrders);
// Optional: update totals by refetching shipment summary endpoint or reload
if (data.shipment) updateTotalsInDOM(data.shipment);
})
.catch(err => {
console.error(err);
alert('Remove failed.');
});
});
});
// Open modal from add-order button
document.querySelectorAll('.glass-btn').forEach(btn => {
btn.addEventListener('click', function(e) {
renderOrdersList('availableOrdersList', availableOrders);
renderOrdersList('deletedOrdersList', deletedOrders);
const modal = new bootstrap.Modal(document.getElementById('newOrderModal'));
modal.show();
});
});
// Add selected orders
document.getElementById('addSelectedOrdersBtn').addEventListener('click', function() {
const checked = Array.from(document.querySelectorAll('.order-checkbox:checked'));
if (!checked.length) { alert('Please select orders'); return; }
const orderIds = checked.map(cb => JSON.parse(cb.getAttribute('data-order')).id);
fetch(`/admin/shipments/${shipmentId}/add-orders`, {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken, 'Accept': 'application/json'},
body: JSON.stringify({ order_ids: orderIds })
})
.then(r => r.json())
.then(data => {
if (!data.success) { alert(data.message || 'Failed'); return; }
// Update orders table — simplest approach: reload page to reflect DB
// But we can also append rows from data.orders (if provided)
location.reload();
})
.catch(err => {
console.error(err);
alert('Add failed.');
});
});
function updateTotalsInDOM(shipment) {
try {
if (document.getElementById('total_qty')) {
document.getElementById('total_qty').innerText = shipment.total_qty;
}
if (document.getElementById('total_amount')) {
document.getElementById('total_amount').innerText = '₹' + parseFloat(shipment.total_amount).toLocaleString('en-IN', {minimumFractionDigits:2});
}
// add other totals similarly...
} catch(e) { console.warn(e); }
}
});
document.addEventListener('hidden.bs.modal', function (event) {
document.body.classList.remove('modal-open');
let backdrop = document.querySelector('.modal-backdrop');
if (backdrop) backdrop.remove();
});
</script>
@endsection

View File

@@ -66,74 +66,69 @@ Route::prefix('admin')
// ---------------------------
// USER REQUESTS
// ---------------------------
Route::get('/requests', [UserRequestController::class, 'index'])
->name('admin.requests');
Route::get('/requests', [UserRequestController::class, 'index'])
->name('admin.requests');
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
->name('admin.requests.approve');
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
->name('admin.requests.approve');
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
->name('admin.requests.reject');
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
->name('admin.requests.reject');
// ---------------------------
// MARK LIST
// ---------------------------
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
->name('admin.marklist.index');
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
->name('admin.marklist.index');
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
->name('admin.marklist.toggle');
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
->name('admin.marklist.toggle');
// ---------------------------
// ORDERS
// ---------------------------
Route::get('/orders', [AdminOrderController::class, 'orderShow'])
->name('admin.orders');
Route::get('/orders', [AdminOrderController::class, 'orderShow'])
->name('admin.orders');
Route::get('/orders/list', [AdminOrderController::class, 'index'])
->name('admin.orders.index');
Route::get('/orders/list', [AdminOrderController::class, 'index'])
->name('admin.orders.index');
Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
->name('admin.orders.show');
Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
->name('admin.orders.show');
Route::post('/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
->name('admin.orders.temp.add');
Route::post('/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
->name('admin.orders.temp.add');
Route::post('/orders/temp/delete', [AdminOrderController::class, 'deleteTempItem'])
->name('admin.orders.temp.delete');
Route::post('/orders/temp/delete', [AdminOrderController::class, 'deleteTempItem'])
->name('admin.orders.temp.delete');
Route::post('/orders/temp/reset', [AdminOrderController::class, 'resetTemp'])
->name('admin.orders.temp.reset');
Route::post('/orders/temp/reset', [AdminOrderController::class, 'resetTemp'])
->name('admin.orders.temp.reset');
Route::post('/orders/finish', [AdminOrderController::class, 'finishOrder'])
->name('admin.orders.finish');
Route::post('/orders/finish', [AdminOrderController::class, 'finishOrder'])
->name('admin.orders.finish');
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
->name('admin.orders.popup');
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
->name('admin.orders.popup');
// ---------------------------
// ORDERS (FIXED ROUTES)
// ---------------------------
// ORDERS (FIXED ROUTES)
// ---------------------------
// Add item to order
Route::post('/orders/{order}/item', [AdminOrderController::class, 'addItem'])
->name('admin.orders.addItem');
// Delete item from order
Route::delete('/orders/item/{id}', [AdminOrderController::class, 'deleteItem'])
->name('admin.orders.deleteItem');
// Restore deleted item
Route::post('/orders/item/{id}/restore', [AdminOrderController::class, 'restoreItem'])
->name('admin.orders.restoreItem');
// Update main order fields
Route::post('/orders/{id}/update', [AdminOrderController::class, 'update'])
->name('admin.orders.update');
Route::put('/admin/orders/item/update/{id}', [AdminOrderController::class, 'updateItem'])
->name('admin.orders.updateItem');
// Delete full order
Route::delete('/orders/{id}/delete', [AdminOrderController::class, 'destroy'])
->name('admin.orders.destroy');
@@ -141,132 +136,141 @@ Route::prefix('admin')
// ---------------------------
// SHIPMENTS (FIXED ROUTES)
// ---------------------------
Route::get('/shipments', [ShipmentController::class, 'index'])
->name('admin.shipments');
Route::post('/shipments', [ShipmentController::class, 'store'])
->name('admin.shipments.store');
// View shipment MUST be before /shipments/{id}
Route::get('/shipments/view/{id}', [ShipmentController::class, 'show'])
->name('admin.shipments.view');
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
->name('admin.shipments.updateStatus');
// List shipments
Route::get('/shipments', [ShipmentController::class, 'index'])
->name('admin.shipments');
// Get shipment orders for modal (AJAX)
Route::get('/shipments/{id}/orders', [ShipmentController::class, 'getShipmentOrders'])
->name('admin.shipments.orders');
// Create shipment
Route::post('/shipments', [ShipmentController::class, 'store'])
->name('admin.shipments.store');
// Get shipment details for edit (AJAX)
Route::get('/shipments/{id}', [ShipmentController::class, 'show'])
->name('admin.shipments.show');
// Update status
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
->name('admin.shipments.updateStatus');
// Shipment Update
Route::put('/shipments/{id}', [ShipmentController::class, 'update'])
->name('admin.shipments.update');
// Shipment orders (AJAX)
Route::get('/shipments/{id}/orders', [ShipmentController::class, 'getShipmentOrders'])
->name('admin.shipments.orders');
// Shipment Delete
Route::delete('/shipments/{id}', [ShipmentController::class, 'destroy'])
->name('admin.shipments.destroy');
// Shipment update
Route::put('/shipments/{id}', [ShipmentController::class, 'update'])
->name('admin.shipments.update');
// Shipment delete
Route::delete('/shipments/{id}', [ShipmentController::class, 'destroy'])
->name('admin.shipments.destroy');
// Remove order
Route::post('/shipments/remove-order', [ShipmentController::class, 'removeOrder'])
->name('admin.shipments.removeOrder');
Route::post('/shipments/add-order', [ShipmentController::class, 'addOrder'])
->name('admin.shipments.addOrder');
Route::post('/shipments/{id}/add-orders', [ShipmentController::class, 'addOrders'])
->name('admin.shipments.addOrders');
// ---------------------------
// INVOICES
// ---------------------------
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
->name('admin.invoices.index');
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
->name('admin.invoices.index');
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
->name('admin.invoices.popup');
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
->name('admin.invoices.popup');
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
->name('admin.invoices.edit');
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
->name('admin.invoices.edit');
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
->name('admin.invoices.update');
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
->name('admin.invoices.update');
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::delete('/installment/{id}', [AdminInvoiceController::class, 'deleteInstallment'])
->name('admin.invoice.installment.delete');
Route::delete('/installment/{id}', [AdminInvoiceController::class, 'deleteInstallment'])
->name('admin.invoice.installment.delete');
//Add New Invoice
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
//Add New Invoice
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
// ---------------------------
// CUSTOMERS
// ---------------------------
Route::get('/customers', [AdminCustomerController::class, 'index'])
->name('admin.customers.index');
Route::get('/customers', [AdminCustomerController::class, 'index'])
->name('admin.customers.index');
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
->name('admin.customers.add');
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
->name('admin.customers.add');
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
->name('admin.customers.store');
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
->name('admin.customers.store');
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
->name('admin.customers.view');
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
->name('admin.customers.view');
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
->name('admin.customers.status');
});
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
->name('admin.customers.status');
});
// ==========================================
// ADMIN ACCOUNT (AJAX) ROUTES
// ==========================================
Route::prefix('admin/account')
->middleware('auth:admin')
->name('admin.account.')
->group(function () {
Route::prefix('admin/account')
->middleware('auth:admin')
->name('admin.account.')
->group(function () {
Route::get('/dashboard', [AdminAccountController::class, 'getDashboardData'])
->name('dashboard');
Route::get('/dashboard', [AdminAccountController::class, 'getDashboardData'])
->name('dashboard');
Route::get('/available-orders', [AdminAccountController::class, 'getAvailableOrders'])
->name('orders.available');
Route::get('/available-orders', [AdminAccountController::class, 'getAvailableOrders'])
->name('orders.available');
Route::post('/create-order', [AdminAccountController::class, 'accountCreateOrder'])
->name('create');
Route::post('/create-order', [AdminAccountController::class, 'accountCreateOrder'])
->name('create');
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])
->name('toggle');
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])
->name('toggle');
Route::post('/installment/create', [AdminAccountController::class, 'addInstallment'])
->name('installment.create');
Route::post('/installment/create', [AdminAccountController::class, 'addInstallment'])
->name('installment.create');
Route::post('/installment/update-status', [AdminAccountController::class, 'updateInstallmentStatus'])
->name('installment.update');
Route::post('/installment/update-status', [AdminAccountController::class, 'updateInstallmentStatus'])
->name('installment.update');
Route::get('/entry/{entry_no}', [AdminAccountController::class, 'getEntryDetails'])
->name('entry.details');
Route::get('/entry/{entry_no}', [AdminAccountController::class, 'getEntryDetails'])
->name('entry.details');
// ⬇⬇ NEW ROUTES FOR EDIT + DELETE ⬇⬇
Route::post('/update-entry', [AdminAccountController::class, 'updateEntry'])
->name('update.entry');
// ⬇⬇ NEW ROUTES FOR EDIT + DELETE ⬇⬇
Route::post('/update-entry', [AdminAccountController::class, 'updateEntry'])
->name('update.entry');
Route::post('/delete-entry', [AdminAccountController::class, 'deleteEntry'])
->name('delete.entry');
Route::post('/delete-entry', [AdminAccountController::class, 'deleteEntry'])
->name('delete.entry');
// ===== Associated Orders Routes (EDIT MODAL साठी) =====
Route::post('/add-orders-to-entry', [AdminAccountController::class, 'addOrdersToEntry'])
->name('add.orders.to.entry');
// ===== Associated Orders Routes (EDIT MODAL साठी) =====
Route::post('/add-orders-to-entry', [AdminAccountController::class, 'addOrdersToEntry'])
->name('add.orders.to.entry');
Route::get('/entry-orders/{entry_no}', [AdminAccountController::class, 'getEntryOrders'])
->name('entry.orders');
Route::get('/entry-orders/{entry_no}', [AdminAccountController::class, 'getEntryOrders'])
->name('entry.orders');
Route::post('/remove-order-from-entry', [AdminAccountController::class, 'removeOrderFromEntry'])
->name('remove.order.from.entry');
Route::post('/remove-order-from-entry', [AdminAccountController::class, 'removeOrderFromEntry'])
->name('remove.order.from.entry');
});
});
@@ -274,11 +278,10 @@ Route::prefix('admin')
// ---------------------------
// REPORTS DOWNLOAD ROUTES
// ---------------------------
Route::get('/admin/orders/download/pdf', [OrderController::class, 'downloadPdf'])->name('admin.orders.download.pdf');
Route::get('/admin/orders/download/excel', [OrderController::class, 'downloadExcel'])->name('admin.orders.download.excel');
Route::get('/admin/orders/download/pdf', [OrderController::class, 'downloadPdf'])->name('admin.orders.download.pdf');
Route::get('/admin/orders/download/excel', [OrderController::class, 'downloadExcel'])->name('admin.orders.download.excel');
//---------------------------
//Edit Button Route
//---------------------------