diff --git a/app/(app)/forms/[id]/results/results-client.tsx b/app/(app)/forms/[id]/results/results-client.tsx index 16de597..c06ad23 100644 --- a/app/(app)/forms/[id]/results/results-client.tsx +++ b/app/(app)/forms/[id]/results/results-client.tsx @@ -2,12 +2,14 @@ import { useState } from 'react' import Link from 'next/link' +import { useRouter } from 'next/navigation' import { ArrowLeft, Download } from 'lucide-react' import { PageHeader, PageTitle } from '@/components/ui/page-title' import { Button } from '@/components/ui/button' import { Empty, EmptyHeader, EmptyTitle, EmptyDescription } from '@/components/ui/empty' +import { useConfirm } from '@/components/ui/confirm' import { toast } from 'sonner' -import { exportFormResultsCsv } from '@/lib/actions' +import { exportFormResultsCsv, deleteForm, deleteSubmission } from '@/lib/actions' import type { FormField } from '@/lib/forms-schema' type Submission = { @@ -28,14 +30,18 @@ export function ResultsClient({ formId, title, fields, - submissions, + submissions: initial, }: { formId: string title: string fields: FormField[] submissions: Submission[] }) { + const router = useRouter() + const confirm = useConfirm() + const [submissions, setSubmissions] = useState(initial) const [exporting, setExporting] = useState(false) + const [busy, setBusy] = useState(false) async function exportCsv() { setExporting(true) @@ -55,6 +61,39 @@ export function ResultsClient({ URL.revokeObjectURL(url) } + async function onDeleteSubmission(id: string) { + const ok = await confirm({ + title: 'Delete this response?', + description: 'This permanently removes this single submission. This cannot be undone.', + confirmText: 'Delete response', + destructive: true, + }) + if (!ok) return + setBusy(true) + const res = await deleteSubmission({ submissionId: id }) + setBusy(false) + if ('error' in res && res.error) return toast.error(res.error) + setSubmissions(prev => prev.filter(s => s.id !== id)) + toast.success('Response deleted') + } + + async function onDeleteForm() { + const ok = await confirm({ + title: `Delete "${title}"?`, + description: + 'This permanently deletes the form AND every response, including any signed waivers. This cannot be undone. Export first if you need a record.', + confirmText: 'Delete form and all responses', + destructive: true, + }) + if (!ok) return + setBusy(true) + const res = await deleteForm({ formId, confirm: true }) + setBusy(false) + if ('error' in res && res.error) return toast.error(res.error) + toast.success('Form deleted') + router.push('/forms') + } + return ( <> @@ -66,9 +105,14 @@ export function ResultsClient({ {title} — responses - +
+ + +
@@ -91,6 +135,7 @@ export function ResultsClient({ {f.label} ))} + Actions @@ -109,6 +154,16 @@ export function ResultsClient({ {cell(a[f.key])} ))} + + + ) })} diff --git a/app/(app)/forms/forms-client.tsx b/app/(app)/forms/forms-client.tsx index c507754..214a064 100644 --- a/app/(app)/forms/forms-client.tsx +++ b/app/(app)/forms/forms-client.tsx @@ -1,12 +1,16 @@ 'use client' +import { useState } from 'react' import Link from 'next/link' import { FileText, Plus } from 'lucide-react' +import { toast } from 'sonner' import { PageHeader, PageTitle } from '@/components/ui/page-title' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty' +import { useConfirm } from '@/components/ui/confirm' import { CopyLinkButton } from '@/components/forms/copy-link-button' +import { deleteForm, relinkAllSubmissions } from '@/lib/actions' type FormRow = { id: string @@ -25,16 +29,51 @@ const VIS_LABEL: Record = { public_anon: 'Public', } -export function FormsClient({ forms, spaceSlug }: { forms: FormRow[]; spaceSlug: string }) { +export function FormsClient({ forms: initial, spaceSlug }: { forms: FormRow[]; spaceSlug: string }) { + const confirm = useConfirm() + const [forms, setForms] = useState(initial) + const [busy, setBusy] = useState(false) + + async function onRelink() { + setBusy(true) + const res = await relinkAllSubmissions() + setBusy(false) + if ('error' in res && res.error) return toast.error(res.error) + const n = (res as { data: { linked: number } }).data.linked + toast.success(n > 0 ? `Linked ${n} submission(s) to members` : 'All submissions already linked') + } + + async function onDelete(f: FormRow) { + const ok = await confirm({ + title: `Delete "${f.title}"?`, + description: + 'This permanently deletes the form AND every response/submission for it, including any signed waivers. This cannot be undone. Export the results first if you need a record.', + confirmText: 'Delete form and all responses', + destructive: true, + }) + if (!ok) return + setBusy(true) + const res = await deleteForm({ formId: f.id, confirm: true }) + setBusy(false) + if ('error' in res && res.error) return toast.error(res.error) + setForms(prev => prev.filter(x => x.id !== f.id)) + toast.success('Form deleted') + } + return ( <> Forms & waivers - +
+ + +
@@ -86,6 +125,9 @@ export function FormsClient({ forms, spaceSlug }: { forms: FormRow[]; spaceSlug: +
))}