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,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Booking;
|
||||
use App\Models\ParkingLot;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class BookingController extends Controller
|
||||
{
|
||||
public function activeIndex(Request $request): \Illuminate\View\View
|
||||
{
|
||||
$parkingLotId = $request->get('parking_lot_id');
|
||||
$parkingLots = ParkingLot::active()->get(['id', 'name']);
|
||||
|
||||
$query = Booking::with('parkingLot')
|
||||
->where('status', 'active')
|
||||
->orderBy('start_time', 'desc');
|
||||
|
||||
if ($parkingLotId) {
|
||||
$query->where('parking_lot_id', $parkingLotId);
|
||||
}
|
||||
|
||||
$activeBookings = $query->paginate(50);
|
||||
|
||||
return view('admin.bookings.active', compact('activeBookings', 'parkingLots', 'parkingLotId'));
|
||||
}
|
||||
|
||||
public function completeBooking(Request $request, Booking $booking): JsonResponse
|
||||
{
|
||||
if ($booking->status !== 'active') {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => 'الحجز غير نشط'
|
||||
], 400);
|
||||
}
|
||||
|
||||
$parkingLot = $booking->parkingLot;
|
||||
$actualDurationHours = $booking->start_time->diffInHours(now());
|
||||
$actualFee = ceil($actualDurationHours) * $parkingLot->price_per_hour;
|
||||
|
||||
$booking->update([
|
||||
'status' => 'completed',
|
||||
'end_time' => now()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'تم إنهاء الحجز بنجاح',
|
||||
'data' => [
|
||||
'actual_duration' => $actualDurationHours . ' ساعات',
|
||||
'actual_fee' => number_format($actualFee, 2) . ' ' . config('app.currency', 'ريال'),
|
||||
'vehicle_plate' => $booking->vehicle_plate ?? $booking->customer_name
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Booking;
|
||||
use App\Models\ParkingLot;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class DashboardController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
return view('admin.dashboard');
|
||||
}
|
||||
|
||||
public function statsJson(Request $request)
|
||||
{
|
||||
// Total metrics
|
||||
$totalParkingLots = ParkingLot::count();
|
||||
$totalBookings = Booking::count();
|
||||
|
||||
// Active bookings (assume status = 'active'; adjust if using time overlap)
|
||||
$activeBookings = Booking::where('status', 'active')->count();
|
||||
|
||||
// Occupancy rate: (active bookings / total capacity) * 100
|
||||
$totalCapacity = ParkingLot::sum('total_capacity');
|
||||
$occupancyRate = $totalCapacity > 0 ? round(($activeBookings / $totalCapacity) * 100, 1) : 0;
|
||||
|
||||
// Estimated revenue: sum price_per_hour for all active bookings
|
||||
$revenue = Booking::where('status', 'active')
|
||||
->join('parking_lots', 'bookings.parking_lot_id', '=', 'parking_lots.id')
|
||||
->sum('parking_lots.price_per_hour');
|
||||
|
||||
// Available spots (total capacity - active)
|
||||
$availableSpots = $totalCapacity - $activeBookings;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'total_parking_lots' => $totalParkingLots,
|
||||
'total_bookings' => $totalBookings,
|
||||
'active_bookings' => $activeBookings,
|
||||
'occupancy_rate' => $occupancyRate,
|
||||
'estimated_revenue' => round($revenue, 2),
|
||||
'available_spots' => $availableSpots,
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function chartsJson(Request $request)
|
||||
{
|
||||
$now = Carbon::now();
|
||||
|
||||
// Daily bookings last 7 days
|
||||
$dailyBookings = Booking::selectRaw('DATE(created_at) as date, COUNT(*) as count')
|
||||
->whereBetween('created_at', [$now->clone()->subDays(7), $now])
|
||||
->groupBy('date')
|
||||
->orderBy('date')
|
||||
->pluck('count', 'date')
|
||||
->toArray();
|
||||
|
||||
// Fill missing days
|
||||
$dates = [];
|
||||
for ($i = 6; $i >= 0; $i--) {
|
||||
$date = $now->clone()->subDays($i)->format('Y-m-d');
|
||||
$dates[$date] = $dailyBookings[$date] ?? 0;
|
||||
}
|
||||
ksort($dates);
|
||||
|
||||
// Top 5 parking lots by total bookings
|
||||
$topLots = ParkingLot::withCount('bookings')
|
||||
->orderBy('bookings_count', 'desc')
|
||||
->take(5)
|
||||
->get(['id', 'name', 'bookings_count']);
|
||||
|
||||
// Occupancy trend: current vs previous day (simplified)
|
||||
$todayActive = Booking::where('status', 'active')->whereDate('created_at', $now->format('Y-m-d'))->count();
|
||||
$yesterdayActive = Booking::where('status', 'active')->whereDate('created_at', $now->clone()->subDay()->format('Y-m-d'))->count();
|
||||
$trend = $todayActive - $yesterdayActive; // positive growth
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'daily_bookings' => array_values($dates),
|
||||
'top_parking_lots' => $topLots->map(fn($lot) => ['name' => $lot->name, 'value' => $lot->bookings_count])->toArray(),
|
||||
'occupancy_trend' => $trend,
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\StoreParkingLotRequest;
|
||||
use App\Models\ParkingLot;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
|
||||
class ParkingLotController extends Controller
|
||||
{
|
||||
public function index(Request $request): \Illuminate\View\View
|
||||
{
|
||||
$parkingLots = ParkingLot::withCount(['bookings as active_bookings_count' => fn($q) => $q->where('status', 'active')])
|
||||
->when($request->search, fn($q) => $q->search($request->search))
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(20);
|
||||
|
||||
return view('admin.parking-lots.index', compact('parkingLots'));
|
||||
}
|
||||
|
||||
public function show(ParkingLot $parkingLot): JsonResponse
|
||||
{
|
||||
$parkingLot->loadCount(['bookings as active_bookings_count' => fn($q) => $q->where('status', 'active')]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => $parkingLot
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(StoreParkingLotRequest $request): JsonResponse
|
||||
{
|
||||
$parkingLot = ParkingLot::create($request->validated());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'تم إضافة موقف السيارات بنجاح',
|
||||
'data' => $parkingLot->loadCount(['bookings as active_bookings_count' => fn($q) => $q->where('status', 'active')])
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(StoreParkingLotRequest $request, ParkingLot $parkingLot): JsonResponse
|
||||
{
|
||||
$parkingLot->update($request->validated());
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => 'تم تحديث موقف السيارات بنجاح',
|
||||
'data' => $parkingLot->loadCount(['bookings as active_bookings_count' => fn($q) => $q->where('status', 'active')])
|
||||
]);
|
||||
}
|
||||
|
||||
public function toggleStatus(ParkingLot $parkingLot): JsonResponse
|
||||
{
|
||||
$parkingLot->update(['is_active' => !$parkingLot->is_active]);
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => $parkingLot->is_active ? 'تم تفعيل الموقف' : 'تم إلغاء تفعيل الموقف',
|
||||
'data' => ['is_active' => $parkingLot->is_active]
|
||||
]);
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
Reference in new issue
Block a user