-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: boilerplate first components of new question editor (#135)
- Loading branch information
Showing
31 changed files
with
1,086 additions
and
6 deletions.
There are no files selected for viewing
167 changes: 167 additions & 0 deletions
167
apps/next-app/pages/modules/[questionBank]/questions/editor/[questionId].page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
47 changes: 47 additions & 0 deletions
47
apps/next-app/pages/modules/[questionBank]/questions/editor/index.page.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
9 changes: 9 additions & 0 deletions
9
libs/react/containers/src/questions/components/vertical-divider.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
42
libs/react/containers/src/questions/hooks/use-question-editor.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 }; | ||
}; |
1 change: 1 addition & 0 deletions
1
libs/react/containers/src/questions/question-editor-annexes/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { QuestionEditorAnnexes } from "./question-editor-annexes"; |
Oops, something went wrong.