mirror of
https://github.com/Dvorinka/PPve.git
synced 2026-06-03 20:12:59 +00:00
test
This commit is contained in:
+210
-94
@@ -582,6 +582,40 @@
|
|||||||
color: #9a3412;
|
color: #9a3412;
|
||||||
border: 1px solid #fed7aa;
|
border: 1px solid #fed7aa;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Vehicle Legend and Filters */
|
||||||
|
#vehicleFilters {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-filter-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
opacity: 0.7;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-filter-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-filter-btn.active {
|
||||||
|
opacity: 1;
|
||||||
|
border-color: #004990;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reservation-item.hidden-vehicle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fc-event.hidden-vehicle {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-brand-gray min-h-screen">
|
<body class="bg-brand-gray min-h-screen">
|
||||||
@@ -635,33 +669,40 @@
|
|||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="container mx-auto px-4 py-8">
|
<div class="container mx-auto px-4 py-8">
|
||||||
<div class="calendar-container">
|
<div class="calendar-container">
|
||||||
<!-- Vehicle Legend --> <div class="bg-white p-4 mb-4 rounded-lg shadow-sm">
|
<!-- Vehicle Legend and Filters -->
|
||||||
|
<div class="bg-white p-4 mb-4 rounded-lg shadow-sm">
|
||||||
<h3 class="text-lg font-semibold mb-3">Přehled vozidel</h3>
|
<h3 class="text-lg font-semibold mb-3">Přehled vozidel</h3>
|
||||||
<div class="flex flex-wrap gap-3">
|
<div class="flex flex-wrap gap-3" id="vehicleFilters">
|
||||||
<div class="flex items-center">
|
<button class="vehicle-filter-btn active" data-vehicle="all">
|
||||||
<div class="w-4 h-4 rounded-full bg-blue-500 mr-2"></div>
|
<span class="vehicle-badge bg-gray-100 text-gray-700">
|
||||||
<span>VW Caddy - 4Z1 8241</span>
|
<i class="fas fa-car"></i>Všechna vozidla
|
||||||
</div>
|
</span>
|
||||||
<div class="flex items-center">
|
</button>
|
||||||
<div class="w-4 h-4 rounded-full bg-green-500 mr-2"></div>
|
<button class="vehicle-filter-btn" data-vehicle="VW Caddy - 4Z1 8241">
|
||||||
<span>VW Golf - 5Z5 8694</span>
|
<span class="vehicle-badge vehicle-vw-caddy">
|
||||||
</div>
|
<i class="fas fa-truck"></i>VW Caddy - 4Z1 8241
|
||||||
<div class="flex items-center">
|
</span>
|
||||||
<div class="w-4 h-4 rounded-full bg-orange-500 mr-2"></div>
|
</button>
|
||||||
<span>Škoda Fabia - 1Z3 5789</span>
|
<button class="vehicle-filter-btn" data-vehicle="VW Golf - 5Z5 8694">
|
||||||
</div>
|
<span class="vehicle-badge vehicle-vw-golf">
|
||||||
<div class="flex items-center">
|
<i class="fas fa-car"></i>VW Golf - 5Z5 8694
|
||||||
<div class="w-4 h-4 rounded-full bg-purple-500 mr-2"></div>
|
</span>
|
||||||
<span>BMW 218d - 6Z5 4739</span>
|
</button>
|
||||||
</div>
|
<button class="vehicle-filter-btn" data-vehicle="Škoda Fabia - 1Z3 5789">
|
||||||
<div class="flex items-center">
|
<span class="vehicle-badge vehicle-skoda-fabia">
|
||||||
<div class="w-4 h-4 rounded-full bg-indigo-500 mr-2"></div>
|
<i class="fas fa-car"></i>Škoda Fabia - 1Z3 5789
|
||||||
<span>BMW 218d - 6Z5 4740</span>
|
</span>
|
||||||
</div>
|
</button>
|
||||||
<div class="flex items-center">
|
<button class="vehicle-filter-btn" data-vehicle="BMW 218d - 6Z5 4739">
|
||||||
<div class="w-4 h-4 rounded-full bg-red-500 mr-2"></div>
|
<span class="vehicle-badge vehicle-bmw-218d">
|
||||||
<span>Škoda Superb - 2BY 2398</span>
|
<i class="fas fa-car-side"></i>BMW 218d - 6Z5 4739
|
||||||
</div>
|
</span>
|
||||||
|
</button>
|
||||||
|
<button class="vehicle-filter-btn" data-vehicle="Škoda Superb - 2BY 2398">
|
||||||
|
<span class="vehicle-badge vehicle-skoda-superb">
|
||||||
|
<i class="fas fa-car-side"></i>Škoda Superb - 2BY 2398
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -864,30 +905,54 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
let calendar;
|
// Helper functions for date/time formatting
|
||||||
const reservationForm = document.getElementById('reservationForm');
|
function formatDateForAPI(date) {
|
||||||
const statusMessage = document.getElementById('statusMessage');
|
return date.toISOString().split('T')[0];
|
||||||
const modal = document.getElementById('eventModal');
|
}
|
||||||
const highTrafficWarning = document.getElementById('highTrafficWarning');
|
|
||||||
const availabilityStatus = document.getElementById('availabilityStatus');
|
function formatTimeForAPI(date) {
|
||||||
let currentEventId = null;
|
return date.getHours().toString().padStart(2, '0') + ':' +
|
||||||
|
date.getMinutes().toString().padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse date and time inputs into a Date object
|
||||||
|
function parseDateTimeInputs(dateInput, timeInput) {
|
||||||
|
const [hours, minutes] = timeInput.value.split(':');
|
||||||
|
const date = new Date(dateInput.value);
|
||||||
|
date.setHours(parseInt(hours, 10), parseInt(minutes, 10), 0, 0);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
// Function to check availability and traffic
|
|
||||||
async function checkAvailabilityAndTraffic() {
|
async function checkAvailabilityAndTraffic() {
|
||||||
const vehicle = document.getElementById('vehicle').value;
|
const vehicle = document.getElementById('vehicle').value;
|
||||||
const startDate = document.getElementById('startDate').value;
|
const startDateInput = document.getElementById('startDate');
|
||||||
const startTime = document.getElementById('startTime').value;
|
const startTimeInput = document.getElementById('startTime');
|
||||||
const endDate = document.getElementById('endDate').value;
|
const endDateInput = document.getElementById('endDate');
|
||||||
const endTime = document.getElementById('endTime').value;
|
const endTimeInput = document.getElementById('endTime');
|
||||||
|
|
||||||
if (!vehicle || !startDate || !startTime || !endDate || !endTime) {
|
if (!vehicle || !startDateInput.value || !startTimeInput.value ||
|
||||||
|
!endDateInput.value || !endTimeInput.value) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/check-availability?vehicle=${encodeURIComponent(vehicle)}&startDate=${startDate}&startTime=${startTime}&endDate=${endDate}&endTime=${endTime}`);
|
const startDateTime = parseDateTimeInputs(startDateInput, startTimeInput);
|
||||||
const data = await response.json();
|
const endDateTime = parseDateTimeInputs(endDateInput, endTimeInput);
|
||||||
|
|
||||||
|
const response = await fetch(`/api/check-availability?` +
|
||||||
|
`vehicle=${encodeURIComponent(vehicle)}&` +
|
||||||
|
`startDate=${formatDateForAPI(startDateTime)}&` +
|
||||||
|
`startTime=${formatTimeForAPI(startDateTime)}&` +
|
||||||
|
`endDate=${formatDateForAPI(endDateTime)}&` +
|
||||||
|
`endTime=${formatTimeForAPI(endDateTime)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
// Update availability status
|
// Update availability status
|
||||||
availabilityStatus.classList.remove('hidden', 'bg-green-50', 'text-green-700', 'bg-red-50', 'text-red-700');
|
availabilityStatus.classList.remove('hidden', 'bg-green-50', 'text-green-700', 'bg-red-50', 'text-red-700');
|
||||||
|
|
||||||
@@ -896,19 +961,10 @@
|
|||||||
availabilityStatus.innerHTML = '<i class="fas fa-check-circle mr-2"></i>Vozidlo je v tomto čase k dispozici';
|
availabilityStatus.innerHTML = '<i class="fas fa-check-circle mr-2"></i>Vozidlo je v tomto čase k dispozici';
|
||||||
} else {
|
} else {
|
||||||
availabilityStatus.classList.add('bg-red-50', 'text-red-700');
|
availabilityStatus.classList.add('bg-red-50', 'text-red-700');
|
||||||
availabilityStatus.innerHTML = '<i class="fas fa-times-circle mr-2"></i>Vozidlo je v tomto čase již rezervované';
|
availabilityStatus.innerHTML = '<i class="fas fa-times-circle mr-2"></i>Vozidlo není v tomto čase k dispozici';
|
||||||
}
|
}
|
||||||
availabilityStatus.classList.remove('hidden');
|
availabilityStatus.classList.remove('hidden');
|
||||||
|
|
||||||
// Check for high traffic
|
|
||||||
if (data.reservationCount >= 3) {
|
|
||||||
highTrafficWarning.querySelector('.warning-message').textContent =
|
|
||||||
`Toto vozidlo má v daný den již ${data.reservationCount} rezervací.`;
|
|
||||||
highTrafficWarning.classList.remove('hidden');
|
|
||||||
} else {
|
|
||||||
highTrafficWarning.classList.add('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable submit button if not available
|
// Disable submit button if not available
|
||||||
const submitButton = reservationForm.querySelector('button[type="submit"]');
|
const submitButton = reservationForm.querySelector('button[type="submit"]');
|
||||||
submitButton.disabled = !data.available;
|
submitButton.disabled = !data.available;
|
||||||
@@ -918,6 +974,7 @@
|
|||||||
return data.available;
|
return data.available;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error checking availability:', error);
|
console.error('Error checking availability:', error);
|
||||||
|
availabilityStatus.classList.add('hidden');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -925,21 +982,20 @@
|
|||||||
// Add event listeners for form inputs
|
// Add event listeners for form inputs
|
||||||
const formInputs = ['vehicle', 'startDate', 'startTime', 'endDate', 'endTime'];
|
const formInputs = ['vehicle', 'startDate', 'startTime', 'endDate', 'endTime'];
|
||||||
formInputs.forEach(inputId => {
|
formInputs.forEach(inputId => {
|
||||||
document.getElementById(inputId).addEventListener('change', checkAvailabilityAndTraffic);
|
document.getElementById(inputId)?.addEventListener('change', checkAvailabilityAndTraffic);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle form submission
|
// Handle form submission
|
||||||
reservationForm.addEventListener('submit', async function(e) {
|
reservationForm.addEventListener('submit', async function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const startDate = document.getElementById('startDate').value;
|
const startDateInput = document.getElementById('startDate');
|
||||||
const startTime = document.getElementById('startTime').value;
|
const startTimeInput = document.getElementById('startTime');
|
||||||
const endDate = document.getElementById('endDate').value;
|
const endDateInput = document.getElementById('endDate');
|
||||||
const endTime = document.getElementById('endTime').value;
|
const endTimeInput = document.getElementById('endTime');
|
||||||
|
|
||||||
// Create ISO datetime strings
|
const startDateTime = parseDateTimeInputs(startDateInput, startTimeInput);
|
||||||
const startDateTime = new Date(`${startDate}T${startTime}`);
|
const endDateTime = parseDateTimeInputs(endDateInput, endTimeInput);
|
||||||
const endDateTime = new Date(`${endDate}T${endTime}`);
|
|
||||||
|
|
||||||
// Validate dates
|
// Validate dates
|
||||||
if (endDateTime <= startDateTime) {
|
if (endDateTime <= startDateTime) {
|
||||||
@@ -948,11 +1004,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check availability one last time
|
// Check availability one last time
|
||||||
const isAvailable = await checkVehicleAvailability(
|
const isAvailable = await checkAvailabilityAndTraffic();
|
||||||
document.getElementById('vehicle').value,
|
|
||||||
startDateTime,
|
|
||||||
endDateTime
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!isAvailable) {
|
if (!isAvailable) {
|
||||||
showMessage('Vozidlo již není k dispozici v tomto čase', 'error');
|
showMessage('Vozidlo již není k dispozici v tomto čase', 'error');
|
||||||
@@ -963,10 +1015,10 @@
|
|||||||
const reservationData = {
|
const reservationData = {
|
||||||
driverName: document.getElementById('driverName').value,
|
driverName: document.getElementById('driverName').value,
|
||||||
vehicle: document.getElementById('vehicle').value,
|
vehicle: document.getElementById('vehicle').value,
|
||||||
startDate,
|
startDate: formatDateForAPI(startDateTime),
|
||||||
startTime,
|
startTime: formatTimeForAPI(startDateTime),
|
||||||
endDate,
|
endDate: formatDateForAPI(endDateTime),
|
||||||
endTime,
|
endTime: formatTimeForAPI(endDateTime),
|
||||||
purpose: document.getElementById('purpose')?.value || ''
|
purpose: document.getElementById('purpose')?.value || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -988,29 +1040,24 @@
|
|||||||
// Add the new event to the calendar
|
// Add the new event to the calendar
|
||||||
calendar.addEvent({
|
calendar.addEvent({
|
||||||
id: result.id,
|
id: result.id,
|
||||||
title: reservationData.driverName,
|
title: `${result.vehicle} - ${result.driverName}`,
|
||||||
start: startDateTime,
|
start: `${result.startDate}T${result.startTime}`,
|
||||||
end: endDateTime,
|
end: `${result.endDate}T${result.endTime}`,
|
||||||
extendedProps: {
|
extendedProps: {
|
||||||
driverName: reservationData.driverName,
|
driverName: result.driverName,
|
||||||
vehicle: reservationData.vehicle,
|
vehicle: result.vehicle,
|
||||||
purpose: reservationData.purpose
|
purpose: result.purpose
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset form and close modal
|
// Reset form and close modal
|
||||||
reservationForm.reset();
|
reservationForm.reset();
|
||||||
document.getElementById('reservationModal').style.display = 'none';
|
document.getElementById('reservationModal').style.display = 'none';
|
||||||
|
|
||||||
// Show success message
|
|
||||||
showMessage('Rezervace byla úspěšně vytvořena', 'success');
|
showMessage('Rezervace byla úspěšně vytvořena', 'success');
|
||||||
|
|
||||||
// Update the reservations list
|
|
||||||
updateReservationsList();
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating reservation:', error);
|
console.error('Error:', error);
|
||||||
showMessage('Nepodařilo se vytvořit rezervaci. Zkuste to prosím znovu.', 'error');
|
showMessage('Nepodařilo se vytvořit rezervaci', 'error');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1040,23 +1087,60 @@
|
|||||||
}
|
}
|
||||||
// Initialize calendar
|
// Initialize calendar
|
||||||
const calendarEl = document.getElementById('calendar');
|
const calendarEl = document.getElementById('calendar');
|
||||||
function createEventContent(arg) {
|
let selectedVehicle = 'all';
|
||||||
return {
|
|
||||||
html: `
|
// Filter events function
|
||||||
<div class="fc-event-main">
|
function filterEvents() {
|
||||||
<div class="fc-event-time">${arg.timeText}</div>
|
const events = calendar.getEvents();
|
||||||
<div class="fc-event-title">${arg.event.extendedProps.driverName}</div>
|
events.forEach(event => {
|
||||||
<div class="fc-event-desc text-sm">${arg.event.extendedProps.vehicle}</div>
|
const eventEl = event.el;
|
||||||
</div>
|
if (eventEl) {
|
||||||
`
|
if (selectedVehicle === 'all' || event.extendedProps.vehicle === selectedVehicle) {
|
||||||
};
|
eventEl.classList.remove('hidden-vehicle');
|
||||||
|
} else {
|
||||||
|
eventEl.classList.add('hidden-vehicle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter reservation list
|
||||||
|
const reservationItems = document.querySelectorAll('#reservationsList .reservation-item');
|
||||||
|
reservationItems.forEach(item => {
|
||||||
|
const vehicleBadge = item.querySelector('.reservation-vehicle-badge');
|
||||||
|
if (vehicleBadge) {
|
||||||
|
const vehicle = vehicleBadge.textContent.trim();
|
||||||
|
if (selectedVehicle === 'all' || vehicle === selectedVehicle) {
|
||||||
|
item.classList.remove('hidden-vehicle');
|
||||||
|
} else {
|
||||||
|
item.classList.add('hidden-vehicle');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update active filter button
|
||||||
|
document.querySelectorAll('.vehicle-filter-btn').forEach(btn => {
|
||||||
|
btn.classList.toggle('active', btn.dataset.vehicle === selectedVehicle);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add click handlers for filter buttons
|
||||||
|
document.querySelectorAll('.vehicle-filter-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => {
|
||||||
|
selectedVehicle = btn.dataset.vehicle;
|
||||||
|
filterEvents();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// Calendar configuration
|
// Calendar configuration
|
||||||
const calendarConfig = {
|
const calendarConfig = {
|
||||||
eventDidMount: function(info) {
|
eventDidMount: function(info) {
|
||||||
// Add vehicle-specific class for styling
|
const vehicleClass = 'event-' + info.event.extendedProps.vehicle.toLowerCase().replace(/\s+/g, '-');
|
||||||
const vehicleClass = 'event-' + info.event.extendedProps.vehicle.toLowerCase().replace(/[^a-z0-9]+/g, '-');
|
|
||||||
info.el.classList.add(vehicleClass);
|
info.el.classList.add(vehicleClass);
|
||||||
|
|
||||||
|
// Apply initial filtering
|
||||||
|
if (selectedVehicle !== 'all' && info.event.extendedProps.vehicle !== selectedVehicle) {
|
||||||
|
info.el.classList.add('hidden-vehicle');
|
||||||
|
}
|
||||||
}, initialView: 'dayGridMonth',
|
}, initialView: 'dayGridMonth',
|
||||||
headerToolbar: {
|
headerToolbar: {
|
||||||
left: 'prev,next today',
|
left: 'prev,next today',
|
||||||
@@ -1218,8 +1302,9 @@
|
|||||||
|
|
||||||
const html = currentAndFutureEvents.map(event => {
|
const html = currentAndFutureEvents.map(event => {
|
||||||
const vehicleClass = 'vehicle-' + event.extendedProps.vehicle.toLowerCase().replace(/\s+/g, '-');
|
const vehicleClass = 'vehicle-' + event.extendedProps.vehicle.toLowerCase().replace(/\s+/g, '-');
|
||||||
|
const isHidden = selectedVehicle !== 'all' && event.extendedProps.vehicle !== selectedVehicle ? 'hidden-vehicle' : '';
|
||||||
return `
|
return `
|
||||||
<div class="reservation-item hover:bg-gray-50">
|
<div class="reservation-item hover:bg-gray-50 ${isHidden}">
|
||||||
<span class="reservation-vehicle-badge ${vehicleClass} text-sm">
|
<span class="reservation-vehicle-badge ${vehicleClass} text-sm">
|
||||||
${event.extendedProps.vehicle}
|
${event.extendedProps.vehicle}
|
||||||
</span>
|
</span>
|
||||||
@@ -1369,6 +1454,37 @@
|
|||||||
modal.style.display = 'none';
|
modal.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vehicle filter functionality
|
||||||
|
const vehicleFilterButtons = document.querySelectorAll('.vehicle-filter-btn');
|
||||||
|
vehicleFilterButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', function() {
|
||||||
|
const vehicle = this.getAttribute('data-vehicle');
|
||||||
|
|
||||||
|
// Update active button
|
||||||
|
vehicleFilterButtons.forEach(btn => btn.classList.remove('active'));
|
||||||
|
this.classList.add('active');
|
||||||
|
|
||||||
|
// Show/hide reservations based on filter
|
||||||
|
const reservations = document.querySelectorAll('.reservation-item');
|
||||||
|
reservations.forEach(item => {
|
||||||
|
if (vehicle === 'all' || item.querySelector('.reservation-vehicle-badge').textContent.trim() === vehicle) {
|
||||||
|
item.classList.remove('hidden-vehicle');
|
||||||
|
} else {
|
||||||
|
item.classList.add('hidden-vehicle');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show/hide calendar events based on filter
|
||||||
|
calendar.getEvents().forEach(event => {
|
||||||
|
if (vehicle === 'all' || event.extendedProps.vehicle === vehicle) {
|
||||||
|
event.setProp('classNames', []);
|
||||||
|
} else {
|
||||||
|
event.setProp('classNames', ['hidden-vehicle']);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user