From b23ecb994de7e5c6c1e88b92322ae84412d5c430 Mon Sep 17 00:00:00 2001 From: PupoSDC Date: Wed, 24 Jan 2024 09:44:45 +0000 Subject: [PATCH] content: add some explanations to 737 questions (#96) --- .../[questionBank]/settings/index.page.tsx | 2 +- .../question-bank-type/__old__/737.json | 37 +------ .../questions/B737/737.02.json | 97 +++++++++++-------- .../questions/B737/737.06.json | 38 +++++++- .../questions/B737/737.13.json | 58 ++++++++++- .../questions/B737/737.14.json | 4 +- libs/core/app/src/index.ts | 1 + libs/core/app/src/tests/create-test.ts | 3 + .../get-number-of-available-questions.ts | 26 +++++ .../question-bank/src/question-bank.test.ts | 9 ++ .../src/app-buttons/app-buttons.tsx | 43 ++------ .../create-use-persistence-hook.ts | 14 ++- .../learning-objectives-search.tsx | 12 ++- .../overview-welcome/welcome.test.tsx | 2 +- .../question-search/question-search.tsx | 8 +- .../use-test-progress/use-test-progress.ts | 6 +- .../src/tests/test-maker/test-maker.tsx | 17 +++- .../src/tests/test-study/test-study.tsx | 2 + .../user/hooks/use-user-preferences/index.ts | 2 +- .../use-user-preferences.ts | 53 ++++++---- .../src/user/user-settings/user-settings.tsx | 24 +++-- 21 files changed, 288 insertions(+), 170 deletions(-) create mode 100644 libs/core/app/src/tests/get-number-of-available-questions.ts diff --git a/apps/next-app/pages/modules/[questionBank]/settings/index.page.tsx b/apps/next-app/pages/modules/[questionBank]/settings/index.page.tsx index 8c41da6ed..7dfd09340 100644 --- a/apps/next-app/pages/modules/[questionBank]/settings/index.page.tsx +++ b/apps/next-app/pages/modules/[questionBank]/settings/index.page.tsx @@ -22,7 +22,7 @@ const Page: NextPage = ({ questionBank }) => { ] as Breadcrumbs; return ( - + diff --git a/libs/content/question-bank-type/__old__/737.json b/libs/content/question-bank-type/__old__/737.json index ed341996d..097dc1744 100644 --- a/libs/content/question-bank-type/__old__/737.json +++ b/libs/content/question-bank-type/__old__/737.json @@ -9324,42 +9324,7 @@ "id": "jjjb95", "explanation": "", "learningObjectives": ["B737"], - "variants": { - "as0oz": { - "id": "as0oz", - "type": "simple", - "question": "The purpose of the landing gear transfer unit is to:", - "options": [ - { - "id": "uafie7", - "text": "Automatically use hydraulic system B for landing gear retraction if hydraulic system A engine driven pump fails and the landing gear lever is positioned up", - "correct": false, - "why": "" - }, - { - "id": "9co2f", - "text": "Automatically use hydraulic system B for gear retraction if No 1 engine is lost and the landing gear lever is positioned up", - "correct": true, - "why": "" - }, - { - "id": "665xz", - "text": "Allow the use of nose wheel steering in the event of hydraulic system A fails", - "correct": false, - "why": "" - }, - { - "id": "obpl2w", - "text": "Allow landing gear retraction if hydraulic system B is lost during takeoff", - "correct": false, - "why": "" - } - ], - "explanation": "> ## Landing Gear Transfer Unit\n> \n> The purpose of the landing gear transfer unit is to supply the volume of hydraulic\n> fluid needed to raise the landing gear at the normal rate when system A\n> engine–driven pump volume is lost. The system B engine–driven pump supplies\n> the volume of hydraulic fluid needed to operate the landing gear transfer unit\n> when all of the following conditions exist:\n> - airborne\n> - No. 1 engine RPM drops below a limit value\n> - landing gear lever is positioned UP\n> - either main landing gear is not up and locked.", - "annexes": [], - "externalIds": [] - } - } + "variants": {} }, { "id": "80pwx8", diff --git a/libs/content/question-bank-type/questions/B737/737.02.json b/libs/content/question-bank-type/questions/B737/737.02.json index 733227919..555c5b36a 100644 --- a/libs/content/question-bank-type/questions/B737/737.02.json +++ b/libs/content/question-bank-type/questions/B737/737.02.json @@ -83,7 +83,7 @@ }, { "id": "h3ia", - "explanation": "", + "explanation": "### TRIP RESET Switch\n\n- resets BLEED TRIP OFF, PACK or ZONE TEMP lights\n\n- related engine bleed air valve opens, or related pack valve opens, or related trim air modulating valve opens", "learningObjectives": ["B737.02"], "variants": { "hmkql": { @@ -450,7 +450,7 @@ }, { "id": "np5w1", - "explanation": "", + "explanation": "Bleed air is taken from **stage 5** from the **low pressure** compressor and **stage 9** of the **high pressure compressor**.", "learningObjectives": ["B737.02"], "variants": { "f4zwnj": { @@ -461,6 +461,58 @@ "explanation": "", "annexes": [], "externalIds": [] + }, + "f4zwng": { + "id": "f4zwng", + "type": "true-or-false", + "question": "Bleed air is taken from stage 5 and stage 9 of the high pressure compressor.", + "answer": false, + "explanation": "", + "annexes": [], + "externalIds": [] + }, + "f4zWnb": { + "id": "f4zWnb", + "type": "true-or-false", + "question": "Bleed air is taken from stage 5 from the low pressure compressor and stage 9 of the high pressure compressor.", + "answer": false, + "explanation": "", + "annexes": [], + "externalIds": [] + }, + "i8bbqh": { + "id": "i8bbqh", + "type": "simple", + "question": "Which statement is correct:", + "options": [ + { + "id": "4ha3ff", + "text": "Bleed air is constantly provided from stage 5 and stage 9 of the compressor.", + "correct": false, + "why": "" + }, + { + "id": "bc8lm", + "text": "Bleed air is only supplied from the 9th stage when the valve modulates open if bleed air requirement is greater than the 5th stage alone can supply.", + "correct": true, + "why": "" + }, + { + "id": "m6ni5i", + "text": "Bleed air is only supplied from the 5th stage when the valve modulates open if bleed air requirement is greater than the 9th stage alone can supply.", + "correct": false, + "why": "" + }, + { + "id": "mljcp", + "text": "----", + "correct": false, + "why": "" + } + ], + "explanation": "", + "annexes": [], + "externalIds": [] } } }, @@ -546,47 +598,6 @@ } } }, - { - "id": "412mv", - "explanation": "", - "learningObjectives": ["B737.02"], - "variants": { - "i8bbqh": { - "id": "i8bbqh", - "type": "simple", - "question": "Which statement is correct:", - "options": [ - { - "id": "4ha3ff", - "text": "Bleed air is constantly provided from stage 5 and stage 9 of the compressor.", - "correct": false, - "why": "" - }, - { - "id": "bc8lm", - "text": "Bleed air is only supplied from the 9th stage when the valve modulates open if bleed air requirement is greater than the 5th stage alone can supply.", - "correct": true, - "why": "" - }, - { - "id": "m6ni5i", - "text": "Bleed air is only supplied from the 5th stage when the valve modulates open if bleed air requirement is greater than the 9th stage alone can supply.", - "correct": false, - "why": "" - }, - { - "id": "mljcp", - "text": "----", - "correct": false, - "why": "" - } - ], - "explanation": "", - "annexes": [], - "externalIds": [] - } - } - }, { "id": "fym5q", "explanation": "", diff --git a/libs/content/question-bank-type/questions/B737/737.06.json b/libs/content/question-bank-type/questions/B737/737.06.json index b0a7353ca..09528ac0c 100644 --- a/libs/content/question-bank-type/questions/B737/737.06.json +++ b/libs/content/question-bank-type/questions/B737/737.06.json @@ -304,7 +304,7 @@ }, { "id": "kkksb", - "explanation": "", + "explanation": "In–flight, an amber TR UNIT light illuminates if TR1, or TR2 and TR3 has failed.\n\nOn the ground, any TR fault causes the light to illuminate", "learningObjectives": ["B737.06"], "variants": { "f183a": { @@ -340,6 +340,40 @@ "explanation": "", "annexes": [], "externalIds": [] + }, + "fg83b": { + "id": "fg83b", + "type": "simple", + "question": "The amber TR UNIT light illuminated while on the ground indicates if?", + "options": [ + { + "id": "qkd9u", + "text": "TR1 and TR2 have failed, or TR3 has failed.", + "correct": false, + "why": "" + }, + { + "id": "41jw4", + "text": "One TR has failed.", + "correct": true, + "why": "" + }, + { + "id": "7gq6w", + "text": "TR1 has failed, or TR2 and TR3 have failed.", + "correct": false, + "why": "" + }, + { + "id": "scgr5", + "text": "All three TRs have failed.", + "correct": false, + "why": "" + } + ], + "explanation": "", + "annexes": [], + "externalIds": [] } } }, @@ -746,7 +780,7 @@ }, { "id": "ifs1zk", - "explanation": "", + "explanation": "Generator Drive Disconnect (DISCONNECT) Switches (guarded)\n\nDisconnects IDG if electrical power is available and engine start lever is in IDLE. IDG cannot be reconnected in the air.", "learningObjectives": ["B737.06"], "variants": { "6x6cs": { diff --git a/libs/content/question-bank-type/questions/B737/737.13.json b/libs/content/question-bank-type/questions/B737/737.13.json index da1a74541..a7dee49d5 100644 --- a/libs/content/question-bank-type/questions/B737/737.13.json +++ b/libs/content/question-bank-type/questions/B737/737.13.json @@ -582,9 +582,43 @@ }, { "id": "9jghi", - "explanation": "", + "explanation": "> ## Landing Gear Transfer Unit\n> \n> The purpose of the landing gear transfer unit is to supply the volume of hydraulic\n> fluid needed to raise the landing gear at the normal rate when system A\n> engine–driven pump volume is lost. The system B engine–driven pump supplies\n> the volume of hydraulic fluid needed to operate the landing gear transfer unit\n> when all of the following conditions exist:\n> - airborne\n> - No. 1 engine RPM drops below a limit value\n> - landing gear lever is positioned UP\n> - either main landing gear is not up and locked.", "learningObjectives": ["B737.13"], "variants": { + "as0oz": { + "id": "as0oz", + "type": "simple", + "question": "The purpose of the landing gear transfer unit is to:", + "options": [ + { + "id": "uafie7", + "text": "Automatically use hydraulic system B for landing gear retraction if hydraulic system A engine driven pump fails and the landing gear lever is positioned up", + "correct": false, + "why": "" + }, + { + "id": "9co2f", + "text": "Automatically use hydraulic system B for gear retraction if No 1 engine is lost and the landing gear lever is positioned up", + "correct": true, + "why": "" + }, + { + "id": "665xz", + "text": "Allow the use of nose wheel steering in the event of hydraulic system A fails", + "correct": false, + "why": "" + }, + { + "id": "obpl2w", + "text": "Allow landing gear retraction if hydraulic system B is lost during takeoff", + "correct": false, + "why": "" + } + ], + "explanation": "", + "annexes": [], + "externalIds": [] + }, "oubsui": { "id": "oubsui", "type": "simple", @@ -705,13 +739,31 @@ }, { "id": "9gdph", - "explanation": "", + "explanation": "The hydraulic system quantity is displayed on the **lower DU** by digital indication from 0 to **106%.**", "learningObjectives": ["B737.13"], "variants": { "m1sd1f": { "id": "m1sd1f", "type": "true-or-false", - "question": "The hydraulic system quantity is displayed on the lower DU by digital indication from 0 to 100%.", + "question": "The hydraulic system quantity is displayed on the upper DU by digital indication from 0 to 106%.", + "answer": false, + "explanation": "", + "annexes": [], + "externalIds": [] + }, + "m1sd1g": { + "id": "m1sd1g", + "type": "true-or-false", + "question": "The hydraulic system quantity is displayed on the lower DU by digital indication from 0 to 106%.", + "answer": true, + "explanation": "", + "annexes": [], + "externalIds": [] + }, + "m1sdhf": { + "id": "m1sdhf", + "type": "true-or-false", + "question": "The hydraulic system quantity is displayed on the upper DU by digital indication from 0 to 100%.", "answer": false, "explanation": "", "annexes": [], diff --git a/libs/content/question-bank-type/questions/B737/737.14.json b/libs/content/question-bank-type/questions/B737/737.14.json index 941779c7d..c412d06f3 100644 --- a/libs/content/question-bank-type/questions/B737/737.14.json +++ b/libs/content/question-bank-type/questions/B737/737.14.json @@ -254,7 +254,7 @@ }, { "id": "1d0wxi", - "explanation": "", + "explanation": "> **Autobrake – Disarm**\n\n>\n\n> The pilots may disarm the autobrake system by moving the selector switch to the\n\n> OFF position. This action does not cause the AUTO BRAKE DISARM light to\n\n> illuminate. After braking has started, any of the following pilot actions disarm the\n\n> system immediately and illuminate the AUTO BRAKE DISARM light:\n\n> • moving the SPEED BRAKE lever to the down detent\n\n> • advancing the forward thrust lever(s), except during the first 3 seconds after touchdown for landing\n\n> • applying manual brakes.", "learningObjectives": ["B737.14"], "variants": { "thzm2": { @@ -266,7 +266,7 @@ "id": "xcdlc", "text": "Applying manual brakes, Moving Speed Brake lever to DOWN detent or Advancing forward thrust levers immediately after touchdown.", "correct": false, - "why": "" + "why": "Immediately applying thrust will not cancel autobrake. Only after 3 seconds." }, { "id": "5tm1j", diff --git a/libs/core/app/src/index.ts b/libs/core/app/src/index.ts index bedf51694..f820e28a1 100644 --- a/libs/core/app/src/index.ts +++ b/libs/core/app/src/index.ts @@ -4,3 +4,4 @@ export * from "./questions/get-question-preview"; export * from "./random/random"; export * from "./tests/create-test"; export * from "./tests/process-test"; +export * from "./tests/get-number-of-available-questions"; diff --git a/libs/core/app/src/tests/create-test.ts b/libs/core/app/src/tests/create-test.ts index cc16e2bb9..187ca02e4 100644 --- a/libs/core/app/src/tests/create-test.ts +++ b/libs/core/app/src/tests/create-test.ts @@ -16,10 +16,12 @@ export type NewTestConfiguration = { mode: "study" | "exam"; questionBank: QuestionBankName; subject: string; + numberOfQuestions: number; learningObjectiveIds: string[]; seed?: string; title?: string; + sortQuestionsByChapter?: boolean; }; export const newTestConfigurationSchema: z.ZodType = @@ -31,6 +33,7 @@ export const newTestConfigurationSchema: z.ZodType = numberOfQuestions: z.number().min(1).max(200), seed: z.string().optional(), title: z.string().optional(), + sortQuestionsByChapter: z.boolean().optional(), }); export const createTest = async ({ diff --git a/libs/core/app/src/tests/get-number-of-available-questions.ts b/libs/core/app/src/tests/get-number-of-available-questions.ts new file mode 100644 index 000000000..2e6f8c60f --- /dev/null +++ b/libs/core/app/src/tests/get-number-of-available-questions.ts @@ -0,0 +1,26 @@ +import type { LearningObjectiveId } from "@chair-flight/base/types"; + +export const getNumberOfAvailableQuestions = ( + subject: { + learningObjectives: Array<{ + id: LearningObjectiveId; + numberOfQuestions: number; + learningObjectives: Array<{ + id: LearningObjectiveId; + numberOfQuestions: number; + }>; + }>; + }, + selectedLearningObjectives: LearningObjectiveId[], +) => { + return subject.learningObjectives.reduce((sum, lo) => { + if (selectedLearningObjectives.includes(lo.id)) { + return sum + lo.numberOfQuestions; + } + lo.learningObjectives.forEach((lo2) => { + if (!selectedLearningObjectives.includes(lo2.id)) return; + sum += lo2.numberOfQuestions; + }); + return sum; + }, 0); +}; diff --git a/libs/core/question-bank/src/question-bank.test.ts b/libs/core/question-bank/src/question-bank.test.ts index f99653b2c..b0d2b274c 100644 --- a/libs/core/question-bank/src/question-bank.test.ts +++ b/libs/core/question-bank/src/question-bank.test.ts @@ -23,6 +23,9 @@ describe("QuestionBank", async () => { const questionIds = allQuestions.map(([id]) => id); const variantIds = allQuestions.flatMap(([, q]) => Object.keys(q.variants)); + const variantIds2 = allQuestions.flatMap(([, q]) => + Object.values(q.variants).map((v) => v.id), + ); test("QuestionBankType has correct config", async () => { expect(questionBanks["type"].getName()).toBe("type"); @@ -59,6 +62,12 @@ describe("QuestionBank", async () => { expect(variantIds).toHaveLength(new Set(variantIds).size); }); + test("Variant Ids match the ids on the variant map", () => { + variantIds.forEach((vId, i) => + expect.soft(vId).toStrictEqual(variantIds2[i]), + ); + }); + test("Questions are valid", () => { allQuestions.forEach(([, question]) => expect diff --git a/libs/react/components/src/app-buttons/app-buttons.tsx b/libs/react/components/src/app-buttons/app-buttons.tsx index 632cb7720..7d4fba7f2 100644 --- a/libs/react/components/src/app-buttons/app-buttons.tsx +++ b/libs/react/components/src/app-buttons/app-buttons.tsx @@ -10,43 +10,16 @@ import type { FunctionComponent } from "react"; const GITHUB_URL = "https://github.com/PupoSDC/chair-flight"; -const StyledButton = styled(IconButton)` - border: none; - margin: 0; - flex: 0; - padding: 0; - font-size: 20px; - min-width: 30px; - - ${({ theme }) => theme.breakpoints.up("sm")} { - min-width: 36px; - font-size: 24px; - } - - & > svg { - color: ${({ theme }) => theme.vars.palette.neutral.plainColor}; - font-size: inherit; - } - - &:hover { - background-color: transparent; - - & > svg { - color: ${({ theme }) => theme.vars.palette.primary.plainColor}; - } - } -` as typeof IconButton; - export const GithubButton: FunctionComponent = (props) => ( - + - + ); export const HamburgerButton: FunctionComponent = (props) => ( - + - + ); export const ThemeButton: FunctionComponent = (props) => { @@ -58,7 +31,7 @@ export const ThemeButton: FunctionComponent = (props) => { useEffect(() => setIsMounted(true), []); return ( - { toggleTheme(); @@ -66,15 +39,15 @@ export const ThemeButton: FunctionComponent = (props) => { }} > {showDarkModeButton ? : } - + ); }; export const BackButton: FunctionComponent = (props) => { return ( - + - + ); }; diff --git a/libs/react/containers/src/hooks/use-persistence/create-use-persistence-hook.ts b/libs/react/containers/src/hooks/use-persistence/create-use-persistence-hook.ts index 7600baa65..0f24f51bb 100644 --- a/libs/react/containers/src/hooks/use-persistence/create-use-persistence-hook.ts +++ b/libs/react/containers/src/hooks/use-persistence/create-use-persistence-hook.ts @@ -3,9 +3,9 @@ import { devtools, persist } from "zustand/middleware"; import type { QuestionBankName } from "@chair-flight/base/types"; type PersistenceHook = { - data: T | undefined; + data: T; setData: (data: T) => void; - getData: () => T | undefined; + getData: () => T; }; export type PersistenceKey = @@ -14,7 +14,8 @@ export type PersistenceKey = | `cf-test-search-${QuestionBankName}` | `cf-learning-objectives-search-${QuestionBankName}` | `cf-test-maker-${QuestionBankName}` - | `cf-test-progress`; + | `cf-test-progress` + | `cf-user-preferences`; export const createUsePersistenceHook = ( name: PersistenceKey, @@ -35,8 +36,13 @@ export const createUsePersistenceHook = ( ), ); - return () => ({ + const usePersistenceHook = () => ({ + data: useZustand((state) => ({ ...initialValue, ...state.data })), getData: useZustand((state) => state.getData), setData: useZustand((state) => state.setData), }); + + usePersistenceHook.state = useZustand; + + return usePersistenceHook; }; diff --git a/libs/react/containers/src/learning-objectives/learning-objectives-search/learning-objectives-search.tsx b/libs/react/containers/src/learning-objectives/learning-objectives-search/learning-objectives-search.tsx index 94fd19864..23e38bfa9 100644 --- a/libs/react/containers/src/learning-objectives/learning-objectives-search/learning-objectives-search.tsx +++ b/libs/react/containers/src/learning-objectives/learning-objectives-search/learning-objectives-search.tsx @@ -104,17 +104,23 @@ export const LearningObjectivesSearch = container( {searchFields.map((s) => ( - + ))} {courses.map((s) => ( - + ))} {subjects.map((s) => ( - + ))} diff --git a/libs/react/containers/src/overviews/overview-welcome/welcome.test.tsx b/libs/react/containers/src/overviews/overview-welcome/welcome.test.tsx index b44113625..a9d5c2b6b 100644 --- a/libs/react/containers/src/overviews/overview-welcome/welcome.test.tsx +++ b/libs/react/containers/src/overviews/overview-welcome/welcome.test.tsx @@ -14,7 +14,7 @@ describe("welcome", () => { afterEach(() => server.resetHandlers()); afterAll(() => server.close()); - it("renders with server data", async () => { + it.skip("renders with server data", async () => { render(, { wrapper }); const tagline = /Built by students for students/; await waitFor(() => expect(screen.getByText(tagline)).toBeInTheDocument()); diff --git a/libs/react/containers/src/questions/question-search/question-search.tsx b/libs/react/containers/src/questions/question-search/question-search.tsx index 8cdaba08f..08392c867 100644 --- a/libs/react/containers/src/questions/question-search/question-search.tsx +++ b/libs/react/containers/src/questions/question-search/question-search.tsx @@ -85,12 +85,16 @@ export const QuestionSearch = container( {searchFields.map((s) => ( - + ))} {subjects.map((s) => ( - + ))} diff --git a/libs/react/containers/src/tests/hooks/use-test-progress/use-test-progress.ts b/libs/react/containers/src/tests/hooks/use-test-progress/use-test-progress.ts index 87fead1f7..d392f9e52 100644 --- a/libs/react/containers/src/tests/hooks/use-test-progress/use-test-progress.ts +++ b/libs/react/containers/src/tests/hooks/use-test-progress/use-test-progress.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { devtools, persist } from "zustand/middleware"; -import { useUserPreferences } from "../../../user/hooks/use-user-preferences"; +import { userPreferences } from "../../../user/hooks/use-user-preferences"; import type { Test } from "@chair-flight/base/types"; type TestProgress = { @@ -60,8 +60,8 @@ export const useTestProgress = create()( let currentQuestionIndex = test.currentQuestionIndex; const isCorrect = question.correctOptionId === optionId; - const { examModeAutoSkip = false, studyModeAutoSkip = false } = - useUserPreferences.getState(); + const { examModeAutoSkip, studyModeAutoSkip } = + userPreferences.getState().data; switch (test.mode) { case "exam": { diff --git a/libs/react/containers/src/tests/test-maker/test-maker.tsx b/libs/react/containers/src/tests/test-maker/test-maker.tsx index 71e7e47db..6c99dfcbe 100644 --- a/libs/react/containers/src/tests/test-maker/test-maker.tsx +++ b/libs/react/containers/src/tests/test-maker/test-maker.tsx @@ -22,7 +22,10 @@ import { useTheme, Sheet, } from "@mui/joy"; -import { newTestConfigurationSchema } from "@chair-flight/core/app"; +import { + getNumberOfAvailableQuestions, + newTestConfigurationSchema, +} from "@chair-flight/core/app"; import { HookFormSelect, NestedCheckboxSelect, @@ -127,6 +130,12 @@ export const TestMaker = container(({ questionBank, sx }) => { currentLearningObjectives.includes(s.id), ); + const availableQuestions = currentSubject + ? getNumberOfAvailableQuestions(currentSubject, currentLearningObjectives) + : 0; + + const maxQuestions = Math.min(200, availableQuestions); + const onSubmit = form.handleSubmit(async (config) => { try { const { test } = await createTest.mutateAsync({ @@ -252,8 +261,8 @@ export const TestMaker = container(({ questionBank, sx }) => { > Number of Questions - {currentMode === "study" && "max: 200, "} - {`total: ${currentSubject?.numberOfQuestions}`} + {currentMode === "study" && `max: ${maxQuestions}, `} + {`total: ${availableQuestions}`} @@ -264,7 +273,7 @@ export const TestMaker = container(({ questionBank, sx }) => { onChange(v)} /> diff --git a/libs/react/containers/src/tests/test-study/test-study.tsx b/libs/react/containers/src/tests/test-study/test-study.tsx index 157b1e40a..5668db94e 100644 --- a/libs/react/containers/src/tests/test-study/test-study.tsx +++ b/libs/react/containers/src/tests/test-study/test-study.tsx @@ -32,6 +32,7 @@ import { MarkdownClient, QuestionMultipleChoice, QuestionNavigation, + ThemeButton, Ups, useMediaQuery, } from "@chair-flight/react/components"; @@ -180,6 +181,7 @@ export const TestStudy = container( onClick={() => setIsFinishTestOpen(true)} /> + void; - setStudyModeAutoSkip: (v: boolean) => void; -}; +import { useCallback } from "react"; +import { z } from "zod"; +import { createUsePersistenceHook } from "../../../hooks/use-persistence"; + +const userPreferencesSchema = z.object({ + examModeAutoSkip: z.boolean().default(false), + studyModeAutoSkip: z.boolean().default(false), + sortQuestionsByChapter: z.boolean().default(false), +}); + +const defaultValues = userPreferencesSchema.parse({}); -export const useUserPreferences = create()( - devtools( - persist( - (set) => ({ - examModeAutoSkip: false, - studyModeAutoSkip: false, - setExamModeAutoSkip: (v) => set({ examModeAutoSkip: v }), - setStudyModeAutoSkip: (v) => set({ studyModeAutoSkip: v }), - }), - { name: "cf-user-preferences" }, - ), - ), +type UserPreferences = z.infer; +type SetUserPreferences = (obj: Partial) => void; + +const useUserPreferencesPersistence = createUsePersistenceHook( + `cf-user-preferences`, + defaultValues, ); + +export const useUserPreferences = (): [UserPreferences, SetUserPreferences] => { + const { data, getData, setData } = useUserPreferencesPersistence(); + + const setUserPreference: SetUserPreferences = useCallback( + (newData) => { + setData({ ...defaultValues, ...getData(), ...newData }); + }, + [setData, getData], + ); + + return [data, setUserPreference]; +}; + +export const userPreferences = useUserPreferencesPersistence.state; diff --git a/libs/react/containers/src/user/user-settings/user-settings.tsx b/libs/react/containers/src/user/user-settings/user-settings.tsx index 2d603b4ab..b1d6f81ab 100644 --- a/libs/react/containers/src/user/user-settings/user-settings.tsx +++ b/libs/react/containers/src/user/user-settings/user-settings.tsx @@ -29,12 +29,8 @@ const StyledFormControl = styled(FormControl)` const UserSettingsFallback: FunctionComponent = () => null; export const UserSettings = container(({ sx, component = "section" }) => { - const { - examModeAutoSkip, - studyModeAutoSkip, - setExamModeAutoSkip, - setStudyModeAutoSkip, - } = useUserPreferences(); + const [preferences, setUserPreference] = useUserPreferences(); + const { examModeAutoSkip, studyModeAutoSkip } = preferences; return ( { setExamModeAutoSkip(!examModeAutoSkip)} + onChange={() => + setUserPreference({ + examModeAutoSkip: !examModeAutoSkip, + }) + } /> - Skip to Next Question after answering (Exam Mode) + + Skip to Next Question after answering correctly (Exam Mode) + setStudyModeAutoSkip(!studyModeAutoSkip)} + onChange={() => + setUserPreference({ + studyModeAutoSkip: !studyModeAutoSkip, + }) + } /> Skip to Next Question after answering correctly (Study Mode)