This commit is contained in:
Utkarsh Khedkar
2025-12-19 11:00:34 +05:30
parent 5dc9fc7db4
commit fc9a401a8c
11 changed files with 6113 additions and 730 deletions

View File

@@ -224,5 +224,81 @@ class ShipmentController extends Controller
return view('admin.view_shipment', compact('shipment', 'dummyData')); return view('admin.view_shipment', compact('shipment', 'dummyData'));
} }
// App\Models\Shipment.php
public function orders()
{
return $this->belongsToMany(\App\Models\Order::class, 'shipment_items', 'shipment_id', 'order_id');
}
public function removeOrder(Shipment $shipment, Order $order)
{
// Remove row from pivot table shipment_items
ShipmentItem::where('shipment_id', $shipment->id)
->where('order_id', $order->id)
->delete(); // removes link shipment <-> order [web:41][web:45]
// Recalculate totals on this shipment (optional but recommended)
$orders = Order::whereIn(
'id',
ShipmentItem::where('shipment_id', $shipment->id)->pluck('order_id')
)->get();
$shipment->total_ctn = $orders->sum('ctn');
$shipment->total_qty = $orders->sum('qty');
$shipment->total_ttl_qty = $orders->sum('ttl_qty');
$shipment->total_cbm = $orders->sum('cbm');
$shipment->total_ttl_cbm = $orders->sum('ttl_cbm');
$shipment->total_kg = $orders->sum('kg');
$shipment->total_ttl_kg = $orders->sum('ttl_kg');
$shipment->total_amount = $orders->sum('ttl_amount');
$shipment->save();
// Redirect back to preview page where your blade is loaded
return redirect()
->route('admin.shipments.dummy', $shipment->id)
->with('success', 'Order removed from shipment successfully.');
}
public function addOrders(Request $request, Shipment $shipment)
{
$request->validate([
'order_ids' => 'required|array|min:1',
]);
// फक्त न वापरलेले orders घ्या
$orders = Order::whereIn('id', $request->order_ids)->get();
foreach ($orders as $order) {
// pivot मध्ये insert
ShipmentItem::create([
'shipment_id' => $shipment->id,
'order_id' => $order->id,
'order_ctn' => $order->ctn,
'order_qty' => $order->qty,
'order_ttl_qty' => $order->ttl_qty,
'order_ttl_amount' => $order->ttl_amount,
'order_ttl_kg' => $order->ttl_kg,
]);
}
// totals
$orderIds = ShipmentItem::where('shipment_id', $shipment->id)->pluck('order_id');
$allOrders = Order::whereIn('id', $orderIds)->get();
$shipment->total_ctn = $allOrders->sum('ctn');
$shipment->total_qty = $allOrders->sum('qty');
$shipment->total_ttl_qty = $allOrders->sum('ttl_qty');
$shipment->total_cbm = $allOrders->sum('cbm');
$shipment->total_ttl_cbm = $allOrders->sum('ttl_cbm');
$shipment->total_kg = $allOrders->sum('kg');
$shipment->total_ttl_kg = $allOrders->sum('ttl_kg');
$shipment->total_amount = $allOrders->sum('ttl_amount');
$shipment->save();
return redirect()
->route('admin.shipments.dummy', $shipment->id)
->with('success', 'Orders added to shipment successfully.');
}
} }

View File

