Files
erika/app/(ui)/admin/form/[id]/result/[submissionId]/page.tsx
T
2026-08-27 14:11:20 +00:00

146 lines
4.6 KiB
TypeScript

import { db } from "@/db";
import { notFound } from "next/navigation";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { ArrowLeft, ArrowRight } from "lucide-react";
import SubmissionDetailClient from "./client";
import { DeleteSubmissionButton } from "@/components/delete-buttons";
import { RealtimeRefresh } from "@/components/realtime-refresh";
import { getDiscordMemberProfile } from "@/lib/discord/discord";
import { submissions } from "@/db/schema";
import { and, asc, desc, eq, gt, lt, or } from "drizzle-orm";
interface SubmissionDetailPageProps {
params: Promise<{
id: string;
submissionId: string;
}>;
searchParams: Promise<{ return?: string | string[] }>;
}
export default async function SubmissionDetailPage({
params,
searchParams,
}: SubmissionDetailPageProps) {
const [{ id: formId, submissionId }, query] = await Promise.all([
params,
searchParams,
]);
const returnValue = Array.isArray(query.return) ? query.return[0] : query.return;
const returnQuery = returnValue?.slice(0, 1000) || "";
const backHref = `/admin/form/${formId}/result${returnQuery ? `?${returnQuery}` : ""}`;
const detailHref = (id: string) =>
`/admin/form/${formId}/result/${id}${returnQuery ? `?return=${encodeURIComponent(returnQuery)}` : ""}`;
const submission = await db.query.submissions.findFirst({
where: (s, { eq, and }) =>
and(eq(s.id, submissionId), eq(s.formId, formId)),
});
if (!submission) {
notFound();
}
const [respondentProfile, questions, answersList, [previous], [next]] =
await Promise.all([
submission.userDiscordId
? getDiscordMemberProfile(submission.userDiscordId)
: null,
db.query.questions.findMany({
where: (q, { eq }) => eq(q.formId, submission.formId),
orderBy: (q, { asc }) => [asc(q.displayOrder)],
}),
db.query.answers.findMany({
where: (a, { eq }) => eq(a.submissionId, submission.id),
}),
db
.select({ id: submissions.id })
.from(submissions)
.where(
and(
eq(submissions.formId, formId),
or(
gt(submissions.submittedAt, submission.submittedAt),
and(
eq(submissions.submittedAt, submission.submittedAt),
gt(submissions.id, submission.id),
),
),
),
)
.orderBy(asc(submissions.submittedAt), asc(submissions.id))
.limit(1),
db
.select({ id: submissions.id })
.from(submissions)
.where(
and(
eq(submissions.formId, formId),
or(
lt(submissions.submittedAt, submission.submittedAt),
and(
eq(submissions.submittedAt, submission.submittedAt),
lt(submissions.id, submission.id),
),
),
),
)
.orderBy(desc(submissions.submittedAt), desc(submissions.id))
.limit(1),
]);
const answersMap: Record<string, { value: string; imageCdnId: string | null }> = {};
for (const ans of answersList) {
answersMap[ans.questionId] = {
value: ans.value,
imageCdnId: ans.imageCdnId ?? null,
};
}
const prevId = previous?.id ?? null;
const nextId = next?.id ?? null;
return (
<div className="w-full space-y-6 p-4 pb-10 md:p-6">
<RealtimeRefresh topic="submissions" formId={formId} />
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<Button variant="ghost" asChild className="self-start">
<Link href={backHref}>
<ArrowLeft data-icon="inline-start" />
Back to responses
</Link>
</Button>
<DeleteSubmissionButton id={submission.id} redirectUrl={backHref} />
</div>
<SubmissionDetailClient
key={submission.id}
submission={{
...submission,
userProfile: respondentProfile,
}}
questions={questions}
answers={answersMap}
/>
<div className="flex justify-between items-center mt-8 pt-6 border-t">
{prevId ? (
<Link href={detailHref(prevId)}>
<Button variant="outline">
<ArrowLeft data-icon="inline-start" /> Previous
</Button>
</Link>
) : (
<div />
)}
{nextId && (
<Link href={detailHref(nextId)}>
<Button variant="outline">
Next <ArrowRight data-icon="inline-end" />
</Button>
</Link>
)}
</div>
</div>
);
}