/** * EditAppointmentModal Component * * Modal for editing existing appointments, including participant management. * Extracted from OwnerScheduler for reusability and enhanced with participant selector. */ import React, { useState, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { X, User as UserIcon, Mail, Phone } from 'lucide-react'; import { createPortal } from 'react-dom'; import { Appointment, AppointmentStatus, Resource, Service, ParticipantInput } from '../types'; import { ParticipantSelector } from './ParticipantSelector'; interface EditAppointmentModalProps { appointment: Appointment & { customerEmail?: string; customerPhone?: string; }; resources: Resource[]; services: Service[]; onSave: (updates: { startTime?: Date; resourceId?: string | null; durationMinutes?: number; status?: AppointmentStatus; notes?: string; participantsInput?: ParticipantInput[]; }) => void; onClose: () => void; isSaving?: boolean; } export const EditAppointmentModal: React.FC = ({ appointment, resources, services, onSave, onClose, isSaving = false, }) => { const { t } = useTranslation(); // Form state const [editDateTime, setEditDateTime] = useState(''); const [editResource, setEditResource] = useState(''); const [editDuration, setEditDuration] = useState(15); const [editStatus, setEditStatus] = useState('SCHEDULED'); const [editNotes, setEditNotes] = useState(''); const [participants, setParticipants] = useState([]); // Initialize form state from appointment useEffect(() => { if (appointment) { // Convert Date to datetime-local format const startTime = appointment.startTime; const localDateTime = new Date(startTime.getTime() - startTime.getTimezoneOffset() * 60000) .toISOString() .slice(0, 16); setEditDateTime(localDateTime); setEditResource(appointment.resourceId || ''); setEditDuration(appointment.durationMinutes || 15); setEditStatus(appointment.status || 'SCHEDULED'); setEditNotes(appointment.notes || ''); // Initialize participants from existing appointment participants if (appointment.participants) { const existingParticipants: ParticipantInput[] = appointment.participants.map(p => ({ role: p.role, userId: p.userId ? parseInt(p.userId) : undefined, resourceId: p.resourceId ? parseInt(p.resourceId) : undefined, externalEmail: p.externalEmail, externalName: p.externalName, })); setParticipants(existingParticipants); } } }, [appointment]); // Get service name const serviceName = useMemo(() => { const service = services.find(s => s.id === appointment.serviceId); return service?.name || 'Unknown Service'; }, [services, appointment.serviceId]); // Check if appointment is unassigned (pending) const isUnassigned = !appointment.resourceId; // Handle save const handleSave = () => { const startTime = new Date(editDateTime); onSave({ startTime, resourceId: editResource || null, durationMinutes: editDuration, status: editStatus, notes: editNotes, participantsInput: participants, }); }; // Validation const canSave = useMemo(() => { if (isUnassigned) { // For unassigned appointments, require resource and valid duration return editResource && editDuration >= 15; } return true; }, [isUnassigned, editResource, editDuration]); const modalContent = (
e.stopPropagation()} > {/* Header */}

{isUnassigned ? t('scheduler.scheduleAppointment') : t('scheduler.editAppointment')}

{/* Scrollable content */}
{/* Customer Info */}

{t('customers.title', 'Customer')}

{appointment.customerName}

{appointment.customerEmail && (
{appointment.customerEmail}
)} {appointment.customerPhone && (
{appointment.customerPhone}
)}
{/* Service & Status */}

{t('services.title', 'Service')}

{serviceName}

{/* Editable Fields */}

{t('scheduler.scheduleDetails', 'Schedule Details')}

{/* Date & Time Picker */}
setEditDateTime(e.target.value)} className="w-full px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-brand-500" />
{/* Resource Selector */}
{/* Duration Input */}
{ const value = parseInt(e.target.value); setEditDuration(value >= 15 ? value : 15); }} className="w-full px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-sm text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-brand-500" />
{/* Participants Section */}
{/* Notes */}