From 894b0722e0f85d4fbf829ae36e1d7e48c7d3a93c Mon Sep 17 00:00:00 2001 From: renee-png Date: Sun, 19 Jul 2026 16:21:04 -0400 Subject: [PATCH] Student profile: read-only view by default, Edit button to edit Profile / Family & pickup / Academics tabs now render a clean read-only view with an Edit toggle that reveals the editable form (Save/Cancel/Done). Guardian/pickup/login cards show summaries in view mode, inputs in edit mode. Co-Authored-By: Claude Opus 4.8 --- src/routes/_authenticated/students.$id.tsx | 286 ++++++++++++++------- 1 file changed, 189 insertions(+), 97 deletions(-) diff --git a/src/routes/_authenticated/students.$id.tsx b/src/routes/_authenticated/students.$id.tsx index ba196e0..415f3c9 100644 --- a/src/routes/_authenticated/students.$id.tsx +++ b/src/routes/_authenticated/students.$id.tsx @@ -10,7 +10,7 @@ import { Switch } from "@/components/ui/switch"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; -import { ArrowLeft, Trash2, Plus, FileText, Loader2, ImageUp, User } from "lucide-react"; +import { ArrowLeft, Trash2, Plus, FileText, Loader2, ImageUp, User, Pencil, Check } from "lucide-react"; import { useState } from "react"; import { toast } from "sonner"; @@ -27,6 +27,7 @@ const DISMISSAL_OPTIONS = [ { value: "rideshare", label: "Rideshare (Uber/Lyft)" }, { value: "other", label: "Other" }, ]; +const dismissalLabel = (v: string) => DISMISSAL_OPTIONS.find((o) => o.value === v)?.label ?? v; function Field({ label, children, className = "" }: { label: string; children: React.ReactNode; className?: string }) { return
{children}
; @@ -39,6 +40,20 @@ function Section({ title, children }: { title: string; children: React.ReactNode ); } +function ViewRow({ label, value }: { label: string; value?: string | null }) { + return ( +
+ {label} + {value || "—"} +
+ ); +} +function EditToggle({ editing, setEditing, canEdit }: { editing: boolean; setEditing: (v: boolean) => void; canEdit: boolean }) { + if (!canEdit) return null; + return editing + ? + : ; +} function StudentDetail() { const { id } = Route.useParams(); @@ -97,7 +112,6 @@ function PhotoAvatar({ studentId, photoPath, canEdit }: { studentId: string; pho return data?.signedUrl ?? null; }, }); - const onUpload = async (file: File) => { if (!file) return; setUploading(true); @@ -112,7 +126,6 @@ function PhotoAvatar({ studentId, photoPath, canEdit }: { studentId: string; pho toast.success("Photo updated"); } catch (e) { toast.error((e as Error).message); } finally { setUploading(false); } }; - return (
@@ -128,9 +141,10 @@ function PhotoAvatar({ studentId, photoPath, canEdit }: { studentId: string; pho ); } -// ── Profile (all single-value student fields) ──────────────────────────────── +// ── Profile ────────────────────────────────────────────────────────────────── function ProfileTab({ studentId, canEdit }: { studentId: string; canEdit: boolean }) { const qc = useQueryClient(); + const [editing, setEditing] = useState(false); const { data: s } = useQuery({ queryKey: ["student", studentId], queryFn: async () => (await supabase.from("students").select("*").eq("id", studentId).single()).data, @@ -151,7 +165,6 @@ function ProfileTab({ studentId, canEdit }: { studentId: string; canEdit: boolea const keys = [ "first_name", "last_name", "dob", "gender", "grade_level", "preferred_start_date", "class_id", "allergies", "chronic_conditions", "primary_physician", "physician_phone", - "home_ed_eval_due", "previous_schools", "special_needs", "portfolio_keeper", "disciplinary_history", "custody_agreement", "dismissal_other", "backup_transport_plan", "interests", "other_info", "agreement_signed_by", "agreement_signed_date", "notes", ]; const payload: Record = { dismissal_methods: dismissal, photo_release: !!c.photo_release }; @@ -161,16 +174,57 @@ function ProfileTab({ studentId, canEdit }: { studentId: string; canEdit: boolea const { error } = await supabase.from("students").update(payload as never).eq("id", studentId); if (error) throw error; }, - onSuccess: () => { toast.success("Profile saved"); qc.invalidateQueries({ queryKey: ["student", studentId] }); setForm(null); }, + onSuccess: () => { toast.success("Profile saved"); qc.invalidateQueries({ queryKey: ["student", studentId] }); setForm(null); setEditing(false); }, onError: (e: Error) => toast.error(e.message), }); if (!c) return
; const val = (k: string) => (c[k] as string) ?? ""; - const T = (k: string, label: string, className = "") => upd(k, e.target.value)} />; - const D = (k: string, label: string) => upd(k, e.target.value)} />; - const A = (k: string, label: string, rows = 2) =>