1171 lines
65 KiB
PHP
1171 lines
65 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{ config('settings.company_name') }} - Dashboard</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
|
<script>
|
|
// Dark mode
|
|
if (localStorage.getItem('darkMode') === 'true' || (!localStorage.getItem('darkMode') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
|
document.documentElement.classList.add('dark');
|
|
}
|
|
|
|
function toggleDarkMode() {
|
|
document.documentElement.classList.toggle('dark');
|
|
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark'));
|
|
updateDarkModeBtn();
|
|
}
|
|
|
|
function updateDarkModeBtn() {
|
|
const isDark = document.documentElement.classList.contains('dark');
|
|
const btn = document.getElementById('darkModeBtn');
|
|
if(btn) {
|
|
btn.innerHTML = isDark ? '<i class="fas fa-sun"></i>' : '<i class="fas fa-moon"></i>';
|
|
}
|
|
}
|
|
</script>
|
|
</head>
|
|
<body class="bg-gray-100 dark:bg-slate-900 min-h-screen transition-colors">
|
|
<!-- Sidebar -->
|
|
<div class="fixed left-0 top-0 h-full w-64 bg-slate-800">
|
|
<div class="p-6 border-b border-slate-700">
|
|
<h1 class="text-xl font-bold text-white flex items-center gap-2">
|
|
@if(config('settings.logo'))
|
|
<img src="{{ asset(config('settings.logo')) }}" alt="Logo" class="w-8 h-8 object-contain">
|
|
@else
|
|
<i class="fas fa-road text-blue-400"></i>
|
|
@endif
|
|
{{ config('settings.company_name') }}
|
|
</h1>
|
|
<p class="text-slate-400 text-xs mt-1">{{ config('settings.company_tagline') }}</p>
|
|
</div>
|
|
<nav class="mt-4 px-4">
|
|
<a href="{{ route('dashboard') }}" class="flex items-center gap-3 px-4 py-3 rounded-lg {{ request()->routeIs('dashboard') ? 'bg-blue-600 text-white' : 'text-slate-300 hover:bg-slate-700' }}">
|
|
<i class="fas fa-chart-line w-5"></i> Dashboard
|
|
</a>
|
|
<a href="{{ route('cars.index') }}" class="flex items-center gap-3 px-4 py-3 rounded-lg {{ request()->routeIs('cars.*') ? 'bg-blue-600 text-white' : 'text-slate-300 hover:bg-slate-700' }} mt-1">
|
|
<i class="fas fa-car w-5"></i> Fleet
|
|
</a>
|
|
<a href="{{ route('customers.index') }}" class="flex items-center gap-3 px-4 py-3 rounded-lg {{ request()->routeIs('customers.*') ? 'bg-blue-600 text-white' : 'text-slate-300 hover:bg-slate-700' }} mt-1">
|
|
<i class="fas fa-users w-5"></i> Customers
|
|
</a>
|
|
<a href="{{ route('rentals.index') }}" class="flex items-center gap-3 px-4 py-3 rounded-lg {{ request()->routeIs('rentals.*') ? 'bg-blue-600 text-white' : 'text-slate-300 hover:bg-slate-700' }} mt-1">
|
|
<i class="fas fa-calendar-check w-5"></i> Rentals
|
|
</a>
|
|
<a href="{{ route('services.index') }}" class="flex items-center gap-3 px-4 py-3 rounded-lg {{ request()->routeIs('services.*') ? 'bg-blue-600 text-white' : 'text-slate-300 hover:bg-slate-700' }} mt-1">
|
|
<i class="fas fa-tools w-5"></i> Services
|
|
</a>
|
|
<a href="{{ route('payments.index') }}" class="flex items-center gap-3 px-4 py-3 rounded-lg {{ request()->routeIs('payments.*') ? 'bg-blue-600 text-white' : 'text-slate-300 hover:bg-slate-700' }} mt-1">
|
|
<i class="fas fa-credit-card w-5"></i> Payments
|
|
</a>
|
|
<a href="{{ route('settings.index') }}" class="flex items-center gap-3 px-4 py-3 rounded-lg {{ request()->routeIs('settings.*') ? 'bg-blue-600 text-white' : 'text-slate-300 hover:bg-slate-700' }} mt-1">
|
|
<i class="fas fa-cog w-5"></i> Settings
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- Main Content -->
|
|
<div class="ml-64 p-8">
|
|
<!-- Header -->
|
|
<div class="flex justify-between items-center mb-8">
|
|
<div>
|
|
<h1 class="text-2xl font-bold text-gray-800">Dashboard</h1>
|
|
<p class="text-gray-500 text-sm">Fleet overview and statistics</p>
|
|
</div>
|
|
<div class="flex items-center gap-3">
|
|
<button onclick="toggleDarkMode()" id="darkModeBtn" class="bg-white dark:bg-slate-800 text-gray-600 dark:text-gray-300 px-4 py-2 rounded-lg border border-gray-200 dark:border-slate-700 hover:bg-gray-50 dark:hover:bg-slate-700">
|
|
<i class="fas fa-moon"></i>
|
|
</button>
|
|
<span class="bg-white dark:bg-slate-800 text-gray-600 dark:text-gray-300 px-4 py-2 rounded-lg border border-gray-200 dark:border-slate-700 text-sm">
|
|
<i class="fas fa-calendar-alt mr-2 text-gray-400"></i>
|
|
{{ now()->format('M d, Y') }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="mb-6 bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-gray-200 dark:border-slate-700 p-4">
|
|
<div class="flex flex-wrap gap-2">
|
|
<button onclick="openQuickRentModal()" class="flex items-center gap-2 bg-gradient-to-r from-amber-500 to-orange-600 text-white px-4 py-2 rounded-lg hover:from-amber-600 hover:to-orange-700 text-sm font-medium">
|
|
<i class="fas fa-bolt"></i> Quick Rent
|
|
</button>
|
|
<a href="{{ route('cars.create') }}" class="flex items-center gap-2 bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 text-sm font-medium">
|
|
<i class="fas fa-plus"></i> Add Car
|
|
</a>
|
|
<a href="{{ route('customers.create') }}" class="flex items-center gap-2 bg-purple-600 text-white px-4 py-2 rounded-lg hover:bg-purple-700 text-sm font-medium">
|
|
<i class="fas fa-user-plus"></i> Add Customer
|
|
</a>
|
|
<a href="{{ route('rentals.create') }}" class="flex items-center gap-2 bg-slate-600 text-white px-4 py-2 rounded-lg hover:bg-slate-700 text-sm font-medium">
|
|
<i class="fas fa-calendar-plus"></i> New Rental
|
|
</a>
|
|
<a href="{{ route('services.create') }}" class="flex items-center gap-2 bg-green-600 text-white px-4 py-2 rounded-lg hover:bg-green-700 text-sm font-medium">
|
|
<i class="fas fa-calendar-check"></i> Schedule Service
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5 mb-8">
|
|
<!-- Total Cars -->
|
|
<a href="{{ route('cars.index') }}" class="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-gray-200 dark:border-slate-700 p-5 hover:border-blue-400 transition">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="text-gray-500 text-sm font-medium">Total Fleet</p>
|
|
<p class="text-3xl font-bold text-gray-800 mt-1">{{ $totalCars }}</p>
|
|
<p class="text-green-600 text-sm mt-2 font-medium">
|
|
{{ $availableCars }} available
|
|
</p>
|
|
</div>
|
|
<div class="w-12 h-12 rounded-lg bg-blue-50 flex items-center justify-center">
|
|
<i class="fas fa-car text-blue-600 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<!-- Active Rentals -->
|
|
<a href="{{ route('rentals.index') }}" class="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-gray-200 dark:border-slate-700 p-5 hover:border-amber-400 transition">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="text-gray-500 text-sm font-medium">Active Rentals</p>
|
|
<p class="text-3xl font-bold text-gray-800 mt-1">{{ $activeRentals }}</p>
|
|
<p class="text-amber-600 text-sm mt-2 font-medium">
|
|
{{ $pendingRentals }} pending
|
|
</p>
|
|
</div>
|
|
<div class="w-12 h-12 rounded-lg bg-amber-50 flex items-center justify-center">
|
|
<i class="fas fa-calendar-check text-amber-600 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<!-- Customers -->
|
|
<a href="{{ route('customers.index') }}" class="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-gray-200 dark:border-slate-700 p-5 hover:border-purple-400 transition">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="text-gray-500 text-sm font-medium">Customers</p>
|
|
<p class="text-3xl font-bold text-gray-800 mt-1">{{ $totalCustomers }}</p>
|
|
<p class="text-purple-600 text-sm mt-2 font-medium">
|
|
{{ $activeCustomers }} active
|
|
</p>
|
|
</div>
|
|
<div class="w-12 h-12 rounded-lg bg-purple-50 flex items-center justify-center">
|
|
<i class="fas fa-users text-purple-600 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
|
|
<!-- Revenue -->
|
|
<a href="{{ route('payments.index') }}" class="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-gray-200 dark:border-slate-700 p-5 hover:border-green-400 transition">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="text-gray-500 text-sm font-medium">Total Revenue</p>
|
|
<p class="text-3xl font-bold text-gray-800 mt-1"> BHD {{ number_format($totalRevenue, 0) }}</p>
|
|
<p class="text-green-600 text-sm mt-2 font-medium">
|
|
BHD {{ number_format($monthlyRevenue, 0) }} this month
|
|
</p>
|
|
</div>
|
|
<div class="w-12 h-12 rounded-lg bg-green-50 flex items-center justify-center">
|
|
<i class="fas fa-dollar-sign text-green-600 text-xl"></i>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<!-- Charts Row -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-5 mb-8">
|
|
<!-- Revenue Chart (2/3 width) -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 lg:col-span-2">
|
|
<h3 class="text-gray-800 dark:text-white font-semibold mb-2">Revenue Overview</h3>
|
|
<canvas id="revenueChart" height="140"></canvas>
|
|
</div>
|
|
|
|
<!-- Fleet Status (1/3 width) -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 flex flex-col">
|
|
<h3 class="text-gray-800 dark:text-white font-semibold mb-2">Fleet Status</h3>
|
|
<div class="flex-1 flex items-center justify-center">
|
|
<canvas id="fleetChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Second Row -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-5 mb-8">
|
|
<!-- Rentals Trend -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4 lg:col-span-2">
|
|
<h3 class="text-gray-800 dark:text-white font-semibold mb-2">Rentals Trend (Last 6 Months)</h3>
|
|
<canvas id="rentalsChart" height="100"></canvas>
|
|
</div>
|
|
|
|
<!-- Service Stats -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
|
<h3 class="text-gray-800 dark:text-white font-semibold mb-3">Service Center</h3>
|
|
<div class="space-y-2">
|
|
<div class="flex justify-between items-center p-2 bg-gray-50 rounded-lg">
|
|
<span class="text-gray-600 text-sm">Total Services</span>
|
|
<span class="text-gray-800 font-semibold text-sm">{{ $totalServices }}</span>
|
|
</div>
|
|
<div class="flex justify-between items-center p-2 bg-gray-50 rounded-lg">
|
|
<span class="text-gray-600 text-sm">Pending</span>
|
|
<span class="text-amber-600 font-semibold text-sm">{{ $pendingServices }}</span>
|
|
</div>
|
|
<div class="flex justify-between items-center p-2 bg-gray-50 rounded-lg">
|
|
<span class="text-gray-600 text-sm">Total Cost</span>
|
|
<span class="text-red-600 font-semibold text-sm"> BHD {{ number_format($totalServiceCost, 0) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Activity -->
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
|
<!-- Recent Rentals -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
|
<h3 class="text-gray-800 dark:text-white font-semibold mb-3">Recent Rentals</h3>
|
|
<div class="space-y-2">
|
|
@forelse($recentRentals as $rental)
|
|
<a href="{{ route('rentals.show', $rental) }}" class="flex items-center justify-between p-2.5 bg-gray-50 rounded-lg hover:bg-gray-100 transition">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-8 h-8 rounded-lg bg-blue-100 flex items-center justify-center">
|
|
<i class="fas fa-car text-blue-600 text-xs"></i>
|
|
</div>
|
|
<div>
|
|
<p class="text-gray-800 font-medium text-sm">{{ $rental->car->brand }} {{ $rental->car->model }}</p>
|
|
<p class="text-gray-500 text-xs">{{ $rental->customer->name }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<span class="px-1.5 py-0.5 rounded text-xs font-medium
|
|
="px-2 {{ $rental->status == 'active' ? 'bg-green-100 text-green-700' :
|
|
($rental->status == 'pending' ? 'bg-amber-100 text-amber-700' : 'bg-gray-100 text-gray-600') }}">
|
|
{{ $rental->status }}
|
|
</span>
|
|
</div>
|
|
</a>
|
|
@empty
|
|
<p class="text-gray-500 text-center py-4 text-sm">No rentals yet</p>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Services -->
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-200 p-4">
|
|
<h3 class="text-gray-800 dark:text-white font-semibold mb-3">Recent Services</h3>
|
|
<div class="space-y-2">
|
|
@forelse($recentServices as $service)
|
|
<a href="{{ route('services.show', $service) }}" class="flex items-center justify-between p-2.5 bg-gray-50 rounded-lg hover:bg-gray-100 transition">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-8 h-8 rounded-lg bg-purple-100 flex items-center justify-center">
|
|
<i class="fas fa-wrench text-purple-600 text-xs"></i>
|
|
</div>
|
|
<div>
|
|
<p class="text-gray-800 font-medium text-sm">{{ $service->car->brand }} {{ $service->car->model }}</p>
|
|
<p class="text-gray-500 text-xs capitalize">{{ str_replace('_', ' ', $service->type) }}</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<span class="text-gray-800 font-medium text-sm"> BHD {{ number_format($service->cost, 0) }}</span>
|
|
</div>
|
|
</a>
|
|
@empty
|
|
<p class="text-gray-500 text-center py-3 text-sm">No services yet</p>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// Revenue Chart
|
|
const revenueCtx = document.getElementById('revenueChart').getContext('2d');
|
|
new Chart(revenueCtx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: {!! json_encode(['5 mo ago', '4 mo ago', '3 mo ago', '2 mo ago', 'Last mo', 'This mo']) !!},
|
|
datasets: [{
|
|
label: 'Revenue',
|
|
data: {!! json_encode($monthlyRevenueChart) !!},
|
|
borderColor: '#2563eb',
|
|
backgroundColor: 'rgba(37, 99, 235, 0.1)',
|
|
fill: true,
|
|
tension: 0.4,
|
|
pointBackgroundColor: '#2563eb',
|
|
pointRadius: 4,
|
|
pointHoverRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
x: { grid: { display: false }, ticks: { color: '#6b7280' } },
|
|
y: { grid: { color: '#e5e7eb' }, ticks: { color: '#6b7280', callback: function(v) { return 'BHD ' + v; } } }
|
|
}
|
|
}
|
|
});
|
|
|
|
// Fleet Chart
|
|
const fleetCtx = document.getElementById('fleetChart').getContext('2d');
|
|
const fleetChart = new Chart(fleetCtx, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: ['Available', 'Rented', 'Maintenance'],
|
|
datasets: [{
|
|
data: [{!! $carStatusData['available'] !!}, {!! $carStatusData['rented'] !!}, {!! $carStatusData['maintenance'] !!}],
|
|
backgroundColor: ['#16a34a', '#f59e0b', '#dc2626'],
|
|
borderWidth: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
onClick: (evt, elements) => {
|
|
if (elements.length > 0) {
|
|
const index = elements[0].index;
|
|
const statuses = ['available', 'rented', 'maintenance'];
|
|
openFleetModal(statuses[index]);
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: { position: 'bottom', labels: { color: '#6b7280', padding: 15 } }
|
|
}
|
|
}
|
|
});
|
|
|
|
// Rentals Chart
|
|
const rentalsCtx = document.getElementById('rentalsChart').getContext('2d');
|
|
new Chart(rentalsCtx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: {!! json_encode(['5 mo ago', '4 mo ago', '3 mo ago', '2 mo ago', 'Last mo', 'This mo']) !!},
|
|
datasets: [{
|
|
label: 'Rentals',
|
|
data: {!! json_encode($monthlyRentals) !!},
|
|
backgroundColor: '#6366f1',
|
|
borderRadius: 4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
x: { grid: { display: false }, ticks: { color: '#6b7280' } },
|
|
y: { grid: { color: '#e5e7eb' }, ticks: { color: '#6b7280' } }
|
|
}
|
|
}
|
|
});
|
|
|
|
// Fleet modal data
|
|
const carsByStatus = {
|
|
available: @json($carsByStatus['available']->toArray()),
|
|
rented: @json($carsByStatus['rented']->toArray()),
|
|
maintenance: @json($carsByStatus['maintenance']->toArray())
|
|
};
|
|
|
|
function openFleetModal(status = null) {
|
|
const modal = document.getElementById('fleetModal');
|
|
const title = document.getElementById('modalTitle');
|
|
const subtitle = document.getElementById('modalSubtitle');
|
|
const list = document.getElementById('modalList');
|
|
|
|
// If no status provided, show all
|
|
if (!status) {
|
|
// Show all cars grouped by status
|
|
let html = '';
|
|
const statusCounts = { available: 0, rented: 0, maintenance: 0 };
|
|
|
|
['available', 'rented', 'maintenance'].forEach(s => {
|
|
const cars = carsByStatus[s];
|
|
statusCounts[s] = cars.length;
|
|
if (cars.length > 0) {
|
|
const colors = { available: 'text-green-600 bg-green-50', rented: 'text-amber-600 bg-amber-50', maintenance: 'text-red-600 bg-red-50' };
|
|
const borderColors = { available: 'border-l-green-500', rented: 'border-l-amber-500', maintenance: 'border-l-red-500' };
|
|
html += `<h4 class="font-semibold text-gray-700 text-sm uppercase tracking-wide mb-2 mt-4 flex items-center gap-2">
|
|
<span class="w-2 h-2 rounded-full ${s === 'available' ? 'bg-green-500' : s === 'rented' ? 'bg-amber-500' : 'bg-red-500'}"></span>
|
|
${s} (${cars.length})
|
|
</h4>`;
|
|
cars.forEach(car => {
|
|
html += `<a href="/cars/${car.id}" class="flex items-center justify-between p-3 bg-white border border-gray-100 rounded-lg hover:border-blue-300 hover:shadow-md hover:bg-blue-50/50 transition-all duration-200 group">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-9 h-9 rounded-lg ${colors[s]} flex items-center justify-center">
|
|
<i class="fas fa-car text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-gray-800 group-hover:text-blue-700">${car.brand} ${car.model}</p>
|
|
<p class="text-xs text-gray-500">${car.year} • ${car.color}</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="font-medium text-gray-600 text-sm">${car.license_plate}</p>
|
|
<p class="text-xs text-gray-400">${car.daily_rate} BHD/day</p>
|
|
</div>
|
|
</a>`;
|
|
});
|
|
}
|
|
});
|
|
list.innerHTML = html || '<p class="text-gray-500 text-center py-8">No cars found</p>';
|
|
title.textContent = 'Complete Fleet';
|
|
subtitle.textContent = `${statusCounts.available + statusCounts.rented + statusCounts.maintenance} vehicles total`;
|
|
} else {
|
|
const cars = carsByStatus[status] || [];
|
|
const statusColors = {
|
|
available: { bg: 'bg-green-50', icon: 'text-green-600', border: 'border-l-green-500' },
|
|
rented: { bg: 'bg-amber-50', icon: 'text-amber-600', border: 'border-l-amber-500' },
|
|
maintenance: { bg: 'bg-red-50', icon: 'text-red-600', border: 'border-l-red-500' }
|
|
};
|
|
const statusLabels = { available: 'Available', rented: 'Rented', maintenance: 'Maintenance' };
|
|
const statusIcons = { available: 'fa-check-circle', rented: 'fa-clock', maintenance: 'fa-tools' };
|
|
|
|
let html = '';
|
|
cars.forEach(car => {
|
|
const c = statusColors[status];
|
|
html += `<a href="/cars/${car.id}" class="flex items-center justify-between p-3 bg-white border border-gray-100 rounded-lg hover:border-blue-300 hover:shadow-md hover:bg-blue-50/50 transition-all duration-200 group">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-9 h-9 rounded-lg ${c.bg} flex items-center justify-center">
|
|
<i class="fas ${statusIcons[status]} ${c.icon} text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-gray-800 group-hover:text-blue-700">${car.brand} ${car.model}</p>
|
|
<p class="text-xs text-gray-500">${car.year} • ${car.color}</p>
|
|
</div>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="font-medium text-gray-600 text-sm">${car.license_plate}</p>
|
|
<p class="text-xs text-gray-400">${car.daily_rate} BHD/day</p>
|
|
</div>
|
|
</a>`;
|
|
});
|
|
list.innerHTML = html || `<div class="text-center py-8">
|
|
<div class="w-16 h-16 rounded-full bg-gray-100 flex items-center justify-center mx-auto mb-3">
|
|
<i class="fas fa-car text-gray-400 text-2xl"></i>
|
|
</div>
|
|
<p class="text-gray-500">No cars in this status</p>
|
|
</div>`;
|
|
title.textContent = statusLabels[status] || status;
|
|
subtitle.textContent = `${cars.length} vehicle${cars.length !== 1 ? 's' : ''}`;
|
|
}
|
|
|
|
modal.classList.remove('hidden');
|
|
}
|
|
|
|
function closeFleetModal() {
|
|
document.getElementById('fleetModal').classList.add('hidden');
|
|
}
|
|
</script>
|
|
|
|
<!-- Fleet Modal -->
|
|
<div id="fleetModal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm hidden z-50 flex items-center justify-center p-4">
|
|
<div class="bg-white rounded-2xl shadow-2xl max-w-lg w-full max-h-[85vh] overflow-hidden transform transition-all">
|
|
<!-- Modal Header -->
|
|
<div class="relative bg-gradient-to-r from-slate-800 to-slate-700 px-6 py-5">
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-white/20 flex items-center justify-center">
|
|
<i class="fas fa-car text-white text-lg"></i>
|
|
</div>
|
|
<div>
|
|
<h3 id="modalTitle" class="text-xl font-bold text-white">Fleet Status</h3>
|
|
<p class="text-slate-300 text-xs" id="modalSubtitle">Vehicle Details</p>
|
|
</div>
|
|
</div>
|
|
<button onclick="closeFleetModal()" class="w-8 h-8 rounded-lg bg-white/10 text-white hover:bg-white/20 flex items-center justify-center transition">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<!-- Modal Body -->
|
|
<div id="modalList" class="p-4 overflow-y-auto max-h-[55vh] space-y-2">
|
|
</div>
|
|
<!-- Modal Footer -->
|
|
<div class="px-6 py-4 bg-gray-50 border-t border-gray-100">
|
|
<p class="text-center text-xs text-gray-400">Click on a car to view full details</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Rent Modal -->
|
|
<div id="quickRentModal" style="display: none; position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(15, 23, 42, 0.6); z-index: 9999; align-items: center; justify-content: center; padding: 16px;">
|
|
<div style="background: white; border-radius: 16px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); max-width: 672px; width: 100%; max-height: 90vh; overflow: hidden; display: flex; flex-direction: column;">
|
|
<!-- Header -->
|
|
<div style="background: linear-gradient(to right, #f59e0b, #ea580c); padding: 20px 24px; border-radius: 16px 16px 0 0;">
|
|
<div class="flex justify-between items-center">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-white/20 flex items-center justify-center">
|
|
<i class="fas fa-bolt text-white text-lg"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="text-xl font-bold text-white">Quick Rent</h3>
|
|
<p class="text-amber-100 text-xs">Complete rental in minutes</p>
|
|
</div>
|
|
</div>
|
|
<button onclick="closeQuickRentModal()" class="w-8 h-8 rounded-lg bg-white/10 text-white hover:bg-white/20 flex items-center justify-center transition">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
<!-- Steps -->
|
|
<div class="flex justify-center mt-4">
|
|
<div class="flex items-center gap-2">
|
|
<div id="stepIndicator1" class="step-dot active"></div>
|
|
<div class="step-line"></div>
|
|
<div id="stepIndicator2" class="step-dot"></div>
|
|
<div class="step-line"></div>
|
|
<div id="stepIndicator3" class="step-dot"></div>
|
|
<div class="step-line"></div>
|
|
<div id="stepIndicator4" class="step-dot"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Body -->
|
|
<div style="padding: 24px; overflow-y: auto; flex: 1;">
|
|
<!-- Step 1: Customer -->
|
|
<div id="step1" class="step-content">
|
|
<h4 class="text-lg font-semibold text-gray-800 mb-4">Select or Create Customer</h4>
|
|
|
|
<!-- Existing Customer -->
|
|
<div class="mb-4">
|
|
<label class="flex items-center gap-2 cursor-pointer mb-3">
|
|
<input type="radio" name="customerType" value="existing" checked onchange="toggleCustomerForm()" class="w-4 h-4 text-amber-500">
|
|
<span class="text-gray-700 font-medium">Existing Customer</span>
|
|
</label>
|
|
<!-- Searchable Dropdown -->
|
|
<div class="relative">
|
|
<input type="text" id="customerSearch" onkeyup="filterCustomers()" onfocus="showCustomerDropdown()" onblur="setTimeout(() => hideCustomerDropdown(), 200)"
|
|
placeholder="Search by name, phone, or email..."
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<div id="customerDropdown" style="display: none; position: absolute; z-index: 99999; width: 100%; margin-top: 4px; background: white; border: 1px solid #e5e7eb; border-radius: 8px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); max-height: 192px; overflow-y: auto;">
|
|
<div id="customerList"></div>
|
|
</div>
|
|
<input type="hidden" id="selectedCustomerId" value="">
|
|
</div>
|
|
<p id="selectedCustomerInfo" style="margin-top: 8px; font-size: 14px; color: #16a34a; display: none;">
|
|
<i class="fas fa-check-circle"></i> <span id="selectedCustomerName"></span> selected
|
|
</p>
|
|
</div>
|
|
|
|
<!-- New Customer -->
|
|
<div>
|
|
<label class="flex items-center gap-2 cursor-pointer mb-3">
|
|
<input type="radio" name="customerType" value="new" onchange="toggleCustomerForm()" class="w-4 h-4 text-amber-500">
|
|
<span class="text-gray-700 font-medium">New Customer</span>
|
|
</label>
|
|
<div id="newCustomerForm" class="hidden grid grid-cols-2 gap-3">
|
|
<input type="text" id="newCustomerName" placeholder="Full Name" class="px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<input type="email" id="newCustomerEmail" placeholder="Email" class="px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<input type="text" id="newCustomerPhone" placeholder="Phone" class="px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<input type="text" id="newCustomerLicense" placeholder="License Number" class="px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<input type="date" id="newCustomerLicenseExpiry" class="px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<input type="date" id="newCustomerDob" class="px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<input type="text" id="newCustomerAddress" placeholder="Address" class="px-3 py-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500 col-span-2">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 2: Car -->
|
|
<div id="step2" class="step-content hidden">
|
|
<h4 class="text-lg font-semibold text-gray-800 mb-4">Select Vehicle</h4>
|
|
<!-- Searchable Car Dropdown -->
|
|
<div class="relative">
|
|
<input type="text" id="carSearch" onkeyup="filterCars()" onfocus="showCarDropdown()" onblur="setTimeout(() => hideCarDropdown(), 200)"
|
|
placeholder="Search by make, model, or plate..."
|
|
class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<div id="carDropdown" style="display: none; position: absolute; z-index: 99999; width: 100%; margin-top: 4px; background: white; border: 1px solid #e5e7eb; border-radius: 8px; box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1); max-height: 192px; overflow-y: auto;">
|
|
<div id="carList"></div>
|
|
</div>
|
|
<input type="hidden" id="selectedCarId" value="">
|
|
<p id="selectedCarInfo" style="margin-top: 8px; font-size: 14px; color: #16a34a; display: none;">
|
|
<i class="fas fa-check-circle"></i> <span id="selectedCarName"></span> selected
|
|
</p>
|
|
</div>
|
|
<div id="selectedCarDetails" class="mt-4 p-4 bg-gray-50 rounded-lg hidden">
|
|
<div class="flex justify-between items-center">
|
|
<div>
|
|
<p id="carDisplayName" class="font-semibold text-gray-800"></p>
|
|
<p id="carDisplayDetails" class="text-sm text-gray-500"></p>
|
|
</div>
|
|
<p id="carDisplayRate" class="text-xl font-bold text-amber-600"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 3: Rental Period -->
|
|
<div id="step3" class="step-content hidden">
|
|
<h4 class="text-lg font-semibold text-gray-800 mb-4">Rental Period</h4>
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Rental Type</label>
|
|
<select id="rentalType" onchange="calculateRentalPrice()" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<option value="daily" data-days="1">Daily</option>
|
|
<option value="weekly" data-days="7">Weekly</option>
|
|
<option value="monthly" data-days="30">Monthly</option>
|
|
<option value="lease" data-days="365">Lease (1 Year)</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Start Date</label>
|
|
<input type="date" id="rentalStartDate" onchange="calculateRentalPrice()" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Pickup Time</label>
|
|
<input type="time" id="rentalPickupTime" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">End Date</label>
|
|
<input type="date" id="rentalEndDate" onchange="calculateRentalPrice()" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Return Time</label>
|
|
<input type="time" id="rentalReturnTime" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
</div>
|
|
</div>
|
|
<!-- Price Summary -->
|
|
<div class="mt-6 p-4 bg-gradient-to-r from-amber-50 to-orange-50 rounded-xl border border-amber-200">
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-gray-600">Rental Amount</span>
|
|
<span id="rentalAmount" class="font-semibold text-gray-800">0.00 BHD</span>
|
|
</div>
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-gray-600">Discount</span>
|
|
<span id="rentalDiscount" class="text-green-600 font-medium">0%</span>
|
|
</div>
|
|
<div class="flex justify-between items-center mb-2">
|
|
<span class="text-gray-600">Advance Payment (50%)</span>
|
|
<span id="rentalAdvance" class="font-semibold text-amber-600">0.00 BHD</span>
|
|
</div>
|
|
<div class="border-t border-amber-200 mt-3 pt-3 flex justify-between items-center">
|
|
<span class="font-semibold text-gray-800">Total</span>
|
|
<span id="rentalTotal" class="text-2xl font-bold text-amber-600">0.00 BHD</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step 4: Confirm & Payment -->
|
|
<div id="step4" class="step-content hidden">
|
|
<h4 class="text-lg font-semibold text-gray-800 mb-4">Confirm & Payment</h4>
|
|
|
|
<!-- Summary -->
|
|
<div class="p-4 bg-gray-50 rounded-lg mb-4">
|
|
<h5 class="font-medium text-gray-700 mb-3">Rental Summary</h5>
|
|
<div class="space-y-2 text-sm">
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500">Customer:</span>
|
|
<span id="summaryCustomer" class="font-medium text-gray-800">-</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500">Vehicle:</span>
|
|
<span id="summaryCar" class="font-medium text-gray-800">-</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500">Period:</span>
|
|
<span id="summaryPeriod" class="font-medium text-gray-800">-</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500">Type:</span>
|
|
<span id="summaryType" class="font-medium text-gray-800 capitalize">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Advance Payment</label>
|
|
<input type="number" id="advancePayment" step="0.01" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Payment Method</label>
|
|
<select id="paymentMethod" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500">
|
|
<option value="cash">Cash</option>
|
|
<option value="card">Card</option>
|
|
<option value="bank_transfer">Bank Transfer</option>
|
|
<option value="online">Online</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Notes (Optional)</label>
|
|
<textarea id="rentalNotes" rows="2" class="w-full px-4 py-3 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-amber-500"></textarea>
|
|
</div>
|
|
|
|
<!-- Digital Signature -->
|
|
<div class="mt-4">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Customer Signature</label>
|
|
<div style="border: 2px dashed #d1d5db; border-radius: 8px; padding: 8px; background: #fafafa;">
|
|
<canvas id="signaturePad" width="400" height="120" style="width: 100%; height: 120px; border-radius: 4px; cursor: crosshair; background: white; touch-action: none;"></canvas>
|
|
</div>
|
|
<div style="display: flex; justify-content: space-between; margin-top: 8px;">
|
|
<button type="button" onclick="clearSignature()" style="padding: 6px 12px; font-size: 12px; border-radius: 6px; border: 1px solid #d1d5db; background: white; color: #6b7280; cursor: pointer;">
|
|
<i class="fas fa-eraser"></i> Clear
|
|
</button>
|
|
<span style="font-size: 12px; color: #9ca3af;">Sign above to confirm</span>
|
|
</div>
|
|
<input type="hidden" id="customerSignature" value="">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success -->
|
|
<div id="stepSuccess" class="hidden text-center py-8">
|
|
<div class="w-20 h-20 bg-green-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
<i class="fas fa-check text-green-500 text-4xl"></i>
|
|
</div>
|
|
<h4 class="text-xl font-semibold text-gray-800 mb-2">Rental Created Successfully!</h4>
|
|
<p class="text-gray-500 mb-6">Contract has been generated and is ready for download.</p>
|
|
<div class="flex justify-center gap-3">
|
|
<a id="downloadContract" href="#" target="_blank" class="flex items-center gap-2 bg-amber-500 text-white px-6 py-3 rounded-lg hover:bg-amber-600 font-medium">
|
|
<i class="fas fa-file-pdf"></i> Download Contract
|
|
</a>
|
|
<button onclick="closeQuickRentModal()" class="flex items-center gap-2 bg-gray-100 text-gray-700 px-6 py-3 rounded-lg hover:bg-gray-200 font-medium">
|
|
Done
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div id="quickRentFooter" style="padding: 16px 24px; background: #f9fafb; border-top: 1px solid #e5e7eb; display: flex; justify-content: space-between; align-items: center;">
|
|
<button id="prevBtn" onclick="prevStep()" style="padding: 8px 16px; border-radius: 8px; color: #6b7280; background: transparent; font-weight: 500; display: none;">Back</button>
|
|
<div style="flex: 1;"></div>
|
|
<button id="nextBtn" onclick="nextStep()" style="padding: 8px 24px; border-radius: 8px; background: #f59e0b; color: white; font-weight: 500;">Next</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.step-dot { width: 10px; height: 10px; border-radius: 50%; background: rgba(255,255,255,0.3); }
|
|
.step-dot.active { background: white; transform: scale(1.2); }
|
|
.step-line { width: 30px; height: 2px; background: rgba(255,255,255,0.3); }
|
|
</style>
|
|
|
|
<script>
|
|
let currentStep = 1;
|
|
const totalSteps = 4;
|
|
let selectedCustomerId = null;
|
|
let selectedCarId = null;
|
|
let newCustomerId = null;
|
|
let totalAmount = 0;
|
|
let finalRentalId = null;
|
|
|
|
// Pricing
|
|
const pricing = { daily: 1, weekly: 0.9, monthly: 0.8, lease: 0.7 };
|
|
|
|
let allCustomers = [];
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadCustomers();
|
|
loadCars();
|
|
});
|
|
|
|
function openQuickRentModal() {
|
|
document.getElementById('quickRentModal').style.display = 'flex';
|
|
currentStep = 1;
|
|
updateStepUI();
|
|
setTimeout(initSignaturePad, 100);
|
|
}
|
|
|
|
function closeQuickRentModal() {
|
|
document.getElementById('quickRentModal').style.display = 'none';
|
|
resetQuickRentForm();
|
|
}
|
|
|
|
function resetQuickRentForm() {
|
|
currentStep = 1;
|
|
selectedCustomerId = null;
|
|
selectedCarId = null;
|
|
selectedCarRate = 0;
|
|
newCustomerId = null;
|
|
totalAmount = 0;
|
|
document.querySelectorAll('#step1, #step2, #step3, #step4').forEach(s => s.classList.add('hidden'));
|
|
document.getElementById('stepSuccess').classList.add('hidden');
|
|
document.getElementById('quickRentFooter').classList.remove('hidden');
|
|
document.querySelector('input[name="customerType"][value="existing"]').checked = true;
|
|
toggleCustomerForm();
|
|
updateStepUI();
|
|
// Reset customer search
|
|
document.getElementById('customerSearch').value = '';
|
|
document.getElementById('selectedCustomerId').value = '';
|
|
document.getElementById('selectedCustomerInfo').style.display = 'none';
|
|
// Reset car search
|
|
document.getElementById('carSearch').value = '';
|
|
document.getElementById('selectedCarId').value = '';
|
|
document.getElementById('selectedCarInfo').style.display = 'none';
|
|
document.getElementById('selectedCarDetails').style.display = 'none';
|
|
// Clear signature
|
|
clearSignature();
|
|
}
|
|
|
|
function toggleCustomerForm() {
|
|
const isNew = document.querySelector('input[name="customerType"]:checked').value === 'new';
|
|
document.getElementById('newCustomerForm').classList.toggle('hidden', !isNew);
|
|
document.getElementById('customerSearch').parentElement.classList.toggle('hidden', isNew);
|
|
}
|
|
|
|
// Searchable Customer Dropdown Functions
|
|
async function loadCustomers() {
|
|
const response = await fetch('/quick-rental/customers');
|
|
allCustomers = await response.json();
|
|
renderCustomerList(allCustomers);
|
|
}
|
|
|
|
function renderCustomerList(customers) {
|
|
const list = document.getElementById('customerList');
|
|
if (customers.length === 0) {
|
|
list.innerHTML = '<div class="px-4 py-3 text-gray-500 text-sm">No customers found</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
customers.forEach(c => {
|
|
html += `<div onclick="selectCustomer(${c.id}, '${c.name}', '${c.phone}', '${c.email}')"
|
|
class="px-4 py-3 hover:bg-amber-50 cursor-pointer border-b border-gray-100 last:border-0">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="font-medium text-gray-800">${c.name}</p>
|
|
<p class="text-sm text-gray-500">${c.phone} • ${c.email}</p>
|
|
</div>
|
|
<span class="text-xs bg-green-100 text-green-700 px-2 py-1 rounded-full">Active</span>
|
|
</div>
|
|
</div>`;
|
|
});
|
|
list.innerHTML = html;
|
|
}
|
|
|
|
function filterCustomers() {
|
|
const search = document.getElementById('customerSearch').value.toLowerCase();
|
|
const filtered = allCustomers.filter(c =>
|
|
c.name.toLowerCase().includes(search) ||
|
|
c.phone.toLowerCase().includes(search) ||
|
|
c.email.toLowerCase().includes(search)
|
|
);
|
|
renderCustomerList(filtered);
|
|
}
|
|
|
|
function showCustomerDropdown() {
|
|
// Move dropdown outside modal to avoid overflow clipping
|
|
const dropdown = document.getElementById('customerDropdown');
|
|
dropdown.style.display = 'block';
|
|
dropdown.style.position = 'fixed';
|
|
const input = document.getElementById('customerSearch');
|
|
const rect = input.getBoundingClientRect();
|
|
dropdown.style.left = rect.left + 'px';
|
|
dropdown.style.top = (rect.bottom + 5) + 'px';
|
|
dropdown.style.width = rect.width + 'px';
|
|
dropdown.style.zIndex = '999999';
|
|
filterCustomers();
|
|
}
|
|
|
|
function hideCustomerDropdown() {
|
|
document.getElementById('customerDropdown').style.display = 'none';
|
|
}
|
|
|
|
function selectCustomer(id, name, phone, email) {
|
|
selectedCustomerId = id;
|
|
document.getElementById('selectedCustomerId').value = id;
|
|
document.getElementById('customerSearch').value = `${name} - ${phone}`;
|
|
document.getElementById('selectedCustomerName').textContent = name;
|
|
document.getElementById('selectedCustomerInfo').style.display = 'block';
|
|
hideCustomerDropdown();
|
|
}
|
|
|
|
let allCars = [];
|
|
|
|
async function loadCars() {
|
|
const response = await fetch('/quick-rental/cars');
|
|
allCars = await response.json();
|
|
renderCarList(allCars);
|
|
}
|
|
|
|
function renderCarList(cars) {
|
|
const list = document.getElementById('carList');
|
|
if (cars.length === 0) {
|
|
list.innerHTML = '<div class="px-4 py-3 text-gray-500 text-sm">No cars found</div>';
|
|
return;
|
|
}
|
|
|
|
let html = '';
|
|
cars.forEach(c => {
|
|
html += `<div onclick="selectCar(${c.id}, '${c.brand}', '${c.model}', '${c.license_plate}', ${c.daily_rate}, '${c.year}', '${c.color}')"
|
|
class="px-4 py-3 hover:bg-amber-50 cursor-pointer border-b border-gray-100 last:border-0">
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<p class="font-medium text-gray-800">${c.brand} ${c.model}</p>
|
|
<p class="text-sm text-gray-500">${c.year} • ${c.color}</p>
|
|
</div>
|
|
<div class="text-right">
|
|
<p class="font-semibold text-amber-600">${c.daily_rate} BHD/day</p>
|
|
<p class="text-xs text-gray-500">${c.license_plate}</p>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
});
|
|
list.innerHTML = html;
|
|
}
|
|
|
|
function filterCars() {
|
|
const search = document.getElementById('carSearch').value.toLowerCase();
|
|
const filtered = allCars.filter(c =>
|
|
c.brand.toLowerCase().includes(search) ||
|
|
c.model.toLowerCase().includes(search) ||
|
|
c.license_plate.toLowerCase().includes(search)
|
|
);
|
|
renderCarList(filtered);
|
|
}
|
|
|
|
function showCarDropdown() {
|
|
// Move dropdown outside modal to avoid overflow clipping
|
|
const dropdown = document.getElementById('carDropdown');
|
|
dropdown.style.display = 'block';
|
|
dropdown.style.position = 'fixed';
|
|
const input = document.getElementById('carSearch');
|
|
const rect = input.getBoundingClientRect();
|
|
dropdown.style.left = rect.left + 'px';
|
|
dropdown.style.top = (rect.bottom + 5) + 'px';
|
|
dropdown.style.width = rect.width + 'px';
|
|
dropdown.style.zIndex = '999999';
|
|
filterCars();
|
|
}
|
|
|
|
function hideCarDropdown() {
|
|
document.getElementById('carDropdown').style.display = 'none';
|
|
}
|
|
|
|
function selectCar(id, brand, model, plate, rate, year, color) {
|
|
selectedCarId = id;
|
|
document.getElementById('selectedCarId').value = id;
|
|
document.getElementById('carSearch').value = `${brand} ${model} - ${plate}`;
|
|
document.getElementById('selectedCarName').textContent = `${brand} ${model}`;
|
|
|
|
// Show selected info
|
|
document.getElementById('selectedCarInfo').style.display = 'block';
|
|
document.getElementById('selectedCarDetails').style.display = 'block';
|
|
document.getElementById('carDisplayName').textContent = `${brand} ${model}`;
|
|
document.getElementById('carDisplayDetails').textContent = `${year} • ${color} • ${plate}`;
|
|
document.getElementById('carDisplayRate').textContent = `${rate} BHD/day`;
|
|
|
|
// Store rate for calculation
|
|
selectedCarRate = rate;
|
|
hideCarDropdown();
|
|
calculateRentalPrice();
|
|
}
|
|
|
|
let selectedCarRate = 0;
|
|
|
|
// Signature Pad - Initialize after DOM loads
|
|
let signatureCanvas, signatureCtx, isDrawing;
|
|
|
|
function initSignaturePad() {
|
|
signatureCanvas = document.getElementById('signaturePad');
|
|
if (!signatureCanvas) return;
|
|
signatureCtx = signatureCanvas.getContext('2d');
|
|
isDrawing = false;
|
|
|
|
signatureCanvas.addEventListener('mousedown', function(e) {
|
|
isDrawing = true;
|
|
signatureCtx.beginPath();
|
|
const rect = signatureCanvas.getBoundingClientRect();
|
|
const scaleX = signatureCanvas.width / rect.width;
|
|
const scaleY = signatureCanvas.height / rect.height;
|
|
signatureCtx.moveTo((e.clientX - rect.left) * scaleX, (e.clientY - rect.top) * scaleY);
|
|
signatureCtx.strokeStyle = '#1f2937';
|
|
signatureCtx.lineWidth = 2;
|
|
signatureCtx.lineCap = 'round';
|
|
});
|
|
|
|
signatureCanvas.addEventListener('mousemove', function(e) {
|
|
if (!isDrawing) return;
|
|
const rect = signatureCanvas.getBoundingClientRect();
|
|
const scaleX = signatureCanvas.width / rect.width;
|
|
const scaleY = signatureCanvas.height / rect.height;
|
|
signatureCtx.lineTo((e.clientX - rect.left) * scaleX, (e.clientY - rect.top) * scaleY);
|
|
signatureCtx.stroke();
|
|
});
|
|
|
|
signatureCanvas.addEventListener('mouseup', function() { isDrawing = false; });
|
|
signatureCanvas.addEventListener('mouseout', function() { isDrawing = false; });
|
|
}
|
|
|
|
function clearSignature() {
|
|
if (!signatureCanvas) return;
|
|
signatureCtx.clearRect(0, 0, signatureCanvas.width, signatureCanvas.height);
|
|
}
|
|
|
|
function calculateRentalPrice() {
|
|
const dailyRate = selectedCarRate || 0;
|
|
|
|
const rentalType = document.getElementById('rentalType');
|
|
const typeOption = rentalType.options[rentalType.selectedIndex];
|
|
const days = parseInt(typeOption.dataset.days) || 1;
|
|
|
|
const startDate = document.getElementById('rentalStartDate').value;
|
|
const endDate = document.getElementById('rentalEndDate').value;
|
|
|
|
let total = 0;
|
|
let displayDays = days;
|
|
|
|
if (startDate && endDate) {
|
|
const start = new Date(startDate);
|
|
const end = new Date(endDate);
|
|
const diffTime = Math.abs(end - start);
|
|
displayDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1;
|
|
}
|
|
|
|
const multiplier = pricing[rentalType.value];
|
|
total = dailyRate * displayDays * multiplier;
|
|
totalAmount = total;
|
|
|
|
document.getElementById('rentalAmount').textContent = (dailyRate * displayDays).toFixed(3) + ' BHD';
|
|
document.getElementById('rentalDiscount').textContent = Math.round((1 - multiplier) * 100) + '%';
|
|
document.getElementById('rentalAdvance').textContent = (total * 0.5).toFixed(3) + ' BHD';
|
|
document.getElementById('rentalTotal').textContent = total.toFixed(3) + ' BHD';
|
|
document.getElementById('advancePayment').value = (total * 0.5).toFixed(2);
|
|
}
|
|
|
|
function updateStepUI() {
|
|
// Update step indicators
|
|
for (let i = 1; i <= totalSteps; i++) {
|
|
const dot = document.getElementById('stepIndicator' + i);
|
|
if (i <= currentStep) dot.classList.add('active');
|
|
else dot.classList.remove('active');
|
|
}
|
|
|
|
// Show/hide steps
|
|
for (let i = 1; i <= totalSteps; i++) {
|
|
document.getElementById('step' + i).classList.toggle('hidden', i !== currentStep);
|
|
}
|
|
|
|
// Buttons
|
|
document.getElementById('prevBtn').classList.toggle('hidden', currentStep === 1);
|
|
document.getElementById('nextBtn').textContent = currentStep === totalSteps ? 'Create Rental' : 'Next';
|
|
|
|
// Hide footer on success
|
|
if (document.getElementById('stepSuccess').classList.contains('hidden') === false) {
|
|
document.getElementById('quickRentFooter').classList.add('hidden');
|
|
}
|
|
}
|
|
|
|
async function nextStep() {
|
|
console.log('Step:', currentStep);
|
|
|
|
if (currentStep === 1) {
|
|
// Validate customer
|
|
const isNew = document.querySelector('input[name="customerType"]:checked').value === 'new';
|
|
console.log('Customer type:', isNew);
|
|
|
|
if (isNew) {
|
|
// Create new customer
|
|
const customerData = {
|
|
name: document.getElementById('newCustomerName').value,
|
|
email: document.getElementById('newCustomerEmail').value,
|
|
phone: document.getElementById('newCustomerPhone').value,
|
|
license_number: document.getElementById('newCustomerLicense').value,
|
|
license_expiry: document.getElementById('newCustomerLicenseExpiry').value,
|
|
dob: document.getElementById('newCustomerDob').value,
|
|
address: document.getElementById('newCustomerAddress').value,
|
|
status: 'active'
|
|
};
|
|
|
|
if (!customerData.name || !customerData.phone || !customerData.email) {
|
|
alert('Please fill in all customer fields');
|
|
return;
|
|
}
|
|
|
|
const response = await fetch('/customers', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
|
body: JSON.stringify(customerData)
|
|
});
|
|
const result = await response.json();
|
|
newCustomerId = result.id;
|
|
selectedCustomerId = result.id;
|
|
} else {
|
|
selectedCustomerId = document.getElementById('selectedCustomerId').value;
|
|
}
|
|
|
|
console.log('Selected customer:', selectedCustomerId, 'New:', newCustomerId);
|
|
|
|
if (!selectedCustomerId && !newCustomerId) {
|
|
alert('Please select or create a customer');
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (currentStep === 2) {
|
|
selectedCarId = document.getElementById('selectedCarId').value;
|
|
console.log('Selected car:', selectedCarId);
|
|
if (!selectedCarId) {
|
|
alert('Please select a car');
|
|
return;
|
|
}
|
|
// Show car info - already shown in selectCar function
|
|
}
|
|
|
|
if (currentStep === 3) {
|
|
console.log('Checking dates');
|
|
if (!document.getElementById('rentalStartDate').value || !document.getElementById('rentalEndDate').value) {
|
|
alert('Please select rental dates');
|
|
return;
|
|
}
|
|
// Update summary
|
|
document.getElementById('summaryCustomer').textContent = document.querySelector('input[name="customerType"]:checked').value === 'new'
|
|
? document.getElementById('newCustomerName').value
|
|
: document.getElementById('customerSearch').value;
|
|
document.getElementById('summaryCar').textContent = document.getElementById('carSearch').value;
|
|
document.getElementById('summaryPeriod').textContent = document.getElementById('rentalStartDate').value + ' to ' + document.getElementById('rentalEndDate').value;
|
|
document.getElementById('summaryType').textContent = document.getElementById('rentalType').value;
|
|
}
|
|
|
|
if (currentStep === totalSteps) {
|
|
await createRental();
|
|
return;
|
|
}
|
|
|
|
currentStep++;
|
|
updateStepUI();
|
|
}
|
|
|
|
function prevStep() {
|
|
if (currentStep > 1) {
|
|
currentStep--;
|
|
updateStepUI();
|
|
}
|
|
}
|
|
|
|
async function createRental() {
|
|
// Get signature data
|
|
const signatureData = signatureCanvas ? signatureCanvas.toDataURL('image/png') : '';
|
|
|
|
const data = {
|
|
customer_id: selectedCustomerId || newCustomerId,
|
|
car_id: selectedCarId,
|
|
rental_type: document.getElementById('rentalType').value,
|
|
start_date: document.getElementById('rentalStartDate').value,
|
|
pickup_time: document.getElementById('rentalPickupTime').value,
|
|
end_date: document.getElementById('rentalEndDate').value,
|
|
return_time: document.getElementById('rentalReturnTime').value,
|
|
total_amount: totalAmount,
|
|
advance_payment: parseFloat(document.getElementById('advancePayment').value) || 0,
|
|
payment_method: document.getElementById('paymentMethod').value,
|
|
notes: document.getElementById('rentalNotes').value,
|
|
customer_signature: signatureData
|
|
};
|
|
|
|
const response = await fetch('/quick-rental/store', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
const result = await response.json();
|
|
finalRentalId = result.rental_id;
|
|
|
|
// Show success
|
|
document.getElementById('step4').classList.add('hidden');
|
|
document.getElementById('stepSuccess').classList.remove('hidden');
|
|
document.getElementById('quickRentFooter').classList.add('hidden');
|
|
document.getElementById('downloadContract').href = '/quick-rental/contract/' + finalRentalId;
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|