From a8586947b86bb48e959610e7f48043bee2352217 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 7 Aug 2026 04:13:11 +0000 Subject: [PATCH] Modified by www.SourceFiles.app --- src/routes/_authenticated/forms.tsx | 512 ---------------------------- 1 file changed, 512 deletions(-) delete mode 100644 src/routes/_authenticated/forms.tsx diff --git a/src/routes/_authenticated/forms.tsx b/src/routes/_authenticated/forms.tsx deleted file mode 100644 index bef5bd6..0000000 --- a/src/routes/_authenticated/forms.tsx +++ /dev/null @@ -1,512 +0,0 @@ -import { createFileRoute } from "@tanstack/react-router"; -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 { Switch } from "@/components/ui/switch"; -import { Badge } from "@/components/ui/badge"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { Plus, Trash2, FileText, Pencil, EyeOff, Eye, AlertTriangle } from "lucide-react"; -import { useState } from "react"; -import { toast } from "sonner"; - -type Field = { label: string; type: "text" | "textarea" | "yesno" | "date" }; -type FormRow = { - id: string; - title: string; - description: string | null; - schema_json: unknown; - active: boolean; -}; - -export const Route = createFileRoute("/_authenticated/forms")({ - head: () => ({ meta: [{ title: "Forms — School Portal" }] }), - component: FormsPage, -}); - -const emptyField: Field = { label: "", type: "text" }; - -function FormsPage() { - const { roles } = useAuth(); - const isAdmin = roles.includes("admin"); - const qc = useQueryClient(); - const [activeForm, setActiveForm] = useState(null); - const [editing, setEditing] = useState(null); - const [creating, setCreating] = useState(false); - - // Admins see inactive forms too, otherwise deactivating one would hide it from - // the only people who could bring it back. - const { data: forms } = useQuery({ - queryKey: ["forms", isAdmin], - queryFn: async () => { - let q = supabase.from("forms").select("*"); - if (!isAdmin) q = q.eq("active", true); - return ((await q.order("created_at", { ascending: false })).data ?? []) as FormRow[]; - }, - }); - - // Response counts drive the delete warning — form_responses cascades on - // delete, so removing a form destroys its submissions. - const { data: counts } = useQuery({ - queryKey: ["form-response-counts"], - enabled: isAdmin, - queryFn: async () => { - const { data } = await supabase.from("form_responses").select("form_id"); - const map: Record = {}; - for (const r of data ?? []) map[r.form_id] = (map[r.form_id] ?? 0) + 1; - return map; - }, - }); - - const refresh = () => { - qc.invalidateQueries({ queryKey: ["forms"] }); - qc.invalidateQueries({ queryKey: ["form-response-counts"] }); - }; - - const setActive = useMutation({ - mutationFn: async ({ id, active }: { id: string; active: boolean }) => { - const { error } = await supabase.from("forms").update({ active }).eq("id", id); - if (error) throw error; - return active; - }, - onSuccess: (active) => { - toast.success(active ? "Form activated" : "Form deactivated"); - refresh(); - }, - onError: (e: Error) => toast.error(e.message), - }); - - const remove = useMutation({ - mutationFn: async (id: string) => { - const { error } = await supabase.from("forms").delete().eq("id", id); - if (error) throw error; - }, - onSuccess: () => { - toast.success("Form deleted"); - refresh(); - }, - onError: (e: Error) => toast.error(e.message), - }); - - const confirmDelete = (f: FormRow) => { - const n = counts?.[f.id] ?? 0; - const msg = - n > 0 - ? `Delete "${f.title}"?\n\nThis also permanently deletes ${n} submitted response${n === 1 ? "" : "s"}. That cannot be undone.\n\nTo stop families seeing the form while keeping its responses, deactivate it instead.` - : `Delete "${f.title}"? This cannot be undone.`; - if (confirm(msg)) remove.mutate(f.id); - }; - - return ( -
-
-
-

Forms

-

- Push a form to families; they complete it in the portal. -

-
- {isAdmin && ( - - )} -
- -
- {(forms ?? []).map((f) => { - const n = counts?.[f.id] ?? 0; - return ( -
- - - {isAdmin && ( -
- - -
- -
- )} -
- ); - })} - {forms?.length === 0 &&
No forms yet.
} -
- - {creating && ( - { - setCreating(false); - refresh(); - }} - /> - )} - {editing && ( - { - setEditing(null); - refresh(); - }} - /> - )} - {activeForm && ( - setActiveForm(null)} isAdmin={isAdmin} /> - )} -
- ); -} - -// Create and edit share one editor: the only differences are the initial values -// and whether we insert or update. -function FormEditor({ - form, - responseCount = 0, - onDone, -}: { - form?: FormRow; - responseCount?: number; - onDone: () => void; -}) { - const { user } = useAuth(); - const isEdit = !!form; - const [title, setTitle] = useState(form?.title ?? ""); - const [description, setDescription] = useState(form?.description ?? ""); - const [active, setActive] = useState(form?.active ?? true); - const [fields, setFields] = useState( - (form?.schema_json as Field[]) ?? [{ label: "Full name", type: "text" }], - ); - - const save = useMutation({ - mutationFn: async () => { - const clean = fields.filter((f) => f.label.trim() !== ""); - if (clean.length === 0) throw new Error("Add at least one field with a label"); - if (isEdit) { - const { error } = await supabase - .from("forms") - .update({ title, description, schema_json: clean, active }) - .eq("id", form!.id); - if (error) throw error; - } else { - const { error } = await supabase - .from("forms") - .insert({ title, description, schema_json: clean, active, created_by: user!.id }); - if (error) throw error; - } - }, - onSuccess: () => { - toast.success(isEdit ? "Form updated" : "Form created"); - onDone(); - }, - onError: (e: Error) => toast.error(e.message), - }); - - const setField = (i: number, patch: Partial) => - setFields((fs) => fs.map((f, j) => (j === i ? { ...f, ...patch } : f))); - - return ( - !o && onDone()}> - - - {isEdit ? "Edit form" : "Create form"} - -
- {/* Responses store answers keyed by field label, so a rename leaves the - old answers stranded under the old key. Say so rather than silently - breaking the response view. */} - {isEdit && responseCount > 0 && ( -
- - - This form already has {responseCount} response{responseCount === 1 ? "" : "s"}. - Answers are stored under their field label, so renaming or removing a field won't - change what was already submitted — those answers stay under the old label. Adding - fields is always safe. - -
- )} - -
- - setTitle(e.target.value)} /> -
-
- -