This commit is contained in:
Tomas Dvorak
2025-06-11 20:07:29 +02:00
parent 2915d4c68c
commit 6808347981
2 changed files with 120 additions and 40 deletions
+83 -16
View File
@@ -311,8 +311,36 @@ func handleGetReservations(w http.ResponseWriter, r *http.Request) {
return return
} }
// Convert reservations to calendar events
type Event struct {
ID string `json:"id"`
Title string `json:"title"`
Start string `json:"start"`
End string `json:"end"`
DriverName string `json:"driverName"`
Vehicle string `json:"vehicle"`
Purpose string `json:"purpose"`
}
var events []Event
for _, res := range reservations {
// Create proper ISO datetime strings
start := fmt.Sprintf("%sT%s:00", res.StartDate, res.StartTime)
end := fmt.Sprintf("%sT%s:00", res.EndDate, res.EndTime)
events = append(events, Event{
ID: res.ID,
Title: fmt.Sprintf("%s - %s", res.Vehicle, res.DriverName),
Start: start,
End: end,
DriverName: res.DriverName,
Vehicle: res.Vehicle,
Purpose: res.Purpose,
})
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(reservations) json.NewEncoder(w).Encode(events)
} }
func handleCreateReservation(w http.ResponseWriter, r *http.Request) { func handleCreateReservation(w http.ResponseWriter, r *http.Request) {
@@ -322,6 +350,9 @@ func handleCreateReservation(w http.ResponseWriter, r *http.Request) {
return return
} }
// Log received data for debugging
log.Printf("Received reservation data: %+v", reservation)
// Validate required fields // Validate required fields
if reservation.DriverName == "" || reservation.Vehicle == "" || if reservation.DriverName == "" || reservation.Vehicle == "" ||
reservation.StartDate == "" || reservation.StartTime == "" || reservation.StartDate == "" || reservation.StartTime == "" ||
@@ -330,18 +361,20 @@ func handleCreateReservation(w http.ResponseWriter, r *http.Request) {
return return
} }
// Create combined date-time string // Create combined date-time string for validation
startDateTime, err := time.Parse("2006-01-02 15:04", startDateTime, err := time.Parse("2006-01-02 15:04",
fmt.Sprintf("%s %s", reservation.StartDate, reservation.StartTime)) fmt.Sprintf("%s %s", reservation.StartDate, reservation.StartTime))
if err != nil { if err != nil {
http.Error(w, "Invalid start date/time", http.StatusBadRequest) log.Printf("Error parsing start date/time: %v", err)
http.Error(w, "Invalid start date/time format", http.StatusBadRequest)
return return
} }
endDateTime, err := time.Parse("2006-01-02 15:04", endDateTime, err := time.Parse("2006-01-02 15:04",
fmt.Sprintf("%s %s", reservation.EndDate, reservation.EndTime)) fmt.Sprintf("%s %s", reservation.EndDate, reservation.EndTime))
if err != nil { if err != nil {
http.Error(w, "Invalid end date/time", http.StatusBadRequest) log.Printf("Error parsing end date/time: %v", err)
http.Error(w, "Invalid end date/time format", http.StatusBadRequest)
return return
} }
@@ -351,6 +384,17 @@ func handleCreateReservation(w http.ResponseWriter, r *http.Request) {
return return
} }
// Check availability
available, err := checkReservationAvailability(reservation.Vehicle, startDateTime, endDateTime)
if err != nil {
http.Error(w, "Failed to check availability", http.StatusInternalServerError)
return
}
if !available {
http.Error(w, "Selected time slot is not available", http.StatusConflict)
return
}
// Generate unique ID // Generate unique ID
reservation.ID = fmt.Sprintf("res_%d", time.Now().UnixNano()) reservation.ID = fmt.Sprintf("res_%d", time.Now().UnixNano())
@@ -368,10 +412,40 @@ func handleCreateReservation(w http.ResponseWriter, r *http.Request) {
return return
} }
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated) w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(reservation) json.NewEncoder(w).Encode(reservation)
} }
// Add helper function to check availability
func checkReservationAvailability(vehicle string, start, end time.Time) (bool, error) {
reservations, err := loadReservations()
if err != nil {
return false, err
}
for _, res := range reservations {
resStart, err := time.Parse("2006-01-02 15:04",
fmt.Sprintf("%s %s", res.StartDate, res.StartTime))
if err != nil {
continue
}
resEnd, err := time.Parse("2006-01-02 15:04",
fmt.Sprintf("%s %s", res.EndDate, res.EndTime))
if err != nil {
continue
}
if res.Vehicle == vehicle &&
!(end.Before(resStart) || start.After(resEnd)) {
return false, nil
}
}
return true, nil
}
func handleCheckAvailability(w http.ResponseWriter, r *http.Request) { func handleCheckAvailability(w http.ResponseWriter, r *http.Request) {
// Get query parameters // Get query parameters
vehicle := r.URL.Query().Get("vehicle") vehicle := r.URL.Query().Get("vehicle")
@@ -386,7 +460,7 @@ func handleCheckAvailability(w http.ResponseWriter, r *http.Request) {
return return
} }
// Parse dates in correct format (YYYY-MM-DD HH:MM) // Parse the dates with specific format
startDateTime, err := time.Parse("2006-01-02 15:04", startDateTime, err := time.Parse("2006-01-02 15:04",
fmt.Sprintf("%s %s", startDate, startTime)) fmt.Sprintf("%s %s", startDate, startTime))
if err != nil { if err != nil {
@@ -401,23 +475,17 @@ func handleCheckAvailability(w http.ResponseWriter, r *http.Request) {
return return
} }
// Validate time order // Load existing reservations
if endDateTime.Before(startDateTime) {
http.Error(w, "End time must be after start time", http.StatusBadRequest)
return
}
// Check availability
reservations, err := loadReservations() reservations, err := loadReservations()
if err != nil { if err != nil {
http.Error(w, "Failed to load reservations", http.StatusInternalServerError) http.Error(w, "Failed to load reservations", http.StatusInternalServerError)
return return
} }
// Check for conflicts
available := true available := true
for _, res := range reservations { for _, res := range reservations {
if res.Vehicle == vehicle { if res.Vehicle == vehicle {
// Parse reservation dates
resStart, err := time.Parse("2006-01-02 15:04", resStart, err := time.Parse("2006-01-02 15:04",
fmt.Sprintf("%s %s", res.StartDate, res.StartTime)) fmt.Sprintf("%s %s", res.StartDate, res.StartTime))
if err != nil { if err != nil {
@@ -430,15 +498,14 @@ func handleCheckAvailability(w http.ResponseWriter, r *http.Request) {
continue continue
} }
// Check for overlap // Check if there is any overlap
if startDateTime.Before(resEnd) && endDateTime.After(resStart) { if !(endDateTime.Before(resStart) || startDateTime.After(resEnd)) {
available = false available = false
break break
} }
} }
} }
// Return response
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]bool{"available": available}) json.NewEncoder(w).Encode(map[string]bool{"available": available})
} }
+37 -24
View File
@@ -454,23 +454,36 @@
locale: 'cs', locale: 'cs',
slotMinTime: '06:00:00', slotMinTime: '06:00:00',
slotMaxTime: '22:00:00', slotMaxTime: '22:00:00',
slotDuration: '01:00:00', // Set slot duration to 1 hour slotDuration: '01:00:00',
snapDuration: '01:00:00', // Snap to hour intervals snapDuration: '01:00:00',
allDaySlot: false, allDaySlot: false,
businessHours: { events: '/api/reservations',
daysOfWeek: [1, 2, 3, 4, 5], eventDisplay: 'block',
eventTimeFormat: {
hour: '2-digit',
minute: '2-digit',
hour12: false
},
eventContent: function(arg) {
return {
html: `
<div class="event-content">
<div class="font-bold">${arg.event.extendedProps.driverName || 'Rezervace'}</div>
<div>${arg.event.extendedProps.purpose || ''}</div>
</div>
`
};
},
// Add these properties to ensure proper event rendering
eventConstraint: {
startTime: '06:00', startTime: '06:00',
endTime: '22:00', endTime: '22:00',
dows: [0,1,2,3,4,5,6]
}, },
events: '/api/reservations', selectConstraint: {
eventClick: function(info) { startTime: '06:00',
alert(` endTime: '22:00',
Řidič: ${info.event.title} dows: [0,1,2,3,4,5,6]
Vozidlo: ${info.event.extendedProps.vehicle}
Účel: ${info.event.extendedProps.purpose}
Od: ${info.event.start.toLocaleString()}
Do: ${info.event.end.toLocaleString()}
`);
} }
}); });
@@ -479,17 +492,16 @@
// Check vehicle availability // Check vehicle availability
async function checkVehicleAvailability(vehicle, startDate, endDate) { async function checkVehicleAvailability(vehicle, startDate, endDate) {
try { try {
// Format dates correctly // Format dates for API
const formatDateTime = (date) => { const startDateTime = new Date(startDate);
return date.toISOString().split('.')[0]; // Remove milliseconds const endDateTime = new Date(endDate);
};
const params = new URLSearchParams({ const params = new URLSearchParams({
vehicle: vehicle, vehicle: vehicle,
startDate: startDate.toISOString().split('T')[0], startDate: startDateTime.toISOString().split('T')[0],
startTime: startDate.toTimeString().split(' ')[0].slice(0, 5), startTime: startDateTime.toTimeString().split(' ')[0].slice(0, 5),
endDate: endDate.toISOString().split('T')[0], endDate: endDateTime.toISOString().split('T')[0],
endTime: endDate.toTimeString().split(' ')[0].slice(0, 5) endTime: endDateTime.toTimeString().split(' ')[0].slice(0, 5)
}); });
const response = await fetch(`/api/check-availability?${params}`); const response = await fetch(`/api/check-availability?${params}`);
@@ -585,15 +597,16 @@
}); });
if (!response.ok) { if (!response.ok) {
throw new Error('Chyba při vytváření rezervace'); const errorData = await response.json();
throw new Error(errorData.message || 'Failed to create reservation');
} }
showMessage('Rezervace byla úspěšně vytvořena', 'success'); showMessage('Rezervace byla úspěšně vytvořena', 'success');
calendar.refetchEvents();
reservationForm.reset(); reservationForm.reset();
calendar.refetchEvents();
} catch (error) { } catch (error) {
console.error('Error:', error); console.error('Error:', error);
showMessage(error.message || 'Nepodařilo se vytvořit rezervaci', 'error'); showMessage(error.message || 'Chyba při vytváření rezervace', 'error');
} }
}); });