Skip to main content

πŸ“… Parent Appointments

This document covers the comprehensive appointment management system for parents in the Com DEALL mobile application, including appointment booking, payment processing, status tracking, and appointment management.

🎯 Overview​

The parent appointment system enables parents to book therapy sessions for their children, manage payments, track appointment status, and handle rescheduling. The system supports multiple appointment types and integrates with payment processing.

πŸ—οΈ Architecture​

Appointment Management Components​

Parent Appointment System
β”œβ”€β”€ Appointment Booking
β”œβ”€β”€ Payment Processing
β”œβ”€β”€ Status Management
β”œβ”€β”€ Rescheduling
β”œβ”€β”€ Appointment History
└── Transaction Management

Data Flow​

Appointment Request β†’ Payment Processing β†’ Confirmation β†’ Status Update β†’ Notification
↓ ↓ ↓ ↓ ↓
Parent Booking β†’ Razorpay/iOS/Android β†’ Database β†’ Real-time Sync β†’ Parent Notification

πŸ“± Mobile Implementation​

Appointment Home Screen​

Tab Navigation System:

  • System implements three main appointment tabs: Requested, Upcoming, and Past
  • Each tab displays appointments filtered by specific status criteria
  • Tab navigation uses Material Top Tab Navigator for smooth transitions
  • Each tab maintains its own state and filtering logic independently

Tab Status Logic:

  • Requested Tab: Shows appointments with Appointment_Status_Enum.Scheduled status
  • Upcoming Tab: Shows appointments with Appointment_Status_Enum.Confirmed and Appointment_Status_Enum.Started status
  • Past Tab: Shows appointments with Appointment_Status_Enum.Rejected, Appointment_Status_Enum.Cancelled, Appointment_Status_Enum.Completed, and Appointment_Status_Enum.Expired status

Filtering System:

  • Each tab supports date range filtering with start and end time selection
  • Time filter component provides quick day picker for future/past appointments
  • Filter state management includes date range, time range, and filter options
  • Filter application updates query variables for real-time data filtering
// AppointmentsHome.tsx - Main appointment management screen with tab navigation
const AppointmentHome = ({ navigation }: ScreenProps) => {
const [index, setIndex] = React.useState(0);

// Tab navigation configuration with status-based filtering
const TopTab = createMaterialTopTabNavigator();

// Tab configuration with status-specific appointment display
const tabConfig = [
{
name: 'RequestedAppointment',
label: 'Pending',
status: [Appointment_Status_Enum.Scheduled],
quickPickerType: 'FUTURE'
},
{
name: 'UpcomingAppointment',
label: 'Upcoming',
status: [Appointment_Status_Enum.Confirmed, Appointment_Status_Enum.Started],
quickPickerType: 'FUTURE'
},
{
name: 'PastAppointment',
label: 'Past',
status: [Appointment_Status_Enum.Rejected, Appointment_Status_Enum.Cancelled,
Appointment_Status_Enum.Completed, Appointment_Status_Enum.Expired],
quickPickerType: 'PAST'
}
];

// Navigation header configuration with back button and title
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => (
<Box alignItems="center" flexDirection="row">
<BackButton navigation={navigation} />
<Text color="secondary20" variant="heading4Light">
{t('appointment')}
</Text>
</Box>
),
});
}, [navigation, colors.secondary63, t]);
};

Upcoming Appointments Tab​

Status-Based Filtering Logic:

  • Displays appointments with Appointment_Status_Enum.Confirmed and Appointment_Status_Enum.Started status
  • Filters appointments to show only future sessions that are confirmed or currently in progress
  • Excludes past appointments and scheduled (pending) appointments from this tab

Data Processing Logic:

  • Processes appointment data to extract therapist information, child details, and time slots
  • Maps appointment slots to find the active time slot using in_use flag
  • Calculates time differences to determine if appointment is still upcoming
  • Formats appointment data for display with proper date/time formatting

Filter Management:

  • Implements date range filtering with start and end time selection
  • Supports quick day picker for future date selection
  • Maintains filter state with timestamp conversion for query optimization
  • Provides filter reset functionality with clear filter option
// Upcoming.tsx - Upcoming appointments with comprehensive filtering
const Upcoming = () => {
const [dateRangeData, setDateRangeData] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});
const [filterOption, setFilterOption] = useState<string>('');
const [timeRangeData, setTimeRangeData] = useState<TimeFilterRange>({
startTime: undefined,
endTime: undefined,
});
const [isFilterApplied, setIsFilterApplied] = useState(false);
const [filterTimeStamp, setFilterTimeStamp] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});

// Query configuration for upcoming appointments with status filtering
const { data, refetch, loading, error } = useGetAppointmentListQuery({
variables: {
status: [
Appointment_Status_Enum.Confirmed,
Appointment_Status_Enum.Started,
],
StartTime: {
_gte: filterTimeStamp?.startTime || dayjs().startOf('day').toISOString(),
},
EndTime: filterTimeStamp?.endTime
? { _lte: filterTimeStamp?.endTime }
: {},
},
});

// Appointment data processing with comprehensive filtering and time validation
const appointments = useMemo(() => {
return (
data?.appointment
?.filter(f => {
const { endTimeSlot } = getAppointmentSlot(f.appointment_slots);
return dayjs(new Date()).isBefore(endTimeSlot?.EndTime);
})
?.map(entry => {
const { startTimeSlot, endTimeSlot } = getAppointmentSlot(
entry.appointment_slots,
);

const slot = entry?.appointment_slots?.find(s => s.in_use);
const startTime = slot?.timeSlot?.StartTime;
const endTime = slot?.timeSlot?.EndTime;

return {
name: entry?.appointment_therapists[0]?.therapist?.user?.name,
child: entry?.child?.name,
degree: entry.appointment_therapists[0]?.therapist?.degree_name,
specialty: entry?.appointment_therapists[0]?.therapist
?.therapist_specialities[0]?.speciality?.title,
date: startTime,
timeSlot: {
startTime: startTime,
endTime: endTime,
},
status: entry.status!,
img: entry.appointment_therapists[0]?.therapist?.user
?.profile_picture?.path,
id: entry.id,
paymentExpiry: entry?.payment_expiry,
rescheduleCount: entry?.reschedule_count,
rescheduleRequested: entry?.reschedule_requested,
appointmentType: entry?.appointment_type,
};
}) || []
);
}, [data?.appointment]);

// Filter application logic with comprehensive state management
const handleApplyFilter = (
dateRange: TimeFilterRange,
timeRange: TimeFilterRange,
selectedOption: string,
isApply: boolean,
) => {
setDateRangeData(dateRange);
setTimeRangeData(timeRange);
setFilterOption(selectedOption);
setIsFilterApplied(isApply);
setFilterTimeStamp({ ...timeRange });
};
};

