diff --git a/app/Http/Controllers/Admin/AdminAuthController.php b/app/Http/Controllers/Admin/AdminAuthController.php index 109f4a5..3b664d9 100644 --- a/app/Http/Controllers/Admin/AdminAuthController.php +++ b/app/Http/Controllers/Admin/AdminAuthController.php @@ -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.'); } } diff --git a/app/Http/Controllers/Admin/AdminOrderController.php b/app/Http/Controllers/Admin/AdminOrderController.php index 697af83..245a6c9 100644 --- a/app/Http/Controllers/Admin/AdminOrderController.php +++ b/app/Http/Controllers/Admin/AdminOrderController.php @@ -148,6 +148,7 @@ class AdminOrderController extends Controller // recalc totals and save to order $this->recalcTotals($order); + $this->updateInvoiceFromOrder($order); // <-- NEW return redirect()->back()->with('success', 'Item added and totals updated.'); } @@ -164,6 +165,8 @@ class AdminOrderController extends Controller // recalc totals $this->recalcTotals($order); + $this->updateInvoiceFromOrder($order); + return redirect()->back()->with('success', 'Item deleted and totals updated.'); } @@ -180,6 +183,8 @@ class AdminOrderController extends Controller // recalc totals $this->recalcTotals($order); + $this->updateInvoiceFromOrder($order); + return redirect()->back()->with('success', 'Item restored and totals updated.'); } @@ -383,79 +388,79 @@ class AdminOrderController extends Controller return view('admin.orders', compact('orders')); } -// inside AdminOrderController + // inside AdminOrderController -private function buildOrdersQueryFromRequest(Request $request) -{ - $query = Order::with(['markList', 'invoice', 'shipments']); + 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}%"); - }); - }); + // 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; } - // Invoice status filter - if ($request->filled('status')) { - $query->whereHas('invoice', function($q) use ($request) { - $q->where('status', $request->status); - }); + 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); } - // Shipment status filter - if ($request->filled('shipment')) { - $query->whereHas('shipments', function($q) use ($request) { - $q->where('status', $request->shipment); - }); + 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'); } - // optional ordering - $query->latest('id'); - return $query; -} - -public function downloadPdf(Request $request) -{ - // Build same filtered query used for table - $query = $this->buildOrdersQueryFromRequest($request); - - $orders = $query->get(); - - // optional: pass filters to view for header - $filters = [ - 'search' => $request->search ?? null, - 'status' => $request->status ?? null, - 'shipment' => $request->shipment ?? null, - ]; - - $pdf = PDF::loadView('admin.orders.pdf', compact('orders', 'filters')) - ->setPaper('a4', 'landscape'); // adjust if needed - - $fileName = 'orders-report' - . ($filters['status'] ? "-{$filters['status']}" : '') - . '-' . date('Y-m-d') . '.pdf'; - - return $pdf->download($fileName); -} - -public function downloadExcel(Request $request) -{ - // pass request to OrdersExport which will build Filtered query internally - return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx'); -} - - -public function addTempItem(Request $request) + public function addTempItem(Request $request) { // Validate item fields $item = $request->validate([ @@ -509,184 +514,228 @@ public function addTempItem(Request $request) // ------------------------------------------------------------------------- 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, ]); + + // 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.'); } - // ======================= - // 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 CRUD: update / destroy // --------------------------- - public function updateItem(Request $request, $id) + 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, + '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, ]); - return back()->with('success', 'Item updated successfully!'); + $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, + ]); + } } } \ No newline at end of file diff --git a/app/Http/Controllers/Admin/AdminStaffController.php b/app/Http/Controllers/Admin/AdminStaffController.php new file mode 100644 index 0000000..020239e --- /dev/null +++ b/app/Http/Controllers/Admin/AdminStaffController.php @@ -0,0 +1,179 @@ +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.'); + } +} diff --git a/app/Models/Admin.php b/app/Models/Admin.php index a107900..118f371 100644 --- a/app/Models/Admin.php +++ b/app/Models/Admin.php @@ -1,22 +1,43 @@ attributes['password'] = Hash::make($value); + } else { + $this->attributes['password'] = $value; + } + } + + public function getDisplayNameAttribute() + { + return "{$this->name}"; + } } diff --git a/app/Models/Staff.php b/app/Models/Staff.php new file mode 100644 index 0000000..77327d8 --- /dev/null +++ b/app/Models/Staff.php @@ -0,0 +1,81 @@ + '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 . ')'; + } +} diff --git a/app/Providers/AuthServiceProvider.php b/app/Providers/AuthServiceProvider.php new file mode 100644 index 0000000..b5ea04c --- /dev/null +++ b/app/Providers/AuthServiceProvider.php @@ -0,0 +1,40 @@ + + */ + 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; + } + }); + } +} diff --git a/bootstrap/providers.php b/bootstrap/providers.php index f2b5dec..8e6dce1 100644 --- a/bootstrap/providers.php +++ b/bootstrap/providers.php @@ -3,4 +3,5 @@ return [ App\Providers\AppServiceProvider::class, App\Providers\RouteServiceProvider::class, + App\Providers\AuthServiceProvider::class, ]; diff --git a/composer.json b/composer.json index 1545c24..a38437b 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "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", diff --git a/composer.lock b/composer.lock index a94cb96..bf0a4be 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "6a2ec43d7e96d38cacfc2f9e1ae5cb9e", + "content-hash": "0ed807863dbf93f06565547ce4303f47", "packages": [ { "name": "barryvdh/laravel-dompdf", @@ -4364,6 +4364,89 @@ ], "time": "2025-08-05T09:57:14+00:00" }, + { + "name": "spatie/laravel-permission", + "version": "6.23.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/laravel-permission.git", + "reference": "9e41247bd512b1e6c229afbc1eb528f7565ae3bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/laravel-permission/zipball/9e41247bd512b1e6c229afbc1eb528f7565ae3bb", + "reference": "9e41247bd512b1e6c229afbc1eb528f7565ae3bb", + "shasum": "" + }, + "require": { + "illuminate/auth": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/container": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/contracts": "^8.12|^9.0|^10.0|^11.0|^12.0", + "illuminate/database": "^8.12|^9.0|^10.0|^11.0|^12.0", + "php": "^8.0" + }, + "require-dev": { + "laravel/passport": "^11.0|^12.0", + "laravel/pint": "^1.0", + "orchestra/testbench": "^6.23|^7.0|^8.0|^9.0|^10.0", + "phpunit/phpunit": "^9.4|^10.1|^11.5" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Spatie\\Permission\\PermissionServiceProvider" + ] + }, + "branch-alias": { + "dev-main": "6.x-dev", + "dev-master": "6.x-dev" + } + }, + "autoload": { + "files": [ + "src/helpers.php" + ], + "psr-4": { + "Spatie\\Permission\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Freek Van der Herten", + "email": "freek@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Permission handling for Laravel 8.0 and up", + "homepage": "https://github.com/spatie/laravel-permission", + "keywords": [ + "acl", + "laravel", + "permission", + "permissions", + "rbac", + "roles", + "security", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/laravel-permission/issues", + "source": "https://github.com/spatie/laravel-permission/tree/6.23.0" + }, + "funding": [ + { + "url": "https://github.com/spatie", + "type": "github" + } + ], + "time": "2025-11-03T20:16:13+00:00" + }, { "name": "symfony/clock", "version": "v7.4.0", diff --git a/config/auth.php b/config/auth.php index 1ced308..3015769 100644 --- a/config/auth.php +++ b/config/auth.php @@ -85,6 +85,11 @@ return [ // 'driver' => 'database', // 'table' => 'users', // ], + 'staff' => [ + 'driver' => 'eloquent', + 'model' => App\Models\Staff::class, + ], + ], /* diff --git a/config/permission.php b/config/permission.php new file mode 100644 index 0000000..b6c88e2 --- /dev/null +++ b/config/permission.php @@ -0,0 +1,202 @@ + [ + + /* + * 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', + ], +]; diff --git a/database/migrations/2025_12_04_070257_create_permission_tables.php b/database/migrations/2025_12_04_070257_create_permission_tables.php new file mode 100644 index 0000000..9eebf62 --- /dev/null +++ b/database/migrations/2025_12_04_070257_create_permission_tables.php @@ -0,0 +1,134 @@ +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']); + } +}; diff --git a/database/migrations/2025_12_04_071300_create_staff_table.php b/database/migrations/2025_12_04_071300_create_staff_table.php new file mode 100644 index 0000000..f4e0c46 --- /dev/null +++ b/database/migrations/2025_12_04_071300_create_staff_table.php @@ -0,0 +1,50 @@ +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'); + } +}; diff --git a/database/migrations/2025_12_04_125930_add_staff_fields_to_admins_table.php b/database/migrations/2025_12_04_125930_add_staff_fields_to_admins_table.php new file mode 100644 index 0000000..7cbf2b8 --- /dev/null +++ b/database/migrations/2025_12_04_125930_add_staff_fields_to_admins_table.php @@ -0,0 +1,41 @@ +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) { + // + }); + } +}; diff --git a/database/migrations/2025_12_04_131613_alter_role_column_in_admins_table.php b/database/migrations/2025_12_04_131613_alter_role_column_in_admins_table.php new file mode 100644 index 0000000..bf9e88f --- /dev/null +++ b/database/migrations/2025_12_04_131613_alter_role_column_in_admins_table.php @@ -0,0 +1,20 @@ +string('role')->nullable()->change(); // <-- Fix problem + }); + } + + public function down() + { + Schema::table('admins', function (Blueprint $table) { + $table->enum('role', ['admin', 'super-admin'])->nullable()->change(); + }); + } +}; diff --git a/database/seeders/PermissionSeeder.php b/database/seeders/PermissionSeeder.php new file mode 100644 index 0000000..aed64eb --- /dev/null +++ b/database/seeders/PermissionSeeder.php @@ -0,0 +1,103 @@ + $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); + } +} diff --git a/public/invoices/invoice-INV-2025-000017.pdf b/public/invoices/invoice-INV-2025-000017.pdf index ea85217..23bd004 100644 Binary files a/public/invoices/invoice-INV-2025-000017.pdf and b/public/invoices/invoice-INV-2025-000017.pdf differ diff --git a/resources/views/admin/account.blade.php b/resources/views/admin/account.blade.php index edac580..c584ca9 100644 --- a/resources/views/admin/account.blade.php +++ b/resources/views/admin/account.blade.php @@ -1040,7 +1040,9 @@ tr:hover td{ background:#fbfdff; } - + @can('account.create_order') + + @endcan
@@ -1250,7 +1252,7 @@ tr:hover td{ background:#fbfdff; }
- +
@@ -1304,7 +1306,9 @@ tr:hover td{ background:#fbfdff; }
+ @can('account.add_installment') + @endcan
@@ -1388,7 +1392,9 @@ tr:hover td{ background:#fbfdff; }
- + @can('account.add_installment') + + @endcan
@@ -1559,6 +1565,14 @@ tr:hover td{ background:#fbfdff; } + + + + +@endsection diff --git a/resources/views/admin/staff/edit.blade.php b/resources/views/admin/staff/edit.blade.php new file mode 100644 index 0000000..d6fc451 --- /dev/null +++ b/resources/views/admin/staff/edit.blade.php @@ -0,0 +1,156 @@ +@extends('admin.layouts.app') + +@section('page-title', 'Account Dashboard') + +@section('content') + + +
+

Edit Staff — {{ $staff->display_name ?? $staff->name }}

+ + @if($errors->any()) +
+ There were some problems with your input: + +
+ @endif + +
+ @csrf + @method('PUT') + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ + +
+ +
+ +
Permissions
+ + @foreach($permissions as $group => $groupPerms) +
+
+
{{ ucfirst($group) }}
+
+ +
+
+ +
+ @foreach($groupPerms as $perm) + + @endforeach +
+
+ @endforeach + +
+ Cancel + +
+
+
+ + +@endsection diff --git a/resources/views/admin/staff/index.blade.php b/resources/views/admin/staff/index.blade.php new file mode 100644 index 0000000..479014d --- /dev/null +++ b/resources/views/admin/staff/index.blade.php @@ -0,0 +1,65 @@ +@extends('admin.layouts.app') + +@section('page-title', 'Account Dashboard') + +@section('content') + + +
+

Staff

+ Add Staff +
+ +
+ @if(session('success')) +
{{ session('success') }}
+ @endif + + + + + + + + + + + + + + + + @forelse($staff as $s) + + + + + + + + + + + @empty + + @endforelse + +
#Employee IDNameEmailPhoneRoleStatusActions
{{ $s->id }}{{ $s->employee_id }}{{ $s->name }}{{ $s->email }}{{ $s->phone }}{{ $s->role ?? '-' }}{{ ucfirst($s->status) }} + Edit +
+ @csrf + @method('DELETE') + +
+
No staff found.
+
+@endsection diff --git a/routes/web.php b/routes/web.php index 18fa2f4..5620968 100644 --- a/routes/web.php +++ b/routes/web.php @@ -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,19 +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'); }); @@ -295,4 +288,13 @@ Route::prefix('admin') //--------------------------- //Edit Button Route //--------------------------- + // protected admin routes + Route::middleware(['auth:admin']) + ->prefix('admin') + ->name('admin.') + ->group(function () { + + // staff resource + Route::resource('staff', AdminStaffController::class); + });