Compare commits

...

24 Commits

Author SHA256 Message Date
Abhishek Mali
0a65d5f596 staff 2025-12-08 10:17:46 +05:30
Abhishek Mali
0a1d0a9c55 staff update 2025-12-05 17:16:02 +05:30
Abhishek Mali
409a854d7b error fix 2025-12-04 12:08:45 +05:30
Utkarsh Khedkar
4dab96b8d1 Account and Shipment Changes 2025-12-04 11:21:46 +05:30
Utkarsh Khedkar
e7fef314fc Merge branch 'dev' of http://103.248.30.24:3000/kent-logistics/Kent-logistics-Laravel into dev 2025-12-03 16:17:36 +05:30
Utkarsh Khedkar
5114357ff2 Account Changes 2025-12-03 16:17:14 +05:30
divya abdar
0afcb23511 conflict resolve 2025-12-03 16:13:37 +05:30
divya abdar
340c2b2132 Shipment dashboard changes 2025-12-03 15:36:04 +05:30
Abhishek Mali
44b8299b0e fixes 2025-12-03 15:31:00 +05:30
Utkarsh Khedkar
9b8c50fcec Merge branch 'dev' of http://103.248.30.24:3000/kent-logistics/Kent-logistics-Laravel into dev 2025-12-03 11:09:28 +05:30
Utkarsh Khedkar
7a814dff1d Dasshboard Changes 2025-12-03 11:09:12 +05:30
Abhishek Mali
f4730a81d8 download pdf and excel function created 2025-12-03 10:35:20 +05:30
Utkarsh Khedkar
3b24ee860a Merge branch 'dev' of http://103.248.30.24:3000/kent-logistics/Kent-logistics-Laravel into dev 2025-12-02 18:12:15 +05:30
Utkarsh Khedkar
2dcd9fe332 Account Changes 2025-12-02 18:11:58 +05:30
Abhishek Mali
922539844d api update 2025-12-02 18:07:15 +05:30
Abhishek Mali
3845972c5c error fix 2025-12-01 12:45:25 +05:30
Abhishek Mali
64d8939208 merge Conflict 2025-12-01 11:49:24 +05:30
Abhishek Mali
ec2a0baceb API changes 2025-12-01 11:44:43 +05:30
divya abdar
68bfd180ed merge resolve conflict 2025-12-01 11:42:47 +05:30
divya abdar
aa616fcf61 Frontend dashboard, shipment, invoice , customer 2025-12-01 10:38:52 +05:30
Utkarsh Khedkar
178fbb224c Account,Order&Request change 2025-12-01 10:34:27 +05:30
Utkarsh Khedkar
97db70c40e Account Section UI Changes 2025-11-27 19:39:36 +05:30
Utkarsh Khedkar
04b00c9db8 frontend Order Section Update 2025-11-26 23:07:12 +05:30
Abhishek Mali
bebe0711f4 gst 2025-11-25 13:14:53 +05:30
88 changed files with 16369 additions and 3517 deletions

View 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',
];
}
}

View File

@@ -11,6 +11,48 @@ use Illuminate\Support\Facades\DB;
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
*/
@@ -31,16 +73,18 @@ class AdminAccountController extends Controller
*/
public function getAvailableOrders()
{
$orders = Order::whereDoesntHave('entries')
->orderBy('id', 'desc')
->get();
->orderBy('id', 'desc')
->get();
return response()->json([
'success' => true,
'orders' => $orders
'orders' => $orders,
]);
}
/**
* 🚀 3. Create new entry
*/
@@ -245,4 +289,111 @@ class AdminAccountController extends Controller
'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);
}
}
}

View File

