Skip to content

Commit e682dc4

Browse files
authored
Merge pull request #15 from database-playground/pan93412/dbp-30-改善「題幹描述」的顯示
2 parents 6e141cc + 9f7112c commit e682dc4

File tree

29 files changed

+566
-256
lines changed

29 files changed

+566
-256
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use client";
2+
3+
import { CardLayout } from "@/components/card-layout";
4+
import { useSuspenseQuery } from "@apollo/client/react";
5+
import { Remark } from "react-remark";
6+
import { DATABASE_DETAIL_QUERY } from "./query";
7+
8+
export function DescriptionCard({ id }: { id: string }) {
9+
return (
10+
<CardLayout title="資料表描述" description="這個資料表的簡單介紹。">
11+
<article
12+
className={`
13+
prose
14+
dark:prose-invert
15+
`}
16+
>
17+
<Description id={id} />
18+
</article>
19+
</CardLayout>
20+
);
21+
}
22+
23+
function Description({ id }: { id: string }) {
24+
const { data } = useSuspenseQuery(DATABASE_DETAIL_QUERY, {
25+
variables: { id },
26+
});
27+
28+
const database = data.database;
29+
30+
if (!database.description) {
31+
return <p className="text-muted-foreground">無描述</p>;
32+
}
33+
34+
return <Remark>{database.description ?? ""}</Remark>;
35+
}