Requested Appointments Tab​

Status-Based Filtering Logic:

  • Displays appointments with Appointment_Status_Enum.Scheduled status
  • Shows appointments that are pending confirmation or payment
  • Includes appointments that are waiting for therapist confirmation or payment processing

Data Processing Logic:

  • Processes scheduled appointments with payment expiry tracking
  • Handles payment link expiration logic with time-based calculations
  • Manages reschedule request status and count tracking
  • Formats appointment data for pending status display
// Requested.tsx - Requested appointments with payment tracking
const Requested = () => {
const [dateRangeData, setDateRangeData] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});
const [filterOption, setFilterOption] = useState<string>('');
const [timeRangeData, setTimeRangeData] = useState<TimeFilterRange>({
startTime: undefined,
endTime: undefined,
});
const [isFilterApplied, setIsFilterApplied] = useState(false);
const [filterTimeStamp, setFilterTimeStamp] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});

// Query configuration for requested appointments with scheduled status
const { data, refetch, loading, error } = useGetAppointmentListQuery({
variables: {
status: [Appointment_Status_Enum.Scheduled],
StartTime: {
_gte: filterTimeStamp?.startTime || dayjs().startOf('day').toISOString(),
},
EndTime: filterTimeStamp?.endTime
? { _lte: filterTimeStamp?.endTime }
: {},
},
});

// Appointment data processing with payment expiry and reschedule tracking
const appointments = useMemo(() => {
return (
data?.appointment
?.filter(f => {
const { endTimeSlot } = getAppointmentSlot(f.appointment_slots);
return dayjs(new Date()).isBefore(endTimeSlot?.EndTime);
})
?.map(entry => {
const { startTimeSlot, endTimeSlot } = getAppointmentSlot(
entry.appointment_slots,
);

const slot = entry?.appointment_slots?.find(s => s.in_use);
const startTime = slot?.timeSlot?.StartTime;
const endTime = slot?.timeSlot?.EndTime;

return {
name: entry?.appointment_therapists[0]?.therapist?.user?.name,
child: entry?.child?.name,
degree: entry.appointment_therapists[0]?.therapist?.degree_name,
specialty: entry?.appointment_therapists[0]?.therapist
?.therapist_specialities[0]?.speciality?.title,
date: startTime,
timeSlot: {
startTime: startTime,
endTime: endTime,
},
status: entry.status!,
img: entry.appointment_therapists[0]?.therapist?.user
?.profile_picture?.path,
id: entry.id,
paymentExpiry: entry?.payment_expiry,
rescheduleCount: entry?.reschedule_count,
appointmentType: entry?.appointment_type,
};
}) || []
);
}, [data?.appointment]);
};

Past Appointments Tab​

Status-Based Filtering Logic:

  • Displays appointments with Appointment_Status_Enum.Rejected, Appointment_Status_Enum.Cancelled, Appointment_Status_Enum.Completed, and Appointment_Status_Enum.Expired status
  • Shows historical appointments that have been concluded or cancelled
  • Includes completed sessions, cancelled appointments, and expired payment links

Data Processing Logic:

  • Processes past appointments with completion status tracking
  • Handles appointment status transformation (Scheduled β†’ Completed for past appointments)
  • Manages appointment history with proper chronological ordering
  • Formats appointment data for historical display

Selection Filtering:

  • Provides radio selection between "All" and "Completed" appointments
  • Filters past appointments based on completion status
  • Maintains selection state for user preference persistence
// Past.tsx - Past appointments with comprehensive filtering
const Past = () => {
const [selected, setSelected] = useState('all');
const [dateRangeData, setDateRangeData] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});
const [filterOption, setFilterOption] = useState<string>('');
const [timeRangeData, setTimeRangeData] = useState<TimeFilterRange>({
startTime: undefined,
endTime: undefined,
});
const [isFilterApplied, setIsFilterApplied] = useState(false);
const [filterTimeStamp, setFilterTimeStamp] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});

// Query configuration for past appointments with multiple status filtering
const { data, refetch, loading, error } = useGetAppointmentListQuery({
variables: {
status: [
Appointment_Status_Enum.Rejected,
Appointment_Status_Enum.Cancelled,
Appointment_Status_Enum.Completed,
Appointment_Status_Enum.Expired,
],
StartTime: filterTimeStamp?.startTime
? {
_gte: filterTimeStamp?.startTime,
}
: {},
EndTime: filterTimeStamp?.endTime
? { _lte: filterTimeStamp?.endTime }
: {},
order_by: Order_By.DescNullsLast,
},
});

// Appointment data processing with status transformation and chronological ordering
const appointments = useMemo(() => {
return (
data?.appointment?.map(entry => {
const { startTimeSlot, endTimeSlot } = getAppointmentSlot(
entry.appointment_slots,
);

const slot = entry?.appointment_slots?.find(s => s.in_use);
const startTime = slot?.timeSlot?.StartTime;
const endTime = slot?.timeSlot?.EndTime;

return {
name: entry?.appointment_therapists[0]?.therapist?.user?.name,
child: entry?.child?.name,
degree: entry.appointment_therapists[0]?.therapist?.degree_name,
specialty: entry?.appointment_therapists[0]?.therapist
?.therapist_specialities[0]?.speciality?.title,
date: startTime,
timeSlot: {
startTime: startTime,
endTime: endTime,
},
status: entry.status === Appointment_Status_Enum.Scheduled
? Appointment_Status_Enum.Completed
: entry.status,
img: entry.appointment_therapists[0]?.therapist?.user?.profile_picture
?.path,
id: entry.id,
paymentExpiry: entry?.payment_expiry,
rescheduleCount: entry?.reschedule_count,
rescheduleRequested: entry?.reschedule_requested,
appointmentType: entry?.appointment_type,
};
}) || []
);
}, [data?.appointment]);

// Selection data configuration for appointment filtering
const selectionData = [
{ name: t('all'), key: 'all' },
{ name: t('completed'), key: 'completed' },
];

// Appointment data filtering based on selection
const appointmentData = useMemo(() => {
return selected === 'completed'
? appointments.filter(x => x.status === Appointment_Status_Enum.Completed)
: appointments;
}, [appointments, selected]);
};

πŸ“± Appointment Details Screen​

Appointment Details Overview​

Navigation Logic:

  • Navigates to appointment details when appointment card is pressed
  • Passes appointment ID as route parameter for data fetching
  • Maintains navigation state for back button functionality