@@ -5,50 +5,52 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use App\Models\Admin;
class AdminAuthController extends Controller
{
/**
* Show the admin login page
*/
public function showLoginForm()
{
return view('admin.login');
}
/**
* Handle admin login
*/
public function login(Request $request)
{
$request->validate([
'email' => 'required|email',
'login' => 'required',
'password' => 'required|string|min:6',
]);
// Try to log in using the 'admin' guard
if (Auth::guard('admin')->attempt($request->only('email', 'password'))) {
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, Admin!');
$loginInput = $request->input('login');
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)
{
Auth::guard('admin')->logout();
// Destroy the session completely
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
}
}

View File

@@ -9,36 +9,42 @@ use Illuminate\Support\Facades\Hash;
class AdminCustomerController extends Controller
{
// ---------------------------------------------------------
// LIST CUSTOMERS (with search + status filter)
// ---------------------------------------------------------
public function index(Request $request)
{
$search = $request->search;
$status = $request->status;
{
$search = $request->search;
$status = $request->status;
$query = User::with(['marks', 'orders'])->orderBy('id', 'desc');
$query = User::with(['marks', 'orders'])->orderBy('id', 'desc');
// SEARCH FILTER
if (!empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('customer_name', 'like', "%$search%")
->orWhere('email', 'like', "%$search%")
->orWhere('mobile_no', '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'));
// SEARCH FILTER
if (!empty($search)) {
$query->where(function ($q) use ($search) {
$q->where('customer_name', 'like', "%$search%")
->orWhere('email', 'like', "%$search%")
->orWhere('mobile_no', 'like', "%$search%")
->orWhere('customer_id', 'like', "%$search%");
});
}
// 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
// ---------------------------------------------------------
@@ -131,4 +137,5 @@ class AdminCustomerController extends Controller
return back()->with('success', 'Customer status updated.');
}
}

View File

@@ -7,6 +7,8 @@ use Illuminate\Http\Request;
use App\Models\Invoice;
use App\Models\InvoiceItem;
use Mpdf\Mpdf;
use App\Models\InvoiceInstallment;
use Illuminate\Support\Facades\Log;
class AdminInvoiceController extends Controller
{
@@ -15,7 +17,7 @@ class AdminInvoiceController extends Controller
// -------------------------------------------------------------
public function index()
{
$invoices = Invoice::latest()->get();
$invoices = Invoice::with(['order.shipments'])->latest()->get();
return view('admin.invoice', compact('invoices'));
}
@@ -24,8 +26,15 @@ class AdminInvoiceController extends Controller
// -------------------------------------------------------------
public function popup($id)
{
$invoice = Invoice::with('items')->findOrFail($id);
return view('admin.popup_invoice', compact('invoice'));
$invoice = Invoice::with(['items', 'order'])->findOrFail($id);
// 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)
{
$invoice = Invoice::findOrFail($id);
return view('admin.invoice_edit', compact('invoice'));
$invoice = Invoice::with(['order.shipments'])->findOrFail($id);
$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)
{
Log::info("🟡 Invoice Update Request Received", [
'invoice_id' => $id,
'request' => $request->all()
]);
$invoice = Invoice::findOrFail($id);
// Validate editable fields
$data = $request->validate([
'invoice_date' => 'required|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',
'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',
'notes' => 'nullable|string',
]);
// Auto-calc
$gst_amount = ($data['final_amount'] * $data['gst_percent']) / 100;
$final_amount_with_gst = $data['final_amount'] + $gst_amount;
Log::info("✅ Validated Invoice Update Data", $data);
$data['gst_amount'] = $gst_amount;
$data['final_amount_with_gst'] = $final_amount_with_gst;
$finalAmount = floatval($data['final_amount']);
$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);
// Generate PDF
Log::info("✅ Invoice Updated Successfully", [
'invoice_id' => $invoice->id
]);
// regenerate PDF
$this->generateInvoicePDF($invoice);
return redirect()
@@ -79,44 +124,104 @@ class AdminInvoiceController extends Controller
// -------------------------------------------------------------
public function generateInvoicePDF($invoice)
{
// PDF Name
$invoice->load(['items', 'order.shipments']);
$shipment = $invoice->order?->shipments?->first();
$fileName = 'invoice-' . $invoice->invoice_number . '.pdf';
// Save directly in /public/invoices
$folder = public_path('invoices/');
// Create folder if not exists
if (!file_exists($folder)) {
mkdir($folder, 0777, true);
}
// Full path
$filePath = $folder . $fileName;
// Delete old file
if (file_exists($filePath)) {
unlink($filePath);
}
// Initialize mPDF
$mpdf = new Mpdf([
'mode' => 'utf-8',
'format' => 'A4',
'default_font' => 'sans-serif'
$mpdf = new Mpdf(['mode' => 'utf-8', 'format' => 'A4', 'default_font' => 'sans-serif']);
$html = view('admin.pdf.invoice', ['invoice' => $invoice, 'shipment' => $shipment])->render();
$mpdf->WriteHTML($html);
$mpdf->Output($filePath, 'F');
$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
$html = view('admin.pdf.invoice', compact('invoice'))->render();
$invoice = Invoice::findOrFail($invoice_id);
// Generate
$mpdf->WriteHTML($html);
$paidTotal = $invoice->installments()->sum('amount');
// Use GST-inclusive total for all calculations/checks
$remaining = $invoice->final_amount_with_gst - $paidTotal;
// Save to public/invoices
$mpdf->Output($filePath, 'F');
if ($request->amount > $remaining) {
return response()->json([
'status' => 'error',
'message' => 'Installment amount exceeds remaining balance.'
], 422);
}
// Save path in DB
$invoice->update([
'pdf_path' => 'invoices/' . $fileName
$installment = InvoiceInstallment::create([
'invoice_id' => $invoice_id,
'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
]);
}
}

View File

@@ -7,20 +7,458 @@ use Illuminate\Http\Request;
use App\Models\Order;
use App\Models\OrderItem;
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
{
// ---------------------------
// LIST / DASHBOARD
// ---------------------------
public function index()
{
// raw list for admin dashboard (simple)
$orders = Order::latest()->get();
$markList = MarkList::where('status', 'active')->get();
return view('admin.dashboard', compact('orders', 'markList'));
}
// -------------------------------------------------------------------------
// 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)
{
@@ -67,31 +505,8 @@ class AdminOrderController extends Controller
session()->push('temp_order_items', $item);
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->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.');
->with('success', 'Item added.');
}
// -------------------------------------------------------------------------
@@ -99,202 +514,228 @@ class AdminOrderController extends Controller
// -------------------------------------------------------------------------
public function finishOrder(Request $request)
{
$request->validate([
'mark_no' => 'required',
'origin' => 'required',
'destination' => 'required',
]);
{
$request->validate([
'mark_no' => 'required',
'origin' => 'required',
'destination' => 'required',
]);
$items = session('temp_order_items', []);
$items = session('temp_order_items', []);
if (empty($items)) {
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('error', 'Add at least one item before finishing.');
}
if (empty($items)) {
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('error', 'Add at least one item before finishing.');
}
// =======================
// GENERATE ORDER ID
// =======================
$year = date('y');
$prefix = "KNT-$year-";
// =======================
// GENERATE ORDER ID
// =======================
$year = date('y');
$prefix = "KNT-$year-";
$lastOrder = Order::latest('id')->first();
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
$lastOrder = Order::latest('id')->first();
$nextNumber = $lastOrder ? intval(substr($lastOrder->order_id, -8)) + 1 : 1;
$orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
$orderId = $prefix . str_pad($nextNumber, 8, '0', STR_PAD_LEFT);
// =======================
// TOTAL SUMS
// =======================
$total_ctn = array_sum(array_column($items, 'ctn'));
$total_qty = array_sum(array_column($items, 'qty'));
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
$total_amount = array_sum(array_column($items, 'ttl_amount'));
$total_cbm = array_sum(array_column($items, 'cbm'));
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
$total_kg = array_sum(array_column($items, 'kg'));
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
// =======================
// TOTAL SUMS
// =======================
$total_ctn = array_sum(array_column($items, 'ctn'));
$total_qty = array_sum(array_column($items, 'qty'));
$total_ttl_qty = array_sum(array_column($items, 'ttl_qty'));
$total_amount = array_sum(array_column($items, 'ttl_amount'));
$total_cbm = array_sum(array_column($items, 'cbm'));
$total_ttl_cbm = array_sum(array_column($items, 'ttl_cbm'));
$total_kg = array_sum(array_column($items, 'kg'));
$total_ttl_kg = array_sum(array_column($items, 'ttl_kg'));
// =======================
// CREATE ORDER
// =======================
$order = Order::create([
'order_id' => $orderId,
'mark_no' => $request->mark_no,
'origin' => $request->origin,
'destination' => $request->destination,
'ctn' => $total_ctn,
'qty' => $total_qty,
'ttl_qty' => $total_ttl_qty,
'ttl_amount' => $total_amount,
'cbm' => $total_cbm,
'ttl_cbm' => $total_ttl_cbm,
'kg' => $total_kg,
'ttl_kg' => $total_ttl_kg,
'status' => 'pending',
]);
// =======================
// CREATE ORDER
// =======================
$order = Order::create([
'order_id' => $orderId,
'mark_no' => $request->mark_no,
'origin' => $request->origin,
'destination' => $request->destination,
'ctn' => $total_ctn,
'qty' => $total_qty,
'ttl_qty' => $total_ttl_qty,
'ttl_amount' => $total_amount,
'cbm' => $total_cbm,
'ttl_cbm' => $total_ttl_cbm,
'kg' => $total_kg,
'ttl_kg' => $total_ttl_kg,
'status' => 'pending',
]);
// SAVE ORDER ITEMS
foreach ($items as $item) {
OrderItem::create([
// SAVE ORDER ITEMS
foreach ($items as $item) {
OrderItem::create([
'order_id' => $order->id,
'description' => $item['description'],
'ctn' => $item['ctn'],
'qty' => $item['qty'],
'ttl_qty' => $item['ttl_qty'],
'unit' => $item['unit'],
'price' => $item['price'],
'ttl_amount' => $item['ttl_amount'],
'cbm' => $item['cbm'],
'ttl_cbm' => $item['ttl_cbm'],
'kg' => $item['kg'],
'ttl_kg' => $item['ttl_kg'],
'shop_no' => $item['shop_no'],
]);
}
// =======================
// INVOICE CREATION START
// =======================
// 1. Auto-generate invoice number
$lastInvoice = \App\Models\Invoice::latest()->first();
$nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1;
$invoiceNumber = 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT);
// 2. Fetch customer (using mark list → customer_id)
$markList = MarkList::where('mark_no', $order->mark_no)->first();
$customer = null;
if ($markList && $markList->customer_id) {
$customer = \App\Models\User::where('customer_id', $markList->customer_id)->first();
}
// 3. Create Invoice Record
$invoice = \App\Models\Invoice::create([
'order_id' => $order->id,
'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'],
'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,
]);
}
// =======================
// INVOICE CREATION START
// =======================
// 1. Auto-generate invoice number
$lastInvoice = \App\Models\Invoice::latest()->first();
$nextInvoice = $lastInvoice ? $lastInvoice->id + 1 : 1;
$invoiceNumber = 'INV-' . date('Y') . '-' . str_pad($nextInvoice, 6, '0', STR_PAD_LEFT);
// 2. Fetch customer (using mark list → customer_id)
$markList = MarkList::where('mark_no', $order->mark_no)->first();
$customer = null;
if ($markList && $markList->customer_id) {
$customer = \App\Models\User::where('customer_id', $markList->customer_id)->first();
}
// 3. Create Invoice Record
$invoice = \App\Models\Invoice::create([
'order_id' => $order->id,
'customer_id' => $customer->id ?? null,
'mark_no' => $order->mark_no,
'invoice_number' => $invoiceNumber,
'invoice_date' => now(),
'due_date' => now()->addDays(10),
'payment_method' => null,
'reference_no' => null,
'status' => 'pending',
'final_amount' => $total_amount,
'gst_percent' => 0,
'gst_amount' => 0,
'final_amount_with_gst' => $total_amount,
// snapshot customer fields
'customer_name' => $customer->customer_name ?? null,
'company_name' => $customer->company_name ?? null,
'customer_email' => $customer->email ?? null,
'customer_mobile' => $customer->mobile_no ?? null,
'customer_address' => $customer->address ?? null,
'pincode' => $customer->pincode ?? null,
'notes' => null,
]);
// 4. Clone order items into invoice_items
foreach ($order->items as $item) {
\App\Models\InvoiceItem::create([
'invoice_id' => $invoice->id,
'description' => $item->description,
'ctn' => $item->ctn,
'qty' => $item->qty,
'ttl_qty' => $item->ttl_qty,
'unit' => $item->unit,
'price' => $item->price,
'ttl_amount' => $item->ttl_amount,
'cbm' => $item->cbm,
'ttl_cbm' => $item->ttl_cbm,
'kg' => $item->kg,
'ttl_kg' => $item->ttl_kg,
'shop_no' => $item->shop_no,
]);
}
// 5. TODO: PDF generation (I will add this later)
$invoice->pdf_path = null; // placeholder for now
$invoice->save();
// =======================
// END INVOICE CREATION
// =======================
// CLEAR TEMP DATA
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
return redirect()->route('admin.orders.index')
->with('success', 'Order + Invoice created successfully.');
}
// -------------------------------------------------------------------------
// 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();
// 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,
]);
}
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)
{
// Load order with items + markList
$order = Order::with(['items', 'markList'])->findOrFail($id);
// =======================
// END INVOICE CREATION
// =======================
// 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()
{
// CLEAR TEMP DATA
session()->forget(['temp_order_items', 'mark_no', 'origin', 'destination']);
return redirect()->to(route('admin.orders.index') . '#createOrderForm')
->with('success', 'Order reset successfully.');
return redirect()->route('admin.orders.index')
->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,
]);
}
}
}

View 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.');
}
}

