add question select for preview

This commit is contained in:
nflnkr 2024-04-11 17:11:43 +03:00
parent 75d313e0d7
commit a5051e8ebc
8 changed files with 190 additions and 97 deletions

@ -6,93 +6,53 @@ import { useQuizData } from "@contexts/QuizDataContext";
import Stepper from "@ui_kit/Stepper"; import Stepper from "@ui_kit/Stepper";
type FooterProps = { type FooterProps = {
stepNumber: number | null; stepNumber: number | null;
nextButton: ReactNode; nextButton: ReactNode;
prevButton: ReactNode; prevButton: ReactNode;
}; };
export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => { export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
const theme = useTheme(); const theme = useTheme();
const { questions, settings } = useQuizData(); const { questions, settings } = useQuizData();
const questionsAmount = questions.filter( const questionsAmount = questions.filter(
({ type }) => type !== "result" ({ type }) => type !== "result"
).length; ).length;
if (stepNumber === null) stepNumber = -1
console.log("stepNumber ", stepNumber)
return (
<Box
sx={{
position: "relative",
padding: "15px 0",
borderTop: `1px solid #9A9AAF80`,
height: "75px",
display: "flex",
background: settings.cfg.design
? "rgba(154,154,175, 0.2)"
: "transparent",
}}
>
<Box
sx={{
width: "100%",
maxWidth: "1410px",
padding: "10px",
margin: "0 auto",
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
{/*{mode[settings.cfg.theme] ? (*/}
{/* <NameplateLogoFQ style={{ fontSize: "34px", width:"200px", height:"auto" }} />*/}
{/*):(*/}
{/* <NameplateLogoFQDark style={{ fontSize: "34px", width:"200px", height:"auto" }} />*/}
{/*)}*/}
{
questions.every(({ content }) => content.rule.parentId !== "root") !== null // null when branching enabled
&&
stepNumber !== null && (
<Box sx={{ flexGrow: 1 }}>
<Typography sx={{ color: theme.palette.text.primary }}>
Вопрос {stepNumber} из {questionsAmount}
</Typography>
<Stepper activeStep={stepNumber} steps={questionsAmount} />
</Box>
)}
return (
<Box <Box
sx={{
display: "flex",
alignItems: "center",
gap: "10px",
marginRight: "auto",
// color: theme.palette.grey1.main,
}}
>
{/* <Typography>Шаг</Typography>
<Typography
sx={{ sx={{
display: "flex", position: "relative",
justifyContent: "center", padding: "15px 0",
alignItems: "center", borderTop: `1px solid #9A9AAF80`,
fontWeight: "bold", height: "75px",
borderRadius: "50%", display: "flex",
width: "30px", background: settings.cfg.design
height: "30px", ? "rgba(154,154,175, 0.2)"
color: "#FFF", : "transparent",
background: theme.palette.brightPurple.main,
}} }}
> >
{stepNumber} */} <Box
{/* </Typography> */} sx={{
{/* <Typography>Из</Typography> width: "100%",
<Typography sx={{ fontWeight: "bold" }}> maxWidth: "1410px",
{questions.length} padding: "10px",
</Typography> */} margin: "0 auto",
display: "flex",
alignItems: "center",
gap: "10px",
}}
>
{stepNumber !== null && (
<Box sx={{ flexGrow: 1 }}>
<Typography sx={{ color: theme.palette.text.primary }}>
Вопрос {stepNumber} из {questionsAmount}
</Typography>
<Stepper activeStep={stepNumber} steps={questionsAmount} />
</Box>
)}
{prevButton}
{nextButton}
</Box>
</Box> </Box>
{prevButton} );
{nextButton}
</Box>
</Box>
);
}; };

@ -21,14 +21,15 @@ import { NameplateLogoFQDark } from "@icons/NameplateLogoFQDark";
import { notReachable } from "@utils/notReachable"; import { notReachable } from "@utils/notReachable";
import { quizThemes } from "@utils/themes/Publication/themePublication"; import { quizThemes } from "@utils/themes/Publication/themePublication";
import type { ReactNode } from "react";
import { DESIGN_LIST } from "@/utils/designList"; import { DESIGN_LIST } from "@/utils/designList";
import type { ReactNode } from "react";
type Props = { type Props = {
currentQuestion: RealTypedQuizQuestion; currentQuestion: RealTypedQuizQuestion;
currentQuestionStepNumber: number | null; currentQuestionStepNumber: number | null;
nextButton: ReactNode; nextButton: ReactNode;
prevButton: ReactNode; prevButton: ReactNode;
questionSelect: ReactNode;
}; };
export const Question = ({ export const Question = ({
@ -36,6 +37,7 @@ export const Question = ({
currentQuestionStepNumber, currentQuestionStepNumber,
nextButton, nextButton,
prevButton, prevButton,
questionSelect,
}: Props) => { }: Props) => {
const theme = useTheme(); const theme = useTheme();
const { settings, show_badge } = useQuizData(); const { settings, show_badge } = useQuizData();
@ -118,6 +120,7 @@ export const Question = ({
)} )}
</Box> </Box>
</Box> </Box>
{questionSelect}
<Footer <Footer
stepNumber={currentQuestionStepNumber} stepNumber={currentQuestionStepNumber}
prevButton={prevButton} prevButton={prevButton}

@ -0,0 +1,112 @@
import { useQuizData } from "@/contexts/QuizDataContext";
import { AnyTypedQuizQuestion } from "@/model/questionTypes/shared";
import { Box, FormControl, MenuItem, Select as MuiSelect, useTheme } from "@mui/material";
interface Props {
selectedQuestion: AnyTypedQuizQuestion;
setQuestion: (questionIdF: string) => void;
}
export default function QuestionSelect({ selectedQuestion, setQuestion }: Props) {
const theme = useTheme();
const { questions, preview } = useQuizData();
if (!preview) return null;
return (
<Box sx={{
p: "20px",
display: "flex",
justifyContent: "center",
}}>
<FormControl
fullWidth
size="small"
sx={{
maxWidth: "500px",
minWidth: "200px",
height: "48px",
}}
className="cancel"
>
<MuiSelect
id="category-select"
variant="outlined"
value={selectedQuestion.id}
placeholder="Заголовок вопроса"
onChange={({ target }) => {
setQuestion(target.value);
}}
sx={{
height: "48px",
borderRadius: "8px",
"& .MuiOutlinedInput-notchedOutline": {
border: `1px solid ${theme.palette.primary.main} !important`,
},
"& .MuiSelect-icon": {
color: theme.palette.primary.main,
},
}}
MenuProps={{
PaperProps: {
sx: {
mt: "8px",
p: "4px",
borderRadius: "8px",
border: "1px solid #EEE4FC",
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
backgroundColor: theme.palette.background.default,
},
},
MenuListProps: {
sx: {
py: 0,
display: "flex",
flexDirection: "column",
gap: "8px",
"& .Mui-selected": {
backgroundColor: theme.palette.background.default,
color: theme.palette.primary.main,
},
},
},
}}
inputProps={{
sx: {
color: theme.palette.primary.main,
display: "block",
px: "9px",
gap: "20px",
width: "87%",
overflow: "hidden",
textOverflow: "ellipsis",
},
}}
>
{questions.filter((q) => q.type !== "result").map(
(question, index) => (
<MenuItem
key={question.id}
value={question.id}
sx={{
display: "flex",
alignItems: "center",
gap: "20px",
p: "4px",
borderRadius: "5px",
color: "#9A9AAF",
wordBreak: "break-word",
whiteSpace: "normal",
}}
>
{`${index + 1}. ${question.title}`}
</MenuItem>
),
)}
</MuiSelect>
</FormControl>
</Box>
);
}

@ -5,6 +5,7 @@ import { useQuizViewStore } from "@stores/quizView";
import { useQuestionFlowControl } from "@utils/hooks/useQuestionFlowControl"; import { useQuestionFlowControl } from "@utils/hooks/useQuestionFlowControl";
import { notReachable } from "@utils/notReachable"; import { notReachable } from "@utils/notReachable";
import { quizThemes } from "@utils/themes/Publication/themePublication"; import { quizThemes } from "@utils/themes/Publication/themePublication";
import "https://markknol.github.io/console-log-viewer/console-log-viewer.js";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { ReactElement, useEffect } from "react"; import { ReactElement, useEffect } from "react";
import { ContactForm } from "./ContactForm"; import { ContactForm } from "./ContactForm";
@ -13,7 +14,7 @@ import { ResultForm } from "./ResultForm";
import { StartPageViewPublication } from "./StartPageViewPublication"; import { StartPageViewPublication } from "./StartPageViewPublication";
import NextButton from "./tools/NextButton"; import NextButton from "./tools/NextButton";
import PrevButton from "./tools/PrevButton"; import PrevButton from "./tools/PrevButton";
import "https://markknol.github.io/console-log-viewer/console-log-viewer.js" import QuestionSelect from "./QuestionSelect";
export default function ViewPublicationPage() { export default function ViewPublicationPage() {
const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle } = useQuizData(); const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle } = useQuizData();
@ -27,6 +28,7 @@ export default function ViewPublicationPage() {
moveToPrevQuestion, moveToPrevQuestion,
moveToNextQuestion, moveToNextQuestion,
showResultAfterContactForm, showResultAfterContactForm,
setQuestion,
} = useQuestionFlowControl(); } = useQuestionFlowControl();
const isAnswer = answers.some(ans => ans.questionId === currentQuestion.id); const isAnswer = answers.some(ans => ans.questionId === currentQuestion.id);
@ -81,7 +83,14 @@ export default function ViewPublicationPage() {
} }
moveToNextQuestion(); moveToNextQuestion();
}} }}
/>} />
}
questionSelect={
<QuestionSelect
selectedQuestion={currentQuestion}
setQuestion={setQuestion}
/>
}
/> />
); );
break; break;

