feat:update form

This commit is contained in:
2026-07-28 00:02:34 +07:00 Unverified
parent 0e8c816276
commit fa35f00f38
25 changed files with 919 additions and 149 deletions
@@ -26,6 +26,7 @@ import Image from 'next/image';
import { stripHtml } from "@/lib/utils";
import { HtmlDisplay } from "@/components/html-display";
import { formatStoredAnswer } from "@/lib/form-answer-values";
import { normalizeStoredAnswerSnapshot } from "@/lib/submission-intake";
interface Question {
id: string;
@@ -40,10 +41,10 @@ interface SubmissionDetailClientProps {
userName: string | null;
userDiscordId: string | null;
submittedAt: Date;
editHistory?: { editedAt: string; oldAnswers: Record<string, string> }[] | null;
editHistory?: { editedAt: string; oldAnswers: Record<string, string | { value: string; imageCdnId: string | null }> }[] | null;
};
questions: Question[];
answers: Record<string, string>;
answers: Record<string, { value: string; imageCdnId: string | null }>;
}
export default function SubmissionDetailClient({
@@ -103,16 +104,25 @@ export default function SubmissionDetailClient({
{/* Answers List */}
<div className="space-y-4">
{questions.map((q) => {
const rawAnswerValue = answers[q.id] || "";
const rawAnswer = answers[q.id] ?? { value: "", imageCdnId: null };
const rawAnswerValue = rawAnswer.value;
const answerValue = formatStoredAnswer(rawAnswerValue, q.type);
const imageUrl = rawAnswer.imageCdnId ? `/cdn/${rawAnswer.imageCdnId}` : "";
const wasEdited = editHistory.some(
(edit) => (edit.oldAnswers[q.id] ?? "") !== rawAnswerValue
(edit) => {
const oldSnapshot = normalizeStoredAnswerSnapshot(edit.oldAnswers[q.id]);
return oldSnapshot.value !== rawAnswerValue || oldSnapshot.imageCdnId !== rawAnswer.imageCdnId;
}
);
const qAnswerHistory = editHistory.map((edit) => ({
editedAt: edit.editedAt,
answer: formatStoredAnswer(edit.oldAnswers[q.id], q.type),
answer: (() => {
const snapshot = normalizeStoredAnswerSnapshot(edit.oldAnswers[q.id]);
const text = formatStoredAnswer(snapshot.value, q.type);
return text || (snapshot.imageCdnId ? `/cdn/${snapshot.imageCdnId}` : "");
})(),
}));
return (
@@ -171,8 +181,8 @@ export default function SubmissionDetailClient({
</div>
)}
</CardHeader>
<CardContent className="space-y-4">
{answerValue ? (
<CardContent className="flex flex-col gap-4">
{answerValue && (
<div className="flex items-start justify-between gap-4 p-3 bg-muted rounded-md group">
<div className="flex flex-col min-w-0">
{wasEdited && (
@@ -198,7 +208,11 @@ export default function SubmissionDetailClient({
)}
</Button>
</div>
) : (
)}
{imageUrl && (
<AnswerImagePreview src={imageUrl} label={stripHtml(q.label)} />
)}
{!answerValue && !imageUrl && (
<span className="text-sm text-muted-foreground italic pl-3 border-l-2 border-muted block">
No response provided
</span>
@@ -255,3 +269,53 @@ export default function SubmissionDetailClient({
</div>
);
}
function AnswerImagePreview({ src, label }: { src: string; label: string }) {
const [unavailable, setUnavailable] = useState(false);
if (unavailable) {
return (
<span className="text-sm text-muted-foreground italic">
Image unavailable
</span>
);
}
return (
<Dialog>
<DialogTrigger asChild>
<Button
type="button"
variant="outline"
className="h-auto w-fit p-2"
aria-label={`Open image answer for ${label}`}
>
<Image
src={src}
alt=""
width={224}
height={160}
unoptimized
className="max-h-40 max-w-56 rounded object-contain"
onError={() => setUnavailable(true)}
/>
</Button>
</DialogTrigger>
<DialogContent className="max-w-4xl">
<DialogHeader>
<DialogTitle>Image answer</DialogTitle>
<DialogDescription>{label}</DialogDescription>
</DialogHeader>
<Image
src={src}
alt=""
width={1200}
height={800}
unoptimized
className="max-h-[70vh] w-full rounded-md object-contain"
onError={() => setUnavailable(true)}
/>
</DialogContent>
</Dialog>
);
}
@@ -47,9 +47,12 @@ export default async function SubmissionDetailPage({
where: (a, { eq }) => eq(a.submissionId, submission.id),
});
const answersMap: Record<string, string> = {};
const answersMap: Record<string, { value: string; imageCdnId: string | null }> = {};
for (const ans of answersList) {
answersMap[ans.questionId] = ans.value;
answersMap[ans.questionId] = {
value: ans.value,
imageCdnId: ans.imageCdnId ?? null,
};
}
const submissionsList = await db.query.submissions.findMany({
where: (s, { eq }) => eq(s.formId, formId),