@@ -1,96 +1,605 @@
@extends('admin.layouts.app') @extends('admin.layouts.app')
@section('page-title', 'Chat Support') @section('page-title', 'Chat Support Dashboard')
@section('content') @section('content')
<style>
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--success-gradient: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
--danger-gradient: linear-gradient(135deg, #ff416c 0%, #ff4b2b 100%);
--warning-gradient: linear-gradient(135deg, #f7971e 0%, #ffd200 100%);
--info-gradient: linear-gradient(135deg, #56ccf2 0%, #2f80ed 100%);
--card-shadow: 0 8px 25px rgba(0,0,0,0.08);
--card-shadow-hover: 0 15px 35px rgba(0,0,0,0.12);
--border-radius: 12px;
--transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
<div class="container py-4"> * {
box-sizing: border-box;
}
<h2 class="mb-4 fw-bold">Customer Support Chat</h2> .chat-dashboard {
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 1.5rem;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
}
<div class="card shadow-sm"> .dashboard-header {
<div class="card-body p-0"> text-align: center;
margin-bottom: 2rem;
position: relative;
}
@if($tickets->count() === 0) .dashboard-title {
<div class="p-4 text-center text-muted"> font-size: clamp(2rem, 4vw, 3rem);
<h5>No customer chats yet.</h5> font-weight: 800;
</div> background: var(--primary-gradient);
@else -webkit-background-clip: text;
<ul class="list-group list-group-flush"> background-clip: text;
-webkit-text-fill-color: transparent;
margin: 0 0 0.75rem 0;
position: relative;
display: inline-block;
}
@foreach($tickets as $ticket) .dashboard-title::before {
@php content: '💬';
// Get last message position: absolute;
$lastMsg = $ticket->messages()->latest()->first(); left: -2.5rem;
@endphp top: 50%;
transform: translateY(-50%);
font-size: 2rem;
animation: bounce 2s infinite;
}
<li class="list-group-item py-3"> @keyframes bounce {
0%, 20%, 50%, 80%, 100% { transform: translateY(-50%) translateY(0); }
40% { transform: translateY(-50%) translateY(-8px); }
60% { transform: translateY(-50%) translateY(-4px); }
}
<div class="d-flex align-items-center justify-content-between"> .dashboard-subtitle {
color: #64748b;
font-size: 1rem;
max-width: 500px;
margin: 0 auto;
line-height: 1.5;
}
<!-- LEFT --> .stats-grid {
<div class="d-flex align-items-center gap-3"> display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1rem;
margin-bottom: 2rem;
}
<!-- Avatar --> .stat-card {
<div class="rounded-circle bg-primary text-white d-flex align-items-center justify-content-center" background: rgba(255, 255, 255, 0.95);
style="width: 45px; height: 45px; font-size: 18px;"> backdrop-filter: blur(15px);
{{ strtoupper(substr($ticket->user->customer_name ?? $ticket->user->name, 0, 1)) }} border-radius: var(--border-radius);
</div> padding: 1.25rem 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
border: 1px solid rgba(255, 255, 255, 0.3);
box-shadow: var(--card-shadow);
transition: var(--transition);
position: relative;
overflow: hidden;
}
<div> .stat-card::before {
<!-- Name + unread badge --> content: '';
<h6 class="mb-1 fw-semibold"> position: absolute;
{{ $ticket->user->customer_name ?? $ticket->user->name }} top: 0;
left: 0;
right: 0;
height: 3px;
background: var(--primary-gradient);
transform: scaleX(0);
transition: transform 0.3s ease;
}
<span .stat-card:hover {
id="badge-{{ $ticket->id }}" transform: translateY(-4px);
class="badge bg-danger ms-2 {{ $ticket->unread_count == 0 ? 'd-none' : '' }}"> box-shadow: var(--card-shadow-hover);
{{ $ticket->unread_count }} }
</span>
</h6>
<!-- Last message --> .stat-card:hover::before {
<small class="text-muted"> transform: scaleX(1);
@if($lastMsg) }
@if($lastMsg->message)
{{ Str::limit($lastMsg->message, 35) }}
@elseif(Str::startsWith($lastMsg->file_type, 'image'))
📷 Image
@else
📎 Attachment
@endif
@else
<i>No messages yet</i>
@endif
</small>
</div>
</div> .stat-icon {
width: 55px;
height: 55px;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
box-shadow: 0 6px 20px rgba(0,0,0,0.1);
flex-shrink: 0;
}
<!-- RIGHT --> .stat-total { background: var(--primary-gradient); color: white; }
<div class="text-end"> .stat-open { background: var(--success-gradient); color: white; }
<span class="badge {{ $ticket->status === 'open' ? 'bg-success' : 'bg-danger' }}"> .stat-closed{ background: var(--danger-gradient); color: white; }
{{ ucfirst($ticket->status) }}
</span>
<a href="{{ route('admin.chat.open', $ticket->id) }}" .stat-content h3 {
class="btn btn-sm btn-primary ms-2"> font-size: 2rem;
Open Chat font-weight: 800;
</a> margin: 0 0 0.125rem 0;
</div> color: #1e293b;
}
.stat-content p {
margin: 0;
color: #64748b;
font-size: 0.9rem;
font-weight: 500;
}
.tickets-container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(15px);
border-radius: var(--border-radius);
padding: 1.5rem;
box-shadow: var(--card-shadow);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.tickets-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 1px solid #f1f5f9;
}
.tickets-title {
font-size: 1.4rem;
font-weight: 700;
color: #1e293b;
display: flex;
align-items: center;
gap: 0.5rem;
}
.tickets-count {
background: var(--primary-gradient);
color: white;
padding: 0.2rem 0.6rem;
border-radius: 16px;
font-size: 0.8rem;
font-weight: 600;
}
/* ========= COMPACT TICKET CARD ========= */
.ticket-item {
background: white;
border-radius: 10px;
padding: 0.75rem 1rem;
margin-bottom: 0.6rem;
border: 1px solid #f1f5f9;
transition: var(--transition);
position: relative;
overflow: hidden;
}
.ticket-item::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--primary-gradient);
transform: scaleY(0);
transition: var(--transition);
}
.ticket-item:hover {
transform: translateY(-2px);
box-shadow: var(--card-shadow-hover);
border-color: rgba(102, 126, 234, 0.15);
}
.ticket-item:hover::before {
transform: scaleY(1);
}
.ticket-header {
display: flex;
align-items: flex-start;
gap: 0.6rem;
margin-bottom: 0.5rem;
}
.ticket-avatar {
width: 40px;
height: 40px;
border-radius: 10px;
background: var(--info-gradient);
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
font-weight: 700;
color: white;
flex-shrink: 0;
box-shadow: 0 6px 16px rgba(86, 204, 242, 0.25);
position: relative;
overflow: hidden;
}
.ticket-avatar::after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
transition: left 0.5s;
}
.ticket-item:hover .ticket-avatar::after {
left: 100%;
}
.ticket-content {
flex: 1;
min-width: 0;
}
.ticket-name {
font-size: 0.95rem;
font-weight: 700;
color: #1e293b;
margin: 0 0 0.15rem 0;
display: flex;
align-items: center;
gap: 0.4rem;
}
.unread-count {
background: #ef4444;
color: white;
min-width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.7rem;
font-weight: 700;
box-shadow: 0 3px 10px rgba(239, 68, 68, 0.4);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.ticket-preview {
color: #64748b;
font-size: 0.8rem;
line-height: 1.4;
margin-bottom: 0.25rem;
display: flex;
align-items: center;
gap: 0.4rem;
}
.ticket-time {
font-size: 0.75rem;
color: #94a3b8;
display: flex;
align-items: center;
gap: 0.25rem;
}
.ticket-time svg {
width: 12px;
height: 12px;
flex-shrink: 0;
}
.ticket-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 0.6rem;
border-top: 1px dashed #f1f5f9;
margin-top: 0.4rem;
}
.status-badge {
padding: 0.25rem 0.7rem;
border-radius: 50px;
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.3px;
}
.status-open { background: var(--success-gradient); color: white; }
.status-closed{ background: var(--danger-gradient); color: white; }
.chat-btn {
background: var(--primary-gradient);
color: white;
border: none;
padding: 0.45rem 0.9rem;
border-radius: 25px;
font-weight: 600;
font-size: 0.8rem;
display: flex;
align-items: center;
gap: 0.4rem;
text-decoration: none;
transition: var(--transition);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.25);
}
.chat-btn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.35);
color: white;
}
.chat-btn::after {
content: '→';
transition: margin-left 0.3s ease;
margin-left: 0;
}
.chat-btn:hover::after {
margin-left: 0.4rem;
}
.empty-state {
text-align: center;
padding: 2.5rem 1.5rem;
background: rgba(255, 255, 255, 0.6);
border-radius: var(--border-radius);
border: 2px dashed #e2e8f0;
}
.empty-icon {
font-size: 4rem;
margin-bottom: 1.5rem;
display: block;
animation: float 3s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-15px); }
}
.empty-title {
font-size: 1.4rem;
font-weight: 700;
color: #1e293b;
margin-bottom: 0.75rem;
}
.empty-subtitle {
color: #64748b;
font-size: 1rem;
margin-bottom: 1.5rem;
line-height: 1.5;
}
.ticket-id {
background: rgba(102, 126, 234, 0.1);
color: #667eea;
padding: 0.2rem 0.75rem;
border-radius: 16px;
font-size: 0.75rem;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 0.25rem;
}
.new-message-dot {
position: absolute;
top: 0.75rem;
right: 0.75rem;
width: 10px;
height: 10px;
background: #ef4444;
border-radius: 50%;
border: 2px solid white;
box-shadow: 0 0 0 2px rgba(239, 68, 68, 0.3);
animation: blink 1.5s infinite;
z-index: 10;
}
@keyframes blink {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.5; transform: scale(1.15); }
}
@media (max-width: 768px) {
.chat-dashboard {
padding: 1rem;
}
.stats-grid {
grid-template-columns: 1fr;
gap: 0.75rem;
}
.ticket-header {
flex-direction: column;
gap: 0.75rem;
}
.ticket-footer {
flex-direction: column;
gap: 0.75rem;
align-items: stretch;
}
.chat-btn {
justify-content: center;
}
}
.tickets-list {
max-height: 55vh;
overflow-y: auto;
padding-right: 0.4rem;
}
.tickets-list::-webkit-scrollbar {
width: 5px;
}
.tickets-list::-webkit-scrollbar-track {
background: #f1f5f9;
border-radius: 8px;
}
.tickets-list::-webkit-scrollbar-thumb {
background: var(--primary-gradient);
border-radius: 8px;
}
</style>
<div class="chat-dashboard">
<!-- Header -->
<div class="dashboard-header">
<h1 class="dashboard-title">Live Chat Dashboard</h1>
<p class="dashboard-subtitle">
Monitor customer conversations with real-time updates
</p>
</div> </div>
</li> <!-- Stats Cards -->
<div class="stats-grid">
@endforeach <div class="stat-card">
<div class="stat-icon stat-total">💬</div>
</ul> <div class="stat-content">
@endif <h3>{{ $tickets->count() }}</h3>
<p>Total Conversations</p>
</div>
</div> </div>
<div class="stat-card">
<div class="stat-icon stat-open">📈</div>
<div class="stat-content">
<h3>{{ $tickets->where('status', 'open')->count() }}</h3>
<p>Active Tickets</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon stat-closed"></div>
<div class="stat-content">
<h3>{{ $tickets->where('status', 'closed')->count() }}</h3>
<p>Resolved Tickets</p>
</div>
</div>
</div>
<!-- Tickets Container -->
<div class="tickets-container">
<div class="tickets-header">
<div>
<h2 class="tickets-title">
📋 Active Conversations
<span class="tickets-count">{{ $tickets->count() }}</span>
</h2>
</div>
</div>
@if($tickets->count() === 0)
<!-- Empty State -->
<div class="empty-state">
<div class="empty-icon">💬</div>
<h3 class="empty-title">No Active Conversations</h3>
<p class="empty-subtitle">
Customer conversations will appear here with real-time notifications.
</p>
<div class="ticket-id">Ready for support requests</div>
</div>
@else
<!-- Tickets List -->
<div class="tickets-list">
@foreach($tickets as $ticket)
<div class="ticket-item" data-ticket-id="{{ $ticket->id }}">
@if($ticket->unread_count > 0)
<div class="new-message-dot"></div>
@endif
<div class="ticket-header">
<div class="ticket-avatar">
{{ strtoupper(substr($ticket->user->customer_name ?? $ticket->user->name, 0, 1)) }}
</div>
<div class="ticket-content">
<div class="ticket-name">
{{ $ticket->user->customer_name ?? $ticket->user->name }}
@if($ticket->unread_count > 0)
<span id="badge-{{ $ticket->id }}" class="unread-count">
{{ $ticket->unread_count }}
</span>
@endif
</div>
@php
$lastMsg = $ticket->messages()->latest()->first();
@endphp
<div class="ticket-preview">
@if($lastMsg)
@if($lastMsg->message)
{{ Str::limit($lastMsg->message, 55) }}
@elseif(Str::startsWith($lastMsg->file_type, 'image'))
📷 Photo shared
@else
📎 File attached
@endif
@else
<em>Conversation started</em>
@endif
</div>
@if($lastMsg)
<div class="ticket-time">
<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 16">
<path d="M8 3.5a.5.5 0 0 0-1 0V9a.5.5 0 0 0 .252.434l3.5 2a.5.5 0 0 0 .496-.868L8 8.71V3.5z"/>
<path d="M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16zm7-8A7 7 0 1 1 1 8a7 7 0 0 1 14 0z"/>
</svg>
{{ $lastMsg->created_at->diffForHumans() }}
</div>
@endif
</div>
<div class="ticket-id">#{{ $ticket->id }}</div>
</div>
<div class="ticket-footer">
<span class="status-badge status-{{ $ticket->status }}">
{{ ucfirst($ticket->status) }}
</span>
<a href="{{ route('admin.chat.open', $ticket->id) }}" class="chat-btn">
Open Chat
</a>
</div>
</div>
@endforeach
</div>
@endif
</div> </div>
</div> </div>
@endsection
@section('scripts') @section('scripts')
<script> <script>
// ------------------------------- // -------------------------------
@@ -135,7 +644,3 @@ waitForEcho(() => {
}); });
</script> </script>
@endsection @endsection
@endsection

