mirror of
https://github.com/Dvorinka/PPve.git
synced 2026-06-04 04:22:58 +00:00
test
This commit is contained in:
+374
-30
@@ -211,6 +211,103 @@
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Enhanced calendar event styles */
|
||||
.fc-event {
|
||||
border: none !important;
|
||||
border-radius: 6px !important;
|
||||
padding: 4px 6px !important;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
|
||||
margin: 2px 0 !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
.fc-event-title {
|
||||
font-weight: 500 !important;
|
||||
font-size: 0.9rem !important;
|
||||
}
|
||||
|
||||
.fc-event-time {
|
||||
font-weight: 600 !important;
|
||||
opacity: 0.8 !important;
|
||||
}
|
||||
|
||||
/* Vehicle-specific event colors */
|
||||
.event-vw-caddy {
|
||||
border-left: 4px solid #1a73e8 !important;
|
||||
background: #e6f3ff !important;
|
||||
}
|
||||
|
||||
.event-vw-golf {
|
||||
border-left: 4px solid #28a745 !important;
|
||||
background: #e6ffe6 !important;
|
||||
}
|
||||
|
||||
.event-skoda-fabia {
|
||||
border-left: 4px solid #fd7e14 !important;
|
||||
background: #fff3e6 !important;
|
||||
}
|
||||
|
||||
.event-bmw-218d {
|
||||
border-left: 4px solid #6f42c1 !important;
|
||||
background: #f3e6ff !important;
|
||||
}
|
||||
|
||||
.event-skoda-superb {
|
||||
border-left: 4px solid #dc3545 !important;
|
||||
background: #ffe6e6 !important;
|
||||
}
|
||||
|
||||
/* Reservation list styles */
|
||||
.reservations-list {
|
||||
margin-top: 2rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.reservations-list-header {
|
||||
background: #f8fafc;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.reservations-list-body {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.reservation-item {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.reservation-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.reservation-vehicle-badge {
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* High traffic warning styles */
|
||||
.high-traffic-warning {
|
||||
display: none;
|
||||
background: #fff3e6;
|
||||
border-left: 4px solid #fd7e14;
|
||||
padding: 0.75rem 1rem;
|
||||
margin-top: 0.5rem;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
color: #c05621;
|
||||
}
|
||||
|
||||
/* Form container styles */
|
||||
.form-container {
|
||||
max-width: 600px;
|
||||
@@ -452,6 +549,44 @@
|
||||
.reservation-modal .close-button:hover {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Availability and warning styles */
|
||||
.form-group button[type="submit"]:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
#availabilityStatus, #highTrafficWarning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.875rem;
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: 0.375rem;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
#availabilityStatus i, #highTrafficWarning i {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
#availabilityStatus.bg-green-50 {
|
||||
background-color: #f0fdf4;
|
||||
color: #166534;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
#availabilityStatus.bg-red-50 {
|
||||
background-color: #fef2f2;
|
||||
color: #991b1b;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
#highTrafficWarning {
|
||||
background-color: #fff7ed;
|
||||
color: #9a3412;
|
||||
border: 1px solid #fed7aa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-brand-gray min-h-screen">
|
||||
@@ -517,15 +652,24 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id='calendar'></div>
|
||||
<div class="flex justify-center mt-6">
|
||||
<div id='calendar'></div> <div class="flex justify-center mt-6">
|
||||
<button id="newReservationBtn"
|
||||
class="bg-brand-blue hover:bg-brand-light-blue text-white font-bold py-3 px-6 rounded-lg shadow-lg transition-all duration-200 transform hover:scale-105 flex items-center gap-2">
|
||||
<i class="fas fa-plus-circle"></i>
|
||||
Vytvořit novou rezervaci
|
||||
</button>
|
||||
</div>
|
||||
</div> <!-- Reservation Form Modal -->
|
||||
|
||||
<!-- Reservations List -->
|
||||
<div class="reservations-list mt-8">
|
||||
<div class="reservations-list-header">
|
||||
<h3 class="text-lg font-semibold text-gray-800">Nadcházející rezervace</h3>
|
||||
</div>
|
||||
<div class="reservations-list-body" id="reservationsList">
|
||||
<!-- Reservations will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- Reservation Form Modal -->
|
||||
<div id="reservationModal" class="reservation-modal">
|
||||
<div class="form-container">
|
||||
<button type="button" class="close-button" id="closeReservationModal">×</button>
|
||||
@@ -538,7 +682,7 @@
|
||||
class="w-full p-2 border border-gray-300 rounded-md">
|
||||
</div>
|
||||
|
||||
<!-- Vehicle Selection -->
|
||||
<!-- Vehicle Selection with Warning -->
|
||||
<div class="form-group">
|
||||
<label for="vehicle">Vozidlo</label>
|
||||
<div class="select-wrapper">
|
||||
@@ -552,6 +696,13 @@
|
||||
<option value="Škoda Superb">Škoda Superb</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- High Traffic Warning -->
|
||||
<div id="highTrafficWarning" class="hidden mt-2 text-sm bg-amber-50 text-amber-700 p-2 rounded-md border border-amber-200">
|
||||
<i class="fas fa-exclamation-triangle mr-2"></i>
|
||||
<span class="warning-message"></span>
|
||||
</div>
|
||||
<!-- Availability Status -->
|
||||
<div id="availabilityStatus" class="hidden mt-2 text-sm rounded-md p-2"></div>
|
||||
</div>
|
||||
|
||||
<!-- Date and Time Selection -->
|
||||
@@ -695,16 +846,100 @@
|
||||
const reservationForm = document.getElementById('reservationForm');
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
const modal = document.getElementById('eventModal');
|
||||
const highTrafficWarning = document.getElementById('highTrafficWarning');
|
||||
const availabilityStatus = document.getElementById('availabilityStatus');
|
||||
let currentEventId = null;
|
||||
|
||||
// Initialize FullCalendar
|
||||
const calendarEl = document.getElementById('calendar'); calendar = new FullCalendar.Calendar(calendarEl, {
|
||||
// Function to check availability and traffic
|
||||
async function checkAvailabilityAndTraffic() {
|
||||
const vehicle = document.getElementById('vehicle').value;
|
||||
const startDate = document.getElementById('startDate').value;
|
||||
const startTime = document.getElementById('startTime').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
const endTime = document.getElementById('endTime').value;
|
||||
|
||||
if (!vehicle || !startDate || !startTime || !endDate || !endTime) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/check-availability?vehicle=${encodeURIComponent(vehicle)}&startDate=${startDate}&startTime=${startTime}&endDate=${endDate}&endTime=${endTime}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Update availability status
|
||||
availabilityStatus.classList.remove('hidden', 'bg-green-50', 'text-green-700', 'bg-red-50', 'text-red-700');
|
||||
|
||||
if (data.available) {
|
||||
availabilityStatus.classList.add('bg-green-50', 'text-green-700');
|
||||
availabilityStatus.innerHTML = '<i class="fas fa-check-circle mr-2"></i>Vozidlo je v tomto čase k dispozici';
|
||||
} else {
|
||||
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.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
|
||||
const submitButton = reservationForm.querySelector('button[type="submit"]');
|
||||
submitButton.disabled = !data.available;
|
||||
submitButton.classList.toggle('opacity-50', !data.available);
|
||||
submitButton.classList.toggle('cursor-not-allowed', !data.available);
|
||||
|
||||
return data.available;
|
||||
} catch (error) {
|
||||
console.error('Error checking availability:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add event listeners for form inputs
|
||||
const formInputs = ['vehicle', 'startDate', 'startTime', 'endDate', 'endTime'];
|
||||
formInputs.forEach(inputId => {
|
||||
document.getElementById(inputId).addEventListener('change', checkAvailabilityAndTraffic);
|
||||
});
|
||||
|
||||
// Modify form submission to check availability first
|
||||
reservationForm.addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const isAvailable = await checkAvailabilityAndTraffic();
|
||||
if (!isAvailable) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Rest of the form submission code
|
||||
// ...existing code...
|
||||
}); // Initialize calendar
|
||||
const calendarEl = document.getElementById('calendar');
|
||||
function createEventContent(arg) {
|
||||
return {
|
||||
html: `
|
||||
<div class="fc-event-main">
|
||||
<div class="fc-event-time">${arg.timeText}</div>
|
||||
<div class="fc-event-title">${arg.event.extendedProps.driverName}</div>
|
||||
<div class="fc-event-desc text-sm">${arg.event.extendedProps.vehicle}</div>
|
||||
</div>
|
||||
`
|
||||
};
|
||||
}
|
||||
const calendarConfig = {
|
||||
initialView: 'timeGridWeek',
|
||||
headerToolbar: {
|
||||
left: 'prev,next today',
|
||||
center: 'title',
|
||||
right: 'dayGridMonth,timeGridWeek,timeGridDay'
|
||||
},
|
||||
validRange: {
|
||||
start: new Date().toISOString().split('T')[0]
|
||||
},
|
||||
locale: 'cs',
|
||||
slotMinTime: '06:00:00',
|
||||
slotMaxTime: '22:00:00',
|
||||
@@ -716,41 +951,37 @@
|
||||
eventTimeFormat: {
|
||||
hour12: false
|
||||
},
|
||||
eventClassNames: function(arg) {
|
||||
return ['event-' + arg.event.extendedProps.vehicle.toLowerCase().replace(/\s+/g, '-')];
|
||||
},
|
||||
eventContent: {
|
||||
html: function(arg) {
|
||||
return `
|
||||
<div class="fc-event-main">
|
||||
<div class="fc-event-time">${arg.timeText}</div>
|
||||
<div class="fc-event-title">${arg.event.extendedProps.driverName}</div>
|
||||
<div class="fc-event-desc text-sm">${arg.event.extendedProps.vehicle}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
},
|
||||
dateClick: function(info) {
|
||||
// Store the timestamp of the last click
|
||||
const now = Date.now();
|
||||
if (this.lastClick && (now - this.lastClick < 300)) {
|
||||
// Double click detected
|
||||
showReservationForm(info.date);
|
||||
}
|
||||
this.lastClick = now;
|
||||
},
|
||||
eventContent: function(arg) {
|
||||
return {
|
||||
html: `
|
||||
<div class="p-1">
|
||||
<div class="font-semibold text-sm">${arg.event.extendedProps.vehicle}</div>
|
||||
<div class="text-xs">${arg.event.extendedProps.driverName}</div>
|
||||
${arg.event.extendedProps.purpose ? `<div class="text-xs text-gray-600">${arg.event.extendedProps.purpose}</div>` : ''}
|
||||
</div>
|
||||
`
|
||||
};
|
||||
},
|
||||
eventClick: function(info) {
|
||||
showEventModal(info.event);
|
||||
},
|
||||
eventConstraint: {
|
||||
dows: [0,1,2,3,4,5,6]
|
||||
},
|
||||
selectConstraint: {
|
||||
startTime: '06:00',
|
||||
endTime: '22:00',
|
||||
dows: [0,1,2,3,4,5,6]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
calendar = new FullCalendar.Calendar(calendarEl, calendarConfig);
|
||||
calendar.render();
|
||||
|
||||
// Rest of the JavaScript code
|
||||
// ...existing code...
|
||||
// Reservation modal functions
|
||||
const reservationModal = document.getElementById('reservationModal');
|
||||
const closeReservationModal = document.getElementById('closeReservationModal'); function showReservationForm(date) {
|
||||
@@ -789,6 +1020,111 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Function to check vehicle availability and high traffic
|
||||
async function checkVehicleAvailability(vehicle, startDate, endDate) {
|
||||
try {
|
||||
const response = await fetch(`/api/check-availability?vehicle=${encodeURIComponent(vehicle)}&startDate=${startDate}&startTime=${startTime}&endDate=${endDate}&endTime=${endTime}`);
|
||||
const data = await response.json();
|
||||
|
||||
// Check for high traffic (more than 3 reservations for the same vehicle on the same day)
|
||||
if (data.reservationCount > 3) {
|
||||
showHighTrafficWarning(vehicle);
|
||||
} else {
|
||||
hideHighTrafficWarning();
|
||||
}
|
||||
|
||||
return data.available;
|
||||
} catch (error) {
|
||||
console.error('Error checking availability:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to show high traffic warning
|
||||
function showHighTrafficWarning(vehicle) {
|
||||
const warningEl = document.querySelector('.high-traffic-warning');
|
||||
if (!warningEl) {
|
||||
const warning = document.createElement('div');
|
||||
warning.className = 'high-traffic-warning';
|
||||
warning.innerHTML = `
|
||||
<div class="flex items-center gap-2">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<span>Upozornění: Vozidlo ${vehicle} má v tento den vysoký počet rezervací.</span>
|
||||
</div>
|
||||
`;
|
||||
document.getElementById('vehicle').parentNode.appendChild(warning);
|
||||
}
|
||||
warningEl.style.display = 'block';
|
||||
}
|
||||
|
||||
// Function to hide high traffic warning
|
||||
function hideHighTrafficWarning() {
|
||||
const warningEl = document.querySelector('.high-traffic-warning');
|
||||
if (warningEl) {
|
||||
warningEl.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// Function to update reservations list
|
||||
function updateReservationsList() {
|
||||
const reservationsList = document.getElementById('reservationsList');
|
||||
const events = calendar.getEvents();
|
||||
|
||||
// Sort events by start date
|
||||
events.sort((a, b) => a.start - b.start);
|
||||
|
||||
// Filter future events
|
||||
const futureEvents = events.filter(event => event.start >= new Date());
|
||||
|
||||
if (futureEvents.length === 0) {
|
||||
reservationsList.innerHTML = '<div class="p-4 text-gray-500">Žádné nadcházející rezervace</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
reservationsList.innerHTML = futureEvents.map(event => {
|
||||
const vehicleClass = 'event-' + event.extendedProps.vehicle.toLowerCase().replace(/\s+/g, '-');
|
||||
return `
|
||||
<div class="reservation-item">
|
||||
<div class="reservation-vehicle-badge ${vehicleClass}">
|
||||
${event.extendedProps.vehicle}
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<div class="font-medium">${event.extendedProps.driverName}</div>
|
||||
<div class="text-sm text-gray-600">
|
||||
${formatDateTime(event.start)} - ${formatDateTime(event.end)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
${event.extendedProps.purpose || 'Bez účelu'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Update the eventContent function to add vehicle-specific styling
|
||||
// (Already defined in calendarConfig above, so this duplicate is removed)
|
||||
|
||||
// Call updateReservationsList when events change
|
||||
calendar.on('eventAdd', updateReservationsList);
|
||||
calendar.on('eventRemove', updateReservationsList);
|
||||
calendar.on('eventChange', updateReservationsList);
|
||||
|
||||
// Initial update of reservations list
|
||||
updateReservationsList();
|
||||
|
||||
// Vehicle select change handler
|
||||
document.getElementById('vehicle').addEventListener('change', async function() {
|
||||
const startDate = document.getElementById('startDate').value;
|
||||
const startTime = document.getElementById('startTime').value;
|
||||
const endDate = document.getElementById('endDate').value;
|
||||
const endTime = document.getElementById('endTime').value;
|
||||
|
||||
if (startDate && startTime && endDate && endTime) {
|
||||
await checkVehicleAvailability(this.value, startDate, endDate);
|
||||
}
|
||||
});
|
||||
|
||||
// Check vehicle availability
|
||||
async function checkVehicleAvailability(vehicle, startDate, endDate) {
|
||||
try {
|
||||
@@ -1025,8 +1361,16 @@
|
||||
reservationModal.style.display = 'block';
|
||||
});
|
||||
|
||||
// Rest of the JavaScript code
|
||||
// ...existing code...
|
||||
// Initialize time dropdowns when page loads
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
populateTimeDropdowns();
|
||||
|
||||
// Set default dates to today
|
||||
const today = new Date();
|
||||
const dateStr = today.toISOString().split('T')[0];
|
||||
document.getElementById('startDate').value = dateStr;
|
||||
document.getElementById('endDate').value = dateStr;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user