Files
PPve/rezervace-aut.html
T
Dvorinka c3d30d7918 test
2025-06-20 11:59:42 +02:00

2013 lines
76 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Poppe Potthoff - Rezervace služebních vozů</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
<link href='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.css' rel='stylesheet' />
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/main.js'></script>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@5.11.3/locales/cs.js'></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'brand-blue': '#004990',
'brand-light-blue': '#0072b0',
'brand-gray': '#f0f2f5'
}
}
}
}
</script>
<style>
/* Existing styles */
.fc-event {
cursor: pointer;
border-radius: 6px;
border: none;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.fc-event:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
.fc-toolbar-title {
text-transform: capitalize;
font-weight: 600;
color: #2d3748;
}
/* Calendar navigation buttons */
.fc .fc-button {
border-radius: 0.375rem;
padding: 0.5rem 1rem;
font-weight: 500;
transition: all 0.2s;
}
.fc .fc-prev-button,
.fc .fc-next-button {
background-color: #ffffff;
border: 1px solid #e5e7eb;
color: #ffffff;
}
.fc .fc-prev-button:hover,
.fc .fc-next-button:hover {
background-color: #f3f4f6;
border-color: #d1d5db;
}
.fc .fc-today-button {
background-color: #004990;
border-color: #004990;
}
.fc .fc-today-button:hover {
background-color: #003970;
border-color: #003970;
}
/* Improved calendar header */
.fc .fc-toolbar-title {
font-size: 1.25rem;
font-weight: 600;
color: #111827;
}
/* Active state for view buttons */
.fc .fc-button-active {
background-color: #004990 !important;
border-color: #004990 !important;
color: white !important;
}
/* Space between button groups */
.fc .fc-button-group {
gap: 0.25rem;
}
.fc .fc-toolbar.fc-header-toolbar {
margin-bottom: 1.5rem;
padding: 0.5rem;
background-color: white;
border-radius: 0.5rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.fc .fc-button-primary {
background-color: #004990;
border-color: #004990;
text-transform: uppercase;
font-size: 0.875rem;
font-weight: 500;
padding: 0.5rem 1rem;
transition: all 0.2s;
}
.fc .fc-button-primary:hover {
background-color: #0072b0;
border-color: #0072b0;
transform: translateY(-1px);
}
.fc .fc-button-primary:not(:disabled).fc-button-active,
.fc .fc-button-primary:not(:disabled):active {
background-color: #0072b0;
border-color: #0072b0;
}
.fc-view-harness {
background: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}
.fc-scrollgrid {
border: none !important;
}
.fc-col-header-cell {
background: #f8fafc;
padding: 1rem 0;
}
.fc-day-today {
background: #e8f4ff !important;
}
.fc-timegrid-slot {
height: 2rem !important;
}
.fc-timegrid-slot-label {
font-size: 0.75rem !important;
padding: 0 !important;
}
.reservation-card {
transition: all 0.3s ease;
}
.reservation-card:hover {
transform: translateY(-2px);
box-shadow: 0 8px 16px rgba(0,0,0,0.1);
}
/* Vehicle badges */
.vehicle-badge {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.75rem;
border-radius: 9999px;
font-size: 0.875rem;
font-weight: 500;
margin-right: 0.5rem;
}
.vehicle-badge i {
margin-right: 0.5rem;
}
/* Vehicle-specific colors */
.vehicle-vw-caddy { background-color: #e6f3ff; color: #1a73e8; }
.vehicle-vw-golf { background-color: #e6ffe6; color: #28a745; }
.vehicle-škoda-fabia { background-color: #fff3e6; color: #fd7e14; }
.vehicle-bmw-218d { background-color: #f3e6ff; color: #6f42c1; }
.vehicle-škoda-superb { background-color: #ffe6e6; color: #dc3545; }
/* Calendar size adjustments */
.calendar-container {
max-width: 1000px;
margin: 0 auto;
padding: 1rem;
}
/* Responsive calendar styles */
@media (max-width: 768px) {
.fc .fc-toolbar {
flex-direction: column;
gap: 0.5rem;
}
.fc .fc-toolbar-title {
font-size: 1.2rem;
}
.fc-header-toolbar {
padding: 0.5rem !important;
}
.fc-view-harness {
height: 400px !important;
}
.fc .fc-button {
padding: 0.4rem 0.8rem;
font-size: 0.875rem;
}
}
/* Adjust calendar size for desktop */
.fc-view-harness {
height: 450px !important;
}
/* Improve event styling */
.fc-event {
padding: 2px 4px;
margin: 1px 0;
border-radius: 4px;
border: none !important;
background: #fff;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.fc-event-title {
font-weight: 500;
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;
color: 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---4z1-8241 {
background-color: rgb(59, 130, 246) !important; /* blue-500 */
}
.event-vw-golf---5z5-8694 {
background-color: rgb(34, 197, 94) !important; /* green-500 */
}
.event-škoda-fabia---1z3-5789 {
background-color: rgb(249, 115, 22) !important; /* orange-500 */
}
.event-bmw-218d---6z5-4739 {
background-color: rgb(168, 85, 247) !important; /* purple-500 */
}
.event-bmw-218d---6z5-4740 {
background-color: rgb(139, 92, 246) !important; /* purple-400 - slightly lighter for distinction */
}
.event-škoda-superb---2by-2398 {
background-color: rgb(239, 68, 68) !important; /* red-500 */
}
/* 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;
margin: 2rem auto;
padding: 2rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
/* Form group spacing */
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #374151;
}
/* Form field styles */
.form-group select {
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
}
.form-group .select-wrapper {
position: relative;
}
.form-group .select-icon {
position: absolute;
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: #6b7280;
pointer-events: none;
}
.form-group select {
width: 100%;
padding: 0.5rem 2rem 0.5rem 2.5rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
background-color: white;
color: #111827;
}
.form-group select:focus {
outline: 2px solid transparent;
outline-offset: 2px;
border-color: #004990;
box-shadow: 0 0 0 1px #004990;
}
.form-group select option {
padding: 0.5rem;
}
/* Time input styling */
.time-input-wrapper {
position: relative;
}
.time-input-wrapper i {
position: absolute;
left: 0.75rem;
top: 50%;
transform: translateY(-50%);
color: #6b7280;
pointer-events: none;
}
.time-input-wrapper input[type="time"] {
width: 100%;
padding: 0.5rem 0.75rem 0.5rem 2.5rem;
border: 1px solid #d1d5db;
border-radius: 0.375rem;
appearance: none;
background-color: white;
color: #111827;
}
.time-input-wrapper input[type="time"]:focus {
outline: 2px solid transparent;
outline-offset: 2px;
border-color: #004990;
box-shadow: 0 0 0 1px #004990;
}
/* Hide default time picker icon in Edge/Chrome */
.time-input-wrapper input[type="time"]::-webkit-calendar-picker-indicator {
background: none;
padding-right: 0.5rem;
}
/* Style for Firefox */
.time-input-wrapper input[type="time"]::-moz-calendar-picker-indicator {
background: none;
}
/* Responsive form layout */
@media (max-width: 640px) {
.form-container {
padding: 1rem;
margin: 1rem;
}
.grid-cols-2 {
grid-template-columns: 1fr !important;
}
.form-group {
margin-bottom: 1rem;
}
}
/* Improve button responsiveness */
.btn {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
transition: all 0.2s;
}
@media (min-width: 640px) {
.btn {
width: auto;
}
}
/* Modal styles */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
padding: 1rem;
}
.modal-content {
background-color: white;
max-width: 500px;
margin: 2rem auto;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
position: relative;
padding: 1.5rem;
}
.modal-close {
position: absolute;
top: 0.75rem;
right: 0.75rem;
cursor: pointer;
font-size: 1.5rem;
color: #666;
}
.modal-header {
border-bottom: 1px solid #e5e7eb;
padding-bottom: 1rem;
margin-bottom: 1rem;
}
.modal-body {
color: #374151;
}
.info-grid {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.75rem;
margin-top: 1rem;
}
.info-label {
font-weight: 500;
color: #6b7280;
}
.delete-btn {
background-color: #ef4444;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
margin-top: 1rem;
}
.delete-btn:hover {
background-color: #dc2626;
}
/* Reservation form modal styles */
.reservation-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
padding: 2rem;
overflow-y: auto;
}
.reservation-modal .form-container {
background: white;
max-width: 600px;
margin: 0 auto;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
position: relative;
padding: 2rem;
}
.reservation-modal .close-button {
position: absolute;
top: 1rem;
right: 1rem;
font-size: 1.5rem;
color: #666;
cursor: pointer;
background: none;
border: none;
padding: 0.5rem;
}
.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;
display: none;
}
#highTrafficWarning.show {
display: flex;
}
/* Vehicle Legend and Filters */
#vehicleFilters {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 1rem;
}
.vehicle-filter-btn {
background: none;
border: none;
padding: 0;
margin: 0 0.25rem 0.25rem 0;
cursor: pointer;
outline: none;
}
.vehicle-filter-btn:hover .vehicle-badge {
opacity: 0.9;
transform: translateY(-1px);
}
.vehicle-filter-btn.active .vehicle-badge {
border: 2px solid #004990;
box-shadow: 0 0 0 1px white;
}
.reservation-item.hidden-vehicle {
display: none;
}
.fc-event.hidden-vehicle {
display: none;
}
/* Clickable reservations styling */
.reservation-item {
cursor: pointer;
transition: all 0.2s ease;
}
.reservation-item:hover {
background-color: #f3f4f6;
transform: translateY(-1px);
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
/* Modal delete button styling */
#deleteReservation {
background-color: #ef4444;
color: white;
padding: 0.5rem 1rem;
border-radius: 0.375rem;
display: flex;
align-items: center;
gap: 0.5rem;
margin-top: 1rem;
transition: all 0.2s ease;
}
#deleteReservation:hover {
background-color: #dc2626;
}
#deleteReservation.hidden {
display: none;
}
/* New styles for improved layout and responsiveness */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f9fafb;
margin: 0;
padding: 0;
}
h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 0;
color: #111827;
}
h1 {
font-size: 1.875rem;
font-weight: 700;
margin-bottom: 1rem;
}
h2 {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.75rem;
}
h3 {
font-size: 1.25rem;
font-weight: 500;
margin-bottom: 0.5rem;
}
p {
margin: 0;
padding: 0;
color: #9ca3af;
line-height: 1.625;
}
a {
color: #004990;
text-decoration: none;
transition: color 0.2s;
}
a:hover {
color: #0072b0;
}
/* Button styles */
.btn {
display: inline-block;
padding: 0.75rem 1.5rem;
border-radius: 0.375rem;
font-weight: 500;
text-align: center;
cursor: pointer;
transition: background-color 0.2s, transform 0.2s;
}
.btn-primary {
background-color: #004990;
color: white;
}
.btn-primary:hover {
background-color: #0072b0;
transform: translateY(-1px);
}
.btn-secondary {
background-color: #f3f4f6;
color: #111827;
}
.btn-secondary:hover {
background-color: #e5e7eb;
}
/* Card styles */
.card {
background: white;
border-radius: 0.5rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.card-header {
margin-bottom: 1rem;
}
.card-title {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: 0.5rem;
}
.card-subtitle {
font-size: 0.875rem;
color: #6b7280;
}
/* Table styles */
.table {
width: 100%;
border-collapse: collapse;
margin-bottom: 1.5rem;
}
.table th, .table td {
padding: 0.75rem;
text-align: left;
border-bottom: 1px solid #e5e7eb;
}
.table th {
background-color: #f8fafc;
font-weight: 500;
}
/* Badge styles */
.badge {
display: inline-block;
padding: 0.375rem 0.75rem;
border-radius: 9999px;
font-size: 0.75rem;
font-weight: 500;
text-align: center;
}
.badge-success {
background-color: #d1fae5;
color: #065f46;
}
.badge-danger {
background-color: #fee2e2;
color: #991b1b;
}
/* Spinner styles */
.spinner {
border: 2px solid #e5e7eb;
border-top: 2px solid #004990;
border-radius: 50%;
width: 1rem;
height: 1rem;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Toast notification styles */
.toast {
position: fixed;
top: 1rem;
right: 1rem;
background: white;
border-radius: 0.375rem;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
padding: 1rem;
z-index: 1000;
transition: transform 0.3s ease, opacity 0.3s ease;
}
.toast.hide {
transform: translateY(-10px);
opacity: 0;
}
.toast.show {
transform: translateY(0);
opacity: 1;
}
/* Responsive styles */
@media (max-width: 768px) {
.calendar-container {
padding: 0.5rem;
}
.form-container {
padding: 1.5rem;
}
.btn {
width: 100%;
padding: 0.5rem;
}
.fc-view-harness {
height: 350px !important;
}
}
@media (max-width: 640px) {
.form-container {
padding: 1rem;
margin: 1rem;
}
.grid-cols-2 {
grid-template-columns: 1fr !important;
}
.form-group {
margin-bottom: 1rem;
}
}
</style>
</head>
<body class="bg-brand-gray min-h-screen">
<!-- Nav bar copied from existing template -->
<nav class="bg-brand-blue text-white shadow-lg">
<div class="max-w-6xl mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center space-x-2">
<a href="http://webportal/index.html"><img src="http://pp-kunovice.cz/wp-content/uploads/2022/04/logo-retina-white.png" alt="Poppe Potthoff Logo" class="h-10"></a>
<div class="hidden md:block text-xl font-semibold">Poppe + Potthoff</div>
</div>
<!-- Mobile menu button -->
<div class="md:hidden">
<button id="mobile-menu-button" class="text-white focus:outline-none">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<!-- Desktop menu -->
<div class="hidden md:flex space-x-6">
<a href="http://webportal/index.html" class="hover:text-brand-light-blue">Rozcestník</a>
<a href="http://webportal/evidence-aut" class="hover:text-brand-light-blue">Evidence aut</a>
<a href="http://ppc-app/pwkweb2/" class="hover:text-brand-light-blue">Obědy</a>
<a href="http://osticket/" class="hover:text-brand-light-blue">OSticket</a>
<a href="http://kanboard/" class="hover:text-brand-light-blue">Kanboard</a>
<a href="http://webportal:8080" class="hover:text-brand-light-blue">Kontakt</a>
<a href="http://webportal/rezervace-aut" class="hover:text-brand-light-blue">Rezervace aut</a>
</div>
</div>
<!-- Mobile menu -->
<div id="mobile-menu" class="hidden md:hidden px-2 pt-2 pb-3 space-y-1">
<a href="webportal/index.html" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Rozcestník</a>
<a href="webportal/evidence-aut" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Evidence aut</a>
<a href="http://ppc-app/pwkweb2/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Obědy</a>
<a href="http://osticket/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">OSticket</a>
<a href="http://kanboard/" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kanboard</a>
<a href="http://webportal:8080" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Kontakt</a>
<a href="http://webportal/rezervace-aut" class="block px-3 py-2 rounded-md text-base font-medium hover:text-brand-light-blue">Rezervace aut</a>
</div>
</nav>
<!-- Page Header -->
<div class="bg-gradient-to-r from-brand-blue to-brand-light-blue text-white py-6 mb-8">
<div class="max-w-6xl mx-auto px-4">
<h1 class="text-3xl font-bold text-white">Poppe + Potthoff Rezervace služebních vozů</h1>
<p class="text-gray-100 mt-2">Systém pro rezervaci služebních vozidel</p>
</div>
</div>
<!-- Main Content -->
<div class="container mx-auto px-4 py-8">
<div class="calendar-container">
<!-- 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>
<div class="flex flex-wrap gap-3" id="vehicleFilters">
<button class="vehicle-filter-btn active" data-vehicle="all">
<span class="vehicle-badge bg-gray-100 text-gray-700">
<i class="fas fa-car"></i>Všechna vozidla
</span>
</button>
<button class="vehicle-filter-btn" data-vehicle="VW Caddy - 4Z1 8241">
<span class="vehicle-badge vehicle-vw-caddy">
<i class="fas fa-truck"></i>VW Caddy - 4Z1 8241
</span>
</button>
<button class="vehicle-filter-btn" data-vehicle="VW Golf - 5Z5 8694">
<span class="vehicle-badge vehicle-vw-golf">
<i class="fas fa-car"></i>VW Golf - 5Z5 8694
</span>
</button>
<button class="vehicle-filter-btn" data-vehicle="Škoda Fabia - 1Z3 5789">
<span class="vehicle-badge vehicle-škoda-fabia">
<i class="fas fa-car"></i>Škoda Fabia - 1Z3 5789
</span>
</button>
</div>
</div>
<!-- Info Banner -->
<div class="bg-blue-50 border-l-4 border-brand-blue p-4 mb-4 rounded-r-lg shadow-sm">
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-info-circle text-brand-blue"></i>
</div>
<div class="ml-3">
<p class="text-sm text-gray-700">
Pro rychlé vytvoření rezervace dvakrát klikněte na požadovaný čas/den v kalendáři.
</p>
</div>
</div>
</div>
<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> <!-- Reservations List -->
<div class="reservations-list mt-8">
<div class="reservations-list-header flex justify-between items-center">
<h3 class="text-lg font-semibold text-gray-800">Aktuální a budoucí rezervace</h3>
<div class="text-sm text-gray-500" id="reservationsCount"></div>
</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">&times;</button>
<h2 class="text-2xl font-bold mb-6 text-gray-800">Nová rezervace</h2>
<form id="reservationForm" class="space-y-6"> <!-- Driver Name -->
<div class="form-group"> <label for="driverName">Jméno a příjmení řidiče</label>
<input type="text" id="driverName" name="driverName" required
class="w-full p-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-brand-blue focus:border-transparent"
placeholder="např. Jan Novák"
pattern="^[A-Za-zÀ-ž]{2,}(\s[A-Za-zÀ-ž]{2,})+$"
title="Prosím zadejte celé jméno (jméno a příjmení)"
/>
<div id="nameError" class="text-red-500 text-sm mt-1 hidden">
Prosím zadejte celé jméno a příjmení
</div>
</div>
<!-- Vehicle Selection with Warning -->
<div class="form-group">
<label for="vehicle">Vozidlo</label>
<div class="select-wrapper">
<i class="fas fa-car select-icon"></i>
<select id="vehicle" name="vehicle" required> <option value="" disabled selected>Vyberte vozidlo...</option>
<option value="VW Caddy - 4Z1 8241">VW Caddy - 4Z1 8241</option>
<option value="VW Golf - 5Z5 8694">VW Golf - 5Z5 8694</option>
<option value="Škoda Fabia - 1Z3 5789">Škoda Fabia - 1Z3 5789</option>
</select>
</div>
<!-- High Traffic Warning -->
<div id="highTrafficWarning" class="hidden mt-2 p-2 bg-yellow-50 text-yellow-800 text-sm rounded-md border border-yellow-200 flex items-start" style="display: none;">
<i class="fas fa-exclamation-triangle mt-0.5 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 -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- Start Date and Time -->
<div class="form-group">
<label for="startDate">Datum začátku</label>
<input type="date" id="startDate" name="startDate" required class="w-full p-2 border border-gray-300 rounded-lg">
</div>
<div class="form-group">
<label for="startTime">Čas začátku</label>
<div class="time-input-wrapper">
<i class="fas fa-clock"></i>
<input type="time" id="startTime" name="startTime" required min="06:00" max="22:00">
</div>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<!-- End Date and Time -->
<div class="form-group">
<label for="endDate">Datum konce</label>
<input type="date" id="endDate" name="endDate" required class="w-full p-2 border border-gray-300 rounded-lg">
</div>
<div class="form-group">
<label for="endTime">Čas konce</label>
<div class="time-input-wrapper">
<i class="fas fa-clock"></i>
<input type="time" id="endTime" name="endTime" required min="06:00" max="22:00">
</div>
</div>
</div>
<!-- Purpose -->
<div class="space-y-2">
<label for="purpose" class="block text-sm font-medium text-gray-700">Účel jízdy (nepovinné)</label>
<div class="relative">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fas fa-briefcase text-gray-400"></i>
</div>
<input type="text" id="purpose" name="purpose"
class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-brand-light-blue focus:border-brand-light-blue">
</div>
</div>
<!-- Submit Button -->
<div class="mt-6 flex justify-end">
<button type="submit"
class="bg-brand-blue hover:bg-brand-light-blue text-white font-bold py-2 px-6 rounded-lg shadow transition-all duration-200">
Vytvořit rezervaci
</button>
</div>
</form>
<!-- Status message container -->
<div class="fixed bottom-4 right-4 z-50 hidden" id="statusMessage">
<div class="flex items-center shadow-lg rounded-lg px-4 py-3">
</div>
</div>
</div>
</div>
<!-- Modal HTML -->
<div id="eventModal" class="modal">
<div class="modal-content">
<span class="modal-close">&times;</span>
<div class="modal-header">
<h3 class="text-xl font-semibold text-gray-900">Detail rezervace</h3>
</div>
<div class="modal-body">
<div class="info-grid">
<span class="info-label">Řidič:</span>
<span id="modalDriver"></span>
<span class="info-label">Vozidlo:</span>
<span id="modalVehicle"></span>
<span class="info-label">Začátek:</span>
<span id="modalStart"></span>
<span class="info-label">Konec:</span>
<span id="modalEnd"></span>
<span class="info-label">Účel:</span>
<span id="modalPurpose"></span>
</div>
<button id="deleteReservation" class="delete-btn hidden">
<i class="fas fa-trash mr-2"></i>Zrušit rezervaci
</button>
</div>
</div>
</div>
</div>
<!-- Footer copied from existing template -->
<footer class="bg-gray-800 text-gray-400 py-8 mt-12">
<div class="max-w-6xl mx-auto px-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
<!-- Company Info -->
<div>
<h3 class="text-white text-lg font-semibold mb-4">Poppe + Potthoff CZ</h3>
<p class="mb-2">IČO: 26902214</p>
<p class="mb-2">DIČ: CZ26902214</p>
<p class="mb-2">Schránka: gfrk5qy</p>
<p>Na Záhonech 1086, 686 04 Kunovice</p>
</div>
<!-- Quick Links -->
<div>
<h3 class="text-white text-lg font-semibold mb-4">Rychlé odkazy</h3>
<ul class="space-y-2">
<li><a href="http://webportal/" class="hover:text-white">Rozcestník</a></li>
<li><a href="http://webportal/evidence-aut" class="hover:text-white">Evidence aut</a></li>
<li><a href="http://ppc-app/pwkweb2/" class="hover:text-white">Objednávka obědů</a></li>
<li><a href="http://osticket/" class="hover:text-white">Technická podpora</a></li>
<li><a href="http://webportal:8080" class="hover:text-white">Kontakty</a></li>
<li><a href="http://webportal/rezervace-aut" class="hover:text-white">Rezervace aut</a></li>
</ul>
</div>
<!-- Copyright -->
<div class="md:text-right">
<img src="http://pp-kunovice.cz/wp-content/uploads/2022/04/logo-retina-white.png" alt="Poppe Potthoff Logo" class="h-10 mb-4 inline-block">
<p class="text-sm"> 2025 Poppe + Potthoff CZ</p>
<p class="text-xs mt-2">Všechna práva vyhrazena</p>
</div>
</div>
<div class="border-t border-gray-700 mt-8 pt-6 text-center text-sm">
<p>Created by <a href="https://tdvorak.dev" class="text-blue-400 hover:text-blue-300">TDvorak</a></p>
</div>
</div>
</footer>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Helper functions for date/time formatting
function formatDateForAPI(date) {
return date.toISOString().split('T')[0];
}
function formatTimeForAPI(date) {
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 for high traffic and show warning
function checkHighTraffic(vehicle, date) {
if (!vehicle || !date) return;
const events = calendar.getEvents();
const dayStart = new Date(date);
dayStart.setHours(0, 0, 0, 0);
const dayEnd = new Date(date);
dayEnd.setHours(23, 59, 59, 999);
// Count reservations for this vehicle on this day
const reservationsCount = events.filter(event => {
const eventStart = new Date(event.start);
return event.extendedProps.vehicle === vehicle &&
eventStart >= dayStart &&
eventStart <= dayEnd;
}).length;
const warningElement = document.getElementById('highTrafficWarning');
if (!warningElement) {
// Create warning element if it doesn't exist
const warningDiv = document.createElement('div');
warningDiv.id = 'highTrafficWarning';
warningDiv.className = 'bg-orange-50 border-l-4 border-orange-500 p-4 mt-4 hidden';
warningDiv.innerHTML = `
<div class="flex">
<div class="flex-shrink-0">
<i class="fas fa-exclamation-triangle text-orange-500"></i>
</div>
<div class="ml-3">
<p class="text-sm text-orange-700">
Upozornění: Pro tento den je již naplánováno více rezervací tohoto vozidla.
</p>
</div>
</div>
`;
const vehicleFormGroup = document.querySelector('#vehicle').closest('.form-group');
if (vehicleFormGroup) {
vehicleFormGroup.appendChild(warningDiv);
}
}
// Show or hide warning based on reservation count
if (warningElement) {
if (reservationsCount >= 2) {
warningElement.classList.remove('hidden');
} else {
warningElement.classList.add('hidden');
}
}
}
// Add event listeners for checking high traffic
document.getElementById('vehicle').addEventListener('change', function(e) {
const startDate = document.getElementById('startDate').value;
if (startDate) {
checkHighTraffic(e.target.value, new Date(startDate));
}
});
document.getElementById('startDate').addEventListener('change', function(e) {
const vehicle = document.getElementById('vehicle').value;
if (vehicle) {
checkHighTraffic(vehicle, new Date(e.target.value));
}
});
async function checkAvailabilityAndTraffic() {
const vehicle = document.getElementById('vehicle').value;
const startDateInput = document.getElementById('startDate');
const startTimeInput = document.getElementById('startTime');
const endDateInput = document.getElementById('endDate');
const endTimeInput = document.getElementById('endTime');
if (!vehicle || !startDateInput.value || !startTimeInput.value ||
!endDateInput.value || !endTimeInput.value) {
return;
}
const startDateTime = parseDateTimeInputs(startDateInput, startTimeInput);
checkHighTraffic(vehicle, startDateTime);
try {
const response = await fetch(`/api/check-availability?` +
`vehicle=${encodeURIComponent(vehicle)}&` +
`startDate=${formatDateForAPI(startDateTime)}&` +
`startTime=${formatTimeForAPI(startDateTime)}&` +
`endDate=${formatDateForAPI(new Date(endDateInput.value))}&` +
`endTime=${endTimeInput.value}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
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 není v tomto čase k dispozici';
}
availabilityStatus.classList.remove('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);
});
// Name validation function
function validateDriverName(name) {
// Check if name has at least two parts with minimum 2 characters each
const parts = name.trim().split(/\s+/);
return parts.length >= 2 && parts.every(part => part.length >= 2 && /^[A-Za-zÀ-ž]+$/.test(part));
}
// Add input validation for driver name
const driverNameInput = document.getElementById('driverName');
const nameError = document.getElementById('nameError');
driverNameInput.addEventListener('input', function() {
const isValid = validateDriverName(this.value);
this.classList.toggle('border-red-500', !isValid);
this.classList.toggle('border-gray-300', isValid);
nameError.classList.toggle('hidden', isValid);
});
// Handle form submission
reservationForm.addEventListener('submit', async function(e) {
e.preventDefault();
const driverName = document.getElementById('driverName').value;
if (!validateDriverName(driverName)) {
showMessage('Prosím zadejte platné jméno a příjmení', 'error');
return;
}
const startDateInput = document.getElementById('startDate');
const startTimeInput = document.getElementById('startTime');
const endDateInput = document.getElementById('endDate');
const endTimeInput = document.getElementById('endTime');
const startDateTime = parseDateTimeInputs(startDateInput, startTimeInput);
const endDateTime = parseDateTimeInputs(endDateInput, endTimeInput);
// Validate dates
if (endDateTime <= startDateTime) {
showMessage('Konec rezervace musí být po začátku', 'error');
return;
}
// Check availability one last time
const isAvailable = await checkAvailabilityAndTraffic();
if (!isAvailable) {
showMessage('Vozidlo již není k dispozici v tomto čase', 'error');
return;
}
// Prepare reservation data
const reservationData = {
driverName: document.getElementById('driverName').value,
vehicle: document.getElementById('vehicle').value,
startDate: formatDateForAPI(startDateTime),
startTime: formatTimeForAPI(startDateTime),
endDate: formatDateForAPI(endDateTime),
endTime: formatTimeForAPI(endDateTime),
purpose: document.getElementById('purpose')?.value || ''
};
try {
const response = await fetch('/api/reservations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(reservationData)
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
// Store reservation ID in localStorage
const userReservations = JSON.parse(localStorage.getItem('userReservations') || '[]');
userReservations.push(result.id);
localStorage.setItem('userReservations', JSON.stringify(userReservations));
// Add the new event to the calendar
calendar.addEvent({
id: result.id,
title: `${result.vehicle} - ${result.driverName}`,
start: `${result.startDate}T${result.startTime}`,
end: `${result.endDate}T${result.endTime}`,
extendedProps: {
driverName: result.driverName,
vehicle: result.vehicle,
purpose: result.purpose
}
});
// Reset form and close modal
reservationForm.reset();
document.getElementById('reservationModal').style.display = 'none';
showMessage('Rezervace byla úspěšně vytvořena', 'success');
} catch (error) {
console.error('Error:', error);
showMessage('Nepodařilo se vytvořit rezervaci', 'error');
}
});
// Show status message function
function showMessage(text, type = 'success') {
const statusContainer = document.getElementById('statusMessage');
statusContainer.className = 'mt-4 p-4 rounded-md';
statusContainer.classList.remove('hidden');
if (type === 'success') {
statusContainer.classList.add('bg-green-50', 'text-green-800');
} else {
statusContainer.classList.add('bg-red-50', 'text-red-800');
}
statusContainer.innerHTML = `
<div class="flex items-center">
<i class="fas ${type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'} mr-2"></i>
<span>${text}</span>
</div>
`;
// Auto hide after 5 seconds
setTimeout(() => {
statusContainer.classList.add('hidden');
}, 5000);
}
// Initialize calendar
const calendarEl = document.getElementById('calendar');
let selectedVehicle = 'all';
let calendar; // Make calendar global
// Update reservations list when page loads
updateReservationsList();
// Fix calendar initialization
const calendarConfig = {
initialView: 'dayGridMonth',
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',
slotDuration: '01:00:00',
snapDuration: '01:00:00',
allDaySlot: false,
events: '/api/reservations',
eventDisplay: 'block',
eventTimeFormat: {
hour12: false
},
eventDidMount: function(info) {
const vehicle = info.event.extendedProps.vehicle;
const vehicleClass = 'event-' + vehicle.toLowerCase().replace(/\s+/g, '-');
info.el.classList.add(vehicleClass);
},
eventSourceSuccess: function(content, xhr) {
// This runs after events are successfully loaded from the API
setTimeout(updateReservationsList, 0); // Use setTimeout to ensure DOM is ready
return content;
},
eventClassNames: function(arg) {
return ['event-' + arg.event.extendedProps.vehicle.toLowerCase().replace(/\s+/g, '-')];
},
eventClick: function(info) {
showEventModal(info.event);
},
dateClick: function(info) {
showReservationForm(info.date);
}
};
calendar = new FullCalendar.Calendar(calendarEl, calendarConfig);
calendar.render();
calendar.refetchEvents();
updateReservationsList();
// Add vehicle filter click handlers
document.querySelectorAll('.vehicle-filter-btn').forEach(btn => {
btn.addEventListener('click', function() {
// Remove active class from all buttons
document.querySelectorAll('.vehicle-filter-btn').forEach(b =>
b.classList.remove('active'));
// Add active class to clicked button
this.classList.add('active');
// Update selected vehicle
selectedVehicle = this.dataset.vehicle;
// Apply filtering
filterEvents();
});
});
// Update the filterEvents function
function filterEvents() {
const events = calendar.getEvents();
events.forEach(event => {
if (selectedVehicle === 'all' || event.extendedProps.vehicle === selectedVehicle) {
event.setProp('display', 'auto');
} else {
event.setProp('display', 'none');
}
});
// Update the reservations list to match filtered events
updateReservationsList();
}
// Fix reservation form show/hide
const reservationModal = document.getElementById('reservationModal');
const closeReservationModal = document.getElementById('closeReservationModal');
const newReservationBtn = document.getElementById('newReservationBtn');
newReservationBtn.addEventListener('click', () => {
showReservationForm(new Date());
});
closeReservationModal.addEventListener('click', () => {
reservationModal.style.display = 'none';
});
// Fix calendar filtering
function filterEvents() {
const events = calendar.getEvents();
events.forEach(event => {
const eventEl = calendar.getEventById(event.id);
if (eventEl) {
if (selectedVehicle === 'all' || event.extendedProps.vehicle === selectedVehicle) {
// Show event
eventEl.setProp('display', 'auto');
} else {
// Hide event
eventEl.setProp('display', 'none');
}
}
});
updateReservationsList();
}
// Update eventDidMount to not handle filtering
// (eventDidMount is already defined in calendarConfig above)
// Fix reservation form show function
function showReservationForm(date) {
const formattedDate = date.toISOString().split('T')[0];
const formattedTime = date.toTimeString().substring(0, 5);
document.getElementById('startDate').value = formattedDate;
document.getElementById('startTime').value = formattedTime;
const endDate = new Date(date);
endDate.setHours(endDate.getHours() + 1);
document.getElementById('endDate').value = formattedDate;
document.getElementById('endTime').value = endDate.toTimeString().substring(0, 5);
reservationModal.style.display = 'block';
}
// Close modal when clicking the close button
closeReservationModal.onclick = function() {
reservationModal.style.display = 'none';
}
// Ensure modal is defined before using it in the window.onclick handler
const modal = document.getElementById('eventModal');
// Close modal when clicking outside
window.onclick = function(event) {
if (event.target === modal) {
modal.style.display = 'none';
}
}
// 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 reservationsCount = document.getElementById('reservationsCount');
// Check if calendar is initialized
if (!calendar) {
console.warn('Calendar not initialized yet');
return;
}
const events = calendar.getEvents();
// Get current date at start of day
const now = new Date();
now.setHours(0, 0, 0, 0);
try {
// Filter and sort events
const currentAndFutureEvents = events
.filter(event => event && event.end && event.end >= now)
.filter(event => selectedVehicle === 'all' || (event.extendedProps && event.extendedProps.vehicle === selectedVehicle))
.sort((a, b) => a.start - b.start);
// Update count and list
if (!currentAndFutureEvents || currentAndFutureEvents.length === 0) {
if (reservationsCount) {
reservationsCount.textContent = 'Žádné aktuální ani budoucí rezervace';
}
if (reservationsList) {
reservationsList.innerHTML = `
<div class="p-4 text-center text-gray-500">
Žádné aktuální ani budoucí rezervace
</div>
`;
}
return;
}
if (reservationsCount) {
reservationsCount.textContent = `Počet rezervací: ${currentAndFutureEvents.length}`;
}
const html = currentAndFutureEvents.map(event => {
if (!event || !event.extendedProps) return '';
const vehicleClass = 'vehicle-' + (event.extendedProps.vehicle || '').toLowerCase().replace(/\s+/g, '-');
return `
<div class="reservation-item hover:bg-gray-50" data-event-id="${event.id || ''}">
<span class="reservation-vehicle-badge ${vehicleClass} text-sm">
${event.extendedProps.vehicle || ''}
</span>
<div class="flex-grow">
<div class="font-medium">${event.extendedProps.driverName || ''}</div>
<div class="text-sm text-gray-600">
${event.start ? formatDateTime(event.start) : ''} - ${event.end ? formatDateTime(event.end) : ''}
</div>
${event.extendedProps.purpose ? `
<div class="text-sm text-gray-500 mt-1">
Účel: ${event.extendedProps.purpose}
</div>
` : ''}
</div>
</div>
`;
}).join('');
if (reservationsList) {
reservationsList.innerHTML = html;
}
} catch (error) {
console.error('Error updating reservations list:', error);
if (reservationsList) {
reservationsList.innerHTML = `
<div class="p-4 text-center text-red-500">
Chyba při načítání rezervací
</div>
`;
}
}
}
// Enhanced date/time formatting function
function formatDateTime(date) {
const options = {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
};
return new Date(date).toLocaleString('cs-CZ', options);
}
// Function to check for high traffic in the selected month
function checkHighTraffic(vehicle, date) {
console.log('[checkHighTraffic] Starting traffic check for vehicle:', vehicle, 'on date:', date);
if (!vehicle || !date) {
console.log('[checkHighTraffic] Missing vehicle or date, aborting');
return;
}
// Get first day of the month
const startOfMonth = new Date(date.getFullYear(), date.getMonth(), 1);
// Get last day of the month
const endOfMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0);
endOfMonth.setHours(23, 59, 59, 999);
console.log('[checkHighTraffic] Checking from', startOfMonth, 'to', endOfMonth);
const events = calendar.getEvents();
console.log('[checkHighTraffic] Total events in calendar:', events.length);
const vehicleEvents = events.filter(event => {
const isMatch = event.extendedProps.vehicle === vehicle &&
event.start >= startOfMonth &&
event.start <= endOfMonth;
if (isMatch) {
console.log('[checkHighTraffic] Matching event:', {
id: event.id,
start: event.start,
end: event.end,
vehicle: event.extendedProps.vehicle,
title: event.title
});
}
return isMatch;
});
const vehicleEventsCount = vehicleEvents.length;
console.log('[checkHighTraffic] Found', vehicleEventsCount, 'matching events for vehicle', vehicle);
const warningEl = document.getElementById('highTrafficWarning');
if (!warningEl) {
console.error('[checkHighTraffic] Warning element not found in DOM');
return vehicleEventsCount;
}
if (vehicleEventsCount >= 10) { // Increased threshold for monthly check
const warningMessage = `Upozornění: Toto vozidlo má v tomto měsíci již ${vehicleEventsCount} rezervací.`;
console.log('[checkHighTraffic] Showing warning:', warningMessage);
warningEl.querySelector('.warning-message').textContent = warningMessage;
warningEl.classList.remove('hidden');
} else {
console.log('[checkHighTraffic] No traffic warning needed, count:', vehicleEventsCount);
warningEl.classList.add('hidden');
}
return vehicleEventsCount;
}
// Debounce function to prevent multiple rapid calls
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Debounced version of checkHighTraffic
const debouncedCheckHighTraffic = debounce(checkHighTraffic, 300);
// Add event listeners for high traffic checking
document.getElementById('vehicle').addEventListener('change', function() {
const startDate = document.getElementById('startDate').value;
if (startDate) {
debouncedCheckHighTraffic(this.value, new Date(startDate));
}
});
document.getElementById('startDate').addEventListener('change', function() {
const vehicle = document.getElementById('vehicle').value;
if (vehicle) {
debouncedCheckHighTraffic(vehicle, new Date(this.value));
}
});
// Update reservations list when calendar events change
calendar.on('eventAdd', updateReservationsList);
calendar.on('eventRemove', updateReservationsList);
calendar.on('eventChange', updateReservationsList);
// Initial update of reservations list
calendar.on('eventSourceSuccess', updateReservationsList);
// Event modal functions
function showEventModal(event) {
currentEventId = event.id;
// Fill modal with event details
document.getElementById('modalDriver').textContent = event.extendedProps.driverName;
document.getElementById('modalVehicle').textContent = event.extendedProps.vehicle;
document.getElementById('modalStart').textContent = formatDateTime(event.start);
document.getElementById('modalEnd').textContent = formatDateTime(event.end);
document.getElementById('modalPurpose').textContent = event.extendedProps.purpose || '(není uvedeno)';
// Check if this reservation belongs to the current user
const userReservations = JSON.parse(localStorage.getItem('userReservations') || '[]');
const deleteButton = document.getElementById('deleteReservation');
if (userReservations.includes(event.id)) {
deleteButton.classList.remove('hidden');
} else {
deleteButton.classList.add('hidden');
}
// Show modal
document.getElementById('eventModal').style.display = 'block';
}
// Add click handlers for modal
document.querySelector('.modal-close').addEventListener('click', function() {
document.getElementById('eventModal').style.display = 'none';
});
window.addEventListener('click', function(event) {
const modal = document.getElementById('eventModal');
if (event.target === modal) {
modal.style.display = 'none';
}
});
// Handle delete button click
document.getElementById('deleteReservation').addEventListener('click', async function() {
if (!currentEventId) return;
if (confirm('Opravdu chcete zrušit tuto rezervaci?')) {
try {
const response = await fetch(`/api/reservations/${currentEventId}`, {
method: 'DELETE'
});
if (!response.ok) {
throw new Error('Failed to delete reservation');
}
// Remove from localStorage
const userReservations = JSON.parse(localStorage.getItem('userReservations') || '[]');
const updatedReservations = userReservations.filter(id => id !== currentEventId);
localStorage.setItem('userReservations', JSON.stringify(updatedReservations));
// Remove from calendar
const event = calendar.getEventById(currentEventId);
if (event) {
event.remove();
}
// Close modal and show success message
document.getElementById('eventModal').style.display = 'none';
showMessage('Rezervace byla úspěšně zrušena', 'success');
// Update the reservations list
updateReservationsList();
} catch (error) {
console.error('Error deleting reservation:', error);
showMessage('Nepodařilo se zrušit rezervaci', 'error');
}
}
});
// Update form submission to track user's reservations
reservationForm.addEventListener('submit', async function(e) {
e.preventDefault();
// Get form values
const driverName = document.getElementById('driverName').value;
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;
const purpose = document.getElementById('purpose')?.value || '';
// Validate form
if (!validateDriverName(driverName)) {
showMessage('Prosím zadejte platné jméno a příjmení', 'error');
return;
}
try {
// Prepare reservation data
const reservationData = {
driverName: driverName,
vehicle: vehicle,
startDate: startDate,
startTime: startTime,
endDate: endDate,
endTime: endTime,
purpose: purpose
};
const response = await fetch('/api/reservations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(reservationData)
});
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
// Store reservation ID in localStorage
const userReservations = JSON.parse(localStorage.getItem('userReservations') || '[]');
userReservations.push(result.id);
localStorage.setItem('userReservations', JSON.stringify(userReservations));
// Add the new event to the calendar
calendar.addEvent({
id: result.id,
title: `${result.vehicle} - ${result.driverName}`,
start: `${result.startDate}T${result.startTime}`,
end: `${result.endDate}T${result.endTime}`,
extendedProps: {
driverName: result.driverName,
vehicle: result.vehicle,
purpose: result.purpose
}
});
// Reset form and close modal
reservationForm.reset();
document.getElementById('reservationModal').style.display = 'none';
showMessage('Rezervace byla úspěšně vytvořena', 'success');
} catch (error) {
console.error('Error:', error);
showMessage('Nepodařilo se vytvořit rezervaci', 'error');
}
});
// Make calendar events clickable
calendar.on('eventClick', function(info) {
showEventModal(info.event);
});
// Make reservation list items clickable
document.getElementById('reservationsList').addEventListener('click', function(e) {
const reservationItem = e.target.closest('.reservation-item');
if (reservationItem) {
const eventId = reservationItem.dataset.eventId;
const event = calendar.getEventById(eventId);
if (event) {
showEventModal(event);
}
}
});
});
</script>
</body>
</html>