feat(submission-details): display last failed test case details and error messages

This commit is contained in:
cfngc4594 2025-04-14 15:38:02 +08:00
parent b18dedcc23
commit 207697c592

View File

@ -1,20 +1,30 @@
"use client"; "use client";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useEffect } from "react"; import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { useEffect, useState } from "react";
import { ArrowLeftIcon } from "lucide-react"; import { ArrowLeftIcon } from "lucide-react";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { useProblem } from "@/hooks/use-problem"; import { useProblem } from "@/hooks/use-problem";
import MdxPreview from "@/components/mdx-preview"; import MdxPreview from "@/components/mdx-preview";
import { useDockviewStore } from "@/stores/dockview"; import { useDockviewStore } from "@/stores/dockview";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { getStatusColorClass, statusMap } from "@/lib/status"; import { getStatusColorClass, statusMap } from "@/lib/status";
import type { TestcaseResultWithTestcase } from "@/types/prisma";
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area";
import { formatDistanceToNow, isBefore, subDays, format } from "date-fns"; import { formatDistanceToNow, isBefore, subDays, format } from "date-fns";
export default function DetailsPage() { export default function DetailsPage() {
const { api, submission } = useDockviewStore(); const { api, submission } = useDockviewStore();
const { editorLanguageConfigs, problemId } = useProblem(); const { editorLanguageConfigs, problemId } = useProblem();
const [lastFailedTestcase, setLastFailedTestcase] =
useState<TestcaseResultWithTestcase | null>(null);
useEffect(() => { useEffect(() => {
if (!api || !problemId || !submission?.id) return; if (!api || !problemId || !submission?.id) return;
@ -24,7 +34,19 @@ export default function DetailsPage() {
api.removePanel(detailsPanel); api.removePanel(detailsPanel);
} }
} }
}, [api, problemId, submission]) }, [api, problemId, submission]);
useEffect(() => {
if (!submission?.id || !submission.testcaseResults) return;
const failedTestcases = submission.testcaseResults.filter(
(result) =>
(submission.status === "WA" && !result.isCorrect) ||
(submission.status === "TLE" && !result.isCorrect) ||
(submission.status === "MLE" && !result.isCorrect) ||
(submission.status === "RE" && !result.isCorrect)
);
setLastFailedTestcase(failedTestcases[0]);
}, [submission]);
if (!api || !problemId || !submission?.id) return null; if (!api || !problemId || !submission?.id) return null;
@ -43,7 +65,7 @@ export default function DetailsPage() {
if (detailsPanel) { if (detailsPanel) {
api.removePanel(detailsPanel); api.removePanel(detailsPanel);
} }
} };
return ( return (
<div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background"> <div className="h-full flex flex-col border border-t-0 border-muted rounded-b-3xl bg-background">
@ -70,9 +92,7 @@ export default function DetailsPage() {
getStatusColorClass(submission.status) getStatusColorClass(submission.status)
)} )}
> >
<span> <span>{statusMap.get(submission.status)?.message}</span>
{statusMap.get(submission.status)?.message}
</span>
</h3> </h3>
<div className="flex max-w-full flex-1 items-center gap-1 overflow-hidden text-xs"> <div className="flex max-w-full flex-1 items-center gap-1 overflow-hidden text-xs">
<span className="whitespace-nowrap">Submitted on</span> <span className="whitespace-nowrap">Submitted on</span>
@ -84,7 +104,74 @@ export default function DetailsPage() {
</div> </div>
</div> </div>
<div className="flex flex-col"> <div className="flex flex-col gap-6">
{lastFailedTestcase && (
<div className="space-y-4">
<div className="space-y-2">
<Accordion
type="single"
collapsible
className="w-full -space-y-px"
>
<AccordionItem
value="input"
className="bg-background has-focus-visible:border-ring has-focus-visible:ring-ring/50 relative border px-4 py-1 outline-none first:rounded-t-md last:rounded-b-md last:border-b has-focus-visible:z-10 has-focus-visible:ring-[3px]"
>
<AccordionTrigger className="py-2 text-[15px] leading-6 hover:no-underline focus-visible:ring-0">
<h4 className="text-sm font-medium">Input</h4>
</AccordionTrigger>
<AccordionContent className="text-muted-foreground pb-2">
<div className="space-y-4">
{lastFailedTestcase.testcase.data.map((field) => (
<div key={field.id} className="space-y-2">
<label className="text-sm font-medium">
{`${field.label} =`}
</label>
<Input
type="text"
value={field.value}
readOnly
className="bg-muted border-transparent shadow-none rounded-lg h-10"
/>
</div>
))}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
<div className="space-y-2">
<h4 className="text-sm font-medium">Expected Output</h4>
<Input
type="text"
value={lastFailedTestcase.testcase.expectedOutput}
readOnly
className="bg-muted border-transparent shadow-none rounded-lg h-10 font-mono"
/>
</div>
{submission.status === "WA" && (
<div className="space-y-2">
<h4 className="text-sm font-medium">Your Output</h4>
<Input
type="text"
value={lastFailedTestcase.output}
readOnly
className="bg-muted border-transparent shadow-none rounded-lg h-10 font-mono"
/>
</div>
)}
</div>
)}
{(submission.status === "CE" ||
submission.status === "SE") && (
<MdxPreview
source={`\`\`\`shell\n${submission.message}\n\`\`\``}
/>
)}
<div className="flex items-center pb-2"> <div className="flex items-center pb-2">
<div className="flex items-center gap-2 text-sm font-medium"> <div className="flex items-center gap-2 text-sm font-medium">
<span>Code</span> <span>Code</span>
@ -95,7 +182,8 @@ export default function DetailsPage() {
<span> <span>
{ {
editorLanguageConfigs.find( editorLanguageConfigs.find(
(config) => config.language === submission.language (config) =>
config.language === submission.language
)?.label )?.label
} }
</span> </span>