diff --git a/src/app/admin/booking/components/TaskManager/BookingManager.tsx b/src/app/admin/booking/components/TaskManager/BookingManager.tsx index 7729d4b..d77dfd8 100644 --- a/src/app/admin/booking/components/TaskManager/BookingManager.tsx +++ b/src/app/admin/booking/components/TaskManager/BookingManager.tsx @@ -148,6 +148,10 @@ export function Content({ phoneNumber: booking.client?.phoneNumber ?? '', address: booking.client?.address ?? '', }, + // Add serviceFormValues to pass to ViewFormModal + serviceFormValues: booking.serviceFormValues || [], + // Add serviceId to help ViewFormModal find the correct service + serviceId: booking.serviceId, }; }); diff --git a/src/app/admin/booking/components/TaskManager/BookingModal.tsx b/src/app/admin/booking/components/TaskManager/BookingModal.tsx index f7edb71..e9cb954 100644 --- a/src/app/admin/booking/components/TaskManager/BookingModal.tsx +++ b/src/app/admin/booking/components/TaskManager/BookingModal.tsx @@ -26,6 +26,7 @@ import type { TaskStatus } from '@/features/service/serviceApi'; import { type Service } from '@/features/service/serviceApi'; import { useCreateServiceBookingMutation } from '@/features/service/serviceBookingApi'; import type { ServiceManagement } from '@/features/service-management/serviceManagementApi'; +import { useGetServiceFormFieldsQuery } from '@/features/service-management/serviceManagementApi'; import { useAppSelector } from '@/redux/hooks'; interface Props { onClose: () => void; @@ -264,9 +265,18 @@ const BookingModal: React.FC = ({ phoneNumber: '', address: '', }); + const [customFormValues, setCustomFormValues] = useState< + Record + >({}); const [createServiceBooking] = useCreateServiceBookingMutation(); const user = useAppSelector(state => state.auth.user); + // Get custom form fields for the selected service + const { data: customFormFields = [] } = useGetServiceFormFieldsQuery( + { serviceId: selectedServiceId }, + { skip: !selectedServiceId }, + ); + // Validate if selected datetime is in the past const isDateTimeInPast = (dateTimeString: string) => { if (!dateTimeString) return false; @@ -337,11 +347,20 @@ const BookingModal: React.FC = ({ name && status && datetime && - // If status is Done, time must be past; if status is not Done, do not allow past time - (status !== 'Done' || isDateTimeInPast(datetime)) && + // If status is Done or Cancelled, time must be past; if status is not Done/Cancelled, do not allow past time + (status === 'Done' || + status === 'Cancelled' || + !isDateTimeInPast(datetime)) && client.name && client.phoneNumber && - client.address; + client.address && + // Check if all required custom form fields are filled + customFormFields.every( + field => + !field.isRequired || + (customFormValues[field._id!] && + customFormValues[field._id!].trim() !== ''), + ); // New: Find corresponding service _id based on selected service name const handleServiceNameChange = (serviceName: string) => { setName(serviceName); @@ -349,6 +368,16 @@ const BookingModal: React.FC = ({ s => s.name === serviceName, ); setSelectedServiceId(selectedService?._id ?? ''); + // Reset custom form values when service changes + setCustomFormValues({}); + }; + + // Handle custom form field value changes + const handleCustomFormFieldChange = (fieldId: string, value: string) => { + setCustomFormValues(prev => ({ + ...prev, + [fieldId]: value, + })); }; // Utility function: Map frontend status to backend booking status @@ -408,13 +437,32 @@ const BookingModal: React.FC = ({ return; } - if (isDateTimeInPast(datetime) && status !== 'Done') { + if ( + isDateTimeInPast(datetime) && + status !== 'Done' && + status !== 'Cancelled' + ) { alert( - 'You cannot book a service for a past date and time unless the status is Done.', + 'You cannot book a service for a past date and time unless the status is Done or Cancelled.', ); return; } - // Remove popup alerts, use form validation to control button state instead + + // Build serviceFormValues with custom form fields + const formValues = [ + // Include the service name as a basic field + { serviceFieldId: 'service_name', answer: name }, + ]; + + // Add custom form field values + customFormFields.forEach(field => { + if (customFormValues[field._id!]) { + formValues.push({ + serviceFieldId: field._id!, + answer: customFormValues[field._id!], + }); + } + }); await createServiceBooking({ serviceId: selectedServiceId, @@ -423,7 +471,7 @@ const BookingModal: React.FC = ({ phoneNumber: client.phoneNumber, address: client.address, }, - serviceFormValues: [{ serviceFieldId: 'dummy', answer: name }], + serviceFormValues: formValues, bookingTime, status: bookingStatus, note: description, @@ -472,9 +520,8 @@ const BookingModal: React.FC = ({ ) => - handleServiceNameChange(e.target.value as string) // Modified: Use new handler function + onChange={(e: SelectChangeEvent) => + handleServiceNameChange(e.target.value as string) } displayEmpty renderValue={selected => { @@ -501,6 +548,164 @@ const BookingModal: React.FC = ({ + {/* Custom Form Fields */} + {customFormFields.length > 0 && ( + <> + {customFormFields.map(field => ( + + + {field.fieldName} + {field.isRequired && ( + * + )} + + {field.fieldType === 'short-answer' && ( + ) => + handleCustomFormFieldChange(field._id!, e.target.value) + } + variant="outlined" + required={field.isRequired} + /> + )} + {field.fieldType === 'paragraph' && ( + ) => + handleCustomFormFieldChange(field._id!, e.target.value) + } + required={field.isRequired} + /> + )} + {field.fieldType === 'dropdown-list' && field.options && ( + ) => + handleCustomFormFieldChange( + field._id!, + e.target.value as string, + ) + } + displayEmpty + required={field.isRequired} + > + + Please Select + + {field.options.map(option => ( + + {option} + + ))} + + )} + {field.fieldType === 'single-choice' && field.options && ( + + {field.options.map(option => ( + + + handleCustomFormFieldChange( + field._id!, + e.target.value, + ) + } + required={field.isRequired} + /> + + + ))} + + )} + {field.fieldType === 'multiple-choice' && field.options && ( + + {field.options.map(option => ( + + { + const currentValues = + customFormValues[field._id!] || ''; + if (!currentValues) return false; + const values = currentValues + .split(',') + .map(v => v.trim()) + .filter(v => v); + return values.includes(option); + })()} + onChange={e => { + const currentValues = + customFormValues[field._id!] || ''; + const values = currentValues + ? currentValues.split(',').filter(v => v.trim()) + : []; + if (e.target.checked) { + values.push(option); + } else { + const index = values.indexOf(option); + if (index > -1) values.splice(index, 1); + } + handleCustomFormFieldChange( + field._id!, + values.join(', '), + ); + }} + /> + + + ))} + + )} + {field.fieldType === 'date' && ( + ) => + handleCustomFormFieldChange(field._id!, e.target.value) + } + InputLabelProps={{ shrink: true }} + required={field.isRequired} + /> + )} + {field.fieldType === 'time' && ( + ) => + handleCustomFormFieldChange(field._id!, e.target.value) + } + InputLabelProps={{ shrink: true }} + required={field.isRequired} + /> + )} + + ))} + + )} + {/* New client information input fields */} Client Name @@ -593,14 +798,24 @@ const BookingModal: React.FC = ({ } InputLabelProps={{ shrink: true }} inputProps={{ - min: status === 'Done' ? undefined : getCurrentDateTimeLocal(), + min: + status === 'Done' || status === 'Cancelled' + ? undefined + : getCurrentDateTimeLocal(), }} - error={isDateTimeInPast(datetime) && status !== 'Done'} + error={ + isDateTimeInPast(datetime) && + status !== 'Done' && + status !== 'Cancelled' + } helperText={ - isDateTimeInPast(datetime) && status !== 'Done' + isDateTimeInPast(datetime) && + status !== 'Done' && + status !== 'Cancelled' ? 'Cannot create booking for past time' - : status === 'Done' && isDateTimeInPast(datetime) - ? 'Recording completion time (past time allowed)' + : (status === 'Done' || status === 'Cancelled') && + isDateTimeInPast(datetime) + ? 'Recording completion time or cancelled time (past time allowed)' : '' } /> diff --git a/src/app/admin/booking/components/TaskManager/ViewFormModal.tsx b/src/app/admin/booking/components/TaskManager/ViewFormModal.tsx index a6aeab9..0ab0c05 100644 --- a/src/app/admin/booking/components/TaskManager/ViewFormModal.tsx +++ b/src/app/admin/booking/components/TaskManager/ViewFormModal.tsx @@ -15,6 +15,8 @@ import { import React from 'react'; import type { Service } from '@/features/service/serviceApi'; +import type { ServiceFormField } from '@/features/service-management/serviceManagementApi'; +import { useGetServiceFormFieldsQuery } from '@/features/service-management/serviceManagementApi'; interface Props { service: Service; @@ -175,6 +177,12 @@ const ViewFormModal: React.FC = ({ service, onClose }) => { const theme = useTheme(); useMediaQuery(theme.breakpoints.down('sm')); + // Get custom form fields for the service + const { data: customFormFields = [] } = useGetServiceFormFieldsQuery( + { serviceId: service.serviceId ?? service._id ?? '' }, + { skip: !(service.serviceId ?? service._id) }, + ); + const formatDateTime = (datetime?: string) => { if (!datetime) return 'No data'; const date = new Date(datetime); @@ -294,6 +302,51 @@ const ViewFormModal: React.FC = ({ service, onClose }) => { )} + {/* Custom Form Fields with Values */} + {customFormFields.length > 0 && ( + <> + + Custom Form Fields: + {customFormFields.map(field => { + // Find the corresponding form value from the service + const formValue = service.serviceFormValues?.find( + value => value.serviceFieldId === field._id, + ); + + return ( + + + {field.fieldName} + {field.isRequired && ( + * + )} + + + {formValue ? ( + + {formValue.answer} + + ) : ( + + Not filled + + )} + + + ); + })} + + + )} + Created By: diff --git a/src/features/service/serviceApi.ts b/src/features/service/serviceApi.ts index c53d8aa..cb81d3e 100644 --- a/src/features/service/serviceApi.ts +++ b/src/features/service/serviceApi.ts @@ -40,6 +40,10 @@ export interface Service { phoneNumber: string; address: string; }; + // Add: service form values for custom form fields + serviceFormValues?: { serviceFieldId: string; answer: string }[]; + // Add: service ID for finding the correct service + serviceId?: string; } // Pagination response wrapper