📅 Therapist Appointments
This document covers the comprehensive appointment management system for therapists in the Com DEALL mobile application, including appointment creation, status management, filtering, and detailed appointment views.
🎯 Overview
The therapist appointment system enables therapists to view appointments in different states (pending, upcoming, completed), create new appointments for assigned children, and manage appointment details. The system supports both One-on-One and Team Meeting appointment types with specific validation rules.
🏗️ Architecture
Appointment Management Components
Appointment Management System
├── Appointment Tabs (Pending, Upcoming, Completed)
├── Appointment Filtering (Date/Time Range)
├── Create Appointment Flow (2-Step Process)
├── Appointment Details View
├── Appointment Actions (Cancel, Reschedule)
└── Status Management
Navigation Flow
Appointments Tab → View Appointments → Create Appointment → Select Child → Set Date/Time → Confirm
📱 Mobile Implementation
Appointment Tabs Overview
Therapist can navigate to the appointments tab to view appointments in different states:
- Pending: Appointments awaiting approval/payment
- Upcoming: Confirmed appointments with session details
- Completed: Past appointments with session notes
Appointment Filtering Logic:
// Date and time range filtering for appointments
const [dateRangeData, setDateRangeData] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});
const handleFilterChange = (filterData: TimeFilterRange) => {
setDateRangeData(filterData);
// Apply filter to appointment queries
refetch({
variables: {
StartTime: {
_gte: filterData.startTime || dayjs().startOf('day').toISOString(),
},
EndTime: filterData.endTime
? { _lte: filterData.endTime }
: {},
},
});
};
📋 Create Appointment Flow
Step 1: Child Selection (CreateAppointment.tsx)
Therapist can create appointments by pressing the + icon, which initiates a 2-step process. The first step involves selecting a child from assigned children.
Child Selection Logic:
// CreateAppointment.tsx - Main appointment creation flow
const CreateAppointment = ({ navigation, route }) => {
const isReschedule = route?.params?.isReschedule;
const [step, setStep] = useState<number>(isReschedule ? 2 : 1);
const [childId, setChildId] = useState(route?.params?.childId || '');
const { therapistId } = useSelector(selectUserData);
// Fetch children assigned to therapist
const { loading, error, data, refetch } = useGetChildrenForTherapistQuery({
fetchPolicy: 'cache-and-network',
variables: { therapist_id: therapistId },
});
const handleSelectChild = (id: string) => {
setActiveChildId(id);
setStep(step + 1);
};
const renderStep = () => {
if (data) {
switch (step) {
case 1:
return (
<SelectChild setChildId={handleSelectChild} childrenData={data} />
);
case 2:
return (
<SetDateTime
childrenData={data}
handleCancel={handleCancel}
isReschedule={isReschedule ?? false}
appointmentId={route?.params?.appointmentId}
/>
);
}
}
};
};
Child Search and Selection:
// SelectChild.tsx - Child selection with search functionality
const SelectChild = ({ childrenData, setChildId }) => {
const [value, setValue] = useState('');
// Filter children based on search input
const filteredChildren = childrenData?.child_therapist?.filter(({ child }) =>
child?.name.toLowerCase().includes(value.toLowerCase()),
);
const handleChildSelection = (childId: string) => {
setChildId(childId);
};
};
Step 2: Set Date and Time (SetDateTime.tsx)
After selecting a child, therapist proceeds to step 2 where they can configure appointment details including type, date, time, and meeting link.
Appointment Type Selection Logic:
When Team Meeting Option is Available:
- Team meeting option is only available when the selected child has 2 or more therapists assigned to them
- The system automatically checks the number of therapists assigned to the child
- If less than 2 therapists are assigned, the team meeting option is automatically disabled
- The system will auto-switch from Team Meeting to One-on-One if the therapist count drops below 2
Team Meeting Requirements:
- Child must have multiple therapists in their care team
- All selected therapists must have compatible schedules
- Common time slots must be available for all participating therapists
- The primary therapist (creator) is automatically included in the team meeting
One-on-One vs Team Meeting Logic:
- One-on-One: Default option, always available for any child
- Team Meeting: Only available when child has 2+ therapists assigned
- System validates therapist count before allowing team meeting selection
- Auto-fallback mechanism prevents invalid team meeting creation
// SetDateTime.tsx - Appointment configuration with team meeting validation
const SetDateTime = ({ childrenData, handleCancel, isReschedule = false, appointmentId }) => {
const [appointmentType, setAppointmentType] = useState<AppointmentType>(
AppointmentType.OneOnOne,
);
const [startTime, setStartTime] = useState<Date | string>('');
const [endTime, setEndTime] = useState<Date | string>('');
const [selectedDates, setSelectDates] = useState<string[]>([]);
const [meetingLink, setMeetingLink] = useState<string>('');
const [meetingLinkError, setMeetingLinkError] = useState<string>('');
const isOneOnOne = appointmentType === AppointmentType.OneOnOne;
const isTeamMeeting = appointmentType === AppointmentType.TeamMeeting;
// Fetch therapists for child (for team meetings)
const { data: therapistsData } = useGetTherapistsForChildQuery({
variables: { child_id: activeChildId },
fetchPolicy: 'cache-and-network',
});
// Auto-switch to One-on-One if less than 2 therapists assigned
useEffect(() => {
if (
therapistsData &&
therapistsData?.child_therapist?.length < 2 &&
isTeamMeeting
) {
setAppointmentType(AppointmentType.OneOnOne);
}
}, [therapistsData, isTeamMeeting]);
};
Meeting Link Validation Logic:
Meeting Link Requirements:
- Must be a valid Google Meet or Zoom meeting link
- Link validation happens in real-time as user types
- Invalid links show error message immediately
- Only valid meeting links allow appointment creation
- Link format validation ensures proper meeting platform compatibility
Validation Process:
- User enters meeting link in the input field
- System trims whitespace and validates format
- Real-time validation provides immediate feedback
- Error state prevents appointment submission
- Success state enables appointment creation
// Meeting link validation logic with real-time feedback
const handleMeetingLinkChange = (link: string) => {
setMeetingLink(link.trim());
const isMeetingLinkValid = isValidMeetLink(link);
if (!isMeetingLinkValid) {
setMeetingLinkError(t('meetLinkError'));
} else {
setMeetingLinkError('');
}
};
Calendar View with Schedule Dots Logic:
Schedule Integration Process:
- Calendar displays dates with orange dots indicating available therapist schedules
- For One-on-One appointments: Shows only the current therapist's schedule
- For Team Meetings: Shows common available slots for all assigned therapists
- Dates without dots are disabled and cannot be selected
- System fetches schedules only when therapist data is available
Schedule Fetching Logic:
- One-on-One: Fetches schedule for single therapist (current user)
- Team Meeting: Fetches schedules for all therapists assigned to the child
- Schedules are fetched lazily to optimize performance
- Date selection is cleared when switching between appointment types
- Common time slots are calculated for team meetings
Visual Indicators:
- Orange dots on calendar dates indicate available schedule slots
- Disabled dates (no dots) cannot be selected for appointments
- Selected dates are highlighted for user confirmation
- Time slot availability is validated in real-time
// Calendar integration with therapist schedules and visual indicators
const [fetchSchedules, { data: schedulesData }] = useGetSchedulesForAppointmentLazyQuery({
variables: {
therapist_ids: isOneOnOne
? [therapistId]
: therapistsData?.child_therapist?.map(({ therapist }) => therapist?.id),
date: format(today, 'YYYY-MM-DD'),
},
fetchPolicy: 'cache-and-network',
});
// Fetch schedules when therapists data is available
useEffect(() => {
if (therapistsData) {
setSelectDates([]);
fetchSchedules();
}
}, [therapistsData]);
Appointment Creation Logic:
Appointment Creation Process:
- System validates all required fields before submission
- Creates appointment with selected child, date, time, and meeting link
- For Team Meetings: Includes all selected therapists in the appointment
- For One-on-One: Only includes the current therapist
- Success triggers modal confirmation and navigation
Payload Construction:
- Child ID: Selected child from step 1
- Meeting Link: Validated Google Meet/Zoom link
- Appointment Type: One-on-One or Team Meeting
- Time Slots: Selected dates with start and end times
- Team Therapists: Additional therapists for team meetings (if applicable)
Success Handling:
- Shows success modal with confirmation message
- Refreshes appointment list to show new appointment
- Navigates back to appointments tab
- Displays appointment in "Pending" status awaiting parent payment
// Create appointment mutation with comprehensive payload construction
const [createAppointment, { loading: createAppointmentLoading }] = useCreateAppointmentMutation({
errorPolicy: 'all',
onCompleted(data) {
if (data?.createAppointment?.success) {
setModalVisible(true);
}
},
onError: e => {
toast.show({ text: e.message });
},
refetchQueries: ['GetAppointmentList'],
});
const handleCreateAppointment = () => {
const payload: CreateAppointmentMutationVariables = {
child_id: activeChildId!,
meet_link: meetingLink,
appointment_type: appointmentType,
slots: selectedDates.map(
(date): AppointmentSlot => ({
startTimeStamp: formatTimeSlot(startTime, date),
endTimeStamp: formatTimeSlot(endTime, date),
}),
),
};
if (appointmentType === AppointmentType.TeamMeeting) {
payload.other_therapists = confirmedTherapists?.map(t => t.id);
}
createAppointment({
variables: payload,
});
};
Validation Logic:
Comprehensive Validation Process:
- System validates all required fields before allowing appointment creation
- Different validation rules apply for new appointments vs rescheduling
- Real-time validation provides immediate feedback to users
- Button state changes based on validation results
Validation Rules:
- Date Selection: At least one date must be selected
- Time Selection: Both start and end times must be provided
- Meeting Link: Valid meeting link required for new appointments
- Team Meeting: Minimum 2 therapists required for team meetings
- Loading State: Prevents multiple submissions during processing
Reschedule vs New Appointment:
- Reschedule: Only requires date and time selection
- New Appointment: Requires all fields including meeting link
- Team Meeting: Additional validation for therapist count
- Error States: Prevents submission with invalid data
// Comprehensive appointment validation with different rules for reschedule vs new
const isConfirmCtaDisabled = () => {
if (isReschedule) {
return selectedDates?.length < 1 || !startTime || !endTime;
}
return (
selectedDates?.length < 1 ||
!startTime ||
!endTime ||
!meetingLink ||
!!meetingLinkError ||
createAppointmentLoading ||
(isTeamMeeting && confirmedTherapists.length < 2)
);
};
📋 Appointment Details View
Appointment Details Screen (AppointmentDetails.tsx)
When therapist taps on any appointment card, they can view comprehensive appointment details including child information, billing details, and available actions.
Appointment Details Navigation Logic:
Appointment Details Access:
- Therapist can tap on any appointment card from any tab (Pending, Upcoming, Completed)
- System fetches comprehensive appointment data including child, therapist, and payment information
- Real-time data fetching ensures up-to-date appointment status
- Error handling provides fallback options for failed data loads
Data Fetching Process:
- Fetches appointment details using appointment ID from navigation params
- Includes child information, therapist details, and payment status
- Caches data for offline access and performance optimization
- Refreshes data when screen comes into focus
Status Determination Logic:
- Upcoming: Confirmed or Started status - active appointments
- Pending: Scheduled status - awaiting payment/confirmation
- Past: Completed, Expired, or Cancelled - historical appointments
- Cancelled: Specific cancelled status with reason display
- Confirmed: Active confirmed appointments with session access
// AppointmentDetails.tsx - Comprehensive appointment details with status logic
const AppointmentDetails = ({ route, navigation }) => {
const [meetLink, setMeetLink] = useState('');
const [isMeetLink, setIsMeetLink] = useState(false);
const { data, refetch, loading, error } = useGetAppointmentDetailsQuery({
variables: { id: route.params?.appointmentId },
onError: e => {
e?.message && toast.show({ text: e?.message });
},
fetchPolicy: 'cache-and-network',
});
const appointmentDetails = data?.appointment?.[0];
const appointmentStatus = appointmentDetails?.status;
// Determine appointment state with comprehensive status logic
const isUpComing = appointmentStatus === Appointment_Status_Enum.Confirmed ||
appointmentStatus === Appointment_Status_Enum.Started;
const isPending = appointmentStatus === Appointment_Status_Enum.Scheduled;
const isPast = appointmentStatus === Appointment_Status_Enum.Completed ||
appointmentStatus === Appointment_Status_Enum.Expired ||
appointmentStatus === Appointment_Status_Enum.Cancelled;
const isCancelled = appointmentStatus === Appointment_Status_Enum.Cancelled;
const isConfirmed = appointmentStatus === Appointment_Status_Enum.Confirmed;
};
Payment Status Display Logic:
Payment Status Indicators:
- Pending Appointments: Shows payment expiry countdown in hours or minutes
- Expired Appointments: Displays "Payment Link Expired" message
- Paid Appointments: Shows "Paid" status for confirmed appointments
- Real-time Updates: Payment status updates automatically as time progresses
Expiry Calculation Process:
- Calculates time difference between current time and payment expiry
- Shows hours if more than 1 hour remaining
- Shows minutes if less than 1 hour remaining
- Updates display text based on time remaining
- Handles edge cases for expired payments
Visual Status Display:
- Color-coded status indicators for different payment states
- Clear messaging for payment requirements
- Time-sensitive information for urgent payments
- Status badges for quick visual identification
// Payment status calculation with real-time expiry logic
let paymentTagText = '';
if (appointmentDetails?.status === Appointment_Status_Enum.Scheduled) {
let hourDiff = dayjs(appointmentDetails?.payment_expiry).diff(
dayjs(),
'hour',
);
if (hourDiff < 1) {
hourDiff = dayjs(appointmentDetails?.payment_expiry).diff(
dayjs(),
'minutes',
);
paymentTagText = tA('paymentLinkExpiresIn') + ` ${hourDiff} ` +
(hourDiff > 1 ? 'min' : 'mins');
} else {
paymentTagText = tA('paymentLinkExpiresIn') + ` ${hourDiff} ` +
(hourDiff > 1 ? 'hrs' : 'hr');
}
}
Billing Details Logic:
Billing Information Processing:
- System processes multiple payment types for comprehensive billing display
- Handles appointment payments, refunds, and additional charges
- Parses complex billing data from backend API responses
- Displays itemized billing breakdown for transparency
Payment Type Processing:
- Appointment Payment: Primary payment for the appointment service
- Refund Payment: Refund amount for cancelled appointments
- Other Charges: Additional fees, taxes, or service charges
- JSON Parsing: Safely parses string-based charge data from backend
Billing Display Logic:
- Shows service fee, GST, platform fee, and other charges
- Calculates total amount with proper formatting
- Handles refund scenarios with negative amounts
- Displays payment status (To Pay, Paid, Total) based on appointment state
Error Handling:
- Graceful handling of malformed billing data
- Fallback to empty arrays for invalid JSON
- User-friendly error messages for parsing failures
- Robust data validation for billing information
// Comprehensive billing information processing with error handling
const paymentDetails = appointmentDetails?.appointment_payments?.find(
({ payment: p }) => p.type === Payment_Type_Enum.Appointment,
);
const refundDetails = appointmentDetails?.appointment_payments?.find(
({ payment: p }) => p.type === Payment_Type_Enum.AppointmentRefund,
);
const getOtherCharges = (charges: string | Array<other_charge>): Array<other_charge> => {
if (typeof charges === 'string') {
try {
const parseCharges = JSON.parse(charges);
return parseCharges;
} catch (error) {
toast.show({ text: t('error.error') });
return [];
}
}
return charges || [];
};
Appointment Actions Logic:
Appointment Action Management:
- System provides different actions based on appointment status and type
- Actions are contextually available based on appointment state
- Real-time updates reflect action results immediately
- Comprehensive error handling for failed actions
Available Actions by Status:
- Pending: No actions available (awaiting parent payment)
- Confirmed: Cancel and Reschedule options available
- Team Meeting: Only Cancel option (reschedule not available)
- One-on-One: Both Cancel and Reschedule options
- Completed: No actions available (session finished)
Action Processing Logic:
- Cancel: Requires reason selection, updates appointment status
- Reschedule: Navigates to reschedule flow with same child
- Success Handling: Shows confirmation modal with appropriate messaging
- Error Handling: Displays error messages for failed operations
- Data Refresh: Updates all related queries after successful actions
Reschedule Constraints:
- Only available for One-on-One appointments
- Child cannot be changed during reschedule
- Same validation rules as new appointment creation
- Maintains original appointment context and billing
// Comprehensive appointment action management with status-based logic
const [cancelAppointment, { loading: isCancellingAppointment }] = useCancelAppointmentMutation({
onCompleted: res => {
if (!res.cancel_appointment?.success) {
toast.show({ text: res.cancel_appointment?.message || t('error.error') });
} else {
setSuccessModelData({
show: true,
title: tA('cancelModal.title'),
subtitle: `${tA('cancelModal.subTilePartOne')} ${appointmentDetails?.child?.name} ${tA('cancelModal.subTilePartTwo')}`,
});
}
cancelModelRef.current?.close();
},
onError: err => {
toast.show({ text: err.message || t('error.error') });
cancelModelRef.current?.close();
},
refetchQueries: 'all',
});
const handleCancel = (reason: string) => {
cancelAppointment({
variables: {
id: route?.params?.appointmentId as any,
reason,
},
});
};
Reschedule Logic:
// Reschedule appointment functionality
const handleReschedule = () => {
navigation.navigate('therapist/create-appointment', {
isReschedule: true,
childId: appointmentDetails?.child?.id,
appointmentId: route?.params?.appointmentId,
});
};
// Reschedule validation - only One-on-One appointments
const canReschedule = !isTeamMeeting && appointmentDetails?.reschedule_count < 3;
🔄 Session Management
Session Timer Logic:
// Session timer for upcoming appointments
const { timer, showJoin } = useGetActiveSessionTimer({
timeSlot: {
startTime: startTime,
endTime: endTime,
},
execute: isUpComing,
});
// Start call functionality
const handleStartCall = async () => {
try {
Linking.openURL(meetLink);
} catch (error) {
toast.show({ text: t('error.error') });
}
};
🎯 Key Implementation Details
Appointment Type Validation:
// Team meeting validation - requires multiple therapists
const isTeamMeetingValid = therapistsData?.child_therapist?.length >= 2;
// Auto-switch logic for team meetings
useEffect(() => {
if (therapistsData && therapistsData?.child_therapist?.length < 2 && isTeamMeeting) {
setAppointmentType(AppointmentType.OneOnOne);
}
}, [therapistsData, isTeamMeeting]);
Schedule Integration:
// Calendar view with therapist schedules
const therapistSchedules = useMemo(() => {
const therapists = schedulesData?.therapist;
const schedules = [];
therapists?.forEach(t => {
schedules.push(...t.schedules);
});
return schedules;
}, [schedulesData?.therapist]);
Time Slot Validation:
// Minimum 15 minutes slot validation
const validateTimeSlot = (startTime: Date, endTime: Date) => {
const duration = endTime.getTime() - startTime.getTime();
const minutes = duration / (1000 * 60);
return minutes >= 15;
};
🎯 Best Practices
Appointment Management
- Type Validation: Ensure team meetings have multiple therapists assigned
- Schedule Integration: Only allow booking on therapist's available time slots
- Time Validation: Minimum 15-minute appointment duration
- Link Validation: Validate Google Meet/Zoom links before submission
User Experience
- Step-by-Step Flow: Clear 2-step appointment creation process
- Visual Feedback: Show schedule dots on calendar for available dates
- Status Indicators: Clear appointment status with payment expiry information
- Action Constraints: Only One-on-One appointments can be rescheduled
Performance
- Lazy Loading: Load therapist schedules only when needed
- Data Caching: Cache appointment data for offline access
- Error Handling: Provide user-friendly error messages
- Validation: Client-side validation before server submission
🔧 Key Implementation Details
Appointment Status Logic:
// Status determination logic
const isUpComing = appointmentStatus === Appointment_Status_Enum.Confirmed ||
appointmentStatus === Appointment_Status_Enum.Started;
const isPending = appointmentStatus === Appointment_Status_Enum.Scheduled;
const isPast = appointmentStatus === Appointment_Status_Enum.Completed ||
appointmentStatus === Appointment_Status_Enum.Expired ||
appointmentStatus === Appointment_Status_Enum.Cancelled;
Reschedule Constraints:
// Reschedule validation - only One-on-One and max 3 times
const canReschedule = !isTeamMeeting && appointmentDetails?.reschedule_count < 3;
const isRescheduleDisabled = appointmentDetails?.reschedule_count >= 3;
Payment Status Logic:
// Payment expiry calculation
const calculatePaymentExpiry = (expiryDate: string) => {
let hourDiff = dayjs(expiryDate).diff(dayjs(), 'hour');
if (hourDiff < 1) {
hourDiff = dayjs(expiryDate).diff(dayjs(), 'minutes');
return `${hourDiff} ${hourDiff > 1 ? 'min' : 'mins'}`;
}
return `${hourDiff} ${hourDiff > 1 ? 'hrs' : 'hr'}`;
};