staff update
This commit is contained in:
@@ -5,50 +5,52 @@ namespace App\Http\Controllers\Admin;
|
|||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Hash;
|
|
||||||
use App\Models\Admin;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class AdminAuthController extends Controller
|
class AdminAuthController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
|
||||||
* Show the admin login page
|
|
||||||
*/
|
|
||||||
public function showLoginForm()
|
public function showLoginForm()
|
||||||
{
|
{
|
||||||
return view('admin.login');
|
return view('admin.login');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle admin login
|
|
||||||
*/
|
|
||||||
public function login(Request $request)
|
public function login(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'email' => 'required|email',
|
'login' => 'required',
|
||||||
'password' => 'required|string|min:6',
|
'password' => 'required|string|min:6',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Try to log in using the 'admin' guard
|
$loginInput = $request->input('login');
|
||||||
if (Auth::guard('admin')->attempt($request->only('email', 'password'))) {
|
|
||||||
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, Admin!');
|
if (filter_var($loginInput, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
$field = 'email';
|
||||||
|
} elseif (preg_match('/^EMP\d+$/i', $loginInput)) {
|
||||||
|
$field = 'employee_id';
|
||||||
|
} else {
|
||||||
|
$field = 'username';
|
||||||
}
|
}
|
||||||
|
|
||||||
return back()->withErrors(['email' => 'Invalid email or password.']);
|
$credentials = [
|
||||||
|
$field => $loginInput,
|
||||||
|
'password' => $request->password,
|
||||||
|
];
|
||||||
|
|
||||||
|
// attempt login
|
||||||
|
if (Auth::guard('admin')->attempt($credentials)) {
|
||||||
|
$request->session()->regenerate();
|
||||||
|
$user = Auth::guard('admin')->user();
|
||||||
|
|
||||||
|
return redirect()->route('admin.dashboard')->with('success', 'Welcome back, ' . $user->name . '!');
|
||||||
|
}
|
||||||
|
|
||||||
|
return back()->withErrors(['login' => 'Invalid login credentials.']);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Logout admin
|
|
||||||
*/
|
|
||||||
public function logout(Request $request)
|
public function logout(Request $request)
|
||||||
{
|
{
|
||||||
Auth::guard('admin')->logout();
|
Auth::guard('admin')->logout();
|
||||||
|
|
||||||
// Destroy the session completely
|
|
||||||
$request->session()->invalidate();
|
$request->session()->invalidate();
|
||||||
$request->session()->regenerateToken();
|
$request->session()->regenerateToken();
|
||||||
|
|
||||||
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
|
return redirect()->route('admin.login')->with('success', 'Logged out successfully.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
// recalc totals and save to order
|
// recalc totals and save to order
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order); // <-- NEW
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item added and totals updated.');
|
return redirect()->back()->with('success', 'Item added and totals updated.');
|
||||||
}
|
}
|
||||||
@@ -164,6 +165,8 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
// recalc totals
|
// recalc totals
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
return redirect()->back()->with('success', 'Item deleted and totals updated.');
|
||||||
}
|
}
|
||||||
@@ -180,6 +183,8 @@ class AdminOrderController extends Controller
|
|||||||
|
|
||||||
// recalc totals
|
// recalc totals
|
||||||
$this->recalcTotals($order);
|
$this->recalcTotals($order);
|
||||||
|
$this->updateInvoiceFromOrder($order);
|
||||||
|
|
||||||
|
|
||||||
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
return redirect()->back()->with('success', 'Item restored and totals updated.');
|
||||||
}
|
}
|
||||||
@@ -383,10 +388,10 @@ class AdminOrderController extends Controller
|
|||||||
return view('admin.orders', compact('orders'));
|
return view('admin.orders', compact('orders'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// inside AdminOrderController
|
// inside AdminOrderController
|
||||||
|
|
||||||
private function buildOrdersQueryFromRequest(Request $request)
|
private function buildOrdersQueryFromRequest(Request $request)
|
||||||
{
|
{
|
||||||
$query = Order::with(['markList', 'invoice', 'shipments']);
|
$query = Order::with(['markList', 'invoice', 'shipments']);
|
||||||
|
|
||||||
// Search across order_id, markList.company_name, markList.customer_id, invoice.invoice_number
|
// Search across order_id, markList.company_name, markList.customer_id, invoice.invoice_number
|
||||||
@@ -422,10 +427,10 @@ private function buildOrdersQueryFromRequest(Request $request)
|
|||||||
$query->latest('id');
|
$query->latest('id');
|
||||||
|
|
||||||
return $query;
|
return $query;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadPdf(Request $request)
|
public function downloadPdf(Request $request)
|
||||||
{
|
{
|
||||||
// Build same filtered query used for table
|
// Build same filtered query used for table
|
||||||
$query = $this->buildOrdersQueryFromRequest($request);
|
$query = $this->buildOrdersQueryFromRequest($request);
|
||||||
|
|
||||||
@@ -446,16 +451,16 @@ public function downloadPdf(Request $request)
|
|||||||
. '-' . date('Y-m-d') . '.pdf';
|
. '-' . date('Y-m-d') . '.pdf';
|
||||||
|
|
||||||
return $pdf->download($fileName);
|
return $pdf->download($fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function downloadExcel(Request $request)
|
public function downloadExcel(Request $request)
|
||||||
{
|
{
|
||||||
// pass request to OrdersExport which will build Filtered query internally
|
// pass request to OrdersExport which will build Filtered query internally
|
||||||
return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx');
|
return Excel::download(new OrdersExport($request), 'orders-report-' . date('Y-m-d') . '.xlsx');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function addTempItem(Request $request)
|
public function addTempItem(Request $request)
|
||||||
{
|
{
|
||||||
// Validate item fields
|
// Validate item fields
|
||||||
$item = $request->validate([
|
$item = $request->validate([
|
||||||
@@ -509,7 +514,7 @@ public function addTempItem(Request $request)
|
|||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
||||||
public function finishOrder(Request $request)
|
public function finishOrder(Request $request)
|
||||||
{
|
{
|
||||||
$request->validate([
|
$request->validate([
|
||||||
'mark_no' => 'required',
|
'mark_no' => 'required',
|
||||||
'origin' => 'required',
|
'origin' => 'required',
|
||||||
@@ -663,13 +668,15 @@ public function addTempItem(Request $request)
|
|||||||
|
|
||||||
return redirect()->route('admin.orders.index')
|
return redirect()->route('admin.orders.index')
|
||||||
->with('success', 'Order + Invoice created successfully.');
|
->with('success', 'Order + Invoice created successfully.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// ORDER CRUD: update / destroy
|
// ORDER CRUD: update / destroy
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
public function updateItem(Request $request, $id)
|
public function updateItem(Request $request, $id)
|
||||||
{
|
{
|
||||||
$item = OrderItem::findOrFail($id);
|
$item = OrderItem::findOrFail($id);
|
||||||
|
$order = $item->order;
|
||||||
|
|
||||||
$item->update([
|
$item->update([
|
||||||
'description' => $request->description,
|
'description' => $request->description,
|
||||||
@@ -686,7 +693,49 @@ public function addTempItem(Request $request)
|
|||||||
'shop_no' => $request->shop_no,
|
'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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
179
app/Http/Controllers/Admin/AdminStaffController.php
Normal file
179
app/Http/Controllers/Admin/AdminStaffController.php
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Admin;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use App\Models\Admin;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
class AdminStaffController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$staff = Admin::where('type', 'staff')->orderBy('id', 'DESC')->get();
|
||||||
|
return view('admin.staff.index', compact('staff'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create()
|
||||||
|
{
|
||||||
|
$permissions = Permission::where('guard_name', 'admin')->get()->groupBy(function ($p) {
|
||||||
|
return explode('.', $p->name)[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
return view('admin.staff.create', compact('permissions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(Request $request)
|
||||||
|
{
|
||||||
|
$request->validate([
|
||||||
|
// Personal Info
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:admins,email',
|
||||||
|
'phone' => 'required|string|max:20',
|
||||||
|
'emergency_phone' => 'nullable|string|max:20',
|
||||||
|
'address' => 'nullable|string|max:255',
|
||||||
|
|
||||||
|
// Professional info
|
||||||
|
'role' => 'nullable|string|max:100',
|
||||||
|
'department' => 'nullable|string|max:100',
|
||||||
|
'designation' => 'nullable|string|max:100',
|
||||||
|
'joining_date' => 'nullable|date',
|
||||||
|
'status' => 'required|string|in:active,inactive',
|
||||||
|
'additional_info' => 'nullable|string',
|
||||||
|
|
||||||
|
// System access
|
||||||
|
'username' => 'nullable|string|unique:admins,username',
|
||||||
|
'password' => 'required|string|min:6',
|
||||||
|
|
||||||
|
// Permissions
|
||||||
|
'permissions' => 'nullable|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$admin = Admin::create([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'phone' => $request->phone,
|
||||||
|
'emergency_phone' => $request->emergency_phone,
|
||||||
|
'address' => $request->address,
|
||||||
|
|
||||||
|
'role' => $request->role,
|
||||||
|
'department' => $request->department,
|
||||||
|
'designation' => $request->designation,
|
||||||
|
'joining_date' => $request->joining_date,
|
||||||
|
'status' => $request->status,
|
||||||
|
'additional_info' => $request->additional_info,
|
||||||
|
|
||||||
|
'username' => $request->username,
|
||||||
|
'password' => Hash::make($request->password),
|
||||||
|
'type' => 'staff',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Generate EMPLOYEE ID using admin ID (safe)
|
||||||
|
$employeeId = 'EMP' . str_pad($admin->id, 4, '0', STR_PAD_LEFT);
|
||||||
|
$admin->update(['employee_id' => $employeeId]);
|
||||||
|
|
||||||
|
// Assign permissions (if any)
|
||||||
|
if ($request->permissions) {
|
||||||
|
$admin->givePermissionTo($request->permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->route('admin.staff.index')
|
||||||
|
->with('success', 'Staff created successfully.');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return back()->withErrors(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function edit($id)
|
||||||
|
{
|
||||||
|
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||||
|
|
||||||
|
$permissions = Permission::where('guard_name', 'admin')->get()->groupBy(function ($p) {
|
||||||
|
return explode('.', $p->name)[0];
|
||||||
|
});
|
||||||
|
|
||||||
|
$staffPermissions = $staff->permissions->pluck('name')->toArray();
|
||||||
|
|
||||||
|
return view('admin.staff.edit', compact('staff', 'permissions', 'staffPermissions'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, $id)
|
||||||
|
{
|
||||||
|
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'name' => 'required|string|max:255',
|
||||||
|
'email' => 'required|email|unique:admins,email,' . $staff->id,
|
||||||
|
'phone' => 'required|string|max:20',
|
||||||
|
'emergency_phone' => 'nullable|string|max:20',
|
||||||
|
'address' => 'nullable|string|max:255',
|
||||||
|
|
||||||
|
'role' => 'nullable|string|max:100',
|
||||||
|
'department' => 'nullable|string|max:100',
|
||||||
|
'designation' => 'nullable|string|max:100',
|
||||||
|
'joining_date' => 'nullable|date',
|
||||||
|
'status' => 'required|string|in:active,inactive',
|
||||||
|
'additional_info' => 'nullable|string',
|
||||||
|
|
||||||
|
'username' => 'nullable|string|unique:admins,username,' . $staff->id,
|
||||||
|
'password' => 'nullable|string|min:6',
|
||||||
|
|
||||||
|
'permissions' => 'nullable|array',
|
||||||
|
]);
|
||||||
|
|
||||||
|
DB::beginTransaction();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$staff->update([
|
||||||
|
'name' => $request->name,
|
||||||
|
'email' => $request->email,
|
||||||
|
'phone' => $request->phone,
|
||||||
|
'emergency_phone' => $request->emergency_phone,
|
||||||
|
'address' => $request->address,
|
||||||
|
|
||||||
|
'role' => $request->role,
|
||||||
|
'department' => $request->department,
|
||||||
|
'designation' => $request->designation,
|
||||||
|
'joining_date' => $request->joining_date,
|
||||||
|
'status' => $request->status,
|
||||||
|
'additional_info' => $request->additional_info,
|
||||||
|
|
||||||
|
'username' => $request->username,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($request->password) {
|
||||||
|
$staff->update(['password' => Hash::make($request->password)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$staff->syncPermissions($request->permissions ?? []);
|
||||||
|
|
||||||
|
DB::commit();
|
||||||
|
|
||||||
|
return redirect()->route('admin.staff.index')
|
||||||
|
->with('success', 'Staff updated successfully.');
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DB::rollBack();
|
||||||
|
return back()->withErrors(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function destroy($id)
|
||||||
|
{
|
||||||
|
$staff = Admin::where('type', 'staff')->findOrFail($id);
|
||||||
|
$staff->delete();
|
||||||
|
|
||||||
|
return redirect()->route('admin.staff.index')
|
||||||
|
->with('success', 'Staff removed successfully.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,22 +1,43 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
// app/Models/Admin.php
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
class Admin extends Authenticatable
|
class Admin extends Authenticatable
|
||||||
{
|
{
|
||||||
use Notifiable;
|
use HasFactory, Notifiable, HasRoles;
|
||||||
|
|
||||||
protected $guard = 'admin';
|
protected $guard_name = 'admin';
|
||||||
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'name', 'email', 'password', 'role',
|
'name', 'email', 'password', 'username',
|
||||||
|
'phone', 'emergency_phone', 'address',
|
||||||
|
'role', 'department', 'designation', 'joining_date',
|
||||||
|
'status', 'additional_info', 'type', // admin/staff indicator
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password', 'remember_token',
|
'password', 'remember_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public function setPasswordAttribute($value)
|
||||||
|
{
|
||||||
|
if (!$value) return;
|
||||||
|
|
||||||
|
if (Hash::needsRehash($value)) {
|
||||||
|
$this->attributes['password'] = Hash::make($value);
|
||||||
|
} else {
|
||||||
|
$this->attributes['password'] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDisplayNameAttribute()
|
||||||
|
{
|
||||||
|
return "{$this->name}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
81
app/Models/Staff.php
Normal file
81
app/Models/Staff.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
|
||||||
|
class Staff extends Authenticatable
|
||||||
|
{
|
||||||
|
use Notifiable, HasRoles, SoftDeletes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The guard name used by Spatie.
|
||||||
|
* Make sure this matches the guard you'll use for admin/staff auth (usually 'web' or 'admin').
|
||||||
|
*/
|
||||||
|
protected $guard_name = 'admin';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attributes that are mass assignable.
|
||||||
|
*/
|
||||||
|
protected $fillable = [
|
||||||
|
'employee_id',
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
'phone',
|
||||||
|
'emergency_phone',
|
||||||
|
'address',
|
||||||
|
'role', // business role/title (not Spatie role)
|
||||||
|
'department',
|
||||||
|
'designation',
|
||||||
|
'joining_date',
|
||||||
|
'status',
|
||||||
|
'additional_info',
|
||||||
|
'username',
|
||||||
|
'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hidden attributes (not returned in arrays / JSON).
|
||||||
|
*/
|
||||||
|
protected $hidden = [
|
||||||
|
'password',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Casts
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'joining_date' => 'date',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutator: automatically hash password when set.
|
||||||
|
* Accepts plain text and hashes it with bcrypt.
|
||||||
|
*/
|
||||||
|
public function setPasswordAttribute($value)
|
||||||
|
{
|
||||||
|
if (empty($value)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If already hashed (starts with $2y$), don't double-hash
|
||||||
|
if (Hash::needsRehash($value)) {
|
||||||
|
$this->attributes['password'] = Hash::make($value);
|
||||||
|
} else {
|
||||||
|
$this->attributes['password'] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional helper to get display name (useful in views/logs).
|
||||||
|
*/
|
||||||
|
public function getDisplayNameAttribute()
|
||||||
|
{
|
||||||
|
return $this->name . ' (' . $this->employee_id . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
40
app/Providers/AuthServiceProvider.php
Normal file
40
app/Providers/AuthServiceProvider.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
|
|
||||||
|
class AuthServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The policy mappings for the application.
|
||||||
|
*
|
||||||
|
* @var array<class-string, class-string>
|
||||||
|
*/
|
||||||
|
protected $policies = [
|
||||||
|
// 'App\Models\Model' => 'App\Policies\ModelPolicy',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register any authentication / authorization services.
|
||||||
|
*/
|
||||||
|
public function boot()
|
||||||
|
{
|
||||||
|
$this->registerPolicies();
|
||||||
|
|
||||||
|
// SUPER ADMIN bypass
|
||||||
|
Gate::before(function ($user, $ability) {
|
||||||
|
if ($user->hasRole('super-admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ADMIN bypass
|
||||||
|
Gate::before(function ($user, $ability) {
|
||||||
|
if ($user->hasRole('admin')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,4 +3,5 @@
|
|||||||
return [
|
return [
|
||||||
App\Providers\AppServiceProvider::class,
|
App\Providers\AppServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
App\Providers\AuthServiceProvider::class,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
"maatwebsite/excel": "^1.1",
|
"maatwebsite/excel": "^1.1",
|
||||||
"mpdf/mpdf": "^8.2",
|
"mpdf/mpdf": "^8.2",
|
||||||
"php-open-source-saver/jwt-auth": "2.8"
|
"php-open-source-saver/jwt-auth": "2.8",
|
||||||
|
"spatie/laravel-permission": "^6.23"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
|||||||
85
composer.lock
generated
85
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "6a2ec43d7e96d38cacfc2f9e1ae5cb9e",
|
"content-hash": "0ed807863dbf93f06565547ce4303f47",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "barryvdh/laravel-dompdf",
|
"name": "barryvdh/laravel-dompdf",
|
||||||
@@ -4364,6 +4364,89 @@
|
|||||||
],
|
],
|
||||||
"time": "2025-08-05T09:57:14+00:00"
|
"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",
|
"name": "symfony/clock",
|
||||||
"version": "v7.4.0",
|
"version": "v7.4.0",
|
||||||
|
|||||||
@@ -85,6 +85,11 @@ return [
|
|||||||
// 'driver' => 'database',
|
// 'driver' => 'database',
|
||||||
// 'table' => 'users',
|
// 'table' => 'users',
|
||||||
// ],
|
// ],
|
||||||
|
'staff' => [
|
||||||
|
'driver' => 'eloquent',
|
||||||
|
'model' => App\Models\Staff::class,
|
||||||
|
],
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
202
config/permission.php
Normal file
202
config/permission.php
Normal file
@@ -0,0 +1,202 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'models' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* Eloquent model should be used to retrieve your permissions. Of course, it
|
||||||
|
* is often just the "Permission" model but you may use whatever you like.
|
||||||
|
*
|
||||||
|
* The model you want to use as a Permission model needs to implement the
|
||||||
|
* `Spatie\Permission\Contracts\Permission` contract.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'permission' => Spatie\Permission\Models\Permission::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* Eloquent model should be used to retrieve your roles. Of course, it
|
||||||
|
* is often just the "Role" model but you may use whatever you like.
|
||||||
|
*
|
||||||
|
* The model you want to use as a Role model needs to implement the
|
||||||
|
* `Spatie\Permission\Contracts\Role` contract.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'role' => Spatie\Permission\Models\Role::class,
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
'table_names' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your roles. We have chosen a basic
|
||||||
|
* default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'roles' => 'roles',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your permissions. We have chosen a basic
|
||||||
|
* default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'permissions' => 'permissions',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasPermissions" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your models permissions. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_has_permissions' => 'model_has_permissions',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your models roles. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_has_roles' => 'model_has_roles',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When using the "HasRoles" trait from this package, we need to know which
|
||||||
|
* table should be used to retrieve your roles permissions. We have chosen a
|
||||||
|
* basic default value but you may easily change it to any table you like.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'role_has_permissions' => 'role_has_permissions',
|
||||||
|
],
|
||||||
|
|
||||||
|
'column_names' => [
|
||||||
|
/*
|
||||||
|
* Change this if you want to name the related pivots other than defaults
|
||||||
|
*/
|
||||||
|
'role_pivot_key' => null, // default 'role_id',
|
||||||
|
'permission_pivot_key' => null, // default 'permission_id',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change this if you want to name the related model primary key other than
|
||||||
|
* `model_id`.
|
||||||
|
*
|
||||||
|
* For example, this would be nice if your primary keys are all UUIDs. In
|
||||||
|
* that case, name this `model_uuid`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'model_morph_key' => 'model_id',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Change this if you want to use the teams feature and your related model's
|
||||||
|
* foreign key is other than `team_id`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'team_foreign_key' => 'team_id',
|
||||||
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the method for checking permissions will be registered on the gate.
|
||||||
|
* Set this to false if you want to implement custom logic for checking permissions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'register_permission_check_method' => true,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, Laravel\Octane\Events\OperationTerminated event listener will be registered
|
||||||
|
* this will refresh permissions on every TickTerminated, TaskTerminated and RequestTerminated
|
||||||
|
* NOTE: This should not be needed in most cases, but an Octane/Vapor combination benefited from it.
|
||||||
|
*/
|
||||||
|
'register_octane_reset_listener' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Events will fire when a role or permission is assigned/unassigned:
|
||||||
|
* \Spatie\Permission\Events\RoleAttached
|
||||||
|
* \Spatie\Permission\Events\RoleDetached
|
||||||
|
* \Spatie\Permission\Events\PermissionAttached
|
||||||
|
* \Spatie\Permission\Events\PermissionDetached
|
||||||
|
*
|
||||||
|
* To enable, set to true, and then create listeners to watch these events.
|
||||||
|
*/
|
||||||
|
'events_enabled' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Teams Feature.
|
||||||
|
* When set to true the package implements teams using the 'team_foreign_key'.
|
||||||
|
* If you want the migrations to register the 'team_foreign_key', you must
|
||||||
|
* set this to true before doing the migration.
|
||||||
|
* If you already did the migration then you must make a new migration to also
|
||||||
|
* add 'team_foreign_key' to 'roles', 'model_has_roles', and 'model_has_permissions'
|
||||||
|
* (view the latest version of this package's migration file)
|
||||||
|
*/
|
||||||
|
|
||||||
|
'teams' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class to use to resolve the permissions team id
|
||||||
|
*/
|
||||||
|
'team_resolver' => \Spatie\Permission\DefaultTeamResolver::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Passport Client Credentials Grant
|
||||||
|
* When set to true the package will use Passports Client to check permissions
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use_passport_client_credentials' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the required permission names are added to exception messages.
|
||||||
|
* This could be considered an information leak in some contexts, so the default
|
||||||
|
* setting is false here for optimum safety.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'display_permission_in_exception' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When set to true, the required role names are added to exception messages.
|
||||||
|
* This could be considered an information leak in some contexts, so the default
|
||||||
|
* setting is false here for optimum safety.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'display_role_in_exception' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default wildcard permission lookups are disabled.
|
||||||
|
* See documentation to understand supported syntax.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'enable_wildcard_permission' => false,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The class to use for interpreting wildcard permissions.
|
||||||
|
* If you need to modify delimiters, override the class and specify its name here.
|
||||||
|
*/
|
||||||
|
// 'wildcard_permission' => Spatie\Permission\WildcardPermission::class,
|
||||||
|
|
||||||
|
/* Cache-specific settings */
|
||||||
|
|
||||||
|
'cache' => [
|
||||||
|
|
||||||
|
/*
|
||||||
|
* By default all permissions are cached for 24 hours to speed up performance.
|
||||||
|
* When permissions or roles are updated the cache is flushed automatically.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'expiration_time' => \DateInterval::createFromDateString('24 hours'),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The cache key used to store all permissions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'key' => 'spatie.permission.cache',
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You may optionally indicate a specific cache driver to use for permission and
|
||||||
|
* role caching using any of the `store` drivers listed in the cache.php config
|
||||||
|
* file. Using 'default' here means to use the `default` set in cache.php.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'store' => 'default',
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
$teams = config('permission.teams');
|
||||||
|
$tableNames = config('permission.table_names');
|
||||||
|
$columnNames = config('permission.column_names');
|
||||||
|
$pivotRole = $columnNames['role_pivot_key'] ?? 'role_id';
|
||||||
|
$pivotPermission = $columnNames['permission_pivot_key'] ?? 'permission_id';
|
||||||
|
|
||||||
|
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||||
|
throw_if($teams && empty($columnNames['team_foreign_key'] ?? null), Exception::class, 'Error: team_foreign_key on config/permission.php not loaded. Run [php artisan config:clear] and try again.');
|
||||||
|
|
||||||
|
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
|
||||||
|
// $table->engine('InnoDB');
|
||||||
|
$table->bigIncrements('id'); // permission id
|
||||||
|
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||||
|
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->unique(['name', 'guard_name']);
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
|
||||||
|
// $table->engine('InnoDB');
|
||||||
|
$table->bigIncrements('id'); // role id
|
||||||
|
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
|
||||||
|
}
|
||||||
|
$table->string('name'); // For MyISAM use string('name', 225); // (or 166 for InnoDB with Redundant/Compact row format)
|
||||||
|
$table->string('guard_name'); // For MyISAM use string('guard_name', 25);
|
||||||
|
$table->timestamps();
|
||||||
|
if ($teams || config('permission.testing')) {
|
||||||
|
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
|
||||||
|
} else {
|
||||||
|
$table->unique(['name', 'guard_name']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
|
||||||
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
|
||||||
|
$table->string('model_type');
|
||||||
|
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||||
|
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
|
||||||
|
|
||||||
|
$table->foreign($pivotPermission)
|
||||||
|
->references('id') // permission id
|
||||||
|
->on($tableNames['permissions'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
if ($teams) {
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
|
||||||
|
|
||||||
|
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_permissions_permission_model_type_primary');
|
||||||
|
} else {
|
||||||
|
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_permissions_permission_model_type_primary');
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
|
||||||
|
$table->unsignedBigInteger($pivotRole);
|
||||||
|
|
||||||
|
$table->string('model_type');
|
||||||
|
$table->unsignedBigInteger($columnNames['model_morph_key']);
|
||||||
|
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
|
||||||
|
|
||||||
|
$table->foreign($pivotRole)
|
||||||
|
->references('id') // role id
|
||||||
|
->on($tableNames['roles'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
if ($teams) {
|
||||||
|
$table->unsignedBigInteger($columnNames['team_foreign_key']);
|
||||||
|
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
|
||||||
|
|
||||||
|
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_roles_role_model_type_primary');
|
||||||
|
} else {
|
||||||
|
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
|
||||||
|
'model_has_roles_role_model_type_primary');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
|
||||||
|
$table->unsignedBigInteger($pivotPermission);
|
||||||
|
$table->unsignedBigInteger($pivotRole);
|
||||||
|
|
||||||
|
$table->foreign($pivotPermission)
|
||||||
|
->references('id') // permission id
|
||||||
|
->on($tableNames['permissions'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
|
||||||
|
$table->foreign($pivotRole)
|
||||||
|
->references('id') // role id
|
||||||
|
->on($tableNames['roles'])
|
||||||
|
->onDelete('cascade');
|
||||||
|
|
||||||
|
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
|
||||||
|
});
|
||||||
|
|
||||||
|
app('cache')
|
||||||
|
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
|
||||||
|
->forget(config('permission.cache.key'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
$tableNames = config('permission.table_names');
|
||||||
|
|
||||||
|
throw_if(empty($tableNames), Exception::class, 'Error: config/permission.php not found and defaults could not be merged. Please publish the package configuration before proceeding, or drop the tables manually.');
|
||||||
|
|
||||||
|
Schema::drop($tableNames['role_has_permissions']);
|
||||||
|
Schema::drop($tableNames['model_has_roles']);
|
||||||
|
Schema::drop($tableNames['model_has_permissions']);
|
||||||
|
Schema::drop($tableNames['roles']);
|
||||||
|
Schema::drop($tableNames['permissions']);
|
||||||
|
}
|
||||||
|
};
|
||||||
50
database/migrations/2025_12_04_071300_create_staff_table.php
Normal file
50
database/migrations/2025_12_04_071300_create_staff_table.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::create('staff', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
// Personal Information
|
||||||
|
$table->string('employee_id')->unique();
|
||||||
|
$table->string('name');
|
||||||
|
$table->string('email')->unique();
|
||||||
|
$table->string('phone');
|
||||||
|
$table->string('emergency_phone')->nullable();
|
||||||
|
$table->text('address')->nullable();
|
||||||
|
|
||||||
|
// Professional Information
|
||||||
|
$table->string('role')->nullable(); // Job title
|
||||||
|
$table->string('department')->nullable();
|
||||||
|
$table->string('designation')->nullable();
|
||||||
|
$table->date('joining_date')->nullable();
|
||||||
|
$table->string('status')->default('active'); // active/inactive
|
||||||
|
$table->text('additional_info')->nullable();
|
||||||
|
|
||||||
|
// System Access
|
||||||
|
$table->string('username')->unique();
|
||||||
|
$table->string('password');
|
||||||
|
|
||||||
|
$table->softDeletes();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('staff');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('admins', function (Blueprint $table) {
|
||||||
|
$table->string('employee_id')->unique()->nullable();
|
||||||
|
$table->string('phone')->nullable();
|
||||||
|
$table->string('emergency_phone')->nullable();
|
||||||
|
$table->text('address')->nullable();
|
||||||
|
|
||||||
|
$table->string('department')->nullable();
|
||||||
|
$table->string('designation')->nullable();
|
||||||
|
$table->date('joining_date')->nullable();
|
||||||
|
$table->enum('status', ['active','inactive'])->default('active');
|
||||||
|
$table->text('additional_info')->nullable();
|
||||||
|
|
||||||
|
$table->string('username')->unique()->nullable();
|
||||||
|
$table->enum('type', ['admin','staff'])->default('staff');
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('admins', function (Blueprint $table) {
|
||||||
|
//
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration {
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('admins', function (Blueprint $table) {
|
||||||
|
$table->string('role')->nullable()->change(); // <-- Fix problem
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('admins', function (Blueprint $table) {
|
||||||
|
$table->enum('role', ['admin', 'super-admin'])->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
103
database/seeders/PermissionSeeder.php
Normal file
103
database/seeders/PermissionSeeder.php
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
|
use Spatie\Permission\Models\Role;
|
||||||
|
|
||||||
|
class PermissionSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// FINAL PERMISSION LIST (YOUR DATA)
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
$permissions = [
|
||||||
|
|
||||||
|
// ORDER
|
||||||
|
'order.view',
|
||||||
|
'order.create',
|
||||||
|
'order.edit',
|
||||||
|
'order.delete',
|
||||||
|
|
||||||
|
// EXTRA (ORDERS)
|
||||||
|
'orders.view', // you added this separately
|
||||||
|
|
||||||
|
// SHIPMENT
|
||||||
|
'shipment.view',
|
||||||
|
'shipment.create',
|
||||||
|
'shipment.delete',
|
||||||
|
|
||||||
|
// INVOICE
|
||||||
|
'invoice.view',
|
||||||
|
'invoice.edit',
|
||||||
|
'invoice.add_installment',
|
||||||
|
|
||||||
|
// CUSTOMER
|
||||||
|
'customer.view',
|
||||||
|
'customer.create',
|
||||||
|
|
||||||
|
// REQUEST
|
||||||
|
'request.view',
|
||||||
|
'request.update_profile',
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// @can('')
|
||||||
|
// @endcan
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ACCOUNT
|
||||||
|
'account.view',
|
||||||
|
'account.create_order',
|
||||||
|
'account.edit_order',
|
||||||
|
'account.delete_order',
|
||||||
|
'account.toggle_payment_status',
|
||||||
|
'account.add_installment',
|
||||||
|
'account.view_installments',
|
||||||
|
|
||||||
|
// REPORT
|
||||||
|
'report.view',
|
||||||
|
|
||||||
|
// MARK LIST
|
||||||
|
'mark_list.view',
|
||||||
|
];
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// CREATE PERMISSIONS
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
foreach ($permissions as $permission) {
|
||||||
|
Permission::firstOrCreate(
|
||||||
|
['name' => $permission, 'guard_name' => 'admin']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// ROLES
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
// Create super-admin role
|
||||||
|
$superAdminRole = Role::firstOrCreate(
|
||||||
|
['name' => 'super-admin', 'guard_name' => 'admin']
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create admin role
|
||||||
|
$adminRole = Role::firstOrCreate(
|
||||||
|
['name' => 'admin', 'guard_name' => 'admin']
|
||||||
|
);
|
||||||
|
|
||||||
|
// ------------------------------------------------------
|
||||||
|
// ASSIGN ALL PERMISSIONS TO BOTH ROLES
|
||||||
|
// ------------------------------------------------------
|
||||||
|
|
||||||
|
$allPermissions = Permission::where('guard_name', 'admin')->get();
|
||||||
|
|
||||||
|
$superAdminRole->syncPermissions($allPermissions);
|
||||||
|
$adminRole->syncPermissions($allPermissions);
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@@ -1040,7 +1040,9 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Create Installment Button -->
|
<!-- Create Installment Button -->
|
||||||
<button class="btn" id="openCreateModalBtn">+ Create New Installment</button>
|
@can('account.create_order')
|
||||||
|
<button class="btn" id="openCreateModalBtn">+ Create New Order</button>
|
||||||
|
@endcan
|
||||||
|
|
||||||
<!-- Date Filters -->
|
<!-- Date Filters -->
|
||||||
<div class="combined-filters-row">
|
<div class="combined-filters-row">
|
||||||
@@ -1250,7 +1252,7 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
|
|
||||||
<div class="create-actions">
|
<div class="create-actions">
|
||||||
<button type="button" class="btn ghost" id="cancelCreateModal">Cancel</button>
|
<button type="button" class="btn ghost" id="cancelCreateModal">Cancel</button>
|
||||||
<button type="submit" class="btn">Create Installment</button>
|
<button type="submit" class="btn">Create Order</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -1304,7 +1306,9 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
|
|
||||||
<div style="display:flex; justify-content: flex-end; gap:12px; margin-top:16px;">
|
<div style="display:flex; justify-content: flex-end; gap:12px; margin-top:16px;">
|
||||||
<button type="button" class="btn ghost" onclick="closeEntryDetailsModal()">Close</button>
|
<button type="button" class="btn ghost" onclick="closeEntryDetailsModal()">Close</button>
|
||||||
|
@can('account.add_installment')
|
||||||
<button type="button" class="btn" id="addInstallmentFromDetails">+ Add New Installment</button>
|
<button type="button" class="btn" id="addInstallmentFromDetails">+ Add New Installment</button>
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1388,7 +1392,9 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
|
|
||||||
<div style="display:flex; justify-content:flex-end; gap:12px; margin-top:14px;">
|
<div style="display:flex; justify-content:flex-end; gap:12px; margin-top:14px;">
|
||||||
<button type="button" class="btn ghost" onclick="closeInstallmentModal()">Cancel</button>
|
<button type="button" class="btn ghost" onclick="closeInstallmentModal()">Cancel</button>
|
||||||
<button type="submit" class="btn">Create Installment</button>
|
@can('account.add_installment')
|
||||||
|
<button type="submit" class="btn">Create Installment2</button>
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -1559,6 +1565,14 @@ tr:hover td{ background:#fbfdff; }
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
window.CAN_EDIT_ORDER = @json(auth()->user()->can('account.edit_order'));
|
||||||
|
window.CAN_DELETE_ORDER = @json(auth()->user()->can('account.delete_order'));
|
||||||
|
window.CAN_TOGGLE_PAYMENT = @json(auth()->user()->can('account.toggle_payment_status'));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
/* ---------- Helpers & state ---------- */
|
/* ---------- Helpers & state ---------- */
|
||||||
|
|
||||||
@@ -1642,7 +1656,12 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
/* ---------- UI binding ---------- */
|
/* ---------- UI binding ---------- */
|
||||||
function bindUI(){
|
function bindUI(){
|
||||||
// Create Order Modal
|
// Create Order Modal
|
||||||
document.getElementById('openCreateModalBtn').addEventListener('click', openCreateOrderModal);
|
//document.getElementById('openCreateModalBtn').addEventListener('click', openCreateOrderModal);
|
||||||
|
const createBtn = document.getElementById('openCreateModalBtn');
|
||||||
|
if (createBtn) {
|
||||||
|
createBtn.addEventListener('click', openCreateOrderModal);
|
||||||
|
}
|
||||||
|
|
||||||
document.getElementById('closeCreateModal').addEventListener('click', closeCreateOrderModal);
|
document.getElementById('closeCreateModal').addEventListener('click', closeCreateOrderModal);
|
||||||
document.getElementById('cancelCreateModal').addEventListener('click', closeCreateOrderModal);
|
document.getElementById('cancelCreateModal').addEventListener('click', closeCreateOrderModal);
|
||||||
document.getElementById('createOrderInlineForm').addEventListener('submit', submitCreateOrderInline);
|
document.getElementById('createOrderInlineForm').addEventListener('submit', submitCreateOrderInline);
|
||||||
@@ -1667,11 +1686,22 @@ function bindUI(){
|
|||||||
clearStatusFilters();
|
clearStatusFilters();
|
||||||
});
|
});
|
||||||
document.getElementById('searchBtn').addEventListener('click', handleSearch);
|
document.getElementById('searchBtn').addEventListener('click', handleSearch);
|
||||||
document.getElementById('addInstallmentFromDetails').addEventListener('click', () => {
|
|
||||||
if(!currentEntry) return;
|
const addInstallBtn = document.getElementById('addInstallmentFromDetails');
|
||||||
openInstallmentModal(currentEntry.entry_no, currentEntry.description, currentEntry.region, currentEntry.pending_amount);
|
|
||||||
|
if (addInstallBtn) {
|
||||||
|
addInstallBtn.addEventListener('click', () => {
|
||||||
|
if (!currentEntry) return;
|
||||||
|
openInstallmentModal(
|
||||||
|
currentEntry.entry_no,
|
||||||
|
currentEntry.description,
|
||||||
|
currentEntry.region,
|
||||||
|
currentEntry.pending_amount
|
||||||
|
);
|
||||||
closeEntryDetailsModal();
|
closeEntryDetailsModal();
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Installment form submit
|
// Installment form submit
|
||||||
document.getElementById('installmentForm').addEventListener('submit', submitInstallment);
|
document.getElementById('installmentForm').addEventListener('submit', submitInstallment);
|
||||||
@@ -2129,94 +2159,99 @@ function renderPaymentTable(list){
|
|||||||
const paginatedEntries = list.slice(startIndex, endIndex);
|
const paginatedEntries = list.slice(startIndex, endIndex);
|
||||||
|
|
||||||
paginatedEntries.forEach(entry => {
|
paginatedEntries.forEach(entry => {
|
||||||
|
|
||||||
const tr = document.createElement('tr');
|
const tr = document.createElement('tr');
|
||||||
|
|
||||||
// फक्त unpaid/pending वरच actions दिसतील
|
const canActions = ['unpaid','pending'].includes(entry.payment_status.toLowerCase());
|
||||||
const canActions = ['unpaid', 'pending'].includes(
|
|
||||||
String(entry.payment_status).toLowerCase()
|
// permissions passed from Blade
|
||||||
);
|
const canEdit = window.CAN_EDIT_ORDER;
|
||||||
|
const canDelete = window.CAN_DELETE_ORDER;
|
||||||
|
const canToggle = window.CAN_TOGGLE_PAYMENT;
|
||||||
|
|
||||||
tr.innerHTML = `
|
tr.innerHTML = `
|
||||||
<td>${escapeHtml(entry.entry_no)}</td>
|
<td>${escapeHtml(entry.entry_no)}</td>
|
||||||
<td>${escapeHtml(entry.entry_date)}</td>
|
<td>${escapeHtml(entry.entry_date)}</td>
|
||||||
<td>${escapeHtml(entry.description)}</td>
|
<td>${escapeHtml(entry.description)}</td>
|
||||||
|
|
||||||
<!-- CLICKABLE ORDER QUANTITY -->
|
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button type="button" class="entry-link"
|
||||||
type="button"
|
onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')">
|
||||||
class="entry-link"
|
|
||||||
onclick="openEntryOrdersModal('${escapeHtml(entry.entry_no)}')"
|
|
||||||
>
|
|
||||||
${entry.order_quantity ?? '-'}
|
${entry.order_quantity ?? '-'}
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>${escapeHtml(entry.region)}</td>
|
<td>${escapeHtml(entry.region)}</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<button class="toggle-switch-btn"
|
<button class="toggle-switch-btn"
|
||||||
data-entry="${escapeHtml(entry.entry_no)}"
|
data-entry="${entry.entry_no}"
|
||||||
data-pos="${Number(entry.toggle_pos) || 0}"
|
data-pos="${entry.toggle_pos ?? 0}"
|
||||||
aria-label="Toggle payment state"></button>
|
${!canToggle ? 'disabled class="toggle-switch-btn disabled-toggle"' : ''}
|
||||||
|
></button>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td>${formatCurrency(entry.amount)}</td>
|
<td>${formatCurrency(entry.amount)}</td>
|
||||||
|
|
||||||
<td>
|
<td>
|
||||||
<span class="status-badge ${statusClass(entry.payment_status)}">
|
<span class="status-badge ${statusClass(entry.payment_status)}">
|
||||||
${capitalize(entry.payment_status)}
|
${capitalize(entry.payment_status)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<!-- इथे तुझा action-btns block paste कर -->
|
|
||||||
<td>
|
<td>
|
||||||
<div class="action-btns">
|
<div class="action-btns">
|
||||||
${canActions ? `
|
|
||||||
<button class="action-btn edit-btn" data-entry="${escapeHtml(entry.entry_no)}" title="Edit entry">
|
${(canActions && canEdit) ? `
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<button class="action-btn edit-btn"
|
||||||
<path d="M11.3333 1.99996C11.5084 1.82485 11.7163 1.686 11.945 1.59124C12.1737 1.49649 12.4189 1.44775 12.6667 1.44775C12.9144 1.44775 13.1596 1.49649 13.3883 1.59124C13.617 1.686 13.8249 1.82485 14 1.99996C14.1751 2.17507 14.314 2.38297 14.4087 2.61167C14.5035 2.84037 14.5522 3.08556 14.5522 3.33329C14.5522 3.58102 14.5035 3.82621 14.4087 4.05491C14.314 4.28361 14.1751 4.49151 14 4.66663L4.99998 13.6666L1.33331 14.6666L2.33331 11L11.3333 1.99996Z"
|
data-entry="${entry.entry_no}"
|
||||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
title="Edit entry">
|
||||||
</svg>
|
✎
|
||||||
</button>
|
|
||||||
<button class="action-btn delete-btn" data-entry="${escapeHtml(entry.entry_no)}" title="Delete entry">
|
|
||||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path d="M2 4H3.33333H14" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
<path d="M5.33331 4V2.66667C5.33331 2.31305 5.47379 1.97391 5.72384 1.72386C5.97389 1.47381 6.31303 1.33333 6.66665 1.33333H9.33331C9.68693 1.33333 10.0261 1.47381 10.2761 1.72386C10.5262 1.97391 10.6666 2.31305 10.6666 2.66667V4M12.6666 4V13.3333C12.6666 13.6869 12.5262 14.0261 12.2761 14.2761C12.0261 14.5262 11.6869 14.6667 11.3333 14.6667H4.66665C4.31303 14.6667 3.97389 14.5262 3.72384 14.2761C3.47379 14.0261 3.33331 13.6869 3.33331 13.3333V4H12.6666Z"
|
|
||||||
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
</button>
|
||||||
` : ''}
|
` : ''}
|
||||||
|
|
||||||
|
${(canActions && canDelete) ? `
|
||||||
|
<button class="action-btn delete-btn"
|
||||||
|
data-entry="${entry.entry_no}"
|
||||||
|
title="Delete entry">
|
||||||
|
🗑
|
||||||
|
</button>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
/* Toggle Button Logic */
|
||||||
|
const toggleBtn = tr.querySelector('.toggle-switch-btn');
|
||||||
|
setToggleVisual(toggleBtn, Number(toggleBtn.dataset.pos));
|
||||||
|
|
||||||
|
if (canToggle) {
|
||||||
|
toggleBtn.addEventListener("click", () => cycleToggle(toggleBtn));
|
||||||
|
} else {
|
||||||
|
toggleBtn.style.opacity = "0.5";
|
||||||
|
toggleBtn.style.cursor = "not-allowed";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* EDIT binding */
|
||||||
|
if (canActions && canEdit) {
|
||||||
|
const editBtn = tr.querySelector(".edit-btn");
|
||||||
|
editBtn?.addEventListener("click", () => openEditModal(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* DELETE binding */
|
||||||
|
if (canActions && canDelete) {
|
||||||
|
const delBtn = tr.querySelector(".delete-btn");
|
||||||
|
delBtn?.addEventListener("click", () => deleteEntry(entry.entry_no));
|
||||||
|
}
|
||||||
|
|
||||||
body.appendChild(tr);
|
body.appendChild(tr);
|
||||||
const btn = tr.querySelector('.toggle-switch-btn');
|
});
|
||||||
btn.dataset.entry = entry.entry_no; // entry_no from API
|
|
||||||
btn.dataset.pos = entry.toggle_pos ?? 0;
|
|
||||||
setToggleVisual(btn, Number(btn.dataset.pos));
|
|
||||||
btn.addEventListener('click', () => cycleToggle(btn));
|
|
||||||
|
|
||||||
|
|
||||||
const actions = tr.querySelector('.action-btns');
|
|
||||||
if (actions) {
|
|
||||||
if (entry.payment_status.toLowerCase() === 'paid') {
|
|
||||||
actions.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
actions.style.display = 'flex';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (canActions) {
|
|
||||||
const editBtn = tr.querySelector('.edit-btn');
|
|
||||||
editBtn.addEventListener('click', () => openEditModal(entry));
|
|
||||||
|
|
||||||
const deleteBtn = tr.querySelector('.delete-btn');
|
|
||||||
deleteBtn.addEventListener('click', () => deleteEntry(entry.entry_no));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function cycleToggle(btn) {
|
function cycleToggle(btn) {
|
||||||
// वर्तमान position घेऊन पुढचा स्टेट कॅल्क्युलेट करा
|
// वर्तमान position घेऊन पुढचा स्टेट कॅल्क्युलेट करा
|
||||||
let pos = parseInt(btn.dataset.pos || '0', 10); // 0 = unpaid, 1 = pending, 2 = paid
|
let pos = parseInt(btn.dataset.pos || '0', 10); // 0 = unpaid, 1 = pending, 2 = paid
|
||||||
@@ -2861,7 +2896,7 @@ async function openEntryDetailsModal(entryNo) {
|
|||||||
style="color: #0c6b2e; font-weight: 500; padding: 10px;">
|
style="color: #0c6b2e; font-weight: 500; padding: 10px;">
|
||||||
✅ Delivered
|
✅ Delivered
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -809,9 +809,11 @@
|
|||||||
All
|
All
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
@can('customer.create')
|
||||||
<a href="{{ route('admin.customers.add') }}" class="add-customer-btn">
|
<a href="{{ route('admin.customers.add') }}" class="add-customer-btn">
|
||||||
<i class="bi bi-plus-circle me-1"></i>Add Customer
|
<i class="bi bi-plus-circle me-1"></i>Add Customer
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1131,9 +1131,12 @@ body, .container-fluid {
|
|||||||
<div class="order-mgmt-box">
|
<div class="order-mgmt-box">
|
||||||
<div class="order-mgmt-bar">
|
<div class="order-mgmt-bar">
|
||||||
<span class="order-mgmt-title"><i class="bi bi-table"></i> Order Management</span>
|
<span class="order-mgmt-title"><i class="bi bi-table"></i> Order Management</span>
|
||||||
|
@can('order.create')
|
||||||
<button class="create-order-btn" id="openCreateOrderModal">
|
<button class="create-order-btn" id="openCreateOrderModal">
|
||||||
<i class="bi bi-plus-circle"></i> Create Order
|
<i class="bi bi-plus-circle"></i> Create Order
|
||||||
</button>
|
</button>
|
||||||
|
@endcan
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="order-mgmt-main">
|
<div class="order-mgmt-main">
|
||||||
|
|||||||
@@ -452,9 +452,11 @@ body {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-end mt-3">
|
<div class="text-end mt-3">
|
||||||
|
@can('invoice.edit')
|
||||||
<button type="submit" class="btn-success-compact btn-compact">
|
<button type="submit" class="btn-success-compact btn-compact">
|
||||||
<i class="fas fa-save me-2"></i>Update Invoice
|
<i class="fas fa-save me-2"></i>Update Invoice
|
||||||
</button>
|
</button>
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -541,11 +543,13 @@ body {
|
|||||||
<h4>
|
<h4>
|
||||||
<i class="fas fa-credit-card me-2"></i>Installment Payments
|
<i class="fas fa-credit-card me-2"></i>Installment Payments
|
||||||
</h4>
|
</h4>
|
||||||
|
@can('invoice.add_installment')
|
||||||
@if($remaining > 0)
|
@if($remaining > 0)
|
||||||
<button id="toggleInstallmentForm" class="btn-primary-compact btn-compact">
|
<button id="toggleInstallmentForm" class="btn-primary-compact btn-compact">
|
||||||
<i class="fas fa-plus-circle me-2"></i>Add Installment
|
<i class="fas fa-plus-circle me-2"></i>Add Installment
|
||||||
</button>
|
</button>
|
||||||
@endif
|
@endif
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body-compact">
|
<div class="card-body-compact">
|
||||||
|
|||||||
@@ -228,44 +228,86 @@
|
|||||||
<div class="word"><strong>KENT</strong><br /><small>International Pvt. Ltd.</small></div>
|
<div class="word"><strong>KENT</strong><br /><small>International Pvt. Ltd.</small></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a href="{{ route('admin.dashboard') }}" class="{{ request()->routeIs('admin.dashboard') ? 'active' : '' }}"><i class="bi bi-house"></i> Dashboard</a>
|
{{-- Dashboard (requires order.view) --}}
|
||||||
<a href="{{ route('admin.shipments') }}" class="{{ request()->routeIs('admin.shipments') ? 'active' : '' }}"><i class="bi bi-truck"></i> Shipments</a>
|
@can('order.view')
|
||||||
<a href="{{ route('admin.invoices.index') }}"
|
<a href="{{ route('admin.dashboard') }}" class="{{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
||||||
class="{{ request()->routeIs('admin.invoices.index') ? 'active' : '' }}">
|
<i class="bi bi-house"></i> Dashboard
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Shipments --}}
|
||||||
|
@can('shipment.view')
|
||||||
|
<a href="{{ route('admin.shipments') }}" class="{{ request()->routeIs('admin.shipments') ? 'active' : '' }}">
|
||||||
|
<i class="bi bi-truck"></i> Shipments
|
||||||
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Invoice --}}
|
||||||
|
@can('invoice.view')
|
||||||
|
<a href="{{ route('admin.invoices.index') }}" class="{{ request()->routeIs('admin.invoices.index') ? 'active' : '' }}">
|
||||||
<i class="bi bi-receipt"></i> Invoice
|
<i class="bi bi-receipt"></i> Invoice
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
<a href="{{ route('admin.customers.index') }}"
|
{{-- Customers --}}
|
||||||
class="{{ request()->routeIs('admin.customers.index') ? 'active' : '' }}">
|
@can('customer.view')
|
||||||
|
<a href="{{ route('admin.customers.index') }}" class="{{ request()->routeIs('admin.customers.index') ? 'active' : '' }}">
|
||||||
<i class="bi bi-people"></i> Customers
|
<i class="bi bi-people"></i> Customers
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
<a href="{{ route('admin.reports') }}"
|
{{-- Reports --}}
|
||||||
class="{{ request()->routeIs('admin.reports') ? 'active' : '' }}">
|
@can('report.view')
|
||||||
|
<a href="{{ route('admin.reports') }}" class="{{ request()->routeIs('admin.reports') ? 'active' : '' }}">
|
||||||
<i class="bi bi-graph-up"></i> Reports
|
<i class="bi bi-graph-up"></i> Reports
|
||||||
</a>
|
</a>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
{{-- Chat Support (NO PERMISSION REQUIRED) --}}
|
||||||
<a href="{{ route('admin.chat_support') }}" class="{{ request()->routeIs('admin.chat_support') ? 'active' : '' }}"><i class="bi bi-chat-dots"></i> Chat Support</a>
|
<a href="{{ route('admin.chat_support') }}" class="{{ request()->routeIs('admin.chat_support') ? 'active' : '' }}">
|
||||||
<!-- <a href="{{ route('admin.orders.index') }}"
|
<i class="bi bi-chat-dots"></i> Chat Support
|
||||||
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>
|
|
||||||
<li>
|
|
||||||
<a href="{{ route('admin.profile.requests') }}">
|
|
||||||
<i class="bi bi-person-lines-fill"></i>
|
|
||||||
Profile Update Requests
|
|
||||||
</a>
|
</a>
|
||||||
</li>
|
|
||||||
|
|
||||||
<a href="{{ route('admin.staff') }}" class="{{ request()->routeIs('admin.staff') ? 'active' : '' }}"><i class="bi bi-person-badge"></i> Staff</a>
|
{{-- Orders --}}
|
||||||
<a href="{{ route('admin.account') }}" class="{{ request()->routeIs('admin.account') ? 'active' : '' }}"><i class="bi bi-gear"></i> Account</a>
|
@can('orders.view')
|
||||||
<a href="{{ route('admin.marklist.index') }}" class="{{ request()->routeIs('admin.marklist.index') ? 'active' : '' }}"><i class="bi bi-list-check"></i> Mark List</a>
|
<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
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
|
|||||||
@@ -121,18 +121,32 @@
|
|||||||
|
|
||||||
<form method="POST" action="{{ route('admin.login.submit') }}">
|
<form method="POST" action="{{ route('admin.login.submit') }}">
|
||||||
@csrf
|
@csrf
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label>Email</label>
|
<label>Username / Email / Employee ID</label>
|
||||||
<input type="email" name="email" class="form-control" value="{{ old('email') }}" required>
|
<input
|
||||||
|
type="text"
|
||||||
|
name="login"
|
||||||
|
class="form-control"
|
||||||
|
value="{{ old('login') }}"
|
||||||
|
placeholder="Enter Email or Username or EMP ID"
|
||||||
|
required
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label>Password</label>
|
<label>Password</label>
|
||||||
<input type="password" name="password" class="form-control" required>
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
class="form-control"
|
||||||
|
required
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary w-100">Login</button>
|
<button type="submit" class="btn btn-primary w-100">Login</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
<div class="secure-encrypted">
|
<div class="secure-encrypted">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="green" class="bi bi-lock-fill" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" fill="green" class="bi bi-lock-fill" viewBox="0 0 16 16">
|
||||||
|
|||||||
@@ -17,9 +17,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- ADD ITEM --}}
|
{{-- ADD ITEM --}}
|
||||||
|
@can('order.create')
|
||||||
<button class="btn btn-add-item" data-bs-toggle="modal" data-bs-target="#addItemModal">
|
<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
|
<i class="fas fa-plus-circle me-2"></i>Add New Item
|
||||||
</button>
|
</button>
|
||||||
|
@endcan
|
||||||
|
|
||||||
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
|
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
|
||||||
</div>
|
</div>
|
||||||
@@ -191,6 +193,7 @@
|
|||||||
<td class="d-flex justify-content-center gap-2">
|
<td class="d-flex justify-content-center gap-2">
|
||||||
|
|
||||||
{{-- EDIT BUTTON --}}
|
{{-- EDIT BUTTON --}}
|
||||||
|
@can('order.edit')
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="btn btn-sm btn-edit-item"
|
class="btn btn-sm btn-edit-item"
|
||||||
@@ -198,7 +201,9 @@
|
|||||||
data-bs-target="#editItemModal{{ $item->id }}">
|
data-bs-target="#editItemModal{{ $item->id }}">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</button>
|
</button>
|
||||||
|
@endcan
|
||||||
|
|
||||||
|
@can('order.delete')
|
||||||
{{-- DELETE BUTTON --}}
|
{{-- DELETE BUTTON --}}
|
||||||
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
|
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
|
||||||
method="POST"
|
method="POST"
|
||||||
@@ -209,6 +214,7 @@
|
|||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
@endcan
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@@ -327,7 +333,7 @@
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-modal-close" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-modal-close" data-bs-dismiss="modal">Close</button>
|
||||||
<button type="submit" class="btn btn-modal-add">Add Item</button>
|
<button type="submit" class="btn btn-modal-add">Edit Item</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -322,8 +322,33 @@
|
|||||||
|
|
||||||
/* NEW: Action Button Styles */
|
/* NEW: Action Button Styles */
|
||||||
.action-container {
|
.action-container {
|
||||||
position: relative;
|
display: flex;
|
||||||
display: inline-block;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view-details {
|
||||||
|
background: linear-gradient(135deg, #10b981, #059669);
|
||||||
|
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;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view-details:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||||
|
background: linear-gradient(135deg, #059669, #047857);
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-edit-status {
|
.btn-edit-status {
|
||||||
@@ -1259,33 +1284,46 @@
|
|||||||
<td>{{ \Carbon\Carbon::parse($ship->shipment_date)->format('d M Y') }}</td>
|
<td>{{ \Carbon\Carbon::parse($ship->shipment_date)->format('d M Y') }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, {{ $ship->id }})" title="Edit Status">
|
<!-- 👁️ View Icon -->
|
||||||
|
<!-- <a href="{{ route('admin.shipment.view', $ship->id) }}"
|
||||||
|
class="btn-view-details"
|
||||||
|
title="View Shipment">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
</a> -->
|
||||||
|
|
||||||
|
<!-- ✏️ Edit Status Icon -->
|
||||||
|
<button type="button" class="btn-edit-status"
|
||||||
|
onclick="toggleStatusDropdown(this, {{ $ship->id }})"
|
||||||
|
title="Edit Status">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Dropdown -->
|
||||||
<div class="status-dropdown" id="statusDropdown-{{ $ship->id }}">
|
<div class="status-dropdown" id="statusDropdown-{{ $ship->id }}">
|
||||||
<form action="{{ route('admin.shipments.updateStatus') }}" method="POST" class="status-form">
|
<form action="{{ route('admin.shipments.updateStatus') }}" method="POST" class="status-form">
|
||||||
@csrf
|
@csrf
|
||||||
<input type="hidden" name="shipment_id" value="{{ $ship->id }}">
|
<input type="hidden" name="shipment_id" value="{{ $ship->id }}">
|
||||||
|
|
||||||
<button type="submit" name="status" value="pending" class="status-option pending">
|
<button type="submit" name="status" value="pending" class="status-option pending">
|
||||||
<span class="status-indicator pending"></span>
|
<span class="status-indicator pending"></span> Pending
|
||||||
Pending
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="submit" name="status" value="in_transit" class="status-option in_transit">
|
<button type="submit" name="status" value="in_transit" class="status-option in_transit">
|
||||||
<span class="status-indicator in_transit"></span>
|
<span class="status-indicator in_transit"></span> In Transit
|
||||||
In Transit
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="submit" name="status" value="dispatched" class="status-option dispatched">
|
<button type="submit" name="status" value="dispatched" class="status-option dispatched">
|
||||||
<span class="status-indicator dispatched"></span>
|
<span class="status-indicator dispatched"></span> Dispatched
|
||||||
Dispatched
|
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button type="submit" name="status" value="delivered" class="status-option delivered">
|
<button type="submit" name="status" value="delivered" class="status-option delivered">
|
||||||
<span class="status-indicator delivered"></span>
|
<span class="status-indicator delivered"></span> Delivered
|
||||||
Delivered
|
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
@@ -1550,6 +1588,9 @@ function renderTable() {
|
|||||||
<td>${new Date(shipment.shipment_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</td>
|
<td>${new Date(shipment.shipment_date).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-container">
|
<div class="action-container">
|
||||||
|
<a href="/admin/dashboard/${shipment.id}" class="btn-view-details" title="View Shipment">
|
||||||
|
<i class="bi bi-eye"></i>
|
||||||
|
</a>
|
||||||
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, ${shipment.id})" title="Edit Status">
|
<button type="button" class="btn-edit-status" onclick="toggleStatusDropdown(this, ${shipment.id})" title="Edit Status">
|
||||||
<i class="bi bi-pencil"></i>
|
<i class="bi bi-pencil"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
187
resources/views/admin/staff/create.blade.php
Normal file
187
resources/views/admin/staff/create.blade.php
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('page-title', 'Account Dashboard')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<style>
|
||||||
|
.card { background:#fff; border:1px solid #eaeaea; padding:1rem; border-radius:6px; }
|
||||||
|
.field { margin-bottom:.8rem; }
|
||||||
|
label { display:block; font-weight:600; margin-bottom:.25rem; }
|
||||||
|
input[type="text"], input[type="email"], input[type="date"], input[type="password"], textarea, select {
|
||||||
|
width:100%; padding:.5rem; border:1px solid #ddd; border-radius:4px;
|
||||||
|
}
|
||||||
|
.stages { display:flex; gap:.5rem; margin-bottom:1rem; }
|
||||||
|
.stage-ind { padding:.35rem .6rem; border-radius:4px; background:#f3f3f3; }
|
||||||
|
.stage { display:none; }
|
||||||
|
.stage.active { display:block; }
|
||||||
|
.btn { padding:.5rem .8rem; border-radius:5px; border:1px solid #ccc; background:#f6f6f6; cursor:pointer; }
|
||||||
|
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||||
|
.perm-group { border:1px dashed #eee; padding:.6rem; margin-bottom:.6rem; border-radius:4px; }
|
||||||
|
.perm-list { display:flex; flex-wrap:wrap; gap:.5rem; }
|
||||||
|
.perm-item { min-width:200px; }
|
||||||
|
.error { color:#b00020; font-size:.95rem; margin-top:.25rem; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Add Staff</h3>
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div style="background:#fff0f0; padding:.6rem; border:1px solid #f3c6c6; margin-bottom:1rem;">
|
||||||
|
<strong>There were some problems with your input:</strong>
|
||||||
|
<ul>
|
||||||
|
@foreach($errors->all() as $err)
|
||||||
|
<li>{{ $err }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.staff.store') }}">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div class="stages">
|
||||||
|
<div class="stage-ind">1. Personal</div>
|
||||||
|
<div class="stage-ind">2. Professional</div>
|
||||||
|
<div class="stage-ind">3. System</div>
|
||||||
|
<div class="stage-ind">4. Permissions</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Stage 1 --}}
|
||||||
|
<div id="stage-1" class="stage active">
|
||||||
|
<div class="field">
|
||||||
|
<label>Name *</label>
|
||||||
|
<input type="text" name="name" value="{{ old('name') }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Email *</label>
|
||||||
|
<input type="email" name="email" value="{{ old('email') }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Phone *</label>
|
||||||
|
<input type="text" name="phone" value="{{ old('phone') }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Emergency Phone</label>
|
||||||
|
<input type="text" name="emergency_phone" value="{{ old('emergency_phone') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Address</label>
|
||||||
|
<textarea name="address" rows="3">{{ old('address') }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:.5rem;">
|
||||||
|
<button type="button" class="btn" onclick="showStage(2)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Stage 2 --}}
|
||||||
|
<div id="stage-2" class="stage">
|
||||||
|
<div class="field">
|
||||||
|
<label>Role (Business role)</label>
|
||||||
|
<input type="text" name="role" value="{{ old('role') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Department</label>
|
||||||
|
<input type="text" name="department" value="{{ old('department') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Designation</label>
|
||||||
|
<input type="text" name="designation" value="{{ old('designation') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Joining Date</label>
|
||||||
|
<input type="date" name="joining_date" value="{{ old('joining_date') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Status</label>
|
||||||
|
<select name="status">
|
||||||
|
<option value="active" {{ old('status')=='active'?'selected':'' }}>Active</option>
|
||||||
|
<option value="inactive" {{ old('status')=='inactive'?'selected':'' }}>Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Additional Info</label>
|
||||||
|
<textarea name="additional_info" rows="3">{{ old('additional_info') }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||||
|
<button type="button" class="btn" onclick="showStage(1)">Back</button>
|
||||||
|
<button type="button" class="btn" onclick="showStage(3)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Stage 3 --}}
|
||||||
|
<div id="stage-3" class="stage">
|
||||||
|
<div class="field">
|
||||||
|
<label>Username *</label>
|
||||||
|
<input type="text" name="username" value="{{ old('username') }}" placeholder="leave blank to use EMPxxxx">
|
||||||
|
<div class="muted" style="font-size:.9rem; margin-top:.25rem;">If left blank employee id will be used.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Password *</label>
|
||||||
|
<input type="password" name="password" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||||
|
<button type="button" class="btn" onclick="showStage(2)">Back</button>
|
||||||
|
<button type="button" class="btn" onclick="showStage(4)">Next</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Stage 4 --}}
|
||||||
|
<div id="stage-4" class="stage">
|
||||||
|
<div style="margin-bottom:.5rem; font-weight:700;">Permissions</div>
|
||||||
|
|
||||||
|
@foreach($permissions as $group => $groupPerms)
|
||||||
|
<div class="perm-group">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||||
|
<div style="font-weight:600;">{{ ucfirst($group) }}</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn" onclick="toggleGroup('{{ $group }}')">Toggle group</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="perm-list" id="group-{{ $group }}">
|
||||||
|
@foreach($groupPerms as $perm)
|
||||||
|
<label class="perm-item">
|
||||||
|
<input type="checkbox" name="permissions[]" value="{{ $perm->name }}"> {{ $perm->name }}
|
||||||
|
</label>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:space-between; gap:.5rem;">
|
||||||
|
<button type="button" class="btn" onclick="showStage(3)">Back</button>
|
||||||
|
<button type="submit" class="btn primary">Create Staff</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function showStage(n){
|
||||||
|
document.querySelectorAll('.stage').forEach(s=>s.classList.remove('active'));
|
||||||
|
document.getElementById('stage-'+n).classList.add('active');
|
||||||
|
window.scrollTo({top:0, behavior:'smooth'});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleGroup(group){
|
||||||
|
const el = document.getElementById('group-'+group);
|
||||||
|
if(!el) return;
|
||||||
|
const inputs = el.querySelectorAll('input[type="checkbox"]');
|
||||||
|
const anyUnchecked = Array.from(inputs).some(i => !i.checked);
|
||||||
|
inputs.forEach(i => i.checked = anyUnchecked);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
156
resources/views/admin/staff/edit.blade.php
Normal file
156
resources/views/admin/staff/edit.blade.php
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('page-title', 'Account Dashboard')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<style>
|
||||||
|
.card { background:#fff; border:1px solid #eaeaea; padding:1rem; border-radius:6px; }
|
||||||
|
.field { margin-bottom:.8rem; }
|
||||||
|
label { display:block; font-weight:600; margin-bottom:.25rem; }
|
||||||
|
input[type="text"], input[type="email"], input[type="date"], input[type="password"], textarea, select {
|
||||||
|
width:100%; padding:.5rem; border:1px solid #ddd; border-radius:4px;
|
||||||
|
}
|
||||||
|
.perm-group { border:1px dashed #eee; padding:.6rem; margin-bottom:.6rem; border-radius:4px; }
|
||||||
|
.perm-list { display:flex; flex-wrap:wrap; gap:.5rem; }
|
||||||
|
.perm-item { min-width:200px; }
|
||||||
|
.btn { padding:.5rem .8rem; border-radius:5px; border:1px solid #ccc; background:#f6f6f6; cursor:pointer; }
|
||||||
|
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Edit Staff — {{ $staff->display_name ?? $staff->name }}</h3>
|
||||||
|
|
||||||
|
@if($errors->any())
|
||||||
|
<div style="background:#fff0f0; padding:.6rem; border:1px solid #f3c6c6; margin-bottom:1rem;">
|
||||||
|
<strong>There were some problems with your input:</strong>
|
||||||
|
<ul>
|
||||||
|
@foreach($errors->all() as $err)
|
||||||
|
<li>{{ $err }}</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<form method="POST" action="{{ route('admin.staff.update', $staff->id) }}">
|
||||||
|
@csrf
|
||||||
|
@method('PUT')
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Employee ID</label>
|
||||||
|
<input type="text" value="{{ $staff->employee_id }}" disabled>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Name *</label>
|
||||||
|
<input type="text" name="name" value="{{ old('name', $staff->name) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Email *</label>
|
||||||
|
<input type="email" name="email" value="{{ old('email', $staff->email) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Phone *</label>
|
||||||
|
<input type="text" name="phone" value="{{ old('phone', $staff->phone) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Emergency Phone</label>
|
||||||
|
<input type="text" name="emergency_phone" value="{{ old('emergency_phone', $staff->emergency_phone) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Address</label>
|
||||||
|
<textarea name="address" rows="3">{{ old('address', $staff->address) }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Role</label>
|
||||||
|
<input type="text" name="role" value="{{ old('role', $staff->role) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Department</label>
|
||||||
|
<input type="text" name="department" value="{{ old('department', $staff->department) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Designation</label>
|
||||||
|
<input type="text" name="designation" value="{{ old('designation', $staff->designation) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Joining Date</label>
|
||||||
|
<input type="date" name="joining_date" value="{{ old('joining_date', optional($staff->joining_date)->format('Y-m-d')) }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Status</label>
|
||||||
|
<select name="status">
|
||||||
|
<option value="active" {{ old('status', $staff->status)=='active'?'selected':'' }}>Active</option>
|
||||||
|
<option value="inactive" {{ old('status', $staff->status)=='inactive'?'selected':'' }}>Inactive</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Additional Info</label>
|
||||||
|
<textarea name="additional_info" rows="3">{{ old('additional_info', $staff->additional_info) }}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>Username *</label>
|
||||||
|
<input type="text" name="username" value="{{ old('username', $staff->username) }}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label>New Password (leave blank to keep existing)</label>
|
||||||
|
<input type="password" name="password" autocomplete="new-password">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div style="margin-bottom:.5rem; font-weight:700;">Permissions</div>
|
||||||
|
|
||||||
|
@foreach($permissions as $group => $groupPerms)
|
||||||
|
<div class="perm-group">
|
||||||
|
<div style="display:flex; justify-content:space-between; align-items:center;">
|
||||||
|
<div style="font-weight:600;">{{ ucfirst($group) }}</div>
|
||||||
|
<div>
|
||||||
|
<button type="button" class="btn" onclick="toggleGroup('{{ $group }}')">Toggle group</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="perm-list" id="group-{{ $group }}">
|
||||||
|
@foreach($groupPerms as $perm)
|
||||||
|
<label class="perm-item">
|
||||||
|
<input type="checkbox" name="permissions[]" value="{{ $perm->name }}"
|
||||||
|
{{ in_array($perm->name, old('permissions', $staffPermissions)) ? 'checked' : '' }}>
|
||||||
|
{{ $perm->name }}
|
||||||
|
</label>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
|
||||||
|
<div style="display:flex; justify-content:flex-end; gap:.5rem;">
|
||||||
|
<a href="{{ route('admin.staff.index') }}" class="btn">Cancel</a>
|
||||||
|
<button type="submit" class="btn primary">Update Staff</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function toggleGroup(group){
|
||||||
|
const el = document.getElementById('group-'+group);
|
||||||
|
if(!el) return;
|
||||||
|
const inputs = el.querySelectorAll('input[type="checkbox"]');
|
||||||
|
const anyUnchecked = Array.from(inputs).some(i => !i.checked);
|
||||||
|
inputs.forEach(i => i.checked = anyUnchecked);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
@endsection
|
||||||
65
resources/views/admin/staff/index.blade.php
Normal file
65
resources/views/admin/staff/index.blade.php
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
@extends('admin.layouts.app')
|
||||||
|
|
||||||
|
@section('page-title', 'Account Dashboard')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<style>
|
||||||
|
.top-bar { display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; }
|
||||||
|
.card { background:#fff; border:1px solid #e4e4e4; border-radius:6px; padding:1rem; box-shadow:0 1px 3px rgba(0,0,0,.03); }
|
||||||
|
table { width:100%; border-collapse:collapse; }
|
||||||
|
th, td { padding:.6rem .75rem; border-bottom:1px solid #f1f1f1; text-align:left; }
|
||||||
|
.btn { padding:.45rem .75rem; border-radius:4px; border:1px solid #ccc; background:#f7f7f7; cursor:pointer; }
|
||||||
|
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; }
|
||||||
|
.actions a { margin-right:.5rem; color:#0b74de; text-decoration:none; }
|
||||||
|
.muted { color:#666; font-size:.95rem; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="top-bar">
|
||||||
|
<h2>Staff</h2>
|
||||||
|
<a href="{{ route('admin.staff.create') }}" class="btn primary">Add Staff</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
@if(session('success'))
|
||||||
|
<div style="padding:.5rem; background:#e6ffed; border:1px solid #b6f0c6; margin-bottom:1rem;">{{ session('success') }}</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Employee ID</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Email</th>
|
||||||
|
<th>Phone</th>
|
||||||
|
<th>Role</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@forelse($staff as $s)
|
||||||
|
<tr>
|
||||||
|
<td>{{ $s->id }}</td>
|
||||||
|
<td class="muted">{{ $s->employee_id }}</td>
|
||||||
|
<td>{{ $s->name }}</td>
|
||||||
|
<td>{{ $s->email }}</td>
|
||||||
|
<td>{{ $s->phone }}</td>
|
||||||
|
<td>{{ $s->role ?? '-' }}</td>
|
||||||
|
<td>{{ ucfirst($s->status) }}</td>
|
||||||
|
<td class="actions">
|
||||||
|
<a href="{{ route('admin.staff.edit', $s->id) }}">Edit</a>
|
||||||
|
<form action="{{ route('admin.staff.destroy', $s->id) }}" method="POST" style="display:inline" onsubmit="return confirm('Delete this staff?')">
|
||||||
|
@csrf
|
||||||
|
@method('DELETE')
|
||||||
|
<button class="btn" type="submit">Delete</button>
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@empty
|
||||||
|
<tr><td colspan="8" class="muted">No staff found.</td></tr>
|
||||||
|
@endforelse
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
@@ -10,6 +10,7 @@ use App\Http\Controllers\Admin\AdminInvoiceController;
|
|||||||
use App\Http\Controllers\Admin\AdminCustomerController;
|
use App\Http\Controllers\Admin\AdminCustomerController;
|
||||||
use App\Http\Controllers\Admin\AdminAccountController;
|
use App\Http\Controllers\Admin\AdminAccountController;
|
||||||
use App\Http\Controllers\Admin\AdminReportController;
|
use App\Http\Controllers\Admin\AdminReportController;
|
||||||
|
use App\Http\Controllers\Admin\AdminStaffController;
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// Public Front Page
|
// Public Front Page
|
||||||
@@ -21,19 +22,11 @@ Route::get('/', function () {
|
|||||||
// ---------------------------
|
// ---------------------------
|
||||||
// ADMIN LOGIN ROUTES
|
// ADMIN LOGIN ROUTES
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
|
// login routes (public)
|
||||||
Route::prefix('admin')->group(function () {
|
Route::prefix('admin')->group(function () {
|
||||||
|
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])->name('admin.login');
|
||||||
// MUST have route name "login" for session redirect
|
Route::post('/login', [AdminAuthController::class, 'login'])->name('admin.login.submit');
|
||||||
Route::get('/login', [AdminAuthController::class, 'showLoginForm'])
|
Route::post('/logout', [AdminAuthController::class, 'logout'])->name('admin.logout');
|
||||||
->name('admin.login');
|
|
||||||
|
|
||||||
Route::post('/login', [AdminAuthController::class, 'login'])
|
|
||||||
->name('admin.login.submit');
|
|
||||||
|
|
||||||
Route::post('/logout', [AdminAuthController::class, 'logout'])
|
|
||||||
->name('admin.logout');
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -295,4 +288,13 @@ Route::prefix('admin')
|
|||||||
//---------------------------
|
//---------------------------
|
||||||
//Edit Button Route
|
//Edit Button Route
|
||||||
//---------------------------
|
//---------------------------
|
||||||
|
// protected admin routes
|
||||||
|
Route::middleware(['auth:admin'])
|
||||||
|
->prefix('admin')
|
||||||
|
->name('admin.')
|
||||||
|
->group(function () {
|
||||||
|
|
||||||
|
|
||||||
|
// staff resource
|
||||||
|
Route::resource('staff', AdminStaffController::class);
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user