diff --git a/src/components/enrollment.tsx b/src/components/enrollment.tsx deleted file mode 100644 index 0833036..0000000 --- a/src/components/enrollment.tsx +++ /dev/null @@ -1,564 +0,0 @@ -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { supabase } from "@/integrations/supabase/client"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Checkbox } from "@/components/ui/checkbox"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { Pencil, Check, Plus, Trash2, Building2, Tag } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; -import type { Database } from "@/integrations/supabase/types"; - -type StudentUpdate = Database["public"]["Tables"]["students"]["Update"]; -type ScheduleUpdate = Database["public"]["Tables"]["student_campus_schedules"]["Update"]; - -const DAYS = [ - { key: "monday", short: "M" }, - { key: "tuesday", short: "T" }, - { key: "wednesday", short: "W" }, - { key: "thursday", short: "Th" }, - { key: "friday", short: "F" }, - { key: "saturday", short: "Sa" }, - { key: "sunday", short: "Su" }, -] as const; - -type DayKey = (typeof DAYS)[number]["key"]; - -const ENROLLMENT_STATUSES = [ - "prospective", - "enrolled", - "waitlisted", - "withdrawn", - "graduated", - "on_hold", -]; - -function EditToggle({ - editing, - setEditing, - canEdit, -}: { - editing: boolean; - setEditing: (v: boolean) => void; - canEdit: boolean; -}) { - if (!canEdit) return null; - return editing ? ( - - ) : ( - - ); -} - -export function StudentEnrollmentTab({ - studentId, - isAdmin, -}: { - studentId: string; - isAdmin: boolean; -}) { - return ( -
- - - -
- ); -} - -// ── Enrollment ────────────────────────────────────────────────────────────── -function EnrollmentCard({ studentId, canEdit }: { studentId: string; canEdit: boolean }) { - const qc = useQueryClient(); - const [editing, setEditing] = useState(false); - - const { data: student } = useQuery({ - queryKey: ["student-enrollment", studentId], - queryFn: async () => - ( - await supabase - .from("students") - .select( - "id, enrollment_status, enrollment_start_date, enrollment_end_date, attendance_basis, calendar_basis, is_support_student, primary_campus_id, tuition_tier_id", - ) - .eq("id", studentId) - .single() - ).data, - }); - const { data: campuses } = useQuery({ - queryKey: ["campuses"], - queryFn: async () => - (await supabase.from("campuses").select("id, name").order("name")).data ?? [], - }); - const { data: tiers } = useQuery({ - queryKey: ["tuition-tiers"], - queryFn: async () => - (await supabase.from("tuition_tiers").select("id, name").order("sort_order")).data ?? [], - }); - - const save = useMutation({ - mutationFn: async (patch: StudentUpdate) => { - const { error } = await supabase.from("students").update(patch).eq("id", studentId); - if (error) throw error; - }, - onSuccess: () => { - qc.invalidateQueries({ queryKey: ["student-enrollment", studentId] }); - qc.invalidateQueries({ queryKey: ["student", studentId] }); - }, - onError: (e: Error) => toast.error(e.message), - }); - - if (!student) return null; - - const campusName = (campuses ?? []).find((c) => c.id === student.primary_campus_id)?.name; - const tierName = (tiers ?? []).find((t) => t.id === student.tuition_tier_id)?.name; - - return ( -
-
-

- Enrollment -

