153 lines
6.0 KiB
TypeScript
153 lines
6.0 KiB
TypeScript
import { db } from "@/db";
|
|
import Link from "next/link";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Table,
|
|
TableBody,
|
|
TableCell,
|
|
TableHead,
|
|
TableHeader,
|
|
TableRow,
|
|
} from "@/components/ui/table";
|
|
import { Badge } from "@/components/ui/badge";
|
|
import { Plus } from "lucide-react";
|
|
import { DeleteFormButton } from "@/components/delete-buttons";
|
|
import { AdminShellServer } from "@/components/admin-shell-server";
|
|
import { createDefaultForm } from "./actions";
|
|
import { RealtimeRefresh } from "@/components/realtime-refresh";
|
|
import { getFormSubmissionStats } from "@/lib/form-submission-stats";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function AdminFormsPage() {
|
|
const formsList = await db.query.forms.findMany({
|
|
orderBy: (f, { desc }) => [desc(f.createdAt)],
|
|
});
|
|
const submissionStats = await getFormSubmissionStats(
|
|
formsList.map((form) => form.id),
|
|
null
|
|
);
|
|
|
|
return (
|
|
<AdminShellServer>
|
|
<RealtimeRefresh topic="forms" />
|
|
<div className="flex w-full flex-col gap-6 p-4 pb-12 md:p-6">
|
|
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold tracking-tight">Forms</h1>
|
|
<p className="text-muted-foreground mt-1">
|
|
Manage your custom forms and view submissions.
|
|
</p>
|
|
</div>
|
|
<form action={createDefaultForm}>
|
|
<Button type="submit">
|
|
<Plus data-icon="inline-start" />
|
|
Create form
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
|
|
<div className="hidden rounded-md border md:block">
|
|
<Table>
|
|
<TableHeader>
|
|
<TableRow>
|
|
<TableHead>Title</TableHead>
|
|
<TableHead>Status</TableHead>
|
|
<TableHead>Responses</TableHead>
|
|
<TableHead>Created</TableHead>
|
|
<TableHead className="text-right">Actions</TableHead>
|
|
</TableRow>
|
|
</TableHeader>
|
|
<TableBody>
|
|
{formsList.length === 0 ? (
|
|
<TableRow>
|
|
<TableCell colSpan={5} className="h-24 text-center text-muted-foreground">
|
|
No forms found. Create one to get started.
|
|
</TableCell>
|
|
</TableRow>
|
|
) : (
|
|
formsList.map((form) => (
|
|
<TableRow key={form.id} className="group hover:bg-muted/50 transition-colors">
|
|
<TableCell className="p-0 align-middle">
|
|
<Link href={`/admin/form/${form.id}`} className="flex items-center p-3 font-medium h-full w-full">
|
|
{form.title}
|
|
</Link>
|
|
</TableCell>
|
|
<TableCell className="p-0 align-middle">
|
|
<Link href={`/admin/form/${form.id}`} className="flex items-center p-3 h-full w-full">
|
|
{form.isOpen ? (
|
|
<Badge variant="default">Open</Badge>
|
|
) : (
|
|
<Badge variant="secondary">Closed</Badge>
|
|
)}
|
|
</Link>
|
|
</TableCell>
|
|
<TableCell className="p-0 align-middle">
|
|
<Link href={`/admin/form/${form.id}/result`} className="flex items-center p-3 h-full w-full">
|
|
{submissionStats.get(form.id)?.totalAnswers ?? 0}
|
|
</Link>
|
|
</TableCell>
|
|
<TableCell className="p-0 align-middle">
|
|
<Link href={`/admin/form/${form.id}`} className="flex items-center p-3 h-full w-full">
|
|
{form.createdAt
|
|
? new Date(form.createdAt).toLocaleDateString("en-GB", {
|
|
timeZone: "Asia/Bangkok",
|
|
})
|
|
: "N/A"}
|
|
</Link>
|
|
</TableCell>
|
|
<TableCell className="p-3 text-right">
|
|
<div className="flex justify-end gap-2">
|
|
<Button variant="outline" asChild size="sm">
|
|
<Link href={`/admin/form/${form.id}/result`}>Responses</Link>
|
|
</Button>
|
|
<DeleteFormButton formId={form.id} />
|
|
</div>
|
|
</TableCell>
|
|
</TableRow>
|
|
))
|
|
)}
|
|
</TableBody>
|
|
</Table>
|
|
</div>
|
|
<div className="flex flex-col gap-3 md:hidden">
|
|
{formsList.map((form) => (
|
|
<Card key={form.id}>
|
|
<CardHeader>
|
|
<div className="flex items-start justify-between gap-3">
|
|
<CardTitle className="text-base">
|
|
<Link href={`/admin/form/${form.id}`}>{form.title}</Link>
|
|
</CardTitle>
|
|
<Badge variant={form.isOpen ? "default" : "secondary"}>
|
|
{form.isOpen ? "Open" : "Closed"}
|
|
</Badge>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="flex items-center justify-between gap-3">
|
|
<Link
|
|
href={`/admin/form/${form.id}/result`}
|
|
className="text-sm text-muted-foreground"
|
|
>
|
|
{submissionStats.get(form.id)?.totalAnswers ?? 0} responses
|
|
</Link>
|
|
<DeleteFormButton formId={form.id} />
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
{formsList.length === 0 && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="text-base">No forms yet</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="text-sm text-muted-foreground">
|
|
Create a form to start collecting responses.
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</AdminShellServer>
|
|
);
|
|
}
|