Merge branch 'delete-modal' of penahub.gitlab.yandexcloud.net:frontend/squiz into delete-modal

This commit is contained in:
IlyaDoronin 2023-12-20 17:18:03 +03:00
commit 2826a7b6f2
5 changed files with 368 additions and 219 deletions

@ -11,8 +11,23 @@ import OptionsPict from "@icons/questionsPage/options_pict";
import Page from "@icons/questionsPage/page"; import Page from "@icons/questionsPage/page";
import RatingIcon from "@icons/questionsPage/rating"; import RatingIcon from "@icons/questionsPage/rating";
import Slider from "@icons/questionsPage/slider"; import Slider from "@icons/questionsPage/slider";
import { Box, FormControlLabel, IconButton, InputAdornment, Paper, useMediaQuery, useTheme } from "@mui/material"; import {
import { toggleExpandQuestion, updateQuestion, updateUntypedQuestion } from "@root/questions/actions"; Box, Checkbox,
FormControl,
FormControlLabel,
IconButton,
InputAdornment,
Paper, TextField,
useMediaQuery,
useTheme
} from "@mui/material";
import {
copyQuestion,
deleteQuestion, deleteQuestionWithTimeout,
toggleExpandQuestion,
updateQuestion,
updateUntypedQuestion
} from "@root/questions/actions";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
@ -51,150 +66,247 @@ export default function QuestionsPageCard({ question, questionIndex, draggablePr
}); });
}, 200); }, 200);
return ( console.log(question)
<> return (
<Paper <>
sx={{ <Paper
overflow: "hidden", sx={{
maxWidth: "796px", overflow: "hidden",
width: "100%", maxWidth: "796px",
backgroundColor: "white", width: "100%",
border: "none", backgroundColor: question.expanded ? "white" : "#EEE4FC",
boxShadow: "none", border: question.expanded ? "none" : "1px solid #9A9AAF",
paddingBottom: "20px", boxShadow: "none",
borderRadius: "0", paddingBottom: "20px",
borderTopLeftRadius: "12px", borderRadius: "0",
borderTopRightRadius: "12px", borderTopLeftRadius: "12px",
}} borderTopRightRadius: "12px",
> }}
<Box
sx={{
display: "flex",
p: 0,
flexDirection: "column",
}}
>
<Box
sx={{
display: "flex",
alignItems: "center",
margin: "20px",
gap: "18px",
flexDirection: isMobile ? "column-reverse" : null,
}}
>
<CustomTextField
placeholder={`Заголовок ${questionIndex + 1} вопроса`}
text={question.title}
onChange={({ target }) => setTitle(target.value)}
sx={{ width: "100%" }}
InputProps={{
startAdornment: (
<Box>
<InputAdornment
ref={anchorRef}
position="start"
sx={{ cursor: "pointer" }}
onClick={() => setOpen((isOpened) => !isOpened)}
>
{IconAndrom(question.type)}
</InputAdornment>
<ChooseAnswerModal
open={open}
onClose={() => setOpen(false)}
anchorRef={anchorRef}
question={question}
questionType={question.type}
/>
</Box>
),
}}
/>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: isMobile ? "100%" : "auto",
position: "relative",
}}
> >
<Box
sx={{
flexDirection: isMobile ? "row-reverse" : null,
display: "flex",
alignItems: "center",
gap: "4px",
}}
>
<IconButton
sx={{ padding: "0", margin: "5px" }}
disableRipple
data-cy="expand-question"
onClick={() => toggleExpandQuestion(question.id)}
>
{question.expanded ? (
<ArrowDownIcon
style={{
width: "18px",
color: "#4D4D4D",
}}
/>
) : (
<ExpandLessIcon
sx={{
boxSizing: "border-box",
fill: theme.palette.brightPurple.main,
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
}}
/>
)}
</IconButton>
<Box <Box
style={{ sx={{
display: "flex", display: "flex",
alignItems: "center", p: 0,
justifyContent: "center", flexDirection: "column",
height: "30px", }}
width: "30px",
marginLeft: "3px",
borderRadius: "50%",
fontSize: "16px",
color: question.expanded ? theme.palette.brightPurple.main : "#FFF",
background: question.expanded ? "#EEE4FC" : theme.palette.brightPurple.main,
}}
> >
{questionIndex + 1} <Box
sx={{
display: "flex",
alignItems: "center",
margin: "20px",
gap: "18px",
flexDirection: isMobile ? "column-reverse" : null,
}}
>
<FormControl
variant="standard"
sx={{
p: 0,
maxWidth: isTablet ? "549px" : "640px",
width: "100%",
marginRight: isMobile ? "0px" : "16.1px",
}}
>
<TextField
placeholder={`Заголовок ${questionIndex + 1} вопроса`}
value={question.title}
onChange={({target}) => setTitle(target.value)}
sx={{
width: "100%",
margin: isMobile ? "10px 0" : 0,
"& .MuiInputBase-root": {
color: "#000000",
backgroundColor: question.expanded
? theme.palette.background.default
: "transparent",
height: "48px",
borderRadius: "10px",
".MuiOutlinedInput-notchedOutline": {
borderWidth: "1px !important",
border: !question.expanded ? "none" : null,
},
"& .MuiInputBase-input::placeholder": {
color: "#4D4D4D",
opacity: 0.8,
},
},
}}
InputProps={{
startAdornment: (
<Box>
<InputAdornment
ref={anchorRef}
position="start"
sx={{cursor: "pointer"}}
onClick={() => setOpen((isOpened) => !isOpened)}
>
{IconAndrom(question.type)}
</InputAdornment>
<ChooseAnswerModal
open={open}
onClose={() => setOpen(false)}
anchorRef={anchorRef}
question={question}
questionType={question.type}
/>
</Box>
),
}}
/>
</FormControl>
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
width: isMobile ? "100%" : "auto",
position: "relative",
}}
>
<Box
sx={{
flexDirection: isMobile ? "row-reverse" : null,
display: "flex",
alignItems: "center",
gap: "4px",
}}
>
<IconButton
sx={{padding: "0", margin: "5px"}}
disableRipple
data-cy="expand-question"
onClick={() => toggleExpandQuestion(question.id)}
>
{question.expanded ? (
<ArrowDownIcon
style={{
width: "18px",
color: "#4D4D4D",
}}
/>
) : (
<ExpandLessIcon
sx={{
boxSizing: "border-box",
fill: theme.palette.brightPurple.main,
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
}}
/>
)}
</IconButton>
{question.expanded ? (
<></>
) : (
<Box
sx={{
display: "flex",
height: "30px",
borderRight: "solid 1px #4D4D4D",
}}
>
<FormControlLabel
control={
<Checkbox
icon={
<HideIcon
style={{
boxSizing: "border-box",
color: "#7E2AEA",
background: "#FFF",
borderRadius: "6px",
height: "30px",
width: "30px",
padding: "3px",
}}
/>
}
checkedIcon={<CrossedEyeIcon />}
/>
}
label={""}
sx={{
color: theme.palette.grey2.main,
ml: "-9px",
mr: 0,
userSelect: "none",
}}
/>
<IconButton
sx={{ padding: "0" }}
onClick={() => copyQuestion(question.id, question.quizId)}
>
<CopyIcon
style={{ color: theme.palette.brightPurple.main }}
/>
</IconButton>
<IconButton
sx={{
cursor: "pointer",
borderRadius: "6px",
padding: "0",
margin: "0 5px 0 10px",
}}
onClick={() => {
deleteQuestionWithTimeout(question.id, deleteQuestion(question.id));
}}
>
<DeleteIcon
style={{ color: theme.palette.brightPurple.main }}
/>
</IconButton>
</Box>
)}
<Box
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "30px",
width: "30px",
marginLeft: "3px",
borderRadius: "50%",
fontSize: "16px",
color: question.expanded ? theme.palette.brightPurple.main : "#FFF",
background: question.expanded ? "#EEE4FC" : theme.palette.brightPurple.main,
}}
>
{questionIndex + 1}
</Box>
</Box>
<IconButton
disableRipple
sx={{
padding: isMobile ? "0" : "0 5px",
right: isMobile ? "0" : null,
bottom: isMobile ? "0" : null,
}}
{...draggableProps}
>
<PointsIcon style={{color: "#4D4D4D", fontSize: "30px"}}/>
</IconButton>
</Box>
</Box>
{question.expanded && (
<>
{question.type === null ? (
<FormTypeQuestions question={question}/>
) : (
<SwitchQuestionsPage question={question}/>
)}
</>
)}
</Box> </Box>
</Box> </Paper>
</>
<IconButton );
disableRipple
sx={{
padding: isMobile ? "0" : "0 5px",
right: isMobile ? "0" : null,
bottom: isMobile ? "0" : null,
}}
{...draggableProps}
>
<PointsIcon style={{ color: "#4D4D4D", fontSize: "30px" }} />
</IconButton>
</Box>
</Box>
{question.type === null ? (
<FormTypeQuestions question={question} />
) : (
<SwitchQuestionsPage question={question} />
)}
</Box>
</Paper>
</>
);
} }
const IconAndrom = (questionType: QuestionType | null) => { const IconAndrom = (questionType: QuestionType | null) => {

@ -1,4 +1,4 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
import ButtonsOptions from "../ButtonsOptions"; import ButtonsOptions from "../ButtonsOptions";
import CustomNumberField from "@ui_kit/CustomNumberField"; import CustomNumberField from "@ui_kit/CustomNumberField";
@ -16,6 +16,38 @@ export default function SliderOptions({ question }: Props) {
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const [switchState, setSwitchState] = useState("setting"); const [switchState, setSwitchState] = useState("setting");
const [stepError, setStepError] = useState(""); const [stepError, setStepError] = useState("");
const [startError, setStartError] = useState<boolean>(false);
const [minError, setMinError] = useState<boolean>(false);
const [maxError, setMaxError] = useState<boolean>(false);
useEffect(() => {
const min = Number(question.content.range.split("—")[0]);
const max = Number(question.content.range.split("—")[1]);
const start = Number(question.content.start);
if (start < min || start > max) {
setStartError(true);
}
if (start >= min && start <= max) {
setStartError(false);
}
}, [question.content.range, question.content.start]);
useEffect(() => {
const min = Number(question.content.range.split("—")[0]);
const max = Number(question.content.range.split("—")[1]);
const step = Number(question.content.step);
const range = max - min;
if (range % step) {
setStepError(
`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`
);
} else {
setStepError("");
}
}, [question]);
const SSHC = (data: string) => { const SSHC = (data: string) => {
setSwitchState(data); setSwitchState(data);
@ -44,42 +76,43 @@ export default function SliderOptions({ question }: Props) {
marginRight: isMobile ? "10px" : "0px", marginRight: isMobile ? "10px" : "0px",
}} }}
> >
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D" }}> <Typography
sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D" }}
>
Выбор значения из диапазона Выбор значения из диапазона
</Typography> </Typography>
<Box sx={{ width: "100%", display: "flex", alignItems: "center", gap: isMobile ? "9px" : "20px" }}> <Box
sx={{
width: "100%",
display: "flex",
alignItems: "center",
gap: isMobile ? "9px" : "20px",
}}
>
<CustomNumberField <CustomNumberField
sx={{ maxWidth: "310px", width: "100%" }} sx={{ maxWidth: "310px", width: "100%" }}
placeholder={"0"} placeholder={"0"}
min={0} min={0}
max={99999999999} max={99999999999}
value={question.content.range.split("—")[0]} value={question.content.range.split("—")[0]}
emptyError={minError}
onChange={({ target }) => { onChange={({ target }) => {
updateQuestion(question.id, (question) => {
if (question.type !== "number") return;
question.content.range = `${target.value}${question.content.range.split("—")[1]}`;
});
}}
onBlur={({ target }) => {
const start = question.content.start;
const min = Number(target.value); const min = Number(target.value);
const max = Number(question.content.range.split("—")[1]); const max = Number(question.content.range.split("—")[1]);
updateQuestion(question.id, (question) => {
if (question.type !== "number") return;
question.content.range = `${target.value}${
question.content.range.split("—")[1]
}`;
});
if (min >= max) { if (min >= max) {
updateQuestion(question.id, (question) => { setMinError(true);
if (question.type !== "number") return; } else {
setMinError(false);
question.content.range = `${max - 1 >= 0 ? max - 1 : 0}${question.content.range.split("—")[1]}`; setMaxError(false);
});
}
if (start < min) {
updateQuestion(question.id, (question) => {
if (question.type !== "number") return;
question.content.start = min;
});
} }
}} }}
/> />
@ -90,35 +123,30 @@ export default function SliderOptions({ question }: Props) {
min={0} min={0}
max={100000000000} max={100000000000}
value={question.content.range.split("—")[1]} value={question.content.range.split("—")[1]}
emptyError={maxError}
onChange={({ target }) => { onChange={({ target }) => {
const min = Number(question.content.range.split("—")[0]);
const max = Number(target.value);
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "number") return; if (question.type !== "number") return;
question.content.range = `${question.content.range.split("—")[0]}${target.value}`; question.content.range = `${
question.content.range.split("—")[0]
}${target.value}`;
}); });
if (max <= min) {
setMaxError(true);
} else {
setMaxError(false);
setMinError(false);
}
}} }}
onBlur={({ target }) => { onBlur={({ target }) => {
const start = question.content.start;
const step = question.content.step; const step = question.content.step;
const min = Number(question.content.range.split("—")[0]); const min = Number(question.content.range.split("—")[0]);
const max = Number(target.value); const max = Number(target.value);
const range = max - min;
if (max <= min) {
updateQuestion(question.id, (question) => {
if (question.type !== "number") return;
question.content.range = `${min}${min + 1 >= 100 ? 100 : min + 1}`;
});
}
if (start > max) {
updateQuestion(question.id, (question) => {
if (question.type !== "number") return;
question.content.start = max;
});
}
if (step > max) { if (step > max) {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
@ -126,12 +154,6 @@ export default function SliderOptions({ question }: Props) {
question.content.step = min; question.content.step = min;
}); });
if (range % step) {
setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`);
} else {
setStepError("");
}
} }
}} }}
/> />
@ -147,15 +169,21 @@ export default function SliderOptions({ question }: Props) {
}} }}
> >
<Box sx={{ width: "100%" }}> <Box sx={{ width: "100%" }}>
<Typography sx={{ fontWeight: "500", fontSize: "18px", color: "#4D4D4D", mb: isMobile ? "10px" : "14px" }}> <Typography
sx={{
fontWeight: "500",
fontSize: "18px",
color: "#4D4D4D",
mb: isMobile ? "10px" : "14px",
}}
>
Начальное значение Начальное значение
</Typography> </Typography>
<CustomNumberField <CustomNumberField
sx={{ maxWidth: "310px", width: "100%" }} sx={{ maxWidth: "310px", width: "100%" }}
placeholder={"50"} placeholder={"50"}
min={Number(question.content.range.split("—")[0])}
max={Number(question.content.range.split("—")[1])}
value={String(question.content.start)} value={String(question.content.start)}
emptyError={startError}
onChange={({ target }) => { onChange={({ target }) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "number") return; if (question.type !== "number") return;
@ -178,8 +206,8 @@ export default function SliderOptions({ question }: Props) {
</Typography> </Typography>
<CustomNumberField <CustomNumberField
sx={{ maxWidth: "310px", width: "100%" }} sx={{ maxWidth: "310px", width: "100%" }}
min={0} min={Number(question.content.range.split("—")[0])}
max={100} max={Number(question.content.range.split("—")[1])}
placeholder={"1"} placeholder={"1"}
error={stepError} error={stepError}
value={String(question.content.step)} value={String(question.content.step)}
@ -191,9 +219,7 @@ export default function SliderOptions({ question }: Props) {
}); });
}} }}
onBlur={({ target }) => { onBlur={({ target }) => {
const min = Number(question.content.range.split("—")[0]);
const max = Number(question.content.range.split("—")[1]); const max = Number(question.content.range.split("—")[1]);
const range = max - min;
const step = Number(target.value); const step = Number(target.value);
if (step > max) { if (step > max) {
@ -203,18 +229,16 @@ export default function SliderOptions({ question }: Props) {
question.content.step = max; question.content.step = max;
}); });
} }
if (range % step) {
setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`);
} else {
setStepError("");
}
}} }}
/> />
</Box> </Box>
</Box> </Box>
</Box> </Box>
<ButtonsOptions switchState={switchState} SSHC={SSHC} question={question} /> <ButtonsOptions
switchState={switchState}
SSHC={SSHC}
question={question}
/>
<SwitchSlider switchState={switchState} question={question} /> <SwitchSlider switchState={switchState} question={question} />
</> </>
); );

