Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add test filters #90

Merged
merged 1 commit into from
Jan 20, 2024
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
Expand Up @@ -74,38 +74,36 @@ export const QuestionSearch = container<Props, Params, Data>(
placeholder="search Questions..."
/>

<FormProvider {...form}>
<SearchFilters
activeFilters={numberOfFilters}
fallback={
<>
<Select size="sm" />
<Select size="sm" />
</>
}
filters={
<>
<HookFormSelect size="sm" {...form.register("searchField")}>
<Option value={"all"}>All Fields</Option>
<Option value={"text"}>Question</Option>
<Option value={"questionId"}>Id</Option>
<Option value={"learningObjectives"}>
Learning Objectives
<SearchFilters
activeFilters={numberOfFilters}
fallback={
<>
<Select size="sm" />
<Select size="sm" />
</>
}
filters={
<FormProvider {...form}>
<HookFormSelect size="sm" {...form.register("searchField")}>
<Option value={"all"}>All Fields</Option>
<Option value={"text"}>Question</Option>
<Option value={"questionId"}>Id</Option>
<Option value={"learningObjectives"}>
Learning Objectives
</Option>
<Option value={"externalIds"}>External Ids</Option>
</HookFormSelect>
<HookFormSelect size="sm" {...form.register("subject")}>
<Option value={"all"}>All Subjects</Option>
{subjects.map(({ id, shortName }) => (
<Option value={id} key={id}>
{shortName}
</Option>
<Option value={"externalIds"}>External Ids</Option>
</HookFormSelect>
<HookFormSelect size="sm" {...form.register("subject")}>
<Option value={"all"}>All Subjects</Option>
{subjects.map(({ id, shortName }) => (
<Option value={id} key={id}>
{shortName}
</Option>
))}
</HookFormSelect>
</>
}
/>
</FormProvider>
))}
</HookFormSelect>
</FormProvider>
}
/>
</Stack>

<QuestionList
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createUsePersistenceHook } from "../../hooks/use-persistence";
import type { QuestionBankName } from "@chair-flight/base/types";
import type { UseFormReturn } from "react-hook-form";

const searchConfigSchema = z.object({
mode: z.enum(["all", "study", "exam"]),
status: z.enum(["all", "created", "started", "finished"]),
});

type SearchConfig = z.infer<typeof searchConfigSchema>;

const defaultSearchConfig: z.infer<typeof searchConfigSchema> = {
mode: "all",
status: "all",
};

const searchPersistence = {
"cf-test-search-atpl": createUsePersistenceHook<SearchConfig>(
"cf-test-search-atpl",
),
"cf-test-search-type": createUsePersistenceHook<SearchConfig>(
"cf-test-search-type",
),
"cf-test-search-prep": createUsePersistenceHook<SearchConfig>(
"cf-test-search-prep",
),
};

const resolver = zodResolver(searchConfigSchema);

export const useSearchConfig = (
questionBank: QuestionBankName,
): [SearchConfig, UseFormReturn<SearchConfig>] => {
const key = `cf-test-search-${questionBank}` as const;
const useSearchPersistence = searchPersistence[key];
const { persistedData, setPersistedData } = useSearchPersistence();
const defaultValues = persistedData ?? defaultSearchConfig;
const form = useForm({ defaultValues, resolver });
const mode = form.watch("mode");
const status = form.watch("status");

useEffect(() => {
setPersistedData({ mode, status });
}, [mode, status, setPersistedData]);

return [{ mode, status }, form];
};
76 changes: 69 additions & 7 deletions libs/react/containers/src/tests/test-search/test-search.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { FormProvider } from "react-hook-form";
import { default as DeleteIcon } from "@mui/icons-material/DeleteOutlineOutlined";
import { default as PlayIcon } from "@mui/icons-material/PlayArrowOutlined";
import { default as EyeIcon } from "@mui/icons-material/VisibilityOutlined";
Expand All @@ -8,14 +9,22 @@ import {
IconButton,
Link,
ListItemContent,
Select,
Option,
Stack,
Tooltip,
Typography,
selectClasses,
} from "@mui/joy";
import { processTest } from "@chair-flight/core/app";
import { SearchList } from "@chair-flight/react/components";
import {
HookFormSelect,
SearchFilters,
SearchList,
} from "@chair-flight/react/components";
import { container } from "../../wraper/container";
import { useTestProgress } from "../hooks/use-test-progress";
import { useSearchConfig } from "./test-search-config-schema";
import type { QuestionBankName } from "@chair-flight/base/types";

type Props = {
Expand All @@ -24,18 +33,62 @@ type Props = {

export const TestSearch = container<Props>(
({ questionBank, sx, component = "section" }) => {
const [{ mode, status }, form] = useSearchConfig(questionBank);

const tests = useTestProgress((s) => s.tests);
const deleteTest = useTestProgress((s) => s.deleteTest);

const testsAsList = Object.values(tests)
.sort((a, b) => b.createdAtEpochMs - a.createdAtEpochMs)
.filter((test) => test.questionBank === questionBank)
.filter((test) => {
if (test.questionBank !== questionBank) return false;
if (status !== "all" && status !== test.status) return false;
if (mode !== "all" && mode !== test.mode) return false;
return true;
})
.map((test) => ({ ...test, ...processTest(test) }));

const numberOfFilters = Number(mode !== "all") + Number(status !== "all");

return (
<Stack component={component} sx={sx}>
<Stack sx={{ pb: { xs: 1, md: 2 } }}>
<Stack
direction={"row"}
sx={{
gap: 1,
justifyContent: "flex-end",
mb: { xs: 1, sm: 2 },
[`& .${selectClasses.root}`]: {
width: "13em",
},
}}
>
<SearchFilters
activeFilters={numberOfFilters}
filters={
<FormProvider {...form}>
<HookFormSelect size="sm" {...form.register("mode")}>
<Option value={"all"}>All Modes</Option>
<Option value={"study"}>Study</Option>
<Option value={"exam"}>Exam</Option>
</HookFormSelect>
<HookFormSelect size="sm" {...form.register("status")}>
<Option value={"all"}>All States</Option>
<Option value={"started"}>Started</Option>
<Option value={"finished"}>Finished</Option>
</HookFormSelect>
</FormProvider>
}
fallback={
<>
<Select size="sm" />
<Select size="sm" />
</>
}
/>

<Button
sx={{ ml: "auto" }}
size="sm"
component={Link}
href={`/modules/${questionBank}/tests/create`}
>
Expand All @@ -51,8 +104,10 @@ export const TestSearch = container<Props>(
<thead>
<tr>
<th style={{ width: "4em" }}>Score</th>
<th style={{ width: "4em" }}>Type</th>
<th style={{ width: "5em" }}>State</th>
<th>Title</th>
<th style={{ width: "12em" }}>Type</th>

<th style={{ textAlign: "center", width: "8em" }}>
No. Questions
</th>
Expand Down Expand Up @@ -93,9 +148,16 @@ export const TestSearch = container<Props>(
component="td"
sx={{ color: `${test.color}.500`, fontWeight: 700 }}
>
{test.title}
{test.mode}
</Box>
<td>{test.mode}</td>
<Box
component="td"
sx={{ color: `${test.color}.500`, fontWeight: 700 }}
>
{test.status}
</Box>
<Box component="td">{test.title}</Box>

<Box component="td" sx={{ textAlign: "center" }}>
{test.questions.length}
</Box>
Expand Down