View File

@@ -14,7 +14,7 @@ class ShipmentController extends Controller
/**
* Show shipment page (Create Shipment + Shipment List)
*/
public function index()
public function index()
{
// 1) Get all used order IDs
$usedOrderIds = ShipmentItem::pluck('order_id')->toArray();
@@ -29,8 +29,6 @@ class ShipmentController extends Controller
return view('admin.shipments', compact('availableOrders', 'shipments'));
}
/**
* Store new shipment
*/
@@ -115,8 +113,6 @@ class ShipmentController extends Controller
return redirect()->back()->with('success', "Shipment $newShipmentId created successfully!");
}
/**
* Show shipment details (for modal popup)
*/
@@ -135,8 +131,6 @@ class ShipmentController extends Controller
]);
}
/**
* 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'));
}
}

View File

@@ -65,4 +65,52 @@ class UserRequestController extends Controller
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.');
}
}

View File

@@ -44,6 +44,8 @@ class RequestController extends Controller
'pincode' => $request->pincode,
'date' => Carbon::now()->toDateString(), // Auto current date
'status' => 'pending', // Default status
]);
// ✅ Response
@@ -53,4 +55,6 @@ class RequestController extends Controller
'data' => $newRequest
]);
}
}

View File

@@ -9,6 +9,74 @@ use App\Models\User;
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
*/
@@ -60,6 +128,8 @@ class UserAuthController extends Controller
]);
}
/**
* User Logout
*/

View 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
]);
}
}

View 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
]);
}
}

View 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);
}
}

View File

@@ -1,22 +1,43 @@
<?php
// app/Models/Admin.php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Support\Facades\Hash;
class Admin extends Authenticatable
{
use Notifiable;
use HasFactory, Notifiable, HasRoles;
protected $guard = 'admin';
protected $guard_name = 'admin';
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 = [
'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}";
}
}

View File

@@ -9,34 +9,41 @@ class Invoice extends Model
{
use HasFactory;
protected $fillable = [
'order_id',
'customer_id',
'mark_no',
protected $fillable = [
'order_id',
'customer_id',
'mark_no',
'invoice_number',
'invoice_date',
'due_date',
'invoice_number',
'invoice_date',
'due_date',
'payment_method',
'reference_no',
'status',
'payment_method',
'reference_no',
'status',
'final_amount',
'gst_percent',
'gst_amount',
'final_amount_with_gst',
'final_amount', // without tax
'customer_name',
'company_name',
'customer_email',
'customer_mobile',
'customer_address',
'pincode',
'tax_type', // gst / igst
'gst_percent', // only used for gst UI input
'cgst_percent',
'sgst_percent',
'igst_percent',
'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
@@ -74,4 +81,16 @@ class Invoice extends Model
{
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);
}
}

View 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);
}
}

View File

@@ -4,10 +4,12 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Order extends Model
{
use HasFactory;
use HasFactory,SoftDeletes;
protected $fillable = [
'order_id',
@@ -46,4 +48,21 @@ class Order extends Model
->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');
}
}

View File

@@ -4,10 +4,11 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class OrderItem extends Model
{
use HasFactory;
use HasFactory, SoftDeletes;
protected $fillable = [
'order_id',

View File

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

81
app/Models/Staff.php Normal file
View 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 . ')';
}
}

View 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);
}
}

View File

@@ -89,4 +89,11 @@ class User extends Authenticatable implements JWTSubject
{
return [];
}
public function invoices()
{
return $this->hasMany(\App\Models\Invoice::class, 'customer_id', 'id');
}
}

View 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;
}
});
}
}

View File

@@ -3,4 +3,5 @@
return [
App\Providers\AppServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\AuthServiceProvider::class,
];

View File