Data Fetching Logic:

  • Fetches comprehensive appointment details using appointment ID
  • Retrieves therapist information, child details, and appointment metadata
  • Handles loading states and error scenarios with retry functionality
  • Implements cache-and-network fetch policy for optimal performance

Status-Based Display Logic:

  • Displays different UI elements based on appointment status
  • Shows payment tags for scheduled appointments with expiry information
  • Handles team meeting display with multiple therapist information
  • Manages appointment action buttons based on current status
// AppointmentDetails.tsx - Comprehensive appointment details with status-based logic
const AppointmentDetails = ({ navigation, route }: ScreenProps) => {
const [isSessionTriggered, setSessionTriggered] = useState(false);
const [showMore, setShowMore] = useState(false);
const [view, setView] = useState<'CANCEL' | 'RESCHEDULE' | 'REBOOK'>();
const [successModalVisible, setSuccessModalVisible] = useState(false);
const [rescheduleModalVisible, setRescheduleModalVisible] = useState(false);

// Appointment details query with comprehensive data fetching
const {
data,
refetch: refetchAppointmentDetails,
loading: appointmentDetailsLoading,
error: appointmentDetailsError,
} = useGetAppointmentDetailsQuery({
variables: {
id: route.params?.appointmentId,
},
fetchPolicy: 'cache-and-network',
});

const appointment = data?.appointment?.[0];

// Therapist and child data processing with comprehensive information extraction
const { therapist, child } = useMemo(() => {
const therapistData = {
id: appointment?.appointment_therapists[0]?.therapist_id,
name: appointment?.appointment_therapists[0]?.therapist?.user?.name,
qualification: appointment?.appointment_therapists[0]?.therapist?.degree_name,
designation: 'Physician',
experience: appointment?.appointment_therapists[0]?.therapist?.experience,
consultationFees: appointment?.appointment_therapists[0]?.therapist?.consultation_fees,
image: appointment?.appointment_therapists[0]?.therapist?.user?.profile_img,
};
const childData = {
childName: appointment?.child?.name,
age: appointment?.child?.age,
lastTherapy: appointment?.child?.appointments?.[0]?.date,
image: appointment?.child?.profile_image?.path,
id: appointment?.child?.id,
assessmentTaken: appointment?.child?.assessment_responses_aggregate?.aggregate?.count,
};

return {
therapist: therapistData,
child: childData,
};
}, [appointment]);

// Appointment data processing with comprehensive status and payment logic
const appointmentData = useMemo(() => {
const isAfter = dayjs(new Date()).isAfter(endTimeSlot?.EndTime);
const activeSessionJustEnded = isSessionTriggered && !showJoin;
const isAfterSession = appointment?.status === Appointment_Status_Enum.Scheduled && isAfter;

const appointmentStatus = activeSessionJustEnded || isAfterSession
? Appointment_Status_Enum.Completed
: appointment?.status;

return {
date: startTime,
startTime: startTime,
endTime: endTime,
status: appointmentStatus,
payment: {
tax: appointment?.taxes,
serviceFee: appointment?.service_fee,
consultationFee: appointment?.consultation_fee,
otherCharges: appointment?.other_charges,
paymentExpiry: appointment?.payment_expiry,
},
meetLink: appointment?.meet_link,
};
}, [
endTimeSlot?.EndTime,
isSessionTriggered,
showJoin,
appointment?.status,
appointment?.date,
appointment?.taxes,
appointment?.service_fee,
appointment?.meet_link,
startTimeSlot?.StartTime,
appointment?.other_charges,
appointment?.consultation_fee,
]);
};

Payment Processing Logic​

Payment Status Management:

  • Handles payment expiry calculations with hour and minute precision
  • Displays payment link expiry information for scheduled appointments
  • Manages payment confirmation flow with Razorpay integration
  • Tracks payment status and provides appropriate user feedback

Payment Flow Logic:

  • Initiates payment process for scheduled appointments
  • Handles Razorpay payment gateway integration
  • Processes payment confirmation with signature validation
  • Manages payment success and failure scenarios
// Make Payment functionality for scheduled appointments
const isScheduled = appointmentData?.status === Appointment_Status_Enum.Scheduled;

// Payment button display logic for scheduled appointments
{isScheduled && (
<Box gap="l" flexDirection="row" flex={1}>
<Box>
<Text variant="title2" color="secondary20">
{renderFee(paymentDetails?.payment?.amount)}
</Text>
<Text mt="xxxxs" variant="paragraph2" color="primary36">
{t('totalAmount')}
</Text>
</Box>
<CsmButton
handleClick={() => handlePayment()}
style={commonFlexStyles.FLEX2}
label={'Make Payment'}
isLoading={startPaymentLoading}
disabled={startPaymentLoading}
/>
</Box>
)}

// Payment processing logic with comprehensive status management
const handlePayment = async () => {
const response = await startPayment();
const orderId = response?.data?.startAppointmentPayment?.data?.order_id;
const parentInfo = parentDetails?.parent[0];

var options = {
description: 'Credits towards Com DEALL',
image: 'https://comdeall-prod-sb.blr1.cdn.digitaloceanspaces.com/COVER_PAGE_BG_TOP_RIGHT.svg',
currency: 'INR',
key: process.env.EXPO_PUBLIC_RAZOR_PAY_KEY!,
name: 'ComDEALL',
order_id: orderId,
prefill: {
email: parentInfo?.user?.email,
contact: parentInfo?.user?.phone,
name: parentInfo?.user?.name,
},
theme: { color: '#EE7B36' },
retry: { enabled: false },
timeout: 600,
modal: {
handleback: false,
escape: false,
confirm_close: true,
},
};

RazorpayCheckout.open(options)
.then((res: any) => {
const paymentConfirmationData = {
payment_signature: res.razorpay_signature,
provider_id: res.razorpay_payment_id,
razorpay_order_id: res.razorpay_order_id,
};
handleConfirmPayment(paymentConfirmationData, true);
})
.catch((err: any) => {
if (err?.details?.error?.metadata?.payment_id) {
const paymentConfirmationData = {
provider_id: err?.details?.error?.metadata?.payment_id,
razorpay_order_id: err?.details?.error?.metadata?.order_id,
};
handleConfirmPayment(paymentConfirmationData, false);
} else {
const paymentConfirmationData = {
provider_id: null,
razorpay_order_id: orderId,
};
handleConfirmPayment(paymentConfirmationData, false);
}
});
};

Make Payment for Requested Appointments​

Payment Button Display Logic:

  • Shows "Make Payment" button only for scheduled appointments (Appointment_Status_Enum.Scheduled)
  • Displays total amount and payment details for pending appointments
  • Handles payment button loading state during payment processing
  • Provides payment button disable state during payment initiation

