Skip to content

Commit 4916e9b

Browse files
committed
feat(questions): allow filtering table
1 parent d9635f8 commit 4916e9b

File tree

8 files changed

+124
-23
lines changed

8 files changed

+124
-23
lines changed

app/(admin)/(question-management)/questions/_components/data-table.tsx

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,27 @@
22

33
import { CursorDataTable } from "@/components/data-table/cursor";
44
import type { Direction } from "@/components/data-table/pagination";
5+
import { QuestionDifficulty } from "@/gql/graphql";
56
import { useSuspenseQuery } from "@apollo/client/react";
7+
import type { VariablesOf } from "@graphql-typed-document-node/core";
68
import { useState } from "react";
79
import { columns, type Question } from "./data-table-columns";
810
import { QUESTIONS_TABLE_QUERY } from "./query";
911

10-
export function QuestionsDataTable() {
12+
export type DifficultyFilter = "all" | QuestionDifficulty;
13+
14+
export function QuestionsDataTable({ query, difficulty }: { query?: string; difficulty: DifficultyFilter }) {
1115
const PAGE_SIZE = 20;
1216
const [cursors, setCursors] = useState<(string | null)[]>([null]);
1317
const [currentIndex, setCurrentIndex] = useState(0);
1418

1519
const after = cursors[currentIndex];
16-
const variables = { first: PAGE_SIZE, after };
20+
const variables = {
21+
first: PAGE_SIZE,
22+
after,
23+
query,
24+
difficulty: difficulty === "all" ? undefined : difficulty,
25+
} satisfies VariablesOf<typeof QUESTIONS_TABLE_QUERY>;
1726

1827
const { data } = useSuspenseQuery(QUESTIONS_TABLE_QUERY, {
1928
variables,
@@ -53,13 +62,15 @@ export function QuestionsDataTable() {
5362
};
5463

5564
return (
56-
<CursorDataTable
57-
columns={columns}
58-
data={questionList}
59-
totalCount={data?.questions.totalCount ?? 0}
60-
hasNextPage={!!pageInfo?.hasNextPage}
61-
hasPreviousPage={currentIndex > 0}
62-
onPageChange={handlePageChange}
63-
/>
65+
<>
66+
<CursorDataTable
67+
columns={columns}
68+
data={questionList}
69+
totalCount={data?.questions.totalCount ?? 0}
70+
hasNextPage={!!pageInfo?.hasNextPage}
71+
hasPreviousPage={currentIndex > 0}
72+
onPageChange={handlePageChange}
73+
/>
74+
</>
6475
);
6576
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"use client";
2+
3+
import { Input } from "@/components/ui/input";
4+
import { SelectItem } from "@/components/ui/select";
5+
6+
import { DataTableSkeleton } from "@/components/data-table/skeleton";
7+
import { Select, SelectContent, SelectTrigger, SelectValue } from "@/components/ui/select";
8+
import { QuestionDifficulty } from "@/gql/graphql";
9+
import { useDebouncedValue } from "foxact/use-debounced-value";
10+
import { Suspense, useState } from "react";
11+
import { type DifficultyFilter, QuestionsDataTable } from "./data-table";
12+
13+
export default function FilterableDataTable() {
14+
const [query, setQuery] = useState("");
15+
const [difficulty, setDifficulty] = useState<DifficultyFilter>("all");
16+
17+
const debouncedQuery = useDebouncedValue(query, 200);
18+
19+
return (
20+
<div className="flex flex-col">
21+
<div className="mb-4 flex items-center gap-4">
22+
<Input
23+
placeholder="搜尋題目"
24+
value={query}
25+
onChange={(e) => setQuery(e.target.value)}
26+
/>
27+
<Select
28+
value={difficulty}
29+
onValueChange={(value) => setDifficulty(value as DifficultyFilter)}
30+
>
31+
<SelectTrigger>
32+
<SelectValue placeholder="選擇難度" />
33+
</SelectTrigger>
34+
<SelectContent>
35+
<SelectItem value="all">全部</SelectItem>
36+
<SelectItem value={QuestionDifficulty.Easy}>簡單</SelectItem>
37+
<SelectItem value={QuestionDifficulty.Medium}>中等</SelectItem>
38+
<SelectItem value={QuestionDifficulty.Hard}>困難</SelectItem>
39+
<SelectItem value={QuestionDifficulty.Unspecified}>
40+
未指定
41+
</SelectItem>
42+
</SelectContent>
43+
</Select>
44+
</div>
45+
46+
<Suspense fallback={<DataTableSkeleton />}>
47+
<QuestionsDataTable query={debouncedQuery} difficulty={difficulty} />
48+
</Suspense>
49+
</div>
50+
);
51+
}

app/(admin)/(question-management)/questions/_components/query.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,24 @@ export const QUESTIONS_TABLE_QUERY = graphql(`
3232
$first: Int
3333
$after: Cursor
3434
$last: Int
35-
$before: Cursor
35+
$before: Cursor,
36+
$query: String,
37+
$difficulty: QuestionDifficulty
3638
) {
37-
questions(first: $first, after: $after, last: $last, before: $before) {
39+
questions(
40+
first: $first,
41+
after: $after,
42+
last: $last,
43+
before: $before,
44+
where: {
45+
or: [
46+
{ titleContains: $query },
47+
{ categoryContains: $query },
48+
{ descriptionContains: $query },
49+
],
50+
difficulty: $difficulty,
51+
},
52+
) {
3853
edges {
3954
node {
4055
id

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { DataTableSkeleton } from "@/components/data-table/skeleton";
21
import { SiteHeader } from "@/components/site-header";
32
import type { Metadata } from "next";
4-
import { Suspense } from "react";
53
import { CreateQuestionTrigger } from "./_components/create";
6-
import { QuestionsDataTable } from "./_components/data-table";
4+
import FilterableDataTable from "./_components/filterable-data-table";
75

86
export const metadata: Metadata = {
97
title: "題庫",
@@ -22,14 +20,14 @@ export default function Page() {
2220
<div className="flex items-center justify-between space-y-2">
2321
<div>
2422
<h2 className="text-2xl font-bold tracking-tight">題庫管理</h2>
25-
<p className="text-muted-foreground">管理 SQL 練習題目,設定難度與分類。</p>
23+
<p className="text-muted-foreground">
24+
管理 SQL 練習題目,設定難度與分類。
25+
</p>
2626
</div>
2727
<CreateQuestionTrigger />
2828
</div>
2929
<div>
30-
<Suspense fallback={<DataTableSkeleton />}>
31-
<QuestionsDataTable />
32-
</Suspense>
30+
<FilterableDataTable />
3331
</div>
3432
</main>
3533
</>

gql/gql.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type Documents = {
3333
"\n mutation DeleteQuestion($id: ID!) {\n deleteQuestion(id: $id)\n }\n": typeof types.DeleteQuestionDocument,
3434
"\n query QuestionById($id: ID!) {\n question(id: $id) {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n": typeof types.QuestionByIdDocument,
3535
"\n query DatabaseList {\n databases {\n id\n slug\n description\n }\n }\n": typeof types.DatabaseListDocument,
36-
"\n query QuestionsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n ) {\n questions(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n": typeof types.QuestionsTableDocument,
36+
"\n query QuestionsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor,\n $query: String,\n $difficulty: QuestionDifficulty\n ) {\n questions(\n first: $first,\n after: $after,\n last: $last,\n before: $before,\n where: {\n or: [\n { titleContains: $query },\n { categoryContains: $query },\n { descriptionContains: $query },\n ],\n difficulty: $difficulty,\n },\n ) {\n edges {\n node {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n": typeof types.QuestionsTableDocument,
3737
"\n query GroupAuditInfo($id: ID!) {\n group(id: $id) {\n id\n createdAt\n updatedAt\n }\n }\n": typeof types.GroupAuditInfoDocument,
3838
"\n query GroupHeader($id: ID!) {\n group(id: $id) {\n id\n name\n description\n }\n }\n": typeof types.GroupHeaderDocument,
3939
"\n query GroupMembers($id: ID!) {\n users(where: { hasGroupWith: { id: $id } }) {\n totalCount\n }\n }\n": typeof types.GroupMembersDocument,
@@ -93,7 +93,7 @@ const documents: Documents = {
9393
"\n mutation DeleteQuestion($id: ID!) {\n deleteQuestion(id: $id)\n }\n": types.DeleteQuestionDocument,
9494
"\n query QuestionById($id: ID!) {\n question(id: $id) {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n": types.QuestionByIdDocument,
9595
"\n query DatabaseList {\n databases {\n id\n slug\n description\n }\n }\n": types.DatabaseListDocument,
96-
"\n query QuestionsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n ) {\n questions(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n": types.QuestionsTableDocument,
96+
"\n query QuestionsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor,\n $query: String,\n $difficulty: QuestionDifficulty\n ) {\n questions(\n first: $first,\n after: $after,\n last: $last,\n before: $before,\n where: {\n or: [\n { titleContains: $query },\n { categoryContains: $query },\n { descriptionContains: $query },\n ],\n difficulty: $difficulty,\n },\n ) {\n edges {\n node {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n": types.QuestionsTableDocument,
9797
"\n query GroupAuditInfo($id: ID!) {\n group(id: $id) {\n id\n createdAt\n updatedAt\n }\n }\n": types.GroupAuditInfoDocument,
9898
"\n query GroupHeader($id: ID!) {\n group(id: $id) {\n id\n name\n description\n }\n }\n": types.GroupHeaderDocument,
9999
"\n query GroupMembers($id: ID!) {\n users(where: { hasGroupWith: { id: $id } }) {\n totalCount\n }\n }\n": types.GroupMembersDocument,
@@ -227,7 +227,7 @@ export function graphql(source: "\n query DatabaseList {\n databases {\n
227227
/**
228228
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
229229
*/
230-
export function graphql(source: "\n query QuestionsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n ) {\n questions(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n"): (typeof documents)["\n query QuestionsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor\n ) {\n questions(first: $first, after: $after, last: $last, before: $before) {\n edges {\n node {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n"];
230+
export function graphql(source: "\n query QuestionsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor,\n $query: String,\n $difficulty: QuestionDifficulty\n ) {\n questions(\n first: $first,\n after: $after,\n last: $last,\n before: $before,\n where: {\n or: [\n { titleContains: $query },\n { categoryContains: $query },\n { descriptionContains: $query },\n ],\n difficulty: $difficulty,\n },\n ) {\n edges {\n node {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n"): (typeof documents)["\n query QuestionsTable(\n $first: Int\n $after: Cursor\n $last: Int\n $before: Cursor,\n $query: String,\n $difficulty: QuestionDifficulty\n ) {\n questions(\n first: $first,\n after: $after,\n last: $last,\n before: $before,\n where: {\n or: [\n { titleContains: $query },\n { categoryContains: $query },\n { descriptionContains: $query },\n ],\n difficulty: $difficulty,\n },\n ) {\n edges {\n node {\n id\n title\n description\n category\n difficulty\n referenceAnswer\n database {\n id\n slug\n }\n }\n }\n totalCount\n pageInfo {\n hasNextPage\n hasPreviousPage\n endCursor\n startCursor\n }\n }\n }\n"];
231231
/**
232232
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
233233
*/

0 commit comments

Comments
 (0)