diff --git a/src/components/alerts.tsx b/src/components/alerts.tsx deleted file mode 100644 index b2ca1fb..0000000 --- a/src/components/alerts.tsx +++ /dev/null @@ -1,411 +0,0 @@ -import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; -import { supabase } from "@/integrations/supabase/client"; -import { useAuth } from "@/hooks/use-auth"; -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 { Checkbox } from "@/components/ui/checkbox"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { AlertTriangle, Check, Plus, ShieldAlert } from "lucide-react"; -import { useEffect, useRef, useState } from "react"; -import { toast } from "sonner"; -import type { Database } from "@/integrations/supabase/types"; - -type AlertRow = Database["public"]["Tables"]["student_alerts"]["Row"]; -type AppRole = Database["public"]["Enums"]["app_role"]; - -const ALERT_TYPES = [ - { value: "parenting_plan", label: "Parenting plan" }, - { value: "custody", label: "Custody limitation" }, - { value: "unauthorized_pickup", label: "Unauthorized pickup" }, - { value: "medical", label: "Medical emergency" }, - { value: "allergy", label: "Severe allergy" }, - { value: "court_order", label: "Court-ordered restriction" }, - { value: "safety", label: "Safety concern" }, - { value: "dismissal", label: "Special dismissal" }, - { value: "other", label: "Other" }, -]; - -const STAFF_ROLES: AppRole[] = [ - "admin", - "org_admin", - "super_admin", - "campus_admin", - "management", - "teacher", - "staff", -]; - -const severityCls = (s: string) => - s === "critical" - ? "border-rose-600 bg-rose-50 dark:bg-rose-950/40" - : s === "high" - ? "border-amber-500 bg-amber-50 dark:bg-amber-950/40" - : "border-slate-300 bg-muted/40"; - -function useActiveAlerts(studentId: string) { - return useQuery({ - queryKey: ["student-alerts-active", studentId], - queryFn: async () => { - const today = new Date().toISOString().slice(0, 10); - const { data, error } = await supabase - .from("student_alerts") - .select("*") - .eq("student_id", studentId) - .eq("is_active", true) - .lte("effective_date", today) - .or(`expiration_date.is.null,expiration_date.gte.${today}`) - .order("severity"); - if (error) throw error; - return (data ?? []) as AlertRow[]; - }, - }); -} - -/** - * The red banner. Sits above everything on the student profile because the spec - * requires these to be visible to every authorized staff member who interacts - * with the student — not filed behind a tab. - */ -export function StudentAlertsBanner({ studentId }: { studentId: string }) { - const { user } = useAuth(); - const qc = useQueryClient(); - const { data: alerts } = useActiveAlerts(studentId); - const logged = useRef(false); - - const { data: acks } = useQuery({ - queryKey: ["my-alert-acks", studentId, user?.id], - enabled: !!user, - queryFn: async () => - ( - await supabase - .from("student_alert_acknowledgments") - .select("alert_id") - .eq("user_id", user!.id) - ).data ?? [], - }); - const ackedIds = new Set((acks ?? []).map((a) => a.alert_id)); - - // Every display is auditable, per the spec. Logged once per mount rather - // than per render so a re-render does not inflate the record. - useEffect(() => { - if (!user || !alerts?.length || logged.current) return; - logged.current = true; - void supabase - .from("student_alert_views") - .insert(alerts.map((a) => ({ alert_id: a.id, user_id: user.id }))); - }, [alerts, user]); - - const acknowledge = useMutation({ - mutationFn: async (alertId: string) => { - const { error } = await supabase - .from("student_alert_acknowledgments") - .insert({ alert_id: alertId, user_id: user!.id }); - if (error) throw error; - }, - onSuccess: () => { - qc.invalidateQueries({ queryKey: ["my-alert-acks", studentId, user?.id] }); - toast.success("Acknowledged"); - }, - onError: (e: Error) => toast.error(e.message), - }); - - if (!alerts?.length) return null; - - return ( -
{a.description}
-- {ALERT_TYPES.find((t) => t.value === a.alert_type)?.label ?? a.alert_type} - {a.expiration_date && ` · until ${a.expiration_date}`} -
-- Narrow this for custody or court-order alerts that should not reach every teacher. -
-- {a.description} -
-- {a.effective_date} - {a.expiration_date && ` → ${a.expiration_date}`} · visible to{" "} - {(a.visible_to_roles ?? []).length} role - {(a.visible_to_roles ?? []).length === 1 ? "" : "s"} · {mine.length}{" "} - acknowledgment{mine.length === 1 ? "" : "s"} -
-No alerts recorded for this student.
- )} -