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
|
||||
@@ -0,0 +1,87 @@
|
||||
@extends('layouts.auth')
|
||||
@section('title', 'تسجيل الدخول — دمشق باركينغ')
|
||||
|
||||
@section('content')
|
||||
|
||||
<h2 class="fw-800 mb-1" style="font-size:1.35rem;color:#0f172a;">تسجيل الدخول</h2>
|
||||
<p class="text-sm mb-4" style="color:#64748b;">أدخل بياناتك للوصول إلى لوحة التحكم</p>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="alert alert-danger border-0 rounded-3 d-flex align-items-center gap-2 py-2 mb-4">
|
||||
<i class="bi bi-exclamation-triangle-fill flex-shrink-0"></i>
|
||||
<span class="text-sm">{{ $errors->first() }}</span>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('login') }}">
|
||||
@csrf
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">البريد الإلكتروني</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="background:#f8fafc;border-color:#e2e8f0;border-left:none;">
|
||||
<i class="bi bi-envelope" style="color:#94a3b8;"></i>
|
||||
</span>
|
||||
<input type="email" name="email" value="{{ old('email') }}" required
|
||||
class="form-control @error('email') is-invalid @enderror"
|
||||
style="border-right:none;border-color:#e2e8f0;"
|
||||
placeholder="[email protected]" dir="ltr">
|
||||
@error('email')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||
<label class="form-label mb-0">كلمة السر</label>
|
||||
<a href="#" class="text-xs" style="color:#6366f1;text-decoration:none;">نسيت كلمة السر؟</a>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="background:#f8fafc;border-color:#e2e8f0;border-left:none;">
|
||||
<i class="bi bi-lock" style="color:#94a3b8;"></i>
|
||||
</span>
|
||||
<input type="password" name="password" required
|
||||
class="form-control @error('password') is-invalid @enderror"
|
||||
style="border-right:none;border-color:#e2e8f0;"
|
||||
placeholder="••••••••" dir="ltr">
|
||||
@error('password')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" name="remember" id="remember">
|
||||
<label class="form-check-label text-sm" for="remember" style="color:#64748b;">تذكرني</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="btn btn-lg w-100 fw-700"
|
||||
style="background:#6366f1;color:#fff;border:none;font-family:'Cairo',sans-serif;border-radius:.5rem;">
|
||||
<i class="bi bi-box-arrow-in-left me-2"></i>
|
||||
دخول
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-divider"><span>أو</span></div>
|
||||
|
||||
<a href="{{ route('register') }}"
|
||||
class="btn btn-lg w-100 fw-600"
|
||||
style="background:#f8fafc;color:#0f172a;border:1px solid #e2e8f0;font-family:'Cairo',sans-serif;border-radius:.5rem;">
|
||||
<i class="bi bi-person-plus me-2"></i>
|
||||
إنشاء حساب جديد
|
||||
</a>
|
||||
|
||||
<div class="mt-4 p-3 rounded-3" style="background:#f0f9ff;border:1px solid #bae6fd;">
|
||||
<p class="text-xs fw-700 mb-2" style="color:#0369a1;">
|
||||
<i class="bi bi-key-fill me-1"></i>حسابات الاختبار
|
||||
</p>
|
||||
<p class="text-xs mb-1" style="color:#0369a1;">
|
||||
<strong>مدير:</strong> admin@damascusparking.com / admin123
|
||||
</p>
|
||||
<p class="text-xs mb-0" style="color:#0369a1;">
|
||||
<strong>مشغّل:</strong> operator@damascusparking.com / operator123
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,92 @@
|
||||
@extends('layouts.auth')
|
||||
@section('title', 'إنشاء حساب — دمشق باركينغ')
|
||||
|
||||
@section('content')
|
||||
|
||||
<h2 class="fw-800 mb-1" style="font-size:1.35rem;color:#0f172a;">إنشاء حساب جديد</h2>
|
||||
<p class="text-sm mb-4" style="color:#64748b;">انضم إلى دمشق باركينغ الآن</p>
|
||||
|
||||
@if($errors->any())
|
||||
<div class="alert alert-danger border-0 rounded-3 d-flex align-items-center gap-2 py-2 mb-4">
|
||||
<i class="bi bi-exclamation-triangle-fill flex-shrink-0"></i>
|
||||
<span class="text-sm">{{ $errors->first() }}</span>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('register.action') }}">
|
||||
@csrf
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">الاسم الكامل</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="background:#f8fafc;border-color:#e2e8f0;border-left:none;">
|
||||
<i class="bi bi-person" style="color:#94a3b8;"></i>
|
||||
</span>
|
||||
<input type="text" name="name" value="{{ old('name') }}" required
|
||||
class="form-control @error('name') is-invalid @enderror"
|
||||
style="border-right:none;border-color:#e2e8f0;"
|
||||
placeholder="الاسم الكامل">
|
||||
@error('name')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">البريد الإلكتروني</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="background:#f8fafc;border-color:#e2e8f0;border-left:none;">
|
||||
<i class="bi bi-envelope" style="color:#94a3b8;"></i>
|
||||
</span>
|
||||
<input type="email" name="email" value="{{ old('email') }}" required
|
||||
class="form-control @error('email') is-invalid @enderror"
|
||||
style="border-right:none;border-color:#e2e8f0;"
|
||||
placeholder="[email protected]" dir="ltr">
|
||||
@error('email')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">كلمة السر</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="background:#f8fafc;border-color:#e2e8f0;border-left:none;">
|
||||
<i class="bi bi-lock" style="color:#94a3b8;"></i>
|
||||
</span>
|
||||
<input type="password" name="password" required
|
||||
class="form-control @error('password') is-invalid @enderror"
|
||||
style="border-right:none;border-color:#e2e8f0;"
|
||||
placeholder="••••••••" dir="ltr">
|
||||
@error('password')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label">تأكيد كلمة السر</label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text" style="background:#f8fafc;border-color:#e2e8f0;border-left:none;">
|
||||
<i class="bi bi-lock-fill" style="color:#94a3b8;"></i>
|
||||
</span>
|
||||
<input type="password" name="password_confirmation" required
|
||||
class="form-control"
|
||||
style="border-right:none;border-color:#e2e8f0;"
|
||||
placeholder="••••••••" dir="ltr">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit"
|
||||
class="btn btn-lg w-100 fw-700"
|
||||
style="background:#059669;color:#fff;border:none;font-family:'Cairo',sans-serif;border-radius:.5rem;">
|
||||
<i class="bi bi-person-check me-2"></i>
|
||||
إنشاء الحساب
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="auth-divider"><span>أو</span></div>
|
||||
|
||||
<a href="{{ route('login') }}"
|
||||
class="btn btn-lg w-100 fw-600"
|
||||
style="background:#f8fafc;color:#0f172a;border:1px solid #e2e8f0;font-family:'Cairo',sans-serif;border-radius:.5rem;">
|
||||
<i class="bi bi-box-arrow-in-left me-2"></i>
|
||||
تسجيل الدخول
|
||||
</a>
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,649 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="theme-color" content="#2c3e50">
|
||||
<meta name="referrer" content="strict-origin-when-cross-origin">
|
||||
<title>مواقف السيارات في دمشق</title>
|
||||
|
||||
<!-- Leaflet CSS for interactive map -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
|
||||
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
input, button { -webkit-user-select: text; user-select: text; }
|
||||
|
||||
body {
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
padding-bottom: 70px;
|
||||
padding-bottom: calc(70px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
/* ===== HEADER ===== */
|
||||
header {
|
||||
background: linear-gradient(135deg, #2c3e50, #4a6491);
|
||||
color: white;
|
||||
padding: 16px 0;
|
||||
text-align: center;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
||||
border-bottom: 4px solid #e74c3c;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.logo { font-size: 24px; }
|
||||
h1 { font-size: 22px; font-weight: 700; }
|
||||
.subtitle {
|
||||
text-align: center;
|
||||
margin: 12px 0 20px;
|
||||
color: #7f8c8d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* ===== SEARCH BAR ===== */
|
||||
.search-bar {
|
||||
display: flex;
|
||||
margin: 16px 0;
|
||||
gap: 8px;
|
||||
background: white;
|
||||
padding: 8px;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 10px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.search-bar input {
|
||||
flex: 1;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #e1e8ed;
|
||||
border-radius: 8px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.search-bar button {
|
||||
background: linear-gradient(135deg, #3498db, #2980b9);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
min-width: 70px;
|
||||
}
|
||||
|
||||
/* ===== CONTENT LAYOUT - DESKTOP DEFAULT ===== */
|
||||
.content {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.parking-list {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 19px;
|
||||
margin: 10px 0 15px;
|
||||
color: #2c3e50;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 2px solid #e74c3c;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.parking-item {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
margin-bottom: 14px;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
|
||||
border-right: 4px solid #3498db;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.parking-item:active { background: #f8f9ff; }
|
||||
|
||||
.parking-name {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #2c3e50;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.parking-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-item { display: flex; align-items: center; gap: 4px; color: #555; }
|
||||
|
||||
.available, .full, .limited {
|
||||
font-weight: bold;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.available { color: #27ae60; background: rgba(39, 174, 96, 0.12); }
|
||||
.full { color: #e74c3c; background: rgba(231, 76, 60, 0.12); }
|
||||
.limited { color: #f39c12; background: rgba(243, 156, 18, 0.12); }
|
||||
|
||||
/* ===== MAP CONTAINER ===== */
|
||||
.map-toggle-btn {
|
||||
display: none;
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
height: 400px;
|
||||
background-color: #e0e0e0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
#map { width: 100%; height: 100%; }
|
||||
|
||||
/* ===== MODAL ===== */
|
||||
.details-modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
z-index: 1000;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
max-width: 550px;
|
||||
border-radius: 20px 20px 0 0;
|
||||
padding: 24px 20px 30px;
|
||||
position: relative;
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.details-modal { align-items: center; }
|
||||
.modal-content { border-radius: 16px; }
|
||||
}
|
||||
|
||||
.close-btn {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
font-size: 26px;
|
||||
cursor: pointer;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
background: #f1f2f6;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: 21px;
|
||||
margin-bottom: 18px;
|
||||
color: #2c3e50;
|
||||
padding-left: 40px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 14px;
|
||||
border-bottom: 1px solid #f1f2f6;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.info-label { color: #7f8c8d; }
|
||||
.info-value { color: #2c3e50; font-weight: 600; }
|
||||
|
||||
.reserve-btn {
|
||||
background: linear-gradient(135deg, #27ae60, #2ecc71);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 16px;
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
width: 100%;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.reserve-btn:disabled {
|
||||
background: #bdc3c7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* ===== FOOTER NAV (YouTube-style) ===== */
|
||||
.app-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border-top: 1px solid #e1e8ed;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 8px 0 calc(12px + env(safe-area-inset-bottom));
|
||||
z-index: 200;
|
||||
box-shadow: 0 -2px 12px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
color: #7f8c8d;
|
||||
font-size: 11px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-item.active { color: #3498db; background: rgba(52, 152, 219, 0.1); }
|
||||
.nav-icon { font-size: 22px; }
|
||||
|
||||
/* ===== 📱 MOBILE-ONLY STYLES (max-width: 767px) ===== */
|
||||
@media (max-width: 767px) {
|
||||
.content {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.parking-list {
|
||||
order: 1;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.map-toggle-btn {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.map-container {
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
transition: height 0.3s ease;
|
||||
}
|
||||
|
||||
.map-container.expanded {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 16px 16px 0 0;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.nav-text { display: none; }
|
||||
.nav-item { min-width: 50px; }
|
||||
}
|
||||
|
||||
/* ===== DESKTOP: Hide app footer, show classic footer ===== */
|
||||
@media (min-width: 1024px) {
|
||||
.app-footer { display: none; }
|
||||
body { padding-bottom: 0; }
|
||||
.footer {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
padding: 24px;
|
||||
color: #7f8c8d;
|
||||
border-top: 1px solid #ecf0f1;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.footer { display: none; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<div class="header-content">
|
||||
<div class="logo">🅿️</div>
|
||||
<h1>مواقف السيارات في دمشق</h1>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<p class="subtitle">ابحث عن أقرب موقف سيارات متاح في دمشق واحجز مكانك بسهولة</p>
|
||||
|
||||
<div class="search-bar">
|
||||
<input type="text" id="searchInput" placeholder="ابحث باسم الموقف أو المنطقة..." autocomplete="off">
|
||||
<button id="searchBtn">بحث</button>
|
||||
</div>
|
||||
|
||||
<div class="content">
|
||||
<div class="parking-list">
|
||||
<button class="map-toggle-btn" id="mapToggleBtn">🗺️ عرض الخريطة</button>
|
||||
|
||||
<div class="map-container" id="mapContainer">
|
||||
<div id="map"></div>
|
||||
</div>
|
||||
|
||||
<h2 class="section-title">مواقف السيارات المتاحة</h2>
|
||||
<div id="parkingList"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Details Modal -->
|
||||
<div class="details-modal" id="detailsModal">
|
||||
<div class="modal-content">
|
||||
<span class="close-btn" id="closeModal">×</span>
|
||||
<h2 class="modal-title" id="modalTitle">اسم الموقف</h2>
|
||||
<div class="modal-info">
|
||||
<div class="info-row"><span class="info-label">السعة الكلية:</span><span class="info-value" id="modalTotal">0</span></div>
|
||||
<div class="info-row"><span class="info-label">الأماكن المتاحة:</span><span class="info-value" id="modalAvailable">0</span></div>
|
||||
<div class="info-row"><span class="info-label">سعر الساعة:</span><span class="info-value" id="modalPrice">0</span></div>
|
||||
<div class="info-row"><span class="info-label">ساعات العمل:</span><span class="info-value" id="modalHours">24/7</span></div>
|
||||
<div class="info-row"><span class="info-label">العنوان:</span><span class="info-value" id="modalAddress">...</span></div>
|
||||
</div>
|
||||
<button class="reserve-btn" id="reserveBtn">احجز مكان</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- YouTube-Style Footer Nav -->
|
||||
<nav class="app-footer">
|
||||
<a href="#" class="nav-item active" data-page="home"><span class="nav-icon">🏠</span><span class="nav-text">الرئيسية</span></a>
|
||||
<a href="#" class="nav-item" data-page="map"><span class="nav-icon">🗺️</span><span class="nav-text">الخريطة</span></a>
|
||||
<a href="#" class="nav-item" data-page="favorites"><span class="nav-icon">❤️</span><span class="nav-text">المفضلة</span></a>
|
||||
<a href="#" class="nav-item" data-page="history"><span class="nav-icon">🕒</span><span class="nav-text">السجل</span></a>
|
||||
<a href="#" class="nav-item" data-page="profile"><span class="nav-icon">👤</span><span class="nav-text">حسابي</span></a>
|
||||
</nav>
|
||||
|
||||
<div class="footer">
|
||||
<div class="container">
|
||||
<p>تطبيق مواقف السيارات في دمشق © 2026 - جميع الحقوق محفوظة</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Leaflet JS -->
|
||||
<script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
|
||||
|
||||
<script>
|
||||
const parkingData = [
|
||||
{ id: 1, name: "موقف ساحة الأمويين", address: "ساحة الأمويين، وسط مدينة دمشق", totalSpaces: 150, availableSpaces: 32, pricePerHour: 500, hours: "24/7", lat: 33.5138, lng: 36.2765 },
|
||||
{ id: 2, name: "موقف الحرمون", address: "شارع الحرمون، المزة", totalSpaces: 80, availableSpaces: 0, pricePerHour: 300, hours: "06:00 - 00:00", lat: 33.4809, lng: 36.2586 },
|
||||
{ id: 3, name: "موقف الجامعة", address: "جامعة دمشق، البرامكة", totalSpaces: 200, availableSpaces: 45, pricePerHour: 200, hours: "07:00 - 20:00", lat: 33.5102, lng: 36.2784 },
|
||||
{ id: 4, name: "موقف الشعلان", address: "حي الشعلان، أبو رمانة", totalSpaces: 60, availableSpaces: 12, pricePerHour: 400, hours: "24/7", lat: 33.5215, lng: 36.2932 },
|
||||
{ id: 5, name: "موقف المطار", address: "مطار دمشق الدولي", totalSpaces: 300, availableSpaces: 120, pricePerHour: 600, hours: "24/7", lat: 33.4144, lng: 36.5200 },
|
||||
{ id: 6, name: "موقف التكية السليمانية", address: "التكية السليمانية، المرجة", totalSpaces: 100, availableSpaces: 25, pricePerHour: 350, hours: "08:00 - 22:00", lat: 33.5147, lng: 36.3065 },
|
||||
{ id: 7, name: "موقف صحيفة تشرين", address: "جادة صحيفة تشرين، المالكي", totalSpaces: 70, availableSpaces: 5, pricePerHour: 450, hours: "07:00 - 23:00", lat: 33.5180, lng: 36.2760 },
|
||||
{ id: 8, name: "موقف شارع بغداد", address: "شارع بغداد، الجسر الأبيض", totalSpaces: 90, availableSpaces: 40, pricePerHour: 300, hours: "24/7", lat: 33.5210, lng: 36.2900 }
|
||||
];
|
||||
|
||||
const parkingList = document.getElementById('parkingList');
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const searchBtn = document.getElementById('searchBtn');
|
||||
const detailsModal = document.getElementById('detailsModal');
|
||||
const closeModal = document.getElementById('closeModal');
|
||||
const reserveBtn = document.getElementById('reserveBtn');
|
||||
const mapToggleBtn = document.getElementById('mapToggleBtn');
|
||||
const mapContainer = document.getElementById('mapContainer');
|
||||
|
||||
let map, markers = [];
|
||||
let currentParking = null;
|
||||
|
||||
function getAvailabilityStatus(available, total) {
|
||||
if (available === 0) return 'full';
|
||||
if (available < total * 0.2) return 'limited';
|
||||
return 'available';
|
||||
}
|
||||
|
||||
function formatPrice(price) {
|
||||
return `${price.toLocaleString('ar-SY')} ل.س`;
|
||||
}
|
||||
|
||||
function initMap() {
|
||||
map = L.map('map', { attributionControl: false }).setView([33.5138, 36.2765], 12);
|
||||
|
||||
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
||||
attribution: '',
|
||||
subdomains: 'abcd',
|
||||
maxZoom: 20
|
||||
}).addTo(map);
|
||||
|
||||
parkingData.forEach(parking => {
|
||||
const marker = L.marker([parking.lat, parking.lng]).addTo(map);
|
||||
marker.bindPopup(`
|
||||
<b>${parking.name}</b><br>
|
||||
${parking.address}<br>
|
||||
<span style="color:${parking.availableSpaces > 0 ? '#27ae60' : '#e74c3c'}">
|
||||
${parking.availableSpaces} متاح من ${parking.totalSpaces}
|
||||
</span>
|
||||
`);
|
||||
marker.parkingId = parking.id;
|
||||
markers.push(marker);
|
||||
});
|
||||
|
||||
if (markers.length > 0) {
|
||||
const group = new L.featureGroup(markers);
|
||||
const bounds = group.getBounds().pad(0.1);
|
||||
map.fitBounds(bounds, {
|
||||
padding: [20, 20],
|
||||
maxZoom: 14
|
||||
});
|
||||
} else {
|
||||
map.setView([33.5138, 36.2765], 13);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (map) map.invalidateSize();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function displayParkingList(parkings) {
|
||||
parkingList.innerHTML = '';
|
||||
|
||||
if (parkings.length === 0) {
|
||||
parkingList.innerHTML = `
|
||||
<div class="parking-item" style="text-align:center;padding:30px;background:#f8f9fa">
|
||||
<p style="font-size:16px;color:#7f8c8d">لا توجد مواقف مطابقة للبحث</p>
|
||||
<p style="margin-top:8px;font-size:13px;color:#95a5a6">جرب استخدام كلمات بحث أخرى</p>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
parkings.forEach(parking => {
|
||||
const status = getAvailabilityStatus(parking.availableSpaces, parking.totalSpaces);
|
||||
const statusText = {
|
||||
'available': `${parking.availableSpaces} متاحة`,
|
||||
'limited': `${parking.availableSpaces} متاحة (محدود)`,
|
||||
'full': 'ممتلئ'
|
||||
}[status];
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'parking-item';
|
||||
item.innerHTML = `
|
||||
<div class="parking-name"><span>🅿️</span>${parking.name}</div>
|
||||
<div class="parking-info">
|
||||
<div class="info-item"><span>📍</span><span>${parking.address}</span></div>
|
||||
</div>
|
||||
<div class="parking-info">
|
||||
<div class="info-item"><span>🚗</span><span>${parking.totalSpaces} مكان</span></div>
|
||||
<div class="${status}">${statusText}</div>
|
||||
</div>
|
||||
<div class="parking-info">
|
||||
<div class="info-item"><span>💰</span><span>${formatPrice(parking.pricePerHour)}/ساعة</span></div>
|
||||
<div class="info-item"><span>🕒</span><span>${parking.hours}</span></div>
|
||||
</div>
|
||||
`;
|
||||
item.addEventListener('click', () => {
|
||||
showParkingDetails(parking);
|
||||
if (window.innerWidth < 768 && map) {
|
||||
map.setView([parking.lat, parking.lng], 14);
|
||||
if (!mapContainer.classList.contains('expanded')) {
|
||||
mapContainer.classList.add('expanded');
|
||||
mapToggleBtn.textContent = '🔼 إخفاء الخريطة';
|
||||
setTimeout(() => map.invalidateSize(), 300);
|
||||
}
|
||||
}
|
||||
});
|
||||
parkingList.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
||||
function showParkingDetails(parking) {
|
||||
currentParking = parking;
|
||||
document.getElementById('modalTitle').textContent = parking.name;
|
||||
document.getElementById('modalTotal').textContent = parking.totalSpaces;
|
||||
document.getElementById('modalAvailable').textContent = parking.availableSpaces;
|
||||
document.getElementById('modalPrice').textContent = formatPrice(parking.pricePerHour) + '/ساعة';
|
||||
document.getElementById('modalHours').textContent = parking.hours;
|
||||
document.getElementById('modalAddress').textContent = parking.address;
|
||||
|
||||
if (parking.availableSpaces > 0) {
|
||||
reserveBtn.textContent = 'احجز مكان الآن';
|
||||
reserveBtn.disabled = false;
|
||||
} else {
|
||||
reserveBtn.textContent = 'لا توجد أماكن متاحة';
|
||||
reserveBtn.disabled = true;
|
||||
}
|
||||
|
||||
detailsModal.style.display = 'flex';
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
function closeDetailsModal() {
|
||||
detailsModal.style.display = 'none';
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
function searchParkings() {
|
||||
const term = searchInput.value.toLowerCase().trim();
|
||||
const filtered = term === '' ? parkingData : parkingData.filter(p =>
|
||||
p.name.toLowerCase().includes(term) ||
|
||||
p.address.toLowerCase().includes(term)
|
||||
);
|
||||
displayParkingList(filtered);
|
||||
|
||||
if (map) {
|
||||
markers.forEach(marker => {
|
||||
const parking = parkingData.find(p => p.id === marker.parkingId);
|
||||
if (!parking) return;
|
||||
|
||||
if (filtered.some(fp => fp.id === parking.id)) {
|
||||
if (!map.hasLayer(marker)) marker.addTo(map);
|
||||
} else {
|
||||
if (map.hasLayer(marker)) map.removeLayer(marker);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function reserveParking() {
|
||||
if (!currentParking || currentParking.availableSpaces <= 0) return;
|
||||
|
||||
currentParking.availableSpaces--;
|
||||
showParkingDetails(currentParking);
|
||||
displayParkingList(parkingData);
|
||||
|
||||
const marker = markers.find(m => m.parkingId === currentParking.id);
|
||||
if (marker) {
|
||||
marker.setPopupContent(`
|
||||
<b>${currentParking.name}</b><br>
|
||||
${currentParking.address}<br>
|
||||
<span style="color:${currentParking.availableSpaces > 0 ? '#27ae60' : '#e74c3c'}">
|
||||
${currentParking.availableSpaces} متاح من ${currentParking.totalSpaces}
|
||||
</span>
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
mapToggleBtn.addEventListener('click', () => {
|
||||
const isExpanded = mapContainer.classList.toggle('expanded');
|
||||
mapToggleBtn.textContent = isExpanded ? '🔼 إخفاء الخريطة' : '🗺️ عرض الخريطة';
|
||||
if (map) setTimeout(() => map.invalidateSize(), 300);
|
||||
});
|
||||
|
||||
searchBtn.addEventListener('click', searchParkings);
|
||||
searchInput.addEventListener('keyup', (e) => { if (e.key === 'Enter') searchParkings(); });
|
||||
closeModal.addEventListener('click', closeDetailsModal);
|
||||
reserveBtn.addEventListener('click', reserveParking);
|
||||
detailsModal.addEventListener('click', (e) => { if (e.target === detailsModal) closeDetailsModal(); });
|
||||
|
||||
document.querySelectorAll('.nav-item').forEach(item => {
|
||||
item.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
|
||||
item.classList.add('active');
|
||||
|
||||
const page = item.dataset.page;
|
||||
if (page === 'map' && window.innerWidth < 768) {
|
||||
if (!mapContainer.classList.contains('expanded')) {
|
||||
mapContainer.classList.add('expanded');
|
||||
mapToggleBtn.textContent = '🔼 إخفاء الخريطة';
|
||||
if (map) setTimeout(() => map.invalidateSize(), 300);
|
||||
}
|
||||
const mapSection = document.getElementById('mapContainer');
|
||||
if (mapSection) {
|
||||
mapSection.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
displayParkingList(parkingData);
|
||||
setTimeout(initMap, 100);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
if (map) map.invalidateSize();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,441 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>مواقف السيارات في دمشق</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
|
||||
@vite(['resources/css/app.scss', 'resources/js/app.js'])
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css">
|
||||
<style>
|
||||
/* Bootstrap resets max-width:100% on all <img> which breaks Leaflet tiles */
|
||||
.leaflet-container img { max-width: none !important; box-shadow: none !important; }
|
||||
.leaflet-container { direction: ltr; }
|
||||
</style>
|
||||
</head>
|
||||
<body style="background:#f1f5f9;font-family:'Cairo',sans-serif;">
|
||||
|
||||
{{-- ══ HEADER ══════════════════════════════════════════════════════════════ --}}
|
||||
<header class="public-header">
|
||||
<div class="container">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div style="width:40px;height:40px;background:#6366f1;border-radius:.625rem;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||
<i class="bi bi-p-square-fill text-white" style="font-size:1.25rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-800" style="color:#f8fafc;font-size:1.05rem;line-height:1.2;">دمشق باركينغ</div>
|
||||
<div style="color:#94a3b8;font-size:.72rem;">مواقف السيارات في دمشق</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
@include('partials.user-dropdown')
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ══ HERO SEARCH ═════════════════════════════════════════════════════════ --}}
|
||||
<div class="mob-hero-compact" style="background:linear-gradient(135deg,#0f172a 0%,#1e3a5f 100%);padding:2.5rem 0 3rem;">
|
||||
<div class="container text-center">
|
||||
<h1 class="fw-800 mb-2 d-none d-md-block" style="color:#f8fafc;font-size:clamp(1.4rem,4vw,2rem);">
|
||||
ابحث عن موقف سيارات في دمشق
|
||||
</h1>
|
||||
<p class="mb-4 d-none d-md-block" style="color:#94a3b8;font-size:.9rem;">
|
||||
تصفّح المواقف المتاحة، تحقق من الأسعار، واحجز مكانك
|
||||
</p>
|
||||
|
||||
<div class="mx-auto" style="max-width:520px;">
|
||||
<div class="d-flex gap-2" style="background:#1e293b;padding:.5rem;border-radius:.75rem;">
|
||||
<input type="text" id="searchInput"
|
||||
class="form-control"
|
||||
style="background:transparent;border:none;color:#f8fafc;font-family:'Cairo',sans-serif;box-shadow:none;"
|
||||
placeholder="اكتب اسم الموقف أو المنطقة...">
|
||||
<button id="searchBtn"
|
||||
class="btn fw-600 flex-shrink-0"
|
||||
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;padding:.5rem 1.25rem;">
|
||||
<i class="bi bi-search me-1"></i>بحث
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="searchStatus" class="mt-3 d-none">
|
||||
<span class="badge" style="background:rgba(255,255,255,.1);color:#cbd5e1;padding:.45em .9em;font-size:.8rem;">
|
||||
نتائج البحث عن "<span id="searchTerm"></span>"
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ══ MAIN CONTENT ════════════════════════════════════════════════════════ --}}
|
||||
<div class="container py-4 mob-nav-pad">
|
||||
<div class="row g-3">
|
||||
|
||||
{{-- Parking List (RTL: renders on right side, but we put it second so it's on LEFT visually) --}}
|
||||
{{-- Actually in RTL, order-1 = appears first = right side --}}
|
||||
{{-- List should be on LEFT (order-2 = left in RTL), Map on RIGHT (order-1 = right in RTL) --}}
|
||||
|
||||
{{-- MAP --}}
|
||||
<div class="col-lg-8 order-lg-1 mob-section-map">
|
||||
<div class="card h-100" style="min-height:520px;">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<span class="fw-700 text-sm" style="color:#0f172a;">
|
||||
<i class="bi bi-map me-1" style="color:#6366f1;"></i>
|
||||
خريطة المواقف
|
||||
</span>
|
||||
<span class="badge badge-soft-primary text-xs" id="map-count">--</span>
|
||||
</div>
|
||||
<div class="card-body p-0" style="border-radius:0 0 .75rem .75rem;overflow:hidden;">
|
||||
<div id="map" style="height:500px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- LIST --}}
|
||||
<div class="col-lg-4 order-lg-2 mob-section-list">
|
||||
<div class="card mob-sticky-desktop" style="position:sticky;top:1rem;">
|
||||
<div class="card-header d-flex align-items-center justify-content-between">
|
||||
<span class="fw-700 text-sm" style="color:#0f172a;">
|
||||
<i class="bi bi-car-front me-1" style="color:#6366f1;"></i>
|
||||
المواقف المتاحة
|
||||
</span>
|
||||
<span class="badge badge-soft-success text-xs" id="list-count">{{ $lots->count() }} موقف</span>
|
||||
</div>
|
||||
<div style="max-height:500px;overflow-y:auto;scrollbar-width:thin;" id="parkingList">
|
||||
@forelse($lots as $lot)
|
||||
@php
|
||||
$avail = $lot['avail'];
|
||||
$total = $lot['total'];
|
||||
if ($avail === 0) {
|
||||
$avCls = 'avail-full';
|
||||
$avText = 'ممتلئ';
|
||||
} elseif ($avail < $total * 0.2) {
|
||||
$avCls = 'avail-limited';
|
||||
$avText = $avail . ' محدود';
|
||||
} else {
|
||||
$avCls = 'avail-open';
|
||||
$avText = $avail . ' متاح';
|
||||
}
|
||||
@endphp
|
||||
<div class="parking-card p-3 border-bottom" id="card-{{ $lot['id'] }}"
|
||||
onclick="showModal(lots.find(x=>x.id==={{ $lot['id'] }})); highlightCard({{ $lot['id'] }});">
|
||||
<div class="d-flex align-items-start justify-content-between gap-2 mb-1">
|
||||
<span class="fw-700" style="color:#0f172a;font-size:.9rem;">{{ $lot['name'] }}</span>
|
||||
<span class="badge {{ $avCls }} flex-shrink-0" style="font-size:.72rem;">{{ $avText }}</span>
|
||||
</div>
|
||||
<p class="text-xs mb-2" style="color:#94a3b8;">{{ $lot['address'] }}</p>
|
||||
<div class="d-flex gap-3 text-xs" style="color:#64748b;">
|
||||
<span><i class="bi bi-car-front me-1"></i>{{ $total }}</span>
|
||||
<span><i class="bi bi-currency-exchange me-1"></i>{{ number_format($lot['price']) }} ر.س</span>
|
||||
<span><i class="bi bi-clock me-1"></i>{{ $lot['hours'] }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="text-center py-5" style="color:#94a3b8;">
|
||||
<i class="bi bi-p-square d-block mb-2" style="font-size:1.8rem;opacity:.4;"></i>
|
||||
<span class="text-sm">لا توجد مواقف متاحة</span>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ══ DETAILS MODAL ═══════════════════════════════════════════════════════ --}}
|
||||
<div class="modal fade" id="detailsModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header" style="border-bottom:1px solid #f1f5f9;">
|
||||
<h5 class="modal-title fw-800" id="modalName" style="font-size:1rem;color:#0f172a;"></h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body p-3">
|
||||
|
||||
{{-- Availability Bar --}}
|
||||
<div class="mb-3 p-3 rounded-3" style="background:#f8fafc;">
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<span class="text-xs fw-600" style="color:#64748b;">الإشغال</span>
|
||||
<span class="text-xs fw-700" id="modalPct" style="color:#0f172a;">--%</span>
|
||||
</div>
|
||||
<div class="progress" style="height:8px;border-radius:4px;background:#e2e8f0;">
|
||||
<div class="progress-bar" id="modalBar" role="progressbar"
|
||||
style="border-radius:4px;transition:width .5s ease;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<div class="text-center">
|
||||
<div class="fw-800" id="modalAvailable" style="color:#10b981;font-size:1.3rem;line-height:1;">--</div>
|
||||
<div class="text-xs" style="color:#64748b;">متاح</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="fw-800" id="modalTotal" style="color:#475569;font-size:1.3rem;line-height:1;">--</div>
|
||||
<div class="text-xs" style="color:#64748b;">إجمالي</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Info Grid --}}
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<div class="p-3 rounded-3" style="background:#f0fdf4;">
|
||||
<div class="text-xs fw-600 mb-1" style="color:#64748b;">السعر / ساعة</div>
|
||||
<div class="fw-800" id="modalPrice" style="color:#059669;font-size:1rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="p-3 rounded-3" style="background:#f0f9ff;">
|
||||
<div class="text-xs fw-600 mb-1" style="color:#64748b;">ساعات العمل</div>
|
||||
<div class="fw-700" id="modalHours" style="color:#0369a1;font-size:.9rem;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="p-3 rounded-3" style="background:#fafafa;">
|
||||
<div class="text-xs fw-600 mb-1" style="color:#64748b;">
|
||||
<i class="bi bi-geo-alt me-1"></i>العنوان
|
||||
</div>
|
||||
<div class="text-sm" id="modalAddress" style="color:#475569;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="border-top:1px solid #f1f5f9;gap:.5rem;">
|
||||
<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="button" id="reserveBtn"
|
||||
class="btn btn-sm fw-700"
|
||||
style="background:#10b981;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;padding:.4rem 1.25rem;">
|
||||
<i class="bi bi-calendar-check me-1"></i>احجز الآن
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ══ MOBILE BOTTOM NAV ══════════════════════════════════════════════════ --}}
|
||||
<nav class="mobile-bottom-nav" aria-label="التنقل الرئيسي">
|
||||
<a href="#" class="mob-nav-item active" data-tab="list"
|
||||
onclick="switchMobSection('list'); return false;">
|
||||
<i class="bi bi-list-ul"></i>
|
||||
<span>المواقف</span>
|
||||
</a>
|
||||
<a href="#" class="mob-nav-item" data-tab="map"
|
||||
onclick="switchMobSection('map'); return false;">
|
||||
<i class="bi bi-map-fill"></i>
|
||||
<span>الخريطة</span>
|
||||
</a>
|
||||
<a href="#" class="mob-nav-item" data-tab="search"
|
||||
onclick="switchMobSection('list'); window.scrollTo({top:0,behavior:'smooth'}); setTimeout(()=>document.getElementById('searchInput').focus(),280); return false;">
|
||||
<i class="bi bi-search"></i>
|
||||
<span>بحث</span>
|
||||
</a>
|
||||
@auth
|
||||
<a href="{{ route('user.dashboard') }}" class="mob-nav-item">
|
||||
<div style="width:26px;height:26px;background:#6366f1;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:.7rem;color:#fff;margin-bottom:1px;">
|
||||
{{ mb_substr(auth()->user()->name, 0, 1) }}
|
||||
</div>
|
||||
<span>حسابي</span>
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="mob-nav-item">
|
||||
<i class="bi bi-person-fill"></i>
|
||||
<span>الدخول</span>
|
||||
</a>
|
||||
@endauth
|
||||
</nav>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.js"></script>
|
||||
<script>
|
||||
// ── Parking Lots (from database) ──────────────────────────────────────
|
||||
const lots = {{ Js::from($lots) }};
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────
|
||||
function availability(l) {
|
||||
if (l.avail === 0) return { cls: 'avail-full', text: 'ممتلئ', pct: 100 };
|
||||
if (l.avail < l.total * .2) return { cls: 'avail-limited', text: `${l.avail} محدود`, pct: Math.round((l.total - l.avail) / l.total * 100) };
|
||||
return { cls: 'avail-open', text: `${l.avail} متاح`, pct: Math.round((l.total - l.avail) / l.total * 100) };
|
||||
}
|
||||
const fmtPrice = p => new Intl.NumberFormat('ar-SY').format(p) + ' ر.س';
|
||||
|
||||
// ── Map (lazy-init on mobile) ─────────────────────────────────────────
|
||||
let map = null, mapReady = false;
|
||||
|
||||
function initMap() {
|
||||
if (mapReady) { map?.invalidateSize(); return; }
|
||||
mapReady = true;
|
||||
|
||||
map = L.map('map').setView([33.5138, 36.2765], 12);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap'
|
||||
}).addTo(map);
|
||||
|
||||
lots.forEach(l => {
|
||||
const av = availability(l);
|
||||
const col = l.avail === 0 ? '#ef4444' : (l.avail < l.total * .2 ? '#f59e0b' : '#10b981');
|
||||
const icon = L.divIcon({
|
||||
html: `<div style="width:36px;height:36px;background:${col};border:3px solid #fff;border-radius:50% 50% 50% 0;transform:rotate(-45deg);box-shadow:0 2px 8px rgba(0,0,0,.25);"></div>`,
|
||||
iconSize: [36, 36],
|
||||
iconAnchor: [18, 36],
|
||||
className: ''
|
||||
});
|
||||
const m = L.marker([l.lat, l.lng], { icon }).addTo(map);
|
||||
m.bindPopup(`
|
||||
<div style="font-family:'Cairo',sans-serif;min-width:170px;padding:4px;">
|
||||
<strong style="font-size:.95rem;color:#0f172a;">${l.name}</strong>
|
||||
<p style="margin:4px 0;font-size:.78rem;color:#64748b;">${l.address}</p>
|
||||
<span style="background:${col}22;color:${col};padding:2px 8px;border-radius:99px;font-size:.78rem;font-weight:700;">${av.text}</span>
|
||||
<hr style="margin:6px 0;border-color:#f1f5f9;">
|
||||
<span style="font-size:.78rem;color:#64748b;">💰 ${fmtPrice(l.price)} / ساعة</span>
|
||||
</div>
|
||||
`);
|
||||
m.on('click', () => showModal(l));
|
||||
});
|
||||
|
||||
if (lots.length) {
|
||||
const group = L.featureGroup(lots.map(l => L.marker([l.lat, l.lng])));
|
||||
map.fitBounds(group.getBounds().pad(.1));
|
||||
}
|
||||
document.getElementById('map-count').textContent = lots.length + ' موقف';
|
||||
}
|
||||
|
||||
// ── Mobile Section Switch ─────────────────────────────────────────────
|
||||
function switchMobSection(name) {
|
||||
const mapEl = document.querySelector('.mob-section-map');
|
||||
const listEl = document.querySelector('.mob-section-list');
|
||||
if (name === 'map') {
|
||||
listEl?.classList.add('mob-hidden');
|
||||
mapEl?.classList.remove('mob-hidden');
|
||||
setTimeout(initMap, 150);
|
||||
} else {
|
||||
mapEl?.classList.add('mob-hidden');
|
||||
listEl?.classList.remove('mob-hidden');
|
||||
}
|
||||
document.querySelectorAll('.mob-nav-item[data-tab]').forEach(b => {
|
||||
b.classList.toggle('active', b.dataset.tab === name);
|
||||
});
|
||||
}
|
||||
|
||||
// ── List ──────────────────────────────────────────────────────────────
|
||||
let activeId = null;
|
||||
|
||||
function renderList(data) {
|
||||
const el = document.getElementById('parkingList');
|
||||
document.getElementById('list-count').textContent = data.length + ' موقف';
|
||||
|
||||
if (!data.length) {
|
||||
el.innerHTML = `
|
||||
<div class="text-center py-5" style="color:#94a3b8;">
|
||||
<i class="bi bi-search d-block mb-2" style="font-size:1.8rem;opacity:.4;"></i>
|
||||
<span class="text-sm">لا توجد نتائج</span>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
el.innerHTML = data.map(l => {
|
||||
const av = availability(l);
|
||||
return `
|
||||
<div class="parking-card p-3 border-bottom" id="card-${l.id}"
|
||||
onclick="showModal(lots.find(x=>x.id===${l.id})); highlightCard(${l.id});">
|
||||
<div class="d-flex align-items-start justify-content-between gap-2 mb-1">
|
||||
<span class="fw-700" style="color:#0f172a;font-size:.9rem;">${l.name}</span>
|
||||
<span class="badge ${av.cls} flex-shrink-0" style="font-size:.72rem;">${av.text}</span>
|
||||
</div>
|
||||
<p class="text-xs mb-2" style="color:#94a3b8;">${l.address}</p>
|
||||
<div class="d-flex gap-3 text-xs" style="color:#64748b;">
|
||||
<span><i class="bi bi-car-front me-1"></i>${l.total}</span>
|
||||
<span><i class="bi bi-currency-exchange me-1"></i>${fmtPrice(l.price)}</span>
|
||||
<span><i class="bi bi-clock me-1"></i>${l.hours}</span>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function highlightCard(id) {
|
||||
if (activeId) document.getElementById('card-' + activeId)?.classList.remove('active-card');
|
||||
activeId = id;
|
||||
document.getElementById('card-' + id)?.classList.add('active-card');
|
||||
}
|
||||
|
||||
// ── Modal ─────────────────────────────────────────────────────────────
|
||||
// Bootstrap is deferred (loaded as ES module), so we lazy-init the
|
||||
// modal on first use rather than at script parse time.
|
||||
let modal = null;
|
||||
function getModal() {
|
||||
if (!modal) modal = new bootstrap.Modal(document.getElementById('detailsModal'));
|
||||
return modal;
|
||||
}
|
||||
let current = null;
|
||||
|
||||
function showModal(l) {
|
||||
current = l;
|
||||
const av = availability(l);
|
||||
const pct = Math.round((l.total - l.avail) / l.total * 100);
|
||||
const barCol = l.avail === 0 ? '#ef4444' : (l.avail < l.total * .2 ? '#f59e0b' : '#10b981');
|
||||
|
||||
document.getElementById('modalName').textContent = l.name;
|
||||
document.getElementById('modalAvailable').textContent = l.avail;
|
||||
document.getElementById('modalTotal').textContent = l.total;
|
||||
document.getElementById('modalPrice').textContent = fmtPrice(l.price);
|
||||
document.getElementById('modalHours').textContent = l.hours;
|
||||
document.getElementById('modalAddress').textContent = l.address;
|
||||
document.getElementById('modalPct').textContent = pct + '%';
|
||||
document.getElementById('modalBar').style.cssText = `width:${pct}%;background:${barCol};border-radius:4px;`;
|
||||
|
||||
const btn = document.getElementById('reserveBtn');
|
||||
if (l.avail > 0) {
|
||||
btn.disabled = false;
|
||||
btn.style.background = '#10b981';
|
||||
btn.innerHTML = '<i class="bi bi-calendar-check me-1"></i>احجز الآن';
|
||||
} else {
|
||||
btn.disabled = true;
|
||||
btn.style.background = '#94a3b8';
|
||||
btn.innerHTML = '<i class="bi bi-x-circle me-1"></i>ممتلئ';
|
||||
}
|
||||
|
||||
if (map) map.setView([l.lat, l.lng], 16);
|
||||
getModal().show();
|
||||
}
|
||||
|
||||
// ── Reserve ───────────────────────────────────────────────────────────
|
||||
document.getElementById('reserveBtn').addEventListener('click', () => {
|
||||
if (!current || current.avail <= 0) return;
|
||||
alert(`تم حجز مكان في ${current.name}!\nالعنوان: ${current.address}\nالسعر: ${fmtPrice(current.price)} / ساعة`);
|
||||
current.avail--;
|
||||
getModal().hide();
|
||||
renderList(lots);
|
||||
});
|
||||
|
||||
// ── Search ────────────────────────────────────────────────────────────
|
||||
function doSearch() {
|
||||
const term = document.getElementById('searchInput').value.trim().toLowerCase();
|
||||
const res = term
|
||||
? lots.filter(l => l.name.includes(term) || l.address.includes(term))
|
||||
: lots;
|
||||
renderList(res);
|
||||
const status = document.getElementById('searchStatus');
|
||||
document.getElementById('searchTerm').textContent = term;
|
||||
status.classList.toggle('d-none', !term);
|
||||
}
|
||||
|
||||
document.getElementById('searchBtn').addEventListener('click', doSearch);
|
||||
document.getElementById('searchInput').addEventListener('keypress', e => {
|
||||
if (e.key === 'Enter') doSearch();
|
||||
});
|
||||
|
||||
// ── Init ──────────────────────────────────────────────────────────────
|
||||
document.getElementById('map-count').textContent = lots.length + ' موقف';
|
||||
if (window.innerWidth >= 768) {
|
||||
initMap(); // Desktop: initialize immediately
|
||||
} else {
|
||||
// Mobile: show list first, lazy-init map when map tab is tapped
|
||||
document.querySelector('.mob-section-map')?.classList.add('mob-hidden');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,213 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>@yield('title', 'دمشق باركينغ')</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
|
||||
@vite(['resources/css/app.scss', 'resources/js/app.js'])
|
||||
@yield('styles')
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="app-layout">
|
||||
|
||||
{{-- ════════════════════════════════════════
|
||||
SIDEBAR (appears on the RIGHT in RTL
|
||||
because it is the first flex child)
|
||||
════════════════════════════════════════ --}}
|
||||
<aside class="app-sidebar" id="appSidebar">
|
||||
|
||||
{{-- Logo --}}
|
||||
<a href="{{ route('admin.dashboard') }}" class="sidebar-logo">
|
||||
<div class="logo-icon">
|
||||
<i class="bi bi-p-square-fill"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="logo-text">دمشق باركينغ</div>
|
||||
<div class="logo-sub">لوحة الإدارة</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{{-- Navigation --}}
|
||||
<nav class="sidebar-nav">
|
||||
|
||||
<div class="sidebar-section">الرئيسية</div>
|
||||
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="sidebar-link {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
||||
<i class="bi bi-speedometer2 sidebar-icon"></i>
|
||||
<span>لوحة التحكم</span>
|
||||
</a>
|
||||
|
||||
<div class="sidebar-section">المواقف والحجوزات</div>
|
||||
|
||||
<a href="{{ route('admin.parking-lots.index') }}"
|
||||
class="sidebar-link {{ request()->routeIs('admin.parking-lots.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-buildings sidebar-icon"></i>
|
||||
<span>المواقف</span>
|
||||
</a>
|
||||
|
||||
<a href="{{ route('admin.bookings.active') }}"
|
||||
class="sidebar-link {{ request()->routeIs('admin.bookings.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-calendar-check sidebar-icon"></i>
|
||||
<span>الحجوزات النشطة</span>
|
||||
</a>
|
||||
|
||||
<div class="sidebar-section">التشغيل</div>
|
||||
|
||||
<a href="{{ route('operator.dashboard') }}"
|
||||
class="sidebar-link {{ request()->routeIs('operator.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-person-badge sidebar-icon"></i>
|
||||
<span>لوحة المشغّل</span>
|
||||
</a>
|
||||
|
||||
</nav>
|
||||
|
||||
{{-- Footer: user info + logout --}}
|
||||
<div class="sidebar-footer">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
<div class="user-avatar">
|
||||
{{ mb_substr(auth()->user()?->name ?? 'م', 0, 1) }}
|
||||
</div>
|
||||
<div style="min-width:0">
|
||||
<div class="user-name text-truncate">{{ auth()->user()?->name ?? 'المستخدم' }}</div>
|
||||
<div class="user-role">
|
||||
{{ auth()->user()?->role === 'admin' ? 'مدير النظام' : 'مشغّل' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="btn btn-sm w-100 mt-1 text-start"
|
||||
style="background:rgba(239,68,68,.12);color:#f87171;border:none;border-radius:.5rem;padding:.45rem .75rem;font-family:'Cairo',sans-serif;font-size:.82rem;">
|
||||
<i class="bi bi-box-arrow-left me-2"></i>تسجيل الخروج
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</aside>
|
||||
|
||||
{{-- ════════════════════════════════════════
|
||||
MAIN BODY
|
||||
════════════════════════════════════════ --}}
|
||||
<div class="app-body">
|
||||
|
||||
{{-- Topbar --}}
|
||||
<header class="app-topbar">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<button class="sidebar-toggle" id="sidebarToggle" aria-label="قائمة التنقل">
|
||||
<i class="bi bi-list"></i>
|
||||
</button>
|
||||
<h1 class="topbar-title">@yield('page-title', 'لوحة التحكم')</h1>
|
||||
</div>
|
||||
<div class="topbar-actions d-flex align-items-center gap-2">
|
||||
{{-- Desktop: link to public site --}}
|
||||
<a href="{{ route('parking.index') }}"
|
||||
class="btn btn-sm d-none d-md-inline-flex align-items-center"
|
||||
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;"
|
||||
target="_blank">
|
||||
<i class="bi bi-globe2 me-1"></i>الموقع العام
|
||||
</a>
|
||||
{{-- Mobile: user avatar + logout --}}
|
||||
<div class="d-flex d-md-none align-items-center gap-2">
|
||||
<div style="width:30px;height:30px;background:rgba(99,102,241,.12);border-radius:50%;display:flex;align-items:center;justify-content:center;color:#6366f1;font-weight:800;font-size:.82rem;flex-shrink:0;">
|
||||
{{ mb_substr(auth()->user()?->name ?? 'م', 0, 1) }}
|
||||
</div>
|
||||
<form method="POST" action="{{ route('logout') }}" style="margin:0;">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
style="background:none;border:none;color:#94a3b8;padding:4px 6px;font-size:1.15rem;cursor:pointer;line-height:1;"
|
||||
title="تسجيل الخروج">
|
||||
<i class="bi bi-box-arrow-left"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- Flash Messages --}}
|
||||
<div class="px-4 pt-3">
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success d-flex align-items-center gap-2 border-0 rounded-3 py-2 mb-0"
|
||||
role="alert">
|
||||
<i class="bi bi-check-circle-fill flex-shrink-0"></i>
|
||||
<span>{{ session('success') }}</span>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger d-flex align-items-center gap-2 border-0 rounded-3 py-2 mb-0"
|
||||
role="alert">
|
||||
<i class="bi bi-exclamation-triangle-fill flex-shrink-0"></i>
|
||||
<span>{{ session('error') }}</span>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
{{-- Page Content --}}
|
||||
<main class="app-content">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
</div>{{-- /app-body --}}
|
||||
|
||||
</div>{{-- /app-layout --}}
|
||||
|
||||
{{-- Mobile sidebar overlay --}}
|
||||
<div class="sidebar-overlay" id="sidebarOverlay"></div>
|
||||
|
||||
{{-- ══ MOBILE BOTTOM NAVIGATION ═══════════════════════════════════════════════ --}}
|
||||
<nav class="mobile-bottom-nav" aria-label="التنقل">
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="mob-nav-item {{ request()->routeIs('admin.dashboard') ? 'active' : '' }}">
|
||||
<i class="bi bi-speedometer2"></i>
|
||||
<span>الرئيسية</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.parking-lots.index') }}"
|
||||
class="mob-nav-item {{ request()->routeIs('admin.parking-lots.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-buildings"></i>
|
||||
<span>المواقف</span>
|
||||
</a>
|
||||
<a href="{{ route('admin.bookings.active') }}"
|
||||
class="mob-nav-item {{ request()->routeIs('admin.bookings.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-calendar-check"></i>
|
||||
<span>الحجوزات</span>
|
||||
</a>
|
||||
<a href="{{ route('operator.dashboard') }}"
|
||||
class="mob-nav-item {{ request()->routeIs('operator.*') ? 'active' : '' }}">
|
||||
<i class="bi bi-person-badge"></i>
|
||||
<span>التشغيل</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<script>
|
||||
// Mobile sidebar toggle
|
||||
const toggle = document.getElementById('sidebarToggle');
|
||||
const sidebar = document.getElementById('appSidebar');
|
||||
const overlay = document.getElementById('sidebarOverlay');
|
||||
|
||||
function openSidebar() {
|
||||
sidebar.classList.add('is-open');
|
||||
overlay.classList.add('is-open');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
function closeSidebar() {
|
||||
sidebar.classList.remove('is-open');
|
||||
overlay.classList.remove('is-open');
|
||||
document.body.style.overflow = '';
|
||||
}
|
||||
|
||||
toggle?.addEventListener('click', openSidebar);
|
||||
overlay.addEventListener('click', closeSidebar);
|
||||
</script>
|
||||
|
||||
@stack('scripts')
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,44 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>@yield('title', 'دمشق باركينغ')</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
|
||||
@vite(['resources/css/app.scss', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="auth-page">
|
||||
|
||||
<div class="auth-box">
|
||||
|
||||
{{-- Brand --}}
|
||||
<div class="auth-brand">
|
||||
<div class="brand-icon">
|
||||
<i class="bi bi-p-square-fill"></i>
|
||||
</div>
|
||||
<h1>دمشق باركينغ</h1>
|
||||
<p>نظام إدارة مواقف السيارات</p>
|
||||
</div>
|
||||
|
||||
{{-- Card --}}
|
||||
<div class="auth-card">
|
||||
@yield('content')
|
||||
</div>
|
||||
|
||||
{{-- Back link --}}
|
||||
<div class="text-center mt-4">
|
||||
<a href="{{ route('parking.index') }}"
|
||||
class="text-sm"
|
||||
style="color:#64748b;text-decoration:none;">
|
||||
<i class="bi bi-arrow-left me-1"></i>
|
||||
العودة إلى الموقع الرئيسي
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,62 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<title>@yield('title', 'دمشق باركينغ')</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cairo:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
|
||||
@vite(['resources/css/app.scss', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body style="background:#f1f5f9;font-family:'Cairo',sans-serif;">
|
||||
|
||||
{{-- ══ HEADER ══════════════════════════════════════════════════════════════ --}}
|
||||
<header class="public-header">
|
||||
<div class="container">
|
||||
<div class="d-flex align-items-center justify-content-between">
|
||||
|
||||
{{-- Logo --}}
|
||||
<a href="{{ route('parking.index') }}" class="d-flex align-items-center gap-3 text-decoration-none">
|
||||
<div style="width:40px;height:40px;background:#6366f1;border-radius:.625rem;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||
<i class="bi bi-p-square-fill text-white" style="font-size:1.25rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-800" style="color:#f8fafc;font-size:1.05rem;line-height:1.2;">دمشق باركينغ</div>
|
||||
<div style="color:#94a3b8;font-size:.72rem;">مواقف السيارات في دمشق</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
{{-- User Dropdown --}}
|
||||
@include('partials.user-dropdown')
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{{-- ══ CONTENT ═════════════════════════════════════════════════════════════ --}}
|
||||
<main class="container py-4" style="max-width:820px;">
|
||||
|
||||
{{-- Flash messages --}}
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success d-flex align-items-center gap-2 border-0 rounded-3 py-2 mb-4">
|
||||
<i class="bi bi-check-circle-fill flex-shrink-0"></i>
|
||||
<span>{{ session('success') }}</span>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger d-flex align-items-center gap-2 border-0 rounded-3 py-2 mb-4">
|
||||
<i class="bi bi-exclamation-triangle-fill flex-shrink-0"></i>
|
||||
<span>{{ session('error') }}</span>
|
||||
<button type="button" class="btn-close ms-auto" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,925 @@
|
||||
@extends('layouts.admin')
|
||||
@section('title', 'لوحة المشغّل — دمشق باركينغ')
|
||||
@section('page-title', 'لوحة المشغّل')
|
||||
|
||||
@push('styles')
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.css">
|
||||
<style>
|
||||
.leaflet-container img { max-width:none !important; box-shadow:none !important; }
|
||||
.leaflet-container { direction:ltr; }
|
||||
|
||||
/* ── Lot picker cards ─────────────────────────────────────────────── */
|
||||
.lot-picker-card {
|
||||
background:#fff;
|
||||
border:2px solid #e2e8f0;
|
||||
border-radius:.875rem;
|
||||
padding:1.125rem 1.25rem;
|
||||
cursor:pointer;
|
||||
transition:border-color .18s, box-shadow .18s, transform .18s;
|
||||
position:relative;
|
||||
overflow:hidden;
|
||||
}
|
||||
.lot-picker-card::before {
|
||||
content:'';
|
||||
position:absolute;
|
||||
inset-inline-start:0; top:0; bottom:0;
|
||||
width:4px;
|
||||
background:#6366f1;
|
||||
opacity:0;
|
||||
transition:opacity .18s;
|
||||
border-radius:2px 0 0 2px;
|
||||
}
|
||||
.lot-picker-card:hover {
|
||||
border-color:#a5b4fc;
|
||||
box-shadow:0 4px 20px rgba(99,102,241,.12);
|
||||
transform:translateY(-2px);
|
||||
}
|
||||
.lot-picker-card:hover::before { opacity:1; }
|
||||
.lot-picker-card.highlighted { border-color:#6366f1; box-shadow:0 0 0 3px rgba(99,102,241,.15); }
|
||||
.lot-picker-card.highlighted::before { opacity:1; }
|
||||
|
||||
.lot-picker-card .select-btn {
|
||||
display:none;
|
||||
width:100%;
|
||||
margin-top:.875rem;
|
||||
padding:.45rem;
|
||||
background:#6366f1;
|
||||
color:#fff;
|
||||
border:none;
|
||||
border-radius:.5rem;
|
||||
font-family:'Cairo',sans-serif;
|
||||
font-weight:700;
|
||||
font-size:.875rem;
|
||||
cursor:pointer;
|
||||
transition:background .15s;
|
||||
}
|
||||
.lot-picker-card:hover .select-btn,
|
||||
.lot-picker-card.highlighted .select-btn { display:block; }
|
||||
.lot-picker-card .select-btn:hover { background:#4f46e5; }
|
||||
|
||||
/* Occupancy bar */
|
||||
.occ-bar { height:6px; background:#e2e8f0; border-radius:3px; overflow:hidden; margin:.5rem 0 .625rem; }
|
||||
.occ-bar-fill { height:100%; border-radius:3px; transition:width .4s; }
|
||||
|
||||
/* ── Operator panel header ───────────────────────────────────────── */
|
||||
.op-header {
|
||||
background:linear-gradient(135deg,#1e1b4b 0%,#312e81 50%,#3730a3 100%);
|
||||
border-radius:.875rem;
|
||||
padding:1.25rem 1.5rem;
|
||||
color:#fff;
|
||||
margin-bottom:1.5rem;
|
||||
}
|
||||
|
||||
/* ── Tabs ─────────────────────────────────────────────────────────── */
|
||||
.op-tabs { display:flex; gap:.375rem; margin-bottom:1.25rem; border-bottom:2px solid #e2e8f0; padding-bottom:0; }
|
||||
.op-tab {
|
||||
padding:.55rem 1.125rem;
|
||||
border:none;
|
||||
border-bottom:3px solid transparent;
|
||||
margin-bottom:-2px;
|
||||
background:none;
|
||||
font-family:'Cairo',sans-serif;
|
||||
font-weight:600;
|
||||
font-size:.875rem;
|
||||
color:#64748b;
|
||||
cursor:pointer;
|
||||
border-radius:.5rem .5rem 0 0;
|
||||
transition:color .15s, border-color .15s;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:.4rem;
|
||||
}
|
||||
.op-tab:hover { color:#0f172a; }
|
||||
.op-tab.active { color:#6366f1; border-bottom-color:#6366f1; }
|
||||
.op-tab .badge { font-size:.68rem; padding:.2em .5em; }
|
||||
|
||||
/* ── Receipt modal ────────────────────────────────────────────────── */
|
||||
.fee-row { display:flex; justify-content:space-between; padding:.35rem 0; border-bottom:1px dashed #f1f5f9; font-size:.875rem; }
|
||||
.fee-row:last-child { border-bottom:none; }
|
||||
.fee-total { display:flex; justify-content:space-between; padding:.625rem 0; border-top:2px solid #e2e8f0; font-weight:800; font-size:1.05rem; color:#0f172a; margin-top:.25rem; }
|
||||
.pay-method { border:2px solid #e2e8f0; border-radius:.625rem; padding:.875rem 1rem; cursor:pointer; transition:border-color .15s,background .15s; text-align:center; }
|
||||
.pay-method:hover { border-color:#a5b4fc; }
|
||||
.pay-method.selected { border-color:#6366f1; background:#f0f4ff; }
|
||||
.pay-method i { font-size:1.5rem; display:block; margin-bottom:.25rem; }
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- ══════════════════════════════════════════════════════════════════════
|
||||
LOT PICKER — shown when no lot is selected
|
||||
══════════════════════════════════════════════════════════════════════ --}}
|
||||
@if(!$selectedLot)
|
||||
|
||||
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2 mb-3">
|
||||
<div>
|
||||
<h2 class="fw-800 mb-0" style="font-size:1.1rem;color:#0f172a;">اختر موقف السيارات</h2>
|
||||
<p class="text-sm mb-0" style="color:#64748b;">ابحث عن موقفك وابدأ إدارة السيارات</p>
|
||||
</div>
|
||||
<span class="badge badge-soft-primary">{{ $parkingLots->count() }} موقف متاح</span>
|
||||
</div>
|
||||
|
||||
{{-- Search bar --}}
|
||||
<div class="input-group mb-4" style="max-width:480px;">
|
||||
<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="lotSearch" class="form-control" style="border-color:#e2e8f0;"
|
||||
placeholder="ابحث باسم الموقف أو العنوان...">
|
||||
<button id="clearSearch" class="btn" style="display:none;background:#f1f5f9;border:1px solid #e2e8f0;color:#475569;">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
|
||||
{{-- ── Cards column ──────────────────────────────────────────────── --}}
|
||||
<div class="col-lg-6 col-xl-5" id="cardsCol">
|
||||
<p id="searchMeta" class="text-xs mb-2" style="color:#94a3b8;display:none;"></p>
|
||||
<div id="cardGrid" class="row g-3">
|
||||
|
||||
@forelse($parkingLots as $lot)
|
||||
@php
|
||||
$pct = $lot['total'] > 0 ? round($lot['occupied'] / $lot['total'] * 100) : 0;
|
||||
$avail = $lot['avail'];
|
||||
if ($avail === 0) { $badgeCls = 'badge-soft-danger'; $badgeTxt = 'ممتلئ'; $barCol = '#ef4444'; }
|
||||
elseif ($avail < $lot['total'] * 0.2) { $badgeCls = 'badge-soft-warning'; $badgeTxt = $avail.' محدود'; $barCol = '#f59e0b'; }
|
||||
else { $badgeCls = 'badge-soft-success'; $badgeTxt = $avail.' متاح'; $barCol = '#10b981'; }
|
||||
@endphp
|
||||
<div class="col-12 lot-card-wrap"
|
||||
data-name="{{ mb_strtolower($lot['name']) }}"
|
||||
data-address="{{ mb_strtolower($lot['address']) }}">
|
||||
<div class="lot-picker-card" id="lcard-{{ $lot['id'] }}"
|
||||
onclick="selectLot({{ $lot['id'] }})">
|
||||
|
||||
<div class="d-flex align-items-start justify-content-between gap-2">
|
||||
<div>
|
||||
<div class="fw-700" style="color:#0f172a;font-size:.95rem;line-height:1.3;">
|
||||
{{ $lot['name'] }}
|
||||
</div>
|
||||
<div class="text-xs mt-1" style="color:#94a3b8;">
|
||||
<i class="bi bi-geo-alt me-1"></i>{{ $lot['address'] }}
|
||||
</div>
|
||||
</div>
|
||||
<span class="badge {{ $badgeCls }} flex-shrink-0" style="font-size:.72rem;">{{ $badgeTxt }}</span>
|
||||
</div>
|
||||
|
||||
<div class="occ-bar mt-2">
|
||||
<div class="occ-bar-fill" style="width:{{ $pct }}%;background:{{ $barCol }};"></div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-3 text-xs" style="color:#64748b;">
|
||||
<span><i class="bi bi-car-front me-1"></i>{{ $lot['total'] }} مكان</span>
|
||||
<span><i class="bi bi-currency-exchange me-1"></i>{{ number_format($lot['price']) }}/ساعة</span>
|
||||
<span><i class="bi bi-clock me-1"></i>{{ $lot['hours'] }}</span>
|
||||
</div>
|
||||
|
||||
<button class="select-btn" onclick="event.stopPropagation();selectLot({{ $lot['id'] }})">
|
||||
<i class="bi bi-check2-circle me-2"></i>اختر هذا الموقف
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="col-12 text-center py-5" style="color:#94a3b8;">
|
||||
<i class="bi bi-buildings d-block mb-2" style="font-size:2.5rem;opacity:.3;"></i>
|
||||
<span class="text-sm">لا توجد مواقف نشطة</span>
|
||||
</div>
|
||||
@endforelse
|
||||
|
||||
<div id="noResults" class="col-12 text-center py-4" style="color:#94a3b8;display:none;">
|
||||
<i class="bi bi-search d-block mb-2" style="font-size:2rem;opacity:.3;"></i>
|
||||
<span class="text-sm">لا توجد نتائج مطابقة</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Map column ─────────────────────────────────────────────────── --}}
|
||||
<div class="col-lg-6 col-xl-7" id="mapCol">
|
||||
<div class="card" style="height:520px;overflow:hidden;">
|
||||
<div class="card-header py-2 d-flex align-items-center justify-content-between">
|
||||
<span class="fw-700 text-sm" style="color:#0f172a;">
|
||||
<i class="bi bi-map me-1" style="color:#6366f1;"></i>خريطة المواقف
|
||||
</span>
|
||||
<span class="badge badge-soft-info text-xs">اضغط على الموقف للتحديد</span>
|
||||
</div>
|
||||
<div id="opMap" style="height:calc(100% - 49px);"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- Mobile tab toggle --}}
|
||||
<div class="d-flex d-lg-none gap-2 mt-3">
|
||||
<button class="btn btn-sm fw-600 flex-fill" id="mTabCards" onclick="mobileTab('cards')"
|
||||
style="background:#6366f1;color:#fff;border:none;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-grid me-1"></i>البطاقات
|
||||
</button>
|
||||
<button class="btn btn-sm fw-600 flex-fill" id="mTabMap" onclick="mobileTab('map')"
|
||||
style="background:#f1f5f9;color:#475569;border:none;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-map me-1"></i>الخريطة
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
{{-- ══════════════════════════════════════════════════════════════════════
|
||||
OPERATOR PANEL — shown after lot is selected
|
||||
══════════════════════════════════════════════════════════════════════ --}}
|
||||
@else
|
||||
@php
|
||||
$pct = $selectedLot->usage_percentage;
|
||||
$barColor = $pct >= 90 ? '#ef4444' : ($pct >= 60 ? '#f59e0b' : '#10b981');
|
||||
@endphp
|
||||
|
||||
{{-- ── Selected lot header ─────────────────────────────────────────── --}}
|
||||
<div class="op-header d-flex align-items-center justify-content-between flex-wrap gap-3">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div style="width:48px;height:48px;background:rgba(255,255,255,.15);border-radius:.75rem;display:flex;align-items:center;justify-content:center;font-size:1.3rem;flex-shrink:0;">
|
||||
<i class="bi bi-buildings"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-800" style="font-size:1.1rem;line-height:1.3;">{{ $selectedLot->name }}</div>
|
||||
<div style="font-size:.78rem;opacity:.72;"><i class="bi bi-geo-alt me-1"></i>{{ $selectedLot->address }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-3 flex-wrap">
|
||||
{{-- Live stats --}}
|
||||
<div class="d-flex gap-3 text-center">
|
||||
<div>
|
||||
<div class="fw-800" style="font-size:1.3rem;line-height:1;color:#6ee7b7;">{{ $selectedLot->available_spaces }}</div>
|
||||
<div style="font-size:.7rem;opacity:.7;">متاح</div>
|
||||
</div>
|
||||
<div style="width:1px;background:rgba(255,255,255,.2);"></div>
|
||||
<div>
|
||||
<div class="fw-800" style="font-size:1.3rem;line-height:1;color:#fcd34d;">{{ $selectedLot->occupied_spaces }}</div>
|
||||
<div style="font-size:.7rem;opacity:.7;">مشغول</div>
|
||||
</div>
|
||||
<div style="width:1px;background:rgba(255,255,255,.2);"></div>
|
||||
<div>
|
||||
<div class="fw-800" style="font-size:1.3rem;line-height:1;color:#e2e8f0;">{{ $selectedLot->total_capacity }}</div>
|
||||
<div style="font-size:.7rem;opacity:.7;">إجمالي</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Capacity bar (inline) --}}
|
||||
<div style="min-width:120px;">
|
||||
<div class="d-flex justify-content-between mb-1" style="font-size:.7rem;opacity:.75;">
|
||||
<span>الإشغال</span><span>{{ round($pct) }}%</span>
|
||||
</div>
|
||||
<div style="height:6px;background:rgba(255,255,255,.2);border-radius:3px;overflow:hidden;">
|
||||
<div style="height:100%;width:{{ $pct }}%;background:{{ $barColor }};border-radius:3px;transition:width .5s;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Change lot --}}
|
||||
<a href="{{ route('operator.dashboard') }}"
|
||||
class="btn btn-sm fw-600"
|
||||
style="background:rgba(255,255,255,.15);color:#fff;border:1px solid rgba(255,255,255,.3);border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-arrow-repeat me-1"></i>تغيير الموقف
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Auto-refresh countdown --}}
|
||||
<div class="d-flex justify-content-end mb-2">
|
||||
<span class="badge badge-soft-secondary text-xs" id="refresh-badge">—</span>
|
||||
</div>
|
||||
|
||||
{{-- ── Tabs ───────────────────────────────────────────────────────────── --}}
|
||||
<div class="op-tabs">
|
||||
<button class="op-tab active" onclick="switchTab('active')" id="tab-active">
|
||||
<i class="bi bi-car-front"></i>
|
||||
السيارات النشطة
|
||||
<span class="badge badge-soft-warning">{{ $activeCars->count() }}</span>
|
||||
</button>
|
||||
<button class="op-tab" onclick="switchTab('checkin')" id="tab-checkin">
|
||||
<i class="bi bi-box-arrow-in-left"></i>
|
||||
إدخال يدوي
|
||||
</button>
|
||||
<button class="op-tab" onclick="switchTab('reservations')" id="tab-reservations">
|
||||
<i class="bi bi-calendar-check"></i>
|
||||
الحجوزات المسبقة
|
||||
<span class="badge badge-soft-primary">{{ $reservations->count() }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{{-- ─────────────────────────────────────────────────────────────────────
|
||||
TAB 1: Active walk-in cars
|
||||
──────────────────────────────────────────────────────────────────────── --}}
|
||||
<div id="panel-active">
|
||||
<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-car-front me-1" style="color:#6366f1;"></i>السيارات داخل الموقف
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if($activeCars->isEmpty())
|
||||
<div class="text-center py-5" style="color:#94a3b8;">
|
||||
<i class="bi bi-car-front d-block mb-2" style="font-size:2.5rem;opacity:.3;"></i>
|
||||
<p class="fw-600 mb-1" style="color:#64748b;">لا توجد سيارات داخل الموقف</p>
|
||||
<button class="btn btn-sm fw-600 mt-1"
|
||||
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;"
|
||||
onclick="switchTab('checkin')">
|
||||
<i class="bi bi-plus-circle me-1"></i>إدخال سيارة جديدة
|
||||
</button>
|
||||
</div>
|
||||
@else
|
||||
<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>
|
||||
@foreach($activeCars as $car)
|
||||
@php
|
||||
$elapsedMin = $car->start_time->diffInMinutes(now());
|
||||
$remainMins = now()->diffInMinutes($car->end_time, false);
|
||||
$isOverdue = $remainMins < 0;
|
||||
@endphp
|
||||
<tr id="row-{{ $car->id }}">
|
||||
<td>
|
||||
<span class="fw-800" style="font-family:monospace;font-size:1rem;color:#0f172a;letter-spacing:.03em;">
|
||||
{{ $car->vehicle_plate ?? '—' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="text-sm" style="color:#475569;">
|
||||
{{ $car->customer_name ?? 'غير محدد' }}
|
||||
@if($car->phone)
|
||||
<div class="text-xs" style="color:#94a3b8;direction:ltr;text-align:right;">{{ $car->phone }}</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-sm" style="color:#475569;">
|
||||
{{ $car->start_time->format('H:i') }}
|
||||
<div class="text-xs" style="color:#94a3b8;">{{ $car->start_time->format('Y/m/d') }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-soft-secondary text-xs">
|
||||
{{ floor($elapsedMin/60) }}س {{ $elapsedMin%60 }}د
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
@if($isOverdue)
|
||||
<span class="badge badge-soft-danger text-xs fw-600">
|
||||
تجاوز {{ floor(abs($remainMins)/60) }}س {{ abs($remainMins)%60 }}د
|
||||
</span>
|
||||
@else
|
||||
<span class="badge badge-soft-warning text-xs fw-600">
|
||||
{{ floor($remainMins/60) }}س {{ $remainMins%60 }}د
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button onclick="openReceipt({{ $car->id }})"
|
||||
class="btn btn-sm fw-600"
|
||||
style="background:rgba(99,102,241,.1);color:#4338ca;border:none;border-radius:.375rem;font-family:'Cairo',sans-serif;padding:.3rem .875rem;">
|
||||
<i class="bi bi-receipt me-1"></i>خروج وفاتورة
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ─────────────────────────────────────────────────────────────────────
|
||||
TAB 2: Manual check-in form
|
||||
──────────────────────────────────────────────────────────────────────── --}}
|
||||
<div id="panel-checkin" style="display:none;">
|
||||
<div class="card" style="max-width:560px;">
|
||||
<div class="card-header" style="background:rgba(16,185,129,.05);">
|
||||
<span class="fw-700 text-sm" style="color:#059669;">
|
||||
<i class="bi bi-box-arrow-in-left me-1"></i>تسجيل دخول سيارة جديدة
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form id="checkInForm">
|
||||
@csrf
|
||||
<input type="hidden" name="parking_lot_id" value="{{ $selectedLot->id }}">
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">رقم اللوحة <span style="color:#ef4444;">*</span></label>
|
||||
<input type="text" name="vehicle_plate" required
|
||||
class="form-control fw-700"
|
||||
style="font-size:1.05rem;letter-spacing:.04em;"
|
||||
placeholder="مثال: أ ب ج 1234"
|
||||
autocomplete="off">
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label">اسم السائق</label>
|
||||
<input type="text" name="customer_name" class="form-control" placeholder="اختياري">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-label">الهاتف</label>
|
||||
<input type="tel" name="phone" class="form-control" placeholder="اختياري" dir="ltr">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="form-label">المدة المتوقعة <span style="color:#ef4444;">*</span></label>
|
||||
<div class="row g-2">
|
||||
@foreach([1,2,3,4,6,8,12,24,48,72] as $h)
|
||||
<div class="col-4 col-sm-3">
|
||||
<label class="d-block text-center px-2 py-2 rounded-3"
|
||||
style="border:2px solid #e2e8f0;cursor:pointer;font-size:.82rem;font-weight:600;color:#475569;transition:all .15s;"
|
||||
onclick="this.parentElement.parentElement.querySelectorAll('label').forEach(l=>l.style.cssText='border:2px solid #e2e8f0;cursor:pointer;font-size:.82rem;font-weight:600;color:#475569;transition:all .15s;');this.style.cssText='border:2px solid #6366f1;cursor:pointer;font-size:.82rem;font-weight:700;color:#4338ca;background:#f0f4ff;transition:all .15s;'">
|
||||
<input type="radio" name="duration_hours" value="{{ $h }}" class="d-none" {{ $h==2 ? 'checked' : '' }}>
|
||||
{{ $h }}{{ $h >= 24 ? ' يوم' : 'س' }}
|
||||
</label>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="checkInBtn"
|
||||
class="btn w-100 fw-700"
|
||||
style="background:#10b981;color:#fff;border:none;border-radius:.5rem;padding:.7rem;font-family:'Cairo',sans-serif;font-size:.95rem;">
|
||||
<i class="bi bi-box-arrow-in-left me-2"></i>تسجيل الدخول
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ─────────────────────────────────────────────────────────────────────
|
||||
TAB 3: Pre-reservations
|
||||
──────────────────────────────────────────────────────────────────────── --}}
|
||||
<div id="panel-reservations" style="display:none;">
|
||||
<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-calendar-check me-1" style="color:#0ea5e9;"></i>حجوزات قادمة
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@if($reservations->isEmpty())
|
||||
<div class="text-center py-5" style="color:#94a3b8;">
|
||||
<i class="bi bi-calendar-x d-block mb-2" style="font-size:2.5rem;opacity:.3;"></i>
|
||||
<p class="fw-600 mb-0" style="color:#64748b;">لا توجد حجوزات مسبقة لهذا الموقف</p>
|
||||
</div>
|
||||
@else
|
||||
<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>
|
||||
@foreach($reservations as $res)
|
||||
<tr id="res-{{ $res->id }}">
|
||||
<td class="text-sm" style="color:#94a3b8;">{{ $res->id }}</td>
|
||||
<td class="fw-600 text-sm" style="color:#0f172a;">{{ $res->customer_name ?? '—' }}</td>
|
||||
<td class="text-sm" style="color:#475569;direction:ltr;text-align:right;">{{ $res->phone ?? '—' }}</td>
|
||||
<td class="text-sm" style="color:#475569;">
|
||||
{{ $res->start_time->format('H:i') }}
|
||||
<div class="text-xs" style="color:#94a3b8;">{{ $res->start_time->format('Y/m/d') }}</div>
|
||||
</td>
|
||||
<td class="text-sm" style="color:#475569;">
|
||||
{{ $res->end_time->format('H:i') }}
|
||||
<div class="text-xs" style="color:#94a3b8;">{{ $res->end_time->format('Y/m/d') }}</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<button onclick="activateRes({{ $res->id }}, this)"
|
||||
class="btn btn-sm fw-600"
|
||||
style="background:rgba(16,185,129,.1);color:#059669;border:none;border-radius:.375rem;font-family:'Cairo',sans-serif;padding:.3rem .875rem;">
|
||||
<i class="bi bi-door-open me-1"></i>فتح البوابة
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endif {{-- end selectedLot --}}
|
||||
|
||||
|
||||
{{-- ══════════════════════════════════════════════════════════════════════
|
||||
RECEIPT & PAYMENT MODAL
|
||||
══════════════════════════════════════════════════════════════════════ --}}
|
||||
<div class="modal fade" id="receiptModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" style="max-width:520px;">
|
||||
<div class="modal-content" style="border:none;border-radius:1rem;overflow:hidden;">
|
||||
|
||||
<div class="modal-header" style="background:#1e1b4b;color:#fff;border:none;padding:1.125rem 1.5rem;">
|
||||
<div>
|
||||
<h5 class="modal-title fw-800 mb-0" style="font-size:1rem;">
|
||||
<i class="bi bi-receipt me-2"></i>فاتورة الخروج
|
||||
</h5>
|
||||
<div id="rcpt-lot" class="text-xs mt-1" style="opacity:.7;"></div>
|
||||
</div>
|
||||
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body p-4">
|
||||
|
||||
{{-- Car info --}}
|
||||
<div class="d-flex align-items-center gap-3 mb-3 p-3 rounded-3" style="background:#f8fafc;">
|
||||
<div style="width:46px;height:46px;background:#6366f1;border-radius:.625rem;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
|
||||
<i class="bi bi-car-front" style="color:#fff;font-size:1.2rem;"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-800" style="font-size:1.1rem;color:#0f172a;letter-spacing:.04em;" id="rcpt-plate">—</div>
|
||||
<div class="text-xs" style="color:#64748b;" id="rcpt-name">—</div>
|
||||
</div>
|
||||
<div class="me-auto text-center">
|
||||
<div class="fw-800" style="color:#6366f1;font-size:1.1rem;" id="rcpt-duration">—</div>
|
||||
<div class="text-xs" style="color:#64748b;">مدة الإقامة</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Times --}}
|
||||
<div class="row g-2 mb-3">
|
||||
<div class="col-6">
|
||||
<div class="p-2 rounded-3 text-center" style="background:#f0fdf4;">
|
||||
<div class="text-xs fw-600 mb-1" style="color:#64748b;">وقت الدخول</div>
|
||||
<div class="fw-700 text-sm" id="rcpt-entry" style="color:#059669;">—</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="p-2 rounded-3 text-center" style="background:#fef2f2;">
|
||||
<div class="text-xs fw-600 mb-1" style="color:#64748b;">وقت الخروج</div>
|
||||
<div class="fw-700 text-sm" id="rcpt-exit" style="color:#dc2626;">—</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Fee breakdown --}}
|
||||
<div class="mb-3">
|
||||
<div class="fw-700 text-sm mb-2" style="color:#0f172a;">تفصيل الأجرة</div>
|
||||
<div id="rcpt-breakdown" style="background:#f8fafc;border-radius:.625rem;padding:.625rem .875rem;">
|
||||
<div class="text-center py-2" style="color:#94a3b8;">
|
||||
<span class="spinner-border spinner-border-sm"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fee-total px-1">
|
||||
<span>الإجمالي</span>
|
||||
<span id="rcpt-total" style="color:#6366f1;">—</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Payment method --}}
|
||||
<div class="mb-3">
|
||||
<div class="fw-700 text-sm mb-2" style="color:#0f172a;">طريقة الدفع</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-6">
|
||||
<div class="pay-method selected" id="pm-cash" onclick="selectPayment('cash')">
|
||||
<i class="bi bi-cash-coin" style="color:#10b981;"></i>
|
||||
<div class="fw-700 text-sm" style="color:#0f172a;">نقداً</div>
|
||||
<div class="text-xs" style="color:#64748b;">دفع مباشر</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="pay-method" id="pm-upload" onclick="selectPayment('upload')">
|
||||
<i class="bi bi-cloud-upload" style="color:#6366f1;"></i>
|
||||
<div class="fw-700 text-sm" style="color:#0f172a;">إيصال إلكتروني</div>
|
||||
<div class="text-xs" style="color:#64748b;">رفع صورة الدفع</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- File upload (hidden until upload selected) --}}
|
||||
<div id="uploadArea" class="mt-3" style="display:none;">
|
||||
<label class="form-label text-sm">رفع إيصال الدفع <span style="color:#ef4444;">*</span></label>
|
||||
<input type="file" id="paymentProofFile" accept="image/*,.pdf"
|
||||
class="form-control form-control-sm">
|
||||
<div class="text-xs mt-1" style="color:#94a3b8;">JPG / PNG / PDF — حد أقصى 4MB</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal-footer" style="border:none;padding:1rem 1.5rem 1.5rem;">
|
||||
<button type="button" class="btn fw-600"
|
||||
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;"
|
||||
data-bs-dismiss="modal">إلغاء</button>
|
||||
<button type="button" id="confirmPayBtn"
|
||||
class="btn fw-700 flex-fill"
|
||||
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;padding:.65rem;">
|
||||
<i class="bi bi-check2-circle me-2"></i>تأكيد الدفع وإغلاق البوابة
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('scripts')
|
||||
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/leaflet.js"></script>
|
||||
<script>
|
||||
const lots = {!! $parkingLots->toJson() !!};
|
||||
const csrf = document.querySelector('meta[name="csrf-token"]').content;
|
||||
|
||||
// ── Navigate to lot ──────────────────────────────────────────────────────────
|
||||
function selectLot(id) { window.location = '/operator/dashboard?lot_id=' + id; }
|
||||
|
||||
@if(!$selectedLot)
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// LOT PICKER SCRIPTS
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ── Map ──────────────────────────────────────────────────────────────────────
|
||||
const map = L.map('opMap').setView([33.5138, 36.2765], 12);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution:'© OpenStreetMap' }).addTo(map);
|
||||
const markers = {};
|
||||
|
||||
lots.forEach(l => {
|
||||
const col = l.avail === 0 ? '#ef4444' : (l.avail < l.total * .2 ? '#f59e0b' : '#10b981');
|
||||
const pct = l.total > 0 ? Math.round(l.occupied / l.total * 100) : 0;
|
||||
const icon = L.divIcon({
|
||||
html: `<div style="width:40px;height:40px;background:${col};border:3px solid #fff;border-radius:50% 50% 50% 0;transform:rotate(-45deg);box-shadow:0 3px 12px rgba(0,0,0,.3);"></div>`,
|
||||
iconSize:[40,40], iconAnchor:[20,40], className:''
|
||||
});
|
||||
const m = L.marker([l.lat, l.lng], { icon }).addTo(map);
|
||||
markers[l.id] = m;
|
||||
|
||||
m.bindPopup(`
|
||||
<div style="font-family:'Cairo',sans-serif;min-width:190px;padding:4px 2px;">
|
||||
<strong style="font-size:.95rem;color:#0f172a;">${l.name}</strong>
|
||||
<p style="margin:4px 0 8px;font-size:.78rem;color:#64748b;">${l.address}</p>
|
||||
<div style="height:5px;background:#e2e8f0;border-radius:3px;overflow:hidden;margin-bottom:8px;">
|
||||
<div style="height:100%;width:${pct}%;background:${col};border-radius:3px;"></div>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;font-size:.78rem;color:#64748b;margin-bottom:8px;">
|
||||
<span>${l.avail > 0 ? l.avail + ' مكان متاح' : 'ممتلئ'}</span>
|
||||
<span>${pct}% مشغول</span>
|
||||
</div>
|
||||
<button onclick="selectLot(${l.id})"
|
||||
style="width:100%;padding:7px;background:${col};color:#fff;border:none;border-radius:6px;font-family:'Cairo',sans-serif;font-size:.82rem;font-weight:700;cursor:pointer;">
|
||||
اختر هذا الموقف
|
||||
</button>
|
||||
</div>
|
||||
`);
|
||||
|
||||
m.on('click', () => highlightCard(l.id));
|
||||
});
|
||||
|
||||
if (lots.length) {
|
||||
const g = L.featureGroup(lots.map(l => L.marker([l.lat, l.lng])));
|
||||
map.fitBounds(g.getBounds().pad(.15));
|
||||
}
|
||||
|
||||
// ── Card highlight ───────────────────────────────────────────────────────────
|
||||
let hlId = null;
|
||||
function highlightCard(id) {
|
||||
if (hlId) document.getElementById('lcard-' + hlId)?.classList.remove('highlighted');
|
||||
hlId = id;
|
||||
const card = document.getElementById('lcard-' + id);
|
||||
if (card) {
|
||||
card.classList.add('highlighted');
|
||||
card.scrollIntoView({ behavior:'smooth', block:'nearest' });
|
||||
}
|
||||
const l = lots.find(x => x.id === id);
|
||||
if (l) map.setView([l.lat, l.lng], 15, { animate:true });
|
||||
}
|
||||
|
||||
// ── Search ───────────────────────────────────────────────────────────────────
|
||||
const searchEl = document.getElementById('lotSearch');
|
||||
const clearBtn = document.getElementById('clearSearch');
|
||||
const noResults = document.getElementById('noResults');
|
||||
const searchMeta= document.getElementById('searchMeta');
|
||||
|
||||
searchEl.addEventListener('input', () => {
|
||||
const q = searchEl.value.trim().toLowerCase();
|
||||
clearBtn.style.display = q ? 'block' : 'none';
|
||||
let vis = 0;
|
||||
document.querySelectorAll('.lot-card-wrap').forEach(w => {
|
||||
const match = !q || w.dataset.name.includes(q) || w.dataset.address.includes(q);
|
||||
w.style.display = match ? '' : 'none';
|
||||
if (match) vis++;
|
||||
});
|
||||
noResults.style.display = vis === 0 ? 'block' : 'none';
|
||||
searchMeta.style.display = q ? 'block' : 'none';
|
||||
searchMeta.textContent = `${vis} نتيجة من أصل ${lots.length}`;
|
||||
});
|
||||
clearBtn.addEventListener('click', () => {
|
||||
searchEl.value = ''; searchEl.dispatchEvent(new Event('input')); searchEl.focus();
|
||||
});
|
||||
|
||||
// ── Mobile tab ───────────────────────────────────────────────────────────────
|
||||
function mobileTab(tab) {
|
||||
const cc = document.getElementById('cardsCol');
|
||||
const mc = document.getElementById('mapCol');
|
||||
const bc = document.getElementById('mTabCards');
|
||||
const bm = document.getElementById('mTabMap');
|
||||
if (tab === 'map') {
|
||||
cc.style.display='none'; mc.style.display='';
|
||||
bm.style.cssText='background:#6366f1;color:#fff;border:none;font-family:Cairo,sans-serif;';
|
||||
bc.style.cssText='background:#f1f5f9;color:#475569;border:none;font-family:Cairo,sans-serif;';
|
||||
setTimeout(() => map.invalidateSize(), 80);
|
||||
} else {
|
||||
cc.style.display=''; mc.style.display='';
|
||||
bc.style.cssText='background:#6366f1;color:#fff;border:none;font-family:Cairo,sans-serif;';
|
||||
bm.style.cssText='background:#f1f5f9;color:#475569;border:none;font-family:Cairo,sans-serif;';
|
||||
}
|
||||
}
|
||||
if (window.innerWidth < 992) {
|
||||
document.getElementById('mapCol').style.display = 'none';
|
||||
}
|
||||
|
||||
// Highlight duration radio default
|
||||
document.querySelectorAll('input[name="duration_hours"]').forEach(r => {
|
||||
if (r.checked) r.closest('label').style.cssText='border:2px solid #6366f1;cursor:pointer;font-size:.82rem;font-weight:700;color:#4338ca;background:#f0f4ff;transition:all .15s;';
|
||||
});
|
||||
|
||||
@else
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// OPERATOR PANEL SCRIPTS
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// ── Tabs ─────────────────────────────────────────────────────────────────────
|
||||
function switchTab(name) {
|
||||
['active','checkin','reservations'].forEach(t => {
|
||||
document.getElementById('panel-' + t).style.display = t === name ? '' : 'none';
|
||||
document.getElementById('tab-' + t).classList.toggle('active', t === name);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Check-in form ─────────────────────────────────────────────────────────────
|
||||
// Default duration highlight
|
||||
document.querySelectorAll('input[name="duration_hours"]').forEach(r => {
|
||||
if (r.checked) r.closest('label').style.cssText='border:2px solid #6366f1;cursor:pointer;font-size:.82rem;font-weight:700;color:#4338ca;background:#f0f4ff;transition:all .15s;';
|
||||
r.addEventListener('change', () => {
|
||||
document.querySelectorAll('input[name="duration_hours"]').forEach(x =>
|
||||
x.closest('label').style.cssText='border:2px solid #e2e8f0;cursor:pointer;font-size:.82rem;font-weight:600;color:#475569;transition:all .15s;'
|
||||
);
|
||||
r.closest('label').style.cssText='border:2px solid #6366f1;cursor:pointer;font-size:.82rem;font-weight:700;color:#4338ca;background:#f0f4ff;transition:all .15s;';
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('checkInForm')?.addEventListener('submit', async e => {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('checkInBtn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>جاري التسجيل...';
|
||||
try {
|
||||
const res = await fetch('/operator/check-in', { method:'POST', body: new FormData(e.target) });
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
btn.style.background = '#059669';
|
||||
btn.innerHTML = '<i class="bi bi-check2-circle me-2"></i>تم التسجيل — يتم التحديث...';
|
||||
setTimeout(() => location.reload(), 900);
|
||||
} else {
|
||||
alert(data.message || 'حدث خطأ'); resetCheckInBtn();
|
||||
}
|
||||
} catch { alert('خطأ في الاتصال'); resetCheckInBtn(); }
|
||||
function resetCheckInBtn() {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="bi bi-box-arrow-in-left me-2"></i>تسجيل الدخول';
|
||||
}
|
||||
});
|
||||
|
||||
// ── Activate reservation ──────────────────────────────────────────────────────
|
||||
async function activateRes(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(`/operator/${id}/activate`, {
|
||||
method:'POST', headers:{'X-CSRF-TOKEN':csrf,'Content-Type':'application/json'}
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
document.getElementById('res-' + id)?.remove();
|
||||
alert(data.message);
|
||||
location.reload();
|
||||
} else { alert(data.message || 'حدث خطأ'); btn.innerHTML=orig; btn.disabled=false; }
|
||||
} catch { alert('خطأ في الاتصال'); btn.innerHTML=orig; btn.disabled=false; }
|
||||
}
|
||||
|
||||
// ── Receipt modal ─────────────────────────────────────────────────────────────
|
||||
let receiptModal = null;
|
||||
let currentBookingId = null;
|
||||
let selectedPayment = 'cash';
|
||||
|
||||
function getModal() {
|
||||
if (!receiptModal) receiptModal = new bootstrap.Modal(document.getElementById('receiptModal'));
|
||||
return receiptModal;
|
||||
}
|
||||
|
||||
async function openReceipt(id) {
|
||||
currentBookingId = id;
|
||||
selectedPayment = 'cash';
|
||||
selectPayment('cash');
|
||||
|
||||
// Reset breakdown
|
||||
document.getElementById('rcpt-breakdown').innerHTML =
|
||||
'<div class="text-center py-2" style="color:#94a3b8;"><span class="spinner-border spinner-border-sm"></span></div>';
|
||||
document.getElementById('rcpt-plate').textContent = '—';
|
||||
document.getElementById('rcpt-name').textContent = '—';
|
||||
document.getElementById('rcpt-duration').textContent = '—';
|
||||
document.getElementById('rcpt-entry').textContent = '—';
|
||||
document.getElementById('rcpt-exit').textContent = '—';
|
||||
document.getElementById('rcpt-total').textContent = '—';
|
||||
|
||||
getModal().show();
|
||||
|
||||
try {
|
||||
const res = await fetch(`/operator/${id}/checkout-preview`);
|
||||
const data = await res.json();
|
||||
if (!data.success) { alert(data.message); return; }
|
||||
const d = data.data;
|
||||
|
||||
document.getElementById('rcpt-lot').textContent = d.lot_name;
|
||||
document.getElementById('rcpt-plate').textContent = d.plate || '—';
|
||||
document.getElementById('rcpt-name').textContent = d.customer_name || 'غير محدد';
|
||||
document.getElementById('rcpt-duration').textContent = d.duration_label;
|
||||
document.getElementById('rcpt-entry').textContent = d.entry_time;
|
||||
document.getElementById('rcpt-exit').textContent = d.exit_time;
|
||||
document.getElementById('rcpt-total').textContent = Number(d.total_fee).toLocaleString('ar-SA') + ' ل.س';
|
||||
|
||||
// Breakdown table
|
||||
const rows = d.fee_details.map(r => `
|
||||
<div class="fee-row">
|
||||
<span>${r.day} <small style="color:#94a3b8;">${r.date}</small></span>
|
||||
<span style="color:#64748b;">${r.hours}س × ${Number(r.rate).toLocaleString('ar-SA')}</span>
|
||||
<span class="fw-600" style="color:#0f172a;">${Number(r.subtotal).toLocaleString('ar-SA')} ل.س</span>
|
||||
</div>`).join('');
|
||||
document.getElementById('rcpt-breakdown').innerHTML = rows || '<p class="text-xs text-center" style="color:#94a3b8;">لا تفاصيل</p>';
|
||||
} catch { alert('تعذّر تحميل بيانات الفاتورة'); }
|
||||
}
|
||||
|
||||
// Payment method toggle
|
||||
function selectPayment(method) {
|
||||
selectedPayment = method;
|
||||
document.getElementById('pm-cash').classList.toggle('selected', method === 'cash');
|
||||
document.getElementById('pm-upload').classList.toggle('selected', method === 'upload');
|
||||
document.getElementById('uploadArea').style.display = method === 'upload' ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Confirm payment
|
||||
document.getElementById('confirmPayBtn').addEventListener('click', async () => {
|
||||
if (!currentBookingId) return;
|
||||
if (selectedPayment === 'upload' && !document.getElementById('paymentProofFile').files.length) {
|
||||
alert('يرجى رفع إيصال الدفع'); return;
|
||||
}
|
||||
const btn = document.getElementById('confirmPayBtn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>جاري المعالجة...';
|
||||
|
||||
const fd = new FormData();
|
||||
fd.append('_method', 'POST');
|
||||
fd.append('payment_method', selectedPayment);
|
||||
if (selectedPayment === 'upload') {
|
||||
fd.append('payment_proof', document.getElementById('paymentProofFile').files[0]);
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`/operator/${currentBookingId}/payment`, {
|
||||
method:'POST',
|
||||
headers:{'X-CSRF-TOKEN':csrf},
|
||||
body: fd
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
getModal().hide();
|
||||
document.getElementById('row-' + currentBookingId)?.remove();
|
||||
const row = document.getElementById('row-' + currentBookingId);
|
||||
if (row) { row.style.transition='opacity .4s'; row.style.opacity='0'; setTimeout(()=>row.remove(),400); }
|
||||
setTimeout(() => location.reload(), 600);
|
||||
} else {
|
||||
alert(data.message || 'حدث خطأ');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="bi bi-check2-circle me-2"></i>تأكيد الدفع وإغلاق البوابة';
|
||||
}
|
||||
} catch {
|
||||
alert('خطأ في الاتصال');
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="bi bi-check2-circle me-2"></i>تأكيد الدفع وإغلاق البوابة';
|
||||
}
|
||||
});
|
||||
|
||||
// ── Auto-refresh countdown ────────────────────────────────────────────────────
|
||||
let t = 30;
|
||||
const badge = document.getElementById('refresh-badge');
|
||||
setInterval(() => {
|
||||
t--;
|
||||
if (badge) badge.textContent = `تحديث تلقائي بعد ${t}ث`;
|
||||
if (t <= 0) location.reload();
|
||||
}, 1000);
|
||||
|
||||
@endif
|
||||
</script>
|
||||
@endpush
|
||||
@endsection
|
||||
@@ -0,0 +1,126 @@
|
||||
@auth
|
||||
{{-- ── Logged-in: profile avatar + dropdown ──────────────────────────────── --}}
|
||||
<div class="dropdown">
|
||||
<button type="button"
|
||||
class="btn btn-sm d-flex align-items-center gap-2"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false"
|
||||
style="background:rgba(255,255,255,.1);color:#f8fafc;border:1px solid rgba(255,255,255,.18);border-radius:.625rem;padding:.35rem .75rem;font-family:'Cairo',sans-serif;">
|
||||
{{-- Avatar circle --}}
|
||||
<div style="width:32px;height:32px;background:#6366f1;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:.9rem;flex-shrink:0;color:#fff;border:2px solid rgba(255,255,255,.25);">
|
||||
{{ mb_substr(auth()->user()->name, 0, 1) }}
|
||||
</div>
|
||||
<span class="d-none d-sm-inline fw-600" style="font-size:.875rem;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
{{ auth()->user()->name }}
|
||||
</span>
|
||||
<i class="bi bi-chevron-down" style="font-size:.65rem;opacity:.7;"></i>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu dropdown-menu-end"
|
||||
style="min-width:230px;border:1px solid #e2e8f0;box-shadow:0 8px 30px rgba(0,0,0,.12);border-radius:.75rem;padding:.5rem;font-family:'Cairo',sans-serif;">
|
||||
|
||||
{{-- User info header --}}
|
||||
<li>
|
||||
<div class="d-flex align-items-center gap-3 px-2 py-2">
|
||||
<div style="width:42px;height:42px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1rem;color:#fff;flex-shrink:0;">
|
||||
{{ mb_substr(auth()->user()->name, 0, 1) }}
|
||||
</div>
|
||||
<div style="min-width:0;">
|
||||
<div class="fw-700" style="color:#0f172a;font-size:.9rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
{{ auth()->user()->name }}
|
||||
</div>
|
||||
<div style="color:#64748b;font-size:.75rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">
|
||||
{{ auth()->user()->email }}
|
||||
</div>
|
||||
{{-- Role badge --}}
|
||||
@php
|
||||
$role = auth()->user()->role;
|
||||
$roleLabel = match($role) {
|
||||
'admin' => 'مدير النظام',
|
||||
'operator' => 'مشغّل',
|
||||
default => 'مستخدم',
|
||||
};
|
||||
$roleColor = match($role) {
|
||||
'admin' => 'background:rgba(239,68,68,.1);color:#b91c1c;',
|
||||
'operator' => 'background:rgba(245,158,11,.1);color:#92400e;',
|
||||
default => 'background:rgba(99,102,241,.1);color:#4338ca;',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge mt-1" style="{{ $roleColor }}font-size:.68rem;padding:.2em .55em;">{{ $roleLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<li><hr class="dropdown-divider my-1" style="border-color:#f1f5f9;"></li>
|
||||
|
||||
{{-- My profile --}}
|
||||
<li>
|
||||
<a href="{{ route('profile.show') }}"
|
||||
class="dropdown-item d-flex align-items-center gap-2 rounded-2"
|
||||
style="color:#374151;font-size:.875rem;padding:.5rem .75rem;">
|
||||
<i class="bi bi-person-circle" style="color:#6366f1;font-size:1rem;width:18px;text-align:center;"></i>
|
||||
معلوماتي
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{{-- My reservations --}}
|
||||
<li>
|
||||
<a href="{{ route('user.dashboard') }}"
|
||||
class="dropdown-item d-flex align-items-center gap-2 rounded-2"
|
||||
style="color:#374151;font-size:.875rem;padding:.5rem .75rem;">
|
||||
<i class="bi bi-calendar3" style="color:#0ea5e9;font-size:1rem;width:18px;text-align:center;"></i>
|
||||
حجوزاتي
|
||||
</a>
|
||||
</li>
|
||||
|
||||
{{-- Operator panel (operators & admins) --}}
|
||||
@if(in_array(auth()->user()->role, ['operator', 'admin']))
|
||||
<li>
|
||||
<a href="{{ route('operator.dashboard') }}"
|
||||
class="dropdown-item d-flex align-items-center gap-2 rounded-2"
|
||||
style="color:#374151;font-size:.875rem;padding:.5rem .75rem;">
|
||||
<i class="bi bi-person-badge" style="color:#f59e0b;font-size:1rem;width:18px;text-align:center;"></i>
|
||||
لوحة المشغّل
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
{{-- Admin panel (admins only) --}}
|
||||
@if(auth()->user()->role === 'admin')
|
||||
<li>
|
||||
<a href="{{ route('admin.dashboard') }}"
|
||||
class="dropdown-item d-flex align-items-center gap-2 rounded-2"
|
||||
style="color:#374151;font-size:.875rem;padding:.5rem .75rem;">
|
||||
<i class="bi bi-speedometer2" style="color:#10b981;font-size:1rem;width:18px;text-align:center;"></i>
|
||||
لوحة الإدارة
|
||||
</a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li><hr class="dropdown-divider my-1" style="border-color:#f1f5f9;"></li>
|
||||
|
||||
{{-- Sign out --}}
|
||||
<li>
|
||||
<form method="POST" action="{{ route('logout') }}" class="m-0">
|
||||
@csrf
|
||||
<button type="submit"
|
||||
class="dropdown-item d-flex align-items-center gap-2 rounded-2"
|
||||
style="color:#ef4444;font-size:.875rem;padding:.5rem .75rem;width:100%;background:none;border:none;cursor:pointer;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-box-arrow-left" style="font-size:1rem;width:18px;text-align:center;"></i>
|
||||
تسجيل الخروج
|
||||
</button>
|
||||
</form>
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@else
|
||||
{{-- ── Guest: login button ──────────────────────────────────────────────── --}}
|
||||
<a href="{{ route('login') }}"
|
||||
class="btn btn-sm fw-600"
|
||||
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-box-arrow-in-left me-1"></i>
|
||||
<span class="d-none d-sm-inline">تسجيل الدخول</span>
|
||||
</a>
|
||||
@endauth
|
||||
@@ -0,0 +1,116 @@
|
||||
@extends('layouts.user')
|
||||
@section('title', 'حجوزاتي — دمشق باركينغ')
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- Page header --}}
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<a href="{{ route('parking.index') }}"
|
||||
class="btn btn-sm"
|
||||
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-arrow-right me-1"></i>العودة
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="fw-800 mb-0" style="font-size:1.25rem;color:#0f172a;">حجوزاتي</h1>
|
||||
<p class="text-sm mb-0" style="color:#64748b;">سجل حجوزاتك في مواقف السيارات</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Stats row ──────────────────────────────────────────────────────────── --}}
|
||||
<div class="row g-3 mb-4">
|
||||
@php
|
||||
$statCards = [
|
||||
['label' => 'إجمالي الحجوزات', 'value' => $stats['total'], 'icon' => 'bi-calendar3', 'color' => '#6366f1', 'bg' => 'rgba(99,102,241,.1)'],
|
||||
['label' => 'نشطة', 'value' => $stats['active'], 'icon' => 'bi-clock-history', 'color' => '#f59e0b', 'bg' => 'rgba(245,158,11,.1)'],
|
||||
['label' => 'مكتملة', 'value' => $stats['completed'], 'icon' => 'bi-check-circle', 'color' => '#10b981', 'bg' => 'rgba(16,185,129,.1)'],
|
||||
['label' => 'ملغاة', 'value' => $stats['cancelled'], 'icon' => 'bi-x-circle', 'color' => '#ef4444', 'bg' => 'rgba(239,68,68,.1)'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@foreach($statCards as $s)
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card stat-card">
|
||||
<div class="card-body p-3 d-flex align-items-center gap-3">
|
||||
<div class="rounded-3 d-flex align-items-center justify-content-center flex-shrink-0"
|
||||
style="width:44px;height:44px;background:{{ $s['bg'] }};">
|
||||
<i class="bi {{ $s['icon'] }}" style="font-size:1.2rem;color:{{ $s['color'] }};"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="fw-800" style="font-size:1.4rem;color:{{ $s['color'] }};line-height:1;">{{ $s['value'] }}</div>
|
||||
<div class="text-xs" style="color:#64748b;">{{ $s['label'] }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{{-- ── Bookings table ──────────────────────────────────────────────────────── --}}
|
||||
<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-list-check me-2" style="color:#6366f1;"></i>
|
||||
قائمة الحجوزات
|
||||
</span>
|
||||
<span class="badge badge-soft-secondary text-xs">{{ $stats['total'] }} حجز</span>
|
||||
</div>
|
||||
|
||||
@if($bookings->isEmpty())
|
||||
<div class="text-center py-5" style="color:#94a3b8;">
|
||||
<i class="bi bi-calendar-x d-block mb-2" style="font-size:2.5rem;opacity:.35;"></i>
|
||||
<p class="fw-600 mb-1" style="color:#64748b;">لا توجد حجوزات بعد</p>
|
||||
<p class="text-sm mb-3" style="color:#94a3b8;">ابدأ بالبحث عن موقف وحجز مكانك</p>
|
||||
<a href="{{ route('parking.index') }}"
|
||||
class="btn btn-sm fw-600"
|
||||
style="background:#6366f1;color:#fff;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-search me-1"></i>تصفح المواقف
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
<div class="table-responsive">
|
||||
<table class="app-table w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>الموقف</th>
|
||||
<th>تاريخ البدء</th>
|
||||
<th>تاريخ الانتهاء</th>
|
||||
<th>الحالة</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($bookings as $booking)
|
||||
<tr>
|
||||
<td class="text-sm" style="color:#94a3b8;">{{ $booking->id }}</td>
|
||||
<td>
|
||||
<span class="fw-600" style="color:#0f172a;">
|
||||
{{ $booking->parkingLot?->name ?? '—' }}
|
||||
</span>
|
||||
<div class="text-xs" style="color:#94a3b8;">{{ $booking->parkingLot?->address }}</div>
|
||||
</td>
|
||||
<td class="text-sm" style="color:#475569;">
|
||||
{{ $booking->start_time->format('Y/m/d') }}
|
||||
<div class="text-xs" style="color:#94a3b8;">{{ $booking->start_time->format('H:i') }}</div>
|
||||
</td>
|
||||
<td class="text-sm" style="color:#475569;">
|
||||
{{ $booking->end_time->format('Y/m/d') }}
|
||||
<div class="text-xs" style="color:#94a3b8;">{{ $booking->end_time->format('H:i') }}</div>
|
||||
</td>
|
||||
<td>
|
||||
@if($booking->status === 'active')
|
||||
<span class="badge badge-soft-warning">نشط</span>
|
||||
@elseif($booking->status === 'completed')
|
||||
<span class="badge badge-soft-success">مكتمل</span>
|
||||
@else
|
||||
<span class="badge badge-soft-danger">ملغي</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,142 @@
|
||||
@extends('layouts.user')
|
||||
@section('title', 'معلوماتي — دمشق باركينغ')
|
||||
|
||||
@section('content')
|
||||
|
||||
{{-- Page header --}}
|
||||
<div class="d-flex align-items-center gap-3 mb-4">
|
||||
<a href="{{ route('parking.index') }}"
|
||||
class="btn btn-sm"
|
||||
style="background:#f1f5f9;color:#475569;border:none;border-radius:.5rem;font-family:'Cairo',sans-serif;">
|
||||
<i class="bi bi-arrow-right me-1"></i>العودة
|
||||
</a>
|
||||
<div>
|
||||
<h1 class="fw-800 mb-0" style="font-size:1.25rem;color:#0f172a;">معلوماتي</h1>
|
||||
<p class="text-sm mb-0" style="color:#64748b;">إدارة بيانات حسابك الشخصي</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
|
||||
{{-- ── Identity card ──────────────────────────────────────────────────── --}}
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex align-items-center gap-4 flex-wrap">
|
||||
{{-- Avatar --}}
|
||||
<div style="width:72px;height:72px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1.75rem;color:#fff;flex-shrink:0;">
|
||||
{{ mb_substr($user->name, 0, 1) }}
|
||||
</div>
|
||||
<div>
|
||||
<h2 class="fw-800 mb-1" style="font-size:1.15rem;color:#0f172a;">{{ $user->name }}</h2>
|
||||
<p class="mb-1 text-sm" style="color:#64748b;">{{ $user->email }}</p>
|
||||
@php
|
||||
$roleLabel = match($user->role) {
|
||||
'admin' => 'مدير النظام',
|
||||
'operator' => 'مشغّل',
|
||||
default => 'مستخدم',
|
||||
};
|
||||
$roleStyle = match($user->role) {
|
||||
'admin' => 'background:rgba(239,68,68,.1);color:#b91c1c;',
|
||||
'operator' => 'background:rgba(245,158,11,.1);color:#92400e;',
|
||||
default => 'background:rgba(99,102,241,.1);color:#4338ca;',
|
||||
};
|
||||
@endphp
|
||||
<span class="badge" style="{{ $roleStyle }}font-size:.78rem;padding:.3em .75em;">{{ $roleLabel }}</span>
|
||||
<span class="text-xs ms-2" style="color:#94a3b8;">
|
||||
عضو منذ {{ $user->created_at->translatedFormat('F Y') }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Edit name ───────────────────────────────────────────────────────── --}}
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<span class="fw-700" style="font-size:.9rem;">
|
||||
<i class="bi bi-person me-2" style="color:#6366f1;"></i>تعديل الاسم
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
@if($errors->updateName->any())
|
||||
<div class="alert alert-danger border-0 rounded-3 py-2 mb-3 text-sm">
|
||||
{{ $errors->updateName->first() }}
|
||||
</div>
|
||||
@endif
|
||||
<form method="POST" action="{{ route('profile.update') }}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<div class="mb-3">
|
||||
<label class="form-label">الاسم الكامل</label>
|
||||
<input type="text" name="name"
|
||||
class="form-control"
|
||||
value="{{ old('name', $user->name) }}"
|
||||
placeholder="أدخل اسمك الكامل">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">البريد الإلكتروني</label>
|
||||
<input type="email"
|
||||
class="form-control"
|
||||
value="{{ $user->email }}"
|
||||
disabled
|
||||
style="background:#f8fafc;color:#94a3b8;">
|
||||
<div class="text-xs mt-1" style="color:#94a3b8;">لا يمكن تغيير البريد الإلكتروني.</div>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="btn fw-700"
|
||||
style="background:#6366f1;color:#fff;border:none;font-family:'Cairo',sans-serif;border-radius:.5rem;padding:.5rem 1.5rem;">
|
||||
<i class="bi bi-check2 me-1"></i>حفظ التغييرات
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- ── Change password ─────────────────────────────────────────────────── --}}
|
||||
<div class="col-md-6">
|
||||
<div class="card h-100">
|
||||
<div class="card-header">
|
||||
<span class="fw-700" style="font-size:.9rem;">
|
||||
<i class="bi bi-lock me-2" style="color:#f59e0b;"></i>تغيير كلمة السر
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
@if($errors->updatePassword->any())
|
||||
<div class="alert alert-danger border-0 rounded-3 py-2 mb-3 text-sm">
|
||||
{{ $errors->updatePassword->first() }}
|
||||
</div>
|
||||
@endif
|
||||
<form method="POST" action="{{ route('profile.password') }}">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<div class="mb-3">
|
||||
<label class="form-label">كلمة السر الحالية</label>
|
||||
<input type="password" name="current_password"
|
||||
class="form-control" placeholder="••••••••" dir="ltr">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">كلمة السر الجديدة</label>
|
||||
<input type="password" name="password"
|
||||
class="form-control" placeholder="••••••••" dir="ltr">
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="form-label">تأكيد كلمة السر الجديدة</label>
|
||||
<input type="password" name="password_confirmation"
|
||||
class="form-control" placeholder="••••••••" dir="ltr">
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="btn fw-700"
|
||||
style="background:#f59e0b;color:#fff;border:none;font-family:'Cairo',sans-serif;border-radius:.5rem;padding:.5rem 1.5rem;">
|
||||
<i class="bi bi-shield-lock me-1"></i>تحديث كلمة السر
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
@@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ar" dir="rtl">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ config('app.name', 'دمشق باركينغ') }}</title>
|
||||
@vite(['resources/css/app.scss', 'resources/js/app.js'])
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<div class="container-fluid vh-100 d-flex align-items-center justify-content-center">
|
||||
<div class="row w-100">
|
||||
<div class="col-lg-6 offset-lg-3">
|
||||
<div class="card border-0 shadow-lg rounded-5 overflow-hidden">
|
||||
<div class="card-body p-5 text-center">
|
||||
<h1 class="display-3 fw-bold text-primary mb-4">
|
||||
<i class="bi bi-car-front"></i>
|
||||
دمشق باركينغ
|
||||
</h1>
|
||||
<p class="lead text-muted mb-5">نظام مواقف السيارات المتكامل</p>
|
||||
<div class="row g-4 justify-content-center">
|
||||
@guest
|
||||
<div class="col-md-6">
|
||||
<a href="{{ route('login') }}" class="btn btn-primary btn-lg w-100 py-3 rounded-4 shadow-lg fs-5">
|
||||
<i class="bi bi-box-arrow-in-left me-2"></i>تسجيل الدخول
|
||||
</a>
|
||||
</div>
|
||||
@if (Route::has('register'))
|
||||
<div class="col-md-6">
|
||||
<a href="{{ route('register') }}" class="btn btn-success btn-lg w-100 py-3 rounded-4 shadow-lg fs-5">
|
||||
<i class="bi bi-person-plus me-2"></i>إنشاء حساب
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="col-md-6">
|
||||
<a href="{{ route('admin.dashboard') }}" class="btn btn-primary btn-lg w-100 py-3 rounded-4 shadow-lg fs-5">
|
||||
<i class="bi bi-house-door me-2"></i>لوحة الإدارة
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<a href="{{ route('operator.dashboard') }}" class="btn btn-warning btn-lg w-100 py-3 rounded-4 shadow-lg fs-5">
|
||||
<i class="bi bi-gear me-2"></i>لوحة المشغل
|
||||
</a>
|
||||
</div>
|
||||
@endguest
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in new issue
Block a user