This commit is contained in:
Utkarsh Khedkar
2025-12-19 16:21:00 +05:30
9 changed files with 283 additions and 100 deletions

View File

@@ -55,6 +55,7 @@ class AdminStaffController extends Controller
DB::beginTransaction(); DB::beginTransaction();
try { try {
// 1⃣ Create staff WITHOUT employee_id (ID not available yet)
$admin = Admin::create([ $admin = Admin::create([
'name' => $request->name, 'name' => $request->name,
'email' => $request->email, 'email' => $request->email,
@@ -69,23 +70,33 @@ class AdminStaffController extends Controller
'status' => $request->status, 'status' => $request->status,
'additional_info' => $request->additional_info, 'additional_info' => $request->additional_info,
'username' => $request->username, // username may be NULL here
'username' => $request->username ?: null,
'password' => Hash::make($request->password), 'password' => Hash::make($request->password),
'type' => 'staff', 'type' => 'staff',
]); ]);
// Generate EMPLOYEE ID using admin ID (safe) // 2 Generate EMPLOYEE ID
$employeeId = 'EMP' . str_pad($admin->id, 4, '0', STR_PAD_LEFT); $employeeId = 'EMP' . str_pad($admin->id, 4, '0', STR_PAD_LEFT);
$admin->update(['employee_id' => $employeeId]);
// Assign permissions (if any) // 3⃣ Auto-generate username if left blank
$username = $request->username ?: strtolower($employeeId);
// 4⃣ Update employee_id + username together
$admin->update([
'employee_id' => $employeeId,
'username' => $username,
]);
// 5⃣ Assign permissions (if any)
if ($request->permissions) { if ($request->permissions) {
$admin->givePermissionTo($request->permissions); $admin->givePermissionTo($request->permissions);
} }
DB::commit(); DB::commit();
return redirect()->route('admin.staff.index') return redirect()
->route('admin.staff.index')
->with('success', 'Staff created successfully.'); ->with('success', 'Staff created successfully.');
} catch (\Exception $e) { } catch (\Exception $e) {
@@ -94,6 +105,7 @@ class AdminStaffController extends Controller
} }
} }
public function edit($id) public function edit($id)
{ {
$staff = Admin::where('type', 'staff')->findOrFail($id); $staff = Admin::where('type', 'staff')->findOrFail($id);

View File

@@ -15,7 +15,12 @@ class UserRequestController extends Controller
public function index() public function index()
{ {
$requests = CustomerRequest::orderBy('id', 'desc')->get(); $requests = CustomerRequest::orderBy('id', 'desc')->get();
return view('admin.requests', compact('requests')); $pendingProfileUpdates = \App\Models\UpdateRequest::where('status', 'pending')->count();
return view('admin.requests', compact(
'requests',
'pendingProfileUpdates'
));
} }
// Approve user request // Approve user request

View File

@@ -18,7 +18,7 @@ class Admin extends Authenticatable
'name', 'email', 'password', 'username', 'name', 'email', 'password', 'username',
'phone', 'emergency_phone', 'address', 'phone', 'emergency_phone', 'address',
'role', 'department', 'designation', 'joining_date', 'role', 'department', 'designation', 'joining_date',
'status', 'additional_info', 'type', // admin/staff indicator 'status', 'additional_info', 'type','employee_id', // admin/staff indicator
]; ];
protected $hidden = [ protected $hidden = [

Binary file not shown.

View File

@@ -201,7 +201,7 @@
background: #fff; background: #fff;
padding: 10px 18px !important; padding: 10px 18px !important;
position: relative; position: relative;
height: 48px; height: 65px;
width: 100%; width: 100%;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
} }
@@ -221,6 +221,32 @@
font-size: 1.06rem; font-size: 1.06rem;
font-weight: 500; font-weight: 500;
} }
/* ================================
HEADER NOTIFICATION BADGE FIX
================================ */
/* Target ONLY badge inside bell icon */
header .bi-bell {
position: relative;
}
/* Override broken global badge styles */
header .bi-bell .badge {
width: 22px !important;
height: 22px !important;
min-width: 22px !important;
padding: 0 !important;
font-size: 12px !important;
line-height: 22px !important;
border-radius: 50% !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
animation: none !important;
box-shadow: 0 0 0 2px #ffffff;
}
</style> </style>
</head> </head>
@@ -285,12 +311,12 @@
</a> </a>
@endcan @endcan
{{-- Profile Update Requests --}} <!-- {{-- Profile Update Requests --}}
@can('request.update_profile') @can('request.update_profile')
<a href="{{ route('admin.profile.requests') }}"> <a href="{{ route('admin.profile.requests') }}">
<i class="bi bi-person-lines-fill"></i> Profile Update Requests <i class="bi bi-person-lines-fill"></i> Profile Update Requests
</a> </a>
@endcan @endcan -->
{{-- Staff (NO PERMISSION REQUIRED) --}} {{-- Staff (NO PERMISSION REQUIRED) --}}
<a href="{{ route('admin.staff.index') }}" class="{{ request()->routeIs('admin.staff.*') ? 'active' : '' }}"> <a href="{{ route('admin.staff.index') }}" class="{{ request()->routeIs('admin.staff.*') ? 'active' : '' }}">