File diff suppressed because it is too large Load Diff

View File

@@ -333,6 +333,13 @@ body, .container-fluid {
border-color: #f59e0b !important; border-color: #f59e0b !important;
} }
.badge-loading {
background: linear-gradient(135deg, #e3f2fd, #90caf9) !important;
color: #1565c0 !important;
border-color: #2196f3 !important;
width: 110px;
}
/* In Transit Status - SAME SIZE WITH TRUCK ICON */ /* In Transit Status - SAME SIZE WITH TRUCK ICON */
.badge-in_transit { .badge-in_transit {
background: linear-gradient(135deg, #dbeafe, #93c5fd) !important; background: linear-gradient(135deg, #dbeafe, #93c5fd) !important;
@@ -1101,6 +1108,7 @@ body, .container-fluid {
break-inside: avoid; break-inside: avoid;
} }
} }
</style> </style>
<div class="container-fluid py-3"> <div class="container-fluid py-3">

View File

@@ -13,48 +13,55 @@
<div class="d-flex justify-content-between align-items-start"> <div class="d-flex justify-content-between align-items-start">
<div> <div>
<h4 class="fw-bold mb-0">Order Details</h4> <h4 class="fw-bold mb-0">Order Details</h4>
@php
$status = strtolower($order->status ?? '');
@endphp
<small class="text-muted">Detailed view of this shipment order</small> <small class="text-muted">Detailed view of this shipment order</small>
</div> </div>
{{-- ADD ITEM --}} {{-- ADD ITEM --}}
@can('order.create') @can('order.create')
<button class="btn btn-add-item" data-bs-toggle="modal" data-bs-target="#addItemModal"> @if($status === 'pending')
<i class="fas fa-plus-circle me-2"></i>Add New Item <button class="btn btn-add-item" data-bs-toggle="modal" data-bs-target="#addItemModal">
</button> <i class="fas fa-plus-circle me-2"></i>Add New Item
</button>
@endif
@endcan @endcan
<a href="{{ route('admin.dashboard') }}" class="btn-close"></a> <a href="{{ route('admin.dashboard') }}" class="btn-close"></a>
</div> </div>
<!-- {{-- ACTION BUTTONS --}} <!-- {{-- ACTION BUTTONS --}}
<div class="mt-3 d-flex gap-2">-->
<div class="mt-3 d-flex gap-2"> <div class="mt-3 d-flex gap-2">
{{-- Edit Order --}}
@if($status === 'pending')
<button class="btn btn-edit-order"
onclick="document.getElementById('editOrderForm').style.display='block'">
<i class="fas fa-edit me-2"></i>Edit Order
</button>
@endif
{{-- Delete Order --}}
@if($status === 'pending')
<form action="{{ route('admin.orders.destroy', $order->id) }}"
method="POST"
onsubmit="return confirm('Delete this entire order?')">
@csrf
@method('DELETE')
<button class="btn btn-delete-order">
<i class="fas fa-trash-alt me-2"></i>Delete Order
</button>
</form>
@endif
</div>
{{-- EDIT ORDER --}} -->
<!-- @if($order->status === 'pending')
<button class="btn btn-edit-order" onclick="document.getElementById('editOrderForm').style.display='block'">
<i class="fas fa-edit me-2"></i>Edit Order
</button>
@else
<button class="btn btn-edit-order" disabled><i class="fas fa-edit me-2"></i>Edit Order</button>
@endif -->
<!-- {{-- DELETE ORDER --}}
@if($order->status === 'pending')
<form action="{{ route('admin.orders.destroy', $order->id) }}"
method="POST"
onsubmit="return confirm('Delete this entire order?')">
@csrf
@method('DELETE')
<button class="btn btn-delete-order">
<i class="fas fa-trash-alt me-2"></i>Delete Order
</button>
</form>
@endif -->
<!-- </div> -->
<hr> <hr>
{{-- EDIT ORDER FORM --}} {{-- EDIT ORDER FORM --}}
@@ -190,32 +197,33 @@
<td>{{ $item->ttl_kg }}</td> <td>{{ $item->ttl_kg }}</td>
<td>{{ $item->shop_no }}</td> <td>{{ $item->shop_no }}</td>
<td class="d-flex justify-content-center gap-2"> <td class="d-flex justify-content-center gap-2">
@if($status === 'pending')
{{-- EDIT BUTTON --}}
@can('order.edit')
<button type="button"
class="btn btn-sm btn-edit-item"
data-bs-toggle="modal"
data-bs-target="#editItemModal{{ $item->id }}">
<i class="fas fa-edit"></i>
</button>
@endcan
{{-- EDIT BUTTON --}} {{-- DELETE BUTTON --}}
@can('order.edit') @can('order.delete')
<button <form action="{{ route('admin.orders.deleteItem', $item->id) }}"
type="button" method="POST"
class="btn btn-sm btn-edit-item" onsubmit="return confirm('Delete this item?')">
data-bs-toggle="modal" @csrf
data-bs-target="#editItemModal{{ $item->id }}"> @method('DELETE')
<i class="fas fa-edit"></i> <button type="submit" class="btn btn-sm btn-delete-item">
</button> <i class="fas fa-trash"></i>
@endcan </button>
</form>
@endcan
@endif
</td>
@can('order.delete')
{{-- DELETE BUTTON --}}
<form action="{{ route('admin.orders.deleteItem', $item->id) }}"
method="POST"
onsubmit="return confirm('Delete this item?')">
@csrf
@method('DELETE')
<button class="btn btn-sm btn-delete-item">
<i class="fas fa-trash"></i>
</button>
</form>
@endcan
</td>
</tr> </tr>
@endforeach @endforeach
@@ -617,7 +625,7 @@ function fillFormFromDeleted(item) {
box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3); box-shadow: 0 4px 15px 0 rgba(102, 126, 234, 0.3);
position: relative; position: relative;
overflow: hidden; overflow: hidden;
margin-right: -800px; margin-right: -650px;
} }
.btn-add-item:hover { .btn-add-item:hover {

View File

@@ -1297,9 +1297,10 @@
</td> </td>
<td>{{ \Carbon\Carbon::parse($ship->shipment_date)->format('d M Y') }}</td> <td>{{ \Carbon\Carbon::parse($ship->shipment_date)->format('d M Y') }}</td>
<td> <td>
<button type="button" class="btn-eye" onclick="openShipmentDetails({{ $ship->id }})" title="View Shipment"> <a href="{{ route('admin.shipments.dummy', $ship->id) }}"
class="btn-view-details">
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</button> </a>
</td> </td>
<td> <td>
@@ -1596,7 +1597,7 @@ 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>
<a href="{{ route('admin.shipments.dummy', $ship->id) }}" <a href="/admin/shipment/dummy/${shipment.id}"
class="btn-view-details"> class="btn-view-details">
<i class="bi bi-eye"></i> <i class="bi bi-eye"></i>
</a> </a>
@@ -1665,6 +1666,8 @@ function openShipmentDetails(id) {
.then(data => { .then(data => {
// Format date properly // Format date properly
const shipmentDate = new Date(data.shipment.shipment_date); const shipmentDate = new Date(data.shipment.shipment_date);
// <div class="shipment-info-value">${data.shipment.shipment_id}</div>
const formattedDate = shipmentDate.toLocaleDateString('en-GB', { const formattedDate = shipmentDate.toLocaleDateString('en-GB', {
day: '2-digit', day: '2-digit',
month: 'short', month: 'short',

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,65 +1,896 @@
@extends('admin.layouts.app') @extends('admin.layouts.app')
@section('page-title', 'Account Dashboard') @section('page-title', 'Staff Management Dashboard')
@section('content') @section('content')
<style> <style>
.top-bar { display:flex; justify-content:space-between; align-items:center; margin-bottom:1rem; } :root {
.card { background:#fff; border:1px solid #e4e4e4; border-radius:6px; padding:1rem; box-shadow:0 1px 3px rgba(0,0,0,.03); } --primary: #4361ee;
table { width:100%; border-collapse:collapse; } --primary-dark: #3a56d4;
th, td { padding:.6rem .75rem; border-bottom:1px solid #f1f1f1; text-align:left; } --secondary: #f72585;
.btn { padding:.45rem .75rem; border-radius:4px; border:1px solid #ccc; background:#f7f7f7; cursor:pointer; } --success: #4cc9f0;
.btn.primary { background:#0b74de; color:#fff; border-color:#0b74de; } --warning: #f8961e;
.actions a { margin-right:.5rem; color:#0b74de; text-decoration:none; } --danger: #e63946;
.muted { color:#666; font-size:.95rem; } --light: #f8f9fa;
--dark: #212529;
--gray: #6c757d;
--border: #e2e8f0;
--card-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
--hover-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--gradient-primary: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
/* Search Bar - Similar to Shipment */
.search-staff-bar {
display: flex;
align-items: center;
gap: 15px;
padding: 20px;
background: var(--gradient-primary);
border-radius: 16px;
box-shadow: var(--card-shadow);
flex-wrap: wrap;
margin-bottom: 30px;
color: white;
position: relative;
overflow: hidden;
}
.search-staff-bar::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255,255,255,0.1);
z-index: 0;
}
.search-staff-bar > * {
position: relative;
z-index: 1;
}
.search-staff-bar input,
.search-staff-bar select {
padding: 12px 16px;
border: 1px solid rgba(255,255,255,0.2);
border-radius: 10px;
flex: 1;
min-width: 150px;
background: rgba(255,255,255,0.9);
font-weight: 500;
transition: all 0.3s ease;
color: var(--dark);
}
.search-staff-bar input:focus,
.search-staff-bar select:focus {
background: white;
box-shadow: 0 0 0 3px rgba(255,255,255,0.3);
outline: none;
}
.btn-add-staff {
background: rgba(255,255,255,0.2);
backdrop-filter: blur(10px);
color: white;
border: 1px solid rgba(255,255,255,0.3);
padding: 12px 24px;
border-radius: 10px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
white-space: nowrap;
font-weight: 600;
text-decoration: none;
}
.btn-add-staff:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
}
.search-icon {
font-size: 20px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
}
.user-icon {
font-size: 18px;
}
@media (max-width: 768px) {
.search-staff-bar {
flex-direction: column;
align-items: stretch;
}
.search-staff-bar input,
.search-staff-bar select {
width: 100%;
}
}
/* Card Styles - Same as Shipment */
.card {
border: none;
border-radius: 16px;
box-shadow: var(--card-shadow);
transition: all 0.3s ease;
overflow: hidden;
}
.card:hover {
transform: translateY(-5px);
box-shadow: var(--hover-shadow);
}
.card-header {
background: var(--gradient-primary);
color: white;
border: none;
padding: 20px 25px;
border-radius: 16px 16px 0 0 !important;
}
.card-header h5 {
margin: 0;
font-weight: 700;
display: flex;
align-items: center;
gap: 10px;
}
/* Table Styles - Similar to Shipment */
.table-responsive {
border-radius: 0 0 16px 16px;
overflow-x: auto;
}
.table {
margin: 0;
border-collapse: separate;
border-spacing: 0;
width: 100%;
padding: 0;
}
.table thead th {
background: #f8f9fa;
border: none;
padding: 16px 12px;
font-weight: 700;
color: var(--dark);
text-align: left;
vertical-align: middle;
border-bottom: 2px solid var(--border);
position: relative;
}
.table tbody tr {
transition: all 0.3s ease;
}
.table tbody tr:hover {
background-color: #f8f9ff;
transform: scale(1.01);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.table tbody td {
padding: 14px 12px;
text-align: left;
vertical-align: middle;
border-bottom: 1px solid var(--border);
font-weight: 500;
}
.table tbody tr:last-child td {
border-bottom: none;
}
/* Status Badges - Similar Style */
.badge {
padding: 6px 12px !important;
border-radius: 20px !important;
font-weight: 600 !important;
font-size: 12px !important;
border: 2px solid transparent !important;
min-width: 80px !important;
text-align: center !important;
display: inline-block !important;
line-height: 1.2 !important;
}
.badge-active {
background: linear-gradient(135deg, #d1fae5, #a7f3d0) !important;
color: #065f46 !important;
border-color: #10b981 !important;
}
.badge-inactive {
background: linear-gradient(135deg, #fecaca, #fca5a5) !important;
color: #991b1b !important;
border-color: #ef4444 !important;
}
.badge-pending {
background: linear-gradient(135deg, #fef3c7, #fde68a) !important;
color: #92400e !important;
border-color: #f59e0b !important;
}
/* Employee ID Badge - Similar to Shipment ID */
.employee-id-badge {
font-family: 'Courier New', monospace;
background: rgba(67, 97, 238, 0.1);
padding: 4px 8px;
border-radius: 6px;
font-size: 0.85rem;
color: var(--primary);
border: 1px solid rgba(67, 97, 238, 0.2);
display: inline-block;
}
/* Action Buttons - Similar Style */
.action-buttons {
display: flex;
gap: 8px;
}
.btn-action {
padding: 6px 12px;
border-radius: 8px;
font-size: 13px;
font-weight: 600;
text-decoration: none;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 5px;
border: none;
cursor: pointer;
}
.btn-edit {
background: linear-gradient(135deg, #4cc9f0, #4361ee);
color: white;
}
.btn-edit:hover {
background: linear-gradient(135deg, #38bdf8, #3a56d4);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(76, 201, 240, 0.3);
}
.btn-delete {
background: linear-gradient(135deg, #f87171, #ef4444);
color: white;
}
.btn-delete:hover {
background: linear-gradient(135deg, #ef4444, #dc2626);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
}
/* Success Message - Similar Style */
.alert-success {
background: linear-gradient(135deg, #e6ffed, #d1f7e5);
border: 1px solid #b6f0c6;
border-left: 4px solid var(--success);
color: #0f5132;
padding: 1rem 1.25rem;
border-radius: 10px;
margin-bottom: 1.5rem;
display: flex;
align-items: center;
gap: 0.75rem;
}
.alert-success:before {
content: '✓';
background: var(--success);
color: white;
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--gray);
}
.empty-state:before {
content: '👤';
font-size: 3rem;
display: block;
margin-bottom: 1rem;
opacity: 0.5;
}
/* Role Badges */
.role-badge {
padding: 4px 8px;
border-radius: 6px;
font-size: 11px;
font-weight: 600;
background: rgba(67, 97, 238, 0.1);
color: var(--primary);
border: 1px solid rgba(67, 97, 238, 0.2);
}
/* Stats Cards - Similar to Shipment Totals */
.stats-cards {
display: flex;
gap: 20px;
margin-bottom: 30px;
flex-wrap: wrap;
}
.stat-card {
flex: 1;
min-width: 200px;
background: white;
padding: 20px;
border-radius: 12px;
box-shadow: var(--card-shadow);
display: flex;
align-items: center;
gap: 15px;
transition: all 0.3s ease;
}
.stat-card:hover {
transform: translateY(-3px);
box-shadow: var(--hover-shadow);
}
.stat-icon {
width: 50px;
height: 50px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
}
.stat-icon.total {
background: linear-gradient(135deg, #e6f3ff, #c2d9ff);
color: var(--primary);
}
.stat-icon.active {
background: linear-gradient(135deg, #d1fae5, #a7f3d0);
color: #10b981;
}
.stat-content h3 {
font-size: 1.8rem;
font-weight: 700;
margin: 0;
color: var(--dark);
}
.stat-content p {
color: var(--gray);
margin: 4px 0 0 0;
font-size: 0.875rem;
}
/* Pagination - Same as Shipment */
.pagination-container {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 12px 25px;
border-top: 1px solid #eef3fb;
}
.pagination-info {
font-size: 13px;
color: #9ba5bb;
font-weight: 600;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 8px;
}
.pagination-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 8px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 32px;
}
.pagination-btn:hover:not(:disabled) {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-btn:disabled {
background: #f8fafc;
color: #cbd5e0;
border-color: #e2e8f0;
cursor: not-allowed;
opacity: 0.6;
}
.pagination-page-btn {
background: #fff;
border: 1px solid #e3eaf6;
color: #1a2951;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
min-width: 36px;
text-align: center;
}
.pagination-page-btn:hover {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-page-btn.active {
background: #1a2951;
color: white;
border-color: #1a2951;
}
.pagination-pages {
display: flex;
gap: 4px;
align-items: center;
}
.pagination-ellipsis {
color: #9ba5bb;
font-size: 13px;
padding: 0 4px;
}
@media (max-width: 768px) {
.stats-cards {
flex-direction: column;
}
.stat-card {
min-width: 100%;
}
.pagination-container {
flex-direction: column;
gap: 10px;
align-items: stretch;
}
.pagination-controls {
justify-content: center;
}
}
</style> </style>
<div class="top-bar"> <div class="container-fluid py-4">
<h2>Staff</h2>
<a href="{{ route('admin.staff.create') }}" class="btn primary">Add Staff</a> @if(session('success'))
<div class="alert-success">
{{ session('success') }}
</div>
@endif
{{-- Stats Cards --}}
<div class="stats-cards">
<div class="stat-card">
<div class="stat-icon total">
👥
</div>
<div class="stat-content">
<h3>{{ $staff->count() }}</h3>
<p>Total Staff</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon active">
</div>
<div class="stat-content">
<h3>{{ $staff->where('status', 'active')->count() }}</h3>
<p>Active Staff</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon" style="background: linear-gradient(135deg, #f3e8ff, #e9d5ff); color: #8b5cf6;">
👑
</div>
<div class="stat-content">
<h3>{{ $staff->unique('role')->count() }}</h3>
<p>Unique Roles</p>
</div>
</div>
</div>
{{-- Search Bar --}}
<div class="search-staff-bar">
<span class="search-icon">🔍</span>
<input type="text" id="searchInput" placeholder="Search by name, email, or employee ID...">
<div class="status-filter-container">
<select id="statusFilter" class="status-filter-select">
<option value="all">All Status</option>
<option value="active">Active</option>
<option value="inactive">Inactive</option>
<option value="pending">Pending</option>
</select>
</div>
<select id="roleFilter">
<option value="all">All Roles</option>
@foreach($staff->unique('role')->pluck('role') as $role)
@if($role)
<option value="{{ $role }}">{{ ucfirst($role) }}</option>
@endif
@endforeach
</select>
<a href="{{ route('admin.staff.create') }}" class="btn-add-staff">
<span class="user-icon"></span>
Add Staff
</a>
</div>
{{-- Staff Table --}}
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-people me-2"></i> Staff Management</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<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 id="staffTableBody">
@php
$totalStaff = count($staff);
@endphp
@forelse($staff as $s)
<tr class="staff-row" data-status="{{ $s->status }}" data-role="{{ $s->role ?? '' }}">
<td class="fw-bold">{{ $totalStaff - $loop->index }}</td>
<td>
<span class="employee-id-badge">{{ $s->employee_id }}</span>
</td>
<td>
<div class="d-flex align-items-center gap-2">
<div style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold;">
{{ strtoupper(substr($s->name, 0, 1)) }}
</div>
<span class="fw-medium">{{ $s->name }}</span>
</div>
</td>
<td>{{ $s->email }}</td>
<td>{{ $s->phone ?? '-' }}</td>
<td>
@if($s->role)
<span class="role-badge">{{ $s->role }}</span>
@else
<span class="text-muted">-</span>
@endif
</td>
<td>
<span class="badge badge-{{ $s->status }}">
{{ ucfirst($s->status) }}
</span>
</td>
<td>
<div class="action-buttons">
<a href="{{ route('admin.staff.edit', $s->id) }}" class="btn-action btn-edit">
<i class="bi bi-pencil"></i>
Edit
</a>
<form action="{{ route('admin.staff.destroy', $s->id) }}" method="POST" style="display:inline" onsubmit="return confirm('Are you sure you want to delete this staff member?')">
@csrf
@method('DELETE')
<button type="submit" class="btn-action btn-delete">
<i class="bi bi-trash"></i>
Delete
</button>
</form>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="8" class="text-center py-5 text-muted">
<div class="empty-state">
No staff members found
</div>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
{{-- Pagination --}}
<div class="pagination-container">
<div class="pagination-info" id="pageInfo">
Showing 1 to {{ $staff->count() }} of {{ $staff->count() }} entries
</div>
<div class="pagination-controls">
<button class="pagination-btn" id="prevPageBtn" title="Previous page" disabled>
<i class="bi bi-chevron-left"></i>
</button>
<div class="pagination-pages" id="paginationPages">
<button class="pagination-page-btn active">1</button>
</div>
<button class="pagination-btn" id="nextPageBtn" title="Next page" disabled>
<i class="bi bi-chevron-right"></i>
</button>
</div>
</div>
</div>
</div>
</div> </div>
<div class="card"> <script>
@if(session('success')) // Pagination state
<div style="padding:.5rem; background:#e6ffed; border:1px solid #b6f0c6; margin-bottom:1rem;">{{ session('success') }}</div> let currentPage = 1;
@endif const itemsPerPage = 10;
let allStaff = @json($staff);
let filteredStaff = [...allStaff];
<table> // Initialize on page load
<thead> document.addEventListener('DOMContentLoaded', function() {
<tr> renderTable();
<th>#</th> updatePaginationControls();
<th>Employee ID</th>
<th>Name</th> // Bind pagination events
<th>Email</th> document.getElementById('prevPageBtn').addEventListener('click', goToPreviousPage);
<th>Phone</th> document.getElementById('nextPageBtn').addEventListener('click', goToNextPage);
<th>Role</th>
<th>Status</th> // Filter functionality
<th>Actions</th> const statusFilter = document.getElementById('statusFilter');
</tr> const searchInput = document.getElementById('searchInput');
</thead> const roleFilter = document.getElementById('roleFilter');
<tbody>
@forelse($staff as $s) function filterStaff() {
<tr> const selectedStatus = statusFilter.value;
<td>{{ $s->id }}</td> const searchTerm = searchInput.value.toLowerCase();
<td class="muted">{{ $s->employee_id }}</td> const selectedRole = roleFilter.value;
<td>{{ $s->name }}</td>
<td>{{ $s->email }}</td> filteredStaff = allStaff.filter(staff => {
<td>{{ $s->phone }}</td> let include = true;
<td>{{ $s->role ?? '-' }}</td>
<td>{{ ucfirst($s->status) }}</td> // Status filter
<td class="actions"> if (selectedStatus !== 'all' && staff.status !== selectedStatus) {
<a href="{{ route('admin.staff.edit', $s->id) }}">Edit</a> include = false;
<form action="{{ route('admin.staff.destroy', $s->id) }}" method="POST" style="display:inline" onsubmit="return confirm('Delete this staff?')"> }
@csrf
@method('DELETE') // Role filter
<button class="btn" type="submit">Delete</button> if (selectedRole !== 'all') {
</form> const staffRole = staff.role || '';
</td> if (staffRole.toLowerCase() !== selectedRole.toLowerCase()) {
</tr> include = false;
@empty }
<tr><td colspan="8" class="muted">No staff found.</td></tr> }
@endforelse
</tbody> // Search filter
</table> if (searchTerm) {
</div> const matchesSearch =
staff.name.toLowerCase().includes(searchTerm) ||
staff.email.toLowerCase().includes(searchTerm) ||
(staff.employee_id && staff.employee_id.toLowerCase().includes(searchTerm)) ||
(staff.phone && staff.phone.toLowerCase().includes(searchTerm));
if (!matchesSearch) include = false;
}
return include;
});
currentPage = 1;
renderTable();
updatePaginationControls();
}
// Event listeners for filters
statusFilter.addEventListener('change', filterStaff);
searchInput.addEventListener('input', filterStaff);
roleFilter.addEventListener('change', filterStaff);
// Initialize filter
filterStaff();
});
// Pagination Functions
function goToPreviousPage() {
if (currentPage > 1) {
currentPage--;
renderTable();
updatePaginationControls();
}
}
function goToNextPage() {
const totalPages = Math.ceil(filteredStaff.length / itemsPerPage);
if (currentPage < totalPages) {
currentPage++;
renderTable();
updatePaginationControls();
}
}
function updatePaginationControls() {
const totalPages = Math.ceil(filteredStaff.length / itemsPerPage);
const prevBtn = document.getElementById('prevPageBtn');
const nextBtn = document.getElementById('nextPageBtn');
const pageInfo = document.getElementById('pageInfo');
const paginationPages = document.getElementById('paginationPages');
prevBtn.disabled = currentPage === 1;
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
// Update page info text
const startIndex = (currentPage - 1) * itemsPerPage + 1;
const endIndex = Math.min(currentPage * itemsPerPage, filteredStaff.length);
pageInfo.textContent = `Showing ${startIndex} to ${endIndex} of ${filteredStaff.length} entries`;
// Generate page numbers
paginationPages.innerHTML = '';
if (totalPages <= 7) {
for (let i = 1; i <= totalPages; i++) {
addPageButton(i, paginationPages);
}
} else {
addPageButton(1, paginationPages);
if (currentPage > 3) {
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
}
const start = Math.max(2, currentPage - 1);
const end = Math.min(totalPages - 1, currentPage + 1);
for (let i = start; i <= end; i++) {
addPageButton(i, paginationPages);
}
if (currentPage < totalPages - 2) {
paginationPages.innerHTML += '<span class="pagination-ellipsis">...</span>';
}
addPageButton(totalPages, paginationPages);
}
}
function addPageButton(pageNumber, container) {
const button = document.createElement('button');
button.className = 'pagination-page-btn';
if (pageNumber === currentPage) {
button.classList.add('active');
}
button.textContent = pageNumber;
button.addEventListener('click', () => {
currentPage = pageNumber;
renderTable();
updatePaginationControls();
});
container.appendChild(button);
}
// Render Table
function renderTable() {
const tbody = document.getElementById('staffTableBody');
if (filteredStaff.length === 0) {
tbody.innerHTML = `
<tr>
<td colspan="8" class="text-center py-5 text-muted">
<div class="empty-state">
No staff members found matching your criteria
</div>
</td>
</tr>
`;
return;
}
const startIndex = (currentPage - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const paginatedItems = filteredStaff.slice(startIndex, endIndex);
const sortedItems = [...paginatedItems].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
tbody.innerHTML = '';
sortedItems.forEach((staff, index) => {
const displayIndex = filteredStaff.length - (startIndex + index);
const row = document.createElement('tr');
row.className = 'staff-row';
row.setAttribute('data-status', staff.status);
row.setAttribute('data-role', staff.role || '');
row.innerHTML = `
<td class="fw-bold">${displayIndex}</td>
<td>
<span class="employee-id-badge">${staff.employee_id}</span>
</td>
<td>
<div class="d-flex align-items-center gap-2">
<div style="width: 36px; height: 36px; border-radius: 50%; background: linear-gradient(135deg, #667eea, #764ba2); display: flex; align-items: center; justify-content: center; color: white; font-weight: bold;">
${staff.name ? staff.name.charAt(0).toUpperCase() : '?'}
</div>
<span class="fw-medium">${staff.name}</span>
</div>
</td>
<td>${staff.email}</td>
<td>${staff.phone || '-'}</td>
<td>
${staff.role ? `<span class="role-badge">${staff.role}</span>` : '<span class="text-muted">-</span>'}
</td>
<td>
<span class="badge badge-${staff.status}">
${staff.status.charAt(0).toUpperCase() + staff.status.slice(1)}
</span>
</td>
<td>
<div class="action-buttons">
<a href="/admin/staff/${staff.id}/edit" class="btn-action btn-edit">
<i class="bi bi-pencil"></i>
Edit
</a>
<form action="/admin/staff/${staff.id}" method="POST" style="display:inline" onsubmit="return confirm('Are you sure you want to delete this staff member?')">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" class="btn-action btn-delete">
<i class="bi bi-trash"></i>
Delete
</button>
</form>
</div>
</td>
`;
tbody.appendChild(row);
});
}
</script>
@endsection @endsection

File diff suppressed because it is too large Load Diff

View File

@@ -176,10 +176,20 @@ Route::prefix('admin')
Route::delete('/shipments/{id}', [ShipmentController::class, 'destroy']) Route::delete('/shipments/{id}', [ShipmentController::class, 'destroy'])
->name('admin.shipments.destroy'); ->name('admin.shipments.destroy');
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
->name('admin.shipments.dummy');
// web.php
Route::delete('/shipments/{shipment}/orders/{order}',
[ShipmentController::class, 'removeOrder']
)->name('admin.shipments.removeOrder');
Route::post('/shipments/{shipment}/add-orders',
[ShipmentController::class, 'addOrders']
)->name('admin.shipments.addOrders');
Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy']) Route::get('/shipment/dummy/{id}', [ShipmentController::class, 'dummy'])
->name('admin.shipments.dummy'); ->name('admin.shipments.dummy');
// --------------------------- // ---------------------------
// INVOICES // INVOICES
// --------------------------- // ---------------------------
@@ -207,8 +217,8 @@ Route::prefix('admin')
->name('admin.invoice.installment.delete'); ->name('admin.invoice.installment.delete');
// //Add New Invoice //Add New Invoice
// Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create'); Route::get('/admin/invoices/create', [InvoiceController::class, 'create'])->name('admin.invoices.create');
// --------------------------- // ---------------------------