import { useState, useEffect } from 'react' import { createFileRoute, useNavigate } from '@tanstack/react-router' import { useQuery, useMutation } from '@tanstack/react-query' import { globalMemberListOptions } from '@/api/members' import { scheduleSlotListOptions, enrollmentMutations, instructorListOptions, lessonTypeListOptions } from '@/api/lessons' import { Button } from '@/components/ui/button' 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 { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { ArrowLeft, Search, X } from 'lucide-react' import { toast } from 'sonner' import type { MemberWithAccount } from '@/api/members' import type { ScheduleSlot, LessonType, Instructor } from '@/types/lesson' export const Route = createFileRoute('/_authenticated/lessons/enrollments/new')({ validateSearch: (search: Record) => ({ memberId: (search.memberId as string) || undefined, accountId: (search.accountId as string) || undefined, }), component: NewEnrollmentPage, }) const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] 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)' }, ] as const function formatSlotLabel(slot: ScheduleSlot, instructors: Instructor[], lessonTypes: LessonType[]) { const instructor = instructors.find((i) => i.id === slot.instructorId) const lessonType = lessonTypes.find((lt) => lt.id === slot.lessonTypeId) const [h, m] = slot.startTime.split(':').map(Number) const ampm = h >= 12 ? 'PM' : 'AM' const hour = h % 12 || 12 const time = `${hour}:${String(m).padStart(2, '0')} ${ampm}` const day = DAYS[slot.dayOfWeek] return `${day} ${time} — ${lessonType?.name ?? 'Unknown'} (${instructor?.displayName ?? 'Unknown'})` } /** Returns the preset rate for a given cycle from slot (falling back to lesson type) */ function getPresetRate( billingInterval: string, billingUnit: string, slot: ScheduleSlot | undefined, lessonType: LessonType | undefined, ): string { if (!slot) return '' const isPreset = billingInterval === '1' if (!isPreset) return '' if (billingUnit === 'week') return slot.rateWeekly ?? lessonType?.rateWeekly ?? '' if (billingUnit === 'month') return slot.rateMonthly ?? lessonType?.rateMonthly ?? '' if (billingUnit === 'quarter') return slot.rateQuarterly ?? lessonType?.rateQuarterly ?? '' return '' } function NewEnrollmentPage() { const navigate = useNavigate() const [memberSearch, setMemberSearch] = useState('') const [showMemberDropdown, setShowMemberDropdown] = useState(false) const [selectedMember, setSelectedMember] = useState(null) const [selectedSlotId, setSelectedSlotId] = useState('') const [startDate, setStartDate] = useState('') const [billingInterval, setBillingInterval] = useState('1') const [billingUnit, setBillingUnit] = useState('month') const [rate, setRate] = useState('') const [rateManual, setRateManual] = useState(false) const [notes, setNotes] = useState('') const { data: membersData } = useQuery( globalMemberListOptions({ page: 1, limit: 20, q: memberSearch || undefined, order: 'asc', sort: 'first_name' }), ) const { data: slotsData } = useQuery(scheduleSlotListOptions({ page: 1, limit: 100, order: 'asc' })) const { data: instructorsData } = useQuery(instructorListOptions({ page: 1, limit: 100, order: 'asc' })) const { data: lessonTypesData } = useQuery(lessonTypeListOptions({ page: 1, limit: 100, order: 'asc' })) const slots = slotsData?.data?.filter((s) => s.isActive) ?? [] const instructors = instructorsData?.data ?? [] const lessonTypes = lessonTypesData?.data ?? [] const selectedSlot = slots.find((s) => s.id === selectedSlotId) const selectedLessonType = lessonTypes.find((lt) => lt.id === selectedSlot?.lessonTypeId) // Auto-fill rate from slot/lesson-type presets when slot or cycle changes, unless user has manually edited useEffect(() => { if (rateManual) return const preset = getPresetRate(billingInterval, billingUnit, selectedSlot, selectedLessonType) setRate(preset ? String(preset) : '') }, [selectedSlotId, billingInterval, billingUnit, selectedSlot, selectedLessonType, rateManual]) const mutation = useMutation({ mutationFn: async (data: Record) => { const enrollment = await enrollmentMutations.create(data) try { await enrollmentMutations.generateSessions(enrollment.id, 4) } catch { // non-fatal — sessions can be generated later } return enrollment }, onSuccess: (enrollment) => { toast.success('Enrollment created') navigate({ to: '/lessons/enrollments/$enrollmentId', params: { enrollmentId: enrollment.id }, search: {} as any }) }, onError: (err) => toast.error(err.message), }) function selectMember(member: MemberWithAccount) { setSelectedMember(member) setShowMemberDropdown(false) setMemberSearch('') } function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!selectedMember || !selectedSlotId || !startDate) return mutation.mutate({ memberId: selectedMember.id, accountId: selectedMember.accountId, scheduleSlotId: selectedSlotId, instructorId: selectedSlot?.instructorId, startDate, rate: rate || undefined, billingInterval: billingInterval ? Number(billingInterval) : undefined, billingUnit: billingUnit || undefined, notes: notes || undefined, }) } const members = membersData?.data ?? [] return (

New Enrollment

{/* Student */} Student {!selectedMember ? (
{ setMemberSearch(e.target.value); setShowMemberDropdown(true) }} onFocus={() => setShowMemberDropdown(true)} className="pl-9" />
{showMemberDropdown && memberSearch.length > 0 && (
{members.length === 0 ? (
No members found
) : ( members.map((m) => ( )) )}
)}
) : (

{selectedMember.firstName} {selectedMember.lastName}

{selectedMember.accountName && (

{selectedMember.accountName}

)}
)}
{/* Schedule Slot */} Schedule Slot
{/* Terms */} Terms
setStartDate(e.target.value)} required className="max-w-xs" />
{ setBillingInterval(e.target.value); setRateManual(false) }} className="w-20" />
$ { setRate(e.target.value); setRateManual(true) }} placeholder="Auto-filled from slot" />
{!rateManual && rate && (

Auto-filled from slot rates

)}