- -
- - {student.enrollment_status !== "enrolled" && ( -

- Only students marked enrolled are picked up by invoice runs. -

- )} - - {editing ? ( -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - save.mutate({ enrollment_start_date: e.target.value || null })} - /> -
-
- - save.mutate({ enrollment_end_date: e.target.value || null })} - /> -
- -
- ) : ( -
- - - - - - - - -
- )} -
- ); -} - -function Row({ label, value }: { label: string; value?: string | null }) { - return ( -
- {label} - {value || "—"} -
- ); -} - -// ── Weekly campus schedule ────────────────────────────────────────────────── -function CampusScheduleCard({ studentId, canEdit }: { studentId: string; canEdit: boolean }) { - const qc = useQueryClient(); - const [editing, setEditing] = useState(false); - const [newCampus, setNewCampus] = useState(""); - - const { data: campuses } = useQuery({ - queryKey: ["campuses"], - queryFn: async () => - (await supabase.from("campuses").select("id, name").order("name")).data ?? [], - }); - const { data: rows } = useQuery({ - queryKey: ["student-schedules", studentId], - queryFn: async () => - ( - await supabase - .from("student_campus_schedules") - .select("*") - .eq("student_id", studentId) - .order("effective_start", { ascending: false }) - ).data ?? [], - }); - - const invalidate = () => { - qc.invalidateQueries({ queryKey: ["student-schedules", studentId] }); - }; - - const add = useMutation({ - mutationFn: async () => { - if (!newCampus) throw new Error("Pick a campus"); - const { error } = await supabase.from("student_campus_schedules").insert({ - student_id: studentId, - campus_id: newCampus, - assignment_type: (rows ?? []).length === 0 ? "primary" : "additional", - }); - if (error) throw error; - }, - onSuccess: () => { - setNewCampus(""); - invalidate(); - toast.success("Schedule added"); - }, - onError: (e: Error) => toast.error(e.message), - }); - - const patch = useMutation({ - mutationFn: async ({ id, values }: { id: string; values: ScheduleUpdate }) => { - const { error } = await supabase.from("student_campus_schedules").update(values).eq("id", id); - if (error) throw error; - }, - onSuccess: invalidate, - onError: (e: Error) => toast.error(e.message), - }); - - const remove = useMutation({ - mutationFn: async (id: string) => { - const { error } = await supabase.from("student_campus_schedules").delete().eq("id", id); - if (error) throw error; - }, - onSuccess: () => { - invalidate(); - toast.success("Schedule removed"); - }, - onError: (e: Error) => toast.error(e.message), - }); - - const campusName = (id: string) => (campuses ?? []).find((c) => c.id === id)?.name ?? "—"; - - return ( -
-
-

- Weekly campus schedule -

- -
-

- One row per campus. A student can attend more than one campus in the same week — billing - charges each campus at its own rate. -

- -
- {(rows ?? []).map((r) => ( -
-
- - - {campusName(r.campus_id)} - - {r.assignment_type} - - - - - {r.scheduled_days_per_week} day{r.scheduled_days_per_week === 1 ? "" : "s"}/week - - {editing && ( - - )} - -
- -
- {DAYS.map((d) => ( - - ))} -
- - {editing && ( -
-
- - - patch.mutate({ id: r.id, values: { effective_start: e.target.value } }) - } - /> -
-
- - - patch.mutate({ id: r.id, values: { effective_end: e.target.value || null } }) - } - /> -
- - -
- )} -
- ))} - - {(rows ?? []).length === 0 && ( -

- No campus schedule yet. Invoice runs skip students without one. -

- )} - - {editing && ( -
- - -
- )} -
-
- ); -} - -// ── Tags ──────────────────────────────────────────────────────────────────── -function TagsCard({ studentId, canEdit }: { studentId: string; canEdit: boolean }) { - const qc = useQueryClient(); - const [editing, setEditing] = useState(false); - - const { data: tags } = useQuery({ - queryKey: ["student-tags"], - queryFn: async () => - ( - await supabase - .from("student_tags") - .select("id, name") - .eq("is_active", true) - .order("sort_order") - ).data ?? [], - }); - const { data: assigned } = useQuery({ - queryKey: ["student-tag-assignments", studentId], - queryFn: async () => - ( - await supabase - .from("student_tag_assignments") - .select("id, tag_id") - .eq("student_id", studentId) - ).data ?? [], - }); - - const assignedIds = new Set((assigned ?? []).map((a) => a.tag_id)); - - const toggle = useMutation({ - mutationFn: async ({ tagId, on }: { tagId: string; on: boolean }) => { - if (on) { - const { error } = await supabase - .from("student_tag_assignments") - .insert({ student_id: studentId, tag_id: tagId }); - if (error) throw error; - } else { - const { error } = await supabase - .from("student_tag_assignments") - .delete() - .eq("student_id", studentId) - .eq("tag_id", tagId); - if (error) throw error; - } - }, - onSuccess: () => qc.invalidateQueries({ queryKey: ["student-tag-assignments", studentId] }), - onError: (e: Error) => toast.error(e.message), - }); - - return ( -
-
-

- Tags -

- -
- - {editing ? ( -
- {(tags ?? []).map((t) => ( - - ))} -
- ) : ( -
- {(tags ?? []) - .filter((t) => assignedIds.has(t.id)) - .map((t) => ( - - - {t.name} - - ))} - {(tags ?? []).filter((t) => assignedIds.has(t.id)).length === 0 && ( - No tags. - )} -
- )} -
- ); -}