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
42 changes: 41 additions & 1 deletion app/(app)/challenges/_components/filter/tag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,35 @@ import { difficultyTranslation } from "@/components/question/difficulty-badge";
import { solvedStatusTranslation } from "@/components/question/solved-status-badge";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { graphql } from "@/gql";
import { QuestionDifficulty } from "@/gql/graphql";
import type { SolvedStatus } from "@/lib/solved-status";
import { useSuspenseQuery } from "@apollo/client/react";
import { FilterIcon } from "lucide-react";

export interface TagState {
solvedStatus: SolvedStatus[];
difficulty: QuestionDifficulty[];
categories: string[];
}

export interface TagFilterSectionProps {
value: TagState;
onChange: (tags: TagState) => void;
}

const TAG_FILTER_SECTION_QUERY = graphql(`
query TagFilterSection {
questionCategories
}
`);

export default function TagFilterSection({
value,
onChange,
}: TagFilterSectionProps) {
const { data } = useSuspenseQuery(TAG_FILTER_SECTION_QUERY);

const getSolvedStatus = (solvedStatus: SolvedStatus) => {
return value.solvedStatus.includes(solvedStatus);
};
Expand All @@ -28,6 +39,10 @@ export default function TagFilterSection({
return value.difficulty.includes(difficulty);
};

const getCategory = (category: string) => {
return value.categories.includes(category);
};

const handleDifficultyChange = (difficulty: QuestionDifficulty) => {
return (checked: boolean) => {
onChange({
Expand All @@ -50,6 +65,15 @@ export default function TagFilterSection({
};
};

const handleCategoryChange = (category: string) => {
return (checked: boolean) => {
onChange({
...value,
categories: checked ? [...value.categories, category] : value.categories.filter((c) => c !== category),
});
};
};

return (
<div className="space-y-3">
<label className="flex items-center gap-2 font-bold">
Expand Down Expand Up @@ -81,7 +105,7 @@ export default function TagFilterSection({
</div>
</div>

<div className="space-y-2 text-sm text-muted-foreground">
<div className="mb-4 space-y-2 text-sm text-muted-foreground">
難度
<div className="mt-2 space-y-2">
<TagCheckbox
Expand Down Expand Up @@ -110,6 +134,22 @@ export default function TagFilterSection({
/>
</div>
</div>

<div className="space-y-2 text-sm text-muted-foreground">
分類
<div className="mt-2 space-y-2">
{data.questionCategories.map((category) => (
<div key={category} className="flex items-center gap-2">
<Checkbox
id={category}
checked={getCategory(category)}
onCheckedChange={handleCategoryChange(category)}
/>
<Label htmlFor={category}>{category}</Label>
</div>
))}
</div>
</div>
</div>
);
}
Expand Down
48 changes: 26 additions & 22 deletions app/(app)/challenges/_components/questions-list.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useDebouncedValue } from "foxact/use-debounced-value";
import { Suspense, useState } from "react";
import { Suspense, useState, useTransition } from "react";
import type { TagState } from "./filter/tag";

import QuestionCard from "@/components/question/question-card";
Expand Down Expand Up @@ -41,6 +41,7 @@ export default function QuestionsList() {
QuestionDifficulty.Hard,
QuestionDifficulty.Unspecified,
],
categories: [],
});

const deferredSearch = useDebouncedValue(search, 200);
Expand All @@ -54,12 +55,10 @@ export default function QuestionsList() {
{
descriptionContainsFold: deferredSearch,
},
{
categoryContainsFold: deferredSearch,
},
]
: undefined,
difficultyIn: tags.difficulty.length > 0 ? tags.difficulty : undefined,
difficultyIn: tags.difficulty || undefined,
categoryIn: tags.categories || undefined,
};

return (
Expand Down Expand Up @@ -94,6 +93,7 @@ export function ChallengeQuestionsList({
where: QuestionWhereInput;
solvedStatusContains: SolvedStatus[];
}) {
const [isPending, startTransition] = useTransition();
const { data, fetchMore } = useSuspenseQuery(LIST_QUESTIONS, {
variables: { where },
});
Expand All @@ -117,23 +117,27 @@ export function ChallengeQuestionsList({
{data?.questions.pageInfo.hasNextPage && (
<div className="flex w-full justify-center">
<Button
onClick={() =>
fetchMore({
variables: {
after: data?.questions.pageInfo.endCursor,
},
updateQuery(previousQueryResult, options) {
return {
questions: {
edges: [
...(previousQueryResult.questions.edges || []),
...(options.fetchMoreResult.questions.edges || []),
],
pageInfo: options.fetchMoreResult.questions.pageInfo,
},
};
},
})}
disabled={isPending}
onClick={() => {
startTransition(() => {
fetchMore({
variables: {
after: data?.questions.pageInfo.endCursor,
},
updateQuery(previousQueryResult, options) {
return {
questions: {
edges: [
...(previousQueryResult.questions.edges || []),
...(options.fetchMoreResult.questions.edges || []),
],
pageInfo: options.fetchMoreResult.questions.pageInfo,
},
};
},
});
});
}}
>
載入更多
</Button>
Expand Down
4 changes: 2 additions & 2 deletions components/question/question-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export default function QuestionCard({
<div className="flex-1 space-y-3 bg-white p-4">
<div>
<h2 className="font-bold tracking-wider">{question.title}</h2>
<p className="tracking-wide">
<div className="tracking-wide">
<Remark>{descriptionFirstLine}</Remark>
</p>
</div>
</div>
<div className="flex flex-wrap gap-1">
<SolvedStatusBadge solvedStatus={solvedStatus} />
Expand Down
6 changes: 6 additions & 0 deletions gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Documents = {
"\n query SqlEditorContext($id: ID!) {\n question(id: $id) {\n id\n database {\n id\n ...DatabaseStructure\n }\n lastSubmission {\n id\n submittedCode\n }\n }\n }\n": typeof types.SqlEditorContextDocument,
"\n query SubmissionHistory($id: ID!) {\n question(id: $id) {\n id\n userSubmissions {\n id\n status\n submittedCode\n submittedAt\n }\n }\n }\n": typeof types.SubmissionHistoryDocument,
"\n fragment DatabaseStructure on Database {\n id\n structure {\n tables {\n columns\n name\n }\n }\n }\n": typeof types.DatabaseStructureFragmentDoc,
"\n query TagFilterSection {\n questionCategories\n }\n": typeof types.TagFilterSectionDocument,
"\n query ChallengeStatistics {\n me {\n id\n submissionStatistics {\n totalQuestions\n solvedQuestions\n attemptedQuestions\n }\n }\n }\n": typeof types.ChallengeStatisticsDocument,
"\n query ListQuestions($where: QuestionWhereInput, $after: Cursor) {\n questions(where: $where, first: 10, after: $after) {\n edges {\n node {\n id\n ...QuestionCard\n ...QuestionSolvedStatus\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": typeof types.ListQuestionsDocument,
"\n fragment MaterialsSchemaCard on Database {\n id\n slug\n description\n }\n": typeof types.MaterialsSchemaCardFragmentDoc,
Expand Down Expand Up @@ -54,6 +55,7 @@ const documents: Documents = {
"\n query SqlEditorContext($id: ID!) {\n question(id: $id) {\n id\n database {\n id\n ...DatabaseStructure\n }\n lastSubmission {\n id\n submittedCode\n }\n }\n }\n": types.SqlEditorContextDocument,
"\n query SubmissionHistory($id: ID!) {\n question(id: $id) {\n id\n userSubmissions {\n id\n status\n submittedCode\n submittedAt\n }\n }\n }\n": types.SubmissionHistoryDocument,
"\n fragment DatabaseStructure on Database {\n id\n structure {\n tables {\n columns\n name\n }\n }\n }\n": types.DatabaseStructureFragmentDoc,
"\n query TagFilterSection {\n questionCategories\n }\n": types.TagFilterSectionDocument,
"\n query ChallengeStatistics {\n me {\n id\n submissionStatistics {\n totalQuestions\n solvedQuestions\n attemptedQuestions\n }\n }\n }\n": types.ChallengeStatisticsDocument,
"\n query ListQuestions($where: QuestionWhereInput, $after: Cursor) {\n questions(where: $where, first: 10, after: $after) {\n edges {\n node {\n id\n ...QuestionCard\n ...QuestionSolvedStatus\n }\n }\n pageInfo {\n hasNextPage\n endCursor\n }\n }\n }\n": types.ListQuestionsDocument,
"\n fragment MaterialsSchemaCard on Database {\n id\n slug\n description\n }\n": types.MaterialsSchemaCardFragmentDoc,
Expand Down Expand Up @@ -128,6 +130,10 @@ export function graphql(source: "\n query SubmissionHistory($id: ID!) {\n qu
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment DatabaseStructure on Database {\n id\n structure {\n tables {\n columns\n name\n }\n }\n }\n"): (typeof documents)["\n fragment DatabaseStructure on Database {\n id\n structure {\n tables {\n columns\n name\n }\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n query TagFilterSection {\n questionCategories\n }\n"): (typeof documents)["\n query TagFilterSection {\n questionCategories\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
Expand Down
Loading