Payment Processing Flow:

  • Initiates payment process by calling startPayment mutation
  • Retrieves Razorpay order ID from payment response
  • Opens Razorpay checkout with pre-filled parent information
  • Handles payment success and failure scenarios with appropriate feedback

Payment Confirmation Logic:

  • Processes payment confirmation with signature validation
  • Updates appointment status after successful payment
  • Shows success modal with appointment confirmation details
  • Handles payment failure scenarios with error feedback
// Make Payment button display for scheduled appointments
const isScheduled = appointmentData?.status === Appointment_Status_Enum.Scheduled;

// Payment button with total amount display
{isScheduled && (
<Box gap="l" flexDirection="row" flex={1}>
<Box>
<Text variant="title2" color="secondary20">
{renderFee(paymentDetails?.payment?.amount)}
</Text>
<Text mt="xxxxs" variant="paragraph2" color="primary36">
{t('totalAmount')}
</Text>
</Box>
<CsmButton
handleClick={() => handlePayment()}
style={commonFlexStyles.FLEX2}
label={'Make Payment'}
isLoading={startPaymentLoading}
disabled={startPaymentLoading}
/>
</Box>
)}

// Payment initiation logic with comprehensive error handling
const [startPayment, { loading: startPaymentLoading }] = useStartAppointmentPaymentMutation({
variables: {
id: route.params?.appointmentId,
},
fetchPolicy: 'no-cache',
});

const handlePayment = async () => {
const response = await startPayment();
const orderId = response?.data?.startAppointmentPayment?.data?.order_id;
const parentInfo = parentDetails?.parent[0];

// Razorpay payment options configuration
var options = {
description: 'Credits towards Com DEALL',
image: 'https://comdeall-prod-sb.blr1.cdn.digitaloceanspaces.com/COVER_PAGE_BG_TOP_RIGHT.svg',
currency: 'INR',
key: process.env.EXPO_PUBLIC_RAZOR_PAY_KEY!,
name: 'ComDEALL',
order_id: orderId,
prefill: {
email: parentInfo?.user?.email,
contact: parentInfo?.user?.phone,
name: parentInfo?.user?.name,
},
theme: { color: '#EE7B36' },
retry: { enabled: false },
timeout: 600,
modal: {
handleback: false,
escape: false,
confirm_close: true,
},
};

// Razorpay payment processing with success/failure handling
RazorpayCheckout.open(options)
.then((res: any) => {
const paymentConfirmationData = {
payment_signature: res.razorpay_signature,
provider_id: res.razorpay_payment_id,
razorpay_order_id: res.razorpay_order_id,
};
handleConfirmPayment(paymentConfirmationData, true);
})
.catch((err: any) => {
if (err?.details?.error?.metadata?.payment_id) {
const paymentConfirmationData = {
provider_id: err?.details?.error?.metadata?.payment_id,
razorpay_order_id: err?.details?.error?.metadata?.order_id,
};
handleConfirmPayment(paymentConfirmationData, false);
} else {
const paymentConfirmationData = {
provider_id: null,
razorpay_order_id: orderId,
};
handleConfirmPayment(paymentConfirmationData, false);
}
});
};

Appointment Actions Logic​

Reschedule Request Logic:

  • Handles reschedule request submission with appointment ID
  • Manages reschedule count tracking and limit enforcement
  • Provides user feedback for successful reschedule requests
  • Tracks reschedule request status and display appropriate UI

Cancellation Logic:

  • Manages appointment cancellation with reason tracking
  • Handles cancellation charges calculation and display
  • Processes refund information for cancelled appointments
  • Provides cancellation confirmation and success feedback
// Appointment actions logic with comprehensive request management
const [requestReschedule, { loading: reScheduleLoading }] = useRescheduleRequestMutation({
onCompleted(data) {
if (data?.appointment_reschedule_request[0]?.reschedule_requested) {
toast.show({ text: t('rescheduleSuccess') });
refetchAppointmentDetails();
}
},
onError(error) {
toast.show({ text: error.message || tError('error') });
},
});

const handleReschedule = () => {
requestReschedule({
variables: { appointmentId: route.params?.appointmentId },
});
setRescheduleModalVisible(false);
};

const handleCancelButton = () => {
setView('CANCEL');
sheetRef.current?.present();
};

Billing Details Display​

Billing Information Processing:

  • Displays comprehensive billing details including consultation fees, taxes, and service fees
  • Handles other charges processing with JSON parsing and display formatting
  • Shows total amount calculation with proper currency formatting
  • Manages cancellation charges and refund amount display for cancelled appointments

Payment Status Display:

  • Shows payment expiry information for scheduled appointments with time calculations
  • Displays payment link expiry warnings with hour and minute precision
  • Handles different payment statuses (Pending, Successful, Failed) with appropriate UI
  • Manages payment confirmation flow with success/failure feedback

Make Payment Option for Requested Appointments:

  • Displays "Make Payment" button for scheduled appointments that require payment
  • Shows total amount and payment details for pending appointments
  • Handles payment processing with Razorpay integration for scheduled appointments
  • Provides payment confirmation and success feedback after successful payment
// Billing details processing with comprehensive charge calculation
const getOtherCharges = (charges: string | 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 || [];
};

// Payment expiry calculation with comprehensive time formatting
let paymentTagText = '';

if (appointmentData?.status === Appointment_Status_Enum.Scheduled) {
let hourDiff = dayjs(appointmentData?.payment?.paymentExpiry).diff(
dayjs(),
'hour',
);

if (hourDiff < 1) {
hourDiff = dayjs(appointmentData?.payment?.paymentExpiry).diff(
dayjs(),
'minutes',
);

paymentTagText =
t('paymentLinkExpiresIn') +
` ${hourDiff} ` +
(hourDiff > 1 ? 'min' : 'mins');
} else {
paymentTagText =
t('paymentLinkExpiresIn') +
` ${hourDiff} ` +
(hourDiff > 1 ? 'hrs' : 'hr');
}
}

Team Meeting Display Logic​

Team Meeting Information:

  • Displays multiple therapist information for team meeting appointments
  • Shows therapist details including name, degree, and profile picture
  • Handles team meeting specific UI elements and information display
  • Manages team meeting appointment type identification and display

Appointment Type Handling:

  • Identifies team meeting appointments using Appointment_Type_Enum.TeamMeeting
  • Displays team meeting specific information and therapist list
  • Handles different appointment types with appropriate UI rendering
  • Manages appointment type-based action button display
// Team meeting display logic with comprehensive therapist information
const isTeamMeeting = appointment?.appointment_type === Appointment_Type_Enum.TeamMeeting;

