From 2d14990a1bf7dd0a04f1bf62eecf0e5127dd71f2 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 7 Aug 2026 04:12:31 +0000 Subject: [PATCH] Modified by www.SourceFiles.app --- src/components/plans.tsx | 1654 -------------------------------------- 1 file changed, 1654 deletions(-) delete mode 100644 src/components/plans.tsx diff --git a/src/components/plans.tsx b/src/components/plans.tsx deleted file mode 100644 index 0483c6b..0000000 --- a/src/components/plans.tsx +++ /dev/null @@ -1,1654 +0,0 @@ -import { useQuery, 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 { Textarea } from "@/components/ui/textarea"; -import { Badge } from "@/components/ui/badge"; -import { Progress } from "@/components/ui/progress"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { - Plus, - Trash2, - Pencil, - Check, - Lock, - FileText, - Target, - CalendarClock, - ClipboardList, - Loader2, - Users, -} from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; -import { - ACCOMMODATION_CATEGORIES, - GOAL_STATUS_LABEL, - MEETING_TYPE_LABEL, - PLAN_STATUS_LABEL, - PLAN_TYPE_LABEL, - COMPLIANCE_CLASS, - accommodationLabel, - complianceLabel, - complianceState, - daysUntil, - defaultReviewDates, - formatDate, - todayISO, - type GoalStatus, - type MeetingType, - type PlanStatus, - type PlanType, -} from "@/lib/plans"; - -type PlanRow = { - id: string; - student_id: string; - plan_type: PlanType; - status: PlanStatus; - case_manager_id: string | null; - effective_date: string | null; - end_date: string | null; - next_annual_review_date: string | null; - next_reevaluation_date: string | null; -}; - -const err = (e: unknown) => toast.error((e as Error).message); - -// ── Small shared bits ─────────────────────────────────────────────────────── -function SectionCard({ - title, - icon: Icon, - confidential, - action, - children, -}: { - title: string; - icon: typeof Target; - confidential?: boolean; - action?: React.ReactNode; - children: React.ReactNode; -}) { - return ( -
-
-

- {title} - {confidential && ( - - Confidential - - )} -

- {action} -
- {children} -
- ); -} - -function Empty({ children }: { children: React.ReactNode }) { - return

{children}

; -} - -export function ComplianceDate({ date, label }: { date: string | null; label: string }) { - const state = complianceState(date); - return ( -
-
{label}
-
{formatDate(date)}
- {state !== "none" && ( -
- {complianceLabel(state, daysUntil(date))} -
- )} -
- ); -} - -// Staff list for the case-manager picker. profiles/user_roles are admin-readable -// only, so this returns empty for everyone else — the picker is admin-only UI. -function useStaff() { - return useQuery({ - queryKey: ["plan-staff"], - queryFn: async () => { - const { data: roles } = await supabase - .from("user_roles") - .select("user_id, role") - .in("role", ["admin", "teacher"]); - const ids = [...new Set((roles ?? []).map((r) => r.user_id))]; - if (!ids.length) - return [] as { id: string; full_name: string | null; email: string | null }[]; - const { data } = await supabase.from("profiles").select("id, full_name, email").in("id", ids); - return data ?? []; - }, - }); -} - -// ── Entry point: the Plans tab on a student ───────────────────────────────── -export function StudentPlansTab({ studentId, isAdmin }: { studentId: string; isAdmin: boolean }) { - const qc = useQueryClient(); - const [adding, setAdding] = useState(false); - - const { data: plans, isLoading } = useQuery({ - queryKey: ["student-plans", studentId], - queryFn: async () => { - const { data, error } = await supabase - .from("student_plans") - .select("*") - .eq("student_id", studentId) - .order("created_at", { ascending: false }); - if (error) throw error; - return (data ?? []) as PlanRow[]; - }, - }); - - if (isLoading) { - return ( -
- Loading plans… -
- ); - } - - return ( -
- {isAdmin && - (adding ? ( - { - setAdding(false); - qc.invalidateQueries({ queryKey: ["student-plans", studentId] }); - }} - /> - ) : ( - - ))} - {(plans ?? []).map((p) => ( - - ))} - {plans?.length === 0 && No 504 or IEP plan on file for this student.} -
- ); -} - -function NewPlanForm({ studentId, onDone }: { studentId: string; onDone: () => void }) { - const { data: staff } = useStaff(); - const [saving, setSaving] = useState(false); - const [f, setF] = useState({ - plan_type: "iep" as PlanType, - status: "draft" as PlanStatus, - case_manager_id: "", - effective_date: todayISO(), - next_annual_review_date: defaultReviewDates(todayISO()).annual, - next_reevaluation_date: defaultReviewDates(todayISO()).reeval, - }); - - const save = async () => { - setSaving(true); - try { - const { data: u } = await supabase.auth.getUser(); - const { error } = await supabase.from("student_plans").insert({ - student_id: studentId, - plan_type: f.plan_type, - status: f.status, - case_manager_id: f.case_manager_id || null, - effective_date: f.effective_date || null, - next_annual_review_date: f.next_annual_review_date || null, - next_reevaluation_date: f.next_reevaluation_date || null, - created_by: u.user?.id, - }); - if (error) throw error; - toast.success("Plan created"); - onDone(); - } catch (e) { - err(e); - } finally { - setSaving(false); - } - }; - - return ( -
-
New plan
-
-
- - -
-
- - -
-
- - -
-
-
- {/* Uncontrolled dates so Safari's native picker isn't reset by React. */} -
- - { - const v = e.target.value; - setF((prev) => - v - ? { - ...prev, - effective_date: v, - ...(() => { - const d = defaultReviewDates(v); - return { - next_annual_review_date: d.annual, - next_reevaluation_date: d.reeval, - }; - })(), - } - : { ...prev, effective_date: v }, - ); - }} - /> -
-
- - setF({ ...f, next_annual_review_date: e.target.value })} - /> -
-
- - setF({ ...f, next_reevaluation_date: e.target.value })} - /> -
-
-

