Skip to content

Commit

Permalink
feat: boilerplate first components of new question editor (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
PupoSDC authored Feb 6, 2024
1 parent f228efc commit 98317d7
Show file tree
Hide file tree
Showing 31 changed files with 1,086 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { useRouter } from "next/router";
import {
Box,
Tab,
TabList,
TabPanel,
Tabs,
tabClasses,
tabPanelClasses,
} from "@mui/joy";
import { AppHead } from "@chair-flight/react/components";
import {
LayoutModule,
QuestionEditorAnnexes,
QuestionEditorExplanation,
QuestionEditorLearningObjectives,
QuestionEditorRelatedQuestions,
QuestionEditorVariant,
} from "@chair-flight/react/containers";
import { ssrHandler } from "@chair-flight/trpc/server";
import type { QuestionBankName } from "@chair-flight/core/question-bank";
import type { Breadcrumbs } from "@chair-flight/react/containers";
import type { NextPage } from "next";

type QueryParams = {
tab?: string;
};

type PageParams = QueryParams & {
questionBank: QuestionBankName;
questionId: string;
};

type PageProps = {
questionBank: QuestionBankName;
questionId: string;
tab: string;
};

const Page: NextPage<PageProps> = ({
questionBank,
questionId,
tab: initialTab,
}) => {
const router = useRouter();
const query = router.query as PageParams;
const tab = query.tab ?? initialTab;

const updateQuery = (query: QueryParams) => {
router.push(
{ ...router, query: { ...router.query, ...query } },
undefined,
{ shallow: true },
);
};

const crumbs = [
[questionBank.toUpperCase(), `/modules/${questionBank}`],
["Questions", `/modules/${questionBank}/questions`],
["Editor", `/modules/${questionBank}/questions/editor`],
questionId,
] as Breadcrumbs;

console.log({
tabClasses,
tabPanelClasses,
});

return (
<LayoutModule
questionBank={questionBank}
breadcrumbs={crumbs}
fixedHeight
noPadding
>
<AppHead
linkTitle={`Chair Flight [${questionId}]`}
linkDescription={""}
/>
<Tabs
value={tab}
onChange={(_, v) => updateQuery({ tab: v as string })}
sx={{
backgroundColor: "transparent",
display: "flex",
flexDirection: "column",
height: "100%",
}}
>
<TabList
sx={{
position: "fixed",
bgcolor: "background.surface",
width: "100%",
height: (theme) => `calc(${theme.spacing(5)} + 2px)`,

[`& .${tabClasses.selected}`]: {
color: "primary.plainColor",
},
}}
>
<Tab value={"question"}>Question</Tab>
<Tab value={"explanation"}>Explanation</Tab>
<Tab value={"los"}>Learning Objectives</Tab>
<Tab value={"relatedQs"}>Related Questions</Tab>
<Tab value={"annexes"}>Annexes</Tab>
</TabList>
<Box sx={{ height: (theme) => `calc(${theme.spacing(5)} + 2px)` }} />
<TabPanel value={"question"} sx={{ flex: 1, overflow: "hidden" }}>
<QuestionEditorVariant
noSsr
questionBank={questionBank}
questionId={questionId}
/>
</TabPanel>
<TabPanel value={"explanation"} sx={{ flex: 1, overflow: "hidden" }}>
<QuestionEditorExplanation
noSsr
questionBank={questionBank}
questionId={questionId}
/>
</TabPanel>
<TabPanel value={"los"} sx={{ flex: 1, overflow: "hidden" }}>
<QuestionEditorLearningObjectives
noSsr
questionBank={questionBank}
questionId={questionId}
/>
</TabPanel>
<TabPanel value={"relatedQs"} sx={{ flex: 1, overflow: "hidden" }}>
<QuestionEditorRelatedQuestions
noSsr
questionBank={questionBank}
questionId={questionId}
/>
</TabPanel>
<TabPanel value={"annexes"} sx={{ flex: 1, overflow: "hidden" }}>
<QuestionEditorAnnexes
noSsr
questionBank={questionBank}
questionId={questionId}
/>
</TabPanel>
</Tabs>
</LayoutModule>
);
};

export const getServerSideProps = ssrHandler<PageProps, PageParams>(
async ({ params, helper, context }) => {
const tab = (context.query?.["tab"] ?? "question") as string;
const allParams = { ...params, tab };

await Promise.all([
LayoutModule.getData({ params: allParams, helper }),
QuestionEditorVariant.getData({ params: allParams, helper }),
QuestionEditorExplanation.getData({ params: allParams, helper }),
QuestionEditorLearningObjectives.getData({ params: allParams, helper }),
QuestionEditorRelatedQuestions.getData({ params: allParams, helper }),
QuestionEditorAnnexes.getData({ params: allParams, helper }),
]);

return { props: allParams };
},
);

export default Page;
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import * as fs from "node:fs/promises";
import { AppHead } from "@chair-flight/react/components";
import { LayoutModule, QuestionManager } from "@chair-flight/react/containers";
import { staticHandler } from "@chair-flight/trpc/server";
import type { QuestionBankName } from "@chair-flight/core/question-bank";
import type { Breadcrumbs } from "@chair-flight/react/containers";
import type { GetStaticPaths, NextPage } from "next";

type PageProps = {
questionBank: QuestionBankName;
};

type PageParams = {
questionBank: QuestionBankName;
};

const Page: NextPage<PageProps> = ({ questionBank }) => {
const crumbs = [
[questionBank.toUpperCase(), `/modules/${questionBank}`],
["Questions", `/modules/${questionBank}/questions`],
"Editor",
] as Breadcrumbs;

return (
<LayoutModule fixedHeight questionBank={questionBank} breadcrumbs={crumbs}>
<AppHead />
<QuestionManager questionBank={questionBank} sx={{ height: "100%" }} />
</LayoutModule>
);
};

export const getStaticProps = staticHandler<PageProps, PageParams>(
async ({ params, helper }) => {
await LayoutModule.getData({ helper, params });
await QuestionManager.getData({ helper, params });
return { props: params };
},
fs,
);

export const getStaticPaths: GetStaticPaths<PageParams> = async () => {
const banks: QuestionBankName[] = ["type", "atpl"];
const paths = banks.map((questionBank) => ({ params: { questionBank } }));
return { fallback: false, paths };
};

export default Page;
4 changes: 2 additions & 2 deletions libs/core/search/src/entities/question-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export type QuestionSearchResult = {
};

export const questionSearchFilters = z.object({
subject: z.string(),
searchField: z.string(),
subject: z.string().default("all"),
searchField: z.string().default("all"),
});

export type QuestionSearchFilters = z.infer<typeof questionSearchFilters>;
Expand Down
7 changes: 7 additions & 0 deletions libs/core/search/src/entities/retrieve-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from "zod";
import { questionBankNameSchema } from "@chair-flight/core/question-bank";

export const retrieveParams = z.object({
questionBank: questionBankNameSchema,
ids: z.string().array(),
});
1 change: 1 addition & 0 deletions libs/core/search/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from "./entities/annex-search";
export * from "./entities/doc-search";
export * from "./entities/learning-objective-search";
export * from "./entities/question-search";
export * from "./entities/retrieve-params";
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type SearchFiltersProps = {
filters: ReactNode;
fallback: ReactNode;
activeFilters: number;
mobileBreakpoint?: "sm" | "md" | "lg";
mobileBreakpoint?: "sm" | "md" | "lg" | "xl";
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type PersistenceHook<T> = {
export type PersistenceKey =
| `cf-annex-search-${QuestionBankName}`
| `cf-question-search-${QuestionBankName}`
| `cf-question-editor-${QuestionBankName}`
| `cf-test-search-${QuestionBankName}`
| `cf-learning-objectives-search-${QuestionBankName}`
| `cf-docs-search-${QuestionBankName}`
Expand All @@ -30,14 +31,17 @@ export const createUsePersistenceHook = <T>(
(set, get) => ({
version,
data: initialValue,
setData: (data: T) =>
set({
setData: (data: T) => {
console.log("setData", data);
return set({
data: {
...initialValue,
...get().data,
...data,
},
}),
});
},

getData: () => ({
...initialValue,
...get().data,
Expand Down
6 changes: 6 additions & 0 deletions libs/react/containers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,15 @@ export * from "./learning-objectives/learning-objectives-search";
export * from "./overviews/overview-modules";
export * from "./overviews/overview-welcome";
export * from "./questions/question-explanation";
export * from "./questions/question-manager";
export * from "./questions/question-meta";
export * from "./questions/question-search";
export * from "./questions/question-stand-alone";
export * from "./questions/question-editor-annexes";
export * from "./questions/question-editor-explanation";
export * from "./questions/question-editor-learning-objectives";
export * from "./questions/question-editor-related-questions";
export * from "./questions/question-editor-variant";
export * from "./tests/test-exam";
export * from "./tests/test-maker";
export * from "./tests/test-review";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { default as RightArrow } from "@mui/icons-material/ChevronRightOutlined";
import { Divider } from "@mui/joy";
import type { FunctionComponent } from "react";

export const VerticalDivider: FunctionComponent = () => (
<Divider orientation="vertical">
<RightArrow size="lg" />
</Divider>
);
42 changes: 42 additions & 0 deletions libs/react/containers/src/questions/hooks/use-question-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { questionTemplateSchema } from "@chair-flight/core/question-bank";
import { createUsePersistenceHook } from "../../hooks/use-persistence";
import type { QuestionBankName } from "@chair-flight/core/question-bank";

const editorSchema = z.object({
deletedQuestions: z.record(questionTemplateSchema.or(z.null())).default({}),
editedQuestions: z.record(questionTemplateSchema.or(z.null())).default({}),
newQuestions: z.record(questionTemplateSchema.or(z.null())).default({}),
});

export type QuestionEditorState = z.infer<typeof editorSchema>;

const defaultValue = editorSchema.parse({});
const resolver = zodResolver(editorSchema);

const useQuestionEditorPersistence = {
atpl: createUsePersistenceHook("cf-question-editor-atpl", defaultValue),
type: createUsePersistenceHook("cf-question-editor-type", defaultValue),
prep: createUsePersistenceHook("cf-question-editor-prep", defaultValue),
};

export const useQuestionEditor = ({
questionBank,
}: {
questionBank: QuestionBankName;
}) => {
const { getData, setData } = useQuestionEditorPersistence[questionBank]();
const form = useForm({ resolver, defaultValues: getData() });

useEffect(() => {
const subscription = form.watch((value) => {
setData(value as QuestionEditorState);
});
return () => subscription.unsubscribe();
}, [form, setData]);

return { form };
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { QuestionEditorAnnexes } from "./question-editor-annexes";
Loading

0 comments on commit 98317d7

Please sign in to comment.