import { useState } from 'react' import { createFileRoute, useNavigate } from '@tanstack/react-router' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { enrollmentDetailOptions, enrollmentMutations, enrollmentKeys, sessionListOptions, lessonPlanListOptions, lessonPlanMutations, lessonPlanKeys, lessonPlanTemplateListOptions, lessonPlanTemplateMutations, instructorDetailOptions, scheduleSlotListOptions, lessonTypeListOptions, } from '@/api/lessons' import { DataTable, type Column } from '@/components/shared/data-table' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog' import { ArrowLeft, RefreshCw } from 'lucide-react' import { toast } from 'sonner' import { useAuthStore } from '@/stores/auth.store' import type { Enrollment, LessonSession, LessonPlan, LessonPlanTemplate } from '@/types/lesson' export const Route = createFileRoute('/_authenticated/lessons/enrollments/$enrollmentId')({ validateSearch: (search: Record) => ({ tab: (search.tab as string) || 'details', }), component: EnrollmentDetailPage, }) const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] function formatTime(t: string) { const [h, m] = t.split(':').map(Number) const ampm = h >= 12 ? 'PM' : 'AM' const hour = h % 12 || 12 return `${hour}:${String(m).padStart(2, '0')} ${ampm}` } function statusBadge(status: string) { const variants: Record = { active: 'default', paused: 'secondary', cancelled: 'destructive', completed: 'outline', } return {status} } function sessionStatusBadge(status: string) { const variants: Record = { scheduled: 'outline', attended: 'default', missed: 'destructive', makeup: 'secondary', cancelled: 'secondary', } return {status} } const sessionColumns: Column[] = [ { key: 'scheduled_date', header: 'Date', sortable: true, render: (s) => <>{new Date(s.scheduledDate + 'T00:00:00').toLocaleDateString()} }, { key: 'scheduled_time', header: 'Time', render: (s) => <>{formatTime(s.scheduledTime)} }, { key: 'status', header: 'Status', sortable: true, render: (s) => sessionStatusBadge(s.status) }, { key: 'substitute', header: 'Sub', render: (s) => s.substituteInstructorId ? Sub : null, }, { key: 'notes', header: 'Notes', render: (s) => s.notesCompletedAt ? Notes : null }, ] const TABS = [ { key: 'details', label: 'Details' }, { key: 'sessions', label: 'Sessions' }, { key: 'plan', label: 'Lesson Plan' }, ] function EnrollmentDetailPage() { const { enrollmentId } = Route.useParams() const search = Route.useSearch() const navigate = useNavigate() const queryClient = useQueryClient() const hasPermission = useAuthStore((s) => s.hasPermission) const canEdit = hasPermission('lessons.edit') const tab = search.tab function setTab(t: string) { navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId }, search: { tab: t } }) } const { data: enrollment, isLoading } = useQuery(enrollmentDetailOptions(enrollmentId)) const updateMutation = useMutation({ mutationFn: (data: Record) => enrollmentMutations.update(enrollmentId, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: enrollmentKeys.detail(enrollmentId) }) toast.success('Enrollment updated') }, onError: (err) => toast.error(err.message), }) const statusMutation = useMutation({ mutationFn: (status: string) => enrollmentMutations.updateStatus(enrollmentId, status), onSuccess: () => { queryClient.invalidateQueries({ queryKey: enrollmentKeys.detail(enrollmentId) }) toast.success('Status updated') }, onError: (err) => toast.error(err.message), }) const generateMutation = useMutation({ mutationFn: () => enrollmentMutations.generateSessions(enrollmentId, 4), onSuccess: (res) => { queryClient.invalidateQueries({ queryKey: ['lesson-sessions'] }) toast.success(`Generated ${res.generated} sessions`) }, onError: (err) => toast.error(err.message), }) const { data: instructorData } = useQuery({ ...instructorDetailOptions(enrollment?.instructorId ?? ''), enabled: !!enrollment?.instructorId, }) const { data: slotsData } = useQuery(scheduleSlotListOptions({ page: 1, limit: 100, order: 'asc' })) const { data: lessonTypesData } = useQuery(lessonTypeListOptions({ page: 1, limit: 100, order: 'asc' })) if (isLoading) return
Loading...
if (!enrollment) return
Enrollment not found.
const slot = slotsData?.data?.find((s) => s.id === enrollment.scheduleSlotId) const lessonType = lessonTypesData?.data?.find((lt) => lt.id === slot?.lessonTypeId) const slotLabel = slot ? `${DAYS[slot.dayOfWeek]} ${formatTime(slot.startTime)}${slot.room ? ` — ${slot.room}` : ''}` : '—' return (

Enrollment

{instructorData?.displayName ?? enrollment.instructorId} · {slotLabel}

{statusBadge(enrollment.status)}
{TABS.map((t) => ( ))}
{tab === 'details' && ( )} {tab === 'sessions' && ( )} {tab === 'plan' && }
) } // ─── Details Tab ────────────────────────────────────────────────────────────── const BILLING_UNITS = [ { value: 'day', label: 'Day(s)' }, { value: 'week', label: 'Week(s)' }, { value: 'month', label: 'Month(s)' }, { value: 'quarter', label: 'Quarter(s)' }, { value: 'year', label: 'Year(s)' }, ] function DetailsTab({ enrollment, slotLabel, lessonTypeName, instructorName, canEdit, onSave, saving, onStatusChange, statusChanging, }: { enrollment: Enrollment slotLabel: string lessonTypeName: string | undefined instructorName: string | undefined canEdit: boolean onSave: (data: Record) => void saving: boolean onStatusChange: (status: string) => void statusChanging: boolean }) { const [rate, setRate] = useState(enrollment.rate ?? '') const [billingInterval, setBillingInterval] = useState(String(enrollment.billingInterval ?? 1)) const [billingUnit, setBillingUnit] = useState(enrollment.billingUnit ?? 'month') const [notes, setNotes] = useState(enrollment.notes ?? '') const [endDate, setEndDate] = useState(enrollment.endDate ?? '') const NEXT_STATUSES: Record = { active: ['paused', 'cancelled', 'completed'], paused: ['active', 'cancelled'], cancelled: [], completed: [], } const nextStatuses = NEXT_STATUSES[enrollment.status] ?? [] return (

Instructor

{instructorName ?? enrollment.instructorId}

Slot

{slotLabel}

Lesson Type

{lessonTypeName ?? '—'}

Start Date

{new Date(enrollment.startDate + 'T00:00:00').toLocaleDateString()}

Billing Cycle

{enrollment.billingInterval ? `${enrollment.billingInterval} ${enrollment.billingUnit}(s)` : '—'}

Rate

{enrollment.rate ? `$${enrollment.rate}` : '—'}

Makeup Credits

{enrollment.makeupCredits}

{canEdit && ( <>
setBillingInterval(e.target.value)} className="w-20" />
$ setRate(e.target.value)} placeholder="Optional" />
setEndDate(e.target.value)} />