View File

@@ -5,107 +5,207 @@
@section('content') @section('content')
<div class="container-fluid px-0"> <div class="container-fluid px-0">
@php @php
$perPage = 5; $perPage = 5;
$currentPage = request()->get('page', 1); $currentPage = request()->get('page', 1);
$currentPage = max(1, (int)$currentPage); $currentPage = max(1, (int)$currentPage);
$total = $requests->count(); $total = $requests->count();
$totalPages = ceil($total / $perPage); $currentItems = $requests->slice(($currentPage - 1) * $perPage, $perPage);
$currentItems = $requests->slice(($currentPage - 1) * $perPage, $perPage); @endphp
@endphp
<style> <style>
.old-value { color: #6b7280; font-weight: 600; } /* ===== Card Wrapper ===== */
.new-value { color: #111827; font-weight: 700; } .request-card {
.changed { background: #fef3c7; padding: 6px; border-radius: 6px; } background: #ffffff;
.box { padding: 10px 14px; border-radius: 8px; background: #f8fafc; margin-bottom: 10px; } border-radius: 14px;
.diff-label { font-size: 13px; font-weight: 700; } padding: 18px;
.actions { display: flex; gap: 10px; } margin-bottom: 18px;
</style> box-shadow: 0 6px 20px rgba(0,0,0,0.05);
}
<h4 class="fw-bold my-3">Profile Update Requests ({{ $total }})</h4> /* ===== Header Row ===== */
.request-header {
display: grid;
grid-template-columns: 60px 1.5fr 1fr 1.2fr 1fr;
gap: 14px;
align-items: center;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 12px;
margin-bottom: 14px;
}
<div class="card mb-4 shadow-sm"> .request-header strong {
<div class="card-body pb-1"> font-size: 14px;
}
<div class="table-responsive custom-table-wrapper"> /* ===== Badges ===== */
<table class="table align-middle mb-0 custom-table"> .badge {
<thead> padding: 6px 14px;
<tr> font-size: 12px;
<th>#</th> border-radius: 999px;
<th>User</th> font-weight: 700;
<th>Requested Changes</th> }
<th>Status</th>
<th>Requested At</th>
<th>Actions</th>
</tr>
</thead>
<tbody> .badge-pending {
@foreach($currentItems as $index => $req) background: #fff7ed;
@php color: #c2410c;
$user = $req->user; border: 1px solid #fed7aa;
// FIX: Convert string to array }
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
@endphp
<tr> .badge-approved {
<td><strong>{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong></td> background: #ecfdf5;
color: #047857;
border: 1px solid #a7f3d0;
}
<td> .badge-rejected {
<strong>{{ $user->customer_name }}</strong><br> background: #fef2f2;
<small>{{ $user->email }}</small><br> color: #b91c1c;
<small>ID: {{ $user->customer_id }}</small> border: 1px solid #fecaca;
</td> }
<td> /* ===== Action Buttons ===== */
@foreach($newData as $key => $newValue) .actions {
@php display: flex;
$oldValue = $user->$key ?? '—'; gap: 10px;
$changed = $oldValue != $newValue; }
@endphp
<div class="box {{ $changed ? 'changed' : '' }}"> .actions .btn {
<span class="diff-label">{{ ucfirst(str_replace('_',' ', $key)) }}:</span><br> padding: 6px 14px;
<span class="old-value">Old: {{ $oldValue }}</span><br> font-size: 13px;
<span class="new-value">New: {{ $newValue ?? '—' }}</span> border-radius: 999px;
</div> font-weight: 600;
@endforeach }
</td>
<td> /* ===== Detail Grid ===== */
@if($req->status == 'pending') .detail-grid {
<span class="badge badge-pending">Pending</span> display: grid;
@elseif($req->status == 'approved') grid-template-columns: repeat(3, 1fr);
<span class="badge badge-approved">Approved</span> gap: 14px;
@else margin-top: 12px;
<span class="badge badge-rejected">Rejected</span> }
@endif
</td>
<td>{{ $req->created_at->format('d M Y, h:i A') }}</td> /* ===== Detail Box ===== */
.detail-box {
background: #f8fafc;
border-radius: 10px;
padding: 12px 14px;
border: 1px solid #e2e8f0;
}
<td class="actions"> .detail-box.changed {
@if($req->status == 'pending') background: linear-gradient(145deg, #fff7ed, #ffedd5);
<a href="{{ route('admin.profile.approve', $req->id) }}" class="btn btn-success btn-sm"> border-left: 4px solid #f59e0b;
<i class="bi bi-check-circle"></i> Approve }
</a>
<a href="{{ route('admin.profile.reject', $req->id) }}" class="btn btn-danger btn-sm"> .detail-label {
<i class="bi bi-x-circle"></i> Reject font-size: 12px;
</a> font-weight: 700;
@else color: #334155;
<span class="text-muted">Completed</span> }
@endif
</td>
</tr> .old {
@endforeach font-size: 12px;
</tbody> color: #64748b;
</table> }
</div>
.new {
font-size: 14px;
font-weight: 700;
color: #020617;
}
/* ===== Responsive ===== */
@media (max-width: 992px) {
.request-header {
grid-template-columns: 1fr;
}
.detail-grid {
grid-template-columns: 1fr;
}
}
</style>
<h4 class="fw-bold my-3">Profile Update Requests ({{ $total }})</h4>
@foreach($currentItems as $index => $req)
@php
$user = $req->user;
$newData = is_array($req->data) ? $req->data : json_decode($req->data, true);
@endphp
<div class="request-card">
<!-- HEADER -->
<div class="request-header">
<strong>#{{ ($currentPage - 1) * $perPage + $index + 1 }}</strong>
<div>
<strong>{{ $user->customer_name }}</strong><br>
<small>{{ $user->email }}</small><br>
<small>ID: {{ $user->customer_id }}</small>
</div>
<div>
@if($req->status == 'pending')
<span class="badge badge-pending">Pending</span>
@elseif($req->status == 'approved')
<span class="badge badge-approved">Approved</span>
@else
<span class="badge badge-rejected">Rejected</span>
@endif
</div>
<div>{{ $req->created_at->format('d M Y, h:i A') }}</div>
<div class="actions">
@if($req->status == 'pending')
<a href="{{ route('admin.profile.approve', $req->id) }}" class="btn btn-success btn-sm">
Approve
</a>
<a href="{{ route('admin.profile.reject', $req->id) }}" class="btn btn-danger btn-sm">
Reject
</a>
@else
<span class="text-muted">Completed</span>
@endif
</div> </div>
</div> </div>
<!-- DETAILS ROW 1 -->
<div class="detail-grid">
@foreach(['customer_name','company_name','email'] as $field)
@php
$old = $user->$field ?? '—';
$new = $newData[$field] ?? $old;
@endphp
<div class="detail-box {{ $old != $new ? 'changed' : '' }}">
<div class="detail-label">{{ ucfirst(str_replace('_',' ', $field)) }}</div>
<div class="old">Old: {{ $old }}</div>
<div class="new">New: {{ $new }}</div>
</div>
@endforeach
</div>
<!-- DETAILS ROW 2 -->
<div class="detail-grid">
@foreach(['mobile_no','address','pincode'] as $field)
@php
$old = $user->$field ?? '—';
$new = $newData[$field] ?? $old;
@endphp
<div class="detail-box {{ $old != $new ? 'changed' : '' }}">
<div class="detail-label">{{ ucfirst(str_replace('_',' ', $field)) }}</div>
<div class="old">Old: {{ $old }}</div>
<div class="new">New: {{ $new }}</div>
</div>
@endforeach
</div>
</div>
@endforeach
</div> </div>
@endsection @endsection

View File

@@ -307,13 +307,54 @@
justify-content: center; justify-content: center;
} }
} }
/* ==============================================
PROFILE UPDATE REQUEST BUTTON BADGE FIX
============================================== */
/* Ensure button is positioning context */
a.btn.btn-primary.position-relative {
position: relative;
margin-right: 10px;
}
/* Fix badge inside Profile Update Requests button */
a.btn.btn-primary.position-relative .badge {
width: 30px !important;
height: 30px !important;
min-width: 30px !important;
padding: 0 !important;
font-size: 14px !important;
line-height: 30px !important;
border-radius: 50% !important;
display: inline-flex !important;
align-items: center;
justify-content: center;
animation: none !important;
box-shadow: 0 0 0 2px #ffffff;
}
</style> </style>
<!-- Counts --> <!-- Counts -->
<div class="d-flex justify-content-between align-items-center mb-2 mt-3"> <div class="d-flex justify-content-between align-items-center mb-2 mt-3">
<h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4> <h4 class="fw-bold mb-0">User Requests (Total: {{ $total }})</h4>
</div> @can('request.update_profile')
<a href="{{ route('admin.profile.requests') }}" class="btn btn-primary position-relative">
<i class="bi bi-person-lines-fill me-1"></i>
Profile Update Requests
@if($pendingProfileUpdates > 0)
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
{{ $pendingProfileUpdates }}
</span>
@endif
</a>
@endcan
</div>
<!-- Search + Table --> <!-- Search + Table -->
<div class="card mb-4 shadow-sm"> <div class="card mb-4 shadow-sm">

View File

@@ -849,8 +849,7 @@
name="username" name="username"
value="{{ old('username') }}" value="{{ old('username') }}"
class="form-input" class="form-input"
placeholder="Leave blank to generate automatically" placeholder="Leave blank to generate automatically">
required>
<div class="helper-text"> <div class="helper-text">
<span></span> <span></span>
If left blank, username will be generated from employee ID If left blank, username will be generated from employee ID