Compare commits
24 Commits
a14fe614e5
...
main
| Author | SHA256 | Date | |
|---|---|---|---|
|
|
0a65d5f596 | ||
|
|
0a1d0a9c55 | ||
|
|
409a854d7b | ||
|
|
4dab96b8d1 | ||
|
|
e7fef314fc | ||
|
|
5114357ff2 | ||
|
|
0afcb23511 | ||
|
|
340c2b2132 | ||
|
|
44b8299b0e | ||
|
|
9b8c50fcec | ||
|
|
7a814dff1d | ||
|
|
f4730a81d8 | ||
|
|
3b24ee860a | ||
|
|
2dcd9fe332 | ||
|
|
922539844d | ||
|
|
3845972c5c | ||
|
|
64d8939208 | ||
|
|
ec2a0baceb | ||
|
|
68bfd180ed | ||
|
|
aa616fcf61 | ||
|
|
178fbb224c | ||
|
|
97db70c40e | ||
|
|
04b00c9db8 | ||
|
|
bebe0711f4 |
98
app/Exports/OrdersExport.php
Normal file
98
app/Exports/OrdersExport.php
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Exports;
|
||||||
|
|
||||||
|
use App\Models\Order;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Maatwebsite\Excel\Concerns\FromCollection;
|
||||||
|
use Maatwebsite\Excel\Concerns\WithHeadings;
|
||||||
|
|
||||||
|
class OrdersExport implements FromCollection, WithHeadings
|
||||||
|
{
|
||||||
|
protected $request;
|
||||||
|
|
||||||
|
public function __construct(Request $request)
|
||||||
|
{
|
||||||
|
$this->request = $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildQuery()
|
||||||
|
{
|
||||||
|
$query = Order::with(['markList', 'invoice', 'shipments']);
|
||||||
|
|
||||||
|
if ($this->request->filled('search')) {
|
||||||
|
$search = $this->request->search;
|
||||||
|
$query->where(function($q) use ($search) {
|
||||||
|
$q->where('order_id', 'like', "%{$search}%")
|
||||||
|
->orWhereHas('markList', function($q2) use ($search) {
|
||||||
|
$q2->where('company_name', 'like', "%{$search}%")
|
||||||
|
->orWhere('customer_id', 'like', "%{$search}%");
|
||||||
|
})
|
||||||
|
->orWhereHas('invoice', function($q3) use ($search) {
|
||||||
|
$q3->where('invoice_number', 'like', "%{$search}%");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->filled('status')) {
|
||||||
|
$query->whereHas('invoice', function($q) {
|
||||||
|
$q->where('status', $this->request->status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->request->filled('shipment')) {
|
||||||
|
$query->whereHas('shipments', function($q) {
|
||||||
|
$q->where('status', $this->request->shipment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->latest('id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function collection()
|
||||||
|
{
|
||||||
|
$orders = $this->buildQuery()->get();
|
||||||
|
|
||||||
|
// Map to simple array rows suitable for Excel
|
||||||
|
return $orders->map(function($order) {
|
||||||
|
$mark = $order->markList;
|
||||||
|
$invoice = $order->invoice;
|
||||||
|
$shipment = $order->shipments->first() ?? null;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Order ID' => $order->order_id,
|
||||||
|
'Shipment ID' => $shipment->shipment_id ?? '-',
|
||||||
|
'Customer ID' => $mark->customer_id ?? '-',
|
||||||
|
'Company' => $mark->company_name ?? '-',
|
||||||
|
'Origin' => $mark->origin ?? $order->origin ?? '-',
|
||||||
|
'Destination' => $mark->destination ?? $order->destination ?? '-',
|
||||||
|
'Order Date' => $order->created_at ? $order->created_at->format('d-m-Y') : '-',
|
||||||
|
'Invoice No' => $invoice->invoice_number ?? '-',
|
||||||
|
'Invoice Date' => $invoice?->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('d-m-Y') : '-',
|
||||||
|
'Amount' => $invoice?->final_amount ? number_format($invoice->final_amount, 2) : '-',
|
||||||
|
'Amount + GST' => $invoice?->final_amount_with_gst ? number_format($invoice->final_amount_with_gst, 2) : '-',
|
||||||
|
'Invoice Status' => $invoice->status ? ucfirst($invoice->status) : 'Pending',
|
||||||
|
'Shipment Status' => $shipment?->status ? ucfirst(str_replace('_', ' ', $shipment->status)) : 'Pending',
|
||||||
|
];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function headings(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Order ID',
|
||||||
|
'Shipment ID',
|
||||||
|
'Customer ID',
|
||||||
|
'Company',
|
||||||
|
'Origin',
|
||||||
|
'Destination',
|
||||||
|
'Order Date',
|
||||||
|
'Invoice No',
|
||||||
|
'Invoice Date',
|
||||||
|
'Amount',
|
||||||
|
'Amount + GST',
|
||||||
|
'Invoice Status',
|
||||||
|
'Shipment Status',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,48 @@ use Illuminate\Support\Facades\DB;
|
|||||||
|
|
||||||
class AdminAccountController extends Controller
|
class AdminAccountController extends Controller
|
||||||
{
|
{
|
||||||
|
public function updateEntry(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = $request->validate([
|
||||||
|
'entry_no' => 'required|exists:entries,entry_no',
|
||||||
|
'description' => 'required|string|max:255',
|
||||||
|
'order_quantity' => 'required|numeric|min:0',
|
||||||
|
'region' => 'required|string|max:50',
|
||||||
|
'amount' => 'required|numeric|min:0',
|
||||||
|
//'payment_status' => 'required|string|max:50',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$entry = Entry::where('entry_no', $data['entry_no'])->first();
|
||||||
|
|
||||||
|
if (!$entry) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Entry not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry->description = $data['description'];
|
||||||
|
$entry->order_quantity = $data['order_quantity'];
|
||||||
|
$entry->region = $data['region'];
|
||||||
|
$entry->amount = $data['amount'];
|
||||||
|
//$entry->payment_status = $data['payment_status'];
|
||||||
|
|
||||||
|
$entry->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Entry updated successfully.',
|
||||||
|
'entry' => $entry,
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Server error: '.$e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 🚀 1. Get dashboard entries
|
* 🚀 1. Get dashboard entries
|
||||||
*/
|
*/
|
||||||
@@ -31,15 +73,17 @@ class AdminAccountController extends Controller
|
|||||||
*/
|
*/
|
||||||
public function getAvailableOrders()
|
public function getAvailableOrders()
|
||||||
{
|
{
|
||||||
$orders = Order::whereDoesntHave('entries')
|
|
||||||
->orderBy('id', 'desc')
|
|
||||||
->get();
|
|
||||||
|
|
||||||
|
$orders = Order::whereDoesntHave('entries')
|
||||||
|
->orderBy('id', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
'orders' => $orders
|
'orders' => $orders,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 🚀 3. Create new entry
|
* 🚀 3. Create new entry
|
||||||
@@ -245,4 +289,111 @@ class AdminAccountController extends Controller
|
|||||||
'pending' => $entry->pending_amount,
|
'pending' => $entry->pending_amount,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//--------------------------
|
||||||
|
//add order Entry
|
||||||
|
//--------------------------
|
||||||
|
public function addOrdersToEntry(Request $request)
|
||||||
|
{
|
||||||
|
$data = $request->validate([
|
||||||
|
'entry_no' => 'required|exists:entries,entry_no',
|
||||||
|
'order_ids' => 'required|array',
|
||||||
|
'order_ids.*' => 'integer|exists:orders,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($data) {
|
||||||
|
$entry = Entry::where('entry_no', $data['entry_no'])->firstOrFail();
|
||||||
|
|
||||||
|
|
||||||
|
$entry->orders()->syncWithoutDetaching($data['order_ids']);
|
||||||
|
|
||||||
|
$entry->order_quantity = $entry->orders()->count();
|
||||||
|
$entry->save();
|
||||||
|
|
||||||
|
$entry->load('orders');
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Orders added successfully.',
|
||||||
|
'entry' => $entry,
|
||||||
|
]);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function getEntryOrders($entry_no)
|
||||||
|
{
|
||||||
|
$entry = Entry::where('entry_no', $entry_no)
|
||||||
|
->with('orders')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$entry) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Entry not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'orders' => $entry->orders,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function removeOrderFromEntry(Request $request)
|
||||||
|
{
|
||||||
|
$data = $request->validate([
|
||||||
|
'entry_no' => 'required|exists:entries,entry_no',
|
||||||
|
'order_id' => 'required|integer|exists:orders,id',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return DB::transaction(function () use ($data) {
|
||||||
|
$entry = Entry::where('entry_no', $data['entry_no'])->firstOrFail();
|
||||||
|
|
||||||
|
// order detach करा
|
||||||
|
$entry->orders()->detach($data['order_id']);
|
||||||
|
|
||||||
|
// इथे quantity auto update
|
||||||
|
$entry->order_quantity = $entry->orders()->count();
|
||||||
|
$entry->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Order removed successfully.',
|
||||||
|
'entry' => $entry,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
public function deleteEntry(Request $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$data = $request->validate([
|
||||||
|
'entry_no' => 'required|exists:entries,entry_no',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$entry = Entry::where('entry_no', $data['entry_no'])->first();
|
||||||
|
|
||||||
|
if (!$entry) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Entry not found.',
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$entry->delete();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Entry deleted successfully.',
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Server error: '.$e->getMessage(),
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,50 +5,52 @@ namespace App\Http\Controllers\Admin;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use App\Models\Admin;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AdminAuthController extends Controller
|
class AdminAuthController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Show the admin login page
|
|
||||||
*/
|
|
||||||
public function showLoginForm()
|
public function showLoginForm()
|
||||||
{
|
{
|
||||||
return view('admin.login');
|
return view('admin.login');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle admin login
|
|
||||||
*/
|
|
||||||
public function login(Request $request)
|
public function login(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'email' => 'required|email',
|
'login' => 'required',
|
||||||
'password' => 'required|string|min:6',
|
'password' => 'required|string|min:6',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Try to log in using the 'admin' guard
|
$loginInput = $request->input('login');
|
||||||
if (Auth::guard('admin')->attempt($request->only('email', 'password'))) {
|
|
||||||
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, Admin!');
|
if (filter_var($loginInput, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$field = 'email';
|
||||||
|
} elseif (preg_match('/^EMP\d+$/i', $loginInput)) {
|
||||||
|
$field = 'employee_id';
|
||||||
|
} else {
|
||||||
|
$field = 'username';
|
||||||
}
|
}
|
||||||
|
|
||||||
return back()->withErrors(['email' => 'Invalid email or password.']);
|
$credentials = [
|
||||||
|
$field => $loginInput,
|
||||||
|
'password' => $request->password,
|
||||||
|
];
|
||||||
|
|
||||||
|
// attempt login
|
||||||
|
if (Auth::guard('admin')->attempt($credentials)) {
|
||||||
|
$request->session()->regenerate();
|
||||||
|
$user = Auth::guard('admin')->user();
|
||||||
|
|
||||||
|
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, ' . $user->name . '!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->withErrors(['login' => 'Invalid login credentials.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Logout admin
|
|
||||||
*/
|
|
||||||
public function logout(Request $request)
|
public function logout(Request $request)
|
||||||
{
|
{
|
||||||
Auth::guard('admin')->logout();
|
Auth::guard('admin')->logout();
|
||||||
|
|
||||||
// Destroy the session completely
|
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
|
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,36 +9,42 @@ use Illuminate\Support\Facades\Hash;
|
|||||||
|
|
||||||
class AdminCustomerController extends Controller
|
class AdminCustomerController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// LIST CUSTOMERS (with search + status filter)
|
// LIST CUSTOMERS (with search + status filter)
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$search = $request->search;
|
$search = $request->search;
|
||||||
$status = $request->status;
|
$status = $request->status;
|
||||||
|
|
||||||
$query = User::with(['marks', 'orders'])->orderBy('id', 'desc');
|
$query = User::with(['marks', 'orders'])->orderBy('id', 'desc');
|
||||||
|
|
||||||
// SEARCH FILTER
|
// SEARCH FILTER
|
||||||
if (!empty($search)) {
|
if (!empty($search)) {
|
||||||
$query->where(function ($q) use ($search) {
|
$query->where(function ($q) use ($search) {
|
||||||
$q->where('customer_name', 'like', "%$search%")
|
$q->where('customer_name', 'like', "%$search%")
|
||||||
->orWhere('email', 'like', "%$search%")
|
->orWhere('email', 'like', "%$search%")
|
||||||
->orWhere('mobile_no', 'like', "%$search%")
|
->orWhere('mobile_no', 'like', "%$search%")
|
||||||
->orWhere('customer_id', 'like', "%$search%");
|
->orWhere('customer_id', 'like', "%$search%");
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// STATUS FILTER
|
|
||||||
if (!empty($status) && in_array($status, ['active', 'inactive'])) {
|
|
||||||
$query->where('status', $status);
|
|
||||||
}
|
|
||||||
|
|
||||||
$customers = $query->get();
|
|
||||||
|
|
||||||
return view('admin.customers', compact('customers', 'search', 'status'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// STATUS FILTER
|
||||||
|
if (!empty($status) && in_array($status, ['active', 'inactive'])) {
|
||||||
|
$query->where('status', $status);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all customers for statistics (without pagination)
|
||||||
|
$allCustomers = $query->get();
|
||||||
|
|
||||||
|
// Get paginated customers for the table (10 per page)
|
||||||
|
$customers = $query->paginate(10);
|
||||||
|
|
||||||
|
return view('admin.customers', compact('customers', 'allCustomers', 'search', 'status'));
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// SHOW ADD CUSTOMER FORM
|
// SHOW ADD CUSTOMER FORM
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
@@ -130,5 +136,6 @@ class AdminCustomerController extends Controller
|
|||||||
|
|
||||||
return back()->with('success', 'Customer status updated.');
|
return back()->with('success', 'Customer status updated.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ use Illuminate\Http\Request;
|
|||||||
use App\Models\Invoice;
|
use App\Models\Invoice;
|
||||||
use App\Models\InvoiceItem;
|
use App\Models\InvoiceItem;
|
||||||
use Mpdf\Mpdf;
|
use Mpdf\Mpdf;
|
||||||
|
use App\Models\InvoiceInstallment;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
class AdminInvoiceController extends Controller
|
class AdminInvoiceController extends Controller
|
||||||
{
|
{
|
||||||
@@ -15,7 +17,7 @@ class AdminInvoiceController extends Controller
|
|||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
$invoices = Invoice::latest()->get();
|
$invoices = Invoice::with(['order.shipments'])->latest()->get();
|
||||||
return view('admin.invoice', compact('invoices'));
|
return view('admin.invoice', compact('invoices'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,8 +26,15 @@ class AdminInvoiceController extends Controller
|
|||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function popup($id)
|
public function popup($id)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::with('items')->findOrFail($id);
|
$invoice = Invoice::with(['items', 'order'])->findOrFail($id);
|
||||||
return view('admin.popup_invoice', compact('invoice'));
|
|
||||||
|
// Find actual Shipment record
|
||||||
|
$shipment = \App\Models\Shipment::whereHas('items', function ($q) use ($invoice) {
|
||||||
|
$q->where('order_id', $invoice->order_id);
|
||||||
|
})
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return view('admin.popup_invoice', compact('invoice', 'shipment'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -33,8 +42,10 @@ class AdminInvoiceController extends Controller
|
|||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function edit($id)
|
public function edit($id)
|
||||||
{
|
{
|
||||||
$invoice = Invoice::findOrFail($id);
|
$invoice = Invoice::with(['order.shipments'])->findOrFail($id);
|
||||||
return view('admin.invoice_edit', compact('invoice'));
|
$shipment = $invoice->order?->shipments?->first();
|
||||||
|
|
||||||
|
return view('admin.invoice_edit', compact('invoice', 'shipment'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
@@ -42,31 +53,65 @@ class AdminInvoiceController extends Controller
|
|||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function update(Request $request, $id)
|
public function update(Request $request, $id)
|
||||||
{
|
{
|
||||||
|
Log::info("🟡 Invoice Update Request Received", [
|
||||||
|
'invoice_id' => $id,
|
||||||
|
'request' => $request->all()
|
||||||
|
]);
|
||||||
|
|
||||||
$invoice = Invoice::findOrFail($id);
|
$invoice = Invoice::findOrFail($id);
|
||||||
|
|
||||||
// Validate editable fields
|
|
||||||
$data = $request->validate([
|
$data = $request->validate([
|
||||||
'invoice_date' => 'required|date',
|
'invoice_date' => 'required|date',
|
||||||
'due_date' => 'required|date|after_or_equal:invoice_date',
|
'due_date' => 'required|date|after_or_equal:invoice_date',
|
||||||
'payment_method' => 'nullable|string',
|
|
||||||
'reference_no' => 'nullable|string|max:255',
|
|
||||||
'final_amount' => 'required|numeric|min:0',
|
'final_amount' => 'required|numeric|min:0',
|
||||||
'gst_percent' => 'required|numeric|min:0|max:28',
|
'tax_type' => 'required|in:gst,igst',
|
||||||
|
'tax_percent' => 'required|numeric|min:0|max:28',
|
||||||
'status' => 'required|in:pending,paid,overdue',
|
'status' => 'required|in:pending,paid,overdue',
|
||||||
'notes' => 'nullable|string',
|
'notes' => 'nullable|string',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Auto-calc
|
Log::info("✅ Validated Invoice Update Data", $data);
|
||||||
$gst_amount = ($data['final_amount'] * $data['gst_percent']) / 100;
|
|
||||||
$final_amount_with_gst = $data['final_amount'] + $gst_amount;
|
|
||||||
|
|
||||||
$data['gst_amount'] = $gst_amount;
|
$finalAmount = floatval($data['final_amount']);
|
||||||
$data['final_amount_with_gst'] = $final_amount_with_gst;
|
$taxPercent = floatval($data['tax_percent']);
|
||||||
|
$taxAmount = 0;
|
||||||
|
|
||||||
|
if ($data['tax_type'] === 'gst') {
|
||||||
|
Log::info("🟢 GST Selected", compact('taxPercent'));
|
||||||
|
$data['cgst_percent'] = $taxPercent / 2;
|
||||||
|
$data['sgst_percent'] = $taxPercent / 2;
|
||||||
|
$data['igst_percent'] = 0;
|
||||||
|
} else {
|
||||||
|
Log::info("🔵 IGST Selected", compact('taxPercent'));
|
||||||
|
$data['cgst_percent'] = 0;
|
||||||
|
$data['sgst_percent'] = 0;
|
||||||
|
$data['igst_percent'] = $taxPercent;
|
||||||
|
}
|
||||||
|
|
||||||
|
$taxAmount = ($finalAmount * $taxPercent) / 100;
|
||||||
|
|
||||||
|
$data['gst_amount'] = $taxAmount;
|
||||||
|
$data['final_amount_with_gst'] = $finalAmount + $taxAmount;
|
||||||
|
$data['gst_percent'] = $taxPercent;
|
||||||
|
|
||||||
|
Log::info("📌 Final Calculated Invoice Values", [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'final_amount' => $finalAmount,
|
||||||
|
'gst_amount' => $data['gst_amount'],
|
||||||
|
'final_amount_with_gst' => $data['final_amount_with_gst'],
|
||||||
|
'tax_type' => $data['tax_type'],
|
||||||
|
'cgst_percent' => $data['cgst_percent'],
|
||||||
|
'sgst_percent' => $data['sgst_percent'],
|
||||||
|
'igst_percent' => $data['igst_percent'],
|
||||||
|
]);
|
||||||
|
|
||||||
// Update DB
|
|
||||||
$invoice->update($data);
|
$invoice->update($data);
|
||||||
|
|
||||||
// Generate PDF
|
Log::info("✅ Invoice Updated Successfully", [
|
||||||
|
'invoice_id' => $invoice->id
|
||||||
|
]);
|
||||||
|
|
||||||
|
// regenerate PDF
|
||||||
$this->generateInvoicePDF($invoice);
|
$this->generateInvoicePDF($invoice);
|
||||||
|
|
||||||
return redirect()
|
return redirect()
|
||||||
@@ -79,44 +124,104 @@ class AdminInvoiceController extends Controller
|
|||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
public function generateInvoicePDF($invoice)
|
public function generateInvoicePDF($invoice)
|
||||||
{
|
{
|
||||||
// PDF Name
|
$invoice->load(['items', 'order.shipments']);
|
||||||
|
$shipment = $invoice->order?->shipments?->first();
|
||||||
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
|
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
|
||||||
|
|
||||||
// Save directly in /public/invoices
|
|
||||||
$folder = public_path('invoices/');
|
$folder = public_path('invoices/');
|
||||||
|
|
||||||
// Create folder if not exists
|
|
||||||
if (!file_exists($folder)) {
|
if (!file_exists($folder)) {
|
||||||
mkdir($folder, 0777, true);
|
mkdir($folder, 0777, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Full path
|
|
||||||
$filePath = $folder . $fileName;
|
$filePath = $folder . $fileName;
|
||||||
|
|
||||||
// Delete old file
|
|
||||||
if (file_exists($filePath)) {
|
if (file_exists($filePath)) {
|
||||||
unlink($filePath);
|
unlink($filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize mPDF
|
$mpdf = new Mpdf(['mode' => 'utf-8', 'format' => 'A4', 'default_font' => 'sans-serif']);
|
||||||
$mpdf = new Mpdf([
|
$html = view('admin.pdf.invoice', ['invoice' => $invoice, 'shipment' => $shipment])->render();
|
||||||
'mode' => 'utf-8',
|
$mpdf->WriteHTML($html);
|
||||||
'format' => 'A4',
|
$mpdf->Output($filePath, 'F');
|
||||||
'default_font' => 'sans-serif'
|
$invoice->update(['pdf_path' => 'invoices/' . $fileName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// INSTALLMENTS (ADD/DELETE)
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
public function storeInstallment(Request $request, $invoice_id)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
'installment_date' => 'required|date',
|
||||||
|
'payment_method' => 'required|string',
|
||||||
|
'reference_no' => 'nullable|string',
|
||||||
|
'amount' => 'required|numeric|min:1',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Load HTML view
|
$invoice = Invoice::findOrFail($invoice_id);
|
||||||
$html = view('admin.pdf.invoice', compact('invoice'))->render();
|
|
||||||
|
|
||||||
// Generate
|
$paidTotal = $invoice->installments()->sum('amount');
|
||||||
$mpdf->WriteHTML($html);
|
// Use GST-inclusive total for all calculations/checks
|
||||||
|
$remaining = $invoice->final_amount_with_gst - $paidTotal;
|
||||||
|
|
||||||
// Save to public/invoices
|
if ($request->amount > $remaining) {
|
||||||
$mpdf->Output($filePath, 'F');
|
return response()->json([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => 'Installment amount exceeds remaining balance.'
|
||||||
|
], 422);
|
||||||
|
}
|
||||||
|
|
||||||
// Save path in DB
|
$installment = InvoiceInstallment::create([
|
||||||
$invoice->update([
|
'invoice_id' => $invoice_id,
|
||||||
'pdf_path' => 'invoices/' . $fileName
|
'installment_date' => $request->installment_date,
|
||||||
|
'payment_method' => $request->payment_method,
|
||||||
|
'reference_no' => $request->reference_no,
|
||||||
|
'amount' => $request->amount,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newPaid = $paidTotal + $request->amount;
|
||||||
|
|
||||||
|
// Mark as 'paid' if GST-inclusive total is cleared
|
||||||
|
if ($newPaid >= $invoice->final_amount_with_gst) {
|
||||||
|
$invoice->update(['status' => 'paid']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Installment added successfully.',
|
||||||
|
'installment' => $installment,
|
||||||
|
'totalPaid' => $newPaid,
|
||||||
|
'gstAmount' => $invoice->gst_amount,
|
||||||
|
'finalAmountWithGst' => $invoice->final_amount_with_gst,
|
||||||
|
'baseAmount' => $invoice->final_amount,
|
||||||
|
'remaining' => max(0, $invoice->final_amount_with_gst - $newPaid),
|
||||||
|
'isCompleted' => $newPaid >= $invoice->final_amount_with_gst
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
public function deleteInstallment($id)
|
||||||
|
{
|
||||||
|
$installment = InvoiceInstallment::findOrFail($id);
|
||||||
|
$invoice = $installment->invoice;
|
||||||
|
|
||||||
|
$installment->delete();
|
||||||
|
|
||||||
|
$paidTotal = $invoice->installments()->sum('amount');
|
||||||
|
$remaining = $invoice->final_amount_with_gst - $paidTotal;
|
||||||
|
|
||||||
|
// Update status if not fully paid anymore
|
||||||
|
if ($remaining > 0 && $invoice->status === "paid") {
|
||||||
|
$invoice->update(['status' => 'pending']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => 'success',
|
||||||
|
'message' => 'Installment deleted.',
|
||||||
|
'totalPaid' => $paidTotal,
|
||||||
|
'gstAmount' => $invoice->gst_amount,
|
||||||
|
'finalAmountWithGst' => $invoice->final_amount_with_gst,
|
||||||
|
'baseAmount' => $invoice->final_amount,
|
||||||
|
'remaining' => $remaining,
|
||||||
|
'isZero' => $paidTotal == 0
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,20 +7,458 @@ use Illuminate\Http\Request;
|
|||||||
use App\Models\Order;
|
use App\Models\Order;
|
||||||
use App\Models\OrderItem;
|
use App\Models\OrderItem;
|
||||||
use App\Models\MarkList;
|
use App\Models\MarkList;
|
||||||
|
use App\Models\Invoice;
|
||||||
|
use App\Models\InvoiceItem;
|
||||||
|
use App\Models\User;
|
||||||
|
use PDF; // barryvdh/laravel-dompdf facade
|
||||||
|
use Maatwebsite\Excel\Facades\Excel;
|
||||||
|
use App\Exports\OrdersExport;
|
||||||
|
|
||||||
|
|
||||||
class AdminOrderController extends Controller
|
class AdminOrderController extends Controller
|
||||||
{
|
{
|
||||||
|
// ---------------------------
|
||||||
|
// LIST / DASHBOARD
|
||||||
|
// ---------------------------
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
|
// raw list for admin dashboard (simple)
|
||||||
$orders = Order::latest()->get();
|
$orders = Order::latest()->get();
|
||||||
$markList = MarkList::where('status', 'active')->get();
|
$markList = MarkList::where('status', 'active')->get();
|
||||||
|
|
||||||
return view('admin.dashboard', compact('orders', 'markList'));
|
return view('admin.dashboard', compact('orders', 'markList'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
/**
|
||||||
// STEP 1 : ADD TEMPORARY ITEM
|
* 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)
|
||||||
|
*/
|
||||||
|
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',
|
||||||
|
'ttl_amount' => 'nullable|numeric',
|
||||||
|
'cbm' => 'nullable|numeric',
|
||||||
|
'ttl_cbm' => 'nullable|numeric',
|
||||||
|
'kg' => 'nullable|numeric',
|
||||||
|
'ttl_kg' => 'nullable|numeric',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$order = Order::create([
|
||||||
|
'order_id' => $this->generateOrderId(),
|
||||||
|
'mark_no' => $data['mark_no'],
|
||||||
|
'origin' => $data['origin'] ?? null,
|
||||||
|
'destination' => $data['destination'] ?? null,
|
||||||
|
'ctn' => $data['ctn'] ?? 0,
|
||||||
|
'qty' => $data['qty'] ?? 0,
|
||||||
|
'ttl_qty' => $data['ttl_qty'] ?? 0,
|
||||||
|
'ttl_amount' => $data['ttl_amount'] ?? 0,
|
||||||
|
'cbm' => $data['cbm'] ?? 0,
|
||||||
|
'ttl_cbm' => $data['ttl_cbm'] ?? 0,
|
||||||
|
'kg' => $data['kg'] ?? 0,
|
||||||
|
'ttl_kg' => $data['ttl_kg'] ?? 0,
|
||||||
|
'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
|
||||||
|
// ---------------------------
|
||||||
|
public function show($id)
|
||||||
|
{
|
||||||
|
$order = Order::with('items', 'markList')->findOrFail($id);
|
||||||
|
$user = $this->getCustomerFromOrder($order);
|
||||||
|
|
||||||
|
return view('admin.orders_show', compact('order', 'user'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// public function popup($id)
|
||||||
|
// {
|
||||||
|
// $order = Order::with(['items', 'markList'])->findOrFail($id);
|
||||||
|
// $user = $this->getCustomerFromOrder($order);
|
||||||
|
|
||||||
|
// return view('admin.popup', compact('order', 'user'));
|
||||||
|
// }
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// ORDER ITEM MANAGEMENT (DB)
|
||||||
|
// ---------------------------
|
||||||
|
/**
|
||||||
|
* Add an item to an existing order
|
||||||
|
*/
|
||||||
|
public function addItem(Request $request, $orderId)
|
||||||
|
{
|
||||||
|
$order = Order::findOrFail($orderId);
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'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',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$data['order_id'] = $order->id;
|
||||||
|
|
||||||
|
OrderItem::create($data);
|
||||||
|
|
||||||
|
// recalc totals and save to order
|
||||||
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order); // <-- NEW
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
$order = Order::findOrFail($item->order_id);
|
||||||
|
|
||||||
|
$item->restore();
|
||||||
|
|
||||||
|
// recalc totals
|
||||||
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// ORDER CRUD: update / destroy
|
||||||
|
// ---------------------------
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$order = Order::findOrFail($id);
|
||||||
|
|
||||||
|
$data = $request->validate([
|
||||||
|
'mark_no' => 'required|string',
|
||||||
|
'origin' => 'nullable|string',
|
||||||
|
'destination' => 'nullable|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
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)),
|
||||||
|
'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)),
|
||||||
|
'ttl_kg' => (float) $items->sum(fn($i) => (float) ($i->ttl_kg ?? 0)),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate order id (keeps old format)
|
||||||
|
*/
|
||||||
|
private function generateOrderId()
|
||||||
|
{
|
||||||
|
$year = date('y');
|
||||||
|
$prefix = "KNT-$year-";
|
||||||
|
|
||||||
|
$lastOrder = Order::latest('id')->first();
|
||||||
|
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
|
||||||
|
|
||||||
|
return $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// INVOICE CREATION (optional helper used by store/finish)
|
||||||
|
// ---------------------------
|
||||||
|
private function createInvoice(Order $order)
|
||||||
|
{
|
||||||
|
$invoiceNumber = $this->generateInvoiceNumber();
|
||||||
|
$customer = $this->getCustomerFromMarkList($order->mark_no);
|
||||||
|
$totalAmount = $order->ttl_amount;
|
||||||
|
|
||||||
|
$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' => $totalAmount,
|
||||||
|
'gst_percent' => 0,
|
||||||
|
'gst_amount' => 0,
|
||||||
|
'final_amount_with_gst' => $totalAmount,
|
||||||
|
'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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// clone order items into invoice items
|
||||||
|
foreach ($order->items as $item) {
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function generateInvoiceNumber()
|
||||||
|
{
|
||||||
|
$lastInvoice = Invoice::latest()->first();
|
||||||
|
$nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1;
|
||||||
|
|
||||||
|
return 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomerFromMarkList($markNo)
|
||||||
|
{
|
||||||
|
$markList = MarkList::where('mark_no', $markNo)->first();
|
||||||
|
|
||||||
|
if ($markList && $markList->customer_id) {
|
||||||
|
return User::where('customer_id', $markList->customer_id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getCustomerFromOrder($order)
|
||||||
|
{
|
||||||
|
if ($order->markList && $order->markList->customer_id) {
|
||||||
|
return User::where('customer_id', $order->markList->customer_id)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
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'));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
return view('admin.orders', compact('orders'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// inside AdminOrderController
|
||||||
|
|
||||||
|
private function buildOrdersQueryFromRequest(Request $request)
|
||||||
|
{
|
||||||
|
$query = Order::with(['markList', 'invoice', 'shipments']);
|
||||||
|
|
||||||
|
// Search across order_id, markList.company_name, markList.customer_id, invoice.invoice_number
|
||||||
|
if ($request->filled('search')) {
|
||||||
|
$search = $request->search;
|
||||||
|
$query->where(function($q) use ($search) {
|
||||||
|
$q->where('order_id', 'like', "%{$search}%")
|
||||||
|
->orWhereHas('markList', function($q2) use ($search) {
|
||||||
|
$q2->where('company_name', 'like', "%{$search}%")
|
||||||
|
->orWhere('customer_id', 'like', "%{$search}%");
|
||||||
|
})
|
||||||
|
->orWhereHas('invoice', function($q3) use ($search) {
|
||||||
|
$q3->where('invoice_number', 'like', "%{$search}%");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoice status filter
|
||||||
|
if ($request->filled('status')) {
|
||||||
|
$query->whereHas('invoice', function($q) use ($request) {
|
||||||
|
$q->where('status', $request->status);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shipment status filter
|
||||||
|
if ($request->filled('shipment')) {
|
||||||
|
$query->whereHas('shipments', function($q) use ($request) {
|
||||||
|
$q->where('status', $request->shipment);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional ordering
|
||||||
|
$query->latest('id');
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function addTempItem(Request $request)
|
public function addTempItem(Request $request)
|
||||||
{
|
{
|
||||||
@@ -67,31 +505,8 @@ class AdminOrderController extends Controller
|
|||||||
session()->push('temp_order_items', $item);
|
session()->push('temp_order_items', $item);
|
||||||
|
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
||||||
->with('success', 'Item added.');
|
|
||||||
}
|
->with('success', 'Item added.');
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// STEP 2 : DELETE TEMPORARY ITEM
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
public function deleteTempItem(Request $request)
|
|
||||||
{
|
|
||||||
$index = $request->index;
|
|
||||||
|
|
||||||
$items = session('temp_order_items', []);
|
|
||||||
|
|
||||||
if (isset($items[$index])) {
|
|
||||||
unset($items[$index]);
|
|
||||||
session(['temp_order_items' => array_values($items)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no items left → reset mark_no lock
|
|
||||||
if (empty($items)) {
|
|
||||||
session()->forget(['mark_no', 'origin', 'destination']);
|
|
||||||
}
|
|
||||||
|
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
|
||||||
->with('success', 'Item removed successfully.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@@ -99,202 +514,228 @@ class AdminOrderController extends Controller
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
public function finishOrder(Request $request)
|
public function finishOrder(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'mark_no' => 'required',
|
'mark_no' => 'required',
|
||||||
'origin' => 'required',
|
'origin' => 'required',
|
||||||
'destination' => 'required',
|
'destination' => 'required',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$items = session('temp_order_items', []);
|
$items = session('temp_order_items', []);
|
||||||
|
|
||||||
if (empty($items)) {
|
if (empty($items)) {
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
||||||
->with('error', 'Add at least one item before finishing.');
|
->with('error', 'Add at least one item before finishing.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
// GENERATE ORDER ID
|
// GENERATE ORDER ID
|
||||||
// =======================
|
// =======================
|
||||||
$year = date('y');
|
$year = date('y');
|
||||||
$prefix = "KNT-$year-";
|
$prefix = "KNT-$year-";
|
||||||
|
|
||||||
$lastOrder = Order::latest('id')->first();
|
$lastOrder = Order::latest('id')->first();
|
||||||
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
|
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
|
||||||
|
|
||||||
$orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
$orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
// TOTAL SUMS
|
// TOTAL SUMS
|
||||||
// =======================
|
// =======================
|
||||||
$total_ctn = array_sum(array_column($items, 'ctn'));
|
$total_ctn = array_sum(array_column($items, 'ctn'));
|
||||||
$total_qty = array_sum(array_column($items, 'qty'));
|
$total_qty = array_sum(array_column($items, 'qty'));
|
||||||
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
|
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
|
||||||
$total_amount = array_sum(array_column($items, 'ttl_amount'));
|
$total_amount = array_sum(array_column($items, 'ttl_amount'));
|
||||||
$total_cbm = array_sum(array_column($items, 'cbm'));
|
$total_cbm = array_sum(array_column($items, 'cbm'));
|
||||||
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
|
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
|
||||||
$total_kg = array_sum(array_column($items, 'kg'));
|
$total_kg = array_sum(array_column($items, 'kg'));
|
||||||
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
|
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
// CREATE ORDER
|
// CREATE ORDER
|
||||||
// =======================
|
// =======================
|
||||||
$order = Order::create([
|
$order = Order::create([
|
||||||
'order_id' => $orderId,
|
'order_id' => $orderId,
|
||||||
'mark_no' => $request->mark_no,
|
'mark_no' => $request->mark_no,
|
||||||
'origin' => $request->origin,
|
'origin' => $request->origin,
|
||||||
'destination' => $request->destination,
|
'destination' => $request->destination,
|
||||||
'ctn' => $total_ctn,
|
'ctn' => $total_ctn,
|
||||||
'qty' => $total_qty,
|
'qty' => $total_qty,
|
||||||
'ttl_qty' => $total_ttl_qty,
|
'ttl_qty' => $total_ttl_qty,
|
||||||
'ttl_amount' => $total_amount,
|
'ttl_amount' => $total_amount,
|
||||||
'cbm' => $total_cbm,
|
'cbm' => $total_cbm,
|
||||||
'ttl_cbm' => $total_ttl_cbm,
|
'ttl_cbm' => $total_ttl_cbm,
|
||||||
'kg' => $total_kg,
|
'kg' => $total_kg,
|
||||||
'ttl_kg' => $total_ttl_kg,
|
'ttl_kg' => $total_ttl_kg,
|
||||||
'status' => 'pending',
|
'status' => 'pending',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// SAVE ORDER ITEMS
|
// SAVE ORDER ITEMS
|
||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
OrderItem::create([
|
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,
|
'order_id' => $order->id,
|
||||||
'description' => $item['description'],
|
'customer_id' => $customer->id ?? null,
|
||||||
'ctn' => $item['ctn'],
|
'mark_no' => $order->mark_no,
|
||||||
'qty' => $item['qty'],
|
|
||||||
'ttl_qty' => $item['ttl_qty'],
|
'invoice_number' => $invoiceNumber,
|
||||||
'unit' => $item['unit'],
|
'invoice_date' => now(),
|
||||||
'price' => $item['price'],
|
'due_date' => now()->addDays(10),
|
||||||
'ttl_amount' => $item['ttl_amount'],
|
|
||||||
'cbm' => $item['cbm'],
|
'payment_method' => null,
|
||||||
'ttl_cbm' => $item['ttl_cbm'],
|
'reference_no' => null,
|
||||||
'kg' => $item['kg'],
|
'status' => 'pending',
|
||||||
'ttl_kg' => $item['ttl_kg'],
|
|
||||||
'shop_no' => $item['shop_no'],
|
'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
|
||||||
// INVOICE CREATION START
|
foreach ($order->items as $item) {
|
||||||
// =======================
|
\App\Models\InvoiceItem::create([
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
// 1. Auto-generate invoice number
|
'description' => $item->description,
|
||||||
$lastInvoice = \App\Models\Invoice::latest()->first();
|
'ctn' => $item->ctn,
|
||||||
$nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1;
|
'qty' => $item->qty,
|
||||||
$invoiceNumber = 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT);
|
'ttl_qty' => $item->ttl_qty,
|
||||||
|
'unit' => $item->unit,
|
||||||
// 2. Fetch customer (using mark list → customer_id)
|
'price' => $item->price,
|
||||||
$markList = MarkList::where('mark_no', $order->mark_no)->first();
|
'ttl_amount' => $item->ttl_amount,
|
||||||
$customer = null;
|
'cbm' => $item->cbm,
|
||||||
|
'ttl_cbm' => $item->ttl_cbm,
|
||||||
if ($markList && $markList->customer_id) {
|
'kg' => $item->kg,
|
||||||
$customer = \App\Models\User::where('customer_id', $markList->customer_id)->first();
|
'ttl_kg' => $item->ttl_kg,
|
||||||
}
|
'shop_no' => $item->shop_no,
|
||||||
|
]);
|
||||||
// 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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
// ORDER SHOW PAGE
|
|
||||||
// -------------------------------------------------------------------------
|
|
||||||
|
|
||||||
public function show($id)
|
|
||||||
{
|
|
||||||
$order = Order::with('items', 'markList')->findOrFail($id);
|
|
||||||
|
|
||||||
$user = null;
|
|
||||||
if ($order->markList && $order->markList->customer_id) {
|
|
||||||
$user = \App\Models\User::where('customer_id', $order->markList->customer_id)->first();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('admin.orders_show', compact('order', 'user'));
|
// 5. TODO: PDF generation (I will add this later)
|
||||||
}
|
$invoice->pdf_path = null; // placeholder for now
|
||||||
|
$invoice->save();
|
||||||
|
|
||||||
public function popup($id)
|
// =======================
|
||||||
{
|
// END INVOICE CREATION
|
||||||
// Load order with items + markList
|
// =======================
|
||||||
$order = Order::with(['items', 'markList'])->findOrFail($id);
|
|
||||||
|
|
||||||
// Fetch user based on markList customer_id (same as show method)
|
// CLEAR TEMP DATA
|
||||||
$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'));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public function resetTemp()
|
|
||||||
{
|
|
||||||
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
|
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
|
||||||
|
|
||||||
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
|
return redirect()->route('admin.orders.index')
|
||||||
->with('success', 'Order reset successfully.');
|
->with('success', 'Order + Invoice created successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// ---------------------------
|
||||||
|
// ORDER CRUD: update / destroy
|
||||||
|
// ---------------------------
|
||||||
|
public function updateItem(Request $request, $id)
|
||||||
|
{
|
||||||
|
$item = OrderItem::findOrFail($id);
|
||||||
|
$order = $item->order;
|
||||||
|
|
||||||
|
$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,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order); // <-- NEW
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
'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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
179
app/Http/Controllers/Admin/AdminStaffController.php
Normal file
179
app/Http/Controllers/Admin/AdminStaffController.php
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use App\Models\Admin;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class AdminStaffController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$staff = Admin::where('type', 'staff')->orderBy('id', 'DESC')->get();
|
||||||
|
return view('admin.staff.index', compact('staff'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$permissions = Permission::where('guard_name', 'admin')->get()->groupBy(function ($p) {
|
||||||
|
return explode('.', $p->name)[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
return view('admin.staff.create', compact('permissions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
// Personal Info
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:admins,email',
|
||||||
|
'phone' => 'required|string|max:20',
|
||||||
|
'emergency_phone' => 'nullable|string|max:20',
|
||||||
|
'address' => 'nullable|string|max:255',
|
||||||
|
|
||||||
|
// Professional info
|
||||||
|
'role' => 'nullable|string|max:100',
|
||||||
|
'department' => 'nullable|string|max:100',
|
||||||
|
'designation' => 'nullable|string|max:100',
|
||||||
|
'joining_date' => 'nullable|date',
|
||||||
|
'status' => 'required|string|in:active,inactive',
|
||||||
|
'additional_info' => 'nullable|string',
|
||||||
|
|
||||||
|
// System access
|
||||||
|
'username' => 'nullable|string|unique:admins,username',
|
||||||
|
'password' => 'required|string|min:6',
|
||||||
|
|
||||||
|
// Permissions
|
||||||
|
'permissions' => 'nullable|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$admin = Admin::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'phone' => $request->phone,
|
||||||
|
'emergency_phone' => $request->emergency_phone,
|
||||||
|
'address' => $request->address,
|
||||||
|
|
||||||
|
'role' => $request->role,
|
||||||
|
'department' => $request->department,
|
||||||
|
'designation' => $request->designation,
|
||||||
|
'joining_date' => $request->joining_date,
|
||||||
|
'status' => $request->status,
|
||||||
|
'additional_info' => $request->additional_info,
|
||||||
|
|
||||||
|
'username' => $request->username,
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
'type' => 'staff',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Generate EMPLOYEE ID using admin ID (safe)
|
||||||
|
$employeeId = 'EMP' . str_pad($admin->id, 4, '0', STR_PAD_LEFT);
|
||||||
|
$admin->update(['employee_id' => $employeeId]);
|
||||||
|
|
||||||
|
// Assign permissions (if any)
|
||||||
|
if ($request->permissions) {
|
||||||
|
$admin->givePermissionTo($request->permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->route('admin.staff.index')
|
||||||
|
->with('success', 'Staff created successfully.');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return back()->withErrors(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||||
|
|
||||||
|
$permissions = Permission::where('guard_name', 'admin')->get()->groupBy(function ($p) {
|
||||||
|
return explode('.', $p->name)[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
$staffPermissions = $staff->permissions->pluck('name')->toArray();
|
||||||
|
|
||||||
|
return view('admin.staff.edit', compact('staff', 'permissions', 'staffPermissions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:admins,email,' . $staff->id,
|
||||||
|
'phone' => 'required|string|max:20',
|
||||||
|
'emergency_phone' => 'nullable|string|max:20',
|
||||||
|
'address' => 'nullable|string|max:255',
|
||||||
|
|
||||||
|
'role' => 'nullable|string|max:100',
|
||||||
|
'department' => 'nullable|string|max:100',
|
||||||
|
'designation' => 'nullable|string|max:100',
|
||||||
|
'joining_date' => 'nullable|date',
|
||||||
|
'status' => 'required|string|in:active,inactive',
|
||||||
|
'additional_info' => 'nullable|string',
|
||||||
|
|
||||||
|
'username' => 'nullable|string|unique:admins,username,' . $staff->id,
|
||||||
|
'password' => 'nullable|string|min:6',
|
||||||
|
|
||||||
|
'permissions' => 'nullable|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$staff->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'phone' => $request->phone,
|
||||||
|
'emergency_phone' => $request->emergency_phone,
|
||||||
|
'address' => $request->address,
|
||||||
|
|
||||||
|
'role' => $request->role,
|
||||||
|
'department' => $request->department,
|
||||||
|
'designation' => $request->designation,
|
||||||
|
'joining_date' => $request->joining_date,
|
||||||
|
'status' => $request->status,
|
||||||
|
'additional_info' => $request->additional_info,
|
||||||
|
|
||||||
|
'username' => $request->username,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->password) {
|
||||||
|
$staff->update(['password' => Hash::make($request->password)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$staff->syncPermissions($request->permissions ?? []);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->route('admin.staff.index')
|
||||||
|
->with('success', 'Staff updated successfully.');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return back()->withErrors(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||||
|
$staff->delete();
|
||||||
|
|
||||||
|
return redirect()->route('admin.staff.index')
|
||||||
|
->with('success', 'Staff removed successfully.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ class ShipmentController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Show shipment page (Create Shipment + Shipment List)
|
* Show shipment page (Create Shipment + Shipment List)
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index()
|
||||||
{
|
{
|
||||||
// 1) Get all used order IDs
|
// 1) Get all used order IDs
|
||||||
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
|
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
|
||||||
@@ -29,8 +29,6 @@ class ShipmentController extends Controller
|
|||||||
return view('admin.shipments', compact('availableOrders', 'shipments'));
|
return view('admin.shipments', compact('availableOrders', 'shipments'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store new shipment
|
* Store new shipment
|
||||||
*/
|
*/
|
||||||
@@ -115,8 +113,6 @@ class ShipmentController extends Controller
|
|||||||
return redirect()->back()->with('success', "Shipment $newShipmentId created successfully!");
|
return redirect()->back()->with('success', "Shipment $newShipmentId created successfully!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show shipment details (for modal popup)
|
* Show shipment details (for modal popup)
|
||||||
*/
|
*/
|
||||||
@@ -135,8 +131,6 @@ class ShipmentController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update Shipment status from action button
|
* Update Shipment status from action button
|
||||||
*/
|
*/
|
||||||
@@ -164,5 +158,71 @@ class ShipmentController extends Controller
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update shipment details
|
||||||
|
*/
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$shipment = Shipment::findOrFail($id);
|
||||||
|
|
||||||
}
|
$data = $request->validate([
|
||||||
|
'origin' => 'required|string',
|
||||||
|
'destination' => 'required|string',
|
||||||
|
'shipment_date' => 'required|date',
|
||||||
|
'status' => 'required|string',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$shipment->update($data);
|
||||||
|
|
||||||
|
// If it's an AJAX request, return JSON response
|
||||||
|
if ($request->ajax() || $request->wantsJson()) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Shipment updated successfully.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->back()->with('success', 'Shipment updated successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete shipment permanently
|
||||||
|
*/
|
||||||
|
public function destroy($id, Request $request)
|
||||||
|
{
|
||||||
|
$shipment = Shipment::findOrFail($id);
|
||||||
|
|
||||||
|
// Delete shipment items
|
||||||
|
ShipmentItem::where('shipment_id', $shipment->id)->delete();
|
||||||
|
|
||||||
|
// Delete shipment itself
|
||||||
|
$shipment->delete();
|
||||||
|
|
||||||
|
// If it's an AJAX request, return JSON response
|
||||||
|
if ($request->ajax() || $request->wantsJson()) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Shipment deleted successfully.'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('admin.shipments')
|
||||||
|
->with('success', 'Shipment deleted successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dummy($id)
|
||||||
|
{
|
||||||
|
// Load shipment
|
||||||
|
$shipment = Shipment::with('orders')->findOrFail($id);
|
||||||
|
|
||||||
|
// Dummy data (you can modify anytime)
|
||||||
|
$dummyData = [
|
||||||
|
'title' => 'Dummy Shipment Preview',
|
||||||
|
'generated_on' => now()->format('d M Y h:i A'),
|
||||||
|
'note' => 'This is dummy shipment information for testing page layout.'
|
||||||
|
];
|
||||||
|
|
||||||
|
return view('admin.view_shipment', compact('shipment', 'dummyData'));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -65,4 +65,52 @@ class UserRequestController extends Controller
|
|||||||
|
|
||||||
return redirect()->back()->with('info', 'Request rejected successfully.');
|
return redirect()->back()->with('info', 'Request rejected successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function profileUpdateRequests()
|
||||||
|
{
|
||||||
|
$requests = \App\Models\UpdateRequest::where('status', 'pending')
|
||||||
|
->orderBy('id', 'desc')
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return view('admin.profile_update_requests', compact('requests'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function approveProfileUpdate($id)
|
||||||
|
{
|
||||||
|
$req = \App\Models\UpdateRequest::findOrFail($id);
|
||||||
|
$user = \App\Models\User::findOrFail($req->user_id);
|
||||||
|
|
||||||
|
// FIX: Ensure data is array
|
||||||
|
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
|
||||||
|
|
||||||
|
foreach ($newData as $key => $value) {
|
||||||
|
if ($value !== null && $value !== "") {
|
||||||
|
if (in_array($key, ['customer_name','company_name','designation','email','mobile_no','address','pincode'])) {
|
||||||
|
$user->$key = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
$req->status = 'approved';
|
||||||
|
$req->admin_note = 'Approved by admin on ' . now();
|
||||||
|
$req->save();
|
||||||
|
|
||||||
|
return back()->with('success', 'Profile updated successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function rejectProfileUpdate($id)
|
||||||
|
{
|
||||||
|
$req = \App\Models\UpdateRequest::findOrFail($id);
|
||||||
|
$req->status = 'rejected';
|
||||||
|
$req->admin_note = 'Rejected by admin on ' . now();
|
||||||
|
$req->save();
|
||||||
|
|
||||||
|
return back()->with('info', 'Profile update request rejected.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ class RequestController extends Controller
|
|||||||
'pincode' => $request->pincode,
|
'pincode' => $request->pincode,
|
||||||
'date' => Carbon::now()->toDateString(), // Auto current date
|
'date' => Carbon::now()->toDateString(), // Auto current date
|
||||||
'status' => 'pending', // Default status
|
'status' => 'pending', // Default status
|
||||||
|
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// ✅ Response
|
// ✅ Response
|
||||||
@@ -53,4 +55,6 @@ class RequestController extends Controller
|
|||||||
'data' => $newRequest
|
'data' => $newRequest
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,74 @@ use App\Models\User;
|
|||||||
|
|
||||||
class UserAuthController extends Controller
|
class UserAuthController extends Controller
|
||||||
{
|
{
|
||||||
|
|
||||||
|
public function refreshToken()
|
||||||
|
{
|
||||||
|
\Log::info('🔄 refreshToken() called');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get current token
|
||||||
|
$currentToken = JWTAuth::getToken();
|
||||||
|
|
||||||
|
if (!$currentToken) {
|
||||||
|
\Log::warning('⚠ No token provided in refreshToken()');
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Token not provided',
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
\Log::info('📥 Current Token:', ['token' => (string) $currentToken]);
|
||||||
|
|
||||||
|
// Try refreshing token
|
||||||
|
$newToken = JWTAuth::refresh($currentToken);
|
||||||
|
|
||||||
|
\Log::info('✅ Token refreshed successfully', ['new_token' => $newToken]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'token' => $newToken,
|
||||||
|
]);
|
||||||
|
|
||||||
|
} catch (\Tymon\JWTAuth\Exceptions\TokenExpiredException $e) {
|
||||||
|
\Log::error('❌ TokenExpiredException in refreshToken()', [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Token expired, cannot refresh.',
|
||||||
|
], 401);
|
||||||
|
|
||||||
|
} catch (\Tymon\JWTAuth\Exceptions\TokenInvalidException $e) {
|
||||||
|
\Log::error('❌ TokenInvalidException in refreshToken()', [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invalid token.',
|
||||||
|
], 401);
|
||||||
|
|
||||||
|
} catch (\Tymon\JWTAuth\Exceptions\JWTException $e) {
|
||||||
|
\Log::error('❌ JWTException in refreshToken()', [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
]);
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Could not refresh token.',
|
||||||
|
], 401);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Log::error('❌ General Exception in refreshToken()', [
|
||||||
|
'message' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
]);
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unexpected error while refreshing token.',
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Login
|
* User Login
|
||||||
*/
|
*/
|
||||||
@@ -60,6 +128,8 @@ class UserAuthController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User Logout
|
* User Logout
|
||||||
*/
|
*/
|
||||||
|
|||||||
296
app/Http/Controllers/user/UserOrderController.php
Normal file
296
app/Http/Controllers/user/UserOrderController.php
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\User;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
|
||||||
|
|
||||||
|
class UserOrderController extends Controller
|
||||||
|
{
|
||||||
|
public function orderSummary()
|
||||||
|
{
|
||||||
|
// Authenticate user via JWT
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return response()->json([
|
||||||
|
'status' => false,
|
||||||
|
'message' => 'Unauthorized'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
// Get all orders
|
||||||
|
// -------------------------------------
|
||||||
|
$orders = $user->orders()->with('invoice')->get();
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
// Counts
|
||||||
|
// -------------------------------------
|
||||||
|
$totalOrders = $orders->count();
|
||||||
|
$delivered = $orders->where('status', 'delivered')->count();
|
||||||
|
$inTransit = $orders->where('status', '!=', 'delivered')->count();
|
||||||
|
$active = $totalOrders;
|
||||||
|
|
||||||
|
// -------------------------------------
|
||||||
|
// Total Amount = Invoice.total_with_gst
|
||||||
|
// -------------------------------------
|
||||||
|
$totalAmount = $orders->sum(function ($o) {
|
||||||
|
return $o->invoice->final_amount_with_gst ?? 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Format total amount in K, L, Cr
|
||||||
|
$formattedAmount = $this->formatIndianNumber($totalAmount);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'status' => true,
|
||||||
|
|
||||||
|
'summary' => [
|
||||||
|
'active_orders' => $active,
|
||||||
|
'in_transit_orders' => $inTransit,
|
||||||
|
'delivered_orders' => $delivered,
|
||||||
|
'total_value' => $formattedAmount, // formatted value
|
||||||
|
'total_raw' => $totalAmount // original value
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert number into Indian Format:
|
||||||
|
* 1000 -> 1K
|
||||||
|
* 100000 -> 1L
|
||||||
|
* 10000000 -> 1Cr
|
||||||
|
*/
|
||||||
|
private function formatIndianNumber($num)
|
||||||
|
{
|
||||||
|
if ($num >= 10000000) {
|
||||||
|
return round($num / 10000000, 1) . 'Cr';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($num >= 100000) {
|
||||||
|
return round($num / 100000, 1) . 'L';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($num >= 1000) {
|
||||||
|
return round($num / 1000, 1) . 'K';
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string)$num;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function allOrders()
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unauthorized'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch orders for this user
|
||||||
|
$orders = $user->orders()
|
||||||
|
->with(['invoice', 'shipments'])
|
||||||
|
->orderBy('id', 'desc')
|
||||||
|
->get()
|
||||||
|
->map(function ($o) {
|
||||||
|
return [
|
||||||
|
'order_id' => $o->order_id,
|
||||||
|
'status' => $o->status,
|
||||||
|
'amount' => $o->ttl_amount,
|
||||||
|
'description'=> "Order from {$o->origin} to {$o->destination}",
|
||||||
|
'created_at' => $o->created_at,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'orders' => $orders
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orderDetails($order_id)
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
$order = $user->orders()
|
||||||
|
->with(['items'])
|
||||||
|
->where('order_id', $order_id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'order' => $order
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function orderShipment($order_id)
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
// Get order
|
||||||
|
$order = $user->orders()->where('order_id', $order_id)->first();
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find shipment only for this order
|
||||||
|
$shipment = $order->shipments()
|
||||||
|
->with(['items' => function ($q) use ($order) {
|
||||||
|
$q->where('order_id', $order->id);
|
||||||
|
}])
|
||||||
|
->first();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'shipment' => $shipment
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function orderInvoice($order_id)
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
$order = $user->orders()
|
||||||
|
->with('invoice.items')
|
||||||
|
->where('order_id', $order_id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'invoice' => $order->invoice
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function trackOrder($order_id)
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
$order = $user->orders()
|
||||||
|
->with('shipments')
|
||||||
|
->where('order_id', $order_id)
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (!$order) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Order not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$shipment = $order->shipments()->first();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'track' => [
|
||||||
|
'order_id' => $order->order_id,
|
||||||
|
'shipment_status' => $shipment->status ?? 'pending',
|
||||||
|
'shipment_date' => $shipment->shipment_date ?? null,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function allInvoices()
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
if (!$user) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unauthorized'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch all invoices of customer
|
||||||
|
$invoices = $user->invoices()
|
||||||
|
->withCount('installments')
|
||||||
|
->orderBy('id', 'desc')
|
||||||
|
->get()
|
||||||
|
->map(function ($invoice) {
|
||||||
|
return [
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'invoice_number' => $invoice->invoice_number,
|
||||||
|
'invoice_date' => $invoice->invoice_date,
|
||||||
|
'status' => $invoice->status,
|
||||||
|
'amount' => $invoice->final_amount_with_gst,
|
||||||
|
'formatted_amount' => $this->formatIndianNumber($invoice->final_amount_with_gst),
|
||||||
|
'pdf_url' => $invoice->pdf_path ? url($invoice->pdf_path) : null,
|
||||||
|
'installment_count' => $invoice->installments_count,
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'invoices' => $invoices
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invoiceInstallmentsById($invoice_id)
|
||||||
|
{
|
||||||
|
$user = \PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find invoice by numeric id and ensure it belongs to logged-in user (invoice.customer_id = user.id)
|
||||||
|
$invoice = \App\Models\Invoice::where('id', (int)$invoice_id)
|
||||||
|
->where('customer_id', $user->id)
|
||||||
|
->with(['installments' => function($q){
|
||||||
|
$q->orderBy('installment_date', 'ASC')->orderBy('id', 'ASC');
|
||||||
|
}])
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $invoice) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Invoice not found for this customer'
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'invoice_id' => $invoice->id,
|
||||||
|
'invoice_number' => $invoice->invoice_number,
|
||||||
|
'installments' => $invoice->installments
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invoiceDetails($invoice_id)
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Unauthorized'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$invoice = \App\Models\Invoice::where('id', $invoice_id)
|
||||||
|
->where('customer_id', $user->id)
|
||||||
|
->with('items')
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if (! $invoice) {
|
||||||
|
return response()->json(['success' => false, 'message' => 'Invoice not found'], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'invoice' => $invoice
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
149
app/Http/Controllers/user/UserProfileController.php
Normal file
149
app/Http/Controllers/user/UserProfileController.php
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\User;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use App\Models\UpdateRequest;
|
||||||
|
use PHPOpenSourceSaver\JWTAuth\Facades\JWTAuth;
|
||||||
|
|
||||||
|
class UserProfileController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get user profile
|
||||||
|
*/
|
||||||
|
public function profile()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Token invalid or expired',
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unauthorized'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => [
|
||||||
|
'customer_id' => $user->customer_id,
|
||||||
|
'customer_name' => $user->customer_name,
|
||||||
|
'company_name' => $user->company_name,
|
||||||
|
'designation' => $user->designation,
|
||||||
|
'email' => $user->email,
|
||||||
|
'mobile' => $user->mobile_no,
|
||||||
|
'address' => $user->address,
|
||||||
|
'pincode' => $user->pincode,
|
||||||
|
'status' => $user->status,
|
||||||
|
'customer_type' => $user->customer_type,
|
||||||
|
'profile_image' => $user->profile_image ? url($user->profile_image) : null,
|
||||||
|
'date' => $user->date,
|
||||||
|
'created_at' => $user->created_at,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update profile IMAGE only (no admin approval)
|
||||||
|
*/
|
||||||
|
public function updateProfileImage(Request $request)
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unauthorized'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'profile_image' => 'required|image|mimes:jpg,jpeg,png|max:2048'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// DELETE OLD IMAGE
|
||||||
|
if ($user->profile_image && file_exists(public_path($user->profile_image))) {
|
||||||
|
@unlink(public_path($user->profile_image));
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAVE NEW IMAGE
|
||||||
|
$file = $request->file('profile_image');
|
||||||
|
$filename = 'profile_' . time() . '.' . $file->getClientOriginalExtension();
|
||||||
|
$folder = 'profile_upload/';
|
||||||
|
$file->move(public_path($folder), $filename);
|
||||||
|
|
||||||
|
$user->profile_image = $folder . $filename;
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Profile image updated successfully',
|
||||||
|
'data' => [
|
||||||
|
'customer_id' => $user->customer_id,
|
||||||
|
'customer_name' => $user->customer_name,
|
||||||
|
'company_name' => $user->company_name,
|
||||||
|
'designation' => $user->designation,
|
||||||
|
'email' => $user->email,
|
||||||
|
'mobile' => $user->mobile_no,
|
||||||
|
'address' => $user->address,
|
||||||
|
'pincode' => $user->pincode,
|
||||||
|
'status' => $user->status,
|
||||||
|
'customer_type' => $user->customer_type,
|
||||||
|
'profile_image' => url($user->profile_image),
|
||||||
|
'date' => $user->date,
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Submit profile update request (requires admin approval)
|
||||||
|
*/
|
||||||
|
public function updateProfileRequest(Request $request)
|
||||||
|
{
|
||||||
|
$user = JWTAuth::parseToken()->authenticate();
|
||||||
|
|
||||||
|
if (! $user) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Unauthorized'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate input
|
||||||
|
$request->validate([
|
||||||
|
'customer_name' => 'nullable|string|max:255',
|
||||||
|
'company_name' => 'nullable|string|max:255',
|
||||||
|
'designation' => 'nullable|string|max:255',
|
||||||
|
'email' => 'nullable|email',
|
||||||
|
'mobile_no' => 'nullable|string|max:15',
|
||||||
|
'address' => 'nullable|string',
|
||||||
|
'pincode' => 'nullable|string|max:10'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// SAVE AS ARRAY (NOT JSON STRING!)
|
||||||
|
$updateReq = \App\Models\UpdateRequest::create([
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'data' => $request->all(), // <---- FIXED
|
||||||
|
'status' => 'pending',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Profile update request submitted. Waiting for admin approval.',
|
||||||
|
'request_id' => $updateReq->id
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
36
app/Http/Middleware/JwtRefreshMiddleware.php
Normal file
36
app/Http/Middleware/JwtRefreshMiddleware.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Tymon\JWTAuth\Facades\JWTAuth;
|
||||||
|
use Tymon\JWTAuth\Exceptions\TokenExpiredException;
|
||||||
|
use Tymon\JWTAuth\Exceptions\TokenInvalidException;
|
||||||
|
use Tymon\JWTAuth\Exceptions\JWTException;
|
||||||
|
|
||||||
|
class JwtRefreshMiddleware
|
||||||
|
{
|
||||||
|
public function handle($request, Closure $next)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
JWTAuth::parseToken()->authenticate();
|
||||||
|
} catch (TokenExpiredException $e) {
|
||||||
|
try {
|
||||||
|
$newToken = JWTAuth::refresh(JWTAuth::getToken());
|
||||||
|
auth()->setToken($newToken);
|
||||||
|
|
||||||
|
$response = $next($request);
|
||||||
|
|
||||||
|
return $response->header('Authorization', 'Bearer ' . $newToken);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return response()->json(['message' => 'Session expired, please login again'], 401);
|
||||||
|
}
|
||||||
|
} catch (TokenInvalidException $e) {
|
||||||
|
return response()->json(['message' => 'Invalid token'], 401);
|
||||||
|
} catch (JWTException $e) {
|
||||||
|
return response()->json(['message' => 'Token missing'], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,43 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
// app/Models/Admin.php
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
class Admin extends Authenticatable
|
class Admin extends Authenticatable
|
||||||
{
|
{
|
||||||
use Notifiable;
|
use HasFactory, Notifiable, HasRoles;
|
||||||
|
|
||||||
protected $guard = 'admin';
|
protected $guard_name = 'admin';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name', 'email', 'password', 'role',
|
'name', 'email', 'password', 'username',
|
||||||
|
'phone', 'emergency_phone', 'address',
|
||||||
|
'role', 'department', 'designation', 'joining_date',
|
||||||
|
'status', 'additional_info', 'type', // admin/staff indicator
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password', 'remember_token',
|
'password', 'remember_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function setPasswordAttribute($value)
|
||||||
|
{
|
||||||
|
if (!$value) return;
|
||||||
|
|
||||||
|
if (Hash::needsRehash($value)) {
|
||||||
|
$this->attributes['password'] = Hash::make($value);
|
||||||
|
} else {
|
||||||
|
$this->attributes['password'] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDisplayNameAttribute()
|
||||||
|
{
|
||||||
|
return "{$this->name}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,34 +9,41 @@ class Invoice extends Model
|
|||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'order_id',
|
'order_id',
|
||||||
'customer_id',
|
'customer_id',
|
||||||
'mark_no',
|
'mark_no',
|
||||||
|
|
||||||
'invoice_number',
|
'invoice_number',
|
||||||
'invoice_date',
|
'invoice_date',
|
||||||
'due_date',
|
'due_date',
|
||||||
|
|
||||||
'payment_method',
|
'payment_method',
|
||||||
'reference_no',
|
'reference_no',
|
||||||
'status',
|
'status',
|
||||||
|
|
||||||
'final_amount',
|
'final_amount', // without tax
|
||||||
'gst_percent',
|
|
||||||
'gst_amount',
|
|
||||||
'final_amount_with_gst',
|
|
||||||
|
|
||||||
'customer_name',
|
'tax_type', // gst / igst
|
||||||
'company_name',
|
'gst_percent', // only used for gst UI input
|
||||||
'customer_email',
|
'cgst_percent',
|
||||||
'customer_mobile',
|
'sgst_percent',
|
||||||
'customer_address',
|
'igst_percent',
|
||||||
'pincode',
|
|
||||||
|
'gst_amount', // total tax amount
|
||||||
|
'final_amount_with_gst',
|
||||||
|
|
||||||
|
'customer_name',
|
||||||
|
'company_name',
|
||||||
|
'customer_email',
|
||||||
|
'customer_mobile',
|
||||||
|
'customer_address',
|
||||||
|
'pincode',
|
||||||
|
|
||||||
|
'pdf_path',
|
||||||
|
'notes',
|
||||||
|
];
|
||||||
|
|
||||||
'pdf_path',
|
|
||||||
'notes',
|
|
||||||
];
|
|
||||||
|
|
||||||
/****************************
|
/****************************
|
||||||
* Relationships
|
* Relationships
|
||||||
@@ -74,4 +81,16 @@ class Invoice extends Model
|
|||||||
{
|
{
|
||||||
return $this->status === 'pending' && now()->gt($this->due_date);
|
return $this->status === 'pending' && now()->gt($this->due_date);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getShipment()
|
||||||
|
{
|
||||||
|
return $this->order?->shipments?->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function installments()
|
||||||
|
{
|
||||||
|
return $this->hasMany(InvoiceInstallment::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
24
app/Models/InvoiceInstallment.php
Normal file
24
app/Models/InvoiceInstallment.php
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class InvoiceInstallment extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'invoice_id',
|
||||||
|
'installment_date',
|
||||||
|
'payment_method',
|
||||||
|
'reference_no',
|
||||||
|
'amount',
|
||||||
|
];
|
||||||
|
|
||||||
|
public function invoice()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Invoice::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,11 +4,13 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
|
|
||||||
class Order extends Model
|
class Order extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory,SoftDeletes;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'order_id',
|
'order_id',
|
||||||
'mark_no',
|
'mark_no',
|
||||||
@@ -46,4 +48,21 @@ class Order extends Model
|
|||||||
->withTimestamps();
|
->withTimestamps();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function shipmentItems()
|
||||||
|
{
|
||||||
|
return $this->hasMany(\App\Models\ShipmentItem::class, 'order_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function shipments()
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(\App\Models\Shipment::class, 'shipment_items', 'order_id', 'shipment_id');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function invoice()
|
||||||
|
{
|
||||||
|
return $this->hasOne(\App\Models\Invoice::class, 'order_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ namespace App\Models;
|
|||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
|
||||||
class OrderItem extends Model
|
class OrderItem extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
use HasFactory, SoftDeletes;
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'order_id',
|
'order_id',
|
||||||
|
|||||||
@@ -11,12 +11,27 @@ class ShipmentItem extends Model
|
|||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'shipment_id',
|
'shipment_id',
|
||||||
'order_id',
|
'order_id',
|
||||||
'order_ctn',
|
|
||||||
'order_qty',
|
// OLD fields (keep them if old data exists)
|
||||||
'order_ttl_qty',
|
'order_ctn',
|
||||||
'order_ttl_amount',
|
'order_qty',
|
||||||
'order_ttl_kg',
|
'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
|
// Helper: return order data with fallback to snapshot
|
||||||
public function getDisplayQty()
|
public function getDisplayQty()
|
||||||
{
|
{
|
||||||
return $this->order->qty ?? $this->order_qty;
|
return $this->qty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDisplayAmount()
|
public function getDisplayAmount()
|
||||||
{
|
{
|
||||||
return $this->order->ttl_amount ?? $this->order_ttl_amount;
|
return $this->ttl_amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
app/Models/Staff.php
Normal file
81
app/Models/Staff.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class Staff extends Authenticatable
|
||||||
|
{
|
||||||
|
use Notifiable, HasRoles, SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guard name used by Spatie.
|
||||||
|
* Make sure this matches the guard you'll use for admin/staff auth (usually 'web' or 'admin').
|
||||||
|
*/
|
||||||
|
protected $guard_name = 'admin';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'employee_id',
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'emergency_phone',
|
||||||
|
'address',
|
||||||
|
'role', // business role/title (not Spatie role)
|
||||||
|
'department',
|
||||||
|
'designation',
|
||||||
|
'joining_date',
|
||||||
|
'status',
|
||||||
|
'additional_info',
|
||||||
|
'username',
|
||||||
|
'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hidden attributes (not returned in arrays / JSON).
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casts
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'joining_date' => 'date',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutator: automatically hash password when set.
|
||||||
|
* Accepts plain text and hashes it with bcrypt.
|
||||||
|
*/
|
||||||
|
public function setPasswordAttribute($value)
|
||||||
|
{
|
||||||
|
if (empty($value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already hashed (starts with $2y$), don't double-hash
|
||||||
|
if (Hash::needsRehash($value)) {
|
||||||
|
$this->attributes['password'] = Hash::make($value);
|
||||||
|
} else {
|
||||||
|
$this->attributes['password'] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional helper to get display name (useful in views/logs).
|
||||||
|
*/
|
||||||
|
public function getDisplayNameAttribute()
|
||||||
|
{
|
||||||
|
return $this->name . ' (' . $this->employee_id . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
30
app/Models/UpdateRequest.php
Normal file
30
app/Models/UpdateRequest.php
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class UpdateRequest extends Model
|
||||||
|
{
|
||||||
|
use HasFactory;
|
||||||
|
|
||||||
|
protected $table = 'update_requests';
|
||||||
|
|
||||||
|
protected $fillable = [
|
||||||
|
'user_id',
|
||||||
|
'data',
|
||||||
|
'status',
|
||||||
|
'admin_note',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'data' => 'array', // converts JSON to array automatically
|
||||||
|
];
|
||||||
|
|
||||||
|
// Relationship: request belongs to a user
|
||||||
|
public function user()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(User::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -89,4 +89,11 @@ class User extends Authenticatable implements JWTSubject
|
|||||||
{
|
{
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
public function invoices()
|
||||||
|
{
|
||||||
|
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
40
app/Providers/AuthServiceProvider.php
Normal file
40
app/Providers/AuthServiceProvider.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
|
class AuthServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The policy mappings for the application.
|
||||||
|
*
|
||||||
|
* @var array<class-string, class-string>
|
||||||
|
*/
|
||||||
|
protected $policies = [
|
||||||
|
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register any authentication / authorization services.
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
$this->registerPolicies();
|
||||||
|
|
||||||
|
// SUPER ADMIN bypass
|
||||||
|
Gate::before(function ($user, $ability) {
|
||||||
|
if ($user->hasRole('super-admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ADMIN bypass
|
||||||
|
Gate::before(function ($user, $ability) {
|
||||||
|
if ($user->hasRole('admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,5 @@
|
|||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
App\Providers\AuthServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,10 +7,13 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.2",
|
"php": "^8.2",
|
||||||
|
"barryvdh/laravel-dompdf": "^3.1",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
|
"maatwebsite/excel": "^1.1",
|
||||||
"mpdf/mpdf": "^8.2",
|
"mpdf/mpdf": "^8.2",
|
||||||
"php-open-source-saver/jwt-auth": "2.8"
|
"php-open-source-saver/jwt-auth": "2.8",
|
||||||
|
"spatie/laravel-permission": "^6.23"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
|||||||
1268
composer.lock
generated
1268
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -85,6 +85,11 @@ return [
|
|||||||
// 'driver' => 'database',
|
// 'driver' => 'database',
|
||||||
// 'table' => 'users',
|
// 'table' => 'users',
|
||||||
// ],
|
// ],
|
||||||
|
'staff' => [
|
||||||
|
'driver' => 'eloquent',
|
||||||
|
'model' => App\Models\Staff::class,
|
||||||
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
202
config/permission.php
Normal file
202
config/permission.php
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'models' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* Eloquent model should be used to retrieve your permissions. Of course, it
|
||||||
|
* is often just the "Permission" model but you may use whatever you like.
|
||||||
|
*
|
||||||
|
* The model you want to use as a Permission model needs to implement the
|
||||||
|
* `Spatie\Permission\Contracts\Permission` contract.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'permission' => Spatie\Permission\Models\Permission::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* Eloquent model should be used to retrieve your roles. Of course, it
|
||||||
|
* is often just the "Role" model but you may use whatever you like.
|
||||||
|
*
|
||||||
|
* The model you want to use as a Role model needs to implement the
|
||||||
|
* `Spatie\Permission\Contracts\Role` contract.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'role' => Spatie\Permission\Models\Role::class,
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
'table_names' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your roles. We have chosen a basic
|
||||||
|
* default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'roles' => 'roles',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your permissions. We have chosen a basic
|
||||||
|
* default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'permissions' => 'permissions',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your models permissions. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_has_permissions' => 'model_has_permissions',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your models roles. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_has_roles' => 'model_has_roles',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your roles permissions. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'role_has_permissions' => 'role_has_permissions',
|
||||||
|
],
|
||||||
|
|
||||||
|
'column_names' => [
|
||||||
|
/*
|
||||||
|
* Change this if you want to name the related pivots other than defaults
|
||||||
|
*/
|
||||||
|
'role_pivot_key' => null, // default 'role_id',
|
||||||
|
'permission_pivot_key' => null, // default 'permission_id',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change this if you want to name the related model primary key other than
|
||||||
|
* `model_id`.
|
||||||
|
*
|
||||||
|
* For example, this would be nice if your primary keys are all UUIDs. In
|
||||||
|
* that case, name this `model_uuid`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_morph_key' => 'model_id',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change this if you want to use the teams feature and your related model's
|
||||||
|
* foreign key is other than `team_id`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'team_foreign_key' => 'team_id',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the method for checking permissions will be registered on the gate.
|
||||||
|
* Set this to false if you want to implement custom logic for checking permissions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'register_permission_check_method' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
|
||||||
|
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
|
||||||
|
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
|
||||||
|
*/
|
||||||
|
'register_octane_reset_listener' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Events will fire when a role or permission is assigned/unassigned:
|
||||||
|
* \Spatie\Permission\Events\RoleAttached
|
||||||
|
* \Spatie\Permission\Events\RoleDetached
|
||||||
|
* \Spatie\Permission\Events\PermissionAttached
|
||||||
|
* \Spatie\Permission\Events\PermissionDetached
|
||||||
|
*
|
||||||
|
* To enable, set to true, and then create listeners to watch these events.
|
||||||
|
*/
|
||||||
|
'events_enabled' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Teams Feature.
|
||||||
|
* When set to true the package implements teams using the 'team_foreign_key'.
|
||||||
|
* If you want the migrations to register the 'team_foreign_key', you must
|
||||||
|
* set this to true before doing the migration.
|
||||||
|
* If you already did the migration then you must make a new migration to also
|
||||||
|
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
|
||||||
|
* (view the latest version of this package's migration file)
|
||||||
|
*/
|
||||||
|
|
||||||
|
'teams' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class to use to resolve the permissions team id
|
||||||
|
*/
|
||||||
|
'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Passport Client Credentials Grant
|
||||||
|
* When set to true the package will use Passports Client to check permissions
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use_passport_client_credentials' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the required permission names are added to exception messages.
|
||||||
|
* This could be considered an information leak in some contexts, so the default
|
||||||
|
* setting is false here for optimum safety.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'display_permission_in_exception' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the required role names are added to exception messages.
|
||||||
|
* This could be considered an information leak in some contexts, so the default
|
||||||
|
* setting is false here for optimum safety.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'display_role_in_exception' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default wildcard permission lookups are disabled.
|
||||||
|
* See documentation to understand supported syntax.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'enable_wildcard_permission' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class to use for interpreting wildcard permissions.
|
||||||
|
* If you need to modify delimiters, override the class and specify its name here.
|
||||||
|
*/
|
||||||
|
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
|
||||||
|
|
||||||
|
/* Cache-specific settings */
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default all permissions are cached for 24 hours to speed up performance.
|
||||||
|
* When permissions or roles are updated the cache is flushed automatically.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The cache key used to store all permissions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'key' => 'spatie.permission.cache',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You may optionally indicate a specific cache driver to use for permission and
|
||||||
|
* role caching using any of the `store` drivers listed in the cache.php config
|
||||||
|
* file. Using 'default' here means to use the `default` set in cache.php.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'store' => 'default',
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
|
||||||
|
// GST type — gst or igst
|
||||||
|
$table->enum('tax_type', ['gst', 'igst'])
|
||||||
|
->default('gst')
|
||||||
|
->after('final_amount');
|
||||||
|
|
||||||
|
// Old gst_percent becomes optional
|
||||||
|
$table->decimal('gst_percent', 5, 2)
|
||||||
|
->nullable()
|
||||||
|
->change();
|
||||||
|
|
||||||
|
// Split GST %
|
||||||
|
$table->decimal('cgst_percent', 5, 2)
|
||||||
|
->nullable()
|
||||||
|
->after('gst_percent');
|
||||||
|
|
||||||
|
$table->decimal('sgst_percent', 5, 2)
|
||||||
|
->nullable()
|
||||||
|
->after('cgst_percent');
|
||||||
|
|
||||||
|
// IGST %
|
||||||
|
$table->decimal('igst_percent', 5, 2)
|
||||||
|
->nullable()
|
||||||
|
->after('sgst_percent');
|
||||||
|
|
||||||
|
// Tax amount recalculation is the same
|
||||||
|
// gst_amount and final_amount_with_gst already exist
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoices', function (Blueprint $table) {
|
||||||
|
$table->dropColumn([
|
||||||
|
'tax_type',
|
||||||
|
'cgst_percent',
|
||||||
|
'sgst_percent',
|
||||||
|
'igst_percent',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
// Table already exists. Add updates here if needed.
|
||||||
|
Schema::table('invoice_installments', function (Blueprint $table) {
|
||||||
|
// nothing to update
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('invoice_installments', function (Blueprint $table) {
|
||||||
|
// nothing to rollback
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('order_items', function (Blueprint $table) {
|
||||||
|
$table->softDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('order_items', function (Blueprint $table) {
|
||||||
|
$table->dropSoftDeletes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('order_items', function (Blueprint $table) {
|
||||||
|
if (! Schema::hasColumn('order_items', 'deleted_at')) {
|
||||||
|
$table->softDeletes(); // adds deleted_at (nullable timestamp)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('order_items', function (Blueprint $table) {
|
||||||
|
if (Schema::hasColumn('order_items', 'deleted_at')) {
|
||||||
|
$table->dropSoftDeletes(); // drops deleted_at
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?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()
|
||||||
|
{
|
||||||
|
Schema::table('orders', function (Blueprint $table) {
|
||||||
|
$table->softDeletes(); // creates deleted_at column
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('orders', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('deleted_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class CreateUpdateRequestsTable extends Migration
|
||||||
|
{
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('update_requests', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
// The user who is requesting profile update
|
||||||
|
$table->unsignedBigInteger('user_id');
|
||||||
|
|
||||||
|
// JSON data of the requested profile changes
|
||||||
|
$table->json('data')->nullable();
|
||||||
|
|
||||||
|
// pending / approved / rejected
|
||||||
|
$table->enum('status', ['pending', 'approved', 'rejected'])->default('pending');
|
||||||
|
|
||||||
|
// Optional message (admin notes)
|
||||||
|
$table->text('admin_note')->nullable();
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// Foreign key constraint
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('update_requests');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<?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
|
||||||
|
{
|
||||||
|
$teams = config('permission.teams');
|
||||||
|
$tableNames = config('permission.table_names');
|
||||||
|
$columnNames = config('permission.column_names');
|
||||||
|
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||||
|
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||||
|
|
||||||
|
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||||
|
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||||
|
|
||||||
|
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||||
|
// $table->engine('InnoDB');
|
||||||
|
$table->bigIncrements('id'); // permission id
|
||||||
|
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||||
|
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['name', 'guard_name']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||||
|
// $table->engine('InnoDB');
|
||||||
|
$table->bigIncrements('id'); // role id
|
||||||
|
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||||
|
}
|
||||||
|
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||||
|
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||||
|
$table->timestamps();
|
||||||
|
if ($teams || config('permission.testing')) {
|
||||||
|
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||||
|
} else {
|
||||||
|
$table->unique(['name', 'guard_name']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||||
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
|
||||||
|
$table->string('model_type');
|
||||||
|
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||||
|
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||||
|
|
||||||
|
$table->foreign($pivotPermission)
|
||||||
|
->references('id') // permission id
|
||||||
|
->on($tableNames['permissions'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
if ($teams) {
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||||
|
|
||||||
|
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_permissions_permission_model_type_primary');
|
||||||
|
} else {
|
||||||
|
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_permissions_permission_model_type_primary');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||||
|
$table->unsignedBigInteger($pivotRole);
|
||||||
|
|
||||||
|
$table->string('model_type');
|
||||||
|
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||||
|
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||||
|
|
||||||
|
$table->foreign($pivotRole)
|
||||||
|
->references('id') // role id
|
||||||
|
->on($tableNames['roles'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
if ($teams) {
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||||
|
|
||||||
|
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_roles_role_model_type_primary');
|
||||||
|
} else {
|
||||||
|
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_roles_role_model_type_primary');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||||
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
$table->unsignedBigInteger($pivotRole);
|
||||||
|
|
||||||
|
$table->foreign($pivotPermission)
|
||||||
|
->references('id') // permission id
|
||||||
|
->on($tableNames['permissions'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
|
||||||
|
$table->foreign($pivotRole)
|
||||||
|
->references('id') // role id
|
||||||
|
->on($tableNames['roles'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
|
||||||
|
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||||
|
});
|
||||||
|
|
||||||
|
app('cache')
|
||||||
|
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||||
|
->forget(config('permission.cache.key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$tableNames = config('permission.table_names');
|
||||||
|
|
||||||
|
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||||
|
|
||||||
|
Schema::drop($tableNames['role_has_permissions']);
|
||||||
|
Schema::drop($tableNames['model_has_roles']);
|
||||||
|
Schema::drop($tableNames['model_has_permissions']);
|
||||||
|
Schema::drop($tableNames['roles']);
|
||||||
|
Schema::drop($tableNames['permissions']);
|
||||||
|
}
|
||||||
|
};
|
||||||
50
database/migrations/2025_12_04_071300_create_staff_table.php
Normal file
50
database/migrations/2025_12_04_071300_create_staff_table.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?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()
|
||||||
|
{
|
||||||
|
Schema::create('staff', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
// Personal Information
|
||||||
|
$table->string('employee_id')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('email')->unique();
|
||||||
|
$table->string('phone');
|
||||||
|
$table->string('emergency_phone')->nullable();
|
||||||
|
$table->text('address')->nullable();
|
||||||
|
|
||||||
|
// Professional Information
|
||||||
|
$table->string('role')->nullable(); // Job title
|
||||||
|
$table->string('department')->nullable();
|
||||||
|
$table->string('designation')->nullable();
|
||||||
|
$table->date('joining_date')->nullable();
|
||||||
|
$table->string('status')->default('active'); // active/inactive
|
||||||
|
$table->text('additional_info')->nullable();
|
||||||
|
|
||||||
|
// System Access
|
||||||
|
$table->string('username')->unique();
|
||||||
|
$table->string('password');
|
||||||
|
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('staff');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?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('admins', function (Blueprint $table) {
|
||||||
|
$table->string('employee_id')->unique()->nullable();
|
||||||
|
$table->string('phone')->nullable();
|
||||||
|
$table->string('emergency_phone')->nullable();
|
||||||
|
$table->text('address')->nullable();
|
||||||
|
|
||||||
|
$table->string('department')->nullable();
|
||||||
|
$table->string('designation')->nullable();
|
||||||
|
$table->date('joining_date')->nullable();
|
||||||
|
$table->enum('status', ['active','inactive'])->default('active');
|
||||||
|
$table->text('additional_info')->nullable();
|
||||||
|
|
||||||
|
$table->string('username')->unique()->nullable();
|
||||||
|
$table->enum('type', ['admin','staff'])->default('staff');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('admins', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('admins', function (Blueprint $table) {
|
||||||
|
$table->string('role')->nullable()->change(); // <-- Fix problem
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('admins', function (Blueprint $table) {
|
||||||
|
$table->enum('role', ['admin', 'super-admin'])->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?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::create('support_tickets', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('user_id'); // user who owns the chat
|
||||||
|
$table->string('status')->default('open'); // open / closed
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// foreign key constraint (optional but recommended)
|
||||||
|
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('support_tickets');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<?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::create('chat_messages', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->unsignedBigInteger('ticket_id'); // support ticket ID
|
||||||
|
$table->unsignedBigInteger('sender_id'); // user or admin/staff
|
||||||
|
$table->text('message')->nullable(); // message content
|
||||||
|
$table->string('file_path')->nullable(); // image/pdf/video
|
||||||
|
$table->string('file_type')->default('text'); // text/image/pdf/video
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
// foreign keys
|
||||||
|
$table->foreign('ticket_id')->references('id')->on('support_tickets')->onDelete('cascade');
|
||||||
|
$table->foreign('sender_id')->references('id')->on('users')->onDelete('cascade'); // admin also stored in users table? If admin separate, change later.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('chat_messages');
|
||||||
|
}
|
||||||
|
};
|
||||||
103
database/seeders/PermissionSeeder.php
Normal file
103
database/seeders/PermissionSeeder.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class PermissionSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// FINAL PERMISSION LIST (YOUR DATA)
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
$permissions = [
|
||||||
|
|
||||||
|
// ORDER
|
||||||
|
'order.view',
|
||||||
|
'order.create',
|
||||||
|
'order.edit',
|
||||||
|
'order.delete',
|
||||||
|
|
||||||
|
// EXTRA (ORDERS)
|
||||||
|
'orders.view', // you added this separately
|
||||||
|
|
||||||
|
// SHIPMENT
|
||||||
|
'shipment.view',
|
||||||
|
'shipment.create',
|
||||||
|
'shipment.delete',
|
||||||
|
|
||||||
|
// INVOICE
|
||||||
|
'invoice.view',
|
||||||
|
'invoice.edit',
|
||||||
|
'invoice.add_installment',
|
||||||
|
|
||||||
|
// CUSTOMER
|
||||||
|
'customer.view',
|
||||||
|
'customer.create',
|
||||||
|
|
||||||
|
// REQUEST
|
||||||
|
'request.view',
|
||||||
|
'request.update_profile',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// @can('')
|
||||||
|
// @endcan
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ACCOUNT
|
||||||
|
'account.view',
|
||||||
|
'account.create_order',
|
||||||
|
'account.edit_order',
|
||||||
|
'account.delete_order',
|
||||||
|
'account.toggle_payment_status',
|
||||||
|
'account.add_installment',
|
||||||
|
'account.view_installments',
|
||||||
|
|
||||||
|
// REPORT
|
||||||
|
'report.view',
|
||||||
|
|
||||||
|
// MARK LIST
|
||||||
|
'mark_list.view',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// CREATE PERMISSIONS
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
foreach ($permissions as $permission) {
|
||||||
|
Permission::firstOrCreate(
|
||||||
|
['name' => $permission, 'guard_name' => 'admin']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// ROLES
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
// Create super-admin role
|
||||||
|
$superAdminRole = Role::firstOrCreate(
|
||||||
|
['name' => 'super-admin', 'guard_name' => 'admin']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create admin role
|
||||||
|
$adminRole = Role::firstOrCreate(
|
||||||
|
['name' => 'admin', 'guard_name' => 'admin']
|
||||||
|
);
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// ASSIGN ALL PERMISSIONS TO BOTH ROLES
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
$allPermissions = Permission::where('guard_name', 'admin')->get();
|
||||||
|
|
||||||
|
$superAdminRole->syncPermissions($allPermissions);
|
||||||
|
$adminRole->syncPermissions($allPermissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000009.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000009.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000010.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000010.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000011.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000011.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000012.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000012.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000013.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000013.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000014.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000014.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000017.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000017.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000019.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000019.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000020.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000020.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000022.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000022.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000023.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000023.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000024.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000024.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000025.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000025.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000026.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000026.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000028.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000028.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000029.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000029.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000030.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000030.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000032.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000032.pdf
Normal file
Binary file not shown.
BIN
public/invoices/invoice-INV-2025-000033.pdf
Normal file
BIN
public/invoices/invoice-INV-2025-000033.pdf
Normal file
Binary file not shown.
BIN
public/profile_upload/profile_1764645094.jpg
Normal file
BIN
public/profile_upload/profile_1764645094.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
public/profile_upload/profile_1764743106.jpg
Normal file
BIN
public/profile_upload/profile_1764743106.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 209 KiB |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,7 @@ html, body {
|
|||||||
|
|
||||||
body, .container-fluid {
|
body, .container-fluid {
|
||||||
background: #f4f7fc;
|
background: #f4f7fc;
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-fluid {
|
.container-fluid {
|
||||||
@@ -34,6 +35,7 @@ body, .container-fluid {
|
|||||||
letter-spacing: .018em;
|
letter-spacing: .018em;
|
||||||
color: #18213e;
|
color: #18213e;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dash-title-desc {
|
.dash-title-desc {
|
||||||
@@ -42,6 +44,7 @@ body, .container-fluid {
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
letter-spacing: .01em;
|
letter-spacing: .01em;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== STATS CARDS - RESPONSIVE GRID ===== */
|
/* ===== STATS CARDS - RESPONSIVE GRID ===== */
|
||||||
@@ -127,12 +130,14 @@ body, .container-fluid {
|
|||||||
color:#63709b;
|
color:#63709b;
|
||||||
font-weight:600;
|
font-weight:600;
|
||||||
letter-spacing:.28px;
|
letter-spacing:.28px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-value {
|
.stats-value {
|
||||||
font-size:1.25rem;
|
font-size:1.25rem;
|
||||||
font-weight:700;
|
font-weight:700;
|
||||||
color:#194073;
|
color:#194073;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stats-card:hover .stats-icon {
|
.stats-card:hover .stats-icon {
|
||||||
@@ -170,6 +175,7 @@ body, .container-fluid {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 11px;
|
gap: 11px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-mgmt-title i {
|
.order-mgmt-title i {
|
||||||
@@ -191,6 +197,7 @@ body, .container-fluid {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-btn:hover {
|
.create-order-btn:hover {
|
||||||
@@ -201,7 +208,7 @@ body, .container-fluid {
|
|||||||
.card-body, .order-mgmt-main {
|
.card-body, .order-mgmt-main {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 0 0 17px 17px;
|
border-radius: 0 0 17px 17px;
|
||||||
padding:25px;
|
padding:20px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +220,7 @@ body, .container-fluid {
|
|||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== TABLE STYLES ===== */
|
/* ===== IMPROVED TABLE STYLES ===== */
|
||||||
.table thead tr {
|
.table thead tr {
|
||||||
background: #feebbe !important;
|
background: #feebbe !important;
|
||||||
border-radius: 12px 12px 0 0;
|
border-radius: 12px 12px 0 0;
|
||||||
@@ -226,9 +233,10 @@ body, .container-fluid {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #343535;
|
color: #343535;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
font-size:15px;
|
font-size: 15px;
|
||||||
padding-top: 12px;
|
padding: 16px 12px;
|
||||||
padding-bottom: 10px;
|
font-family: 'Inter', sans-serif;
|
||||||
|
border-bottom: 2px solid #e9ecef;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table thead th:first-child { border-radius: 9px 0 0 0;}
|
.table thead th:first-child { border-radius: 9px 0 0 0;}
|
||||||
@@ -239,20 +247,24 @@ body, .container-fluid {
|
|||||||
border-radius:9px;
|
border-radius:9px;
|
||||||
box-shadow:0 2px 12px #e2ebf941;
|
box-shadow:0 2px 12px #e2ebf941;
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
border-spacing: 0 2px;
|
border-spacing: 0 8px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-striped tbody tr {
|
.table-striped tbody tr {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
box-shadow: 0 2px 6px rgba(0,0,0,0.04);
|
||||||
margin-bottom: 2px;
|
margin-bottom: 8px;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
|
height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-striped tbody tr:hover {
|
.table-striped tbody tr:hover {
|
||||||
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||||
transform: translateY(-1px);
|
transform: translateY(-2px);
|
||||||
|
background: #f8fafd;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-striped tbody tr:nth-of-type(odd) {
|
.table-striped tbody tr:nth-of-type(odd) {
|
||||||
@@ -264,39 +276,110 @@ body, .container-fluid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table td {
|
.table td {
|
||||||
padding: 12px 6px;
|
padding: 14px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #495057;
|
||||||
|
line-height: 1.5;
|
||||||
|
border-bottom: 1px solid #f1f3f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table td:first-child {
|
.table td:first-child {
|
||||||
border-radius: 6px 0 0 6px;
|
border-radius: 8px 0 0 8px;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table td:last-child {
|
.table td:last-child {
|
||||||
border-radius: 0 6px 6px 0;
|
border-radius: 0 8px 8px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-responsive {
|
.table-responsive {
|
||||||
border-radius:10px;
|
border-radius:10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
/* ===== UPDATED STATUS BADGE STYLES ===== */
|
||||||
font-size:13px;
|
.badge {
|
||||||
font-weight:600;
|
padding: 7px 17px !important;
|
||||||
padding:7px 17px;
|
border-radius: 20px !important;
|
||||||
border-radius:12px;
|
font-weight: 600 !important;
|
||||||
|
font-size: 13px !important;
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
min-width: 40px !important;
|
||||||
|
text-align: center !important;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1.2 !important;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
gap: 6px;
|
||||||
|
width: 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-info {
|
.status-icon {
|
||||||
background-color:#22cbfa!important;
|
font-size: 13px;
|
||||||
color:#fff!important;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pending Status - SAME SIZE WITH CLOCK ICON */
|
||||||
|
.badge-pending {
|
||||||
|
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
|
||||||
|
color: #d97706 !important;
|
||||||
|
border-color: #f59e0b !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In Transit Status - SAME SIZE WITH TRUCK ICON */
|
||||||
|
.badge-in_transit {
|
||||||
|
background: linear-gradient(135deg, #dbeafe, #93c5fd) !important;
|
||||||
|
color: #1e40af !important;
|
||||||
|
border-color: #3b82f6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dispatched Status - SAME SIZE WITH BOX ICON */
|
||||||
|
.badge-dispatched {
|
||||||
|
background: linear-gradient(135deg, #e9d5ff, #c4b5fd) !important;
|
||||||
|
color: #6b21a8 !important;
|
||||||
|
border-color: #8b5cf6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delivered Status - SAME SIZE WITH CHECK ICON */
|
||||||
|
.badge-delivered {
|
||||||
|
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
|
||||||
|
color: #065f46 !important;
|
||||||
|
border-color: #10b981 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default badge styles - SAME SIZE */
|
||||||
|
.badge.bg-info {
|
||||||
|
background: linear-gradient(135deg, #4cc9f0, #4361ee) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-success {
|
||||||
|
background: linear-gradient(135deg, #4ade80, #22c55e) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-warning {
|
||||||
|
background: linear-gradient(135deg, #fbbf24, #f59e0b) !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.bg-danger {
|
||||||
|
background: linear-gradient(135deg, #f87171, #ef4444) !important;
|
||||||
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-label {
|
.form-label {
|
||||||
font-weight:600;
|
font-weight:600;
|
||||||
color:#1d3159;
|
color:#1d3159;
|
||||||
font-size:15px;
|
font-size:15px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control, .form-select {
|
.form-control, .form-select {
|
||||||
@@ -305,6 +388,7 @@ body, .container-fluid {
|
|||||||
background: #f7f9fe;
|
background: #f7f9fe;
|
||||||
border:1.2px solid #c7dbfa;
|
border:1.2px solid #c7dbfa;
|
||||||
font-weight:500;
|
font-weight:500;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control:focus, .form-select:focus {
|
.form-control:focus, .form-select:focus {
|
||||||
@@ -316,6 +400,7 @@ body, .container-fluid {
|
|||||||
color:#2469d6;
|
color:#2469d6;
|
||||||
font-weight:600;
|
font-weight:600;
|
||||||
text-decoration:underline;
|
text-decoration:underline;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== HORIZONTAL SCROLL CONTAINER ===== */
|
/* ===== HORIZONTAL SCROLL CONTAINER ===== */
|
||||||
@@ -327,7 +412,7 @@ body, .container-fluid {
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
|
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
|
||||||
padding: 8px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-wrapper::-webkit-scrollbar {
|
.table-wrapper::-webkit-scrollbar {
|
||||||
@@ -348,7 +433,7 @@ body, .container-fluid {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
min-width: 1200px;
|
min-width: 2000px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,6 +479,7 @@ body, .container-fluid {
|
|||||||
font-size: 1.4rem;
|
font-size: 1.4rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .close-btn {
|
.create-order-modal .close-btn {
|
||||||
@@ -425,6 +511,7 @@ body, .container-fluid {
|
|||||||
color: #1d3159;
|
color: #1d3159;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .form-control,
|
.create-order-modal .form-control,
|
||||||
@@ -435,6 +522,7 @@ body, .container-fluid {
|
|||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .form-control:focus,
|
.create-order-modal .form-control:focus,
|
||||||
@@ -449,6 +537,7 @@ body, .container-fluid {
|
|||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .btn-info:hover {
|
.create-order-modal .btn-info:hover {
|
||||||
@@ -461,6 +550,7 @@ body, .container-fluid {
|
|||||||
padding: 12px 30px;
|
padding: 12px 30px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .btn-success:hover {
|
.create-order-modal .btn-success:hover {
|
||||||
@@ -472,6 +562,7 @@ body, .container-fluid {
|
|||||||
border: none;
|
border: none;
|
||||||
color: #000;
|
color: #000;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .btn-warning:hover {
|
.create-order-modal .btn-warning:hover {
|
||||||
@@ -481,6 +572,7 @@ body, .container-fluid {
|
|||||||
.create-order-modal .btn-danger {
|
.create-order-modal .btn-danger {
|
||||||
background: #dc3545;
|
background: #dc3545;
|
||||||
border: none;
|
border: none;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-order-modal .btn-danger:hover {
|
.create-order-modal .btn-danger:hover {
|
||||||
@@ -505,6 +597,7 @@ body, .container-fluid {
|
|||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== ORDER DETAILS MODAL STYLES ===== */
|
/* ===== ORDER DETAILS MODAL STYLES ===== */
|
||||||
@@ -536,6 +629,7 @@ body, .container-fluid {
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: white;
|
color: white;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-header .btn-close {
|
.modal-header .btn-close {
|
||||||
@@ -554,6 +648,143 @@ body, .container-fluid {
|
|||||||
background: #f8fafc;
|
background: #f8fafc;
|
||||||
max-height: 70vh;
|
max-height: 70vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== PAGINATION STYLES ===== */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-top: 1px solid #eef3fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ba5bb;
|
||||||
|
font-weight: 600;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
color: #1a2951;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:hover:not(:disabled) {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #cbd5e0;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
color: #1a2951;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
min-width: 36px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn:hover {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn.active {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-pages {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-ellipsis {
|
||||||
|
color: #9ba5bb;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 0 4px;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:hover:not(:disabled) {
|
||||||
|
background: #1a2951;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
|
||||||
|
transition: filter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:hover:not(:disabled) img {
|
||||||
|
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:disabled img {
|
||||||
|
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ===== RESPONSIVE BREAKPOINTS ===== */
|
/* ===== RESPONSIVE BREAKPOINTS ===== */
|
||||||
@@ -596,7 +827,17 @@ body, .container-fluid {
|
|||||||
|
|
||||||
.table th,
|
.table th,
|
||||||
.table td {
|
.table td {
|
||||||
padding: 10px 5px;
|
padding: 12px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -669,7 +910,7 @@ body, .container-fluid {
|
|||||||
|
|
||||||
.table th,
|
.table th,
|
||||||
.table td {
|
.table td {
|
||||||
padding: 8px 4px;
|
padding: 10px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge {
|
.badge {
|
||||||
@@ -771,7 +1012,7 @@ body, .container-fluid {
|
|||||||
|
|
||||||
.table th,
|
.table th,
|
||||||
.table td {
|
.table td {
|
||||||
padding: 6px 3px;
|
padding: 8px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control, .form-select {
|
.form-control, .form-select {
|
||||||
@@ -890,9 +1131,12 @@ body, .container-fluid {
|
|||||||
<div class="order-mgmt-box">
|
<div class="order-mgmt-box">
|
||||||
<div class="order-mgmt-bar">
|
<div class="order-mgmt-bar">
|
||||||
<span class="order-mgmt-title"><i class="bi bi-table"></i> Order Management</span>
|
<span class="order-mgmt-title"><i class="bi bi-table"></i> Order Management</span>
|
||||||
<button class="create-order-btn" id="openCreateOrderModal">
|
@can('order.create')
|
||||||
<i class="bi bi-plus-circle"></i> Create Order
|
<button class="create-order-btn" id="openCreateOrderModal">
|
||||||
</button>
|
<i class="bi bi-plus-circle"></i> Create Order
|
||||||
|
</button>
|
||||||
|
@endcan
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="order-mgmt-main">
|
<div class="order-mgmt-main">
|
||||||
@@ -900,69 +1144,107 @@ body, .container-fluid {
|
|||||||
<div class="card shadow-sm">
|
<div class="card shadow-sm">
|
||||||
<div class="card-header bg-light">
|
<div class="card-header bg-light">
|
||||||
<strong>Recent Orders</strong>
|
<strong>Recent Orders</strong>
|
||||||
|
<span style="font-size:13px;color:#6577a3;margin-left:10px;">
|
||||||
|
Total orders: <span id="ordersCount">{{ $orders->count() }}</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body table-responsive">
|
<div class="card-body table-responsive">
|
||||||
<table class="table table-striped table-bordered align-middle text-center">
|
<div class="table-wrapper">
|
||||||
<thead class="table-light">
|
<table class="table table-striped table-bordered align-middle text-center">
|
||||||
<tr>
|
<thead class="table-light">
|
||||||
<th>#</th>
|
<tr>
|
||||||
<th>Order ID</th>
|
<th>#</th>
|
||||||
<th>Mark No</th>
|
<th>Order ID</th>
|
||||||
<th>Origin</th>
|
<th>Mark No</th>
|
||||||
<th>Destination</th>
|
<th>Origin</th>
|
||||||
<th>Total CTN</th>
|
<th>Destination</th>
|
||||||
<th>Total QTY</th>
|
<th>Total CTN</th>
|
||||||
<th>Total TTL/QTY</th>
|
<th>Total QTY</th>
|
||||||
<th>Total Amount (₹)</th>
|
<th>Total TTL/QTY</th>
|
||||||
<th>Total CBM</th>
|
<th>Total Amount (₹)</th>
|
||||||
<th>Total TTL CBM</th>
|
<th>Total CBM</th>
|
||||||
<th>Total KG</th>
|
<th>Total TTL CBM</th>
|
||||||
<th>Total TTL KG</th>
|
<th>Total KG</th>
|
||||||
<th>Status</th>
|
<th>Total TTL KG</th>
|
||||||
<th>Date</th>
|
<th>Status</th>
|
||||||
<th>Actions</th>
|
<th>Date</th>
|
||||||
</tr>
|
<th>Actions</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
@forelse($orders as $order)
|
<tbody id="ordersTableBody">
|
||||||
<tr>
|
@forelse($orders as $order)
|
||||||
<td>{{ $order->id }}</td>
|
<tr>
|
||||||
<td>
|
<td>{{ $order->id }}</td>
|
||||||
<a href="javascript:void(0)"
|
|
||||||
class="fw-semibold text-primary open-order-modal"
|
|
||||||
data-id="{{ $order->id }}">
|
|
||||||
{{ $order->order_id }}
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td>{{ $order->mark_no }}</td>
|
|
||||||
<td>{{ $order->origin }}</td>
|
|
||||||
<td>{{ $order->destination }}</td>
|
|
||||||
<td>{{ $order->ctn }}</td>
|
|
||||||
<td>{{ $order->qty }}</td>
|
|
||||||
<td>{{ $order->ttl_qty }}</td>
|
|
||||||
<td>₹{{ number_format($order->ttl_amount, 2) }}</td>
|
|
||||||
<td>{{ $order->cbm }}</td>
|
|
||||||
<td>{{ $order->ttl_cbm }}</td>
|
|
||||||
<td>{{ $order->kg }}</td>
|
|
||||||
<td>{{ $order->ttl_kg }}</td>
|
|
||||||
<td>
|
<td>
|
||||||
<span class="badge bg-info text-dark">{{ ucfirst($order->status) }}</span>
|
<a href="javascript:void(0)"
|
||||||
</td>
|
class="fw-semibold text-primary open-order-modal"
|
||||||
<td>{{ $order->created_at->format('d-m-Y') }}</td>
|
data-id="{{ $order->id }}">
|
||||||
<td>
|
{{ $order->order_id }}
|
||||||
<a href="{{ route('admin.orders.show', $order->id) }}" class="btn btn-sm btn-outline-primary">
|
|
||||||
<i class="bi bi-eye"></i> View
|
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
|
||||||
@empty
|
<td>{{ $order->mark_no }}</td>
|
||||||
<tr>
|
<td>{{ $order->origin }}</td>
|
||||||
<td colspan="16" class="text-muted">No orders found</td>
|
<td>{{ $order->destination }}</td>
|
||||||
</tr>
|
<td>{{ $order->ctn }}</td>
|
||||||
@endforelse
|
<td>{{ $order->qty }}</td>
|
||||||
</tbody>
|
<td>{{ $order->ttl_qty }}</td>
|
||||||
</table>
|
<td>₹{{ number_format($order->ttl_amount, 2) }}</td>
|
||||||
|
<td>{{ $order->cbm }}</td>
|
||||||
|
<td>{{ $order->ttl_cbm }}</td>
|
||||||
|
<td>{{ $order->kg }}</td>
|
||||||
|
<td>{{ $order->ttl_kg }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-{{ $order->status }}">
|
||||||
|
@if($order->status == 'pending')
|
||||||
|
<i class="bi bi-clock-fill status-icon"></i>
|
||||||
|
@elseif($order->status == 'in_transit')
|
||||||
|
<i class="bi bi-truck status-icon"></i>
|
||||||
|
@elseif($order->status == 'dispatched')
|
||||||
|
<i class="bi bi-box-seam status-icon"></i>
|
||||||
|
@elseif($order->status == 'delivered')
|
||||||
|
<i class="bi bi-check-circle-fill status-icon"></i>
|
||||||
|
@endif
|
||||||
|
{{ ucfirst(str_replace('_', ' ', $order->status)) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ $order->created_at->format('d-m-Y') }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.orders.show', $order->id) }}" class="btn btn-sm btn-outline-primary">
|
||||||
|
<i class="bi bi-eye"></i> View
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="16" class="text-muted">No orders found</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
<div class="pagination-container">
|
||||||
|
<div class="pagination-info" id="pageInfo">Showing 1 to 10 of {{ $orders->count() }} entries</div>
|
||||||
|
<div class="pagination-controls">
|
||||||
|
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page">
|
||||||
|
<!-- Left arrow SVG -->
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="pagination-pages" id="paginationPages">
|
||||||
|
<!-- Page numbers will be inserted here -->
|
||||||
|
</div>
|
||||||
|
<button class="pagination-img-btn" id="nextPageBtn" title="Next page">
|
||||||
|
<!-- Right arrow SVG -->
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1167,6 +1449,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const closeBtn = document.getElementById('closeCreateOrderModal');
|
const closeBtn = document.getElementById('closeCreateOrderModal');
|
||||||
const clearFormBtn = document.getElementById('clearForm');
|
const clearFormBtn = document.getElementById('clearForm');
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
let currentPage = 1;
|
||||||
|
const ordersPerPage = 10;
|
||||||
|
let allOrders = @json($orders->values());
|
||||||
|
|
||||||
|
// Initialize pagination
|
||||||
|
initializePagination();
|
||||||
|
|
||||||
// Reset temp data function
|
// Reset temp data function
|
||||||
const resetTempData = () => {
|
const resetTempData = () => {
|
||||||
fetch('{{ route("admin.orders.temp.reset") }}', {
|
fetch('{{ route("admin.orders.temp.reset") }}', {
|
||||||
@@ -1265,7 +1555,187 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ---------- Pagination Functions ---------- */
|
||||||
|
function initializePagination() {
|
||||||
|
renderOrdersTable(allOrders);
|
||||||
|
updatePaginationControls();
|
||||||
|
|
||||||
|
// Bind pagination buttons
|
||||||
|
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
|
||||||
|
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToPreviousPage() {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
renderOrdersTable(allOrders);
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextPage() {
|
||||||
|
const totalPages = Math.ceil(allOrders.length / ordersPerPage);
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
currentPage++;
|
||||||
|
renderOrdersTable(allOrders);
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginationControls() {
|
||||||
|
const totalPages = Math.ceil(allOrders.length / ordersPerPage);
|
||||||
|
const prevBtn = document.getElementById('prevPageBtn');
|
||||||
|
const nextBtn = document.getElementById('nextPageBtn');
|
||||||
|
const pageInfo = document.getElementById('pageInfo');
|
||||||
|
const paginationPages = document.getElementById('paginationPages');
|
||||||
|
|
||||||
|
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) {
|
||||||
|
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Math.max(2, currentPage - 1);
|
||||||
|
const end = Math.min(totalPages - 1, currentPage + 1);
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
addPageButton(i, paginationPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage < totalPages - 2) {
|
||||||
|
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
addPageButton(totalPages, paginationPages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPageButton(pageNumber, container) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'pagination-page-btn';
|
||||||
|
if (pageNumber === currentPage) {
|
||||||
|
button.classList.add('active');
|
||||||
|
}
|
||||||
|
button.textContent = pageNumber;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
currentPage = pageNumber;
|
||||||
|
renderOrdersTable(allOrders);
|
||||||
|
updatePaginationControls();
|
||||||
|
});
|
||||||
|
container.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOrdersTable(orders) {
|
||||||
|
const tbody = document.getElementById('ordersTableBody');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (!orders || orders.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="16" class="text-muted">No orders found</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate pagination
|
||||||
|
const startIndex = (currentPage - 1) * ordersPerPage;
|
||||||
|
const endIndex = startIndex + ordersPerPage;
|
||||||
|
const paginatedOrders = orders.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
paginatedOrders.forEach(order => {
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td>${order.id}</td>
|
||||||
|
<td>
|
||||||
|
<a href="javascript:void(0)"
|
||||||
|
class="fw-semibold text-primary open-order-modal"
|
||||||
|
data-id="${order.id}">
|
||||||
|
${order.order_id}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>${order.mark_no || ''}</td>
|
||||||
|
<td>${order.origin || ''}</td>
|
||||||
|
<td>${order.destination || ''}</td>
|
||||||
|
<td>${order.ctn || ''}</td>
|
||||||
|
<td>${order.qty || ''}</td>
|
||||||
|
<td>${order.ttl_qty || ''}</td>
|
||||||
|
<td>₹${order.ttl_amount ? Number(order.ttl_amount).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '0.00'}</td>
|
||||||
|
<td>${order.cbm || ''}</td>
|
||||||
|
<td>${order.ttl_cbm || ''}</td>
|
||||||
|
<td>${order.kg || ''}</td>
|
||||||
|
<td>${order.ttl_kg || ''}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-${order.status}">
|
||||||
|
${order.status === 'pending' ? '<i class="bi bi-clock-fill status-icon"></i>' : ''}
|
||||||
|
${order.status === 'in_transit' ? '<i class="bi bi-truck status-icon"></i>' : ''}
|
||||||
|
${order.status === 'dispatched' ? '<i class="bi bi-box-seam status-icon"></i>' : ''}
|
||||||
|
${order.status === 'delivered' ? '<i class="bi bi-check-circle-fill status-icon"></i>' : ''}
|
||||||
|
${order.status ? order.status.charAt(0).toUpperCase() + order.status.slice(1).replace('_', ' ') : ''}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>${new Date(order.created_at).toLocaleDateString('en-GB')}</td>
|
||||||
|
<td>
|
||||||
|
<a href="/admin/orders/${order.id}" class="btn btn-sm btn-outline-primary">
|
||||||
|
<i class="bi bi-eye"></i> View
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
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'));
|
||||||
|
|
||||||
|
document.getElementById('orderDetailsBody').innerHTML =
|
||||||
|
"<p class='text-center text-muted'>Loading...</p>";
|
||||||
|
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
fetch(`/admin/orders/view/${id}`)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(html => {
|
||||||
|
document.getElementById('orderDetailsBody').innerHTML = html;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
document.getElementById('orderDetailsBody').innerHTML =
|
||||||
|
"<p class='text-danger text-center'>Failed to load order details.</p>";
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</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
|
@endsection
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,8 @@
|
|||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet" />
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css" rel="stylesheet" />
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.5/font/bootstrap-icons.css">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
@@ -15,6 +17,7 @@
|
|||||||
font-family: 'Inter', Arial, sans-serif;
|
font-family: 'Inter', Arial, sans-serif;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ✨ Sidebar Glass + Animated Highlight Effect */
|
/* ✨ Sidebar Glass + Animated Highlight Effect */
|
||||||
@@ -34,7 +37,13 @@
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar collapsed state */
|
||||||
|
.sidebar.collapsed {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar .logo {
|
.sidebar .logo {
|
||||||
@@ -71,7 +80,6 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.25s ease;
|
transition: all 0.25s ease;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Background Animation */
|
/* Background Animation */
|
||||||
@@ -149,6 +157,39 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: calc(100vw - 190px);
|
width: calc(100vw - 190px);
|
||||||
margin-left: 190px;
|
margin-left: 190px;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main content when sidebar is collapsed */
|
||||||
|
.main-content.expanded {
|
||||||
|
margin-left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header hamburger button */
|
||||||
|
.header-toggle {
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px;
|
||||||
|
margin-right: 15px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-toggle:hover {
|
||||||
|
transform: scale(1.1);
|
||||||
|
background: rgba(43, 92, 182, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-toggle i {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
color: #2b5cb6;
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
@@ -163,8 +204,13 @@
|
|||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.content-wrapper {
|
.content-wrapper {
|
||||||
padding: 18px 16px 0 16px;
|
padding: 18px 16px 16px 16px;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@@ -182,44 +228,96 @@
|
|||||||
<div class="word"><strong>KENT</strong><br /><small>International Pvt. Ltd.</small></div>
|
<div class="word"><strong>KENT</strong><br /><small>International Pvt. Ltd.</small></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="{{ route('admin.dashboard') }}" class="{{ request()->routeIs('admin.dashboard') ? 'active' : '' }}"><i class="bi bi-house"></i> Dashboard</a>
|
{{-- Dashboard (requires order.view) --}}
|
||||||
<a href="{{ route('admin.shipments') }}" class="{{ request()->routeIs('admin.shipments') ? 'active' : '' }}"><i class="bi bi-truck"></i> Shipments</a>
|
@can('order.view')
|
||||||
<a href="{{ route('admin.invoices.index') }}"
|
<a href="{{ route('admin.dashboard') }}" class="{{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
||||||
class="{{ request()->routeIs('admin.invoices.index') ? 'active' : '' }}">
|
<i class="bi bi-house"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Shipments --}}
|
||||||
|
@can('shipment.view')
|
||||||
|
<a href="{{ route('admin.shipments') }}" class="{{ request()->routeIs('admin.shipments') ? 'active' : '' }}">
|
||||||
|
<i class="bi bi-truck"></i> Shipments
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Invoice --}}
|
||||||
|
@can('invoice.view')
|
||||||
|
<a href="{{ route('admin.invoices.index') }}" class="{{ request()->routeIs('admin.invoices.index') ? 'active' : '' }}">
|
||||||
<i class="bi bi-receipt"></i> Invoice
|
<i class="bi bi-receipt"></i> Invoice
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
<a href="{{ route('admin.customers.index') }}"
|
{{-- Customers --}}
|
||||||
class="{{ request()->routeIs('admin.customers.index') ? 'active' : '' }}">
|
@can('customer.view')
|
||||||
|
<a href="{{ route('admin.customers.index') }}" class="{{ request()->routeIs('admin.customers.index') ? 'active' : '' }}">
|
||||||
<i class="bi bi-people"></i> Customers
|
<i class="bi bi-people"></i> Customers
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
<a href="{{ route('admin.reports') }}"
|
{{-- Reports --}}
|
||||||
class="{{ request()->routeIs('admin.reports') ? 'active' : '' }}">
|
@can('report.view')
|
||||||
|
<a href="{{ route('admin.reports') }}" class="{{ request()->routeIs('admin.reports') ? 'active' : '' }}">
|
||||||
<i class="bi bi-graph-up"></i> Reports
|
<i class="bi bi-graph-up"></i> Reports
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Chat Support (NO PERMISSION REQUIRED) --}}
|
||||||
|
<a href="{{ route('admin.chat_support') }}" class="{{ request()->routeIs('admin.chat_support') ? 'active' : '' }}">
|
||||||
|
<i class="bi bi-chat-dots"></i> Chat Support
|
||||||
|
</a>
|
||||||
|
|
||||||
<a href="{{ route('admin.chat_support') }}" class="{{ request()->routeIs('admin.chat_support') ? 'active' : '' }}"><i class="bi bi-chat-dots"></i> Chat Support</a>
|
{{-- Orders --}}
|
||||||
<!-- <a href="{{ route('admin.orders.index') }}"
|
@can('orders.view')
|
||||||
class="{{ request()->routeIs('admin.orders.*') ? 'active' : '' }}">
|
<a href="{{ route('admin.orders') }}" class="{{ request()->routeIs('admin.orders') ? 'active' : '' }}">
|
||||||
<i class="bi bi-bag"></i> Orders
|
<i class="bi bi-bag"></i> Orders
|
||||||
</a> -->
|
</a>
|
||||||
<a href="{{ route('admin.orders') }}" class="{{ request()->routeIs('admin.orders') ? 'active' : '' }}"><i class="bi bi-bag"></i> Orders</a>
|
@endcan
|
||||||
<a href="{{ route('admin.requests') }}" class="{{ request()->routeIs('admin.requests') ? 'active' : '' }}"><i class="bi bi-envelope"></i> Requests</a>
|
|
||||||
<a href="{{ route('admin.staff') }}" class="{{ request()->routeIs('admin.staff') ? 'active' : '' }}"><i class="bi bi-person-badge"></i> Staff</a>
|
|
||||||
<a href="{{ route('admin.account') }}" class="{{ request()->routeIs('admin.account') ? 'active' : '' }}"><i class="bi bi-gear"></i> Account</a>
|
|
||||||
<a href="{{ route('admin.marklist.index') }}" class="{{ request()->routeIs('admin.marklist.index') ? 'active' : '' }}"><i class="bi bi-list-check"></i> Mark List</a>
|
|
||||||
|
|
||||||
<form method="POST" action="{{ route('admin.logout') }}" class="mt-4 px-3">
|
{{-- Requests --}}
|
||||||
@csrf
|
@can('request.view')
|
||||||
<button type="submit" class="btn btn-danger w-100"><i class="bi bi-box-arrow-right"></i> Logout</button>
|
<a href="{{ route('admin.requests') }}" class="{{ request()->routeIs('admin.requests') ? 'active' : '' }}">
|
||||||
</form>
|
<i class="bi bi-envelope"></i> Requests
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Profile Update Requests --}}
|
||||||
|
@can('request.update_profile')
|
||||||
|
<a href="{{ route('admin.profile.requests') }}">
|
||||||
|
<i class="bi bi-person-lines-fill"></i> Profile Update Requests
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Staff (NO PERMISSION REQUIRED) --}}
|
||||||
|
<a href="{{ route('admin.staff.index') }}" class="{{ request()->routeIs('admin.staff.*') ? 'active' : '' }}">
|
||||||
|
<i class="bi bi-person-badge"></i> Staff
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{{-- Account Section --}}
|
||||||
|
@can('account.view')
|
||||||
|
<a href="{{ route('admin.account') }}" class="{{ request()->routeIs('admin.account') ? 'active' : '' }}">
|
||||||
|
<i class="bi bi-gear"></i> Account
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Mark List --}}
|
||||||
|
@can('mark_list.view')
|
||||||
|
<a href="{{ route('admin.marklist.index') }}" class="{{ request()->routeIs('admin.marklist.index') ? 'active' : '' }}">
|
||||||
|
<i class="bi bi-list-check"></i> Mark List
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
<header>
|
<header>
|
||||||
<h5>@yield('page-title')</h5>
|
<div class="header-left">
|
||||||
|
<button class="header-toggle" id="headerToggle">
|
||||||
|
<i class="bi bi-list"></i>
|
||||||
|
</button>
|
||||||
|
<h5 class="mb-0">@yield('page-title')</h5>
|
||||||
|
</div>
|
||||||
<div class="d-flex align-items-center gap-3">
|
<div class="d-flex align-items-center gap-3">
|
||||||
<i class="bi bi-bell position-relative fs-4">
|
<i class="bi bi-bell position-relative fs-4">
|
||||||
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">2</span>
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">2</span>
|
||||||
@@ -246,7 +344,25 @@
|
|||||||
@yield('content')
|
@yield('content')
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
|
<script>
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const headerToggle = document.getElementById('headerToggle');
|
||||||
|
const sidebar = document.querySelector('.sidebar');
|
||||||
|
const mainContent = document.querySelector('.main-content');
|
||||||
|
|
||||||
|
// Function to toggle sidebar
|
||||||
|
function toggleSidebar() {
|
||||||
|
sidebar.classList.toggle('collapsed');
|
||||||
|
mainContent.classList.toggle('expanded');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header toggle button click event
|
||||||
|
if (headerToggle) {
|
||||||
|
headerToggle.addEventListener('click', toggleSidebar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -120,19 +120,33 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<form method="POST" action="{{ route('admin.login.submit') }}">
|
<form method="POST" action="{{ route('admin.login.submit') }}">
|
||||||
@csrf
|
@csrf
|
||||||
<div class="mb-3">
|
|
||||||
<label>Email</label>
|
|
||||||
<input type="email" name="email" class="form-control" value="{{ old('email') }}" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label>Password</label>
|
<label>Username / Email / Employee ID</label>
|
||||||
<input type="password" name="password" class="form-control" required>
|
<input
|
||||||
</div>
|
type="text"
|
||||||
|
name="login"
|
||||||
|
class="form-control"
|
||||||
|
value="{{ old('login') }}"
|
||||||
|
placeholder="Enter Email or Username or EMP ID"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label>Password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
class="form-control"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<div class="secure-encrypted">
|
<div class="secure-encrypted">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="green" class="bi bi-lock-fill" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="green" class="bi bi-lock-fill" viewBox="0 0 16 16">
|
||||||
|
|||||||
@@ -80,60 +80,158 @@
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ✨ NEW BADGE STYLES - Same as Invoice System */
|
||||||
.badge {
|
.badge {
|
||||||
padding: 7px 16px;
|
font-size: 11px !important;
|
||||||
border-radius: 20px;
|
font-weight: 600 !important;
|
||||||
font-size: 0.85rem;
|
padding: 6px 12px 6px 8px !important;
|
||||||
font-weight: 600;
|
border-radius: 20px !important;
|
||||||
user-select: none;
|
text-transform: uppercase;
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
letter-spacing: 0.3px;
|
||||||
|
display: inline-flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-size: cover !important;
|
||||||
|
background-position: center !important;
|
||||||
|
color: #fff !important;
|
||||||
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||||
|
border: 2px solid transparent !important;
|
||||||
|
min-width: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
line-height: 1.2;
|
||||||
|
gap: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-success {
|
/* Status icons */
|
||||||
background-color: #28a745 !important;
|
.status-icon {
|
||||||
color: #fff !important;
|
font-size: 12px;
|
||||||
box-shadow: 0 0 10px #6ee86e77;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom status badge backgrounds with icons */
|
||||||
|
.badge-active {
|
||||||
|
background: url('/images/status-bg-paid.png') !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-inactive {
|
||||||
|
background: url('/images/status-bg-overdue.png') !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fallback colors if images don't load - ALL WITH SAME SIZE */
|
||||||
|
.badge.badge-active {
|
||||||
|
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
|
||||||
|
color: #065f46 !important;
|
||||||
|
border-color: #10b981 !important;
|
||||||
|
width: 98px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge.badge-inactive {
|
||||||
|
background: linear-gradient(135deg, #e9d5ff, #c4b5fd) !important;
|
||||||
|
color: #6b21a8 !important;
|
||||||
|
border-color: #8b5cf6 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation effects for badges */
|
||||||
|
.badge.badge-active {
|
||||||
animation: pulseGreen 2s infinite;
|
animation: pulseGreen 2s infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge.badge-inactive {
|
||||||
|
animation: pulseRed 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes pulseGreen {
|
@keyframes pulseGreen {
|
||||||
0% { box-shadow: 0 0 8px #6ee86e77; }
|
0% { box-shadow: 0 0 8px #6ee86e77; }
|
||||||
50% { box-shadow: 0 0 14px #5dd75d88; }
|
50% { box-shadow: 0 0 14px #5dd75d88; }
|
||||||
100% { box-shadow: 0 0 8px #6ee86e77; }
|
100% { box-shadow: 0 0 8px #6ee86e77; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-danger {
|
|
||||||
background-color: #dc3545 !important;
|
|
||||||
color: #fff !important;
|
|
||||||
box-shadow: 0 0 10px #f97f7f77;
|
|
||||||
animation: pulseRed 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulseRed {
|
@keyframes pulseRed {
|
||||||
0% { box-shadow: 0 0 8px #f97f7f77; }
|
0% { box-shadow: 0 0 8px #f97f7f77; }
|
||||||
50% { box-shadow: 0 0 14px #ff868677; }
|
50% { box-shadow: 0 0 14px #ff868677; }
|
||||||
100% { box-shadow: 0 0 8px #f97f7f77; }
|
100% { box-shadow: 0 0 8px #f97f7f77; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ✨ ENHANCED ACTION BUTTONS - Professional & Attractive */
|
||||||
.btn-action {
|
.btn-action {
|
||||||
background-color: #ffd283;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
border: none;
|
border: none;
|
||||||
color: #1b1c21;
|
color: white;
|
||||||
padding: 7px 20px;
|
padding: 10px 22px;
|
||||||
font-size: 0.95rem;
|
font-size: 0.85rem;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
font-weight: 700;
|
font-weight: 600;
|
||||||
transition: all 0.25s ease;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
margin: 3px;
|
margin: 3px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 2px 6px rgba(141, 106, 0, 0.1);
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
text-decoration: none;
|
||||||
|
min-width: 120px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
||||||
|
transition: left 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action:hover::before {
|
||||||
|
left: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-action:hover {
|
.btn-action:hover {
|
||||||
background-color: #ffc85a;
|
transform: translateY(-3px);
|
||||||
transform: translateY(-2px);
|
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
|
||||||
box-shadow: 0 4px 10px rgba(141, 106, 0, 0.25);
|
color: white;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action:active {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific styles for Activate button */
|
||||||
|
.btn-action-activate {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
box-shadow: 0 4px 15px rgba(79, 172, 254, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-activate:hover {
|
||||||
|
box-shadow: 0 8px 25px rgba(79, 172, 254, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific styles for Deactivate button */
|
||||||
|
.btn-action-deactivate {
|
||||||
|
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
||||||
|
box-shadow: 0 4px 15px rgba(250, 112, 154, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-deactivate:hover {
|
||||||
|
box-shadow: 0 8px 25px rgba(250, 112, 154, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button icons */
|
||||||
|
.btn-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-muted {
|
.text-muted {
|
||||||
@@ -179,6 +277,139 @@
|
|||||||
from { box-shadow: 0 0 0px #b4a02455 inset; }
|
from { box-shadow: 0 0 0px #b4a02455 inset; }
|
||||||
to { box-shadow: 0 0 10px #b4a024aa inset; }
|
to { box-shadow: 0 0 10px #b4a024aa inset; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- Pagination Styles ---------- */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-top: 1px solid #eef3fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ba5bb;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
color: #1a2951;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:hover:not(:disabled) {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #cbd5e0;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
color: #1a2951;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
min-width: 36px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn:hover {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn.active {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-pages {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-ellipsis {
|
||||||
|
color: #9ba5bb;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Image-based pagination buttons */
|
||||||
|
.pagination-img-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:hover:not(:disabled) {
|
||||||
|
background: #1a2951;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
|
||||||
|
transition: filter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:hover:not(:disabled) img {
|
||||||
|
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:disabled img {
|
||||||
|
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
@if(session('success'))
|
@if(session('success'))
|
||||||
@@ -206,52 +437,208 @@
|
|||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="markListTableBody">
|
||||||
@foreach($markList as $mark)
|
<!-- Data will be loaded dynamically -->
|
||||||
<tr>
|
|
||||||
<td>{{ $mark->id }}</td>
|
|
||||||
<td>{{ $mark->mark_no }}</td>
|
|
||||||
<td>{{ $mark->origin }}</td>
|
|
||||||
<td>{{ $mark->destination }}</td>
|
|
||||||
<td>{{ $mark->customer_name }}</td>
|
|
||||||
<td>{{ $mark->customer_id }}</td>
|
|
||||||
<td>{{ $mark->mobile_no }}</td>
|
|
||||||
<td>{{ \Carbon\Carbon::parse($mark->date)->format('d-m-Y') }}</td>
|
|
||||||
<td>
|
|
||||||
@if($mark->status == 'active')
|
|
||||||
<span class="badge bg-success">Active</span>
|
|
||||||
@else
|
|
||||||
<span class="badge bg-danger">In-Active</span>
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if($mark->status == 'active')
|
|
||||||
<a href="{{ route('admin.marklist.toggle', $mark->id) }}" class="btn-action">Deactivate</a>
|
|
||||||
@else
|
|
||||||
<a href="{{ route('admin.marklist.toggle', $mark->id) }}" class="btn-action">Activate</a>
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
@if($markList->isEmpty())
|
<!-- Pagination Controls -->
|
||||||
<div class="py-4 text-center text-muted fst-italic">No mark numbers found.</div>
|
<div class="pagination-container">
|
||||||
@endif
|
<div class="pagination-info" id="pageInfo">Showing 0 entries</div>
|
||||||
|
<div class="pagination-controls">
|
||||||
|
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" disabled>
|
||||||
|
<!-- Left arrow SVG -->
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="pagination-pages" id="paginationPages">
|
||||||
|
<!-- Page numbers will be inserted here -->
|
||||||
|
</div>
|
||||||
|
<button class="pagination-img-btn" id="nextPageBtn" title="Next page" disabled>
|
||||||
|
<!-- Right arrow SVG -->
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="noResults" class="py-4 text-center text-muted fst-italic" style="display: none;">No mark numbers found.</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('globalSearch').addEventListener('input', function () {
|
// Pagination state
|
||||||
const filter = this.value.toLowerCase();
|
let currentPage = 1;
|
||||||
const rows = document.querySelectorAll('#markListTable tbody tr');
|
const itemsPerPage = 10;
|
||||||
|
let allMarkList = @json($markList);
|
||||||
|
let filteredMarkList = [...allMarkList];
|
||||||
|
|
||||||
rows.forEach(row => {
|
// Initialize pagination
|
||||||
const text = row.textContent.toLowerCase();
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
row.style.display = text.includes(filter) ? '' : 'none';
|
renderTable();
|
||||||
});
|
updatePaginationControls();
|
||||||
|
|
||||||
|
// Bind pagination events
|
||||||
|
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
|
||||||
|
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
|
||||||
|
|
||||||
|
// Bind search event
|
||||||
|
document.getElementById('globalSearch').addEventListener('input', handleSearch);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pagination Functions
|
||||||
|
function goToPreviousPage() {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextPage() {
|
||||||
|
const totalPages = Math.ceil(filteredMarkList.length / itemsPerPage);
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
currentPage++;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginationControls() {
|
||||||
|
const totalPages = Math.ceil(filteredMarkList.length / itemsPerPage);
|
||||||
|
const prevBtn = document.getElementById('prevPageBtn');
|
||||||
|
const nextBtn = document.getElementById('nextPageBtn');
|
||||||
|
const pageInfo = document.getElementById('pageInfo');
|
||||||
|
const paginationPages = document.getElementById('paginationPages');
|
||||||
|
|
||||||
|
prevBtn.disabled = currentPage === 1;
|
||||||
|
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||||||
|
|
||||||
|
// Update page info text
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage + 1;
|
||||||
|
const endIndex = Math.min(currentPage * itemsPerPage, filteredMarkList.length);
|
||||||
|
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredMarkList.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) {
|
||||||
|
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Math.max(2, currentPage - 1);
|
||||||
|
const end = Math.min(totalPages - 1, currentPage + 1);
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
addPageButton(i, paginationPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage < totalPages - 2) {
|
||||||
|
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
addPageButton(totalPages, paginationPages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPageButton(pageNumber, container) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'pagination-page-btn';
|
||||||
|
if (pageNumber === currentPage) {
|
||||||
|
button.classList.add('active');
|
||||||
|
}
|
||||||
|
button.textContent = pageNumber;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
currentPage = pageNumber;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
});
|
||||||
|
container.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search Function
|
||||||
|
function handleSearch() {
|
||||||
|
const filter = this.value.toLowerCase();
|
||||||
|
filteredMarkList = allMarkList.filter(mark => {
|
||||||
|
return Object.values(mark).some(value =>
|
||||||
|
String(value).toLowerCase().includes(filter)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
currentPage = 1;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Table Function - FIXED: Using direct URL construction
|
||||||
|
function renderTable() {
|
||||||
|
const tbody = document.getElementById('markListTableBody');
|
||||||
|
const noResults = document.getElementById('noResults');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
if (filteredMarkList.length === 0) {
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
noResults.style.display = 'block';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
noResults.style.display = 'none';
|
||||||
|
|
||||||
|
// Calculate pagination
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
|
const endIndex = startIndex + itemsPerPage;
|
||||||
|
const paginatedItems = filteredMarkList.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
paginatedItems.forEach(mark => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td>${mark.id}</td>
|
||||||
|
<td>${mark.mark_no}</td>
|
||||||
|
<td>${mark.origin}</td>
|
||||||
|
<td>${mark.destination}</td>
|
||||||
|
<td>${mark.customer_name}</td>
|
||||||
|
<td>${mark.customer_id}</td>
|
||||||
|
<td>${mark.mobile_no}</td>
|
||||||
|
<td>${new Date(mark.date).toLocaleDateString('en-GB')}</td>
|
||||||
|
<td>
|
||||||
|
${mark.status == 'active'
|
||||||
|
? `<span class="badge badge-active">
|
||||||
|
<i class="bi bi-check-circle-fill status-icon"></i>
|
||||||
|
Active
|
||||||
|
</span>`
|
||||||
|
: `<span class="badge badge-inactive">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill status-icon"></i>
|
||||||
|
In-Active
|
||||||
|
</span>`
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
${mark.status == 'active'
|
||||||
|
? `<a href="/admin/mark-list/status/${mark.id}" class="btn-action btn-action-deactivate">
|
||||||
|
<i class="bi bi-power btn-icon"></i>
|
||||||
|
Deactivate
|
||||||
|
</a>`
|
||||||
|
: `<a href="/admin/mark-list/status/${mark.id}" class="btn-action btn-action-activate">
|
||||||
|
<i class="bi bi-play-circle btn-icon"></i>
|
||||||
|
Activate
|
||||||
|
</a>`
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
File diff suppressed because it is too large
Load Diff
70
resources/views/admin/orders/pdf.blade.php
Normal file
70
resources/views/admin/orders/pdf.blade.php
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Orders Report</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: DejaVu Sans, sans-serif; font-size:12px; }
|
||||||
|
table { width:100%; border-collapse: collapse; }
|
||||||
|
th, td { border: 1px solid #ddd; padding: 6px; text-align: left; }
|
||||||
|
th { background: #f2f2f2; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Orders Report</h3>
|
||||||
|
|
||||||
|
@if(!empty($filters))
|
||||||
|
<p>
|
||||||
|
@if($filters['search']) Search: <strong>{{ $filters['search'] }}</strong> @endif
|
||||||
|
@if($filters['status']) | Status: <strong>{{ ucfirst($filters['status']) }}</strong> @endif
|
||||||
|
@if($filters['shipment']) | Shipment: <strong>{{ ucfirst($filters['shipment']) }}</strong> @endif
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Order ID</th>
|
||||||
|
<th>Shipment ID</th>
|
||||||
|
<th>Customer ID</th>
|
||||||
|
<th>Company</th>
|
||||||
|
<th>Origin</th>
|
||||||
|
<th>Destination</th>
|
||||||
|
<th>Order Date</th>
|
||||||
|
<th>Invoice No</th>
|
||||||
|
<th>Invoice Date</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
<th>Amount + GST</th>
|
||||||
|
<th>Invoice Status</th>
|
||||||
|
<th>Shipment Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($orders as $order)
|
||||||
|
@php
|
||||||
|
$mark = $order->markList ?? null;
|
||||||
|
$invoice = $order->invoice ?? null;
|
||||||
|
$shipment = $order->shipments->first() ?? null;
|
||||||
|
@endphp
|
||||||
|
<tr>
|
||||||
|
<td>{{ $order->order_id }}</td>
|
||||||
|
<td>{{ $shipment->shipment_id ?? '-' }}</td>
|
||||||
|
<td>{{ $mark->customer_id ?? '-' }}</td>
|
||||||
|
<td>{{ $mark->company_name ?? '-' }}</td>
|
||||||
|
<td>{{ $mark->origin ?? $order->origin ?? '-' }}</td>
|
||||||
|
<td>{{ $mark->destination ?? $order->destination ?? '-' }}</td>
|
||||||
|
<td>{{ $order->created_at ? $order->created_at->format('d-m-Y') : '-' }}</td>
|
||||||
|
<td>{{ $invoice->invoice_number ?? '-' }}</td>
|
||||||
|
<td>{{ $invoice?->invoice_date ? \Carbon\Carbon::parse($invoice->invoice_date)->format('d-m-Y') : '-' }}</td>
|
||||||
|
<td>{{ $invoice?->final_amount ? number_format($invoice->final_amount, 2) : '-' }}</td>
|
||||||
|
<td>{{ $invoice?->final_amount_with_gst ? number_format($invoice->final_amount_with_gst, 2) : '-' }}</td>
|
||||||
|
<td>{{ $invoice->status ? ucfirst($invoice->status) : 'Pending' }}</td>
|
||||||
|
<td>{{ $shipment?->status ? ucfirst(str_replace('_',' ',$shipment->status)) : 'Pending' }}</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr><td colspan="13" style="text-align:center">No orders found</td></tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -5,28 +5,101 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
<div class="container py-4">
|
<div class="container py-4">
|
||||||
|
|
||||||
{{-- Header --}}
|
{{-- HEADER --}}
|
||||||
<div class="card shadow-sm rounded-3 border-0">
|
<div class="card shadow-sm rounded-3 border-0">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
|
{{-- TOP BAR --}}
|
||||||
<div class="d-flex justify-content-between align-items-start">
|
<div class="d-flex justify-content-between align-items-start">
|
||||||
<div>
|
<div>
|
||||||
<h4 class="fw-bold mb-0">Order Details</Details></h4>
|
<h4 class="fw-bold mb-0">Order Details</h4>
|
||||||
<small class="text-muted">Detailed view of this shipment order</small>
|
<small class="text-muted">Detailed view of this shipment order</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{-- ADD ITEM --}}
|
||||||
|
@can('order.create')
|
||||||
|
<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>
|
||||||
|
@endcan
|
||||||
|
|
||||||
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
|
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
|
||||||
</div>
|
</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 -->
|
||||||
|
|
||||||
|
<!-- {{-- DELETE ORDER --}}
|
||||||
|
@if($order->status === 'pending')
|
||||||
|
<form action="{{ route('admin.orders.destroy', $order->id) }}"
|
||||||
|
method="POST"
|
||||||
|
onsubmit="return confirm('Delete this entire order?')">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button class="btn btn-delete-order">
|
||||||
|
<i class="fas fa-trash-alt me-2"></i>Delete Order
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endif -->
|
||||||
|
|
||||||
|
<!-- </div> -->
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
{{-- Customer Info --}}
|
{{-- EDIT ORDER FORM --}}
|
||||||
|
<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.updateItem', $order->id) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label>Mark No</label>
|
||||||
|
<input type="text" name="mark_no" value="{{ $order->mark_no }}" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label>Origin</label>
|
||||||
|
<input type="text" name="origin" value="{{ $order->origin }}" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<label>Destination</label>
|
||||||
|
<input type="text" name="destination" value="{{ $order->destination }}" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn btn-save-changes">Save Changes</button>
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-cancel"
|
||||||
|
onclick="document.getElementById('editOrderForm').style.display='none'">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- CUSTOMER INFO --}}
|
||||||
<div class="row mb-4">
|
<div class="row mb-4">
|
||||||
<div class="col-md-8 d-flex">
|
<div class="col-md-8 d-flex">
|
||||||
<div class="me-3">
|
<div class="me-3">
|
||||||
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center"
|
<div class="rounded-circle bg-secondary text-white d-flex align-items-center justify-content-center"
|
||||||
style="width:60px; height:60px; font-size:20px;">
|
style="width:60px; height:60px; font-size:20px;">
|
||||||
{{ strtoupper(substr($user->customer_name ?? 'U', 0, 1)) }}
|
{{ strtoupper(substr($user->customer_name ?? 'U', 0, 1)) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h5 class="mb-0">{{ $user->customer_name ?? 'Unknown Customer' }}</h5>
|
<h5 class="mb-0">{{ $user->customer_name ?? 'Unknown Customer' }}</h5>
|
||||||
<p class="mb-0">{{ $user->company_name ?? 'N/A' }}</p>
|
<p class="mb-0">{{ $user->company_name ?? 'N/A' }}</p>
|
||||||
@@ -34,13 +107,14 @@
|
|||||||
<p class="mb-0 text-muted">{{ $user->mobile_no ?? '' }}</p>
|
<p class="mb-0 text-muted">{{ $user->mobile_no ?? '' }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-4 text-end">
|
<div class="col-md-4 text-end">
|
||||||
<p class="mb-0">{{ $user->address ?? '' }}</p>
|
<p class="mb-0">{{ $user->address ?? '' }}</p>
|
||||||
<small class="text-muted">{{ $user->pincode ?? '' }}</small>
|
<small class="text-muted">{{ $user->pincode ?? '' }}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Order Summary --}}
|
{{-- ORDER SUMMARY --}}
|
||||||
<div class="bg-light rounded p-3 mb-3">
|
<div class="bg-light rounded p-3 mb-3">
|
||||||
<div class="row text-center">
|
<div class="row text-center">
|
||||||
<div class="col-md-3 border-end">
|
<div class="col-md-3 border-end">
|
||||||
@@ -65,7 +139,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Origin - Destination --}}
|
{{-- ORIGIN / DESTINATION --}}
|
||||||
<div class="row text-center mb-4">
|
<div class="row text-center mb-4">
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<p class="mb-1 fw-semibold text-muted">Origin</p>
|
<p class="mb-1 fw-semibold text-muted">Origin</p>
|
||||||
@@ -77,7 +151,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Order Items Table --}}
|
{{-- ITEMS TABLE --}}
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="table table-bordered align-middle text-center">
|
<table class="table table-bordered align-middle text-center">
|
||||||
<thead class="table-light">
|
<thead class="table-light">
|
||||||
@@ -88,15 +162,17 @@
|
|||||||
<th>QTY</th>
|
<th>QTY</th>
|
||||||
<th>TTL/QTY</th>
|
<th>TTL/QTY</th>
|
||||||
<th>Unit</th>
|
<th>Unit</th>
|
||||||
<th>Price (₹)</th>
|
<th>Price</th>
|
||||||
<th>TTL Amount (₹)</th>
|
<th>Total Amount</th>
|
||||||
<th>CBM</th>
|
<th>CBM</th>
|
||||||
<th>TTL CBM</th>
|
<th>TTL CBM</th>
|
||||||
<th>KG</th>
|
<th>KG</th>
|
||||||
<th>TTL KG</th>
|
<th>TTL KG</th>
|
||||||
<th>Shop No</th>
|
<th>Shop No</th>
|
||||||
|
<th>Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
|
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach($order->items as $index => $item)
|
@foreach($order->items as $index => $item)
|
||||||
<tr>
|
<tr>
|
||||||
@@ -113,38 +189,733 @@
|
|||||||
<td>{{ $item->kg }}</td>
|
<td>{{ $item->kg }}</td>
|
||||||
<td>{{ $item->ttl_kg }}</td>
|
<td>{{ $item->ttl_kg }}</td>
|
||||||
<td>{{ $item->shop_no }}</td>
|
<td>{{ $item->shop_no }}</td>
|
||||||
|
|
||||||
|
<td class="d-flex justify-content-center gap-2">
|
||||||
|
|
||||||
|
{{-- EDIT BUTTON --}}
|
||||||
|
@can('order.edit')
|
||||||
|
<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>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
@can('order.delete')
|
||||||
|
{{-- 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>
|
||||||
|
@endcan
|
||||||
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- Totals --}}
|
|
||||||
|
@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">Edit Item</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
|
||||||
|
{{-- TOTALS --}}
|
||||||
<div class="row text-center mt-4">
|
<div class="row text-center mt-4">
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<h6 class="fw-bold text-primary">{{ $order->ctn }}</h6>
|
<h6 class="fw-bold text-primary">{{ $order->ctn }}</h6>
|
||||||
<small class="text-muted">Total CTN</small>
|
<small>Total CTN</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<h6 class="fw-bold text-primary">{{ $order->qty }}</h6>
|
<h6 class="fw-bold text-primary">{{ $order->qty }}</h6>
|
||||||
<small class="text-muted">Total QTY</small>
|
<small>Total QTY</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<h6 class="fw-bold text-success">{{ $order->ttl_kg }}</h6>
|
<h6 class="fw-bold text-success">{{ $order->ttl_kg }}</h6>
|
||||||
<small class="text-muted">Total TTL KG</small>
|
<small>Total KG</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-3">
|
<div class="col-md-3">
|
||||||
<h6 class="fw-bold text-danger">₹{{ number_format($order->ttl_amount, 2) }}</h6>
|
<h6 class="fw-bold text-danger">₹{{ number_format($order->ttl_amount, 2) }}</h6>
|
||||||
<small class="text-muted">Total Amount</small>
|
<small>Total Amount</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
|
||||||
|
|
||||||
|
{{-- ADD ITEM MODAL --}}
|
||||||
|
<div class="modal fade" id="addItemModal" 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-cube me-2"></i>Add Item To Order</h5>
|
||||||
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form id="addItemForm" action="{{ route('admin.orders.addItem', $order->id) }}" method="POST">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
{{-- DELETED ITEMS --}}
|
||||||
|
@php
|
||||||
|
$deletedItems = \App\Models\OrderItem::onlyTrashed()
|
||||||
|
->where('order_id', $order->id)
|
||||||
|
->get();
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<h5 class="section-title">Deleted Items (Use / Restore)</h5>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<td class="d-flex justify-content-center gap-2">
|
||||||
|
|
||||||
|
{{-- USE THIS ITEM --}}
|
||||||
|
<button type="button"
|
||||||
|
class="btn btn-sm btn-use-item"
|
||||||
|
onclick="fillFormFromDeleted({{ json_encode($deleted) }})">
|
||||||
|
Use
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{{-- 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>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr>
|
||||||
|
<td colspan="14" class="text-muted">No deleted items.</td>
|
||||||
|
</tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{-- ADD FORM --}}
|
||||||
|
<div class="row g-3">
|
||||||
|
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Description</label>
|
||||||
|
<input type="text" name="description" class="form-control custom-input" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">CTN</label>
|
||||||
|
<input type="number" name="ctn" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">QTY</label>
|
||||||
|
<input type="number" name="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" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Unit</label>
|
||||||
|
<input type="text" name="unit" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Price</label>
|
||||||
|
<input type="number" name="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" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">CBM</label>
|
||||||
|
<input type="number" name="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" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">KG</label>
|
||||||
|
<input type="number" name="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" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Shop No</label>
|
||||||
|
<input type="text" name="shop_no" class="form-control custom-input">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="btn btn-modal-close" data-bs-dismiss="modal">Close</button>
|
||||||
|
<button class="btn btn-modal-add">Add Item</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</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) {
|
||||||
|
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>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Custom Button Styles */
|
||||||
|
.btn-add-item {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
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 {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px 0 rgba(102, 126, 234, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-item::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: -100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
|
||||||
|
transition: 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-item:hover::before {
|
||||||
|
left: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit-order {
|
||||||
|
background: linear-gradient(135deg, #4776E6 0%, #8E54E9 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px 0 rgba(71, 118, 230, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit-order:hover:not(:disabled) {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px 0 rgba(71, 118, 230, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-edit-order:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete-order {
|
||||||
|
background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px 0 rgba(255, 65, 108, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete-order:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px 0 rgba(255, 65, 108, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete-item {
|
||||||
|
background: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete-item:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Buttons */
|
||||||
|
.btn-save-changes {
|
||||||
|
background: linear-gradient(135deg, #00b09b 0%, #96c93d 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 176, 155, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save-changes:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 176, 155, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background: linear-gradient(135deg, #8e9eab 0%, #eef2f3 100%);
|
||||||
|
border: none;
|
||||||
|
color: #2c3e50;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(142, 158, 171, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal Styles */
|
||||||
|
.custom-modal {
|
||||||
|
border-radius: 15px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-gradient-header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-bottom: none;
|
||||||
|
padding: 20px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
color: #2c3e50;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
border-bottom: 2px solid #667eea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-input {
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-input:focus {
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-use-item {
|
||||||
|
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 5px 15px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-use-item:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(79, 172, 254, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-restore {
|
||||||
|
background: linear-gradient(135deg, #00b09b 0%, #96c93d 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 5px 15px;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-restore:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 176, 155, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modal-close {
|
||||||
|
background: linear-gradient(135deg, #8e9eab 0%, #eef2f3 100%);
|
||||||
|
border: none;
|
||||||
|
color: #2c3e50;
|
||||||
|
padding: 10px 25px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modal-close:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(142, 158, 171, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modal-add {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 25px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-modal-add:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 8px 25px 0 rgba(102, 126, 234, 0.4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation for the Add Item Button */
|
||||||
|
@keyframes pulse-glow {
|
||||||
|
0% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0.7);
|
||||||
|
}
|
||||||
|
70% {
|
||||||
|
box-shadow: 0 0 0 10px rgba(102, 126, 234, 0);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
box-shadow: 0 0 0 0 rgba(102, 126, 234, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-item {
|
||||||
|
animation: pulse-glow 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* List group items */
|
||||||
|
.list-group-item {
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-group-item:hover {
|
||||||
|
border-color: #667eea;
|
||||||
|
transform: translateX(5px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Enhanced table styling */
|
||||||
|
.table {
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background: #667eea ;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 15px 10px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:hover {
|
||||||
|
background-color: rgba(102, 126, 234, 0.05);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Badge styling */
|
||||||
|
.badge {
|
||||||
|
font-size: 0.85em;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Card enhancements */
|
||||||
|
.card {
|
||||||
|
border: none;
|
||||||
|
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive adjustments */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.btn-add-item,
|
||||||
|
.btn-edit-order,
|
||||||
|
.btn-delete-order {
|
||||||
|
padding: 10px 15px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-gradient-header {
|
||||||
|
padding: 15px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@endsection
|
||||||
43
resources/views/admin/pdf/order_pdf.blade.php
Normal file
43
resources/views/admin/pdf/order_pdf.blade.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Orders Report</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: DejaVu Sans, sans-serif; font-size: 12px; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
||||||
|
th, td { border: 1px solid #ccc; padding: 6px 8px; text-align: left; }
|
||||||
|
th { background: #f3f4f6; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h3>Orders Report</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Order ID</th>
|
||||||
|
<th>Company</th>
|
||||||
|
<th>Invoice No</th>
|
||||||
|
<th>Invoice Status</th>
|
||||||
|
<th>Shipment Status</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($orders as $order)
|
||||||
|
@php
|
||||||
|
$mark = $order->markList ?? null;
|
||||||
|
$invoice = $order->invoice ?? null;
|
||||||
|
$shipment = $order->shipments->first() ?? null;
|
||||||
|
@endphp
|
||||||
|
<tr>
|
||||||
|
<td>{{ $order->order_id ?? '-' }}</td>
|
||||||
|
<td>{{ $mark->company_name ?? '-' }}</td>
|
||||||
|
<td>{{ $invoice->invoice_number ?? '-' }}</td>
|
||||||
|
<td>{{ $invoice->status ?? '-' }}</td>
|
||||||
|
<td>{{ $shipment->status ?? '-' }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,71 +1,438 @@
|
|||||||
<div class="p-4">
|
<!DOCTYPE html>
|
||||||
<!-- Invoice Header -->
|
<html lang="en">
|
||||||
<div class="row mb-4">
|
<head>
|
||||||
<div class="col-md-6">
|
<meta charset="UTF-8">
|
||||||
<h2 class="fw-bold text-primary mb-1">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<i class="fas fa-file-invoice me-2"></i>INVOICE
|
<title>Professional Invoice</title>
|
||||||
</h2>
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<h4 class="fw-bold text-dark mb-0">{{ $invoice->invoice_number }}</h4>
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
</div>
|
<style>
|
||||||
<div class="col-md-6 text-end">
|
:root {
|
||||||
<div class="d-inline-block bg-light rounded-3 p-3">
|
--primary: #2c3e50;
|
||||||
<span class="badge
|
--secondary: #3498db;
|
||||||
@if($invoice->status=='paid') bg-success
|
--accent: #e74c3c;
|
||||||
@elseif($invoice->status=='overdue') bg-danger
|
--light: #f8f9fa;
|
||||||
@elseif($invoice->status=='pending') bg-warning text-dark
|
--dark: #2c3e50;
|
||||||
@else bg-secondary @endif
|
--success: #27ae60;
|
||||||
fs-6 px-3 py-2">
|
--warning: #f39c12;
|
||||||
<i class="fas
|
--danger: #e74c3c;
|
||||||
@if($invoice->status=='paid') fa-check-circle
|
--border-radius: 8px;
|
||||||
@elseif($invoice->status=='overdue') fa-exclamation-circle
|
--box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
@elseif($invoice->status=='pending') fa-clock
|
}
|
||||||
@else fa-question-circle @endif me-1"></i>
|
|
||||||
{{ ucfirst($invoice->status) }}
|
body {
|
||||||
</span>
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
</div>
|
background-color: #f5f7fa;
|
||||||
</div>
|
color: #333;
|
||||||
</div>
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
box-shadow: var(--box-shadow);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-header {
|
||||||
|
background: white;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.invoice-title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 50px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-container {
|
||||||
|
margin-bottom: 1rem; /* Reduced from 1.5rem */
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-box {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 1rem;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-box:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-box-primary {
|
||||||
|
border-left: 4px solid var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-box-secondary {
|
||||||
|
border-left: 4px solid var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-box-accent {
|
||||||
|
border-left: 4px solid var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-icon {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-bottom: 0.5rem; /* Reduced from 0.75rem */
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-icon-primary {
|
||||||
|
background: rgba(52, 152, 219, 0.1);
|
||||||
|
color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-icon-secondary {
|
||||||
|
background: rgba(39, 174, 96, 0.1);
|
||||||
|
color: var(--success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-icon-accent {
|
||||||
|
background: rgba(243, 156, 18, 0.1);
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-label {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6c757d;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-value {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
padding: 1rem; /* Reduced from 1.25rem */
|
||||||
|
margin-bottom: 1rem; /* Reduced from 1.5rem */
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
box-shadow: var(--box-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-card {
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
border: 1px solid rgba(0,0,0,0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-icon {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0 auto 0.5rem; /* Reduced from 0.75rem */
|
||||||
|
background: var(--secondary);
|
||||||
|
color: white;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-label {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #6c757d;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-value {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary);
|
||||||
|
padding: 0.5rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-connector {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-connector i {
|
||||||
|
background: var(--light);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--secondary);
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: 1px solid #e9ecef;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
margin-bottom: 1rem; /* Reduced from 1.5rem */
|
||||||
|
box-shadow: var(--box-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: var(--light);
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
padding: 0.75rem 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
margin-bottom: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table > :not(caption) > * > * {
|
||||||
|
padding: 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table thead th {
|
||||||
|
background-color: var(--light);
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tbody tr:hover {
|
||||||
|
background-color: rgba(52, 152, 219, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-card {
|
||||||
|
background: var(--light);
|
||||||
|
border-left: 4px solid var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-header {
|
||||||
|
background: var(--primary);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-row {
|
||||||
|
border-bottom: 1px solid #e9ecef;
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-row {
|
||||||
|
border-top: 2px solid #dee2e6;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-primary {
|
||||||
|
color: var(--primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-success {
|
||||||
|
color: var(--success) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: var(--danger) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 0.35rem 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* COMPACT HEADER STYLES */
|
||||||
|
.compact-header {
|
||||||
|
margin-bottom: 0.75rem; /* Reduced from default */
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-header .invoice-title {
|
||||||
|
margin-bottom: 0.25rem; /* Reduced gap */
|
||||||
|
}
|
||||||
|
|
||||||
|
.compact-header .status-badge {
|
||||||
|
margin-top: 0.25rem; /* Reduced gap */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.invoice-container {
|
||||||
|
margin: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-connector {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-responsive {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.id-box {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="invoice-container">
|
||||||
|
<div class="p-4">
|
||||||
|
<!-- ============================
|
||||||
|
INVOICE HEADER - COMPACT
|
||||||
|
============================ -->
|
||||||
|
@php
|
||||||
|
$showActions = $showActions ?? true;
|
||||||
|
@endphp
|
||||||
|
|
||||||
<!-- Dates - Compact Professional Layout -->
|
<div class="compact-header">
|
||||||
<div class="row mb-3">
|
<div class="row align-items-center">
|
||||||
<div class="col-12">
|
<div class="col-md-6">
|
||||||
<div class="card border-0 shadow-sm">
|
<h2 class="invoice-title mb-1">
|
||||||
<div class="card-body py-2">
|
<i class="fas fa-file-invoice me-2"></i> INVOICE
|
||||||
<div class="row align-items-center text-center">
|
</h2>
|
||||||
<div class="col-md-5">
|
<h4 class="fw-bold text-dark mb-0">{{ $invoice->invoice_number }}</h4>
|
||||||
<div class="mb-0">
|
</div>
|
||||||
<div class="text-muted fw-semibold small">INVOICE DATE</div>
|
|
||||||
|
<div class="col-md-6 text-end">
|
||||||
|
<span class="status-badge
|
||||||
|
@if($invoice->status=='paid') bg-success
|
||||||
|
@elseif($invoice->status=='overdue') bg-danger
|
||||||
|
@elseif($invoice->status=='pending') bg-warning text-dark
|
||||||
|
@else bg-secondary @endif">
|
||||||
|
<i class="fas
|
||||||
|
@if($invoice->status=='paid') fa-check-circle
|
||||||
|
@elseif($invoice->status=='overdue') fa-exclamation-circle
|
||||||
|
@elseif($invoice->status=='pending') fa-clock
|
||||||
|
@else fa-question-circle @endif me-1"></i>
|
||||||
|
{{ ucfirst($invoice->status) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Three ID Boxes in One Row -->
|
||||||
|
<div class="id-container">
|
||||||
|
<div class="row">
|
||||||
|
<!-- Invoice ID Box -->
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="id-box id-box-primary">
|
||||||
|
<div class="id-icon id-icon-primary">
|
||||||
|
<i class="fas fa-receipt"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="fw-bold text-dark" style="font-size: 0.95rem;">
|
<div class="id-label">Invoice ID</div>
|
||||||
{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('M d, Y') }}
|
<div class="id-value">{{ $invoice->invoice_number }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Order ID Box -->
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="id-box id-box-secondary">
|
||||||
|
<div class="id-icon id-icon-secondary">
|
||||||
|
<i class="fas fa-shopping-cart"></i>
|
||||||
|
</div>
|
||||||
|
<div class="id-label">Order ID</div>
|
||||||
|
<div class="id-value">
|
||||||
|
@if($invoice->order && $invoice->order->order_id)
|
||||||
|
{{ $invoice->order->order_id }}
|
||||||
|
@elseif($invoice->order_id)
|
||||||
|
{{ $invoice->order_id }}
|
||||||
|
@else
|
||||||
|
N/A
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
</div>
|
||||||
<div class="date-connector">
|
|
||||||
<i class="fas fa-arrow-right text-muted small"></i>
|
<!-- Shipment ID Box -->
|
||||||
|
<div class="col-md-4 mb-3">
|
||||||
|
<div class="id-box id-box-accent">
|
||||||
|
<div class="id-icon id-icon-accent">
|
||||||
|
<i class="fas fa-shipping-fast"></i>
|
||||||
|
</div>
|
||||||
|
<div class="id-label">Shipment ID</div>
|
||||||
|
<div class="id-value">
|
||||||
|
@php
|
||||||
|
$shipmentId = 'N/A';
|
||||||
|
// Try multiple ways to get shipment ID
|
||||||
|
if($invoice->shipment && $invoice->shipment->shipment_id) {
|
||||||
|
$shipmentId = $invoice->shipment->shipment_id;
|
||||||
|
} elseif($invoice->shipment_id) {
|
||||||
|
$shipmentId = $invoice->shipment_id;
|
||||||
|
} elseif(isset($shipment) && $shipment && $shipment->shipment_id) {
|
||||||
|
$shipmentId = $shipment->shipment_id;
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
{{ $shipmentId }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-5">
|
</div>
|
||||||
<div class="mb-0">
|
</div>
|
||||||
<div class="text-muted fw-semibold small">DUE DATE</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ============================
|
||||||
|
DATES SECTION
|
||||||
|
============================ -->
|
||||||
|
<div class="date-container">
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="date-card">
|
||||||
|
<div class="date-icon">
|
||||||
|
<i class="fas fa-calendar-alt"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="fw-bold @if($invoice->status == 'overdue') text-danger @else text-dark @endif" style="font-size: 0.95rem;">
|
<div class="date-label">INVOICE DATE</div>
|
||||||
|
<div class="date-value">{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('M d, Y') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div class="date-connector">
|
||||||
|
<i class="fas fa-arrow-right"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-5">
|
||||||
|
<div class="date-card">
|
||||||
|
<div class="date-icon">
|
||||||
|
<i class="fas fa-clock"></i>
|
||||||
|
</div>
|
||||||
|
<div class="date-label">DUE DATE</div>
|
||||||
|
<div class="date-value @if($invoice->status == 'overdue') text-danger @endif">
|
||||||
{{ \Carbon\Carbon::parse($invoice->due_date)->format('M d, Y') }}
|
{{ \Carbon\Carbon::parse($invoice->due_date)->format('M d, Y') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Customer Details -->
|
<!-- ============================
|
||||||
<div class="row mb-4">
|
CUSTOMER DETAILS
|
||||||
<div class="col-12">
|
============================ -->
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card">
|
||||||
<div class="card-header bg-light py-2">
|
<div class="card-header">
|
||||||
<h6 class="mb-0 fw-bold text-dark">
|
<h6 class="mb-0 fw-bold">
|
||||||
<i class="fas fa-user me-2"></i>Customer Details
|
<i class="fas fa-user me-2"></i> Customer Details
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -85,24 +452,25 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<p class="mb-0">
|
<p class="mb-1">
|
||||||
<strong>Address:</strong><br>
|
<strong>Address:</strong><br>
|
||||||
{{ $invoice->customer_address }}
|
{{ $invoice->customer_address }}
|
||||||
</p>
|
</p>
|
||||||
|
<p class="mb-1">
|
||||||
|
<strong>Pincode:</strong> {{ $invoice->pincode }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Invoice Items -->
|
<!-- ============================
|
||||||
<div class="row mb-4">
|
INVOICE ITEMS
|
||||||
<div class="col-12">
|
============================ -->
|
||||||
<div class="card border-0 shadow-sm">
|
<div class="card">
|
||||||
<div class="card-header bg-light py-2">
|
<div class="card-header">
|
||||||
<h6 class="mb-0 fw-bold text-dark">
|
<h6 class="mb-0 fw-bold">
|
||||||
<i class="fas fa-list me-2"></i>Invoice Items
|
<i class="fas fa-list me-2"></i> Invoice Items
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body p-0">
|
<div class="card-body p-0">
|
||||||
@@ -150,58 +518,103 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Final Summary -->
|
<!-- ============================
|
||||||
<div class="row">
|
FINAL SUMMARY
|
||||||
<div class="col-md-6 offset-md-6">
|
============================ -->
|
||||||
<div class="card border-0 bg-light">
|
<div class="row">
|
||||||
<div class="card-header bg-dark text-white py-2">
|
<div class="col-md-6 offset-md-6">
|
||||||
<h6 class="mb-0 fw-bold">
|
<div class="card summary-card">
|
||||||
<i class="fas fa-calculator me-2"></i>Final Summary
|
<div class="card-header summary-header">
|
||||||
</h6>
|
<h6 class="mb-0 fw-bold">
|
||||||
</div>
|
<i class="fas fa-calculator me-2"></i> Final Summary
|
||||||
<div class="card-body">
|
</h6>
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
</div>
|
||||||
<span class="fw-semibold">Amount:</span>
|
<div class="card-body">
|
||||||
<span class="fw-bold text-dark">₹{{ number_format($invoice->final_amount,2) }}</span>
|
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
||||||
</div>
|
<span class="fw-semibold">Amount:</span>
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
<span class="fw-bold text-dark">₹{{ number_format($invoice->final_amount,2) }}</span>
|
||||||
<span class="fw-semibold">GST ({{ $invoice->gst_percent }}%):</span>
|
</div>
|
||||||
<span class="fw-bold text-danger">₹{{ number_format($invoice->gst_amount,2) }}</span>
|
|
||||||
</div>
|
@if($invoice->tax_type === 'gst')
|
||||||
<div class="d-flex justify-content-between align-items-center pt-1">
|
{{-- CGST --}}
|
||||||
<span class="fw-bold text-dark">Total With GST:</span>
|
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
||||||
<span class="fw-bold text-success">₹{{ number_format($invoice->final_amount_with_gst,2) }}</span>
|
<span class="fw-semibold">CGST ({{ $invoice->cgst_percent ?? ($invoice->gst_percent/2) }}%):</span>
|
||||||
|
<span class="fw-bold text-danger">₹{{ number_format($invoice->gst_amount/2, 2) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- SGST --}}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
||||||
|
<span class="fw-semibold">SGST ({{ $invoice->sgst_percent ?? ($invoice->gst_percent/2) }}%):</span>
|
||||||
|
<span class="fw-bold text-danger">₹{{ number_format($invoice->gst_amount/2, 2) }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@elseif($invoice->tax_type === 'igst')
|
||||||
|
{{-- IGST --}}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
||||||
|
<span class="fw-semibold">IGST ({{ $invoice->igst_percent ?? $invoice->gst_percent }}%):</span>
|
||||||
|
<span class="fw-bold text-danger">₹{{ number_format($invoice->gst_amount, 2) }}</span>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
{{-- Default GST --}}
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
|
||||||
|
<span class="fw-semibold">GST ({{ $invoice->gst_percent }}%):</span>
|
||||||
|
<span class="fw-bold text-danger">₹{{ number_format($invoice->gst_amount, 2) }}</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between align-items-center pt-1">
|
||||||
|
<span class="fw-bold text-dark">Total Payable:</span>
|
||||||
|
<span class="fw-bold text-success">₹{{ number_format($invoice->final_amount_with_gst,2) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ============================
|
||||||
|
FOOTER — DOWNLOAD & SHARE
|
||||||
|
============================ -->
|
||||||
|
<div class="mt-4 pt-3 border-top text-center">
|
||||||
|
@if($invoice->pdf_path)
|
||||||
|
<a href="{{ asset($invoice->pdf_path) }}" class="btn btn-primary me-2" download>
|
||||||
|
<i class="fas fa-download me-1"></i> Download PDF
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<button class="btn btn-success" onclick="shareInvoice()">
|
||||||
|
<i class="fas fa-share me-1"></i> Share
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer Message -->
|
||||||
|
<div class="mt-4 pt-3 border-top text-center text-muted">
|
||||||
|
<p class="mb-1">Thank you for your business!</p>
|
||||||
|
<p class="mb-0">For any inquiries, contact us at support@Kent Logistic</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
.date-connector {
|
|
||||||
display: flex;
|
<!-- ============================
|
||||||
align-items: center;
|
SHARE SCRIPT
|
||||||
justify-content: center;
|
============================ -->
|
||||||
height: 100%;
|
<script>
|
||||||
color: #6c757d;
|
function shareInvoice() {
|
||||||
}
|
const shareData = {
|
||||||
.date-connector i {
|
title: "Invoice {{ $invoice->invoice_number }}",
|
||||||
background: #f8f9fa;
|
text: "Sharing invoice {{ $invoice->invoice_number }}",
|
||||||
padding: 4px;
|
url: "{{ asset($invoice->pdf_path) }}"
|
||||||
border-radius: 50%;
|
};
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
if (navigator.share) {
|
||||||
.card {
|
navigator.share(shareData).catch(() => {});
|
||||||
border-radius: 6px;
|
} else {
|
||||||
}
|
navigator.clipboard.writeText(shareData.url);
|
||||||
.table {
|
alert("Link copied! Sharing not supported on this browser.");
|
||||||
margin-bottom: 0;
|
}
|
||||||
}
|
}
|
||||||
.table > :not(caption) > * > * {
|
</script>
|
||||||
padding: 10px 6px;
|
</body>
|
||||||
}
|
</html>
|
||||||
</style>
|
|
||||||
111
resources/views/admin/profile_update_requests.blade.php
Normal file
111
resources/views/admin/profile_update_requests.blade.php
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('page-title', 'Profile Update Requests')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="container-fluid px-0">
|
||||||
|
|
||||||
|
@php
|
||||||
|
$perPage = 5;
|
||||||
|
$currentPage = request()->get('page', 1);
|
||||||
|
$currentPage = max(1, (int)$currentPage);
|
||||||
|
$total = $requests->count();
|
||||||
|
$totalPages = ceil($total / $perPage);
|
||||||
|
$currentItems = $requests->slice(($currentPage - 1) * $perPage, $perPage);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.old-value { color: #6b7280; font-weight: 600; }
|
||||||
|
.new-value { color: #111827; font-weight: 700; }
|
||||||
|
.changed { background: #fef3c7; padding: 6px; border-radius: 6px; }
|
||||||
|
.box { padding: 10px 14px; border-radius: 8px; background: #f8fafc; margin-bottom: 10px; }
|
||||||
|
.diff-label { font-size: 13px; font-weight: 700; }
|
||||||
|
.actions { display: flex; gap: 10px; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h4 class="fw-bold my-3">Profile Update Requests ({{ $total }})</h4>
|
||||||
|
|
||||||
|
<div class="card mb-4 shadow-sm">
|
||||||
|
<div class="card-body pb-1">
|
||||||
|
|
||||||
|
<div class="table-responsive custom-table-wrapper">
|
||||||
|
<table class="table align-middle mb-0 custom-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Requested Changes</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Requested At</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
@foreach($currentItems as $index => $req)
|
||||||
|
@php
|
||||||
|
$user = $req->user;
|
||||||
|
// FIX: Convert string to array
|
||||||
|
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td><strong>{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong></td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<strong>{{ $user->customer_name }}</strong><br>
|
||||||
|
<small>{{ $user->email }}</small><br>
|
||||||
|
<small>ID: {{ $user->customer_id }}</small>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
@foreach($newData as $key => $newValue)
|
||||||
|
@php
|
||||||
|
$oldValue = $user->$key ?? '—';
|
||||||
|
$changed = $oldValue != $newValue;
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="box {{ $changed ? 'changed' : '' }}">
|
||||||
|
<span class="diff-label">{{ ucfirst(str_replace('_',' ', $key)) }}:</span><br>
|
||||||
|
<span class="old-value">Old: {{ $oldValue }}</span><br>
|
||||||
|
<span class="new-value">New: {{ $newValue ?? '—' }}</span>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
@if($req->status == 'pending')
|
||||||
|
<span class="badge badge-pending">Pending</span>
|
||||||
|
@elseif($req->status == 'approved')
|
||||||
|
<span class="badge badge-approved">Approved</span>
|
||||||
|
@else
|
||||||
|
<span class="badge badge-rejected">Rejected</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{{ $req->created_at->format('d M Y, h:i A') }}</td>
|
||||||
|
|
||||||
|
<td class="actions">
|
||||||
|
@if($req->status == 'pending')
|
||||||
|
<a href="{{ route('admin.profile.approve', $req->id) }}" class="btn btn-success btn-sm">
|
||||||
|
<i class="bi bi-check-circle"></i> Approve
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{{ route('admin.profile.reject', $req->id) }}" class="btn btn-danger btn-sm">
|
||||||
|
<i class="bi bi-x-circle"></i> Reject
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<span class="text-muted">Completed</span>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,225 +5,458 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
<div class="container-fluid px-0">
|
<div class="container-fluid px-0">
|
||||||
|
|
||||||
|
@php
|
||||||
|
$perPage = 5; // ✅ FORCED to show pagination even with few records
|
||||||
|
$currentPage = request()->get('page', 1);
|
||||||
|
$currentPage = max(1, (int)$currentPage);
|
||||||
|
$total = $requests->count();
|
||||||
|
$totalPages = ceil($total / $perPage);
|
||||||
|
$currentItems = $requests->slice(($currentPage - 1) * $perPage, $perPage);
|
||||||
|
@endphp
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* 🌟 Smooth fade-in animation */
|
/* [ALL YOUR ORIGINAL CSS HERE - SAME AS BEFORE] */
|
||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {0% { transform: translateY(20px); opacity: 0; }100% { transform: translateY(0); opacity: 1; }}
|
||||||
0% {
|
.card, .custom-table-wrapper { animation: fadeInUp 0.8s ease both; }
|
||||||
transform: translateY(20px);
|
.custom-table tbody tr { transition: all 0.25s ease-in-out; }
|
||||||
opacity: 0;
|
.custom-table tbody tr:hover { background-color: #fffbea; transform: scale(1.01); box-shadow: 0 2px 6px rgba(0,0,0,0.05); }
|
||||||
}
|
.priority-badge {
|
||||||
100% {
|
display: inline-flex; align-items: center; font-size: 13.5px; padding: 6px 16px; border-radius: 12px; font-weight: 600;
|
||||||
transform: translateY(0);
|
box-shadow: 0 1px 2px 0 rgba(130,130,130,0.15); width: 90px; min-height: 28px; justify-content: center;
|
||||||
opacity: 1;
|
color: #fff; margin: 2px 0; transition: transform 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
.priority-badge:hover { transform: scale(1.08); }
|
||||||
|
.priority-high { background: linear-gradient(135deg, #ff8a8a, #d12929); }
|
||||||
|
.priority-medium { background: linear-gradient(135deg, #ffe390, #f5b041); }
|
||||||
|
.priority-low { background: linear-gradient(135deg, #b8f0c2, #1d8660); }
|
||||||
|
.custom-table thead th {
|
||||||
|
text-align: center; font-weight: 700; color: #000; padding: 14px; font-size: 17px; letter-spacing: 0.5px;
|
||||||
|
border-bottom: 2px solid #bfbfbf; background-color: #fde4b3;
|
||||||
|
}
|
||||||
|
.custom-table thead tr:first-child th:first-child { border-top-left-radius: 12px; }
|
||||||
|
.custom-table thead tr:first-child th:last-child { border-top-right-radius: 12px; }
|
||||||
|
.custom-table tbody tr:last-child td:first-child { border-bottom-left-radius: 12px; }
|
||||||
|
.custom-table tbody tr:last-child td:last-child { border-bottom-right-radius: 12px; }
|
||||||
|
.input-group input { border-radius: 10px 0 0 10px; border: 1px solid #ccc; box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); }
|
||||||
|
.input-group .btn { border-radius: 0 10px 10px 0; transition: all 0.2s ease-in-out; }
|
||||||
|
.input-group .btn:hover { background: #ffd65a; border-color: #ffd65a; color: #000; }
|
||||||
|
.card { border-radius: 16px; border: none; box-shadow: 0 4px 10px rgba(0,0,0,0.08); background: #fff; }
|
||||||
|
.badge {
|
||||||
|
font-size: 11px !important; font-weight: 600 !important; padding: 7px 13px !important; border-radius: 20px !important;
|
||||||
|
text-transform: uppercase; letter-spacing: 0.3px; display: inline-flex !important; align-items: center; justify-content: center;
|
||||||
|
color: #fff !important; text-shadow: 0 1px 2px rgba(0,0,0,0.3); border: 2px solid transparent !important;
|
||||||
|
line-height: 1.2; gap: 6px; animation: pulse 2s infinite; width: 99px;
|
||||||
|
}
|
||||||
|
.status-icon { font-size: 0px; display: flex; align-items: center; justify-content: center; }
|
||||||
|
.badge.badge-pending { background: linear-gradient(135deg, #fef3c7, #fde68a) !important; color: #d97706 !important; border-color: #f59e0b !important; width: 85px; }
|
||||||
|
.badge.badge-approved { background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important; color: #065f46 !important; border-color: #10b981 !important; width: 85px; }
|
||||||
|
.badge.badge-rejected { background: linear-gradient(135deg, #fecaca, #fca5a5) !important; color: #991b1b !important; border-color: #ef4444 !important; width: 85px; }
|
||||||
|
@keyframes pulse {0% { box-shadow: 0 0 8px rgba(0,0,0,0.1); }50% { box-shadow: 0 0 14px rgba(0,0,0,0.15); }100% { box-shadow: 0 0 8px rgba(0,0,0,0.1); }}
|
||||||
|
.count-badge { --bs-badge-padding-x: 0.65em; --bs-badge-padding-y: 0.35em; --bs-badge-font-size: 0.75em; --bs-badge-font-weight: 700; --bs-badge-color: #fff; --bs-badge-border-radius: var(--bs-border-radius); }
|
||||||
|
h4.fw-bold { background: linear-gradient(90deg, #000000ff 0%, #030302ff 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 800; letter-spacing: 1px; }
|
||||||
|
.custom-table tbody td:last-child { text-align: center !important; vertical-align: middle !important; }
|
||||||
|
|
||||||
|
/* ===== PAGINATION STYLES ===== */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px 0;
|
||||||
|
border-top: 1px solid #eef3fb;
|
||||||
|
}
|
||||||
|
|
||||||
/* ✨ Container animation */
|
.pagination-info {
|
||||||
.card, .custom-table-wrapper {
|
font-size: 13px;
|
||||||
animation: fadeInUp 0.8s ease both;
|
color: #9ba5bb;
|
||||||
}
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
/* 🪄 Table hover effect */
|
.pagination-controls {
|
||||||
.custom-table tbody tr {
|
display: flex;
|
||||||
transition: all 0.25s ease-in-out;
|
align-items: center;
|
||||||
}
|
gap: 8px;
|
||||||
.custom-table tbody tr:hover {
|
}
|
||||||
background-color: #fffbea;
|
|
||||||
transform: scale(1.01);
|
|
||||||
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🎯 Priority Badges */
|
.pagination-btn {
|
||||||
.priority-badge {
|
background: #fff;
|
||||||
display: inline-flex;
|
border: 1px solid #e3eaf6;
|
||||||
align-items: center;
|
color: #1a2951;
|
||||||
font-size: 13.5px;
|
padding: 8px 12px;
|
||||||
padding: 4px 16px 4px 11px;
|
border-radius: 6px;
|
||||||
border-radius: 12px;
|
font-size: 13px;
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
gap: 6px;
|
cursor: pointer;
|
||||||
box-shadow: 0 1px 2px 0 rgba(130,130,130,0.15);
|
transition: all 0.3s ease;
|
||||||
min-width: 76px;
|
display: flex;
|
||||||
min-height: 28px;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #fff;
|
min-width: 40px;
|
||||||
margin: 2px 0;
|
height: 32px;
|
||||||
transition: transform 0.2s ease-in-out;
|
}
|
||||||
}
|
|
||||||
.priority-badge:hover {
|
|
||||||
transform: scale(1.08);
|
|
||||||
}
|
|
||||||
.priority-high { background: linear-gradient(135deg, #ff8a8a, #d12929); color: #fff; }
|
|
||||||
.priority-medium { background: linear-gradient(135deg, #ffe390, #f5b041); color: #fff; }
|
|
||||||
.priority-low { background: linear-gradient(135deg, #b8f0c2, #1d8660); color: #fff; }
|
|
||||||
|
|
||||||
.priority-badge .icon {
|
.pagination-btn:hover:not(:disabled) {
|
||||||
margin-right: 5px;
|
background: #1a2951;
|
||||||
font-size: 1.1em;
|
color: white;
|
||||||
line-height: 1;
|
border-color: #1a2951;
|
||||||
display:inline-block;
|
}
|
||||||
width: 17px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🎨 Table Header (keep original bg-light color) */
|
.pagination-btn:disabled {
|
||||||
.custom-table thead th {
|
background: #f8fafc;
|
||||||
text-align: center;
|
color: #cbd5e0;
|
||||||
font-weight: 700;
|
border-color: #e2e8f0;
|
||||||
color: #000;
|
cursor: not-allowed;
|
||||||
padding: 14px;
|
opacity: 0.6;
|
||||||
font-size: 17px;
|
}
|
||||||
letter-spacing: 0.5px;
|
|
||||||
border-bottom: 2px solid #bfbfbf;
|
|
||||||
background-color: #fde4b3 /* Bootstrap bg-light */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🟢 Rounded Corners for Header */
|
.pagination-page-btn {
|
||||||
.custom-table thead tr:first-child th:first-child {
|
background: #fff;
|
||||||
border-top-left-radius: 12px;
|
border: 1px solid #e3eaf6;
|
||||||
}
|
color: #1a2951;
|
||||||
.custom-table thead tr:first-child th:last-child {
|
padding: 6px 12px;
|
||||||
border-top-right-radius: 12px;
|
border-radius: 6px;
|
||||||
}
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
min-width: 36px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* 🧾 Table bottom corners */
|
.pagination-page-btn:hover {
|
||||||
.custom-table tbody tr:last-child td:first-child {
|
background: #1a2951;
|
||||||
border-bottom-left-radius: 12px;
|
color: white;
|
||||||
}
|
border-color: #1a2951;
|
||||||
.custom-table tbody tr:last-child td:last-child {
|
}
|
||||||
border-bottom-right-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 💫 Search box styling */
|
.pagination-page-btn.active {
|
||||||
.input-group input {
|
background: #1a2951;
|
||||||
border-radius: 10px 0 0 10px;
|
color: white;
|
||||||
border: 1px solid #ccc;
|
border-color: #1a2951;
|
||||||
box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
|
}
|
||||||
}
|
|
||||||
.input-group .btn {
|
|
||||||
border-radius: 0 10px 10px 0;
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
}
|
|
||||||
.input-group .btn:hover {
|
|
||||||
background: #ffd65a;
|
|
||||||
border-color: #ffd65a;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 🎨 Card Style */
|
.pagination-pages {
|
||||||
.card {
|
display: flex;
|
||||||
border-radius: 16px;
|
gap: 4px;
|
||||||
border: none;
|
align-items: center;
|
||||||
box-shadow: 0 4px 10px rgba(0,0,0,0.08);
|
}
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ⚡ Status Badges */
|
.pagination-ellipsis {
|
||||||
.badge {
|
color: #9ba5bb;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
padding: 7px 12px;
|
padding: 0 4px;
|
||||||
border-radius: 10px;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/* ✨ Heading style */
|
.pagination-img-btn {
|
||||||
h4.fw-bold {
|
background: #fff;
|
||||||
background: linear-gradient(90deg, #000000ff 0%, #030302ff 100%);
|
border: 1px solid #e3eaf6;
|
||||||
-webkit-background-clip: text;
|
border-radius: 6px;
|
||||||
-webkit-text-fill-color: transparent;
|
cursor: pointer;
|
||||||
font-weight: 800;
|
transition: all 0.3s ease;
|
||||||
letter-spacing: 1px;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* ✅ Center align for last column */
|
.pagination-img-btn:hover:not(:disabled) {
|
||||||
.custom-table tbody td:last-child {
|
background: #1a2951;
|
||||||
text-align: center !important;
|
border-color: #1a2951;
|
||||||
vertical-align: middle !important;
|
}
|
||||||
}
|
|
||||||
|
.pagination-img-btn:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn svg {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
transition: filter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:hover:not(:disabled) svg {
|
||||||
|
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:disabled svg {
|
||||||
|
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== UPDATED SEARCH BAR STYLES ===== */
|
||||||
|
.search-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-group {
|
||||||
|
display: flex;
|
||||||
|
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input {
|
||||||
|
flex: 1;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-right: none;
|
||||||
|
padding: 10px 16px;
|
||||||
|
font-size: 14px;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button {
|
||||||
|
background: #3b82f6;
|
||||||
|
border: 1px solid #3b82f6;
|
||||||
|
color: white;
|
||||||
|
padding: 10px 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-button:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
border-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badges {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 20px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-pending {
|
||||||
|
background: linear-gradient(135deg, #fef3c7, #fde68a);
|
||||||
|
color: #d97706;
|
||||||
|
border: 1px solid #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-approved {
|
||||||
|
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
|
||||||
|
color: #065f46;
|
||||||
|
border: 1px solid #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badge-rejected {
|
||||||
|
background: linear-gradient(135deg, #fecaca, #fca5a5);
|
||||||
|
color: #991b1b;
|
||||||
|
border: 1px solid #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive styles */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.pagination-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
.pagination-controls {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-form {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-badges {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- Heading and status badges row -->
|
<!-- Counts -->
|
||||||
<div class="d-flex justify-content-between align-items-center mb-2 mt-3">
|
<div class="d-flex justify-content-between align-items-center mb-2 mt-3">
|
||||||
<h4 class="fw-bold mb-0">User Requests</h4>
|
<h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4>
|
||||||
<div>
|
|
||||||
<span class="badge rounded-pill bg-primary me-1">
|
|
||||||
{{ $requests->where('status', 'pending')->count() }} Pending
|
|
||||||
</span>
|
|
||||||
<span class="badge rounded-pill bg-success me-1">
|
|
||||||
{{ $requests->where('status', 'approved')->count() }} Approved
|
|
||||||
</span>
|
|
||||||
<span class="badge rounded-pill bg-danger">
|
|
||||||
{{ $requests->where('status', 'rejected')->count() }} Rejected
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Search + Table -->
|
||||||
<div class="card mb-4 shadow-sm">
|
<div class="card mb-4 shadow-sm">
|
||||||
<div class="card-body pb-1">
|
<div class="card-body pb-1">
|
||||||
<form method="GET" action="">
|
<!-- Updated Search Bar with Status Badges in the same line -->
|
||||||
<div class="input-group mb-2">
|
<div class="search-container">
|
||||||
<input type="text" name="search" value="{{ old('search', request('search')) }}" class="form-control" placeholder="Search Request by name, email, Company or Request ID">
|
<form method="GET" action="" class="search-form">
|
||||||
<button class="btn btn-outline-primary" type="submit"><i class="bi bi-search"></i></button>
|
<div class="search-input-group">
|
||||||
|
<input type="text" name="search" value="{{ request('search') }}" class="search-input" placeholder="Search by name, email, company...">
|
||||||
|
<button class="search-button" type="submit">
|
||||||
|
<svg class="search-icon" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M21 21L16.514 16.506L21 21ZM19 10.5C19 15.194 15.194 19 10.5 19C5.806 19 2 15.194 2 10.5C2 5.806 5.806 2 10.5 2C15.194 2 19 5.806 19 10.5Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div class="status-badges">
|
||||||
|
<span class="status-badge status-badge-pending">{{ $requests->where('status', 'pending')->count() }} Pending</span>
|
||||||
|
<span class="status-badge status-badge-approved">{{ $requests->where('status', 'approved')->count() }} Approved</span>
|
||||||
|
<span class="status-badge status-badge-rejected">{{ $requests->where('status', 'rejected')->count() }} Rejected</span>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive custom-table-wrapper">
|
<div class="table-responsive custom-table-wrapper">
|
||||||
<table class="table align-middle mb-0 custom-table">
|
<table class="table align-middle mb-0 custom-table">
|
||||||
<thead class="bg-light">
|
<thead><tr>
|
||||||
<tr>
|
<th>#</th><th>Request ID</th><th>Name</th><th>Company</th><th>Email</th><th>Mobile</th><th>Address</th><th>Priority</th><th>Date</th><th>Status</th><th>Actions</th>
|
||||||
<th>#</th>
|
</tr></thead>
|
||||||
<th>Request ID</th>
|
|
||||||
<th>Name</th>
|
|
||||||
<th>Company</th>
|
|
||||||
<th>Email</th>
|
|
||||||
<th>Mobile</th>
|
|
||||||
<th>Address</th>
|
|
||||||
<th>Priority</th>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Status</th>
|
|
||||||
<th>Actions</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
<tbody>
|
||||||
@foreach($requests as $index => $req)
|
@forelse($currentItems as $index => $req)
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $requests->count() - $index }}</td>
|
<td><strong>{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong></td>
|
||||||
<td>{{ $req->request_id }}</td>
|
<td>{{ $req->request_id }}</td>
|
||||||
<td>{{ $req->customer_name }}</td>
|
<td>{{ $req->customer_name }}</td>
|
||||||
<td>{{ $req->company_name }}</td>
|
<td>{{ $req->company_name }}</td>
|
||||||
<td>{{ $req->email }}</td>
|
<td>{{ $req->email }}</td>
|
||||||
<td>{{ $req->mobile_no }}</td>
|
<td>{{ $req->mobile_no }}</td>
|
||||||
<td>{{ $req->address }}</td>
|
<td>{{ Str::limit($req->address, 30) }}</td>
|
||||||
<td>
|
<td>
|
||||||
@if(strtolower($req->priority) == 'high')
|
@if(strtolower($req->priority) == 'high')<span class="priority-badge priority-high">High</span>
|
||||||
<span class="priority-badge priority-high"><span class="icon">🔥</span>High</span>
|
@elseif(strtolower($req->priority) == 'medium')<span class="priority-badge priority-medium">Medium</span>
|
||||||
@elseif(strtolower($req->priority) == 'medium')
|
@elseif(strtolower($req->priority) == 'low')<span class="priority-badge priority-low">Low</span>
|
||||||
<span class="priority-badge priority-medium"><span class="icon">⚡</span>Medium</span>
|
@else{{ $req->priority ?? 'N/A' }}@endif
|
||||||
@elseif(strtolower($req->priority) == 'low')
|
|
||||||
<span class="priority-badge priority-low"><span class="icon">🟢</span>Low</span>
|
|
||||||
@else
|
|
||||||
{{ $req->priority }}
|
|
||||||
@endif
|
|
||||||
</td>
|
</td>
|
||||||
<td>{{ $req->date }}</td>
|
<td>{{ $req->date }}</td>
|
||||||
<td>
|
<td>
|
||||||
@if($req->status == 'approved')
|
@if($req->status == 'approved')<span class="badge badge-approved"><i class="bi bi-check-circle-fill status-icon"></i>Approved</span>
|
||||||
<span class="badge bg-success">Approved</span>
|
@elseif($req->status == 'rejected')<span class="badge badge-rejected"><i class="bi bi-x-circle-fill status-icon"></i>Rejected</span>
|
||||||
@elseif($req->status == 'rejected')
|
@else<span class="badge badge-pending"><i class="bi bi-clock-fill status-icon"></i>Pending</span>@endif
|
||||||
<span class="badge bg-danger">Rejected</span>
|
</td>
|
||||||
|
<td>
|
||||||
|
@if($req->status == 'pending')
|
||||||
|
<a href="{{ route('admin.requests.approve', $req->id) }}"
|
||||||
|
class="btn btn-success btn-sm">
|
||||||
|
<i class="bi bi-check-circle"></i> Approve
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="{{ route('admin.requests.reject', $req->id) }}"
|
||||||
|
class="btn btn-danger btn-sm">
|
||||||
|
<i class="bi bi-x-circle"></i> Reject
|
||||||
|
</a>
|
||||||
@else
|
@else
|
||||||
<span class="badge bg-warning text-dark">Pending</span>
|
<span class="text-muted">No Action</span>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</td>
|
||||||
<td>N/A</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
@endforeach
|
@empty
|
||||||
|
<tr><td colspan="11" class="text-center text-muted py-4">No records found.</td></tr>
|
||||||
|
@endforelse
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@if($requests->isEmpty())
|
</div>
|
||||||
<div class="py-4 text-center text-muted">No records found.</div>
|
|
||||||
@endif
|
{{-- ✅ PAGINATION - WITH ARROW BUTTONS --}}
|
||||||
|
<div class="pagination-container">
|
||||||
|
<div class="pagination-info">
|
||||||
|
Showing {{ ($currentPage - 1) * $perPage + 1 }} to {{ min($currentPage * $perPage, $total) }} of {{ $total }} entries
|
||||||
|
</div>
|
||||||
|
<div class="pagination-controls">
|
||||||
|
{{-- Previous Page --}}
|
||||||
|
@if($currentPage > 1)
|
||||||
|
<a href="{{ request()->fullUrlWithQuery(['page' => $currentPage - 1]) }}" class="pagination-img-btn" title="Previous page">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<button class="pagination-img-btn" disabled title="Previous page">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Page Numbers --}}
|
||||||
|
<div class="pagination-pages">
|
||||||
|
@php
|
||||||
|
$start = max(1, $currentPage - 1);
|
||||||
|
$end = min($totalPages, $currentPage + 1);
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
@if($start > 1)
|
||||||
|
<a href="{{ request()->fullUrlWithQuery(['page' => 1]) }}" class="pagination-page-btn">1</a>
|
||||||
|
@if($start > 2)
|
||||||
|
<span class="pagination-ellipsis">...</span>
|
||||||
|
@endif
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@for($i = $start; $i <= $end; $i++)
|
||||||
|
@if($i == $currentPage)
|
||||||
|
<span class="pagination-page-btn active">{{ $i }}</span>
|
||||||
|
@else
|
||||||
|
<a href="{{ request()->fullUrlWithQuery(['page' => $i]) }}" class="pagination-page-btn">{{ $i }}</a>
|
||||||
|
@endif
|
||||||
|
@endfor
|
||||||
|
|
||||||
|
@if($end < $totalPages)
|
||||||
|
@if($end < $totalPages - 1)
|
||||||
|
<span class="pagination-ellipsis">...</span>
|
||||||
|
@endif
|
||||||
|
<a href="{{ request()->fullUrlWithQuery(['page' => $totalPages]) }}" class="pagination-page-btn">{{ $totalPages }}</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Next Page --}}
|
||||||
|
@if($currentPage < $totalPages)
|
||||||
|
<a href="{{ request()->fullUrlWithQuery(['page' => $currentPage + 1]) }}" class="pagination-img-btn" title="Next page">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
@else
|
||||||
|
<button class="pagination-img-btn" disabled title="Next page">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
||||||
@@ -258,6 +258,14 @@
|
|||||||
line-height: 1.2 !important;
|
line-height: 1.2 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Loading Status - PROPER */
|
||||||
|
.badge-loading {
|
||||||
|
background: linear-gradient(135deg, #e3f2fd, #90caf9) !important;
|
||||||
|
color: #1565c0 !important;
|
||||||
|
border-color: #2196f3 !important;
|
||||||
|
width: 110px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Pending Status - SAME SIZE */
|
/* Pending Status - SAME SIZE */
|
||||||
.badge-pending {
|
.badge-pending {
|
||||||
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
|
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
|
||||||
@@ -320,7 +328,30 @@
|
|||||||
padding: 6px 12px !important;
|
padding: 6px 12px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NEW: Action Button Styles */
|
/* Eye Button Style - PROPER */
|
||||||
|
.btn-eye {
|
||||||
|
background: linear-gradient(135deg, #4cc9f0, #4361ee);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-eye:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(76, 201, 240, 0.3);
|
||||||
|
background: linear-gradient(135deg, #38bdf8, #3a56d4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Action Button Styles */
|
||||||
.action-container {
|
.action-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -368,6 +399,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Loading Status Option - PROPER */
|
||||||
|
.status-option.loading {
|
||||||
|
background: rgba(33, 150, 243, 0.1);
|
||||||
|
color: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-option.loading:hover {
|
||||||
|
background: rgba(33, 150, 243, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.status-option {
|
.status-option {
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -430,6 +471,11 @@
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Loading Status Indicator - PROPER */
|
||||||
|
.status-indicator.loading {
|
||||||
|
background: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
.status-indicator.pending {
|
.status-indicator.pending {
|
||||||
background: #f8961e;
|
background: #f8961e;
|
||||||
@@ -917,6 +963,151 @@
|
|||||||
.shipment-row.visible {
|
.shipment-row.visible {
|
||||||
display: table-row;
|
display: table-row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ---------- Pagination Styles (Same as Account Dashboard) ---------- */
|
||||||
|
.pagination-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px 25px;
|
||||||
|
border-top: 1px solid #eef3fb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ba5bb;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
color: #1a2951;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:hover:not(:disabled) {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-btn:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #cbd5e0;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
color: #1a2951;
|
||||||
|
padding: 6px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
min-width: 36px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn:hover {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-page-btn.active {
|
||||||
|
background: #1a2951;
|
||||||
|
color: white;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-pages {
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-ellipsis {
|
||||||
|
color: #9ba5bb;
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Image-based pagination buttons */
|
||||||
|
.pagination-img-btn {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e3eaf6;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 40px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:hover:not(:disabled) {
|
||||||
|
background: #1a2951;
|
||||||
|
border-color: #1a2951;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:disabled {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
filter: brightness(0) saturate(100%) invert(26%) sepia(89%) saturate(748%) hue-rotate(201deg) brightness(93%) contrast(89%);
|
||||||
|
transition: filter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:hover:not(:disabled) img {
|
||||||
|
filter: brightness(0) saturate(100%) invert(100%) sepia(100%) saturate(0%) hue-rotate(288deg) brightness(106%) contrast(101%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-img-btn:disabled img {
|
||||||
|
filter: brightness(0) saturate(100%) invert(84%) sepia(8%) saturate(165%) hue-rotate(179deg) brightness(89%) contrast(86%);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.pagination-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-controls {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
@@ -938,15 +1129,13 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<!-- ============================= -->
|
|
||||||
<!-- SEARCH BAR AND ADD BUTTON -->
|
|
||||||
<!-- ============================= -->
|
|
||||||
<div class="search-shipment-bar">
|
<div class="search-shipment-bar">
|
||||||
<span class="search-icon">🔍</span>
|
<span class="search-icon">🔍</span>
|
||||||
<input type="text" id="searchInput" placeholder="Search Shipments...">
|
<input type="text" id="searchInput" placeholder="Search Shipments...">
|
||||||
<div class="status-filter-container">
|
<div class="status-filter-container">
|
||||||
<select id="statusFilter" class="status-filter-select">
|
<select id="statusFilter" class="status-filter-select">
|
||||||
<option value="all">All Status</option>
|
<option value="all">All Status</option>
|
||||||
|
<option value="loading">Loading</option>
|
||||||
<option value="pending">Pending</option>
|
<option value="pending">Pending</option>
|
||||||
<option value="in_transit">In Transit</option>
|
<option value="in_transit">In Transit</option>
|
||||||
<option value="dispatched">Dispatched</option>
|
<option value="dispatched">Dispatched</option>
|
||||||
@@ -965,9 +1154,6 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============================= -->
|
|
||||||
<!-- CREATE SHIPMENT MODAL -->
|
|
||||||
<!-- ============================= -->
|
|
||||||
<div class="modal fade" id="createShipmentModal" tabindex="-1" aria-labelledby="createShipmentModalLabel" aria-hidden="true">
|
<div class="modal fade" id="createShipmentModal" tabindex="-1" aria-labelledby="createShipmentModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-xl">
|
<div class="modal-dialog modal-xl">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -1059,9 +1245,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============================= -->
|
|
||||||
<!-- SHIPMENT LIST TABLE -->
|
|
||||||
<!-- ============================= -->
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0"><i class="bi bi-truck me-2"></i> Shipments List</h5>
|
<h5 class="mb-0"><i class="bi bi-truck me-2"></i> Shipments List</h5>
|
||||||
@@ -1082,6 +1265,7 @@
|
|||||||
<th>Total Amount</th>
|
<th>Total Amount</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
|
<th>View</th>
|
||||||
<th>Action</th>
|
<th>Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -1112,6 +1296,12 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ \Carbon\Carbon::parse($ship->shipment_date)->format('d M Y') }}</td>
|
<td>{{ \Carbon\Carbon::parse($ship->shipment_date)->format('d M Y') }}</td>
|
||||||
|
<td>
|
||||||
|
<button type="button" class="btn-eye" onclick="openShipmentDetails({{ $ship->id }})" title="View Shipment">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
</button>
|
||||||
|
</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, {{ $ship->id }})" title="Edit Status">
|
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, {{ $ship->id }})" title="Edit Status">
|
||||||
@@ -1121,6 +1311,10 @@
|
|||||||
<form action="{{ route('admin.shipments.updateStatus') }}" method="POST" class="status-form">
|
<form action="{{ route('admin.shipments.updateStatus') }}" method="POST" class="status-form">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="shipment_id" value="{{ $ship->id }}">
|
<input type="hidden" name="shipment_id" value="{{ $ship->id }}">
|
||||||
|
<button type="submit" name="status" value="loading" class="status-option loading">
|
||||||
|
<span class="status-indicator loading"></span>
|
||||||
|
Loading
|
||||||
|
</button>
|
||||||
<button type="submit" name="status" value="pending" class="status-option pending">
|
<button type="submit" name="status" value="pending" class="status-option pending">
|
||||||
<span class="status-indicator pending"></span>
|
<span class="status-indicator pending"></span>
|
||||||
Pending
|
Pending
|
||||||
@@ -1144,7 +1338,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="11" class="text-center py-5 text-muted">
|
<td colspan="12" class="text-center py-5 text-muted">
|
||||||
<i class="bi bi-inbox display-4 d-block mb-3"></i>
|
<i class="bi bi-inbox display-4 d-block mb-3"></i>
|
||||||
No shipments found
|
No shipments found
|
||||||
</td>
|
</td>
|
||||||
@@ -1153,12 +1347,28 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="pagination-container">
|
||||||
|
<div class="pagination-info" id="pageInfo">Showing 1 to {{ $shipments->count() }} of {{ $shipments->count() }} entries</div>
|
||||||
|
<div class="pagination-controls">
|
||||||
|
<button class="pagination-img-btn" id="prevPageBtn" title="Previous page" disabled>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10 12L6 8L10 4" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div class="pagination-pages" id="paginationPages">
|
||||||
|
</div>
|
||||||
|
<button class="pagination-img-btn" id="nextPageBtn" title="Next page" disabled>
|
||||||
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M6 4L10 8L6 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============================= -->
|
{{-- SHIPMENT DETAILS MODAL --}}
|
||||||
<!-- SHIPMENT DETAILS MODAL -->
|
|
||||||
<!-- ============================= -->
|
|
||||||
<div class="modal fade" id="shipmentDetailsModal" tabindex="-1">
|
<div class="modal fade" id="shipmentDetailsModal" tabindex="-1">
|
||||||
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -1181,16 +1391,26 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ========================= -->
|
|
||||||
<!-- MODAL LOAD SCRIPT (AJAX) -->
|
|
||||||
<!-- ========================= -->
|
|
||||||
<script>
|
<script>
|
||||||
// Status Filter Functionality
|
// Pagination state
|
||||||
|
let currentPage = 1;
|
||||||
|
const itemsPerPage = 10;
|
||||||
|
let allShipments = @json($shipments);
|
||||||
|
let filteredShipments = [...allShipments];
|
||||||
|
|
||||||
|
// Initialize pagination on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
|
||||||
|
// Bind pagination events
|
||||||
|
document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
|
||||||
|
document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
|
||||||
|
|
||||||
|
// Status Filter Functionality
|
||||||
const statusFilter = document.getElementById('statusFilter');
|
const statusFilter = document.getElementById('statusFilter');
|
||||||
const searchInput = document.getElementById('searchInput');
|
const searchInput = document.getElementById('searchInput');
|
||||||
const carrierFilter = document.getElementById('carrierFilter');
|
const carrierFilter = document.getElementById('carrierFilter');
|
||||||
const shipmentRows = document.querySelectorAll('.shipment-row');
|
|
||||||
|
|
||||||
// Function to filter shipments
|
// Function to filter shipments
|
||||||
function filterShipments() {
|
function filterShipments() {
|
||||||
@@ -1198,48 +1418,34 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const searchTerm = searchInput.value.toLowerCase();
|
const searchTerm = searchInput.value.toLowerCase();
|
||||||
const selectedCarrier = carrierFilter.value;
|
const selectedCarrier = carrierFilter.value;
|
||||||
|
|
||||||
shipmentRows.forEach(row => {
|
filteredShipments = allShipments.filter(shipment => {
|
||||||
const status = row.getAttribute('data-status');
|
let include = true;
|
||||||
const shipmentId = row.getAttribute('data-shipment-id').toLowerCase();
|
|
||||||
const origin = row.cells[2].textContent.toLowerCase();
|
|
||||||
const destination = row.cells[3].textContent.toLowerCase();
|
|
||||||
|
|
||||||
let statusMatch = selectedStatus === 'all' || status === selectedStatus;
|
|
||||||
let searchMatch = searchTerm === '' ||
|
|
||||||
shipmentId.includes(searchTerm) ||
|
|
||||||
origin.includes(searchTerm) ||
|
|
||||||
destination.includes(searchTerm);
|
|
||||||
let carrierMatch = selectedCarrier === 'all'; // You can add carrier data attribute if needed
|
|
||||||
|
|
||||||
if (statusMatch && searchMatch && carrierMatch) {
|
// Status filter
|
||||||
row.classList.remove('hidden');
|
if (selectedStatus !== 'all' && shipment.status !== selectedStatus) {
|
||||||
row.classList.add('visible');
|
include = false;
|
||||||
} else {
|
|
||||||
row.classList.add('hidden');
|
|
||||||
row.classList.remove('visible');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Search filter
|
||||||
|
if (searchTerm) {
|
||||||
|
const matchesSearch =
|
||||||
|
shipment.shipment_id.toLowerCase().includes(searchTerm) ||
|
||||||
|
shipment.origin.toLowerCase().includes(searchTerm) ||
|
||||||
|
shipment.destination.toLowerCase().includes(searchTerm);
|
||||||
|
if (!matchesSearch) include = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Carrier filter (you can add carrier data attribute if needed)
|
||||||
|
if (selectedCarrier !== 'all') {
|
||||||
|
// Add carrier filtering logic here if you have carrier data
|
||||||
|
}
|
||||||
|
|
||||||
|
return include;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show message if no results
|
currentPage = 1;
|
||||||
const visibleRows = document.querySelectorAll('.shipment-row:not(.hidden)');
|
renderTable();
|
||||||
const noResultsRow = document.querySelector('.shipment-row[colspan]');
|
updatePaginationControls();
|
||||||
|
|
||||||
if (visibleRows.length === 0 && !noResultsRow) {
|
|
||||||
// Add no results message
|
|
||||||
const tbody = document.getElementById('shipmentsTableBody');
|
|
||||||
const noResultsHtml = `
|
|
||||||
<tr>
|
|
||||||
<td colspan="11" class="text-center py-5 text-muted">
|
|
||||||
<i class="bi bi-search display-4 d-block mb-3"></i>
|
|
||||||
No shipments found matching your criteria
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
`;
|
|
||||||
tbody.innerHTML = noResultsHtml;
|
|
||||||
} else if (visibleRows.length > 0 && noResultsRow) {
|
|
||||||
// Remove no results message
|
|
||||||
noResultsRow.remove();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Event listeners for filters
|
// Event listeners for filters
|
||||||
@@ -1251,6 +1457,193 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
filterShipments();
|
filterShipments();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Pagination Functions
|
||||||
|
function goToPreviousPage() {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
currentPage--;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function goToNextPage() {
|
||||||
|
const totalPages = Math.ceil(filteredShipments.length / itemsPerPage);
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
currentPage++;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePaginationControls() {
|
||||||
|
const totalPages = Math.ceil(filteredShipments.length / itemsPerPage);
|
||||||
|
const prevBtn = document.getElementById('prevPageBtn');
|
||||||
|
const nextBtn = document.getElementById('nextPageBtn');
|
||||||
|
const pageInfo = document.getElementById('pageInfo');
|
||||||
|
const paginationPages = document.getElementById('paginationPages');
|
||||||
|
|
||||||
|
prevBtn.disabled = currentPage === 1;
|
||||||
|
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||||||
|
|
||||||
|
// Update page info text
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage + 1;
|
||||||
|
const endIndex = Math.min(currentPage * itemsPerPage, filteredShipments.length);
|
||||||
|
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredShipments.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) {
|
||||||
|
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = Math.max(2, currentPage - 1);
|
||||||
|
const end = Math.min(totalPages - 1, currentPage + 1);
|
||||||
|
|
||||||
|
for (let i = start; i <= end; i++) {
|
||||||
|
addPageButton(i, paginationPages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage < totalPages - 2) {
|
||||||
|
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
|
||||||
|
}
|
||||||
|
|
||||||
|
addPageButton(totalPages, paginationPages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addPageButton(pageNumber, container) {
|
||||||
|
const button = document.createElement('button');
|
||||||
|
button.className = 'pagination-page-btn';
|
||||||
|
if (pageNumber === currentPage) {
|
||||||
|
button.classList.add('active');
|
||||||
|
}
|
||||||
|
button.textContent = pageNumber;
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
currentPage = pageNumber;
|
||||||
|
renderTable();
|
||||||
|
updatePaginationControls();
|
||||||
|
});
|
||||||
|
container.appendChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render Table Function
|
||||||
|
function renderTable() {
|
||||||
|
const tbody = document.getElementById('shipmentsTableBody');
|
||||||
|
|
||||||
|
if (filteredShipments.length === 0) {
|
||||||
|
tbody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="12" class="text-center py-5 text-muted">
|
||||||
|
<i class="bi bi-search display-4 d-block mb-3"></i>
|
||||||
|
No shipments found matching your criteria
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate pagination
|
||||||
|
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||||
|
const endIndex = startIndex + itemsPerPage;
|
||||||
|
const paginatedItems = filteredShipments.slice(startIndex, endIndex);
|
||||||
|
|
||||||
|
// Sort by creation date (newest first)
|
||||||
|
const sortedItems = [...paginatedItems].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
|
|
||||||
|
// Render table rows
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
sortedItems.forEach((shipment, index) => {
|
||||||
|
const displayIndex = filteredShipments.length - (startIndex + index);
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.className = 'shipment-row';
|
||||||
|
row.setAttribute('data-status', shipment.status);
|
||||||
|
row.setAttribute('data-shipment-id', shipment.shipment_id);
|
||||||
|
|
||||||
|
// Function to format status string
|
||||||
|
const formatStatus = (status) => {
|
||||||
|
if (!status) return '';
|
||||||
|
return status.charAt(0).toUpperCase() + status.slice(1).replace(/_/g, ' ');
|
||||||
|
};
|
||||||
|
|
||||||
|
row.innerHTML = `
|
||||||
|
<td class="fw-bold">${displayIndex}</td>
|
||||||
|
<td>
|
||||||
|
<a href="#" class="text-primary fw-bold" onclick="openShipmentDetails(${shipment.id})">
|
||||||
|
${shipment.shipment_id}
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>${shipment.origin}</td>
|
||||||
|
<td>${shipment.destination}</td>
|
||||||
|
<td><span class="badge bg-light text-dark">${shipment.total_qty}</span></td>
|
||||||
|
<td><span class="badge bg-light text-dark">${shipment.total_kg} kg</span></td>
|
||||||
|
<td><span class="badge bg-light text-dark">${shipment.total_cbm} CBM</span></td>
|
||||||
|
<td class="fw-bold text-success">₹${parseFloat(shipment.total_amount).toLocaleString('en-IN', {minimumFractionDigits: 2, maximumFractionDigits: 2})}</td>
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-${shipment.status}">
|
||||||
|
${formatStatus(shipment.status)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>${new Date(shipment.shipment_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('admin.shipments.dummy', $ship->id) }}"
|
||||||
|
class="btn-view-details">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<div class="action-container">
|
||||||
|
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, ${shipment.id})" title="Edit Status">
|
||||||
|
<i class="bi bi-pencil"></i>
|
||||||
|
</button>
|
||||||
|
<div class="status-dropdown" id="statusDropdown-${shipment.id}">
|
||||||
|
<form action="/admin/shipments/update-status" method="POST" class="status-form">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||||
|
<input type="hidden" name="shipment_id" value="${shipment.id}">
|
||||||
|
<button type="submit" name="status" value="loading" class="status-option loading">
|
||||||
|
<span class="status-indicator loading"></span>
|
||||||
|
Loading
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="status" value="pending" class="status-option pending">
|
||||||
|
<span class="status-indicator pending"></span>
|
||||||
|
Pending
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="status" value="in_transit" class="status-option in_transit">
|
||||||
|
<span class="status-indicator in_transit"></span>
|
||||||
|
In Transit
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="status" value="dispatched" class="status-option dispatched">
|
||||||
|
<span class="status-indicator dispatched"></span>
|
||||||
|
Dispatched
|
||||||
|
</button>
|
||||||
|
<button type="submit" name="status" value="delivered" class="status-option delivered">
|
||||||
|
<span class="status-indicator delivered"></span>
|
||||||
|
Delivered
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
`;
|
||||||
|
tbody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
// Function: Open Consolidated Shipment Details Modal
|
||||||
|
// --------------------------------------------------------------------
|
||||||
function openShipmentDetails(id) {
|
function openShipmentDetails(id) {
|
||||||
let modal = new bootstrap.Modal(document.getElementById('shipmentDetailsModal'));
|
let modal = new bootstrap.Modal(document.getElementById('shipmentDetailsModal'));
|
||||||
let content = document.getElementById('shipmentDetailsContent');
|
let content = document.getElementById('shipmentDetailsContent');
|
||||||
@@ -1278,8 +1671,13 @@ function openShipmentDetails(id) {
|
|||||||
year: 'numeric'
|
year: 'numeric'
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Function to format status string
|
||||||
|
const formatStatus = (status) => {
|
||||||
|
if (!status) return 'N/A';
|
||||||
|
return status.charAt(0).toUpperCase() + status.slice(1).replace(/_/g, ' ');
|
||||||
|
};
|
||||||
|
|
||||||
let html = `
|
let html = `
|
||||||
<!-- Shipment Basic Info -->
|
|
||||||
<div class="shipment-info-row">
|
<div class="shipment-info-row">
|
||||||
<div class="shipment-info-item">
|
<div class="shipment-info-item">
|
||||||
<div class="shipment-info-label">Shipment ID</div>
|
<div class="shipment-info-label">Shipment ID</div>
|
||||||
@@ -1291,7 +1689,7 @@ function openShipmentDetails(id) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="shipment-info-item">
|
<div class="shipment-info-item">
|
||||||
<div class="shipment-info-label">Status</div>
|
<div class="shipment-info-label">Status</div>
|
||||||
<div class="shipment-info-value">${data.shipment.status}</div>
|
<div class="shipment-info-value">${formatStatus(data.shipment.status)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="shipment-info-item">
|
<div class="shipment-info-item">
|
||||||
<div class="shipment-info-label">Date</div>
|
<div class="shipment-info-label">Date</div>
|
||||||
@@ -1326,7 +1724,9 @@ function openShipmentDetails(id) {
|
|||||||
data.orders.forEach(order => {
|
data.orders.forEach(order => {
|
||||||
html += `
|
html += `
|
||||||
<tr>
|
<tr>
|
||||||
<td class="fw-bold text-primary">${order.order_id}</td>
|
<td class="fw-bold">
|
||||||
|
${order.order_id}
|
||||||
|
</td>
|
||||||
<td>${order.origin || 'N/A'}</td>
|
<td>${order.origin || 'N/A'}</td>
|
||||||
<td>${order.destination || 'N/A'}</td>
|
<td>${order.destination || 'N/A'}</td>
|
||||||
<td>${order.mark_no || 'ITEM001'}</td>
|
<td>${order.mark_no || 'ITEM001'}</td>
|
||||||
@@ -1348,7 +1748,6 @@ function openShipmentDetails(id) {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Totals Section -->
|
|
||||||
<div class="shipment-totals">
|
<div class="shipment-totals">
|
||||||
<div class="shipment-totals-row">
|
<div class="shipment-totals-row">
|
||||||
<div class="shipment-total-item">
|
<div class="shipment-total-item">
|
||||||
|
|||||||
187
resources/views/admin/staff/create.blade.php
Normal file
187
resources/views/admin/staff/create.blade.php
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('page-title', 'Account Dashboard')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<style>
|
||||||
|
.card { background:#fff; border:1px solid #eaeaea; padding:1rem; border-radius:6px; }
|
||||||
|
.field { margin-bottom:.8rem; }
|
||||||
|
label { display:block; font-weight:600; margin-bottom:.25rem; }
|
||||||
|
input[type="text"], input[type="email"], input[type="date"], input[type="password"], textarea, select {
|
||||||
|
width:100%; padding:.5rem; border:1px solid #ddd; border-radius:4px;
|
||||||
|
}
|
||||||
|
.stages { display:flex; gap:.5rem; margin-bottom:1rem; }
|
||||||
|
.stage-ind { padding:.35rem .6rem; border-radius:4px; background:#f3f3f3; }
|
||||||
|
.stage { display:none; }
|
||||||
|
.stage.active { display:block; }
|
||||||
|
.btn { padding:.5rem .8rem; border-radius:5px; border:1px solid #ccc; background:#f6f6f6; cursor:pointer; }
|
||||||
|
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||||
|
.perm-group { border:1px dashed #eee; padding:.6rem; margin-bottom:.6rem; border-radius:4px; }
|
||||||
|
.perm-list { display:flex; flex-wrap:wrap; gap:.5rem; }
|
||||||
|
.perm-item { min-width:200px; }
|
||||||
|
.error { color:#b00020; font-size:.95rem; margin-top:.25rem; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Add Staff</h3>
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div style="background:#fff0f0; padding:.6rem; border:1px solid #f3c6c6; margin-bottom:1rem;">
|
||||||
|
<strong>There were some problems with your input:</strong>
|
||||||
|
<ul>
|
||||||
|
@foreach($errors->all() as $err)
|
||||||
|
<li>{{ $err }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.staff.store') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="stages">
|
||||||
|
<div class="stage-ind">1. Personal</div>
|
||||||
|
<div class="stage-ind">2. Professional</div>
|
||||||
|
<div class="stage-ind">3. System</div>
|
||||||
|
<div class="stage-ind">4. Permissions</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Stage 1 --}}
|
||||||
|
<div id="stage-1" class="stage active">
|
||||||
|
<div class="field">
|
||||||
|
<label>Name *</label>
|
||||||
|
<input type="text" name="name" value="{{ old('name') }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Email *</label>
|
||||||
|
<input type="email" name="email" value="{{ old('email') }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Phone *</label>
|
||||||
|
<input type="text" name="phone" value="{{ old('phone') }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Emergency Phone</label>
|
||||||
|
<input type="text" name="emergency_phone" value="{{ old('emergency_phone') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Address</label>
|
||||||
|
<textarea name="address" rows="3">{{ old('address') }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:.5rem;">
|
||||||
|
<button type="button" class="btn" onclick="showStage(2)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Stage 2 --}}
|
||||||
|
<div id="stage-2" class="stage">
|
||||||
|
<div class="field">
|
||||||
|
<label>Role (Business role)</label>
|
||||||
|
<input type="text" name="role" value="{{ old('role') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Department</label>
|
||||||
|
<input type="text" name="department" value="{{ old('department') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Designation</label>
|
||||||
|
<input type="text" name="designation" value="{{ old('designation') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Joining Date</label>
|
||||||
|
<input type="date" name="joining_date" value="{{ old('joining_date') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Status</label>
|
||||||
|
<select name="status">
|
||||||
|
<option value="active" {{ old('status')=='active'?'selected':'' }}>Active</option>
|
||||||
|
<option value="inactive" {{ old('status')=='inactive'?'selected':'' }}>Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Additional Info</label>
|
||||||
|
<textarea name="additional_info" rows="3">{{ old('additional_info') }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||||
|
<button type="button" class="btn" onclick="showStage(1)">Back</button>
|
||||||
|
<button type="button" class="btn" onclick="showStage(3)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Stage 3 --}}
|
||||||
|
<div id="stage-3" class="stage">
|
||||||
|
<div class="field">
|
||||||
|
<label>Username *</label>
|
||||||
|
<input type="text" name="username" value="{{ old('username') }}" placeholder="leave blank to use EMPxxxx">
|
||||||
|
<div class="muted" style="font-size:.9rem; margin-top:.25rem;">If left blank employee id will be used.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Password *</label>
|
||||||
|
<input type="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||||
|
<button type="button" class="btn" onclick="showStage(2)">Back</button>
|
||||||
|
<button type="button" class="btn" onclick="showStage(4)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Stage 4 --}}
|
||||||
|
<div id="stage-4" class="stage">
|
||||||
|
<div style="margin-bottom:.5rem; font-weight:700;">Permissions</div>
|
||||||
|
|
||||||
|
@foreach($permissions as $group => $groupPerms)
|
||||||
|
<div class="perm-group">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||||
|
<div style="font-weight:600;">{{ ucfirst($group) }}</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn" onclick="toggleGroup('{{ $group }}')">Toggle group</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="perm-list" id="group-{{ $group }}">
|
||||||
|
@foreach($groupPerms as $perm)
|
||||||
|
<label class="perm-item">
|
||||||
|
<input type="checkbox" name="permissions[]" value="{{ $perm->name }}"> {{ $perm->name }}
|
||||||
|
</label>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||||
|
<button type="button" class="btn" onclick="showStage(3)">Back</button>
|
||||||
|
<button type="submit" class="btn primary">Create Staff</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showStage(n){
|
||||||
|
document.querySelectorAll('.stage').forEach(s=>s.classList.remove('active'));
|
||||||
|
document.getElementById('stage-'+n).classList.add('active');
|
||||||
|
window.scrollTo({top:0, behavior:'smooth'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleGroup(group){
|
||||||
|
const el = document.getElementById('group-'+group);
|
||||||
|
if(!el) return;
|
||||||
|
const inputs = el.querySelectorAll('input[type="checkbox"]');
|
||||||
|
const anyUnchecked = Array.from(inputs).some(i => !i.checked);
|
||||||
|
inputs.forEach(i => i.checked = anyUnchecked);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
156
resources/views/admin/staff/edit.blade.php
Normal file
156
resources/views/admin/staff/edit.blade.php
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('page-title', 'Account Dashboard')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<style>
|
||||||
|
.card { background:#fff; border:1px solid #eaeaea; padding:1rem; border-radius:6px; }
|
||||||
|
.field { margin-bottom:.8rem; }
|
||||||
|
label { display:block; font-weight:600; margin-bottom:.25rem; }
|
||||||
|
input[type="text"], input[type="email"], input[type="date"], input[type="password"], textarea, select {
|
||||||
|
width:100%; padding:.5rem; border:1px solid #ddd; border-radius:4px;
|
||||||
|
}
|
||||||
|
.perm-group { border:1px dashed #eee; padding:.6rem; margin-bottom:.6rem; border-radius:4px; }
|
||||||
|
.perm-list { display:flex; flex-wrap:wrap; gap:.5rem; }
|
||||||
|
.perm-item { min-width:200px; }
|
||||||
|
.btn { padding:.5rem .8rem; border-radius:5px; border:1px solid #ccc; background:#f6f6f6; cursor:pointer; }
|
||||||
|
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Edit Staff — {{ $staff->display_name ?? $staff->name }}</h3>
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div style="background:#fff0f0; padding:.6rem; border:1px solid #f3c6c6; margin-bottom:1rem;">
|
||||||
|
<strong>There were some problems with your input:</strong>
|
||||||
|
<ul>
|
||||||
|
@foreach($errors->all() as $err)
|
||||||
|
<li>{{ $err }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.staff.update', $staff->id) }}">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Employee ID</label>
|
||||||
|
<input type="text" value="{{ $staff->employee_id }}" disabled>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Name *</label>
|
||||||
|
<input type="text" name="name" value="{{ old('name', $staff->name) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Email *</label>
|
||||||
|
<input type="email" name="email" value="{{ old('email', $staff->email) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Phone *</label>
|
||||||
|
<input type="text" name="phone" value="{{ old('phone', $staff->phone) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Emergency Phone</label>
|
||||||
|
<input type="text" name="emergency_phone" value="{{ old('emergency_phone', $staff->emergency_phone) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Address</label>
|
||||||
|
<textarea name="address" rows="3">{{ old('address', $staff->address) }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Role</label>
|
||||||
|
<input type="text" name="role" value="{{ old('role', $staff->role) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Department</label>
|
||||||
|
<input type="text" name="department" value="{{ old('department', $staff->department) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Designation</label>
|
||||||
|
<input type="text" name="designation" value="{{ old('designation', $staff->designation) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Joining Date</label>
|
||||||
|
<input type="date" name="joining_date" value="{{ old('joining_date', optional($staff->joining_date)->format('Y-m-d')) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Status</label>
|
||||||
|
<select name="status">
|
||||||
|
<option value="active" {{ old('status', $staff->status)=='active'?'selected':'' }}>Active</option>
|
||||||
|
<option value="inactive" {{ old('status', $staff->status)=='inactive'?'selected':'' }}>Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Additional Info</label>
|
||||||
|
<textarea name="additional_info" rows="3">{{ old('additional_info', $staff->additional_info) }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Username *</label>
|
||||||
|
<input type="text" name="username" value="{{ old('username', $staff->username) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>New Password (leave blank to keep existing)</label>
|
||||||
|
<input type="password" name="password" autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div style="margin-bottom:.5rem; font-weight:700;">Permissions</div>
|
||||||
|
|
||||||
|
@foreach($permissions as $group => $groupPerms)
|
||||||
|
<div class="perm-group">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||||
|
<div style="font-weight:600;">{{ ucfirst($group) }}</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn" onclick="toggleGroup('{{ $group }}')">Toggle group</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="perm-list" id="group-{{ $group }}">
|
||||||
|
@foreach($groupPerms as $perm)
|
||||||
|
<label class="perm-item">
|
||||||
|
<input type="checkbox" name="permissions[]" value="{{ $perm->name }}"
|
||||||
|
{{ in_array($perm->name, old('permissions', $staffPermissions)) ? 'checked' : '' }}>
|
||||||
|
{{ $perm->name }}
|
||||||
|
</label>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:.5rem;">
|
||||||
|
<a href="{{ route('admin.staff.index') }}" class="btn">Cancel</a>
|
||||||
|
<button type="submit" class="btn primary">Update Staff</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleGroup(group){
|
||||||
|
const el = document.getElementById('group-'+group);
|
||||||
|
if(!el) return;
|
||||||
|
const inputs = el.querySelectorAll('input[type="checkbox"]');
|
||||||
|
const anyUnchecked = Array.from(inputs).some(i => !i.checked);
|
||||||
|
inputs.forEach(i => i.checked = anyUnchecked);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
65
resources/views/admin/staff/index.blade.php
Normal file
65
resources/views/admin/staff/index.blade.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('page-title', 'Account Dashboard')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<style>
|
||||||
|
.top-bar { display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; }
|
||||||
|
.card { background:#fff; border:1px solid #e4e4e4; border-radius:6px; padding:1rem; box-shadow:0 1px 3px rgba(0,0,0,.03); }
|
||||||
|
table { width:100%; border-collapse:collapse; }
|
||||||
|
th, td { padding:.6rem .75rem; border-bottom:1px solid #f1f1f1; text-align:left; }
|
||||||
|
.btn { padding:.45rem .75rem; border-radius:4px; border:1px solid #ccc; background:#f7f7f7; cursor:pointer; }
|
||||||
|
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||||
|
.actions a { margin-right:.5rem; color:#0b74de; text-decoration:none; }
|
||||||
|
.muted { color:#666; font-size:.95rem; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="top-bar">
|
||||||
|
<h2>Staff</h2>
|
||||||
|
<a href="{{ route('admin.staff.create') }}" class="btn primary">Add Staff</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
@if(session('success'))
|
||||||
|
<div style="padding:.5rem; background:#e6ffed; border:1px solid #b6f0c6; margin-bottom:1rem;">{{ session('success') }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Employee ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Phone</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($staff as $s)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $s->id }}</td>
|
||||||
|
<td class="muted">{{ $s->employee_id }}</td>
|
||||||
|
<td>{{ $s->name }}</td>
|
||||||
|
<td>{{ $s->email }}</td>
|
||||||
|
<td>{{ $s->phone }}</td>
|
||||||
|
<td>{{ $s->role ?? '-' }}</td>
|
||||||
|
<td>{{ ucfirst($s->status) }}</td>
|
||||||
|
<td class="actions">
|
||||||
|
<a href="{{ route('admin.staff.edit', $s->id) }}">Edit</a>
|
||||||
|
<form action="{{ route('admin.staff.destroy', $s->id) }}" method="POST" style="display:inline" onsubmit="return confirm('Delete this staff?')">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button class="btn" type="submit">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr><td colspan="8" class="muted">No staff found.</td></tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
128
resources/views/admin/view_shipment.blade.php
Normal file
128
resources/views/admin/view_shipment.blade.php
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('page-title', 'Shipment Preview')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
|
||||||
|
<div class="container py-4">
|
||||||
|
|
||||||
|
<div class="card shadow-sm border-0">
|
||||||
|
<div class="card-header bg-primary text-white">
|
||||||
|
<h5 class="mb-0">
|
||||||
|
<i class="bi bi-box-seam me-2"></i>
|
||||||
|
Shipment Preview: {{ $shipment->shipment_id }}
|
||||||
|
</h5>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-body">
|
||||||
|
|
||||||
|
<!-- Dummy Info -->
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<strong>{{ $dummyData['title'] }}</strong><br>
|
||||||
|
Generated On: {{ $dummyData['generated_on'] }} <br>
|
||||||
|
Note: {{ $dummyData['note'] }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Shipment Basic Details -->
|
||||||
|
<h5 class="fw-bold mt-3">Shipment Details</h5>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th>Shipment ID</th>
|
||||||
|
<td>{{ $shipment->shipment_id }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Origin</th>
|
||||||
|
<td>{{ $shipment->origin }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Destination</th>
|
||||||
|
<td>{{ $shipment->destination }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<td>{{ ucfirst($shipment->status) }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Date</th>
|
||||||
|
<td>{{ \Carbon\Carbon::parse($shipment->shipment_date)->format('d M Y') }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Orders in Shipment -->
|
||||||
|
<h5 class="fw-bold mt-4">Orders Contained in Shipment</h5>
|
||||||
|
|
||||||
|
@if($shipment->orders->isEmpty())
|
||||||
|
<p class="text-muted">No orders found.</p>
|
||||||
|
@else
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover table-striped">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Order ID</th>
|
||||||
|
<th>Origin</th>
|
||||||
|
<th>Destination</th>
|
||||||
|
<th>CTN</th>
|
||||||
|
<th>QTY</th>
|
||||||
|
<th>TTL/QTY</th>
|
||||||
|
<th>KG</th>
|
||||||
|
<th>Amount</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($shipment->orders as $order)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $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->ttl_kg }}</td>
|
||||||
|
<td>₹{{ number_format($order->ttl_amount, 2) }}</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<!-- Shipment Totals -->
|
||||||
|
<h5 class="fw-bold mt-4">Shipment Totals</h5>
|
||||||
|
<table class="table table-bordered">
|
||||||
|
<tr>
|
||||||
|
<th>Total CTN</th>
|
||||||
|
<td>{{ $shipment->total_ctn }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Total Quantity</th>
|
||||||
|
<td>{{ $shipment->total_qty }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Total TTL Quantity</th>
|
||||||
|
<td>{{ $shipment->total_ttl_qty }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Total CBM</th>
|
||||||
|
<td>{{ $shipment->total_cbm }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Total KG</th>
|
||||||
|
<td>{{ $shipment->total_kg }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Total Amount</th>
|
||||||
|
<td class="fw-bold text-success">
|
||||||
|
₹{{ number_format($shipment->total_amount, 2) }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<a href="{{ route('admin.shipments') }}" class="btn btn-secondary mt-3">
|
||||||
|
← Back to Shipments
|
||||||
|
</a>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endsection
|
||||||
@@ -4,17 +4,46 @@ use Illuminate\Support\Facades\Route;
|
|||||||
use App\Http\Controllers\RequestController;
|
use App\Http\Controllers\RequestController;
|
||||||
use App\Http\Controllers\UserAuthController;
|
use App\Http\Controllers\UserAuthController;
|
||||||
use App\Http\Controllers\MarkListController;
|
use App\Http\Controllers\MarkListController;
|
||||||
|
use App\Http\Controllers\User\UserOrderController;
|
||||||
|
use App\Http\Controllers\User\UserProfileController;
|
||||||
|
|
||||||
|
|
||||||
//user send request
|
//user send request
|
||||||
Route::post('/signup-request', [RequestController::class, 'usersignup']);
|
Route::post('/signup-request', [RequestController::class, 'usersignup']);
|
||||||
|
|
||||||
//login / logout
|
//login / logout
|
||||||
Route::post('/user/login', [UserAuthController::class, 'login']);
|
Route::post('/user/login', [UserAuthController::class, 'login']);
|
||||||
Route::post('/user/logout', [UserAuthController::class, 'logout'])->middleware('auth:api');
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Route::middleware(['auth:api'])->group(function () {
|
Route::middleware(['auth:api'])->group(function () {
|
||||||
Route::get('/show-mark-list', [MarkListController::class, 'showmarklist']); // Fetch all marks by user
|
//Route::post('/user/refresh', [UserAuthController::class, 'refreshToken']);
|
||||||
Route::post('/add-mark-no', [MarkListController::class, 'addmarkno']); // Create new mark
|
|
||||||
|
Route::post('/user/logout', [UserAuthController::class, 'logout']);
|
||||||
|
|
||||||
|
// Marklist
|
||||||
|
Route::get('/show-mark-list', [MarkListController::class, 'showmarklist']);
|
||||||
|
Route::post('/add-mark-no', [MarkListController::class, 'addmarkno']);
|
||||||
|
|
||||||
|
// Orders
|
||||||
|
Route::get('/user/order-summary', [UserOrderController::class, 'orderSummary']);
|
||||||
|
Route::get('/user/orders', [UserOrderController::class, 'allOrders']);
|
||||||
|
Route::get('/user/order/{order_id}/details', [UserOrderController::class, 'orderDetails']);
|
||||||
|
Route::get('/user/order/{order_id}/shipment', [UserOrderController::class, 'orderShipment']);
|
||||||
|
Route::get('/user/order/{order_id}/invoice', [UserOrderController::class, 'orderInvoice']);
|
||||||
|
Route::get('/user/order/{order_id}/track', [UserOrderController::class, 'trackOrder']);
|
||||||
|
Route::get('/user/invoice/{invoice_id}/details', [UserOrderController::class, 'invoiceDetails']);
|
||||||
|
|
||||||
|
// Invoice List
|
||||||
|
Route::get('/user/invoices', [UserOrderController::class, 'allInvoices']);
|
||||||
|
Route::get('/user/invoice/{invoice_id}/installments', [UserOrderController::class, 'invoiceInstallmentsById']);
|
||||||
|
|
||||||
|
// Profile
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/user/profile', [UserProfileController::class, 'profile']);
|
||||||
|
Route::post('/user/profile-image', [UserProfileController::class, 'updateProfileImage']);
|
||||||
|
Route::post('/user/profile-update-request', [UserProfileController::class, 'updateProfileRequest']);
|
||||||
|
|
||||||
|
// Route::post('/user/profile/update', [UserProfileController::class, 'updateProfile']);
|
||||||
});
|
});
|
||||||
|
|||||||
320
routes/web.php
320
routes/web.php
@@ -10,6 +10,7 @@ use App\Http\Controllers\Admin\AdminInvoiceController;
|
|||||||
use App\Http\Controllers\Admin\AdminCustomerController;
|
use App\Http\Controllers\Admin\AdminCustomerController;
|
||||||
use App\Http\Controllers\Admin\AdminAccountController;
|
use App\Http\Controllers\Admin\AdminAccountController;
|
||||||
use App\Http\Controllers\Admin\AdminReportController;
|
use App\Http\Controllers\Admin\AdminReportController;
|
||||||
|
use App\Http\Controllers\Admin\AdminStaffController;
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// Public Front Page
|
// Public Front Page
|
||||||
@@ -21,17 +22,11 @@ Route::get('/', function () {
|
|||||||
// ---------------------------
|
// ---------------------------
|
||||||
// ADMIN LOGIN ROUTES
|
// ADMIN LOGIN ROUTES
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
|
// login routes (public)
|
||||||
Route::prefix('admin')->group(function () {
|
Route::prefix('admin')->group(function () {
|
||||||
|
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])->name('admin.login');
|
||||||
// MUST have route name "login" for session redirect
|
Route::post('/login', [AdminAuthController::class, 'login'])->name('admin.login.submit');
|
||||||
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])
|
Route::post('/logout', [AdminAuthController::class, 'logout'])->name('admin.logout');
|
||||||
->name('admin.login');
|
|
||||||
|
|
||||||
Route::post('/login', [AdminAuthController::class, 'login'])
|
|
||||||
->name('admin.login.submit');
|
|
||||||
|
|
||||||
Route::post('/logout', [AdminAuthController::class, 'logout'])
|
|
||||||
->name('admin.logout');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -64,139 +59,246 @@ Route::prefix('admin')
|
|||||||
// ---------------------------
|
// ---------------------------
|
||||||
// USER REQUESTS
|
// USER REQUESTS
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
Route::get('/requests', [UserRequestController::class, 'index'])
|
Route::get('/requests', [UserRequestController::class, 'index'])
|
||||||
->name('admin.requests');
|
->name('admin.requests');
|
||||||
|
|
||||||
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
|
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
|
||||||
->name('admin.requests.approve');
|
->name('admin.requests.approve');
|
||||||
|
|
||||||
|
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
|
||||||
|
->name('admin.requests.reject');
|
||||||
|
|
||||||
|
// PROFILE UPDATE REQUESTS
|
||||||
|
Route::get('/profile-update-requests',
|
||||||
|
[UserRequestController::class, 'profileUpdateRequests']
|
||||||
|
)->name('admin.profile.requests');
|
||||||
|
|
||||||
|
Route::get('/profile-update/approve/{id}',
|
||||||
|
[UserRequestController::class, 'approveProfileUpdate']
|
||||||
|
)->name('admin.profile.approve');
|
||||||
|
|
||||||
|
Route::get('/profile-update/reject/{id}',
|
||||||
|
[UserRequestController::class, 'rejectProfileUpdate']
|
||||||
|
)->name('admin.profile.reject');
|
||||||
|
|
||||||
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
|
|
||||||
->name('admin.requests.reject');
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// MARK LIST
|
// MARK LIST
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
|
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
|
||||||
->name('admin.marklist.index');
|
->name('admin.marklist.index');
|
||||||
|
|
||||||
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
|
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
|
||||||
->name('admin.marklist.toggle');
|
->name('admin.marklist.toggle');
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// ORDERS
|
// ORDERS
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
Route::get('/orders', fn() => view('admin.orders'))
|
Route::get('/orders', [AdminOrderController::class, 'orderShow'])
|
||||||
->name('admin.orders');
|
->name('admin.orders');
|
||||||
|
|
||||||
Route::get('/orders/list', [AdminOrderController::class, 'index'])
|
|
||||||
->name('admin.orders.index');
|
|
||||||
|
|
||||||
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/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/finish', [AdminOrderController::class, 'finishOrder'])
|
|
||||||
->name('admin.orders.finish');
|
|
||||||
|
|
||||||
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
|
|
||||||
->name('admin.orders.popup');
|
|
||||||
|
|
||||||
|
|
||||||
|
Route::get('/orders/list', [AdminOrderController::class, 'index'])
|
||||||
|
->name('admin.orders.index');
|
||||||
|
|
||||||
|
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/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/finish', [AdminOrderController::class, 'finishOrder'])
|
||||||
|
->name('admin.orders.finish');
|
||||||
|
|
||||||
|
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
|
||||||
|
->name('admin.orders.popup');
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// SHIPMENTS
|
// ORDERS (FIXED ROUTES)
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
Route::get('/shipments', [ShipmentController::class, 'index'])
|
// Add item to order
|
||||||
->name('admin.shipments');
|
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::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');
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// SHIPMENTS (FIXED ROUTES)
|
||||||
|
// ---------------------------
|
||||||
|
Route::get('/shipments', [ShipmentController::class, 'index'])
|
||||||
|
->name('admin.shipments');
|
||||||
|
|
||||||
|
Route::post('/shipments', [ShipmentController::class, 'store'])
|
||||||
|
->name('admin.shipments.store');
|
||||||
|
|
||||||
|
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
|
||||||
|
->name('admin.shipments.updateStatus');
|
||||||
|
|
||||||
|
// Get shipment orders for modal (AJAX)
|
||||||
|
Route::get('/shipments/{id}/orders', [ShipmentController::class, 'getShipmentOrders'])
|
||||||
|
->name('admin.shipments.orders');
|
||||||
|
|
||||||
|
// Get shipment details for edit (AJAX)
|
||||||
|
Route::get('/shipments/{id}', [ShipmentController::class, 'show'])
|
||||||
|
->name('admin.shipments.show');
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
|
||||||
Route::post('/shipments/store', [ShipmentController::class, 'store'])
|
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
|
||||||
->name('admin.shipments.store');
|
->name('admin.shipments.dummy');
|
||||||
|
|
||||||
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
|
|
||||||
->name('admin.shipments.updateStatus');
|
|
||||||
|
|
||||||
Route::get('/shipments/{id}', [ShipmentController::class, 'show'])
|
|
||||||
->name('admin.shipments.show');
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// INVOICES
|
// INVOICES
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
|
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
|
||||||
->name('admin.invoices.index');
|
->name('admin.invoices.index');
|
||||||
|
|
||||||
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
|
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
|
||||||
->name('admin.invoices.popup');
|
->name('admin.invoices.popup');
|
||||||
|
|
||||||
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
|
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
|
||||||
->name('admin.invoices.edit');
|
->name('admin.invoices.edit');
|
||||||
|
|
||||||
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
|
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
|
||||||
->name('admin.invoices.update');
|
->name('admin.invoices.update');
|
||||||
|
|
||||||
//Add New Invoice
|
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
|
||||||
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
|
->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');
|
||||||
|
|
||||||
|
|
||||||
|
//Add New Invoice
|
||||||
|
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// CUSTOMERS
|
// CUSTOMERS
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
Route::get('/customers', [AdminCustomerController::class, 'index'])
|
Route::get('/customers', [AdminCustomerController::class, 'index'])
|
||||||
->name('admin.customers.index');
|
->name('admin.customers.index');
|
||||||
|
|
||||||
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
|
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
|
||||||
->name('admin.customers.add');
|
->name('admin.customers.add');
|
||||||
|
|
||||||
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
|
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
|
||||||
->name('admin.customers.store');
|
->name('admin.customers.store');
|
||||||
|
|
||||||
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
|
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
|
||||||
->name('admin.customers.view');
|
->name('admin.customers.view');
|
||||||
|
|
||||||
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
|
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
|
||||||
->name('admin.customers.status');
|
->name('admin.customers.status');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// ADMIN ACCOUNT (AJAX) ROUTES
|
||||||
|
// ==========================================
|
||||||
|
Route::prefix('admin/account')
|
||||||
|
->middleware('auth:admin')
|
||||||
|
->name('admin.account.')
|
||||||
|
->group(function () {
|
||||||
|
|
||||||
|
Route::get('/dashboard', [AdminAccountController::class, 'getDashboardData'])
|
||||||
|
->name('dashboard');
|
||||||
|
|
||||||
|
Route::get('/available-orders', [AdminAccountController::class, 'getAvailableOrders'])
|
||||||
|
->name('orders.available');
|
||||||
|
|
||||||
|
Route::post('/create-order', [AdminAccountController::class, 'accountCreateOrder'])
|
||||||
|
->name('create');
|
||||||
|
|
||||||
|
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])
|
||||||
|
->name('toggle');
|
||||||
|
|
||||||
|
Route::post('/installment/create', [AdminAccountController::class, 'addInstallment'])
|
||||||
|
->name('installment.create');
|
||||||
|
|
||||||
|
Route::post('/installment/update-status', [AdminAccountController::class, 'updateInstallmentStatus'])
|
||||||
|
->name('installment.update');
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
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');
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// REPORTS DOWNLOAD ROUTES
|
||||||
|
// ---------------------------
|
||||||
|
Route::get('/admin/orders/download/pdf', [AdminOrderController::class, 'downloadPdf'])
|
||||||
|
->name('admin.orders.download.pdf');
|
||||||
|
|
||||||
|
Route::get('/admin/orders/download/excel', [AdminOrderController::class, 'downloadExcel'])
|
||||||
|
->name('admin.orders.download.excel');
|
||||||
|
|
||||||
|
|
||||||
|
Route::prefix('admin/account')->middleware('auth:admin')->name('admin.account.')->group(function () {
|
||||||
|
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])->name('toggle');
|
||||||
|
});
|
||||||
|
|
||||||
|
//---------------------------
|
||||||
|
//Edit Button Route
|
||||||
|
//---------------------------
|
||||||
|
// protected admin routes
|
||||||
|
Route::middleware(['auth:admin'])
|
||||||
|
->prefix('admin')
|
||||||
|
->name('admin.')
|
||||||
|
->group(function () {
|
||||||
|
|
||||||
|
|
||||||
|
// staff resource
|
||||||
|
Route::resource('staff', AdminStaffController::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
// ==========================================
|
|
||||||
// ADMIN ACCOUNT (AJAX) ROUTES
|
|
||||||
// ==========================================
|
|
||||||
Route::prefix('admin/account')
|
|
||||||
->middleware('auth:admin')
|
|
||||||
->name('admin.account.')
|
|
||||||
->group(function () {
|
|
||||||
|
|
||||||
Route::get('/dashboard', [AdminAccountController::class, 'getDashboardData'])
|
|
||||||
->name('dashboard');
|
|
||||||
|
|
||||||
Route::get('/available-orders', [AdminAccountController::class, 'getAvailableOrders'])
|
|
||||||
->name('orders.available');
|
|
||||||
|
|
||||||
Route::post('/create-order', [AdminAccountController::class, 'accountCreateOrder'])
|
|
||||||
->name('create');
|
|
||||||
|
|
||||||
Route::post('/toggle-payment', [AdminAccountController::class, 'togglePayment'])
|
|
||||||
->name('toggle');
|
|
||||||
|
|
||||||
Route::post('/installment/create', [AdminAccountController::class, 'addInstallment'])
|
|
||||||
->name('installment.create');
|
|
||||||
|
|
||||||
Route::post('/installment/update-status', [AdminAccountController::class, 'updateInstallmentStatus'])
|
|
||||||
->name('installment.update');
|
|
||||||
|
|
||||||
Route::get('/entry/{entry_no}', [AdminAccountController::class, 'getEntryDetails'])
|
|
||||||
->name('entry.details');
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user