- Review dates default to +1 year and +3 years from the effective date. Adjust to match the - district's actual timeline. -

-
- - -
-
- ); -} - -// ── One plan ──────────────────────────────────────────────────────────────── -function PlanCard({ plan, isAdmin }: { plan: PlanRow; isAdmin: boolean }) { - const qc = useQueryClient(); - const [editing, setEditing] = useState(false); - - // Ask the database who we are rather than re-deriving the tier rules here — - // these are the same predicates the RLS policies use. - const { data: access } = useQuery({ - queryKey: ["plan-access", plan.id], - queryFn: async () => { - const [full, edit] = await Promise.all([ - supabase.rpc("can_view_plan_full", { _plan: plan.id }), - supabase.rpc("can_edit_plan", { _plan: plan.id }), - ]); - return { full: full.data === true, edit: edit.data === true }; - }, - }); - const canViewFull = access?.full ?? false; - const canEdit = access?.edit ?? false; - - const { data: staff } = useStaff(); - const manager = (staff ?? []).find((s) => s.id === plan.case_manager_id); - - const invalidate = () => qc.invalidateQueries({ queryKey: ["student-plans", plan.student_id] }); - - const remove = async () => { - if (!confirm("Delete this plan and everything attached to it? This cannot be undone.")) return; - const { error } = await supabase.from("student_plans").delete().eq("id", plan.id); - if (error) return err(error); - toast.success("Plan deleted"); - invalidate(); - }; - - return ( -
-
-
-
- - {PLAN_TYPE_LABEL[plan.plan_type]} - - - {PLAN_STATUS_LABEL[plan.status]} - - - {formatDate(plan.effective_date)} - {plan.end_date ? ` – ${formatDate(plan.end_date)}` : ""} - -
-
- {canEdit && - (editing ? ( - - ) : ( - - ))} - {isAdmin && ( - - )} -
-
- -
- - -
-
Case manager
-
- {manager - ? manager.full_name || manager.email - : plan.case_manager_id - ? "Assigned" - : "Unassigned"} -
-
-
- - {editing && } -
- -
- - - {canViewFull ? ( - <> - - - - - - ) : ( -
- - - Eligibility, goals, meeting notes and documents are restricted to the case manager, - administrators, and the student's parents. You can see the accommodations and services - you are responsible for implementing. - -
- )} -
-
- ); -} - -function PlanHeaderEditor({ - plan, - staff, - onSaved, -}: { - plan: PlanRow; - staff: { id: string; full_name: string | null; email: string | null }[]; - onSaved: () => void; -}) { - const [f, setF] = useState({ - status: plan.status, - case_manager_id: plan.case_manager_id ?? "", - effective_date: plan.effective_date ?? "", - end_date: plan.end_date ?? "", - next_annual_review_date: plan.next_annual_review_date ?? "", - next_reevaluation_date: plan.next_reevaluation_date ?? "", - }); - const [saving, setSaving] = useState(false); - - const save = async () => { - setSaving(true); - try { - const { error } = await supabase - .from("student_plans") - .update({ - status: f.status, - case_manager_id: f.case_manager_id || null, - effective_date: f.effective_date || null, - end_date: f.end_date || null, - next_annual_review_date: f.next_annual_review_date || null, - next_reevaluation_date: f.next_reevaluation_date || null, - }) - .eq("id", plan.id); - if (error) throw error; - toast.success("Plan updated"); - onSaved(); - } catch (e) { - err(e); - } finally { - setSaving(false); - } - }; - - return ( -
-
-
- - -
-
- - -
-
- - setF({ ...f, effective_date: e.target.value })} - /> -
-
- - setF({ ...f, end_date: e.target.value })} - /> -
-
- - setF({ ...f, next_annual_review_date: e.target.value })} - /> -
-
- - setF({ ...f, next_reevaluation_date: e.target.value })} - /> -
-
- -
- ); -} - -// ── Accommodations (visible to implementing teachers) ─────────────────────── -function AccommodationsSection({ planId, canEdit }: { planId: string; canEdit: boolean }) { - const qc = useQueryClient(); - const key = ["plan-accommodations", planId]; - const { data } = useQuery({ - queryKey: key, - queryFn: async () => - ( - await supabase - .from("plan_accommodations") - .select("*") - .eq("plan_id", planId) - .order("sort_order") - ).data ?? [], - }); - const [draft, setDraft] = useState({ category: "instructional", description: "", setting: "" }); - const [adding, setAdding] = useState(false); - - const add = async () => { - if (!draft.description.trim()) return toast.error("Describe the accommodation"); - const { error } = await supabase.from("plan_accommodations").insert({ - plan_id: planId, - category: draft.category, - description: draft.description.trim(), - setting: draft.setting || null, - sort_order: data?.length ?? 0, - }); - if (error) return err(error); - setDraft({ category: "instructional", description: "", setting: "" }); - setAdding(false); - qc.invalidateQueries({ queryKey: key }); - }; - const remove = async (id: string) => { - const { error } = await supabase.from("plan_accommodations").delete().eq("id", id); - if (error) return err(error); - qc.invalidateQueries({ queryKey: key }); - }; - - return ( - setAdding(true)}> - Add - - ) : undefined - } - > - {adding && ( -
-
- - setDraft({ ...draft, setting: e.target.value })} - /> -
-