app/(admin)/(question-management)/database/[id]/_components/header.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ function HeaderMain({ id }: { id: string }) {
2020

2121
const database = data.database;
2222

23+
// 只擷取第一段 description
24+
const description = database.description?.split("\n")[0] || "此資料庫沒有描述";
25+
2326
return (
2427
<div className="flex items-start gap-4">
2528
<PageHeader
2629
title={`資料庫「${database.slug}」`}
27-
description={database.description || "此資料庫沒有描述"}
30+
description={description}
2831
/>
2932
</div>
3033
);

app/(admin)/(question-management)/database/[id]/_components/relation-card.tsx

Lines changed: 34 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,48 @@ import { useSuspenseQuery } from "@apollo/client/react";
55
import { DATABASE_DETAIL_QUERY } from "./query";
66

77
export function RelationCard({ id }: { id: string }) {
8+
return (
9+
<CardLayout title="關係圖" description="資料庫表格關係圖">
10+
<RelationFigure id={id} />
11+
</CardLayout>
12+
);
13+
}
14+
15+
function RelationFigure({ id }: { id: string }) {
816
const { data } = useSuspenseQuery(DATABASE_DETAIL_QUERY, {
917
variables: { id },
1018
});
1119

1220
const database = data.database;
1321

1422
// Check if the relation figure looks like a URL (basic check)
15-
const isUrl = database.relationFigure.startsWith("http://") || database.relationFigure.startsWith("https://");
23+
const isUrl = database.relationFigure.startsWith("http://")
24+
|| database.relationFigure.startsWith("https://");
1625

17-
return (
18-
<CardLayout title="關係圖" description="資料庫表格關係圖">
19-
<div className="space-y-4">
20-
{isUrl
21-
? (
22-
<div>
23-
<div className="overflow-hidden rounded-lg border">
24-
<picture>
25-
<img
26-
src={database.relationFigure}
27-
alt="資料庫關係圖"
28-
className="h-auto max-h-96 w-full object-contain"
29-
onError={(e) => {
30-
e.currentTarget.style.display = "none";
31-
e.currentTarget.nextElementSibling?.classList.remove("hidden");
32-
}}
33-
/>
34-
</picture>
35-
</div>
36-
</div>
37-
)
38-
: (
39-
<div>
40-
<pre
41-
className={`
42-
max-h-80 overflow-x-auto rounded-lg border bg-muted p-4
43-
font-mono text-sm whitespace-pre-wrap
44-
`}
45-
>
46-
{database.relationFigure}
47-
</pre>
48-
</div>
49-
)}
26+
if (isUrl) {
27+
return (
28+
<a href={database.relationFigure} aria-label="打開資料庫關係圖" target="_blank" rel="noopener noreferrer">
29+
<picture>
30+
<img
31+
src={database.relationFigure}
32+
alt="資料庫關係圖"
33+
className="h-auto max-h-80 w-full rounded object-contain"
34+
/>
35+
</picture>
36+
</a>
37+
);
38+
}
5039

51-
<div className="border-t pt-2">
52-
<p className="text-xs text-muted-foreground">
53-
關係圖顯示了資料庫中各表格之間的關聯性和主外鍵約束。
54-
</p>
55-
</div>
56-
</div>
57-
</CardLayout>
40+
return (
41+
<div>
42+
<pre
43+
className={`
44+
max-h-80 overflow-x-auto rounded-lg border bg-muted p-4 font-mono
45+
text-sm whitespace-pre-wrap
46+
`}
47+
>
48+
{database.relationFigure}
49+
</pre>
50+
</div>
5851
);
5952
}

app/(admin)/(question-management)/database/[id]/_components/schema-card.tsx

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,11 @@ import { useSuspenseQuery } from "@apollo/client/react";
55
import { DATABASE_DETAIL_QUERY } from "./query";
66

77
export function SchemaCard({ id }: { id: string }) {
8-
const { data } = useSuspenseQuery(DATABASE_DETAIL_QUERY, {
9-
variables: { id },
10-
});
11-
12-
const database = data.database;
13-
148
return (
159
<CardLayout title="資料結構" description="SQL DDL 建表語句">
1610
<div className="space-y-4">
1711
<div>
18-
<pre
19-
className={`
20-
max-h-96 overflow-x-auto rounded-lg border bg-muted p-4 font-mono
21-
text-xs whitespace-pre-wrap
22-
`}
23-
>
24-
{database.schema}
25-
</pre>
12+
<Schema id={id} />
2613
</div>
2714

2815
<div className="border-t pt-2">
@@ -34,3 +21,22 @@ export function SchemaCard({ id }: { id: string }) {
3421
</CardLayout>
3522
);
3623
}
24+
25+
function Schema({ id }: { id: string }) {
26+
const { data } = useSuspenseQuery(DATABASE_DETAIL_QUERY, {
27+
variables: { id },
28+
});
29+
30+
const database = data.database;
31+
32+
return (
33+
<pre
34+
className={`
35+
max-h-96 overflow-x-auto rounded-lg border bg-muted p-4 font-mono
36+
text-xs whitespace-pre-wrap
37+
`}
38+
>
39+
{database.schema}
40+
</pre>
41+
);
42+
}

app/(admin)/(question-management)/database/[id]/page.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { SiteHeader } from "@/components/site-header";
2+
import { Suspense } from "react";
23
import { DeleteDatabaseButtonTrigger } from "../_components/delete";
34
import { UpdateDatabaseButtonTrigger } from "../_components/update";
5+
import { DescriptionCard } from "./_components/description-card";
46
import { Header } from "./_components/header";
57
import { RelationCard } from "./_components/relation-card";
68
import { SchemaCard } from "./_components/schema-card";
@@ -35,8 +37,11 @@ export default async function DatabasePage({
3537
lg:grid-cols-2
3638
`}
3739
>
38-
<SchemaCard id={id as string} />
39-
<RelationCard id={id as string} />
40+
<Suspense>
41+
<DescriptionCard id={id as string} />
42+
<RelationCard id={id as string} />
43+
<SchemaCard id={id as string} />
44+
</Suspense>
4045
</div>
4146
</main>
4247
</>

app/(admin)/(question-management)/database/page.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { DataTableSkeleton } from "@/components/data-table/skeleton";
21
import { SiteHeader } from "@/components/site-header";
3-
import { Suspense } from "react";
42
import { CreateDatabaseTrigger } from "./_components/create";
53
import { DatabaseDataTable } from "./_components/data-table";
64

@@ -22,9 +20,7 @@ export default function Page() {
2220
<CreateDatabaseTrigger />
2321
</div>
2422
<div>
25-
<Suspense fallback={<DataTableSkeleton />}>
26-
<DatabaseDataTable />
27-
</Suspense>
23+
<DatabaseDataTable />
2824
</div>
2925
</main>
3026
</>

app/(admin)/(question-management)/questions/[id]/_components/database-card.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@ export function DatabaseCard({ id }: { id: string }) {
1414

1515
return (
1616
<CardLayout title="所屬資料庫" description="這個題目要操作的資料庫。">
17-
<p>{database.slug}</p>
18-
<p className="text-sm text-muted-foreground">
19-
{database.description}{" "}
20-
<StyledLink href={`/database/${database.id}`}>
21-
schema 等資訊 →
22-
</StyledLink>
23-
</p>
17+
<div>
18+
<p>{database.slug}</p>
19+
<p className="text-sm text-muted-foreground">
20+
{database.description}{" "}
21+
<StyledLink href={`/database/${database.id}`}>
22+
schema 等資訊 →
23+
</StyledLink>
24+
</p>
25+
</div>
2426
</CardLayout>
2527
);
2628
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use client";
2+
3+
import { CardLayout } from "@/components/card-layout";
4+
import { useSuspenseQuery } from "@apollo/client/react";
5+
import { Remark } from "react-remark";
6+
import { QUESTION_DETAIL_QUERY } from "./query";
7+
8+
export function DescriptionCard({ id }: { id: string }) {
9+
const { data } = useSuspenseQuery(QUESTION_DETAIL_QUERY, {
10+
variables: { id },
11+
});
12+
13+
const question = data.question;
14+
15+
return (
16+
<CardLayout title="題幹描述" description="這道題目的詳細說明。">
17+
<article
18+
className={`
19+
prose
20+
dark:prose-invert
21+
`}
22+
>
23+
{!question.description
24+
? <p className="text-muted-foreground">無描述</p>
25+
: <Remark>{question.description}</Remark>}
26+
</article>
27+
</CardLayout>
28+
);
29+
}

app/(admin)/(question-management)/questions/[id]/_components/header.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,16 @@ function HeaderMain({ id }: { id: string }) {
2828
const question = data.question;
2929
const difficultyInfo = difficultyMap[question.difficulty as keyof typeof difficultyMap];
3030

31+
const description = question.description?.split("\n")[0] || "此題目沒有描述";
32+
3133
return (
3234
<div>
3335
<div className="flex items-center gap-2">
3436
<h1 className="text-2xl font-bold">題目「{question.title}</h1>
3537
<Badge variant="outline">{question.category}</Badge>
3638
<Badge variant={difficultyInfo.variant}>{difficultyInfo.label}</Badge>
3739
</div>
38-
<p className="text-muted-foreground">{question.description}</p>
40+
<p className="text-muted-foreground">{description}</p>
3941
</div>
4042
);
4143
}

app/(admin)/(question-management)/questions/[id]/_components/result.tsx

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,20 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
44
import { Badge } from "@/components/ui/badge";
55
import { useQuery } from "@apollo/client/react";
66
import { AlertTriangle, ChevronDown, Database, Table } from "lucide-react";
7-
import React from "react";
87
import { useState } from "react";
98
import { QUESTION_REFERENCE_ANSWER_RESULT_QUERY } from "./query";
109

1110
export function ReferenceAnswerResult({ id }: { id: string }) {
1211
const [isOpen, setIsOpen] = useState(false);
1312

14-
const handleToggle = () => {
15-
if (!isOpen) {
16-
setIsOpen(true);
17-
}
18-
};
19-
2013
return (
2114
<div className="space-y-4">
22-
<details className="group overflow-hidden rounded-lg border border-border" open={isOpen}>
15+
<details
16+
className="group overflow-hidden rounded-lg border border-border"
17+
onToggle={(e) => {
18+
setIsOpen(e.currentTarget.open);
19+
}}
20+
>
2321
<summary
2422
className={`
2523
flex cursor-pointer items-center justify-between bg-muted/50 px-4
@@ -28,7 +26,6 @@ export function ReferenceAnswerResult({ id }: { id: string }) {
2826
[&_svg]:shrink-0 [&_svg]:transition-transform [&_svg]:duration-200
2927
group-open:[&_svg]:rotate-180
3028
`}
31-
onClick={handleToggle}
3229
>
3330
<span className="flex items-center gap-2 text-sm">
3431
<Table className="h-4 w-4 text-muted-foreground" />
@@ -37,33 +34,21 @@ export function ReferenceAnswerResult({ id }: { id: string }) {
3734
<ChevronDown className="h-4 w-4 text-muted-foreground" />
3835
</summary>
3936
<div className="border-t border-border bg-background p-4">
40-
<ReferenceAnswerResultContent id={id} />
37+
<ReferenceAnswerResultContent id={id} open={isOpen} />
4138
</div>
4239
</details>
4340
</div>
4441
);
4542
}
4643

47-
function ReferenceAnswerResultContent({ id }: { id: string }) {
44+
function ReferenceAnswerResultContent({ id, open }: { id: string; open: boolean }) {
4845
const { data, loading, error } = useQuery(QUESTION_REFERENCE_ANSWER_RESULT_QUERY, {
4946
variables: { id },
47+
skip: !open,
5048
});
5149

5250
if (loading) {
53-
return (
54-
<div className="flex items-center justify-center py-12">
55-
<div className="flex items-center gap-3 text-muted-foreground">
56-
<div
57-
className={`
58-
h-5 w-5 animate-spin rounded-full border-2
59-
border-muted-foreground/20 border-t-muted-foreground
60-
`}
61-
>
62-
</div>
63-
<span className="text-sm">載入執行結果中...</span>
64-
</div>
65-
</div>
66-
);
51+
return null;
6752
}
6853

6954
if (error) {

0 commit comments

Comments
 (0)