402 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@extends('layouts.admin')
@section('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')
{{-- ── Page header ─────────────────────────────────────────────────────────── --}}
<div class="d-flex align-items-center justify-content-between flex-wrap gap-3 mb-4">
<div>
<h2 class="fw-800 mb-1" style="font-size:1.15rem;color:#0f172a;">الحجوزات النشطة</h2>
<p class="text-sm mb-0" style="color:#64748b;">
السيارات داخل المواقف حالياً
</p>
</div>
<div class="d-flex align-items-center gap-2">
<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>
{{-- ── Stat cards ───────────────────────────────────────────────────────────── --}}
<div class="row g-3 mb-4">
<div class="col-sm-6 col-xl-3">
<div class="stat-card">
<div class="stat-icon" style="background:rgba(99,102,241,.1);">
<i class="bi bi-car-front" style="color:#6366f1;"></i>
</div>
<div>
<div class="stat-value">{{ $stats['total'] }}</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(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>
</div>
</div>
{{-- ── 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-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">
<table class="app-table w-100">
<thead>
<tr>
<th>السيارة</th>
<th>السائق</th>
<th>الموقف</th>
<th>وقت الدخول</th>
<th>المدة / الوضع</th>
<th class="text-center">إجراء</th>
</tr>
</thead>
<tbody>
@forelse($activeBookings as $booking)
@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>
<span class="plate-tag">
{{ $booking->vehicle_plate ?? '—' }}
</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>
{{-- Driver --}}
<td>
<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 }}
</span>
</td>
{{-- Entry time --}}
<td>
<div class="fw-600 text-sm" style="color:#0f172a;">
{{ $booking->start_time->format('H:i') }}
</div>
<div class="text-xs" style="color:#94a3b8;">
{{ $booking->start_time->format('d/m/Y') }}
</div>
</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">
<button class="btn btn-sm fw-600"
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)">
<i class="bi bi-stop-circle me-1"></i>إنهاء
</button>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center py-5">
<i class="bi bi-check-circle d-block mb-3" style="font-size:3rem;color:#10b981;opacity:.4;"></i>
<p class="fw-700 mb-1" style="color:#475569;font-size:1rem;">لا توجد حجوزات نشطة</p>
<p class="text-sm mb-0" style="color:#94a3b8;">جميع المواقف خالية حالياً</p>
</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($activeBookings->hasPages())
<div class="card-header d-flex align-items-center justify-content-between flex-wrap gap-2">
<span class="text-xs" style="color:#64748b;">
عرض {{ $activeBookings->firstItem() }}{{ $activeBookings->lastItem() }}
من {{ $activeBookings->total() }}
</span>
{{ $activeBookings->appends(request()->query())->links('pagination::bootstrap-5') }}
</div>
@endif
</div>
@push('scripts')
<script>
function applyFilters() {
const url = new URL(window.location);
const lot = document.getElementById('lotFilter').value;
const search = document.getElementById('searchInput').value.trim();
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();
}
document.getElementById('lotFilter').addEventListener('change', applyFilters);
document.getElementById('searchInput').addEventListener('keydown', e => {
if (e.key === 'Enter') applyFilters();
});
async function completeBooking(id, btn) {
if (!confirm('إنهاء هذا الحجز؟')) return;
const orig = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
try {
const res = await fetch(`/admin/bookings/${id}/complete`, {
method: 'POST',
headers: {
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content,
'Content-Type': 'application/json'
}
});
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.disabled = false;
}
} catch {
alert('خطأ في الاتصال');
btn.innerHTML = orig;
btn.disabled = false;
}
}
// Countdown refresh
let t = 30;
const badge = document.getElementById('refresh-badge');
setInterval(() => {
t--;
badge.textContent = `تحديث بعد ${t}ث`;
if (t <= 0) location.reload();
}, 1000);
</script>
@endpush
@endsection