// Team meeting therapist information processing
{isTeamMeeting && (
<Box p="m" bg="white" mb="l" borderRadius="s">
<Text variant="title2" mb="m">
{t('teamMeeting')}
</Text>
<Box gap="l">
{appointment?.appointment_therapists?.map(
({ therapist, therapist_id }) => (
<TherapistInfoCard
imageUrl={therapist?.user?.profile_picture?.path}
name={therapist?.user?.name}
degree={therapist?.degree_name}
key={therapist_id}
/>
),
)}
</Box>
</Box>
)}

Reschedule History Display​

Reschedule Tracking Logic:

  • Displays reschedule history with chronological order
  • Shows reschedule count and request status
  • Handles reschedule limit enforcement (maximum 3 reschedules)
  • Manages reschedule request display and user feedback

Reschedule Status Management:

  • Tracks reschedule count and request status
  • Displays reschedule history with proper date formatting
  • Handles reschedule limit warnings and restrictions
  • Manages reschedule request confirmation and success feedback
// Reschedule history processing with comprehensive tracking
const getReschedules = () => {
const reschedules: JSX.Element[] = [];

appointment?.appointment_slots?.forEach((slot, index) => {
if (slot?.schedule_number > 0) {
reschedules.push(
<Box flexDirection="row" alignItems="center" gap="xxs" key={index}>
<CalenderIcon width={20} />
<Text
variant="body3"
color="secondary20"
opacity={slot?.in_use ? 1 : 0.3}>
{t('rescheduledTo')}
{dayjs(slot?.timeSlot?.StartTime).format('MMM DD, YYYY')}
</Text>
</Box>,
);
}
});
return reschedules;
};

πŸ’³ Payment Processing​

Payment Integration​

Payment Processing Architecture: The appointment payment system supports multiple payment gateways to accommodate different platforms and regions. The system integrates Razorpay for web-based payments, iOS In-App Purchases for Apple devices, and Google Play Billing for Android devices. This multi-platform approach ensures seamless payment experiences across all supported devices.

Payment Gateway Selection Logic:

  • Razorpay: Primary payment gateway for web and cross-platform payments, supporting UPI, cards, net banking, and wallets
  • iOS In-App Purchase: Used for subscription and appointment payments on iOS devices to comply with Apple's guidelines
  • Android In-App Billing: Handles payments on Android devices through Google Play Store integration

Payment State Management: The payment processing hook maintains loading states throughout the payment flow to provide visual feedback to users. Loading states are activated when payment is initiated and deactivated upon completion or failure, preventing duplicate payment attempts and improving user experience.

// Payment processing for appointments
const usePaymentProcessing = () => {
const [paymentLoading, setPaymentLoading] = useState(false);

const processRazorpayPayment = async (paymentData: PaymentData) => {
setPaymentLoading(true);
try {
const result = await RazorpayCheckout.open({
description: paymentData.description,
image: 'https://your-logo-url.com/logo.png',
currency: 'INR',
key: process.env.EXPO_PUBLIC_RAZORPAY_KEY,
amount: paymentData.amount * 100,
name: 'Com DEALL',
order_id: paymentData.razorpay_order_id,
prefill: {
email: paymentData.email,
contact: paymentData.phone,
name: paymentData.name,
},
theme: { color: '#F37254' }
});

return result;
} catch (error) {
console.error('Payment error:', error);
throw error;
} finally {
setPaymentLoading(false);
}
};

const processIOSPayment = async (productId: string) => {
setPaymentLoading(true);
try {
const result = await purchaseProduct(productId);
return result;
} catch (error) {
console.error('iOS payment error:', error);
throw error;
} finally {
setPaymentLoading(false);
}
};

const processAndroidPayment = async (productId: string) => {
setPaymentLoading(true);
try {
const result = await purchaseProduct(productId);
return result;
} catch (error) {
console.error('Android payment error:', error);
throw error;
} finally {
setPaymentLoading(false);
}
};

return {
processRazorpayPayment,
processIOSPayment,
processAndroidPayment,
paymentLoading
};
};

Payment Confirmation​

// Payment confirmation handling
const usePaymentConfirmation = () => {
const [confirmPayment] = useConfirmAppointmentPaymentMutation({
onCompleted: (data) => {
if (data.confirmAppointmentPayment.success) {
toast.show({ text: 'Payment confirmed successfully' });
navigation.navigate('parent/appointments');
} else {
toast.show({ text: data.confirmAppointmentPayment.message || 'Payment confirmation failed' });
}
},
onError: (error) => {
toast.show({ text: error.message || 'Payment confirmation failed' });
}
});

const confirmRazorpayPayment = async (orderId: string, paymentId: string, signature: string) => {
await confirmPayment({
variables: {
order_id: orderId,
payment_id: paymentId,
payment_signature: signature
}
});
};

const confirmIOSPayment = async (transactionId: string, productId: string) => {
await confirmPayment({
variables: {
ios_transaction_id: transactionId,
ios_product_id: productId
}
});
};

const confirmAndroidPayment = async (purchaseToken: string, productId: string) => {
await confirmPayment({
variables: {
android_purchase_token: purchaseToken,
android_product_id: productId
}
});
};

// Payment confirmation logic with comprehensive error handling
const confirmRazorpayPayment = async (orderId: string, paymentId: string, signature: string) => {
await confirmPayment({
variables: {
order_id: orderId,
payment_id: paymentId,
payment_signature: signature
}
});
};

const confirmIOSPayment = async (transactionId: string, productId: string) => {
await confirmPayment({
variables: {
ios_transaction_id: transactionId,
ios_product_id: productId
}
});
};

const confirmAndroidPayment = async (purchaseToken: string, productId: string) => {
await confirmPayment({
variables: {
android_purchase_token: purchaseToken,
android_product_id: productId
}
});
};
};

πŸ“Š Appointment Analytics - Actual Implementation

Based on the provided code files, here's the real implementation of appointment statistics logic with explanations:


1. Pending Appointments with Filtering​

From:PendingAppointment.tsx

 
const PendingAppointment = () => {
// State management for date/time filtering
const [filterTimeStamp, setFilterTimeStamp] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});
const [isFilterApplied, setIsFilterApplied] = useState(false);

// Lazy query for fetching appointment data
const [loadData, { data, refetch, loading, error }] =
useGetAppointmentListLazyQuery({
fetchPolicy: 'cache-and-network',
variables: {
status: [Appointment_Status_Enum.Scheduled], // Filter by scheduled status
StartTime: {
_gte: filterTimeStamp?.startTime || dayjs().startOf('day').toISOString(),
},
EndTime: filterTimeStamp?.endTime
? { _lte: filterTimeStamp?.endTime }
: {},
},
});