@ -18,21 +18,29 @@ export const Number = ({ currentQuestion }: NumberProps) => {
const [maxRange, setMaxRange] = useState<string>("100000000000"); const [maxRange, setMaxRange] = useState<string>("100000000000");
const theme = useTheme(); const theme = useTheme();
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const updateMinRangeDebounced = useDebouncedCallback((value, crowded = false) => { const updateMinRangeDebounced = useDebouncedCallback(
if (crowded) { (value, crowded = false) => {
setMinRange(maxRange); if (crowded) {
} setMinRange(maxRange);
}
updateAnswer(currentQuestion.content.id, value); updateAnswer(currentQuestion.content.id, value);
}, 1000); },
const updateMaxRangeDebounced = useDebouncedCallback((value, crowded = false) => { 1000
if (crowded) { );
setMaxRange(minRange); const updateMaxRangeDebounced = useDebouncedCallback(
} (value, crowded = false) => {
if (crowded) {
setMaxRange(minRange);
}
updateAnswer(currentQuestion.content.id, value); updateAnswer(currentQuestion.content.id, value);
}, 1000); },
const answer = answers.find(({ questionId }) => questionId === currentQuestion.content.id)?.answer as string; 1000
);
const answer = answers.find(
({ questionId }) => questionId === currentQuestion.content.id
)?.answer as string;
const min = window.Number(currentQuestion.content.range.split("—")[0]); const min = window.Number(currentQuestion.content.range.split("—")[0]);
const max = window.Number(currentQuestion.content.range.split("—")[1]); const max = window.Number(currentQuestion.content.range.split("—")[1]);

@ -9,6 +9,7 @@ interface CustomNumberFieldProps {
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void; onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onBlur?: (event: FocusEvent<HTMLInputElement>) => void; onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
error?: string; error?: string;
emptyError?: boolean;
value: string; value: string;
sx?: SxProps<Theme>; sx?: SxProps<Theme>;
min?: number; min?: number;
@ -20,6 +21,7 @@ export default function CustomNumberField({
value, value,
sx, sx,
error, error,
emptyError,
onChange, onChange,
onKeyDown, onKeyDown,
onBlur, onBlur,
@ -57,6 +59,7 @@ export default function CustomNumberField({
placeholder={placeholder} placeholder={placeholder}
sx={sx} sx={sx}
error={error} error={error}
emptyError={emptyError}
onChange={onInputChange} onChange={onInputChange}
onKeyDown={onKeyDown} onKeyDown={onKeyDown}
onBlur={onInputBlur} onBlur={onInputBlur}

@ -7,6 +7,7 @@ interface CustomTextFieldProps {
placeholder: string; placeholder: string;
value?: string; value?: string;
error?: string; error?: string;
emptyError?: boolean;
onChange?: (event: ChangeEvent<HTMLInputElement>) => void; onChange?: (event: ChangeEvent<HTMLInputElement>) => void;
onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void; onKeyDown?: (event: KeyboardEvent<HTMLInputElement>) => void;
onBlur?: (event: FocusEvent<HTMLInputElement>) => void; onBlur?: (event: FocusEvent<HTMLInputElement>) => void;
@ -25,6 +26,7 @@ export default function CustomTextField({
text, text,
sx, sx,
error, error,
emptyError,
InputProps, InputProps,
maxLength = 200, maxLength = 200,
}: CustomTextFieldProps) { }: CustomTextFieldProps) {
@ -62,7 +64,7 @@ export default function CustomTextField({
value={inputValue} value={inputValue}
placeholder={placeholder} placeholder={placeholder}
onChange={handleInputChange} onChange={handleInputChange}
error={!!error} error={!!error || emptyError}
label={error} label={error}
onFocus={handleInputFocus} onFocus={handleInputFocus}
onBlur={handleInputBlur} onBlur={handleInputBlur}