@@ -7,10 +7,13 @@
"license": "MIT",
"require": {
"php": "^8.2",
"barryvdh/laravel-dompdf": "^3.1",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1",
"maatwebsite/excel": "^1.1",
"mpdf/mpdf": "^8.2",
"php-open-source-saver/jwt-auth": "2.8"
"php-open-source-saver/jwt-auth": "2.8",
"spatie/laravel-permission": "^6.23"
},
"require-dev": {
"fakerphp/faker": "^1.23",

1268
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -85,6 +85,11 @@ return [
// 'driver' => 'database',
// 'table' => 'users',
// ],
'staff' => [
'driver' => 'eloquent',
'model' => App\Models\Staff::class,
],
],
/*

202
config/permission.php Normal file
View 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',
],
];

View File

@@ -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',
]);
});
}
};

View File

@@ -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
});
}
};

View File

@@ -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();
});
}
};

View File

@@ -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
}
});
}
};

View File

@@ -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');
});
}
};

View File

@@ -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');
}
}

View File

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

View File

@@ -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']);
}
};

View 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');
}
};

View File

@@ -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) {
//
});
}
};

View File

@@ -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();
});
}
};

View File

@@ -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');
}
};

View File

@@ -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');
}
};

View 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.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

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

View File

@@ -12,6 +12,7 @@ html, body {
body, .container-fluid {
background: #f4f7fc;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.container-fluid {
@@ -34,6 +35,7 @@ body, .container-fluid {
letter-spacing: .018em;
color: #18213e;
margin-bottom: 2px;
font-family: 'Inter', sans-serif;
}
.dash-title-desc {
@@ -42,6 +44,7 @@ body, .container-fluid {
margin-bottom: 5px;
font-weight: 500;
letter-spacing: .01em;
font-family: 'Inter', sans-serif;
}
/* ===== STATS CARDS - RESPONSIVE GRID ===== */
@@ -127,12 +130,14 @@ body, .container-fluid {
color:#63709b;
font-weight:600;
letter-spacing:.28px;
font-family: 'Inter', sans-serif;
}
.stats-value {
font-size:1.25rem;
font-weight:700;
color:#194073;
font-family: 'Inter', sans-serif;
}
.stats-card:hover .stats-icon {
@@ -170,6 +175,7 @@ body, .container-fluid {
display: flex;
align-items: center;
gap: 11px;
font-family: 'Inter', sans-serif;
}
.order-mgmt-title i {
@@ -191,6 +197,7 @@ body, .container-fluid {
align-items: center;
gap: 8px;
font-family: inherit;
font-family: 'Inter', sans-serif;
}
.create-order-btn:hover {
@@ -201,7 +208,7 @@ body, .container-fluid {
.card-body, .order-mgmt-main {
background: #fff;
border-radius: 0 0 17px 17px;
padding:25px;
padding:20px;
margin-top: 15px;
}
@@ -213,7 +220,7 @@ body, .container-fluid {
margin-top: 15px;
}
/* ===== TABLE STYLES ===== */
/* ===== IMPROVED TABLE STYLES ===== */
.table thead tr {
background: #feebbe !important;
border-radius: 12px 12px 0 0;
@@ -226,9 +233,10 @@ body, .container-fluid {
font-weight: 700;
color: #343535;
letter-spacing: 0.02em;
font-size:15px;
padding-top: 12px;
padding-bottom: 10px;
font-size: 15px;
padding: 16px 12px;
font-family: 'Inter', sans-serif;
border-bottom: 2px solid #e9ecef;
}
.table thead th:first-child { border-radius: 9px 0 0 0;}
@@ -239,20 +247,24 @@ body, .container-fluid {
border-radius:9px;
box-shadow:0 2px 12px #e2ebf941;
border-collapse: separate;
border-spacing: 0 2px;
border-spacing: 0 8px;
font-family: 'Inter', sans-serif;
font-size: 14px;
}
.table-striped tbody tr {
background: #fff;
border-radius: 6px;
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
margin-bottom: 2px;
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0,0,0,0.04);
margin-bottom: 8px;
transition: all 0.3s ease;
height: 60px;
}
.table-striped tbody tr:hover {
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
transform: translateY(-2px);
background: #f8fafd;
}
.table-striped tbody tr:nth-of-type(odd) {
@@ -264,39 +276,110 @@ body, .container-fluid {
}
.table td {
padding: 12px 6px;
padding: 14px 12px;
border: none;
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 {
border-radius: 6px 0 0 6px;
border-radius: 8px 0 0 8px;
border-left: 3px solid transparent;
}
.table td:last-child {
border-radius: 0 6px 6px 0;
border-radius: 0 8px 8px 0;
}
.table-responsive {
border-radius:10px;
}
/* ===== UPDATED STATUS BADGE STYLES ===== */
.badge {
font-size:13px;
font-weight:600;
padding:7px 17px;
border-radius:12px;
padding: 7px 17px !important;
border-radius: 20px !important;
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 {
background-color:#22cbfa!important;
color:#fff!important;
.status-icon {
font-size: 13px;
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 {
font-weight:600;
color:#1d3159;
font-size:15px;
font-family: 'Inter', sans-serif;
}
.form-control, .form-select {
@@ -305,6 +388,7 @@ body, .container-fluid {
background: #f7f9fe;
border:1.2px solid #c7dbfa;
font-weight:500;
font-family: 'Inter', sans-serif;
}
.form-control:focus, .form-select:focus {
@@ -316,6 +400,7 @@ body, .container-fluid {
color:#2469d6;
font-weight:600;
text-decoration:underline;
font-family: 'Inter', sans-serif;
}
/* ===== HORIZONTAL SCROLL CONTAINER ===== */
@@ -327,7 +412,7 @@ body, .container-fluid {
border-radius: 12px;
background: #fff;
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
padding: 8px;
padding: 12px;
}
.table-wrapper::-webkit-scrollbar {
@@ -348,7 +433,7 @@ body, .container-fluid {
}
.table {
min-width: 1200px;
min-width: 2000px;
border-radius: 10px;
}
@@ -394,6 +479,7 @@ body, .container-fluid {
font-size: 1.4rem;
font-weight: 700;
margin: 0;
font-family: 'Inter', sans-serif;
}
.create-order-modal .close-btn {
@@ -425,6 +511,7 @@ body, .container-fluid {
color: #1d3159;
font-size: 15px;
margin-bottom: 5px;
font-family: 'Inter', sans-serif;
}
.create-order-modal .form-control,
@@ -435,6 +522,7 @@ body, .container-fluid {
font-size: 15px;
font-weight: 500;
padding: 8px 12px;
font-family: 'Inter', sans-serif;
}
.create-order-modal .form-control:focus,
@@ -449,6 +537,7 @@ body, .container-fluid {
padding: 10px 20px;
font-weight: 600;
border-radius: 8px;
font-family: 'Inter', sans-serif;
}
.create-order-modal .btn-info:hover {
@@ -461,6 +550,7 @@ body, .container-fluid {
padding: 12px 30px;
font-weight: 600;
border-radius: 8px;
font-family: 'Inter', sans-serif;
}
.create-order-modal .btn-success:hover {
@@ -472,6 +562,7 @@ body, .container-fluid {
border: none;
color: #000;
font-weight: 600;
font-family: 'Inter', sans-serif;
}
.create-order-modal .btn-warning:hover {
@@ -481,6 +572,7 @@ body, .container-fluid {
.create-order-modal .btn-danger {
background: #dc3545;
border: none;
font-family: 'Inter', sans-serif;
}
.create-order-modal .btn-danger:hover {
@@ -505,6 +597,7 @@ body, .container-fluid {
position: sticky;
top: 0;
z-index: 10;
font-family: 'Inter', sans-serif;
}
/* ===== ORDER DETAILS MODAL STYLES ===== */
@@ -536,6 +629,7 @@ body, .container-fluid {
font-weight: 700;
color: white;
margin: 0;
font-family: 'Inter', sans-serif;
}
.modal-header .btn-close {
@@ -554,6 +648,143 @@ body, .container-fluid {
background: #f8fafc;
max-height: 70vh;
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 ===== */
@@ -596,7 +827,17 @@ body, .container-fluid {
.table th,
.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 td {
padding: 8px 4px;
padding: 10px 6px;
}
.badge {
@@ -771,7 +1012,7 @@ body, .container-fluid {
.table th,
.table td {
padding: 6px 3px;
padding: 8px 4px;
}
.form-control, .form-select {
@@ -890,9 +1131,12 @@ body, .container-fluid {
<div class="order-mgmt-box">
<div class="order-mgmt-bar">
<span class="order-mgmt-title"><i class="bi bi-table"></i> Order Management</span>
<button class="create-order-btn" id="openCreateOrderModal">
<i class="bi bi-plus-circle"></i> Create Order
</button>
@can('order.create')
<button class="create-order-btn" id="openCreateOrderModal">
<i class="bi bi-plus-circle"></i> Create Order
</button>
@endcan
</div>
<div class="order-mgmt-main">
@@ -900,69 +1144,107 @@ body, .container-fluid {
<div class="card shadow-sm">
<div class="card-header bg-light">
<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 class="card-body table-responsive">
<table class="table table-striped table-bordered align-middle text-center">
<thead class="table-light">
<tr>
<th>#</th>
<th>Order ID</th>
<th>Mark No</th>
<th>Origin</th>
<th>Destination</th>
<th>Total CTN</th>
<th>Total QTY</th>
<th>Total TTL/QTY</th>
<th>Total Amount ()</th>
<th>Total CBM</th>
<th>Total TTL CBM</th>
<th>Total KG</th>
<th>Total TTL KG</th>
<th>Status</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($orders as $order)
<tr>
<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>{{ 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>
<div class="table-wrapper">
<table class="table table-striped table-bordered align-middle text-center">
<thead class="table-light">
<tr>
<th>#</th>
<th>Order ID</th>
<th>Mark No</th>
<th>Origin</th>
<th>Destination</th>
<th>Total CTN</th>
<th>Total QTY</th>
<th>Total TTL/QTY</th>
<th>Total Amount ()</th>
<th>Total CBM</th>
<th>Total TTL CBM</th>
<th>Total KG</th>
<th>Total TTL KG</th>
<th>Status</th>
<th>Date</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="ordersTableBody">
@forelse($orders as $order)
<tr>
<td>{{ $order->id }}</td>
<td>
<span class="badge bg-info text-dark">{{ ucfirst($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 href="javascript:void(0)"
class="fw-semibold text-primary open-order-modal"
data-id="{{ $order->id }}">
{{ $order->order_id }}
</a>
</td>
</tr>
@empty
<tr>
<td colspan="16" class="text-muted">No orders found</td>
</tr>
@endforelse
</tbody>
</table>
<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>
<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>
@@ -1167,6 +1449,14 @@ document.addEventListener('DOMContentLoaded', function() {
const closeBtn = document.getElementById('closeCreateOrderModal');
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
const resetTempData = () => {
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>
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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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-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://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>
body {
@@ -15,6 +17,7 @@
font-family: 'Inter', Arial, sans-serif;
display: flex;
flex-direction: row;
transition: all 0.3s ease-in-out;
}
/* ✨ Sidebar Glass + Animated Highlight Effect */
@@ -34,7 +37,13 @@
position: fixed;
top: 0;
left: 0;
}
/* Sidebar collapsed state */
.sidebar.collapsed {
transform: translateX(-100%);
opacity: 0;
visibility: hidden;
}
.sidebar .logo {
@@ -71,7 +80,6 @@
overflow: hidden;
transition: all 0.25s ease;
z-index: 0;
}
/* Background Animation */
@@ -149,6 +157,39 @@
flex-direction: column;
width: calc(100vw - 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 {
@@ -163,8 +204,13 @@
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
}
.header-left {
display: flex;
align-items: center;
}
.content-wrapper {
padding: 18px 16px 0 16px;
padding: 18px 16px 16px 16px;
flex-grow: 1;
overflow-y: auto;
}
@@ -182,44 +228,96 @@
<div class="word"><strong>KENT</strong><br /><small>International Pvt. Ltd.</small></div>
</div>
<a href="{{ route('admin.dashboard') }}" class="{{ request()->routeIs('admin.dashboard') ? 'active' : '' }}"><i class="bi bi-house"></i> Dashboard</a>
<a href="{{ route('admin.shipments') }}" class="{{ request()->routeIs('admin.shipments') ? 'active' : '' }}"><i class="bi bi-truck"></i> Shipments</a>
<a href="{{ route('admin.invoices.index') }}"
class="{{ request()->routeIs('admin.invoices.index') ? 'active' : '' }}">
{{-- Dashboard (requires order.view) --}}
@can('order.view')
<a href="{{ route('admin.dashboard') }}" class="{{ request()->routeIs('admin.dashboard') ? '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
</a>
@endcan
<a href="{{ route('admin.customers.index') }}"
class="{{ request()->routeIs('admin.customers.index') ? 'active' : '' }}">
{{-- Customers --}}
@can('customer.view')
<a href="{{ route('admin.customers.index') }}" class="{{ request()->routeIs('admin.customers.index') ? 'active' : '' }}">
<i class="bi bi-people"></i> Customers
</a>
@endcan
<a href="{{ route('admin.reports') }}"
class="{{ request()->routeIs('admin.reports') ? 'active' : '' }}">
{{-- Reports --}}
@can('report.view')
<a href="{{ route('admin.reports') }}" class="{{ request()->routeIs('admin.reports') ? 'active' : '' }}">
<i class="bi bi-graph-up"></i> Reports
</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>
<!-- <a href="{{ route('admin.orders.index') }}"
class="{{ request()->routeIs('admin.orders.*') ? 'active' : '' }}">
<i class="bi bi-bag"></i> Orders
</a> -->
<a href="{{ route('admin.orders') }}" class="{{ request()->routeIs('admin.orders') ? 'active' : '' }}"><i class="bi bi-bag"></i> Orders</a>
<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>
{{-- Orders --}}
@can('orders.view')
<a href="{{ route('admin.orders') }}" class="{{ request()->routeIs('admin.orders') ? 'active' : '' }}">
<i class="bi bi-bag"></i> Orders
</a>
@endcan
{{-- Requests --}}
@can('request.view')
<a href="{{ route('admin.requests') }}" class="{{ request()->routeIs('admin.requests') ? 'active' : '' }}">
<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
<form method="POST" action="{{ route('admin.logout') }}" class="mt-4 px-3">
@csrf
<button type="submit" class="btn btn-danger w-100"><i class="bi bi-box-arrow-right"></i> Logout</button>
</form>
</div>
<div class="main-content">
<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">
<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>
@@ -248,5 +346,23 @@
</div>
<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>
</html>

View File

@@ -120,19 +120,33 @@
@endif
<form method="POST" action="{{ route('admin.login.submit') }}">
@csrf
<div class="mb-3">
<label>Email</label>
<input type="email" name="email" class="form-control" value="{{ old('email') }}" required>
</div>
@csrf
<div class="mb-3">
<label>Password</label>
<input type="password" name="password" class="form-control" required>
</div>
<div class="mb-3">
<label>Username / Email / Employee ID</label>
<input
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">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="green" class="bi bi-lock-fill" viewBox="0 0 16 16">

View File

@@ -80,60 +80,158 @@
transition: all 0.2s ease;
}
/* ✨ NEW BADGE STYLES - Same as Invoice System */
.badge {
padding: 7px 16px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: 600;
user-select: none;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
font-size: 11px !important;
font-weight: 600 !important;
padding: 6px 12px 6px 8px !important;
border-radius: 20px !important;
text-transform: uppercase;
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 {
background-color: #28a745 !important;
color: #fff !important;
box-shadow: 0 0 10px #6ee86e77;
/* Status icons */
.status-icon {
font-size: 12px;
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;
}
.badge.badge-inactive {
animation: pulseRed 2s infinite;
}
@keyframes pulseGreen {
0% { box-shadow: 0 0 8px #6ee86e77; }
50% { box-shadow: 0 0 14px #5dd75d88; }
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 {
0% { box-shadow: 0 0 8px #f97f7f77; }
50% { box-shadow: 0 0 14px #ff868677; }
100% { box-shadow: 0 0 8px #f97f7f77; }
}
/* ✨ ENHANCED ACTION BUTTONS - Professional & Attractive */
.btn-action {
background-color: #ffd283;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
color: #1b1c21;
padding: 7px 20px;
font-size: 0.95rem;
border-radius: 8px;
font-weight: 700;
transition: all 0.25s ease;
color: white;
padding: 10px 22px;
font-size: 0.85rem;
border-radius: 12px;
font-weight: 600;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
margin: 3px;
cursor: pointer;
box-shadow: 0 2px 6px rgba(141, 106, 0, 0.1);
display: inline-block;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
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 {
background-color: #ffc85a;
transform: translateY(-2px);
box-shadow: 0 4px 10px rgba(141, 106, 0, 0.25);
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
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 {
@@ -179,6 +277,139 @@
from { box-shadow: 0 0 0px #b4a02455 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>
@if(session('success'))
@@ -206,51 +437,207 @@
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach($markList as $mark)
<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 id="markListTableBody">
<!-- Data will be loaded dynamically -->
</tbody>
</table>
@if($markList->isEmpty())
<div class="py-4 text-center text-muted fst-italic">No mark numbers found.</div>
@endif
<!-- Pagination Controls -->
<div class="pagination-container">
<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>
<script>
document.getElementById('globalSearch').addEventListener('input', function () {
const filter = this.value.toLowerCase();
const rows = document.querySelectorAll('#markListTable tbody tr');
// Pagination state
let currentPage = 1;
const itemsPerPage = 10;
let allMarkList = @json($markList);
let filteredMarkList = [...allMarkList];
rows.forEach(row => {
const text = row.textContent.toLowerCase();
row.style.display = text.includes(filter) ? '' : 'none';
});
// Initialize pagination
document.addEventListener('DOMContentLoaded', function() {
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>
</div>
</div>

File diff suppressed because it is too large Load Diff

View 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']) &nbsp; | Status: <strong>{{ ucfirst($filters['status']) }}</strong> @endif
@if($filters['shipment']) &nbsp; | 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>

View File

@@ -5,20 +5,92 @@
@section('content')
<div class="container py-4">
{{-- Header --}}
{{-- HEADER --}}
<div class="card shadow-sm rounded-3 border-0">
<div class="card-body">
{{-- TOP BAR --}}
<div class="d-flex justify-content-between align-items-start">
<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>
</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>
</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>
{{-- 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="col-md-8 d-flex">
<div class="me-3">
@@ -27,6 +99,7 @@
{{ strtoupper(substr($user->customer_name ?? 'U', 0, 1)) }}
</div>
</div>
<div>
<h5 class="mb-0">{{ $user->customer_name ?? 'Unknown Customer' }}</h5>
<p class="mb-0">{{ $user->company_name ?? 'N/A' }}</p>
@@ -34,13 +107,14 @@
<p class="mb-0 text-muted">{{ $user->mobile_no ?? '' }}</p>
</div>
</div>
<div class="col-md-4 text-end">
<p class="mb-0">{{ $user->address ?? '' }}</p>
<small class="text-muted">{{ $user->pincode ?? '' }}</small>
</div>
</div>
{{-- Order Summary --}}
{{-- ORDER SUMMARY --}}
<div class="bg-light rounded p-3 mb-3">
<div class="row text-center">
<div class="col-md-3 border-end">
@@ -65,7 +139,7 @@
</div>
</div>
{{-- Origin - Destination --}}
{{-- ORIGIN / DESTINATION --}}
<div class="row text-center mb-4">
<div class="col-md-6">
<p class="mb-1 fw-semibold text-muted">Origin</p>
@@ -77,7 +151,7 @@
</div>
</div>
{{-- Order Items Table --}}
{{-- ITEMS TABLE --}}
<div class="table-responsive">
<table class="table table-bordered align-middle text-center">
<thead class="table-light">
@@ -88,15 +162,17 @@
<th>QTY</th>
<th>TTL/QTY</th>
<th>Unit</th>
<th>Price ()</th>
<th>TTL Amount ()</th>
<th>Price</th>
<th>Total 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>
@foreach($order->items as $index => $item)
<tr>
@@ -113,38 +189,733 @@
<td>{{ $item->kg }}</td>
<td>{{ $item->ttl_kg }}</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>
@endforeach
</tbody>
</table>
</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="col-md-3">
<h6 class="fw-bold text-primary">{{ $order->ctn }}</h6>
<small class="text-muted">Total CTN</small>
<small>Total CTN</small>
</div>
<div class="col-md-3">
<h6 class="fw-bold text-primary">{{ $order->qty }}</h6>
<small class="text-muted">Total QTY</small>
<small>Total QTY</small>
</div>
<div class="col-md-3">
<h6 class="fw-bold text-success">{{ $order->ttl_kg }}</h6>
<small class="text-muted">Total TTL KG</small>
<small>Total KG</small>
</div>
<div class="col-md-3">
<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>
@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

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

View File

@@ -1,71 +1,438 @@
<div class="p-4">
<!-- Invoice Header -->
<div class="row mb-4">
<div class="col-md-6">
<h2 class="fw-bold text-primary mb-1">
<i class="fas fa-file-invoice me-2"></i>INVOICE
</h2>
<h4 class="fw-bold text-dark mb-0">{{ $invoice->invoice_number }}</h4>
</div>
<div class="col-md-6 text-end">
<div class="d-inline-block bg-light rounded-3 p-3">
<span class="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
fs-6 px-3 py-2">
<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>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Professional Invoice</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<style>
:root {
--primary: #2c3e50;
--secondary: #3498db;
--accent: #e74c3c;
--light: #f8f9fa;
--dark: #2c3e50;
--success: #27ae60;
--warning: #f39c12;
--danger: #e74c3c;
--border-radius: 8px;
--box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
<!-- Dates - Compact Professional Layout -->
<div class="row mb-3">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-body py-2">
<div class="row align-items-center text-center">
<div class="col-md-5">
<div class="mb-0">
<div class="text-muted fw-semibold small">INVOICE DATE</div>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
color: #333;
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
<div class="compact-header">
<div class="row align-items-center">
<div class="col-md-6">
<h2 class="invoice-title mb-1">
<i class="fas fa-file-invoice me-2"></i> INVOICE
</h2>
<h4 class="fw-bold text-dark mb-0">{{ $invoice->invoice_number }}</h4>
</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 class="fw-bold text-dark" style="font-size: 0.95rem;">
{{ \Carbon\Carbon::parse($invoice->invoice_date)->format('M d, Y') }}
<div class="id-label">Invoice ID</div>
<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 class="col-md-2">
<div class="date-connector">
<i class="fas fa-arrow-right text-muted small"></i>
</div>
<!-- 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 class="col-md-5">
<div class="mb-0">
<div class="text-muted fw-semibold small">DUE DATE</div>
</div>
</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 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') }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Customer Details -->
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light py-2">
<h6 class="mb-0 fw-bold text-dark">
<i class="fas fa-user me-2"></i>Customer Details
<!-- ============================
CUSTOMER DETAILS
============================ -->
<div class="card">
<div class="card-header">
<h6 class="mb-0 fw-bold">
<i class="fas fa-user me-2"></i> Customer Details
</h6>
</div>
<div class="card-body">
@@ -85,24 +452,25 @@
</p>
</div>
<div class="col-md-6">
<p class="mb-0">
<p class="mb-1">
<strong>Address:</strong><br>
{{ $invoice->customer_address }}
</p>
<p class="mb-1">
<strong>Pincode:</strong> {{ $invoice->pincode }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Invoice Items -->
<div class="row mb-4">
<div class="col-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light py-2">
<h6 class="mb-0 fw-bold text-dark">
<i class="fas fa-list me-2"></i>Invoice Items
<!-- ============================
INVOICE ITEMS
============================ -->
<div class="card">
<div class="card-header">
<h6 class="mb-0 fw-bold">
<i class="fas fa-list me-2"></i> Invoice Items
</h6>
</div>
<div class="card-body p-0">
@@ -150,58 +518,103 @@
</div>
</div>
</div>
</div>
</div>
<!-- Final Summary -->
<div class="row">
<div class="col-md-6 offset-md-6">
<div class="card border-0 bg-light">
<div class="card-header bg-dark text-white py-2">
<h6 class="mb-0 fw-bold">
<i class="fas fa-calculator me-2"></i>Final Summary
</h6>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
<span class="fw-semibold">Amount:</span>
<span class="fw-bold text-dark">{{ number_format($invoice->final_amount,2) }}</span>
</div>
<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>
<div class="d-flex justify-content-between align-items-center pt-1">
<span class="fw-bold text-dark">Total With GST:</span>
<span class="fw-bold text-success">{{ number_format($invoice->final_amount_with_gst,2) }}</span>
<!-- ============================
FINAL SUMMARY
============================ -->
<div class="row">
<div class="col-md-6 offset-md-6">
<div class="card summary-card">
<div class="card-header summary-header">
<h6 class="mb-0 fw-bold">
<i class="fas fa-calculator me-2"></i> Final Summary
</h6>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
<span class="fw-semibold">Amount:</span>
<span class="fw-bold text-dark">{{ number_format($invoice->final_amount,2) }}</span>
</div>
@if($invoice->tax_type === 'gst')
{{-- CGST --}}
<div class="d-flex justify-content-between align-items-center mb-2 pb-1 border-bottom">
<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>
<!-- ============================
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>
<style>
.date-connector {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #6c757d;
}
.date-connector i {
background: #f8f9fa;
padding: 4px;
border-radius: 50%;
border: 1px solid #e9ecef;
}
.card {
border-radius: 6px;
}
.table {
margin-bottom: 0;
}
.table > :not(caption) > * > * {
padding: 10px 6px;
}
</style>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
<!-- ============================
SHARE SCRIPT
============================ -->
<script>
function shareInvoice() {
const shareData = {
title: "Invoice {{ $invoice->invoice_number }}",
text: "Sharing invoice {{ $invoice->invoice_number }}",
url: "{{ asset($invoice->pdf_path) }}"
};
if (navigator.share) {
navigator.share(shareData).catch(() => {});
} else {
navigator.clipboard.writeText(shareData.url);
alert("Link copied! Sharing not supported on this browser.");
}
}
</script>
</body>
</html>

View 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

View File

@@ -5,223 +5,456 @@
@section('content')
<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>
/* 🌟 Smooth fade-in animation */
@keyframes fadeInUp {
0% {
transform: translateY(20px);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
/* [ALL YOUR ORIGINAL CSS HERE - SAME AS BEFORE] */
@keyframes fadeInUp {0% { transform: translateY(20px); opacity: 0; }100% { transform: translateY(0); opacity: 1; }}
.card, .custom-table-wrapper { animation: fadeInUp 0.8s ease both; }
.custom-table tbody tr { transition: all 0.25s ease-in-out; }
.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 {
display: inline-flex; align-items: center; font-size: 13.5px; padding: 6px 16px; border-radius: 12px; font-weight: 600;
box-shadow: 0 1px 2px 0 rgba(130,130,130,0.15); width: 90px; min-height: 28px; justify-content: center;
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; }
/* ✨ Container animation */
.card, .custom-table-wrapper {
animation: fadeInUp 0.8s ease both;
}
/* ===== PAGINATION STYLES ===== */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 0;
border-top: 1px solid #eef3fb;
}
/* 🪄 Table hover effect */
.custom-table tbody tr {
transition: all 0.25s ease-in-out;
}
.custom-table tbody tr:hover {
background-color: #fffbea;
transform: scale(1.01);
box-shadow: 0 2px 6px rgba(0,0,0,0.05);
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
/* 🎯 Priority Badges */
.priority-badge {
display: inline-flex;
align-items: center;
font-size: 13.5px;
padding: 4px 16px 4px 11px;
border-radius: 12px;
font-weight: 500;
gap: 6px;
box-shadow: 0 1px 2px 0 rgba(130,130,130,0.15);
min-width: 76px;
min-height: 28px;
justify-content: center;
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); color: #fff; }
.priority-medium { background: linear-gradient(135deg, #ffe390, #f5b041); color: #fff; }
.priority-low { background: linear-gradient(135deg, #b8f0c2, #1d8660); color: #fff; }
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.priority-badge .icon {
margin-right: 5px;
font-size: 1.1em;
line-height: 1;
display:inline-block;
width: 17px;
text-align: center;
}
.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;
}
/* 🎨 Table Header (keep original bg-light color) */
.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 /* Bootstrap bg-light */
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
/* 🟢 Rounded Corners for Header */
.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;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
/* 🧾 Table bottom corners */
.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;
}
.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;
text-decoration: none;
}
/* 💫 Search box styling */
.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;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
/* 🎨 Card Style */
.card {
border-radius: 16px;
border: none;
box-shadow: 0 4px 10px rgba(0,0,0,0.08);
background: #fff;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
/* ⚡ Status Badges */
.badge {
font-size: 13px;
padding: 7px 12px;
border-radius: 10px;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
/* ✨ Heading style */
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;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
/* ✅ Center align for last column */
.custom-table tbody td:last-child {
text-align: center !important;
vertical-align: middle !important;
}
.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;
text-decoration: none;
}
.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 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>
<!-- Heading and status badges row -->
<!-- Counts -->
<div class="d-flex justify-content-between align-items-center mb-2 mt-3">
<h4 class="fw-bold mb-0">User Requests</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>
<h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4>
</div>
<!-- Search + Table -->
<div class="card mb-4 shadow-sm">
<div class="card-body pb-1">
<form method="GET" action="">
<div class="input-group mb-2">
<input type="text" name="search" value="{{ old('search', request('search')) }}" class="form-control" placeholder="Search Request by name, email, Company or Request ID">
<button class="btn btn-outline-primary" type="submit"><i class="bi bi-search"></i></button>
<!-- Updated Search Bar with Status Badges in the same line -->
<div class="search-container">
<form method="GET" action="" class="search-form">
<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>
</form>
</div>
<div class="table-responsive custom-table-wrapper">
<table class="table align-middle mb-0 custom-table">
<thead class="bg-light">
<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>
</tr>
</thead>
<thead><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>
</tr></thead>
<tbody>
@foreach($requests as $index => $req)
@forelse($currentItems as $index => $req)
<tr>
<td>{{ $requests->count() - $index }}</td>
<td><strong>{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong></td>
<td>{{ $req->request_id }}</td>
<td>{{ $req->customer_name }}</td>
<td>{{ $req->company_name }}</td>
<td>{{ $req->email }}</td>
<td>{{ $req->mobile_no }}</td>
<td>{{ $req->address }}</td>
<td>{{ Str::limit($req->address, 30) }}</td>
<td>
@if(strtolower($req->priority) == 'high')
<span class="priority-badge priority-high"><span class="icon">🔥</span>High</span>
@elseif(strtolower($req->priority) == 'medium')
<span class="priority-badge priority-medium"><span class="icon"></span>Medium</span>
@elseif(strtolower($req->priority) == 'low')
<span class="priority-badge priority-low"><span class="icon">🟢</span>Low</span>
@else
{{ $req->priority }}
@endif
@if(strtolower($req->priority) == 'high')<span class="priority-badge priority-high">High</span>
@elseif(strtolower($req->priority) == 'medium')<span class="priority-badge priority-medium">Medium</span>
@elseif(strtolower($req->priority) == 'low')<span class="priority-badge priority-low">Low</span>
@else{{ $req->priority ?? 'N/A' }}@endif
</td>
<td>{{ $req->date }}</td>
<td>
@if($req->status == 'approved')
<span class="badge bg-success">Approved</span>
@elseif($req->status == 'rejected')
<span class="badge bg-danger">Rejected</span>
@if($req->status == 'approved')<span class="badge badge-approved"><i class="bi bi-check-circle-fill status-icon"></i>Approved</span>
@elseif($req->status == 'rejected')<span class="badge badge-rejected"><i class="bi bi-x-circle-fill status-icon"></i>Rejected</span>
@else<span class="badge badge-pending"><i class="bi bi-clock-fill status-icon"></i>Pending</span>@endif
</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
<span class="badge bg-warning text-dark">Pending</span>
<span class="text-muted">No Action</span>
@endif
</td>
<td>N/A</td>
</tr>
@endforeach
@empty
<tr><td colspan="11" class="text-center text-muted py-4">No records found.</td></tr>
@endforelse
</tbody>
</table>
@if($requests->isEmpty())
<div class="py-4 text-center text-muted">No records found.</div>
@endif
</div>
{{-- 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>

View File

@@ -258,6 +258,14 @@
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 */
.badge-pending {
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
@@ -320,7 +328,30 @@
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 {
position: relative;
display: inline-block;
@@ -368,6 +399,16 @@
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 {
padding: 10px 12px;
border: none;
@@ -431,6 +472,11 @@
display: inline-block;
}
/* Loading Status Indicator - PROPER */
.status-indicator.loading {
background: #2196f3;
}
.status-indicator.pending {
background: #f8961e;
}
@@ -917,6 +963,151 @@
.shipment-row.visible {
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>
<div class="container-fluid py-4">
@@ -938,15 +1129,13 @@
</div>
@endif
<!-- ============================= -->
<!-- SEARCH BAR AND ADD BUTTON -->
<!-- ============================= -->
<div class="search-shipment-bar">
<span class="search-icon">🔍</span>
<input type="text" id="searchInput" placeholder="Search Shipments...">
<div class="status-filter-container">
<select id="statusFilter" class="status-filter-select">
<option value="all">All Status</option>
<option value="loading">Loading</option>
<option value="pending">Pending</option>
<option value="in_transit">In Transit</option>
<option value="dispatched">Dispatched</option>
@@ -965,9 +1154,6 @@
</button>
</div>
<!-- ============================= -->
<!-- CREATE SHIPMENT MODAL -->
<!-- ============================= -->
<div class="modal fade" id="createShipmentModal" tabindex="-1" aria-labelledby="createShipmentModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
@@ -1059,9 +1245,6 @@
</div>
</div>
<!-- ============================= -->
<!-- SHIPMENT LIST TABLE -->
<!-- ============================= -->
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-truck me-2"></i> Shipments List</h5>
@@ -1082,6 +1265,7 @@
<th>Total Amount</th>
<th>Status</th>
<th>Date</th>
<th>View</th>
<th>Action</th>
</tr>
</thead>
@@ -1112,6 +1296,12 @@
</span>
</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>
<div class="action-container">
<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">
@csrf
<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">
<span class="status-indicator pending"></span>
Pending
@@ -1144,7 +1338,7 @@
</tr>
@empty
<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>
No shipments found
</td>
@@ -1153,12 +1347,28 @@
</tbody>
</table>
</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>
<!-- ============================= -->
<!-- SHIPMENT DETAILS MODAL -->
<!-- ============================= -->
{{-- SHIPMENT DETAILS MODAL --}}
<div class="modal fade" id="shipmentDetailsModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
@@ -1181,16 +1391,26 @@
</div>
<!-- ========================= -->
<!-- MODAL LOAD SCRIPT (AJAX) -->
<!-- ========================= -->
<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() {
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 searchInput = document.getElementById('searchInput');
const carrierFilter = document.getElementById('carrierFilter');
const shipmentRows = document.querySelectorAll('.shipment-row');
// Function to filter shipments
function filterShipments() {
@@ -1198,48 +1418,34 @@ document.addEventListener('DOMContentLoaded', function() {
const searchTerm = searchInput.value.toLowerCase();
const selectedCarrier = carrierFilter.value;
shipmentRows.forEach(row => {
const status = row.getAttribute('data-status');
const shipmentId = row.getAttribute('data-shipment-id').toLowerCase();
const origin = row.cells[2].textContent.toLowerCase();
const destination = row.cells[3].textContent.toLowerCase();
filteredShipments = allShipments.filter(shipment => {
let include = true;
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) {
row.classList.remove('hidden');
row.classList.add('visible');
} else {
row.classList.add('hidden');
row.classList.remove('visible');
// Status filter
if (selectedStatus !== 'all' && shipment.status !== selectedStatus) {
include = false;
}
// 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
const visibleRows = document.querySelectorAll('.shipment-row:not(.hidden)');
const noResultsRow = document.querySelector('.shipment-row[colspan]');
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();
}
currentPage = 1;
renderTable();
updatePaginationControls();
}
// Event listeners for filters
@@ -1251,6 +1457,193 @@ document.addEventListener('DOMContentLoaded', function() {
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) {
let modal = new bootstrap.Modal(document.getElementById('shipmentDetailsModal'));
let content = document.getElementById('shipmentDetailsContent');
@@ -1278,8 +1671,13 @@ function openShipmentDetails(id) {
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 = `
<!-- Shipment Basic Info -->
<div class="shipment-info-row">
<div class="shipment-info-item">
<div class="shipment-info-label">Shipment ID</div>
@@ -1291,7 +1689,7 @@ function openShipmentDetails(id) {
</div>
<div class="shipment-info-item">
<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 class="shipment-info-item">
<div class="shipment-info-label">Date</div>
@@ -1326,7 +1724,9 @@ function openShipmentDetails(id) {
data.orders.forEach(order => {
html += `
<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.destination || 'N/A'}</td>
<td>${order.mark_no || 'ITEM001'}</td>
@@ -1348,7 +1748,6 @@ function openShipmentDetails(id) {
</table>
</div>
<!-- Totals Section -->
<div class="shipment-totals">
<div class="shipment-totals-row">
<div class="shipment-total-item">

View 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

View 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

View 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

View 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

View File

@@ -4,17 +4,46 @@ use Illuminate\Support\Facades\Route;
use App\Http\Controllers\RequestController;
use App\Http\Controllers\UserAuthController;
use App\Http\Controllers\MarkListController;
use App\Http\Controllers\User\UserOrderController;
use App\Http\Controllers\User\UserProfileController;
//user send request
Route::post('/signup-request', [RequestController::class, 'usersignup']);
//login / logout
Route::post('/user/login', [UserAuthController::class, 'login']);
Route::post('/user/logout', [UserAuthController::class, 'logout'])->middleware('auth:api');
Route::middleware(['auth:api'])->group(function () {
Route::get('/show-mark-list', [MarkListController::class, 'showmarklist']); // Fetch all marks by user
Route::post('/add-mark-no', [MarkListController::class, 'addmarkno']); // Create new mark
//Route::post('/user/refresh', [UserAuthController::class, 'refreshToken']);
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']);
});

View File

@@ -10,6 +10,7 @@ use App\Http\Controllers\Admin\AdminInvoiceController;
use App\Http\Controllers\Admin\AdminCustomerController;
use App\Http\Controllers\Admin\AdminAccountController;
use App\Http\Controllers\Admin\AdminReportController;
use App\Http\Controllers\Admin\AdminStaffController;
// ---------------------------
// Public Front Page
@@ -21,17 +22,11 @@ Route::get('/', function () {
// ---------------------------
// ADMIN LOGIN ROUTES
// ---------------------------
// login routes (public)
Route::prefix('admin')->group(function () {
// MUST have route name "login" for session redirect
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])
->name('admin.login');
Route::post('/login', [AdminAuthController::class, 'login'])
->name('admin.login.submit');
Route::post('/logout', [AdminAuthController::class, 'logout'])
->name('admin.logout');
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])->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
// ---------------------------
Route::get('/requests', [UserRequestController::class, 'index'])
->name('admin.requests');
Route::get('/requests', [UserRequestController::class, 'index'])
->name('admin.requests');
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
->name('admin.requests.approve');
Route::get('/requests/approve/{id}', [UserRequestController::class, 'approve'])
->name('admin.requests.approve');
Route::get('/requests/reject/{id}', [UserRequestController::class, 'reject'])
->name('admin.requests.reject');
// 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
// ---------------------------
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
->name('admin.marklist.index');
Route::get('/mark-list', [AdminMarkListController::class, 'index'])
->name('admin.marklist.index');
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
->name('admin.marklist.toggle');
Route::get('/mark-list/status/{id}', [AdminMarkListController::class, 'toggleStatus'])
->name('admin.marklist.toggle');
// ---------------------------
// ORDERS
// ---------------------------
Route::get('/orders', fn() => view('admin.orders'))
->name('admin.orders');
Route::get('/orders', [AdminOrderController::class, 'orderShow'])
->name('admin.orders');
Route::get('/orders/list', [AdminOrderController::class, 'index'])
->name('admin.orders.index');
Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
->name('admin.orders.show');
Route::get('/orders/list', [AdminOrderController::class, 'index'])
->name('admin.orders.index');
Route::post('/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
->name('admin.orders.temp.add');
Route::get('/orders/{id}', [AdminOrderController::class, 'show'])
->name('admin.orders.show');
Route::post('/orders/temp/delete', [AdminOrderController::class, 'deleteTempItem'])
->name('admin.orders.temp.delete');
Route::post('/orders/temp/add', [AdminOrderController::class, 'addTempItem'])
->name('admin.orders.temp.add');
Route::post('/orders/temp/reset', [AdminOrderController::class, 'resetTemp'])
->name('admin.orders.temp.reset');
Route::post('/orders/temp/delete', [AdminOrderController::class, 'deleteTempItem'])
->name('admin.orders.temp.delete');
Route::post('/orders/finish', [AdminOrderController::class, 'finishOrder'])
->name('admin.orders.finish');
Route::post('/orders/temp/reset', [AdminOrderController::class, 'resetTemp'])
->name('admin.orders.temp.reset');
Route::get('/orders/view/{id}', [AdminOrderController::class, 'popup'])
->name('admin.orders.popup');
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'])
->name('admin.shipments');
// Add item to order
Route::post('/orders/{order}/item', [AdminOrderController::class, 'addItem'])
->name('admin.orders.addItem');
// Delete item from order
Route::delete('/orders/item/{id}', [AdminOrderController::class, 'deleteItem'])
->name('admin.orders.deleteItem');
// Restore deleted item
Route::post('/orders/item/{id}/restore', [AdminOrderController::class, 'restoreItem'])
->name('admin.orders.restoreItem');
// Update main order fields
Route::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');
Route::post('/shipments/store', [ShipmentController::class, 'store'])
->name('admin.shipments.store');
// ---------------------------
// SHIPMENTS (FIXED ROUTES)
// ---------------------------
Route::get('/shipments', [ShipmentController::class, 'index'])
->name('admin.shipments');
Route::post('/shipments/update-status', [ShipmentController::class, 'updateStatus'])
->name('admin.shipments.updateStatus');
Route::post('/shipments', [ShipmentController::class, 'store'])
->name('admin.shipments.store');
Route::get('/shipments/{id}', [ShipmentController::class, 'show'])
->name('admin.shipments.show');
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::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
->name('admin.shipments.dummy');
// ---------------------------
// INVOICES
// ---------------------------
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
->name('admin.invoices.index');
Route::get('/invoices', [AdminInvoiceController::class, 'index'])
->name('admin.invoices.index');
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
->name('admin.invoices.popup');
Route::get('/invoices/{id}/popup', [AdminInvoiceController::class, 'popup'])
->name('admin.invoices.popup');
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
->name('admin.invoices.edit');
Route::get('/invoices/{id}/edit', [AdminInvoiceController::class, 'edit'])
->name('admin.invoices.edit');
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
->name('admin.invoices.update');
Route::post('/invoices/{id}/update', [AdminInvoiceController::class, 'update'])
->name('admin.invoices.update');
//Add New Invoice
Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
Route::post('/invoices/{invoice}/installments', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::post('/invoices/{id}/installment', [AdminInvoiceController::class, 'storeInstallment'])
->name('admin.invoice.installment.store');
Route::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
// ---------------------------
Route::get('/customers', [AdminCustomerController::class, 'index'])
->name('admin.customers.index');
Route::get('/customers', [AdminCustomerController::class, 'index'])
->name('admin.customers.index');
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
->name('admin.customers.add');
Route::get('/customers/add', [AdminCustomerController::class, 'create'])
->name('admin.customers.add');
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
->name('admin.customers.store');
Route::post('/customers/store', [AdminCustomerController::class, 'store'])
->name('admin.customers.store');
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
->name('admin.customers.view');
Route::get('/customers/{id}/view', [AdminCustomerController::class, 'view'])
->name('admin.customers.view');
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
->name('admin.customers.status');
Route::post('/customers/{id}/status', [AdminCustomerController::class, 'toggleStatus'])
->name('admin.customers.status');
});
// ==========================================
// ADMIN ACCOUNT (AJAX) ROUTES
// ==========================================
Route::prefix('admin/account')
->middleware('auth:admin')
->name('admin.account.')
->group(function () {
Route::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');
});