// Process and transform appointment data
const newAppointments: TAppointmentCardItem[] = useMemo(() => {
return (
data?.appointment?.map(appointment => {
const { startTimeSlot, endTimeSlot } = getAppointmentSlot(
appointment?.appointment_slots,
);

const slot = appointment.appointment_slots?.find(s => s.in_use);
const startTime = slot?.timeSlot?.StartTime;
const endTime = slot?.timeSlot?.EndTime;

return {
id: appointment.id,
profile_image_url: appointment.child?.profile_image?.path || '',
child_name: appointment.child?.name || '',
age: appointment.child?.age,
parent1: appointment.child?.parent.parent1_name || '',
appointment_date: startTime,
appointment_start_time: startTime,
appointment_end_time: endTime,
status: appointment.status || '',
appointmentType: appointment?.appointment_type || '',
paymentExpiry: appointment?.payment_expiry,
};
}) || []
);
}, [data]);

// Calculate if appointments exist
const hasAppointmentData = useMemo(() => {
return (newAppointments?.length ?? 0) > 0;
}, [newAppointments]);

// Determine if filter UI should be shown
const shouldShowFilter = useMemo(() => {
if (isFilterApplied) {
return true; // Always show if filter is applied
}
return hasAppointmentData; // Show if there's data to filter
}, [isFilterApplied, hasAppointmentData]);
};

πŸ“˜ Explanation​

1. Filter State Management​

  • filterTimeStamp: Stores timestamp values used in the query.
  • isFilterApplied: Tracks if filters are applied.
  • Controls both query variables and UI visibility.

2. Lazy Query Pattern​

fetchPolicy: 'cache-and-network'
  • First returns cached data (fast initial render)
  • Then fetches from network (gets fresh data)
  • Best of both worlds: speed + freshness

3. Dynamic Query Variables​

StartTime: {
_gte: filterTimeStamp?.startTime || dayjs().startOf('day').toISOString(),
}
  • Uses the filter timestamp if available.

  • Falls back to start of the current day otherwise.

  • _gte means "greater than or equal to" (GraphQL comparison operator)

4. Slot Processing​

const slot = appointment.appointment_slots?.find(s => s.in_use);
  • Finds the active slot marked with in_use: true.
  • in_use: true marks the currently active slot
  • Ensures the correct appointment time is displayed.

5. Data Transformation**:​

  • Converts raw backend data β†’ UI-friendly format.

  • Uses useMemo for performance optimization (avoids re-computation).

6. Conditional UI Logic**:​

const shouldShowFilter = useMemo(() => {
if (isFilterApplied) return true;
return hasAppointmentData;
}, [isFilterApplied, hasAppointmentData]);

Shows the filter only when relevant or applied.

2 πŸ†• New Appointments with Status Filtering​

File: NewAppointment.tsx


const NewAppointment = () => {
const [dateRange, setDateRange] = useState<TimeFilterRange>({
startTime: '',
endTime: '',
});

// Query with multiple status filtering
const [loadData, { data, refetch, loading, error }] =
useGetAppointmentListLazyQuery({
fetchPolicy: 'cache-and-network',
variables: {
status: [
Appointment_Status_Enum.Pending,
Appointment_Status_Enum.Rejected,
Appointment_Status_Enum.Cancelled,
],
date: dateRange?.startTime
? {
_gte: format(new Date(dateRange.startTime), 'YYYY-MM-DD'),
_lte: format(new Date(dateRange.endTime!), 'YYYY-MM-DD'),
}
: {
_gte: format(new Date(), 'YYYY-MM-DD'),
},
},
});

// Process appointments with cancellation filtering
const newAppointments = useMemo(() => {
return data?.appointment
?.filter(tempAppointment => tempAppointment?.is_cancelled === false)
?.map(appointment => {
const { startTimeSlot, endTimeSlot } = getAppointmentSlot(
appointment?.appointment_slots,
);

return {
id: appointment.id,
profile_image_url: appointment.child?.profile_image?.path,
child_name: appointment.child?.name,
age: appointment.child?.age,
parent1: appointment.child?.parent.parent1_name,
appointment_date: appointment.date,
appointment_start_time: startTimeSlot?.StartTime,
appointment_end_time: endTimeSlot?.EndTime,
status: appointment.approval_status,
};
});
}, [data]);
};

🧠 Explanation

1. Multiple Status Filtering​

status: [
Appointment_Status_Enum.Pending,
Appointment_Status_Enum.Rejected,
Appointment_Status_Enum.Cancelled,
]
  • Shows appointments in multiple states
  • Therapists can see all "new" appointments requiring attention
  • GraphQL _in operator checks if status matches any in the array

2. Conditional Date Filtering:​

3.Additional Client-Side Filtering:​

?.filter(tempAppointment => tempAppointment?.is_cancelled === false)
  • Server returns appointments with various statuses
  • Client removes those with is_cancelled = true
  • Why? Status might be "Pending" but is_cancelled flag provides additional context

4. Helper Function Usage:​

const { startTimeSlot, endTimeSlot } = getAppointmentSlot(
appointment?.appointment_slots,
);
  • Abstracts complex slot extraction logic
  • Returns the active time slot from multiple slots
  • Reusable across different components

Upcoming Appointments with Real-time Sorting​

From: UpcomingAppointments.tsx


const UpcomingAppointments = ({ appointments }) => {
const [upcommingAppointments, setUpcommingAppointments] = useState([]);
const [isSessionTriggered, setSessionTriggered] = useState(false);

const appointmentData = upcommingAppointments?.[0]; // First = next appointment

const { startTimeSlot, endTimeSlot } = getAppointmentSlot(
appointmentData?.appointment_slots,
);

// Check if session can be joined (within time window)
const { showJoin } = useGetActiveSessionTimer({
timeSlot: {
startTime: startTimeSlot?.StartTime,
endTime: endTimeSlot?.EndTime,
},
showTimer: false,
execute: hasAppointments,
});

// Sort and filter appointments when screen focused
useFocusEffect(
useCallback(() => {
const filteredData = sortBy(
appointments?.appointment,
d => d.appointment_slots?.[0]?.timeSlot?.StartTime, // Sort by start time
)?.filter(f => {
const { endTimeSlot: curEndTimeSlot } = getAppointmentSlot(
f.appointment_slots,
);
return dayjs(new Date()).isBefore(curEndTimeSlot?.EndTime); // Only future appointments
});
setUpcommingAppointments(filteredData);
}, [appointments]),
);

// Track when session becomes active
useFocusEffect(
useCallback(() => {
if (showJoin) {
setSessionTriggered(true);
}
}, [showJoin]),
);
};

