Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use client";

import { CardLayout } from "@/components/card-layout";
import { useSuspenseQuery } from "@apollo/client/react";
import { Remark } from "react-remark";
import { DATABASE_DETAIL_QUERY } from "./query";

export function DescriptionCard({ id }: { id: string }) {
return (
<CardLayout title="資料表描述" description="這個資料表的簡單介紹。">
<article
className={`
prose
dark:prose-invert
`}
>
<Description id={id} />
</article>
</CardLayout>
);
}

function Description({ id }: { id: string }) {
const { data } = useSuspenseQuery(DATABASE_DETAIL_QUERY, {
variables: { id },
});

const database = data.database;

if (!database.description) {
return <p className="text-muted-foreground">無描述</p>;
}

return <Remark>{database.description ?? ""}</Remark>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ function HeaderMain({ id }: { id: string }) {

const database = data.database;

// 只擷取第一段 description
const description = database.description?.split("\n")[0] || "此資料庫沒有描述";

return (
<div className="flex items-start gap-4">
<PageHeader
title={`資料庫「${database.slug}」`}
description={database.description || "此資料庫沒有描述"}
description={description}
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,48 @@ import { useSuspenseQuery } from "@apollo/client/react";
import { DATABASE_DETAIL_QUERY } from "./query";

export function RelationCard({ id }: { id: string }) {
return (
<CardLayout title="關係圖" description="資料庫表格關係圖">
<RelationFigure id={id} />
</CardLayout>
);
}

function RelationFigure({ id }: { id: string }) {
const { data } = useSuspenseQuery(DATABASE_DETAIL_QUERY, {
variables: { id },
});

const database = data.database;

// Check if the relation figure looks like a URL (basic check)
const isUrl = database.relationFigure.startsWith("http://") || database.relationFigure.startsWith("https://");
const isUrl = database.relationFigure.startsWith("http://")
|| database.relationFigure.startsWith("https://");

return (
<CardLayout title="關係圖" description="資料庫表格關係圖">
<div className="space-y-4">
{isUrl
? (
<div>
<div className="overflow-hidden rounded-lg border">
<picture>
<img
src={database.relationFigure}
alt="資料庫關係圖"
className="h-auto max-h-96 w-full object-contain"
onError={(e) => {
e.currentTarget.style.display = "none";
e.currentTarget.nextElementSibling?.classList.remove("hidden");
}}
/>
</picture>
</div>
</div>
)
: (
<div>
<pre
className={`
max-h-80 overflow-x-auto rounded-lg border bg-muted p-4
font-mono text-sm whitespace-pre-wrap
`}
>
{database.relationFigure}
</pre>
</div>
)}
if (isUrl) {
return (
<a href={database.relationFigure} aria-label="打開資料庫關係圖" target="_blank" rel="noopener noreferrer">
<picture>
<img
src={database.relationFigure}
alt="資料庫關係圖"
className="h-auto max-h-80 w-full rounded object-contain"
/>
</picture>
</a>
);
}

<div className="border-t pt-2">
<p className="text-xs text-muted-foreground">
關係圖顯示了資料庫中各表格之間的關聯性和主外鍵約束。
</p>
</div>
</div>
</CardLayout>
return (
<div>
<pre
className={`
max-h-80 overflow-x-auto rounded-lg border bg-muted p-4 font-mono
text-sm whitespace-pre-wrap
`}
>
{database.relationFigure}
</pre>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,11 @@ import { useSuspenseQuery } from "@apollo/client/react";
import { DATABASE_DETAIL_QUERY } from "./query";

export function SchemaCard({ id }: { id: string }) {
const { data } = useSuspenseQuery(DATABASE_DETAIL_QUERY, {
variables: { id },
});

const database = data.database;

return (
<CardLayout title="資料結構" description="SQL DDL 建表語句">
<div className="space-y-4">
<div>
<pre
className={`
max-h-96 overflow-x-auto rounded-lg border bg-muted p-4 font-mono
text-xs whitespace-pre-wrap
`}
>
{database.schema}
</pre>
<Schema id={id} />
</div>

<div className="border-t pt-2">
Expand All @@ -34,3 +21,22 @@ export function SchemaCard({ id }: { id: string }) {
</CardLayout>
);
}

function Schema({ id }: { id: string }) {
const { data } = useSuspenseQuery(DATABASE_DETAIL_QUERY, {
variables: { id },
});

const database = data.database;

return (
<pre
className={`
max-h-96 overflow-x-auto rounded-lg border bg-muted p-4 font-mono
text-xs whitespace-pre-wrap
`}
>
{database.schema}
</pre>
);
}
9 changes: 7 additions & 2 deletions app/(admin)/(question-management)/database/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { SiteHeader } from "@/components/site-header";
import { Suspense } from "react";
import { DeleteDatabaseButtonTrigger } from "../_components/delete";
import { UpdateDatabaseButtonTrigger } from "../_components/update";
import { DescriptionCard } from "./_components/description-card";
import { Header } from "./_components/header";
import { RelationCard } from "./_components/relation-card";
import { SchemaCard } from "./_components/schema-card";
Expand Down Expand Up @@ -35,8 +37,11 @@ export default async function DatabasePage({
lg:grid-cols-2
`}
>
<SchemaCard id={id as string} />
<RelationCard id={id as string} />
<Suspense>
<DescriptionCard id={id as string} />
<RelationCard id={id as string} />
<SchemaCard id={id as string} />
</Suspense>
</div>
</main>
</>
Expand Down
6 changes: 1 addition & 5 deletions app/(admin)/(question-management)/database/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { DataTableSkeleton } from "@/components/data-table/skeleton";
import { SiteHeader } from "@/components/site-header";
import { Suspense } from "react";
import { CreateDatabaseTrigger } from "./_components/create";
import { DatabaseDataTable } from "./_components/data-table";

Expand All @@ -22,9 +20,7 @@ export default function Page() {
<CreateDatabaseTrigger />
</div>
<div>
<Suspense fallback={<DataTableSkeleton />}>
<DatabaseDataTable />
</Suspense>
<DatabaseDataTable />
</div>
</main>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ export function DatabaseCard({ id }: { id: string }) {

return (
<CardLayout title="所屬資料庫" description="這個題目要操作的資料庫。">
<p>{database.slug}</p>
<p className="text-sm text-muted-foreground">
{database.description}{" "}
<StyledLink href={`/database/${database.id}`}>
schema 等資訊 →
</StyledLink>
</p>
<div>
<p>{database.slug}</p>
<p className="text-sm text-muted-foreground">
{database.description}{" "}
<StyledLink href={`/database/${database.id}`}>
schema 等資訊 →
</StyledLink>
</p>
</div>
</CardLayout>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client";

import { CardLayout } from "@/components/card-layout";
import { useSuspenseQuery } from "@apollo/client/react";
import { Remark } from "react-remark";
import { QUESTION_DETAIL_QUERY } from "./query";

export function DescriptionCard({ id }: { id: string }) {
const { data } = useSuspenseQuery(QUESTION_DETAIL_QUERY, {
variables: { id },
});

const question = data.question;

return (
<CardLayout title="題幹描述" description="這道題目的詳細說明。">
<article
className={`
prose
dark:prose-invert
`}
>
{!question.description
? <p className="text-muted-foreground">無描述</p>
: <Remark>{question.description}</Remark>}
</article>
</CardLayout>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,16 @@ function HeaderMain({ id }: { id: string }) {
const question = data.question;
const difficultyInfo = difficultyMap[question.difficulty as keyof typeof difficultyMap];

const description = question.description?.split("\n")[0] || "此題目沒有描述";

return (
<div>
<div className="flex items-center gap-2">
<h1 className="text-2xl font-bold">題目「{question.title}」</h1>
<Badge variant="outline">{question.category}</Badge>
<Badge variant={difficultyInfo.variant}>{difficultyInfo.label}</Badge>
</div>
<p className="text-muted-foreground">{question.description}</p>
<p className="text-muted-foreground">{description}</p>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@ import { Alert, AlertDescription } from "@/components/ui/alert";
import { Badge } from "@/components/ui/badge";
import { useQuery } from "@apollo/client/react";
import { AlertTriangle, ChevronDown, Database, Table } from "lucide-react";
import React from "react";
import { useState } from "react";
import { QUESTION_REFERENCE_ANSWER_RESULT_QUERY } from "./query";

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

const handleToggle = () => {
if (!isOpen) {
setIsOpen(true);
}
};

return (
<div className="space-y-4">
<details className="group overflow-hidden rounded-lg border border-border" open={isOpen}>
<details
className="group overflow-hidden rounded-lg border border-border"
onToggle={(e) => {
setIsOpen(e.currentTarget.open);
}}
>
<summary
className={`
flex cursor-pointer items-center justify-between bg-muted/50 px-4
Expand All @@ -28,7 +26,6 @@ export function ReferenceAnswerResult({ id }: { id: string }) {
[&_svg]:shrink-0 [&_svg]:transition-transform [&_svg]:duration-200
group-open:[&_svg]:rotate-180
`}
onClick={handleToggle}
>
<span className="flex items-center gap-2 text-sm">
<Table className="h-4 w-4 text-muted-foreground" />
Expand All @@ -37,33 +34,21 @@ export function ReferenceAnswerResult({ id }: { id: string }) {
<ChevronDown className="h-4 w-4 text-muted-foreground" />
</summary>
<div className="border-t border-border bg-background p-4">
<ReferenceAnswerResultContent id={id} />
<ReferenceAnswerResultContent id={id} open={isOpen} />
</div>
</details>
</div>
);
}

function ReferenceAnswerResultContent({ id }: { id: string }) {
function ReferenceAnswerResultContent({ id, open }: { id: string; open: boolean }) {
const { data, loading, error } = useQuery(QUESTION_REFERENCE_ANSWER_RESULT_QUERY, {
variables: { id },
skip: !open,
});

if (loading) {
return (
<div className="flex items-center justify-center py-12">
<div className="flex items-center gap-3 text-muted-foreground">
<div
className={`
h-5 w-5 animate-spin rounded-full border-2
border-muted-foreground/20 border-t-muted-foreground
`}
>
</div>
<span className="text-sm">載入執行結果中...</span>
</div>
</div>
);
return null;
}

if (error) {
Expand Down
Loading