@ -1,22 +1,23 @@
import {Button, useTheme} from "@mui/material"; import { Button, useTheme } from "@mui/material";
import {useRootContainerSize} from "../../../contexts/RootContainerWidthContext"; import { useRootContainerSize } from "../../../contexts/RootContainerWidthContext";
import { quizThemes } from "@utils/themes/Publication/themePublication"; import { quizThemes } from "@utils/themes/Publication/themePublication";
import { useQuizData } from "@contexts/QuizDataContext"; import { useQuizData } from "@contexts/QuizDataContext";
interface Props{ interface Props {
isPreviousButtonEnabled: boolean, isPreviousButtonEnabled: boolean,
moveToPrevQuestion: () => void, moveToPrevQuestion: () => void,
} }
export default function PrevButton ({isPreviousButtonEnabled, moveToPrevQuestion}: Props) { export default function PrevButton({ isPreviousButtonEnabled, moveToPrevQuestion }: Props) {
const theme = useTheme(); const theme = useTheme();
const { settings } = useQuizData(); const { settings } = useQuizData();
const isMobileMini = useRootContainerSize() < 382; const isMobileMini = useRootContainerSize() < 382;
return( return (
<Button <Button
disabled={!isPreviousButtonEnabled} disabled={!isPreviousButtonEnabled}
variant="contained" variant="contained"
sx={{ sx={{
ml: "auto",
fontSize: "16px", fontSize: "16px",
padding: "10px 15px", padding: "10px 15px",
color: quizThemes[settings.cfg.theme].isLight ? theme.palette.primary.main : "#FFFFFF", color: quizThemes[settings.cfg.theme].isLight ? theme.palette.primary.main : "#FFFFFF",
@ -34,5 +35,5 @@ export default function PrevButton ({isPreviousButtonEnabled, moveToPrevQuestion
> >
{isMobileMini ? "←" : "← Назад"} {isMobileMini ? "←" : "← Назад"}
</Button> </Button>
) );
} }

@ -35,10 +35,10 @@ interface QuizViewActions {
export const QuizViewContext = createContext<ReturnType<typeof createQuizViewStore> | null>(null); export const QuizViewContext = createContext<ReturnType<typeof createQuizViewStore> | null>(null);
export function useQuizViewStore<U>(selector: (state: QuizViewStore & QuizViewActions) => U): U { export function useQuizViewStore<U>(selector: (state: QuizViewStore & QuizViewActions) => U): U {
const context = useContext(QuizViewContext); const store = useContext(QuizViewContext);
if (!context) throw new Error("QuizViewStore context is null"); if (!store) throw new Error("QuizViewStore context is null");
return useStore(context, selector); return useStore(store, selector);
} }
export const createQuizViewStore = () => createStore<QuizViewStore & QuizViewActions>()( export const createQuizViewStore = () => createStore<QuizViewStore & QuizViewActions>()(

@ -149,7 +149,7 @@ export function useQuestionFlowControl() {
settings.cfg.resultInfo.showResultForm === "after" settings.cfg.resultInfo.showResultForm === "after"
|| isResultQuestionEmpty(nextQuestion) || isResultQuestionEmpty(nextQuestion)
) setCurrentQuizStep("contactform"); ) setCurrentQuizStep("contactform");
}, [nextQuestion, settings.cfg.resultInfo.showResultForm]); }, [nextQuestion, setCurrentQuizStep, settings.cfg.resultInfo.showResultForm]);
const showResultAfterContactForm = useCallback(() => { const showResultAfterContactForm = useCallback(() => {
if (currentQuestion.type !== "result") throw new Error("Current question is not result"); if (currentQuestion.type !== "result") throw new Error("Current question is not result");
@ -159,7 +159,7 @@ export function useQuestionFlowControl() {
} }
setCurrentQuizStep("question"); setCurrentQuizStep("question");
}, [currentQuestion]); }, [currentQuestion, setCurrentQuizStep]);
const moveToPrevQuestion = useCallback(() => { const moveToPrevQuestion = useCallback(() => {
if (!prevQuestion) throw new Error("Previous question not found"); if (!prevQuestion) throw new Error("Previous question not found");
@ -175,6 +175,13 @@ export function useQuestionFlowControl() {
setCurrentQuestion(nextQuestion); setCurrentQuestion(nextQuestion);
}, [nextQuestion, showResult]); }, [nextQuestion, showResult]);
const setQuestion = useCallback((questionId: string) => {
const question = questions.find(q => q.id === questionId);
if (!question) return;
setCurrentQuestion(question);
}, [questions]);
const isPreviousButtonEnabled = Boolean(prevQuestion); const isPreviousButtonEnabled = Boolean(prevQuestion);
const isNextButtonEnabled = useMemo(() => { const isNextButtonEnabled = useMemo(() => {
@ -202,5 +209,6 @@ export function useQuestionFlowControl() {
moveToPrevQuestion, moveToPrevQuestion,
moveToNextQuestion, moveToNextQuestion,
showResultAfterContactForm, showResultAfterContactForm,
setQuestion,
}; };
} }

@ -1,6 +1,6 @@
{ {
"name": "@frontend/squzanswerer", "name": "@frontend/squzanswerer",
"version": "1.0.25", "version": "1.0.26",
"type": "module", "type": "module",
"main": "./dist-package/index.js", "main": "./dist-package/index.js",
"module": "./dist-package/index.js", "module": "./dist-package/index.js",