Explanation:​

1. Chronological Sorting:​

sortBy(appointments, d => d.appointment_slots?.[0]?.timeSlot?.StartTime)
  • lodash sortBy orders appointments by start time
  • Earliest appointment appears first
  • Ensures "next appointment" is always shown at top

2. Future-Only Filtering:​

return dayjs(new Date()).isBefore(curEndTimeSlot?.EndTime);
  • dayjs().isBefore(endTime) checks if appointment hasn't ended yet
  • Removes past appointments from the list
  • Shows only relevant upcoming sessions

3. useFocusEffect Hook:​

useFocusEffect(
useCallback(() => {
// Logic runs when screen gains focus
}, [appointments])
);
  • Runs when user navigates to this screen
  • Re-sorts/filters data with latest appointments
  • Updates when appointments prop changes

4. Active Session Detection:​

const { showJoin } = useGetActiveSessionTimer({
timeSlot: { startTime, endTime },
execute: hasAppointments,
});
  • Custom hook checks if current time is within session window
  • Returns showJoin: true when session can be joined
  • Typically allows joining 10-15 mins before start time

5. Session State Persistence:​

if (showJoin) {
setSessionTriggered(true); // Once triggered, stays true
}
  • Prevents "Join" button from disappearing if user navigates away
  • Marks session as "active" even if timer expires
  • Helps show "Completed" badge after session ends

πŸ”„ Real-time Updates​

Appointment Notifications​

// Real-time appointment updates
const useAppointmentSubscription = (parentId: string) => {
const { data: subscriptionData } = useAppointmentUpdatesSubscription({
variables: { parent_id: parentId }
});

useEffect(() => {
if (subscriptionData?.appointment_updated) {
const update = subscriptionData.appointment_updated;

// Update local cache
updateAppointmentCache(update);

// Show notification
toast.show({
text: `Appointment ${update.status.toLowerCase()}`,
type: 'info'
});
}
}, [subscriptionData]);
};

πŸ”Ή Explanation: Real-time Appointment Updates​

  1. Subscription Hook
    useAppointmentUpdatesSubscription listens for any appointment changes for a specific parentId.

  2. Effect on Data Change
    useEffect triggers whenever subscriptionData updates.

  3. Cache Update
    updateAppointmentCache(update) ensures the local state reflects the latest appointment status.

  4. User Notification
    toast.show displays a real-time notification to inform the user of the updated appointment status.

πŸ“± Mobile-Specific Features​

Touch Interactions​

// Appointment card touch interactions
const useAppointmentGestures = () => {
const handleSwipeLeft = (appointment: Appointment) => {
// Swipe left to cancel
showCancelConfirmation(appointment);
};

const handleSwipeRight = (appointment: Appointment) => {
// Swipe right to reschedule
showRescheduleOptions(appointment);
};

const handleLongPress = (appointment: Appointment) => {
// Long press to show options
showAppointmentOptions(appointment);
};

return {
handleSwipeLeft,
handleSwipeRight,
handleLongPress
};
};

πŸ“± Explanation: Mobile-Specific Touch Interactions​

  1. Gesture Hook
    useAppointmentGestures manages touch-based interactions for appointment cards.

  2. Swipe Left β†’ Cancel
    Triggers showCancelConfirmation(appointment) to confirm appointment cancellation.

  3. Swipe Right β†’ Reschedule
    Opens showRescheduleOptions(appointment) for choosing a new time slot.

  4. Long Press β†’ Options Menu
    Invokes showAppointmentOptions(appointment) to display additional actions.

Offline Appointment Management​

// Offline appointment capabilities
const useOfflineAppointments = () => {
const [offlineAppointments, setOfflineAppointments] = useState([]);

const saveOfflineAppointment = (appointment: Appointment) => {
const offlineAppointment = {
...appointment,
id: `offline_${Date.now()}`,
isOffline: true,
createdAt: new Date().toISOString()
};

setOfflineAppointments(prev => [...prev, offlineAppointment]);
mmkvStorage.set('offline_appointments', JSON.stringify([...offlineAppointments, offlineAppointment]));
};

const syncOfflineAppointments = async () => {
if (offlineAppointments.length === 0) return;

try {
for (const appointment of offlineAppointments) {
await createAppointment(appointment);
}

setOfflineAppointments([]);
mmkvStorage.delete('offline_appointments');

toast.show({ text: 'Offline appointments synced successfully' });
} catch (error) {
toast.show({ text: 'Failed to sync offline appointments' });
}
};

// Offline appointment management with comprehensive sync logic
const saveOfflineAppointment = (appointment: Appointment) => {
const offlineAppointment = {
...appointment,
id: `offline_${Date.now()}`,
isOffline: true,
createdAt: new Date().toISOString()
};

setOfflineAppointments(prev => [...prev, offlineAppointment]);
mmkvStorage.set('offline_appointments', JSON.stringify([...offlineAppointments, offlineAppointment]));
};

const syncOfflineAppointments = async () => {
if (offlineAppointments.length === 0) return;

try {
for (const appointment of offlineAppointments) {
await createAppointment(appointment);
}

setOfflineAppointments([]);
mmkvStorage.delete('offline_appointments');

toast.show({ text: 'Offline appointments synced successfully' });
} catch (error) {
toast.show({ text: 'Failed to sync offline appointments' });
}
};
};

βš™οΈ Explanation: Offline Appointment Management​

  1. Offline State Handling
    offlineAppointments stores appointments created while offline using React state.

  2. Save Offline Appointment
    saveOfflineAppointment()

    • Generates a temporary ID (offline_<timestamp>)
    • Marks the appointment as offline (isOffline: true)
    • Persists data locally using mmkvStorage for reliability.
  3. Sync Logic
    syncOfflineAppointments()

    • Checks if any offline appointments exist.
    • Attempts to sync each appointment to the server using createAppointment().
    • On success, clears both local state and stored data.
  4. User Feedback

    • Shows a success toast on successful sync.
    • Displays an error toast if syncing fails.
  5. Purpose
    Enables seamless appointment creation and recovery even without an internet connection.

🎨 UI Components​

Appointment Card​

