initial: project state before operator dashboard card redesign
This commit is contained in:
commit
e0aed8eed7
113 files changed
+20074
No files matched your search
@@ -0,0 +1,177 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'الحجوزات النشطة — دمشق باركينغ')
|
||||
@section('page-title', 'الحجوزات النشطة')
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- ── 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;">
|
||||
السيارات المسجّلة حالياً · يتجدد كل 30 ثانية
|
||||
</p>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Filter ────────────────────────────────────────────────────────────────── --}}
|
||||
<div class="card mb-4">
|
||||
<div class="card-body p-3">
|
||||
<div class="row align-items-end g-3">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">فلترة حسب الموقف</label>
|
||||
<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>
|
||||
@if(request('parking_lot_id'))
|
||||
<div class="col-auto">
|
||||
<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;">
|
||||
<i class="bi bi-x me-1"></i>إلغاء الفلتر
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Table ────────────────────────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="table-responsive">
|
||||
<table class="app-table w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>رقم اللوحة</th>
|
||||
<th>السائق</th>
|
||||
<th>الهاتف</th>
|
||||
<th>الموقف</th>
|
||||
<th>وقت الدخول</th>
|
||||
<th>وقت الخروج</th>
|
||||
<th>المدة</th>
|
||||
<th class="text-center">إنهاء</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($activeBookings as $booking)
|
||||
<tr id="row-{{ $booking->id }}">
|
||||
<td>
|
||||
<span class="fw-700" style="font-family:monospace;font-size:.95rem;color:#0f172a;">
|
||||
{{ $booking->vehicle_plate ?? $booking->customer_name ?? '--' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-sm">{{ $booking->user_name ?? $booking->customer_name ?? '--' }}</td>
|
||||
<td class="text-sm" style="direction:ltr;text-align:right;">
|
||||
{{ $booking->user_phone ?? $booking->phone ?? '--' }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-soft-info text-xs fw-600">
|
||||
{{ $booking->parkingLot->name }}
|
||||
</span>
|
||||
</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>
|
||||
<td>
|
||||
<span class="badge badge-soft-warning text-xs fw-600">
|
||||
{{ $booking->start_time->diffForHumans(now(), true) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<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;"
|
||||
onclick="completeBooking({{ $booking->id }}, this)">
|
||||
<i class="bi bi-stop-circle me-1"></i>إنهاء
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" 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>
|
||||
<p class="fw-600 mb-0" style="color:#475569;">لا توجد حجوزات نشطة</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>
|
||||
document.getElementById('lotFilter').addEventListener('change', function () {
|
||||
const url = new URL(window.location);
|
||||
this.value ? url.searchParams.set('parking_lot_id', this.value)
|
||||
: url.searchParams.delete('parking_lot_id');
|
||||
window.location.href = url.toString();
|
||||
});
|
||||
|
||||
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';
|
||||
row.style.opacity = '0';
|
||||
setTimeout(() => row.remove(), 400);
|
||||
} else {
|
||||
alert(data.message || 'خطأ');
|
||||
btn.innerHTML = orig;
|
||||
btn.disabled = false;
|
||||
}
|
||||
} catch {
|
||||
alert('خطأ في الاتصال');
|
||||
btn.innerHTML = orig;
|
||||
btn.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Countdown
|
||||
let t = 30;
|
||||
const badge = document.getElementById('refresh-badge');
|
||||
setInterval(() => {
|
||||
t--;
|
||||
badge.textContent = `تحديث بعد ${t}ث`;
|
||||
if (t <= 0) location.reload();
|
||||
}, 1000);
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,110 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'جميع الحجوزات — دمشق باركينغ')
|
||||
@section('page-title', 'جميع الحجوزات')
|
||||
|
||||
@section('content')
|
||||
|
||||
<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 gap-2">
|
||||
<a href="{{ route('admin.bookings.active') }}"
|
||||
class="btn btn-sm fw-600"
|
||||
style="background:rgba(16,185,129,.1);color:#059669;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-activity me-1"></i>النشطة فقط
|
||||
</a>
|
||||
<button class="btn btn-sm fw-600" onclick="exportCSV()"
|
||||
style="background:rgba(16,185,129,.1);color:#059669;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-download me-1"></i>CSV
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<span class="fw-700 text-sm">قائمة الحجوزات</span>
|
||||
<div class="input-group" style="max-width:240px;">
|
||||
<input type="text" id="searchInput" class="form-control form-control-sm"
|
||||
style="border-color:#e2e8f0;border-inline-end:none;" placeholder="بحث...">
|
||||
<button class="btn btn-sm" style="background:#6366f1;color:#fff;border:none;" onclick="doSearch()">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="app-table w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>الموقف</th>
|
||||
<th>العميل</th>
|
||||
<th>اللوحة</th>
|
||||
<th>الهاتف</th>
|
||||
<th>البداية</th>
|
||||
<th>النهاية</th>
|
||||
<th class="text-center">الحالة</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($bookings as $booking)
|
||||
<tr>
|
||||
<td class="text-xs" style="color:#94a3b8;">{{ $booking->id }}</td>
|
||||
<td><span class="fw-600">{{ $booking->parkingLot->name ?? '--' }}</span></td>
|
||||
<td class="text-sm">{{ $booking->customer_name ?? $booking->user_name ?? '--' }}</td>
|
||||
<td>
|
||||
<span style="font-family:monospace;font-weight:700;color:#0f172a;">
|
||||
{{ $booking->vehicle_plate ?? '--' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-sm" style="direction:ltr;text-align:right;">
|
||||
{{ $booking->phone ?? $booking->user_phone ?? '--' }}
|
||||
</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>
|
||||
<td class="text-center">
|
||||
@if($booking->status === 'active')
|
||||
<span class="badge badge-soft-success text-xs">نشط</span>
|
||||
@elseif($booking->status === 'completed')
|
||||
<span class="badge badge-soft-secondary text-xs">مكتمل</span>
|
||||
@else
|
||||
<span class="badge badge-soft-warning text-xs">{{ $booking->status }}</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-5">
|
||||
<i class="bi bi-calendar-x d-block mb-2" style="font-size:2rem;color:#cbd5e1;"></i>
|
||||
<span class="text-sm" style="color:#94a3b8;">لا توجد حجوزات</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if(isset($bookings) && $bookings->hasPages())
|
||||
<div class="card-header">
|
||||
{{ $bookings->links('pagination::bootstrap-5') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
function exportCSV() {
|
||||
window.location.href = '{{ route("admin.bookings.export", ["format" => "csv"]) }}';
|
||||
}
|
||||
function doSearch() {
|
||||
window.location.href = `/admin/bookings?search=${encodeURIComponent(document.getElementById('searchInput').value)}`;
|
||||
}
|
||||
document.getElementById('searchInput').addEventListener('keypress', e => {
|
||||
if (e.key === 'Enter') doSearch();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,265 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'لوحة التحكم — دمشق باركينغ')
|
||||
@section('page-title', 'لوحة التحكم')
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- ── Stats Row ──────────────────────────────────────────────────────────── --}}
|
||||
<div class="row g-3 mb-4">
|
||||
|
||||
@php
|
||||
$stats = [
|
||||
['id' => 'total-parking-lots', 'label' => 'إجمالي المواقف', 'icon' => 'bi-buildings', 'color' => '#6366f1', 'bg' => 'rgba(99,102,241,.1)'],
|
||||
['id' => 'total-bookings', 'label' => 'إجمالي الحجوزات', 'icon' => 'bi-calendar3', 'color' => '#0ea5e9', 'bg' => 'rgba(14,165,233,.1)'],
|
||||
['id' => 'active-bookings', 'label' => 'الحجوزات النشطة', 'icon' => 'bi-clock-history', 'color' => '#f59e0b', 'bg' => 'rgba(245,158,11,.1)'],
|
||||
['id' => 'occupancy-rate', 'label' => 'معدل الإشغال', 'icon' => 'bi-graph-up-arrow', 'color' => '#ef4444', 'bg' => 'rgba(239,68,68,.1)'],
|
||||
['id' => 'estimated-revenue', 'label' => 'الإيرادات المتوقعة', 'icon' => 'bi-cash-coin', 'color' => '#10b981', 'bg' => 'rgba(16,185,129,.1)'],
|
||||
['id' => 'available-spots', 'label' => 'الأماكن المتاحة', 'icon' => 'bi-check2-square', 'color' => '#8b5cf6', 'bg' => 'rgba(139,92,246,.1)'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach($stats as $s)
|
||||
<div class="col-xl-2 col-lg-4 col-sm-6">
|
||||
<div class="card stat-card h-100">
|
||||
<div class="card-body p-3">
|
||||
<div class="d-flex align-items-center justify-content-between mb-3">
|
||||
<div class="rounded-3 d-flex align-items-center justify-content-center"
|
||||
style="width:42px;height:42px;background:{{ $s['bg'] }};">
|
||||
<i class="bi {{ $s['icon'] }}" style="font-size:1.2rem;color:{{ $s['color'] }};"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fw-800 lh-sm mb-1" id="{{ $s['id'] }}"
|
||||
style="font-size:1.6rem;color:{{ $s['color'] }};">--</div>
|
||||
<div class="text-xs" style="color:#64748b;">{{ $s['label'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
</div>
|
||||
|
||||
{{-- ── Quick Actions ───────────────────────────────────────────────────────── --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-12">
|
||||
<h5 class="fw-700 mb-3" style="color:#0f172a;font-size:.95rem;">
|
||||
<i class="bi bi-lightning-charge-fill me-2" style="color:#f59e0b;"></i>
|
||||
إجراءات سريعة
|
||||
</h5>
|
||||
<div class="row g-3">
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<a href="{{ route('admin.parking-lots.index') }}" class="quick-card card h-100 p-4">
|
||||
<div class="quick-icon" style="background:rgba(99,102,241,.1);">
|
||||
<i class="bi bi-buildings" style="color:#6366f1;"></i>
|
||||
</div>
|
||||
<h6 style="color:#0f172a;">إدارة المواقف</h6>
|
||||
<small>إضافة · تعديل · تفعيل</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<a href="{{ route('admin.bookings.active') }}" class="quick-card card h-100 p-4">
|
||||
<div class="quick-icon" style="background:rgba(16,185,129,.1);">
|
||||
<i class="bi bi-calendar-check" style="color:#10b981;"></i>
|
||||
</div>
|
||||
<h6 style="color:#0f172a;">الحجوزات النشطة</h6>
|
||||
<small>إنهاء · فلترة · متابعة</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<a href="{{ route('operator.dashboard') }}" class="quick-card card h-100 p-4">
|
||||
<div class="quick-icon" style="background:rgba(245,158,11,.1);">
|
||||
<i class="bi bi-person-badge" style="color:#f59e0b;"></i>
|
||||
</div>
|
||||
<h6 style="color:#0f172a;">لوحة المشغّل</h6>
|
||||
<small>دخول وخروج فوري</small>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-lg-3 col-sm-6">
|
||||
<div class="quick-card card h-100 p-4" onclick="alert('قريباً...')">
|
||||
<div class="quick-icon" style="background:rgba(100,116,139,.1);">
|
||||
<i class="bi bi-gear" style="color:#64748b;"></i>
|
||||
</div>
|
||||
<h6 style="color:#0f172a;">الإعدادات</h6>
|
||||
<small>تخصيص · تقارير</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Charts ──────────────────────────────────────────────────────────────── --}}
|
||||
<div class="row g-3 mb-4">
|
||||
<div class="col-lg-7">
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<span class="fw-700" style="font-size:.9rem;">
|
||||
<i class="bi bi-bar-chart-line me-2" style="color:#6366f1;"></i>
|
||||
الحجوزات اليومية — آخر 7 أيام
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body p-3">
|
||||
<canvas id="dailyChart" height="140"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<div class="card h-100">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<span class="fw-700" style="font-size:.9rem;">
|
||||
<i class="bi bi-trophy me-2" style="color:#f59e0b;"></i>
|
||||
أفضل 5 مواقف
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body p-3 d-flex align-items-center justify-content-center">
|
||||
<canvas id="topChart" height="140"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Recent Bookings ─────────────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<span class="fw-700" style="font-size:.9rem;">
|
||||
<i class="bi bi-clock-history me-2" style="color:#0ea5e9;"></i>
|
||||
آخر الحجوزات
|
||||
</span>
|
||||
<span class="badge badge-soft-secondary" id="last-updated">--</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>وقت الانتهاء</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="bookings-body">
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4" style="color:#94a3b8;">
|
||||
<div class="spinner-border spinner-border-sm me-2"></div>
|
||||
جاري التحميل...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/chart.umd.min.js"></script>
|
||||
<script>
|
||||
let dailyChart, topChart;
|
||||
|
||||
async function loadStats() {
|
||||
try {
|
||||
const { success, data } = await fetch('/admin/stats').then(r => r.json());
|
||||
if (!success) return;
|
||||
document.getElementById('total-parking-lots').textContent = data.total_parking_lots ?? 0;
|
||||
document.getElementById('total-bookings').textContent = data.total_bookings ?? 0;
|
||||
document.getElementById('active-bookings').textContent = data.active_bookings ?? 0;
|
||||
document.getElementById('occupancy-rate').textContent = (data.occupancy_rate ?? 0) + '%';
|
||||
document.getElementById('estimated-revenue').textContent = (data.estimated_revenue ?? 0).toLocaleString('ar-SA') + ' ر.س';
|
||||
document.getElementById('available-spots').textContent = data.available_spots ?? 0;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function loadCharts() {
|
||||
try {
|
||||
const { success, data } = await fetch('/admin/charts').then(r => r.json());
|
||||
if (!success) return;
|
||||
|
||||
const labels = Array.from({length: 7}, (_, i) => {
|
||||
const d = new Date();
|
||||
d.setDate(d.getDate() - (6 - i));
|
||||
return d.toLocaleDateString('ar-SA', {weekday: 'short'});
|
||||
});
|
||||
|
||||
if (dailyChart) dailyChart.destroy();
|
||||
dailyChart = new Chart(document.getElementById('dailyChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels,
|
||||
datasets: [{
|
||||
label: 'حجوزات',
|
||||
data: data.daily_bookings ?? [],
|
||||
backgroundColor: 'rgba(99,102,241,.2)',
|
||||
borderColor: '#6366f1',
|
||||
borderWidth: 2,
|
||||
borderRadius: 6,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: false }
|
||||
},
|
||||
scales: {
|
||||
y: { beginAtZero: true, ticks: { precision: 0 }, grid: { color: '#f1f5f9' } },
|
||||
x: { grid: { display: false } }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (topChart) topChart.destroy();
|
||||
const lots = data.top_parking_lots ?? [];
|
||||
topChart = new Chart(document.getElementById('topChart'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: lots.map(l => l.name),
|
||||
datasets: [{
|
||||
data: lots.map(l => l.value),
|
||||
backgroundColor: ['#6366f1','#10b981','#f59e0b','#ef4444','#0ea5e9'],
|
||||
borderWidth: 3,
|
||||
borderColor: '#fff',
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { position: 'bottom', labels: { usePointStyle: true, padding: 12, font: { family: 'Cairo', size: 12 } } }
|
||||
},
|
||||
cutout: '65%',
|
||||
}
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function loadBookings() {
|
||||
const tbody = document.getElementById('bookings-body');
|
||||
try {
|
||||
const { success, data } = await fetch('/api/v1/bookings?per_page=8').then(r => r.json());
|
||||
if (!success || !data?.data?.length) {
|
||||
tbody.innerHTML = `<tr><td colspan="6" class="text-center py-4" style="color:#94a3b8;">لا توجد حجوزات</td></tr>`;
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = data.data.map(b => {
|
||||
const badge = b.status === 'active'
|
||||
? '<span class="badge badge-soft-success">نشط</span>'
|
||||
: '<span class="badge badge-soft-secondary">مكتمل</span>';
|
||||
return `<tr>
|
||||
<td><span class="fw-600">${b.parking_lot?.name ?? '--'}</span></td>
|
||||
<td>${b.customer_name ?? '--'}</td>
|
||||
<td style="direction:ltr;text-align:right;">${b.phone ?? '--'}</td>
|
||||
<td>${badge}</td>
|
||||
<td class="text-sm" style="color:#64748b;">${new Date(b.start_time).toLocaleString('ar-SA')}</td>
|
||||
<td class="text-sm" style="color:#64748b;">${new Date(b.end_time).toLocaleString('ar-SA')}</td>
|
||||
</tr>`;
|
||||
}).join('');
|
||||
} catch {
|
||||
tbody.innerHTML = `<tr><td colspan="6" class="text-center py-3 text-sm" style="color:#94a3b8;">تعذّر تحميل البيانات</td></tr>`;
|
||||
}
|
||||
document.getElementById('last-updated').textContent = new Date().toLocaleTimeString('ar-SA');
|
||||
}
|
||||
|
||||
loadStats();
|
||||
loadCharts();
|
||||
loadBookings();
|
||||
setInterval(() => { loadStats(); loadCharts(); loadBookings(); }, 30000);
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,447 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'إدارة المواقف — دمشق باركينغ')
|
||||
@section('page-title', 'إدارة المواقف')
|
||||
|
||||
@section('styles')
|
||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
|
||||
<style>
|
||||
#lotsMap { height: 380px; border-radius: .5rem; z-index: 0; }
|
||||
#modalMap { height: 280px; border-radius: .5rem; z-index: 0; }
|
||||
.map-hint { font-size: .78rem; color: #64748b; margin-top: .35rem; }
|
||||
/* keep Leaflet tiles crisp inside RTL layout */
|
||||
.leaflet-container { direction: ltr; }
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- ── 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;">
|
||||
{{ $parkingLots->total() }} موقف مسجّل في النظام
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn fw-600"
|
||||
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;padding:.55rem 1.25rem;font-family:'Cairo',sans-serif;"
|
||||
data-bs-toggle="modal" data-bs-target="#lotModal" id="addLotBtn">
|
||||
<i class="bi bi-plus-lg me-1"></i>
|
||||
إضافة موقف
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- ── Overview Map ─────────────────────────────────────────────────────────── --}}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">
|
||||
<span class="fw-700 text-sm">
|
||||
<i class="bi bi-map me-1" style="color:#6366f1;"></i>
|
||||
خريطة المواقف
|
||||
</span>
|
||||
</div>
|
||||
<div class="p-2">
|
||||
<div id="lotsMap"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Table Card ───────────────────────────────────────────────────────────── --}}
|
||||
<div class="card">
|
||||
|
||||
{{-- Toolbar --}}
|
||||
<div class="card-header d-flex align-items-center justify-content-between gap-3 flex-wrap">
|
||||
<span class="fw-700 text-sm">
|
||||
<i class="bi bi-list-ul me-1" style="color:#6366f1;"></i>
|
||||
قائمة المواقف
|
||||
</span>
|
||||
<div class="input-group" style="max-width:260px;">
|
||||
<input type="text" id="searchInput"
|
||||
class="form-control form-control-sm"
|
||||
style="border-color:#e2e8f0;border-inline-end:none;"
|
||||
placeholder="بحث..." value="{{ request('search') }}">
|
||||
<button class="btn btn-sm" style="background:#6366f1;color:#fff;border:none;" onclick="doSearch()">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="app-table w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>الموقف</th>
|
||||
<th>العنوان</th>
|
||||
<th class="text-center">السعة</th>
|
||||
<th class="text-center">السعر / ساعة</th>
|
||||
<th class="text-center">ساعات العمل</th>
|
||||
<th class="text-center">الحالة</th>
|
||||
<th class="text-center">نشط حالياً</th>
|
||||
<th class="text-center">إجراءات</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($parkingLots as $lot)
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-600" style="color:#0f172a;">{{ $lot->name }}</div>
|
||||
<div class="text-xs" style="color:#94a3b8;direction:ltr;text-align:right;">
|
||||
{{ number_format($lot->latitude, 5) }}, {{ number_format($lot->longitude, 5) }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-sm" style="color:#475569;" title="{{ $lot->address }}">
|
||||
{{ Str::limit($lot->address, 40) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge badge-soft-info fw-600">{{ $lot->total_capacity }}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge badge-soft-success fw-600">
|
||||
{{ number_format($lot->price_per_hour, 0) }} ر.س
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge badge-soft-secondary text-xs">{{ $lot->working_hours }}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
@if($lot->is_active)
|
||||
<span class="badge badge-soft-success">
|
||||
<i class="bi bi-circle-fill me-1" style="font-size:.45rem;vertical-align:middle;"></i>نشط
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-soft-danger">
|
||||
<i class="bi bi-circle-fill me-1" style="font-size:.45rem;vertical-align:middle;"></i>معطل
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="badge badge-soft-warning fw-600">{{ $lot->active_bookings_count ?? 0 }}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div class="d-inline-flex gap-1">
|
||||
<button class="btn btn-sm"
|
||||
style="background:#f1f5f9;color:#475569;border:none;border-radius:.375rem;width:30px;height:30px;padding:0;"
|
||||
data-bs-toggle="modal" data-bs-target="#lotModal"
|
||||
onclick="editLot({{ $lot->id }})" title="تعديل">
|
||||
<i class="bi bi-pencil" style="font-size:.8rem;"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm"
|
||||
style="background:{{ $lot->is_active ? 'rgba(239,68,68,.1)' : 'rgba(16,185,129,.1)' }};color:{{ $lot->is_active ? '#dc2626' : '#059669' }};border:none;border-radius:.375rem;width:30px;height:30px;padding:0;"
|
||||
onclick="toggleStatus({{ $lot->id }})"
|
||||
title="{{ $lot->is_active ? 'تعطيل' : 'تفعيل' }}">
|
||||
<i class="bi {{ $lot->is_active ? 'bi-slash-circle' : 'bi-check-circle' }}" style="font-size:.8rem;"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="8" class="text-center py-5">
|
||||
<i class="bi bi-buildings d-block mb-3" style="font-size:2.5rem;color:#cbd5e1;"></i>
|
||||
<p class="fw-600 mb-1" style="color:#475569;">لا توجد مواقف بعد</p>
|
||||
<p class="text-sm mb-3" style="color:#94a3b8;">ابدأ بإضافة أول موقف سيارات</p>
|
||||
<button class="btn btn-sm fw-600"
|
||||
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;"
|
||||
data-bs-toggle="modal" data-bs-target="#lotModal">
|
||||
<i class="bi bi-plus-lg me-1"></i>إضافة موقف
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if($parkingLots->hasPages())
|
||||
<div class="card-header d-flex align-items-center justify-content-between flex-wrap gap-2">
|
||||
<span class="text-xs" style="color:#64748b;">
|
||||
عرض {{ $parkingLots->firstItem() ?? 0 }}–{{ $parkingLots->lastItem() ?? 0 }}
|
||||
من {{ $parkingLots->total() }}
|
||||
</span>
|
||||
{{ $parkingLots->appends(request()->query())->links('pagination::bootstrap-5') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
|
||||
{{-- ── Add / Edit Modal ────────────────────────────────────────────────────── --}}
|
||||
<div class="modal fade" id="lotModal" tabindex="-1" data-bs-backdrop="static">
|
||||
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
|
||||
<div class="modal-header" style="border-bottom:1px solid #f1f5f9;">
|
||||
<h5 class="modal-title fw-700" id="modalLabel" style="font-size:1rem;color:#0f172a;">
|
||||
إضافة موقف جديد
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<form id="lotForm">
|
||||
@csrf
|
||||
<input type="hidden" id="lotId">
|
||||
|
||||
<div class="modal-body p-4">
|
||||
<div class="row g-3">
|
||||
|
||||
{{-- Basic Info --}}
|
||||
<div class="col-12">
|
||||
<p class="text-xs fw-700 text-uppercase mb-2" style="color:#94a3b8;letter-spacing:.05em;">
|
||||
المعلومات الأساسية
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">اسم الموقف <span style="color:#ef4444;">*</span></label>
|
||||
<input type="text" name="name" id="f_name" class="form-control" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">السعة <span style="color:#ef4444;">*</span></label>
|
||||
<input type="number" name="total_capacity" id="f_capacity" class="form-control" min="1" required>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">السعر / ساعة <span style="color:#ef4444;">*</span></label>
|
||||
<input type="number" name="price_per_hour" id="f_price" class="form-control" step="0.01" min="0" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">ساعات العمل <span style="color:#ef4444;">*</span></label>
|
||||
<input type="text" name="working_hours" id="f_hours" class="form-control" value="24/7" required>
|
||||
</div>
|
||||
|
||||
{{-- Location --}}
|
||||
<div class="col-12 mt-2">
|
||||
<p class="text-xs fw-700 text-uppercase mb-2" style="color:#94a3b8;letter-spacing:.05em;">
|
||||
الموقع الجغرافي
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">خط العرض <span style="color:#ef4444;">*</span></label>
|
||||
<input type="number" name="latitude" id="f_lat" class="form-control" step="any" required dir="ltr">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">خط الطول <span style="color:#ef4444;">*</span></label>
|
||||
<input type="number" name="longitude" id="f_lng" class="form-control" step="any" required dir="ltr">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">العنوان الكامل <span style="color:#ef4444;">*</span></label>
|
||||
<input type="text" name="address" id="f_address" class="form-control" required>
|
||||
</div>
|
||||
|
||||
{{-- Map Picker --}}
|
||||
<div class="col-12">
|
||||
<label class="form-label mb-1">
|
||||
<i class="bi bi-cursor-fill me-1" style="color:#6366f1;"></i>
|
||||
انقر على الخريطة لتحديد الموقع
|
||||
</label>
|
||||
<div id="modalMap"></div>
|
||||
<p class="map-hint">انقر على أي نقطة في الخريطة لتعبئة إحداثيات خط العرض والطول تلقائياً.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" style="border-top:1px solid #f1f5f9;">
|
||||
<button type="button" class="btn btn-sm fw-600"
|
||||
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;"
|
||||
data-bs-dismiss="modal">إلغاء</button>
|
||||
<button type="submit" id="submitBtn"
|
||||
class="btn btn-sm fw-600"
|
||||
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||
<span id="submitSpinner" class="spinner-border spinner-border-sm me-1 d-none"></span>
|
||||
<span id="submitText">حفظ الموقف</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
|
||||
<script>
|
||||
// ── Parking lots data from server ─────────────────────────────────────────
|
||||
const lotsData = @json($parkingLots->items());
|
||||
|
||||
// Damascus city centre fallback
|
||||
const DAMASCUS = [33.5138, 36.2765];
|
||||
|
||||
// ── Overview map ──────────────────────────────────────────────────────────
|
||||
const lotsMap = L.map('lotsMap').setView(DAMASCUS, 13);
|
||||
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 19
|
||||
}).addTo(lotsMap);
|
||||
|
||||
const activeIcon = L.icon({
|
||||
iconUrl: 'https://unpkg.com/[email protected]/dist/images/marker-icon.png',
|
||||
shadowUrl: 'https://unpkg.com/[email protected]/dist/images/marker-shadow.png',
|
||||
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34]
|
||||
});
|
||||
|
||||
lotsData.forEach(lot => {
|
||||
const lat = parseFloat(lot.latitude);
|
||||
const lng = parseFloat(lot.longitude);
|
||||
if (isNaN(lat) || isNaN(lng)) return;
|
||||
|
||||
const statusBadge = lot.is_active
|
||||
? '<span style="color:#059669;font-weight:700;">● نشط</span>'
|
||||
: '<span style="color:#dc2626;font-weight:700;">● معطل</span>';
|
||||
|
||||
L.marker([lat, lng], { icon: activeIcon })
|
||||
.addTo(lotsMap)
|
||||
.bindPopup(`
|
||||
<div style="font-family:'Cairo',sans-serif;min-width:160px;direction:rtl;">
|
||||
<div style="font-weight:700;font-size:.92rem;margin-bottom:4px;">${lot.name}</div>
|
||||
<div style="font-size:.8rem;color:#475569;margin-bottom:4px;">${lot.address ?? ''}</div>
|
||||
<div style="font-size:.8rem;margin-bottom:6px;">${statusBadge}</div>
|
||||
<div style="font-size:.78rem;color:#64748b;">
|
||||
السعة: ${lot.total_capacity} | ${lot.price_per_hour} ر.س/س
|
||||
</div>
|
||||
</div>
|
||||
`, { maxWidth: 220 });
|
||||
});
|
||||
|
||||
// Fit map to markers if any exist
|
||||
const validLots = lotsData.filter(l => !isNaN(parseFloat(l.latitude)) && !isNaN(parseFloat(l.longitude)));
|
||||
if (validLots.length > 0) {
|
||||
const bounds = L.latLngBounds(validLots.map(l => [parseFloat(l.latitude), parseFloat(l.longitude)]));
|
||||
lotsMap.fitBounds(bounds, { padding: [40, 40], maxZoom: 15 });
|
||||
}
|
||||
|
||||
// ── Modal map picker ──────────────────────────────────────────────────────
|
||||
let modalMap = null;
|
||||
let modalMarker = null;
|
||||
|
||||
function initModalMap(lat, lng) {
|
||||
const center = (lat && lng) ? [lat, lng] : DAMASCUS;
|
||||
const zoom = (lat && lng) ? 15 : 13;
|
||||
|
||||
if (!modalMap) {
|
||||
modalMap = L.map('modalMap').setView(center, zoom);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
||||
maxZoom: 19
|
||||
}).addTo(modalMap);
|
||||
|
||||
modalMap.on('click', e => {
|
||||
const { lat, lng } = e.latlng;
|
||||
document.getElementById('f_lat').value = lat.toFixed(7);
|
||||
document.getElementById('f_lng').value = lng.toFixed(7);
|
||||
placeModalMarker(lat, lng);
|
||||
});
|
||||
} else {
|
||||
modalMap.setView(center, zoom);
|
||||
}
|
||||
|
||||
if (lat && lng) {
|
||||
placeModalMarker(lat, lng);
|
||||
} else if (modalMarker) {
|
||||
modalMap.removeLayer(modalMarker);
|
||||
modalMarker = null;
|
||||
}
|
||||
|
||||
// Must invalidate after modal finishes animating
|
||||
setTimeout(() => modalMap.invalidateSize(), 350);
|
||||
}
|
||||
|
||||
function placeModalMarker(lat, lng) {
|
||||
if (modalMarker) {
|
||||
modalMarker.setLatLng([lat, lng]);
|
||||
} else {
|
||||
modalMarker = L.marker([lat, lng], { draggable: true }).addTo(modalMap);
|
||||
modalMarker.on('dragend', e => {
|
||||
const pos = e.target.getLatLng();
|
||||
document.getElementById('f_lat').value = pos.lat.toFixed(7);
|
||||
document.getElementById('f_lng').value = pos.lng.toFixed(7);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sync map marker when user types into lat/lng fields
|
||||
['f_lat', 'f_lng'].forEach(id => {
|
||||
document.getElementById(id).addEventListener('input', () => {
|
||||
const lat = parseFloat(document.getElementById('f_lat').value);
|
||||
const lng = parseFloat(document.getElementById('f_lng').value);
|
||||
if (!isNaN(lat) && !isNaN(lng) && modalMap) {
|
||||
modalMap.setView([lat, lng], 15);
|
||||
placeModalMarker(lat, lng);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── Bootstrap modal events ────────────────────────────────────────────────
|
||||
const lotModalEl = document.getElementById('lotModal');
|
||||
lotModalEl.addEventListener('shown.bs.modal', () => {
|
||||
const lat = parseFloat(document.getElementById('f_lat').value) || null;
|
||||
const lng = parseFloat(document.getElementById('f_lng').value) || null;
|
||||
initModalMap(lat, lng);
|
||||
});
|
||||
|
||||
// ── Modal logic ───────────────────────────────────────────────────────────
|
||||
let editingId = null;
|
||||
const modal = new bootstrap.Modal(lotModalEl, { backdrop: 'static' });
|
||||
|
||||
document.getElementById('addLotBtn').onclick = () => {
|
||||
editingId = null;
|
||||
document.getElementById('lotForm').reset();
|
||||
document.getElementById('modalLabel').textContent = 'إضافة موقف جديد';
|
||||
document.getElementById('submitText').textContent = 'حفظ الموقف';
|
||||
modal.show();
|
||||
};
|
||||
|
||||
async function editLot(id) {
|
||||
editingId = id;
|
||||
setBtnLoading(true);
|
||||
try {
|
||||
const { data } = await fetch(`/admin/parking-lots/${id}`).then(r => r.json());
|
||||
document.getElementById('modalLabel').textContent = 'تعديل: ' + data.name;
|
||||
document.getElementById('submitText').textContent = 'تحديث الموقف';
|
||||
document.getElementById('f_name').value = data.name;
|
||||
document.getElementById('f_capacity').value = data.total_capacity;
|
||||
document.getElementById('f_price').value = data.price_per_hour;
|
||||
document.getElementById('f_hours').value = data.working_hours;
|
||||
document.getElementById('f_lat').value = data.latitude;
|
||||
document.getElementById('f_lng').value = data.longitude;
|
||||
document.getElementById('f_address').value = data.address;
|
||||
modal.show();
|
||||
} catch { alert('خطأ في تحميل البيانات'); }
|
||||
finally { setBtnLoading(false); }
|
||||
}
|
||||
|
||||
document.getElementById('lotForm').onsubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
setBtnLoading(true);
|
||||
try {
|
||||
const res = await fetch(editingId ? `/admin/parking-lots/${editingId}` : '/admin/parking-lots', {
|
||||
method: editingId ? 'PUT' : 'POST',
|
||||
body: new FormData(e.target)
|
||||
});
|
||||
const result = await res.json();
|
||||
result.success ? location.reload() : alert(result.message || 'خطأ في العملية');
|
||||
} catch { alert('خطأ في الاتصال'); }
|
||||
finally { setBtnLoading(false); }
|
||||
};
|
||||
|
||||
function setBtnLoading(on) {
|
||||
document.getElementById('submitBtn').disabled = on;
|
||||
document.getElementById('submitSpinner').classList.toggle('d-none', !on);
|
||||
}
|
||||
|
||||
function toggleStatus(id) {
|
||||
if (!confirm('تغيير حالة الموقف؟')) return;
|
||||
fetch(`/admin/parking-lots/${id}/toggle`, {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content }
|
||||
}).then(r => r.json()).then(d => d.success ? location.reload() : alert(d.message));
|
||||
}
|
||||
|
||||
document.getElementById('searchInput').addEventListener('keypress', e => {
|
||||
if (e.key === 'Enter') doSearch();
|
||||
});
|
||||
function doSearch() {
|
||||
const t = document.getElementById('searchInput').value;
|
||||
window.location.href = `/admin/parking-lots?search=${encodeURIComponent(t)}`;
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@endsection
|
||||
Reference in new issue
Block a user