Improve admin form workspace UX
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { db } from "@/db";
|
||||
import { answers } from "@/db/schema";
|
||||
import { inArray } from "drizzle-orm";
|
||||
import { requireAdmin } from "@/lib/auth";
|
||||
import {
|
||||
getFilteredSubmissions,
|
||||
parseResultFilters,
|
||||
} from "@/lib/admin-form-results";
|
||||
import { formatStoredAnswer } from "@/lib/form-answer-values";
|
||||
import { stripHtml } from "@/lib/utils";
|
||||
|
||||
function csvCell(value: unknown) {
|
||||
const text = String(value ?? "");
|
||||
return `"${text.replaceAll('"', '""')}"`;
|
||||
}
|
||||
|
||||
function safeFilename(value: string) {
|
||||
return (
|
||||
value
|
||||
.normalize("NFKD")
|
||||
.replace(/[^\p{L}\p{N}]+/gu, "-")
|
||||
.replace(/^-|-$/g, "")
|
||||
.slice(0, 60) || "form"
|
||||
);
|
||||
}
|
||||
|
||||
export async function GET(
|
||||
request: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
await requireAdmin();
|
||||
const { id } = await params;
|
||||
const form = await db.query.forms.findFirst({
|
||||
where: (record, { eq }) => eq(record.id, id),
|
||||
});
|
||||
if (!form) {
|
||||
return NextResponse.json({ error: "Form not found" }, { status: 404 });
|
||||
}
|
||||
|
||||
const url = new URL(request.url);
|
||||
const filters = parseResultFilters(
|
||||
Object.fromEntries(url.searchParams.entries())
|
||||
);
|
||||
const [{ rows }, questions] = await Promise.all([
|
||||
getFilteredSubmissions(id, filters, { paginate: false }),
|
||||
db.query.questions.findMany({
|
||||
where: (question, { eq }) => eq(question.formId, id),
|
||||
orderBy: (question, { asc }) => [asc(question.displayOrder)],
|
||||
}),
|
||||
]);
|
||||
const answerRows =
|
||||
rows.length === 0
|
||||
? []
|
||||
: await db
|
||||
.select()
|
||||
.from(answers)
|
||||
.where(inArray(answers.submissionId, rows.map((row) => row.id)));
|
||||
const answerMap = new Map(
|
||||
answerRows.map((answer) => [
|
||||
`${answer.submissionId}:${answer.questionId}`,
|
||||
answer.value,
|
||||
])
|
||||
);
|
||||
const header = [
|
||||
"Respondent",
|
||||
"Discord ID",
|
||||
"Submitted at (Asia/Bangkok)",
|
||||
"Edit count",
|
||||
...questions.map((question) => stripHtml(question.label)),
|
||||
];
|
||||
const lines = [
|
||||
header.map(csvCell).join(","),
|
||||
...rows.map((submission) =>
|
||||
[
|
||||
submission.userName || "Anonymous",
|
||||
submission.userDiscordId || "",
|
||||
submission.submittedAt.toLocaleString("en-GB", {
|
||||
timeZone: "Asia/Bangkok",
|
||||
hour12: false,
|
||||
}),
|
||||
Array.isArray(submission.editHistory)
|
||||
? submission.editHistory.length
|
||||
: 0,
|
||||
...questions.map((question) =>
|
||||
formatStoredAnswer(
|
||||
answerMap.get(`${submission.id}:${question.id}`) || "",
|
||||
question.type
|
||||
)
|
||||
),
|
||||
]
|
||||
.map(csvCell)
|
||||
.join(",")
|
||||
),
|
||||
];
|
||||
const date = new Date().toISOString().slice(0, 10);
|
||||
|
||||
return new NextResponse(`\uFEFF${lines.join("\r\n")}`, {
|
||||
headers: {
|
||||
"Content-Type": "text/csv; charset=utf-8",
|
||||
"Content-Disposition": `attachment; filename="${safeFilename(form.title)}-responses-${date}.csv"`,
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user