// AppointmentCard.tsx
const AppointmentCard = ({
appointment,
onReschedule,
onCancel,
onJoin
}) => {
const { colors } = useTheme<Theme>();

return (
<TouchableOpacity
style={[
styles.appointmentCard,
{ borderLeftColor: getStatusColor(appointment.status) }
]}
onPress={() => onJoin(appointment)}
>
<Box flexDirection="row" alignItems="center">
<FastImage
source={{ uri: appointment.img }}
style={styles.therapistImage}
/>

<Box flex={1} marginLeft="m">
<Text variant="heading5">{appointment.name}</Text>
<Text variant="body2" color="secondary63">
Child: {appointment.child}
</Text>
<Text variant="body2" color="secondary63">
{appointment.degree} β€’ {appointment.specialty}
</Text>
</Box>

<Box alignItems="flex-end">
<Text variant="body2" color="primary36">
{format(appointment.date, 'MMM dd')}
</Text>
<Text variant="caption">
{appointment.timeSlot}
</Text>
</Box>
</Box>

<Box flexDirection="row" justifyContent="space-between" marginTop="m">
<StatusBadge status={appointment.status} />

<Box flexDirection="row">
<TouchableOpacity
onPress={() => onReschedule(appointment.id)}
style={[styles.actionButton, styles.rescheduleButton]}
>
<Text color="primary36">Reschedule</Text>
</TouchableOpacity>

<TouchableOpacity
onPress={() => onCancel(appointment.id)}
style={[styles.actionButton, styles.cancelButton]}
>
<Text color="error">Cancel</Text>
</TouchableOpacity>
</Box>
</Box>
</TouchableOpacity>
);
};

🎨 Explanation: Appointment Card UI Component​

  1. Purpose
    Displays a single appointment with therapist info, status, and quick actions (join, reschedule, cancel).

  2. Main Layout
    Wrapped in a TouchableOpacity to make the entire card tappable β€” triggers onJoin(appointment) when pressed.

  3. Therapist Info Section

    • FastImage shows the therapist’s profile picture.
    • Displays therapist name, child’s name, degree, and specialty using styled Text components.
  4. Date & Time Display

    • Shows appointment date (MMM dd) and time slot aligned to the right.
  5. Status and Actions

    • StatusBadge indicates current appointment status (e.g., Pending, Confirmed, Cancelled).
    • Includes Reschedule and Cancel buttons with respective handlers:
      • onReschedule(appointment.id)
      • onCancel(appointment.id)
  6. Styling

    • Dynamic left border color based on appointment status (getStatusColor).
    • Responsive layout using Box for flexible alignment and spacing.

βœ… Provides an interactive, visually clear appointment summary optimized for mobile use.

πŸ”§ GraphQL Integration​

Appointment Queries​

# Get upcoming appointments
query GetUpcomingAppointments($parent_id: String!, $date: date_comparison_exp) {
appointment(
where: {
child: { parent_id: { _eq: $parent_id } }
date: $date
status: { _in: ["SCHEDULED", "CONFIRMED"] }
}
order_by: { date: asc }
) {
id
date
status
appointment_type
child { name }
therapist {
user { name profile_picture { path } }
degree_name
therapist_specialities {
speciality { title }
}
}
appointment_slots {
timeSlot {
StartTime
EndTime
}
}
}
}

# Create appointment
mutation CreateAppointment($child_id: uuid!, $therapist_id: uuid!, $date: date!, $time_slot_id: uuid!, $appointment_type: String!) {
createAppointment(
child_id: $child_id
therapist_id: $therapist_id
date: $date
time_slot_id: $time_slot_id
appointment_type: $appointment_type
) {
success
message
data {
order_id
appointment_id
}
}
}

πŸ”§ Explanation: GraphQL Appointment Integration​

  1. πŸ“… Get Upcoming Appointments (GetUpcomingAppointments)

    • Purpose: Fetches all upcoming appointments for a specific parent.
    • Filters:
      • parent_id: Matches appointments linked to the parent’s children.
      • date: Can be dynamically filtered using date_comparison_exp (e.g., future dates).
      • status: Includes only "SCHEDULED" and "CONFIRMED" appointments.
    • Sorting: Orders results by date (ascending).
    • Fetched Fields:
      • Appointment Info: id, date, status, appointment_type.
      • Child Info: Child’s name.
      • Therapist Info: Name, profile picture, degree, and specialties.
      • Time Slot Info: Start and end times from appointment_slots.
  2. βž• Create Appointment (CreateAppointment)

    • Purpose: Creates a new appointment entry.
    • Parameters:
      • child_id: ID of the child for whom the appointment is booked.
      • therapist_id: ID of the assigned therapist.
      • date: Appointment date.
      • time_slot_id: Chosen time slot.
      • appointment_type: Type of session (e.g., "Online" or "In-Person").
    • Response:
      • success: Boolean indicating success status.
      • message: Operation result message.
      • data: Contains order_id and appointment_id for reference.

βœ… Together, these queries and mutations form the backbone of the app’s appointment scheduling and management workflow.

Payment Mutations​

# Confirm appointment payment
mutation ConfirmAppointmentPayment($order_id: String!, $payment_id: String!, $payment_signature: String!) {
confirmAppointmentPayment(
order_id: $order_id
payment_id: $payment_id
payment_signature: $payment_signature
) {
success
message
}
}

# Cancel appointment
mutation CancelAppointment($appointment_id: uuid!, $reason: String!) {
cancelAppointment(
appointment_id: $appointment_id
reason: $reason
) {
success
message
}
}

πŸ’³ Explanation: Payment & Cancellation Mutations​

  1. βœ… Confirm Appointment Payment (ConfirmAppointmentPayment)

    • Purpose: Confirms that a payment for an appointment has been successfully processed.
    • Parameters:
      • order_id: Unique ID for the payment order.
      • payment_id: Payment provider transaction ID.
      • payment_signature: Signature for verifying payment authenticity.
    • Response:
      • success: Boolean indicating whether payment confirmation succeeded.
      • message: Provides additional status or error information.
  2. ❌ Cancel Appointment (CancelAppointment)

    • Purpose: Cancels an existing appointment.
    • Parameters:
      • appointment_id: ID of the appointment to cancel.
      • reason: Reason for cancellation.
    • Response:
      • success: Boolean indicating if cancellation was successful.
      • message: Provides confirmation or error details.

βœ… Use Case: These mutations handle both payment verification and appointment lifecycle management, ensuring data consistency and user transparency.

🎯 Best Practices​

Appointment Management​

  • Payment Security: Secure payment processing with proper validation
  • Status Tracking: Clear appointment status management
  • Real-time Updates: Live appointment status synchronization
  • Offline Support: Allow offline appointment management

User Experience​

  • Visual Feedback: Clear status indicators and payment confirmations
  • Touch Interactions: Intuitive swipe and tap gestures
  • Payment Flow: Smooth payment processing experience
  • Notifications: Timely appointment notifications

Performance​

  • Payment Optimization: Fast payment processing
  • Data Caching: Cache appointment data locally
  • Lazy Loading: Load appointments on demand
  • Background Sync: Sync data in background

🎯 Next Steps​