feature: redesign active bookings page with stat cards, overdue indicators, search filter
This commit is contained in:
parent
89539f84ec
commit
c6d2d98cc8
@ -13,19 +13,38 @@ class BookingController extends Controller
|
|||||||
public function activeIndex(Request $request): \Illuminate\View\View
|
public function activeIndex(Request $request): \Illuminate\View\View
|
||||||
{
|
{
|
||||||
$parkingLotId = $request->get('parking_lot_id');
|
$parkingLotId = $request->get('parking_lot_id');
|
||||||
$parkingLots = ParkingLot::active()->get(['id', 'name']);
|
$search = $request->get('search');
|
||||||
|
$parkingLots = ParkingLot::active()->get(['id', 'name']);
|
||||||
|
|
||||||
$query = Booking::with('parkingLot')
|
$base = Booking::with('parkingLot')->where('status', 'active');
|
||||||
->where('status', 'active')
|
|
||||||
->orderBy('start_time', 'desc');
|
// Summary stats (always across all lots/filters)
|
||||||
|
$stats = [
|
||||||
|
'total' => (clone $base)->count(),
|
||||||
|
'walkin' => (clone $base)->where('source', 'walk_in')->count(),
|
||||||
|
'reservation' => (clone $base)->where('source', 'reservation')->count(),
|
||||||
|
'overdue' => (clone $base)->where('end_time', '<', now())->count(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Filtered query
|
||||||
|
$query = (clone $base)->orderBy('start_time', 'asc');
|
||||||
|
|
||||||
if ($parkingLotId) {
|
if ($parkingLotId) {
|
||||||
$query->where('parking_lot_id', $parkingLotId);
|
$query->where('parking_lot_id', $parkingLotId);
|
||||||
}
|
}
|
||||||
|
if ($search) {
|
||||||
|
$query->where(function ($q) use ($search) {
|
||||||
|
$q->where('vehicle_plate', 'like', "%{$search}%")
|
||||||
|
->orWhere('customer_name', 'like', "%{$search}%")
|
||||||
|
->orWhere('phone', 'like', "%{$search}%");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$activeBookings = $query->paginate(50);
|
$activeBookings = $query->paginate(50);
|
||||||
|
|
||||||
return view('admin.bookings.active', compact('activeBookings', 'parkingLots', 'parkingLotId'));
|
return view('admin.bookings.active', compact(
|
||||||
|
'activeBookings', 'parkingLots', 'parkingLotId', 'stats', 'search'
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function completeBooking(Request $request, Booking $booking): JsonResponse
|
public function completeBooking(Request $request, Booking $booking): JsonResponse
|
||||||
|
|||||||
@ -2,106 +2,322 @@
|
|||||||
@section('title', 'الحجوزات النشطة — دمشق باركينغ')
|
@section('title', 'الحجوزات النشطة — دمشق باركينغ')
|
||||||
@section('page-title', 'الحجوزات النشطة')
|
@section('page-title', 'الحجوزات النشطة')
|
||||||
|
|
||||||
|
@section('styles')
|
||||||
|
<style>
|
||||||
|
/* ── Stat cards ──────────────────────────────────────────────────── */
|
||||||
|
.stat-card {
|
||||||
|
background:#fff;
|
||||||
|
border-radius:.875rem;
|
||||||
|
padding:1.125rem 1.25rem;
|
||||||
|
box-shadow:0 2px 8px rgba(0,0,0,.05);
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:1rem;
|
||||||
|
border:1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
.stat-icon {
|
||||||
|
width:48px; height:48px;
|
||||||
|
border-radius:.75rem;
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
font-size:1.3rem;
|
||||||
|
flex-shrink:0;
|
||||||
|
}
|
||||||
|
.stat-value { font-size:1.6rem; font-weight:800; line-height:1; color:#0f172a; }
|
||||||
|
.stat-label { font-size:.75rem; color:#64748b; margin-top:.2rem; }
|
||||||
|
|
||||||
|
/* ── Filter bar ──────────────────────────────────────────────────── */
|
||||||
|
.filter-bar {
|
||||||
|
background:#fff;
|
||||||
|
border-radius:.875rem;
|
||||||
|
padding:1rem 1.25rem;
|
||||||
|
box-shadow:0 2px 8px rgba(0,0,0,.05);
|
||||||
|
border:1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Table enhancements ──────────────────────────────────────────── */
|
||||||
|
.booking-row { transition:background .15s; position:relative; }
|
||||||
|
.booking-row:hover { background:#f8fafc !important; }
|
||||||
|
.booking-row.is-overdue { background:rgba(239,68,68,.03); }
|
||||||
|
.booking-row.is-overdue td:first-child { border-inline-start:3px solid #ef4444; }
|
||||||
|
.booking-row.is-warning td:first-child { border-inline-start:3px solid #f59e0b; }
|
||||||
|
.booking-row.is-ok td:first-child { border-inline-start:3px solid #10b981; }
|
||||||
|
|
||||||
|
.plate-tag {
|
||||||
|
font-family:monospace;
|
||||||
|
font-size:1rem;
|
||||||
|
font-weight:800;
|
||||||
|
color:#0f172a;
|
||||||
|
letter-spacing:.04em;
|
||||||
|
background:#f8fafc;
|
||||||
|
border:1.5px solid #e2e8f0;
|
||||||
|
border-radius:.375rem;
|
||||||
|
padding:.2rem .6rem;
|
||||||
|
display:inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Duration bar */
|
||||||
|
.dur-bar-wrap { width:80px; height:5px; background:#e2e8f0; border-radius:3px; overflow:hidden; margin-top:.3rem; }
|
||||||
|
.dur-bar-fill { height:100%; border-radius:3px; transition:width .3s; }
|
||||||
|
|
||||||
|
/* Source badge */
|
||||||
|
.src-badge {
|
||||||
|
display:inline-flex; align-items:center; gap:.25rem;
|
||||||
|
font-size:.65rem; font-weight:700;
|
||||||
|
padding:.15em .55em; border-radius:20px;
|
||||||
|
margin-top:.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Time remaining pill */
|
||||||
|
.time-pill {
|
||||||
|
display:inline-flex; align-items:center; gap:.3rem;
|
||||||
|
padding:.28em .7em; border-radius:20px;
|
||||||
|
font-size:.75rem; font-weight:700;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@endsection
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
{{-- ── Header ──────────────────────────────────────────────────────────────── --}}
|
{{-- ── Page header ─────────────────────────────────────────────────────────── --}}
|
||||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3 mb-4">
|
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<h2 class="fw-800 mb-1" style="font-size:1.15rem;color:#0f172a;">الحجوزات النشطة</h2>
|
<h2 class="fw-800 mb-1" style="font-size:1.15rem;color:#0f172a;">الحجوزات النشطة</h2>
|
||||||
<p class="text-sm mb-0" style="color:#64748b;">
|
<p class="text-sm mb-0" style="color:#64748b;">
|
||||||
السيارات المسجّلة حالياً · يتجدد كل 30 ثانية
|
السيارات داخل المواقف حالياً
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex align-items-center gap-2">
|
<div class="d-flex align-items-center gap-2">
|
||||||
<span class="badge badge-soft-success fw-600" style="font-size:.82rem;padding:.4em .9em;">
|
|
||||||
<i class="bi bi-circle-fill me-1" style="font-size:.45rem;vertical-align:middle;"></i>
|
|
||||||
{{ $activeBookings->total() }} نشط
|
|
||||||
</span>
|
|
||||||
<span class="badge badge-soft-secondary text-xs" id="refresh-badge">تحديث بعد 30ث</span>
|
<span class="badge badge-soft-secondary text-xs" id="refresh-badge">تحديث بعد 30ث</span>
|
||||||
|
<button onclick="location.reload()"
|
||||||
|
class="btn btn-sm fw-600"
|
||||||
|
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||||
|
<i class="bi bi-arrow-clockwise me-1"></i>تحديث الآن
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- ── Filter ────────────────────────────────────────────────────────────────── --}}
|
{{-- ── Stat cards ───────────────────────────────────────────────────────────── --}}
|
||||||
<div class="card mb-4">
|
<div class="row g-3 mb-4">
|
||||||
<div class="card-body p-3">
|
<div class="col-sm-6 col-xl-3">
|
||||||
<div class="row align-items-end g-3">
|
<div class="stat-card">
|
||||||
<div class="col-md-5">
|
<div class="stat-icon" style="background:rgba(99,102,241,.1);">
|
||||||
<label class="form-label">فلترة حسب الموقف</label>
|
<i class="bi bi-car-front" style="color:#6366f1;"></i>
|
||||||
<select id="lotFilter" class="form-select form-select-sm">
|
|
||||||
<option value="">جميع المواقف</option>
|
|
||||||
@foreach($parkingLots as $lot)
|
|
||||||
<option value="{{ $lot->id }}" {{ request('parking_lot_id') == $lot->id ? 'selected' : '' }}>
|
|
||||||
{{ $lot->name }}
|
|
||||||
</option>
|
|
||||||
@endforeach
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
@if(request('parking_lot_id'))
|
<div>
|
||||||
<div class="col-auto">
|
<div class="stat-value">{{ $stats['total'] }}</div>
|
||||||
<a href="{{ route('admin.bookings.active') }}"
|
<div class="stat-label">إجمالي النشطة</div>
|
||||||
class="btn btn-sm fw-600"
|
</div>
|
||||||
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
</div>
|
||||||
<i class="bi bi-x me-1"></i>إلغاء الفلتر
|
</div>
|
||||||
</a>
|
<div class="col-sm-6 col-xl-3">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon" style="background:rgba(16,185,129,.1);">
|
||||||
|
<i class="bi bi-box-arrow-in-right" style="color:#10b981;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ $stats['walkin'] }}</div>
|
||||||
|
<div class="stat-label">دخول مباشر</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-xl-3">
|
||||||
|
<div class="stat-card">
|
||||||
|
<div class="stat-icon" style="background:rgba(14,165,233,.1);">
|
||||||
|
<i class="bi bi-calendar-check" style="color:#0ea5e9;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value">{{ $stats['reservation'] }}</div>
|
||||||
|
<div class="stat-label">حجوزات مسبقة</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6 col-xl-3">
|
||||||
|
<div class="stat-card" style="{{ $stats['overdue'] > 0 ? 'border-color:rgba(239,68,68,.25);background:rgba(239,68,68,.03);' : '' }}">
|
||||||
|
<div class="stat-icon" style="background:rgba(239,68,68,.1);">
|
||||||
|
<i class="bi bi-exclamation-triangle" style="color:#ef4444;"></i>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="stat-value" style="{{ $stats['overdue'] > 0 ? 'color:#ef4444;' : '' }}">
|
||||||
|
{{ $stats['overdue'] }}
|
||||||
|
</div>
|
||||||
|
<div class="stat-label">متأخرة عن الخروج</div>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{-- ── Table ────────────────────────────────────────────────────────────────── --}}
|
{{-- ── Filter bar ───────────────────────────────────────────────────────────── --}}
|
||||||
|
<div class="filter-bar mb-4">
|
||||||
|
<div class="row align-items-end g-3">
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label text-sm fw-600" style="color:#475569;">الموقف</label>
|
||||||
|
<select id="lotFilter" class="form-select form-select-sm" style="border-color:#e2e8f0;">
|
||||||
|
<option value="">جميع المواقف</option>
|
||||||
|
@foreach($parkingLots as $lot)
|
||||||
|
<option value="{{ $lot->id }}" {{ request('parking_lot_id') == $lot->id ? 'selected' : '' }}>
|
||||||
|
{{ $lot->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-4">
|
||||||
|
<label class="form-label text-sm fw-600" style="color:#475569;">بحث</label>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text" style="background:#fff;border-color:#e2e8f0;">
|
||||||
|
<i class="bi bi-search" style="color:#94a3b8;"></i>
|
||||||
|
</span>
|
||||||
|
<input type="text" id="searchInput"
|
||||||
|
class="form-control" style="border-color:#e2e8f0;"
|
||||||
|
placeholder="رقم اللوحة أو السائق..."
|
||||||
|
value="{{ $search ?? '' }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-auto d-flex gap-2">
|
||||||
|
<button onclick="applyFilters()"
|
||||||
|
class="btn btn-sm fw-600"
|
||||||
|
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;padding:.45rem 1rem;">
|
||||||
|
<i class="bi bi-funnel me-1"></i>تطبيق
|
||||||
|
</button>
|
||||||
|
@if(request('parking_lot_id') || request('search'))
|
||||||
|
<a href="{{ route('admin.bookings.active') }}"
|
||||||
|
class="btn btn-sm fw-600"
|
||||||
|
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;padding:.45rem 1rem;">
|
||||||
|
<i class="bi bi-x me-1"></i>مسح
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ── Bookings table ───────────────────────────────────────────────────────── --}}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
|
|
||||||
|
<div class="card-header d-flex align-items-center justify-content-between py-2">
|
||||||
|
<span class="fw-700 text-sm" style="color:#0f172a;">
|
||||||
|
<i class="bi bi-list-ul me-1" style="color:#6366f1;"></i>
|
||||||
|
قائمة الحجوزات
|
||||||
|
</span>
|
||||||
|
<span class="badge badge-soft-primary text-xs">
|
||||||
|
{{ $activeBookings->total() }} سجل
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
<table class="app-table w-100">
|
<table class="app-table w-100">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>رقم اللوحة</th>
|
<th>السيارة</th>
|
||||||
<th>السائق</th>
|
<th>السائق</th>
|
||||||
<th>الهاتف</th>
|
|
||||||
<th>الموقف</th>
|
<th>الموقف</th>
|
||||||
<th>وقت الدخول</th>
|
<th>وقت الدخول</th>
|
||||||
<th>وقت الخروج</th>
|
<th>المدة / الوضع</th>
|
||||||
<th>المدة</th>
|
<th class="text-center">إجراء</th>
|
||||||
<th class="text-center">إنهاء</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@forelse($activeBookings as $booking)
|
@forelse($activeBookings as $booking)
|
||||||
<tr id="row-{{ $booking->id }}">
|
@php
|
||||||
|
$elapsedMin = $booking->start_time->diffInMinutes(now());
|
||||||
|
$remainMins = now()->diffInMinutes($booking->end_time, false);
|
||||||
|
$isOverdue = $remainMins < 0;
|
||||||
|
$isWarning = !$isOverdue && $remainMins < 15;
|
||||||
|
$totalMins = max(1, $booking->start_time->diffInMinutes($booking->end_time));
|
||||||
|
$pctUsed = min(100, round($elapsedMin / $totalMins * 100));
|
||||||
|
$barColor = $isOverdue ? '#ef4444' : ($isWarning ? '#f59e0b' : '#10b981');
|
||||||
|
$rowClass = $isOverdue ? 'is-overdue' : ($isWarning ? 'is-warning' : 'is-ok');
|
||||||
|
$source = $booking->source ?? 'walk_in';
|
||||||
|
@endphp
|
||||||
|
<tr class="booking-row {{ $rowClass }}" id="row-{{ $booking->id }}">
|
||||||
|
|
||||||
|
{{-- Plate + source --}}
|
||||||
<td>
|
<td>
|
||||||
<span class="fw-700" style="font-family:monospace;font-size:.95rem;color:#0f172a;">
|
<span class="plate-tag">
|
||||||
{{ $booking->vehicle_plate ?? $booking->customer_name ?? '--' }}
|
{{ $booking->vehicle_plate ?? '—' }}
|
||||||
</span>
|
</span>
|
||||||
|
<div>
|
||||||
|
@if($source === 'walk_in')
|
||||||
|
<span class="src-badge" style="background:rgba(16,185,129,.1);color:#059669;">
|
||||||
|
<i class="bi bi-box-arrow-in-right"></i>مباشر
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<span class="src-badge" style="background:rgba(14,165,233,.1);color:#0284c7;">
|
||||||
|
<i class="bi bi-calendar-check"></i>حجز
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-sm">{{ $booking->user_name ?? $booking->customer_name ?? '--' }}</td>
|
|
||||||
<td class="text-sm" style="direction:ltr;text-align:right;">
|
{{-- Driver --}}
|
||||||
{{ $booking->user_phone ?? $booking->phone ?? '--' }}
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-soft-info text-xs fw-600">
|
<div class="fw-600 text-sm" style="color:#0f172a;">
|
||||||
|
{{ $booking->customer_name ?? '—' }}
|
||||||
|
</div>
|
||||||
|
@if($booking->phone)
|
||||||
|
<div class="text-xs" style="color:#94a3b8;direction:ltr;text-align:right;">
|
||||||
|
{{ $booking->phone }}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{{-- Lot --}}
|
||||||
|
<td>
|
||||||
|
<span class="badge badge-soft-info fw-600 text-xs">
|
||||||
{{ $booking->parkingLot->name }}
|
{{ $booking->parkingLot->name }}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="text-xs" style="color:#64748b;">{{ $booking->start_time->format('Y/m/d H:i') }}</td>
|
|
||||||
<td class="text-xs" style="color:#64748b;">{{ $booking->end_time->format('Y/m/d H:i') }}</td>
|
{{-- Entry time --}}
|
||||||
<td>
|
<td>
|
||||||
<span class="badge badge-soft-warning text-xs fw-600">
|
<div class="fw-600 text-sm" style="color:#0f172a;">
|
||||||
{{ $booking->start_time->diffForHumans(now(), true) }}
|
{{ $booking->start_time->format('H:i') }}
|
||||||
</span>
|
</div>
|
||||||
|
<div class="text-xs" style="color:#94a3b8;">
|
||||||
|
{{ $booking->start_time->format('d/m/Y') }}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
|
{{-- Duration / status --}}
|
||||||
|
<td>
|
||||||
|
@if($isOverdue)
|
||||||
|
<span class="time-pill" style="background:rgba(239,68,68,.1);color:#dc2626;">
|
||||||
|
<i class="bi bi-exclamation-triangle-fill"></i>
|
||||||
|
تجاوز {{ floor(abs($remainMins)/60) > 0 ? floor(abs($remainMins)/60).'س ' : '' }}{{ abs($remainMins)%60 }}د
|
||||||
|
</span>
|
||||||
|
@elseif($isWarning)
|
||||||
|
<span class="time-pill" style="background:rgba(245,158,11,.1);color:#d97706;">
|
||||||
|
<i class="bi bi-hourglass-split"></i>
|
||||||
|
متبقي {{ $remainMins }}د
|
||||||
|
</span>
|
||||||
|
@else
|
||||||
|
<span class="time-pill" style="background:rgba(16,185,129,.1);color:#059669;">
|
||||||
|
<i class="bi bi-clock"></i>
|
||||||
|
{{ floor($elapsedMin/60) }}س {{ $elapsedMin%60 }}د
|
||||||
|
</span>
|
||||||
|
@endif
|
||||||
|
<div class="dur-bar-wrap">
|
||||||
|
<div class="dur-bar-fill" style="width:{{ $pctUsed }}%;background:{{ $barColor }};"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{{-- Action --}}
|
||||||
<td class="text-center">
|
<td class="text-center">
|
||||||
<button class="btn btn-sm fw-600"
|
<button class="btn btn-sm fw-600"
|
||||||
style="background:rgba(239,68,68,.1);color:#dc2626;border:none;border-radius:.375rem;font-family:'Cairo',sans-serif;padding:.3rem .75rem;"
|
style="background:rgba(239,68,68,.1);color:#dc2626;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;padding:.35rem .9rem;"
|
||||||
onclick="completeBooking({{ $booking->id }}, this)">
|
onclick="completeBooking({{ $booking->id }}, this)">
|
||||||
<i class="bi bi-stop-circle me-1"></i>إنهاء
|
<i class="bi bi-stop-circle me-1"></i>إنهاء
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
@empty
|
@empty
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="8" class="text-center py-5">
|
<td colspan="6" class="text-center py-5">
|
||||||
<i class="bi bi-check-circle d-block mb-3" style="font-size:2.5rem;color:#10b981;opacity:.5;"></i>
|
<i class="bi bi-check-circle d-block mb-3" style="font-size:3rem;color:#10b981;opacity:.4;"></i>
|
||||||
<p class="fw-600 mb-0" style="color:#475569;">لا توجد حجوزات نشطة</p>
|
<p class="fw-700 mb-1" style="color:#475569;font-size:1rem;">لا توجد حجوزات نشطة</p>
|
||||||
<p class="text-sm mb-0" style="color:#94a3b8;">جميع المواقف خالية حالياً</p>
|
<p class="text-sm mb-0" style="color:#94a3b8;">جميع المواقف خالية حالياً</p>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -119,58 +335,66 @@
|
|||||||
{{ $activeBookings->appends(request()->query())->links('pagination::bootstrap-5') }}
|
{{ $activeBookings->appends(request()->query())->links('pagination::bootstrap-5') }}
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@push('scripts')
|
@push('scripts')
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('lotFilter').addEventListener('change', function () {
|
function applyFilters() {
|
||||||
const url = new URL(window.location);
|
const url = new URL(window.location);
|
||||||
this.value ? url.searchParams.set('parking_lot_id', this.value)
|
const lot = document.getElementById('lotFilter').value;
|
||||||
: url.searchParams.delete('parking_lot_id');
|
const search = document.getElementById('searchInput').value.trim();
|
||||||
window.location.href = url.toString();
|
lot ? url.searchParams.set('parking_lot_id', lot) : url.searchParams.delete('parking_lot_id');
|
||||||
});
|
search ? url.searchParams.set('search', search) : url.searchParams.delete('search');
|
||||||
|
url.searchParams.delete('page');
|
||||||
|
window.location.href = url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
async function completeBooking(id, btn) {
|
document.getElementById('lotFilter').addEventListener('change', applyFilters);
|
||||||
if (!confirm('إنهاء هذا الحجز؟')) return;
|
document.getElementById('searchInput').addEventListener('keydown', e => {
|
||||||
const orig = btn.innerHTML;
|
if (e.key === 'Enter') applyFilters();
|
||||||
btn.disabled = true;
|
});
|
||||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
|
||||||
|
|
||||||
try {
|
async function completeBooking(id, btn) {
|
||||||
const res = await fetch(`/admin/bookings/${id}/complete`, {
|
if (!confirm('إنهاء هذا الحجز؟')) return;
|
||||||
method: 'POST',
|
const orig = btn.innerHTML;
|
||||||
headers: {
|
btn.disabled = true;
|
||||||
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
|
||||||
'Content-Type': 'application/json'
|
try {
|
||||||
}
|
const res = await fetch(`/admin/bookings/${id}/complete`, {
|
||||||
});
|
method: 'POST',
|
||||||
const data = await res.json();
|
headers: {
|
||||||
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
|
||||||
if (data.success) {
|
'Content-Type': 'application/json'
|
||||||
const row = document.getElementById('row-' + id);
|
|
||||||
row.style.transition = 'opacity .4s';
|
|
||||||
row.style.opacity = '0';
|
|
||||||
setTimeout(() => row.remove(), 400);
|
|
||||||
} else {
|
|
||||||
alert(data.message || 'خطأ');
|
|
||||||
btn.innerHTML = orig;
|
|
||||||
btn.disabled = false;
|
|
||||||
}
|
}
|
||||||
} catch {
|
});
|
||||||
alert('خطأ في الاتصال');
|
const data = await res.json();
|
||||||
|
if (data.success) {
|
||||||
|
const row = document.getElementById('row-' + id);
|
||||||
|
row.style.transition = 'opacity .4s, transform .4s';
|
||||||
|
row.style.opacity = '0';
|
||||||
|
row.style.transform = 'translateX(20px)';
|
||||||
|
setTimeout(() => row.remove(), 420);
|
||||||
|
} else {
|
||||||
|
alert(data.message || 'خطأ');
|
||||||
btn.innerHTML = orig;
|
btn.innerHTML = orig;
|
||||||
btn.disabled = false;
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
|
} catch {
|
||||||
|
alert('خطأ في الاتصال');
|
||||||
|
btn.innerHTML = orig;
|
||||||
|
btn.disabled = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Countdown
|
// Countdown refresh
|
||||||
let t = 30;
|
let t = 30;
|
||||||
const badge = document.getElementById('refresh-badge');
|
const badge = document.getElementById('refresh-badge');
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
t--;
|
t--;
|
||||||
badge.textContent = `تحديث بعد ${t}ث`;
|
badge.textContent = `تحديث بعد ${t}ث`;
|
||||||
if (t <= 0) location.reload();
|
if (t <= 0) location.reload();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
</script>
|
</script>
|
||||||
@endpush
|
@endpush
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user