Skip to main content

📅 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
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 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'}`;
};

🎯 Next Steps​