📋 Parent Assessments
This document covers the comprehensive assessment system for parents in the Com DEALL mobile application, including assessment taking, resuming, retaking, progress tracking, and results management.
🎯 Overview
The parent assessment system enables parents to take assessments for their children, track progress, resume incomplete assessments, and retake assessments as needed. The system supports multiple assessment types and provides detailed progress tracking.
🏗️ Architecture
Assessment System Components
Parent Assessment System
├── Assessment Taking
├── Progress Tracking
├── Resume Functionality
├── Retake Management
├── Results & Reports
└── Assessment Analytics
Data Flow
Assessment Selection → Question Navigation → Answer Submission → Progress Save → Results Generation
↓ ↓ ↓ ↓ ↓
Parent Action → Question Flow → Local Storage → Database Update → Report Generation
📱 Mobile Implementation
Parent Assessment Business Logic
Assessment All Tab Logic
Assessment Display Logic:
- Shows assessments with
assessor=parentand child age as filters - Displays assessments irrespective of child's subscription plan
- Implements comprehensive locking mechanism based on screening completion and subscription status
- Maintains specific ordering: Screening → Case History → CDDC (GM, FM, ADL, RL, EL, Cog, Soc, Emo) → PLS → ORO-Motor → Others
Locking Mechanism Logic:
- Screening Not Completed: Everything locked except Case History
- Screening Completed: Assessments linked to child subscription are unlocked
- Subscription Cancelled/Expired: All previously attempted assessments become locked
- Unlocked First: Unlocked assessments shown at top, locked ones below
- CDDC Question Count: Dynamic question count based on child's age
Subscription Status Logic:
- Active Subscription: Unlocks subscribed assessments after screening completion
- Expired/Cancelled Subscription: Locks all previously accessible assessments
- Real-time Updates: Updates lock status when subscription status changes
// ParentAssessmentAll.tsx - Assessment All tab with comprehensive locking logic
const ParentAssessmentAll = ({ navigation, route }: ScreenProps) => {
const { childId } = route.params;
const [filter, setFilter] = useState<AssessmentFilter>({
assessor: 'parent',
age: childAge,
category: 'ALL'
});
// Get child subscription details
const { data: childSubscriptionData } = useGetChildSubscriptionQuery({
variables: { child_id: childId },
fetchPolicy: 'cache-and-network'
});
// Get all assessments with parent assessor and age filtering
const { data: assessmentsData, loading, error, refetch } = useGetParentAssessmentsQuery({
variables: {
child_id: childId,
assessor: 'parent',
child_age: childAge,
category: filter.category
},
fetchPolicy: 'cache-and-network'
});
// Assessment locking logic with comprehensive subscription validation
const assessmentAllData = useMemo(() => {
const assessments = assessmentsData?.assessments || [];
const subscription = childSubscriptionData?.child_subscription;
const isSubscriptionActive = subscription?.status === 'ACTIVE';
const subscriptionFeatures = subscription?.features || [];
// Check if all Screening assessments are completed
const allScreeningCompleted = assessments
?.filter(item => item?.type === AssessmentType.Screening)
?.every(item =>
item?.assessment_responses?.some(
response => response?.status === UserAssessmentStatus.Completed,
),
);
// Process assessments with locking logic
const processedAssessments = assessments.map(item => {
let isLocked = true;
// Always unlock Screening and Case History
if (
item?.type === AssessmentType.Screening ||
item?.type === AssessmentType.CaseHistory
) {
isLocked = false;
}
// Unlock subscribed assessments after screening completion
if (allScreeningCompleted && item?.subscribed && isSubscriptionActive) {
isLocked = false;
}
// Lock all assessments if subscription is cancelled/expired
if (!isSubscriptionActive && item?.subscribed) {
isLocked = true;
}
return { ...item, isLocked };
});
// Sort assessments: unlocked first, then locked, maintaining category order
const categoryOrder = [
'Screening',
'Case History',
'CDDC_GM',
'CDDC_FM',
'CDDC_ADL',
'CDDC_RL',
'CDDC_EL',
'CDDC_Cog',
'CDDC_Soc',
'CDDC_Emo',
'PLS',
'ORO_Motor',
'Others'
];
const unlockedAssessments = processedAssessments
.filter(item => !item.isLocked)
.sort((a, b) => {
const aIndex = categoryOrder.indexOf(a.category);
const bIndex = categoryOrder.indexOf(b.category);
if (aIndex !== bIndex) {
return aIndex - bIndex;
}
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
});
const lockedAssessments = processedAssessments
.filter(item => item.isLocked)
.sort((a, b) => {
const aIndex = categoryOrder.indexOf(a.category);
const bIndex = categoryOrder.indexOf(b.category);
if (aIndex !== bIndex) {
return aIndex - bIndex;
}
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
});
return [...unlockedAssessments, ...lockedAssessments];
}, [assessmentsData, childSubscriptionData]);
// CDDC question count based on child age
const getCDDCQuestionCount = (assessment: Assessment) => {
if (assessment.category.startsWith('CDDC')) {
if (childAge <= 24) return 15; // 0-2 years
if (childAge <= 60) return 20; // 2-5 years
if (childAge <= 96) return 25; // 5-8 years
return 30; // 8+ years
}
return assessment.question_count;
};
};
Assessment Assigned Tab Logic
Assigned Assessment Display Logic:
- Shows all assessments assigned to child by Therapist or Admin
- Everything is unlocked regardless of subscription status
- Ordered by assigned date with latest assigned at the top
- Displays assignment source and progress status
Assignment Source Tracking:
- Therapist Assignments: Shows assessments assigned by current or other therapists
- Admin Assignments: Shows assessments assigned by admin
- Assignment Date: Tracks when assessment was assigned
- Progress Status: Shows completion status and progress
// ParentAssessmentAssigned.tsx - Assigned assessments with source tracking
const ParentAssessmentAssigned = ({ navigation, route }: ScreenProps) => {
const { childId } = route.params;
// Get assigned assessments with source tracking
const { data: assignedAssessments, loading, refetch } = useGetAssignedAssessmentsQuery({
variables: {
child_id: childId,
include_all_assignments: true
},
fetchPolicy: 'cache-and-network'
});
// Process assigned assessments with source identification
const assessmentAssignedData = useMemo(() => {
const assessments = assignedAssessments?.assigned_assessments || [];
return assessments.map(item => {
const isCompleted = item?.assessment_responses?.some(
response => response?.status === UserAssessmentStatus.Completed,
);
const isInProgress = item?.assessment_responses?.some(
response => response?.status === UserAssessmentStatus.InProgress,
);
return {
...item,
isCompleted,
isInProgress,
canRetake: isCompleted && item?.allow_retake,
assignmentSource: getAssignmentSource(item.assigned_by),
assignedDate: item.assigned_at
};
}).sort((a, b) =>
new Date(b.assignedDate).getTime() - new Date(a.assignedDate).getTime()
);
}, [assignedAssessments]);
// Assignment source identification logic
const getAssignmentSource = (assignedBy: string) => {
if (assignedBy === 'ADMIN') return 'Assigned by Admin';
if (assignedBy === 'THERAPIST') return 'Assigned by Therapist';
return 'Assigned by System';
};
};
Parent Lesson Plan Business Logic
Lesson Plan All Tab Logic
Lesson Plan Display Logic:
- Shows lesson plans with
assessor=parentand child age as filters - Displays lesson plans irrespective of child's subscription plan
- Implements subscription-based locking mechanism
- Maintains specific ordering: Screening → Case History → CDDC (GM, FM, ADL, RL, EL, Cog, Soc, Emo) → PLS → ORO-Motor → Others
Subscription Locking Logic:
- Active Subscription: Unlocks lesson plans linked to child subscription
- No Subscription: Locks all lesson plans except free ones
- Unlocked First: Unlocked lesson plans shown at top, locked ones below
- Real-time Updates: Updates lock status when subscription changes
// ParentLessonPlanAll.tsx - Lesson plan All tab with subscription locking
const ParentLessonPlanAll = ({ navigation, route }: ScreenProps) => {
const { childId } = route.params;
const [filter, setFilter] = useState<LessonPlanFilter>({
assessor: 'parent',
age: childAge,
category: 'ALL'
});
// Get child subscription details
const { data: childSubscriptionData } = useGetChildSubscriptionQuery({
variables: { child_id: childId },
fetchPolicy: 'cache-and-network'
});
// Get all lesson plans with parent assessor and age filtering
const { data: lessonPlansData, loading, error, refetch } = useGetParentLessonPlansQuery({
variables: {
child_id: childId,
assessor: 'parent',
child_age: childAge,
category: filter.category
},
fetchPolicy: 'cache-and-network'
});
// Lesson plan locking logic with subscription validation
const lessonPlanAllData = useMemo(() => {
const lessonPlans = lessonPlansData?.lesson_plans || [];
const subscription = childSubscriptionData?.child_subscription;
const isSubscriptionActive = subscription?.status === 'ACTIVE';
const subscriptionFeatures = subscription?.features || [];
// Process lesson plans with locking logic
const processedLessonPlans = lessonPlans.map(item => {
let isLocked = true;
// Always unlock free lesson plans
if (item?.type === LessonPlanType.Free) {
isLocked = false;
}
// Unlock subscribed lesson plans if subscription is active
if (item?.subscribed && isSubscriptionActive) {
isLocked = false;
}
return { ...item, isLocked };
});
// Sort lesson plans: unlocked first, then locked, maintaining category order
const categoryOrder = [
'Screening',
'Case History',
'CDDC_GM',
'CDDC_FM',
'CDDC_ADL',
'CDDC_RL',
'CDDC_EL',
'CDDC_Cog',
'CDDC_Soc',
'CDDC_Emo',
'PLS',
'ORO_Motor',
'Others'
];
const unlockedLessonPlans = processedLessonPlans
.filter(item => !item.isLocked)
.sort((a, b) => {
const aIndex = categoryOrder.indexOf(a.category);
const bIndex = categoryOrder.indexOf(b.category);
if (aIndex !== bIndex) {
return aIndex - bIndex;
}
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
});
const lockedLessonPlans = processedLessonPlans
.filter(item => item.isLocked)
.sort((a, b) => {
const aIndex = categoryOrder.indexOf(a.category);
const bIndex = categoryOrder.indexOf(b.category);
if (aIndex !== bIndex) {
return aIndex - bIndex;
}
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
});
return [...unlockedLessonPlans, ...lockedLessonPlans];
}, [lessonPlansData, childSubscriptionData]);
};
Assessment Home Screen
// Assessment.tsx - Main assessment screen
const Assessment = ({ navigation }: Props) => {
const { activeChildId, setActiveChildId } = useChildContext();
const [index, setIndex] = React.useState(0);
const [page, setPage] = useState(1);
const [totalPages, setTotalPages] = useState(1);
const [filter, setFilter] = useState<AssessmentFilterType>(initialFilterState);
const userData = useSelector(selectUserData);
const {
data,
loading,
error,
refetch: refetchChildAssessmentData,
} = useGetActiveChildrenForAssessmentQuery({
fetchPolicy: 'cache-and-network',
variables: {
parent_id: userData?.parentId,
},
skip: !userData?.parentId,
});
const filteredChildren = useMemo(
() =>
data?.child.map(c => {
return {
name: c.name,
age: calculateAgeInMonths(c?.dob),
profile_image: {
path: c.profile_image?.path,
},
id: c.id,
};
}) ?? [],
[data?.child],
);
const selectedChild = useMemo(
() => filteredChildren.find(c => c.id === activeChildId),
[filteredChildren, activeChildId],
);
const {
data: rawAssessmentAllData,
loading: assessmentAllLoading,
error: assessmentsAllError,
refetch: refetchAll,
fetchMore: fetchMoreAssessments,
} = useGetAssessmentsForAllQuery({
variables: {
child_id: selectedChild?.id,
limit: 50,
page: 1,
filter_type: AllAssessmentsFilterType.All,
},
skip: !selectedChild?.id || !selectedChild?.age,
fetchPolicy: 'cache-and-network',
notifyOnNetworkStatusChange: true,
});
const assessmentAllData = useMemo(() => {
const assessments: AssessmentData[] = (rawAssessmentAllData
?.getAllAssessments?.data?.assessments ?? []) as AssessmentData[];
// Checking if all Screening assessments are completed
const allScreeningCompleted = assessments
?.filter(item => item?.type === AssessmentType.Screening)
?.every(item =>
item?.assessment_responses?.some(
response => response?.status === UserAssessmentStatus.Completed,
),
);
const allData =
assessments?.map(item => {
let isLocked = true;
if (
item?.type === AssessmentType.Screening ||
item?.type === AssessmentType?.CaseHistory
) {
isLocked = false;
}
if (allScreeningCompleted && item?.subscribed) {
isLocked = false;
}
return { ...item, isLocked };
}) ?? [];
return allData;
}, [rawAssessmentAllData?.getAllAssessments]);
const assessmentAssignedData = useMemo(() => {
const assessments: AssessmentData[] = (rawAssessmentAssignedData
?.getAssessmentsForAssigned?.data?.assessments ?? []) as AssessmentData[];
return assessments?.map(item => {
const isCompleted = item?.assessment_responses?.some(
response => response?.status === UserAssessmentStatus.Completed,
);
const isInProgress = item?.assessment_responses?.some(
response => response?.status === UserAssessmentStatus.InProgress,
);
return {
...item,
isCompleted,
isInProgress,
canRetake: isCompleted && item?.allow_retake,
};
});
}, [rawAssessmentAssignedData?.getAssessmentsForAssigned]);
const tabs = [
{ key: 'all', title: 'All Assessments', component: AllAssessments },
{ key: 'assigned', title: 'Assigned', component: AssignedAssessments },
];
// Assessment data processing with comprehensive filtering and locking logic
const assessmentAllData = useMemo(() => {
const assessments: AssessmentData[] = (rawAssessmentAllData
?.getAllAssessments?.data?.assessments ?? []) as AssessmentData[];
// Checking if all Screening assessments are completed
const allScreeningCompleted = assessments
?.filter(item => item?.type === AssessmentType.Screening)
?.every(item =>
item?.assessment_responses?.some(
response => response?.status === UserAssessmentStatus.Completed,
),
);
const allData =
assessments?.map(item => {
let isLocked = true;
if (
item?.type === AssessmentType.Screening ||
item?.type === AssessmentType?.CaseHistory
) {
isLocked = false;
}
if (allScreeningCompleted && item?.subscribed) {
isLocked = false;
}
return { ...item, isLocked };
}) ?? [];
return allData;
}, [rawAssessmentAllData?.getAllAssessments]);
};
Take Assessment Screen
Assessment Taking Logic:
- Question Navigation: Manages current question index and navigation between questions
- Answer Management: Tracks user answers using Record data structure for efficient lookups
- Time Management: Monitors time remaining for timed assessments and overall completion time
- Auto-Save Functionality: Automatically saves progress when answers are provided
- State Management: Handles submission and saving states to prevent duplicate operations
Question Flow Logic:
- Question Validation: Ensures all required questions are answered before submission
- Progress Calculation: Calculates completion percentage based on answered questions
- Navigation Control: Enables next/previous navigation with boundary checks
- Answer Tracking: Maintains record of all user answers for progress persistence
Data Persistence Logic:
- Local State Management: Manages current question index and answers in component state
- Server Synchronization: Syncs progress with server using GraphQL mutations
- Error Handling: Provides user feedback for save and submission operations
- Cache Management: Uses cache-and-network fetch policy for optimal data loading
// TakeAssessment.tsx - Assessment taking interface with comprehensive question management
const TakeAssessment = ({ navigation, route }: ScreenProps) => {
const { assessmentId, childId } = route.params;
// Question navigation state management
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [answers, setAnswers] = useState<Record<string, any>>({});
// Operation state management for UI feedback
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [timeRemaining, setTimeRemaining] = useState(0);
// Fetch assessment details with comprehensive data loading
const { data: assessmentData, loading, error, refetch } = useGetAssessmentDetailsQuery({
variables: { id: assessmentId },
fetchPolicy: 'cache-and-network'
});
// Extract assessment data and questions for processing
const assessment = assessmentData?.assessment_by_pk;
const questions = assessment?.questions || [];
const currentQuestion = questions[currentQuestionIndex];
// Progress saving mutation with comprehensive error handling
const [saveProgress] = useSaveAssessmentProgressMutation({
onCompleted: (data) => {
if (data.saveAssessmentProgress.success) {
toast.show({ text: 'Progress saved successfully' });
}
},
onError: (error) => {
toast.show({ text: 'Failed to save progress' });
}
});
// Assessment submission mutation with success/failure handling
const [submitAssessment] = useSubmitAssessmentMutation({
onCompleted: (data) => {
if (data.submitAssessment.success) {
toast.show({ text: 'Assessment submitted successfully' });
navigation.navigate('parent/assessment-results', {
assessmentId,
childId,
responseId: data.submitAssessment.response_id
});
} else {
toast.show({ text: data.submitAssessment.message || 'Failed to submit assessment' });
}
},
onError: (error) => {
toast.show({ text: error.message || 'Failed to submit assessment' });
}
});
// Answer handling logic with auto-save functionality
const handleAnswerChange = (questionId: string, answer: any) => {
setAnswers(prev => ({
...prev,
[questionId]: answer
}));
// Auto-save progress with current answer
saveProgress({
variables: {
assessment_id: assessmentId,
child_id: childId,
question_id: questionId,
answer: answer,
progress: (currentQuestionIndex + 1) / questions.length
}
});
};
// Navigation logic with boundary checks
const handleNext = () => {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(prev => prev + 1);
} else {
handleSubmit();
}
};
const handlePrevious = () => {
if (currentQuestionIndex > 0) {
setCurrentQuestionIndex(prev => prev - 1);
}
};
const handleSubmit = async () => {
setIsSubmitting(true);
try {
await submitAssessment({
variables: {
assessment_id: assessmentId,
child_id: childId,
answers: answers,
completion_time: new Date().toISOString()
}
});
} catch (error) {
console.error('Submit assessment error:', error);
} finally {
setIsSubmitting(false);
}
};
// Assessment taking logic with comprehensive progress tracking
const handleAnswerChange = (questionId: string, answer: any) => {
setAnswers(prev => ({
...prev,
[questionId]: answer
}));
// Auto-save progress
saveProgress({
variables: {
assessment_id: assessmentId,
child_id: childId,
question_id: questionId,
answer: answer,
progress: (currentQuestionIndex + 1) / questions.length
}
});
};
const handleNext = () => {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(prev => prev + 1);
} else {
handleSubmit();
}
};
const handlePrevious = () => {
if (currentQuestionIndex > 0) {
setCurrentQuestionIndex(prev => prev - 1);
}
};
const handleSubmit = async () => {
setIsSubmitting(true);
try {
await submitAssessment({
variables: {
assessment_id: assessmentId,
child_id: childId,
answers: answers,
completion_time: new Date().toISOString()
}
});
} catch (error) {
console.error('Submit assessment error:', error);
} finally {
setIsSubmitting(false);
}
};
const handleSaveAndExit = async () => {
setIsSaving(true);
try {
await saveProgress({
variables: {
assessment_id: assessmentId,
child_id: childId,
answers: answers,
progress: (currentQuestionIndex + 1) / questions.length,
status: 'IN_PROGRESS'
}
});
navigation.goBack();
} catch (error) {
console.error('Save progress error:', error);
} finally {
setIsSaving(false);
}
};
return (
<SafeAreaView style={commonFlexStyles.FLEX}>
<StatusBar backgroundColor={colors.white} barStyle={'dark-content'} />
<Box flex={1}>
<AssessmentHeader
onBack={() => navigation.goBack()}
title={assessment?.title}
progress={(currentQuestionIndex + 1) / questions.length}
timeRemaining={timeRemaining}
/>
<ScrollView>
<QuestionCard
question={currentQuestion}
answer={answers[currentQuestion.id]}
onChange={(answer) => handleAnswerChange(currentQuestion.id, answer)}
questionNumber={currentQuestionIndex + 1}
totalQuestions={questions.length}
/>
</ScrollView>
<AssessmentNavigation
onPrevious={handlePrevious}
onNext={handleNext}
onSaveAndExit={handleSaveAndExit}
isFirst={currentQuestionIndex === 0}
isLast={currentQuestionIndex === questions.length - 1}
isSubmitting={isSubmitting}
isSaving={isSaving}
/>
</Box>
</SafeAreaView>
);
};
Resume Assessment Screen
Resume Logic:
- Progress Restoration: Loads previously saved progress from server and restores user's position
- Answer Recovery: Restores all previously answered questions and their responses
- Seamless Continuation: Allows user to continue from where they left off without losing progress
- Data Validation: Ensures saved progress is valid and questions are still available
Progress Loading Logic:
- Server Synchronization: Fetches saved progress data using response ID for accurate restoration
- Question Index Calculation: Calculates current question index based on saved progress percentage
- Answer Restoration: Restores all previously answered questions for seamless continuation
- State Recovery: Recovers all assessment state including answers and progress
State Management Logic:
- Loading State: Shows loading indicator while fetching saved progress data
- Error Handling: Handles cases where saved progress is invalid or corrupted
- Data Validation: Validates that saved questions still exist in current assessment
- Fallback Logic: Provides fallback to beginning if saved progress is invalid
// ResumeAssessment.tsx - Resume incomplete assessment with comprehensive state restoration
const ResumeAssessment = ({ navigation, route }: ScreenProps) => {
const { assessmentId, childId, responseId } = route.params;
// State management for progress restoration
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
const [answers, setAnswers] = useState<Record<string, any>>({});
const [isLoading, setIsLoading] = useState(true);
// Fetch saved progress data with comprehensive error handling
const { data: progressData, loading, error } = useGetAssessmentProgressQuery({
variables: {
assessment_id: assessmentId,
child_id: childId,
response_id: responseId
},
fetchPolicy: 'cache-and-network'
});
// Extract assessment data and saved progress information
const assessment = progressData?.assessment_by_pk;
const questions = assessment?.questions || [];
const savedProgress = progressData?.assessment_response?.progress || 0;
const savedAnswers = progressData?.assessment_response?.answers || {};
// Progress restoration logic with comprehensive state management
useEffect(() => {
if (progressData) {
// Restore all previously answered questions
setAnswers(savedAnswers);
// Calculate current question index based on saved progress
setCurrentQuestionIndex(Math.floor(savedProgress * questions.length));
// Mark loading as complete
setIsLoading(false);
}
}, [progressData, savedProgress, savedAnswers, questions.length]);
// Progress saving mutation with success feedback
const [saveProgress] = useSaveAssessmentProgressMutation({
onCompleted: (data) => {
if (data.saveAssessmentProgress.success) {
toast.show({ text: 'Progress saved successfully' });
}
}
});
// Assessment submission mutation with navigation handling
const [submitAssessment] = useSubmitAssessmentMutation({
onCompleted: (data) => {
if (data.submitAssessment.success) {
toast.show({ text: 'Assessment completed successfully' });
navigation.navigate('parent/assessment-results', {
assessmentId,
childId,
responseId: data.submitAssessment.response_id
});
}
}
});
// Answer handling logic with auto-save functionality
const handleAnswerChange = (questionId: string, answer: any) => {
setAnswers(prev => ({
...prev,
[questionId]: answer
}));
// Auto-save progress with updated answer
saveProgress({
variables: {
assessment_id: assessmentId,
child_id: childId,
question_id: questionId,
answer: answer,
progress: (currentQuestionIndex + 1) / questions.length
}
});
};
// Navigation logic with boundary checks
const handleNext = () => {
if (currentQuestionIndex < questions.length - 1) {
setCurrentQuestionIndex(prev => prev + 1);
} else {
handleSubmit();
}
};
const handlePrevious = () => {
if (currentQuestionIndex > 0) {
setCurrentQuestionIndex(prev => prev - 1);
}
};
// Assessment submission logic with comprehensive error handling
const handleSubmit = async () => {
try {
await submitAssessment({
variables: {
assessment_id: assessmentId,
child_id: childId,
answers: answers,
completion_time: new Date().toISOString()
}
});
} catch (error) {
console.error('Submit assessment error:', error);
}
};
};
Retake Assessment Screen
Retake Logic:
- Permission Validation: Checks if assessment allows retaking based on
allow_retakeflag - Previous Response Handling: Manages previous completion data and creates new response record
- Confirmation Flow: Requires user confirmation before initiating retake process
- Fresh Start: Resets all progress and starts assessment from beginning
Retake Process Logic:
- Data Archival: Archives previous completion data for historical tracking
- New Response Creation: Creates new response record for retake attempt
- Progress Reset: Resets all progress indicators to initial state
- Navigation Handling: Navigates to fresh assessment taking experience
State Management Logic:
- Confirmation State: Manages confirmation dialog visibility and user interaction
- Loading State: Shows loading indicator during retake initiation process
- Error Handling: Handles retake failures and provides user feedback
- Success Handling: Navigates to assessment taking screen on successful retake initiation
// RetakeAssessment.tsx - Retake completed assessment with comprehensive validation
const RetakeAssessment = ({ navigation, route }: ScreenProps) => {
const { assessmentId, childId } = route.params;
const [showConfirmation, setShowConfirmation] = useState(false);
const [isRetaking, setIsRetaking] = useState(false);
// Get assessment details to check retake permissions
const { data: assessmentData, loading } = useGetAssessmentDetailsQuery({
variables: { id: assessmentId },
fetchPolicy: 'cache-and-network'
});
const assessment = assessmentData?.assessment_by_pk;
const canRetake = assessment?.allow_retake || false;
// Retake assessment mutation with comprehensive error handling
const [retakeAssessment, { loading: retakeLoading }] = useRetakeAssessmentMutation({
onCompleted: (data) => {
if (data.retakeAssessment.success) {
toast.show({ text: 'Assessment retake initiated successfully' });
navigation.navigate('parent/take-assessment', {
assessmentId,
childId,
responseId: data.retakeAssessment.response_id
});
} else {
toast.show({ text: data.retakeAssessment.message || 'Failed to retake assessment' });
}
},
onError: (error) => {
toast.show({ text: error.message || 'Failed to retake assessment' });
}
});
// Retake initiation logic with confirmation handling
const handleRetake = async () => {
if (!canRetake) {
toast.show({ text: 'This assessment does not allow retaking' });
return;
}
setIsRetaking(true);
try {
await retakeAssessment({
variables: {
assessment_id: assessmentId,
child_id: childId
}
});
} catch (error) {
console.error('Retake assessment error:', error);
} finally {
setIsRetaking(false);
}
};
// Confirmation dialog handling
const handleConfirmRetake = () => {
setShowConfirmation(true);
};
const handleCancelRetake = () => {
setShowConfirmation(false);
};
const handleConfirmRetakeAction = () => {
setShowConfirmation(false);
handleRetake();
};
};
}, [progressData, savedProgress, savedAnswers, questions.length]); };
### Retake Assessment Screen
```typescript
// RetakeAssessment.tsx - Retake completed assessment
const RetakeAssessment = ({ navigation, route }: ScreenProps) => {
const { assessmentId, childId } = route.params;
const [showConfirmation, setShowConfirmation] = useState(false);
const { data: assessmentData, loading } = useGetAssessmentDetailsQuery({
variables: { id: assessmentId },
fetchPolicy: 'cache-and-network'
});
const assessment = assessmentData?.assessment_by_pk;
const previousResults = assessment?.assessment_responses?.filter(
response => response?.status === 'COMPLETED'
);
const [retakeAssessment] = useRetakeAssessmentMutation({
onCompleted: (data) => {
if (data.retakeAssessment.success) {
toast.show({ text: 'Assessment retake initiated successfully' });
navigation.navigate('parent/take-assessment', {
assessmentId,
childId,
responseId: data.retakeAssessment.response_id
});
} else {
toast.show({ text: data.retakeAssessment.message || 'Failed to retake assessment' });
}
},
onError: (error) => {
toast.show({ text: error.message || 'Failed to retake assessment' });
}
});
const handleRetake = async () => {
try {
await retakeAssessment({
variables: {
assessment_id: assessmentId,
child_id: childId
}
});
} catch (error) {
console.error('Retake assessment error:', error);
}
};
// Retake assessment logic with confirmation
const handleConfirmRetake = () => {
setShowConfirmation(true);
};
};
📊 Assessment Progress Tracking
Progress Analytics
// Assessment progress analytics
const useAssessmentProgress = (childId: string) => {
const { data: progressData } = useGetAssessmentProgressAnalyticsQuery({
variables: {
child_id: childId,
date_range: {
start: format(subDays(new Date(), 30), 'YYYY-MM-DD'),
end: format(new Date(), 'YYYY-MM-DD')
}
}
});
const analytics = useMemo(() => {
if (!progressData) return null;
return {
totalAssessments: progressData.total_assessments,
completedAssessments: progressData.completed_assessments,
inProgressAssessments: progressData.in_progress_assessments,
averageScore: progressData.average_score,
improvementRate: progressData.improvement_rate,
strengths: progressData.strengths,
areasForImprovement: progressData.areas_for_improvement,
monthlyProgress: progressData.monthly_progress
};
}, [progressData]);
return analytics;
};
🔄 Real-time Updates
Assessment Notifications
// Real-time assessment updates
const useAssessmentSubscription = (childId: string) => {
const { data: subscriptionData } = useAssessmentUpdatesSubscription({
variables: { child_id: childId }
});
useEffect(() => {
if (subscriptionData?.assessment_updated) {
const update = subscriptionData.assessment_updated;
// Update local cache
updateAssessmentCache(update);
// Show notification
toast.show({
text: `Assessment ${update.status.toLowerCase()}`,
type: 'info'
});
}
}, [subscriptionData]);
};
📱 Mobile-Specific Features
Touch Interactions
// Assessment touch interactions
const useAssessmentGestures = () => {
const handleSwipeLeft = (assessment: Assessment) => {
// Swipe left to retake
showRetakeConfirmation(assessment);
};
const handleSwipeRight = (assessment: Assessment) => {
// Swipe right to resume
resumeAssessment(assessment);
};
const handleLongPress = (assessment: Assessment) => {
// Long press to show options
showAssessmentOptions(assessment);
};
return {
handleSwipeLeft,
handleSwipeRight,
handleLongPress
};
};
Offline Assessment Management
// Offline assessment capabilities
const useOfflineAssessments = () => {
const [offlineAssessments, setOfflineAssessments] = useState([]);
const saveOfflineAssessment = (assessment: Assessment) => {
const offlineAssessment = {
...assessment,
id: `offline_${Date.now()}`,
isOffline: true,
createdAt: new Date().toISOString()
};
setOfflineAssessments(prev => [...prev, offlineAssessment]);
mmkvStorage.set('offline_assessments', JSON.stringify([...offlineAssessments, offlineAssessment]));
};
const syncOfflineAssessments = async () => {
if (offlineAssessments.length === 0) return;
try {
for (const assessment of offlineAssessments) {
await submitAssessment(assessment);
}
setOfflineAssessments([]);
mmkvStorage.delete('offline_assessments');
toast.show({ text: 'Offline assessments synced successfully' });
} catch (error) {
toast.show({ text: 'Failed to sync offline assessments' });
}
};
return {
saveOfflineAssessment,
syncOfflineAssessments,
offlineAssessments
};
};
🎨 UI Components
Assessment Card
// AssessmentCard.tsx
const AssessmentCard = ({
assessment,
onTake,
onResume,
onRetake,
onViewResults
}) => {
const { colors } = useTheme<Theme>();
// Assessment card logic with comprehensive status management
const getStatusColor = (status: string) => {
switch (status) {
case 'COMPLETED': return colors.success;
case 'IN_PROGRESS': return colors.warning;
case 'PENDING': return colors.info;
default: return colors.secondary;
}
};
const handleCardPress = () => {
if (assessment.isCompleted) {
onViewResults(assessment);
} else if (assessment.isInProgress) {
onResume(assessment);
} else {
onTake(assessment);
}
};
};
🔧 GraphQL Integration
Assessment Queries
# Get assessments for child
query GetAssessmentsForAll($child_id: uuid!, $limit: Int!, $page: Int!, $filter_type: String!) {
getAllAssessments(
child_id: $child_id
limit: $limit
page: $page
filter_type: $filter_type
) {
success
data {
assessments {
id
title
description
type
difficulty
estimated_duration
questions {
id
question_text
question_type
options
}
assessment_responses {
id
status
score
progress
answers
}
}
}
}
}
# Get assessment details
query GetAssessmentDetails($id: uuid!) {
assessment_by_pk(id: $id) {
id
title
description
type
difficulty
estimated_duration
questions {
id
question_text
question_type
options
required
}
}
}
Assessment Mutations
# Save assessment progress
mutation SaveAssessmentProgress($assessment_id: uuid!, $child_id: uuid!, $question_id: uuid!, $answer: jsonb!, $progress: Float!) {
saveAssessmentProgress(
assessment_id: $assessment_id
child_id: $child_id
question_id: $question_id
answer: $answer
progress: $progress
) {
success
message
}
}
# Submit assessment
mutation SubmitAssessment($assessment_id: uuid!, $child_id: uuid!, $answers: jsonb!, $completion_time: timestamptz!) {
submitAssessment(
assessment_id: $assessment_id
child_id: $child_id
answers: $answers
completion_time: $completion_time
) {
success
message
response_id
}
}
# Retake assessment
mutation RetakeAssessment($assessment_id: uuid!, $child_id: uuid!) {
retakeAssessment(
assessment_id: $assessment_id
child_id: $child_id
) {
success
message
response_id
}
}
🎯 Best Practices
Assessment Management
- Progress Tracking: Save progress automatically
- Resume Functionality: Allow resuming incomplete assessments
- Retake Management: Enable retaking completed assessments
- Real-time Updates: Live progress synchronization
User Experience
- Visual Feedback: Clear progress indicators and status
- Touch Interactions: Intuitive assessment navigation
- Offline Support: Allow offline assessment taking
- Notifications: Timely assessment reminders
Performance
- Data Caching: Cache assessment data locally
- Lazy Loading: Load assessments on demand
- Optimistic Updates: Update UI before server confirmation
- Background Sync: Sync progress in background