Skip to content

Commit fced4d9

Browse files
authored
Merge pull request #7 from database-playground/pan93412/dbp-36-question-page
DBP-36: question page
2 parents 1aa9520 + ffc487b commit fced4d9

39 files changed

+2114
-659
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use client";
2+
3+
import DifficultyBadge from "@/components/question/difficulty-badge";
4+
import SolvedStatusBadge from "@/components/question/solved-status-badge";
5+
import { Badge } from "@/components/ui/badge";
6+
import { graphql } from "@/gql";
7+
import { getQuestionSolvedStatus } from "@/lib/solved-status";
8+
import { useSuspenseQuery } from "@apollo/client/react";
9+
10+
export const QUESTION_HEADER = graphql(`
11+
query QuestionHeader($id: ID!) {
12+
question(id: $id) {
13+
id
14+
title
15+
difficulty
16+
category
17+
18+
...QuestionSolvedStatus
19+
}
20+
}
21+
`);
22+
23+
export default function Header({ id }: { id: string }) {
24+
const { data } = useSuspenseQuery(QUESTION_HEADER, { variables: { id } });
25+
const { title, difficulty, category } = data.question;
26+
27+
const solvedStatus = getQuestionSolvedStatus(data.question);
28+
29+
return (
30+
<div>
31+
{/* Header */}
32+
<header className="mb-6 flex items-center gap-6">
33+
<div className="text-xl leading-none font-bold tracking-wide">{title}</div>
34+
<div className="h-4 w-px bg-border" />
35+
<div className="flex items-center gap-2">
36+
<Badge>{category}</Badge>
37+
<DifficultyBadge difficulty={difficulty} />
38+
<SolvedStatusBadge solvedStatus={solvedStatus} />
39+
</div>
40+
</header>
41+
</div>
42+
);
43+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Skeleton } from "@/components/ui/skeleton";
2+
3+
export default function HeaderSkeleton() {
4+
return (
5+
<header className="mb-6 flex items-center gap-6">
6+
<Skeleton className="h-6 w-42" />
7+
<div className="h-4 w-px bg-border" />
8+
<Skeleton className="h-6 w-56" />
9+
</header>
10+
);
11+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
"use client";
2+
3+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
4+
import { graphql } from "@/gql";
5+
import { SubmissionStatus } from "@/gql/graphql";
6+
import { compareCSV, tableToCSV } from "@/lib/csv-utils";
7+
import { useSuspenseQuery } from "@apollo/client/react";
8+
import { EditorState, EditorView } from "@uiw/react-codemirror";
9+
import { AlertCircle, CheckCircle, Pencil, XCircle } from "lucide-react";
10+
import CodeMirrorMerge from "react-codemirror-merge";
11+
12+
export interface CompareAnswerProps {
13+
id: string;
14+
}
15+
16+
export const COMPARE_ANSWER_QUERY = graphql(`
17+
query CompareAnswer($id: ID!) {
18+
question(id: $id) {
19+
id
20+
referenceAnswerResult {
21+
columns
22+
rows
23+
}
24+
lastSubmission {
25+
id
26+
status
27+
queryResult {
28+
columns
29+
rows
30+
}
31+
error
32+
}
33+
}
34+
}
35+
`);
36+
37+
const Original = CodeMirrorMerge.Original;
38+
const Modified = CodeMirrorMerge.Modified;
39+
40+
export default function CompareAnswer({ id }: CompareAnswerProps) {
41+
const { data } = useSuspenseQuery(COMPARE_ANSWER_QUERY, {
42+
variables: { id },
43+
fetchPolicy: "cache-and-network",
44+
});
45+
46+
const { referenceAnswerResult, lastSubmission } = data.question;
47+
48+
// 如果沒有提交答案
49+
if (!lastSubmission) {
50+
return (
51+
<Alert>
52+
<Pencil />
53+
<AlertTitle>您尚未提交答案</AlertTitle>
54+
<AlertDescription>
55+
在左邊的 SQL 編輯器送出你的答案後,你就能在這邊看到與正確答案的比較。
56+
</AlertDescription>
57+
</Alert>
58+
);
59+
}
60+
61+
// 如果有語法錯誤
62+
if (lastSubmission.error) {
63+
return (
64+
<Alert variant="destructive">
65+
<AlertCircle />
66+
<AlertTitle>無法比較答案</AlertTitle>
67+
<AlertDescription>
68+
您的 SQL 查詢有語法錯誤,請先修正錯誤後再進行比較。
69+
</AlertDescription>
70+
</Alert>
71+
);
72+
}
73+
74+
// 如果沒有查詢結果
75+
if (!lastSubmission.queryResult) {
76+
return (
77+
<Alert variant="destructive">
78+
<AlertCircle className="h-4 w-4" />
79+
<AlertTitle>無法比較答案</AlertTitle>
80+
<AlertDescription>
81+
您的查詢沒有回傳結果,無法進行比較。
82+
</AlertDescription>
83+
</Alert>
84+
);
85+
}
86+
87+
// 轉換為 CSV 格式
88+
const correctAnswerCSV = tableToCSV(
89+
referenceAnswerResult.columns,
90+
referenceAnswerResult.rows,
91+
);
92+
93+
const myAnswerCSV = tableToCSV(
94+
lastSubmission.queryResult.columns,
95+
lastSubmission.queryResult.rows,
96+
);
97+
98+
// 如果答案正確,顯示成功訊息
99+
if (
100+
lastSubmission.status === SubmissionStatus.Success
101+
|| compareCSV(correctAnswerCSV, myAnswerCSV)
102+
) {
103+
return (
104+
<Alert className="border-green-200 bg-green-50 text-green-800">
105+
<CheckCircle className="h-4 w-4 text-green-600" />
106+
<AlertTitle>答案正確</AlertTitle>
107+
<AlertDescription>
108+
恭喜!您的查詢結果與正確答案完全一致。
109+
</AlertDescription>
110+
</Alert>
111+
);
112+
}
113+
114+
// 顯示比較界面
115+
return (
116+
<div className="space-y-4">
117+
<Alert variant="destructive">
118+
<XCircle />
119+
<AlertTitle>答案不正確</AlertTitle>
120+
<AlertDescription>
121+
您的查詢結果與正確答案不符,請查看下方的比較結果。
122+
</AlertDescription>
123+
</Alert>
124+
125+
<div className="space-y-2">
126+
<h3 className="text-sm font-medium text-muted-foreground">
127+
答案比較 (左側:正確答案,右側:您的答案)
128+
</h3>
129+
<div className="overflow-hidden rounded-md border">
130+
<CodeMirrorMerge>
131+
<Original
132+
value={correctAnswerCSV}
133+
extensions={[
134+
EditorView.editable.of(false),
135+
EditorState.readOnly.of(true),
136+
]}
137+
/>
138+
<Modified
139+
value={myAnswerCSV}
140+
extensions={[
141+
EditorView.editable.of(false),
142+
EditorState.readOnly.of(true),
143+
]}
144+
/>
145+
</CodeMirrorMerge>
146+
</div>
147+
</div>
148+
</div>
149+
);
150+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import AnswerTable from "@/components/answer/table";
2+
import { graphql } from "@/gql";
3+
import { useSuspenseQuery } from "@apollo/client/react";
4+
5+
export interface CorrectAnswerProps {
6+
id: string;
7+
}
8+
9+
const CORRECT_ANSWER_QUERY = graphql(`
10+
query CorrectAnswer($id: ID!) {
11+
question(id: $id) {
12+
id
13+
referenceAnswerResult {
14+
columns
15+
rows
16+
}
17+
}
18+
}
19+
`);
20+
21+
export default function CorrectAnswer({ id }: CorrectAnswerProps) {
22+
const { data } = useSuspenseQuery(CORRECT_ANSWER_QUERY, {
23+
variables: { id },
24+
});
25+
const { columns, rows } = data.question.referenceAnswerResult;
26+
27+
return <AnswerTable columns={columns} rows={rows} />;
28+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { graphql } from "@/gql";
2+
import { useSuspenseQuery } from "@apollo/client/react";
3+
import { Remark } from "react-remark";
4+
5+
const QUESTION_DESCRIPTION = graphql(`
6+
query QuestionDescription($id: ID!) {
7+
question(id: $id) {
8+
id
9+
description
10+
}
11+
}
12+
`);
13+
14+
export default function QuestionDescription({ id }: { id: string }) {
15+
const { data } = useSuspenseQuery(QUESTION_DESCRIPTION, {
16+
variables: { id },
17+
});
18+
const { description } = data.question;
19+
20+
return (
21+
<div className="prose leading-4 tracking-wide text-foreground">
22+
<Remark>{description}</Remark>
23+
</div>
24+
);
25+
}

0 commit comments

Comments
 (0)