import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Button } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Textarea } from "@/components/ui/textarea"; import { ComboboxMulti } from './ComboboxMulti'; import { cn } from "@/lib/utils"; import { format } from "date-fns"; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import { Badge } from "@/components/ui/badge"; import { CalendarIcon, Clock, AlertCircle } from "lucide-react"; import { MeetingRoom, Employee, RoomBookingCreateRequest, RoomBooking } from '@/types'; import { userSessionService, roomBookingService } from '@/lib/api'; interface BookingFormProps { room?: MeetingRoom; employees: Employee[]; onSuccess?: () => void; onCancel?: () => void; defaultValues?: { startTime?: Date; endTime?: Date; }; existingBooking?: RoomBooking; isEditing?: boolean; } const timeOptions = Array.from({ length: 24 * 4 }, (_, i) => { const hour = Math.floor(i / 4); const minute = (i % 4) * 15; return { value: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`, label: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`, }; }); const bookingFormSchema = z.object({ title: z.string().min(3, { message: "Title must be at least 3 characters.", }), date: z.date({ required_error: "Please select a date.", }), startTime: z.string({ required_error: "Please select a start time.", }), endTime: z.string({ required_error: "Please select an end time.", }), attendees: z.array(z.number()).min(1, { message: "Please select at least one attendee.", }), description: z.string().optional(), // Status field is not editable by the user, it will be set to 'pending' automatically }).refine((data) => { // Convert times to numbers for comparison const start = data.startTime.replace(':', ''); const end = data.endTime.replace(':', ''); return start < end; }, { message: "End time must be after start time", path: ["endTime"], }); type FormValues = z.infer; const BookingForm: React.FC = ({ room, employees, onSuccess, onCancel, defaultValues, existingBooking, isEditing }) => { const navigate = useNavigate(); const currentUser = userSessionService.getSession(); const [isSubmitting, setIsSubmitting] = useState(false); const form = useForm({ resolver: zodResolver(bookingFormSchema), defaultValues: { title: existingBooking?.title || '', date: existingBooking?.startTime ? new Date(existingBooking.startTime) : defaultValues?.startTime || new Date(), startTime: existingBooking?.startTime ? format(new Date(existingBooking.startTime), 'HH:mm') : defaultValues?.startTime ? format(defaultValues.startTime, 'HH:mm') : format(new Date(), 'HH:mm'), endTime: existingBooking?.endTime ? format(new Date(existingBooking.endTime), 'HH:mm') : defaultValues?.endTime ? format(defaultValues.endTime, 'HH:mm') : format(new Date(new Date().getTime() + 60 * 60 * 1000), 'HH:mm'), attendees: existingBooking?.attendees || currentUser ? [...(existingBooking?.attendees || []), currentUser?.employeeId] : [], description: existingBooking?.description || '', }, }); // Filter out the current user from attendees options and organize by name const attendeeOptions = React.useMemo(() => { // Make sure employees is an array const safeEmployees = Array.isArray(employees) ? employees : []; const attendees = safeEmployees .filter(emp => emp.id !== currentUser?.employeeId) .map(emp => ({ value: emp.id, label: `${emp.firstName} ${emp.lastName}`, })); // Sort alphabetically by label return attendees.sort((a, b) => a.label.localeCompare(b.label)); }, [employees, currentUser]); const onSubmit = async (data: FormValues) => { if (!room || !currentUser) { console.error('Missing room or user data:', { room, currentUser }); return; } setIsSubmitting(true); console.log('Submitting booking form with data:', data); try { // Combine date and time const startDate = new Date(data.date); const [startHours, startMinutes] = data.startTime.split(':').map(Number); startDate.setHours(startHours, startMinutes, 0, 0); const endDate = new Date(data.date); const [endHours, endMinutes] = data.endTime.split(':').map(Number); endDate.setHours(endHours, endMinutes, 0, 0); // Ensure attendees array is valid const attendees = Array.isArray(data.attendees) ? data.attendees : []; // Ensure current user is included if (!attendees.includes(currentUser.employeeId)) { attendees.push(currentUser.employeeId); } if (isEditing && existingBooking) { // Update existing booking const bookingData = { id: existingBooking.id, roomId: room.id, roomName: room.name, title: data.title, startTime: startDate.toISOString(), endTime: endDate.toISOString(), attendees: attendees, description: data.description || "", // Maintain the current status unless admin is changing it status: existingBooking.status, employeeId: currentUser.employeeId, employeeName: `${currentUser.firstName} ${currentUser.lastName}` }; console.log('Updating booking data:', bookingData); await roomBookingService.updateBooking(bookingData); console.log('Booking updated successfully'); alert(`Room "${room.name}" booking updated successfully!`); } else { // Create new booking const bookingData: RoomBookingCreateRequest = { roomId: room.id, roomName: room.name, employeeId: currentUser.employeeId, employeeName: `${currentUser.firstName} ${currentUser.lastName}`, title: data.title, startTime: startDate.toISOString(), endTime: endDate.toISOString(), attendees: attendees, description: data.description || "", status: 'pending', createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; console.log('Creating new booking with data:', bookingData); await roomBookingService.createBooking(bookingData); console.log('Booking created successfully'); alert(`Room "${room.name}" booked successfully!\n\nStatus: Pending Approval\n\nYour booking will be reviewed by an administrator.`); } // Use a timeout to ensure the alert has time to be displayed before navigation setTimeout(() => { if (onSuccess) { onSuccess(); } else { // Use relative navigation with hash to ensure page refresh const baseUrl = window.location.origin; const bookingsPath = "/meeting-room/bookings"; // Create a timestamp to force a page refresh const timestamp = new Date().getTime(); const fullUrl = `${baseUrl}${bookingsPath}#${timestamp}`; // Use full URL to navigate window.location.href = fullUrl; } }, 500); // Increased timeout to give more time for alert to be seen } catch (error) { console.error('Failed to process booking:', error); alert(`There was an error ${isEditing ? 'updating' : 'creating'} the booking. Please try again.`); } finally { setIsSubmitting(false); } }; return ( {isEditing ? 'Edit Booking' : 'Book a Meeting Room'} {room ? (
Room: {room.name} (Capacity: {room.capacity})
{!isEditing && (
Booking Status: Pending Approval
)} {isEditing && existingBooking && (
Current Status: {existingBooking.status || 'pending'}
)}
) : ( 'Complete the form to book a meeting room' )}
( Meeting Title )} />
( Date date < new Date(new Date().setHours(0, 0, 0, 0))} initialFocus /> )} />
( Start Time
)} /> ( End Time
)} />
{ // Make sure we have a valid array for the field value const fieldValue = Array.isArray(field.value) ? field.value : []; // Check if we have any attendee options const hasOptions = Array.isArray(attendeeOptions) && attendeeOptions.length > 0; // Get current user name for display const currentUserName = currentUser ? `${currentUser.firstName} ${currentUser.lastName}` : 'You'; return ( Meeting Attendees { try { // Ensure values is an array const safeValues = Array.isArray(values) ? values : []; // Ensure current user is always included if (currentUser && !safeValues.includes(currentUser.employeeId)) { const updatedValues = [currentUser.employeeId, ...safeValues]; field.onChange(updatedValues); } else { field.onChange(safeValues); } } catch (error) { console.error("Error updating attendees:", error); // Fallback to empty array if there's an error field.onChange(currentUser ? [currentUser.employeeId] : []); } }} placeholder={hasOptions ? "Select meeting attendees..." : "No other attendees available"} emptyText={hasOptions ? "No matching employees found" : "No other employees available"} label="Attendees" /> {currentUserName} will be automatically added as an attendee. {hasOptions ? 'Search for additional attendees by name.' : ''} ); }} /> ( Description (Optional)