146 lines
4.6 KiB
TypeScript
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>
|
|
);
|
|
}
|