Merge branch 'dev' into refactor
This commit is contained in:
commit
1e3fc3ae26
2
.gitignore
vendored
2
.gitignore
vendored
@ -22,4 +22,4 @@ widget
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
*.sw?
|
@ -1,38 +1,32 @@
|
||||
include:
|
||||
- project: "devops/pena-continuous-integration"
|
||||
file: "/templates/docker/build-template.gitlab-ci.yml"
|
||||
- project: "devops/pena-continuous-integration"
|
||||
file: "/templates/docker/clean-template.gitlab-ci.yml"
|
||||
- project: "devops/pena-continuous-integration"
|
||||
file: "/templates/docker/deploy-template.gitlab-ci.yml"
|
||||
stages:
|
||||
- clean
|
||||
- build
|
||||
- deploy
|
||||
|
||||
clear-old-images:
|
||||
extends: .clean_template
|
||||
variables:
|
||||
STAGING_BRANCH: "main"
|
||||
PRODUCTION_BRANCH: "main"
|
||||
image:
|
||||
name: docker/compose:1.28.0
|
||||
entrypoint: [""]
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
- docker images
|
||||
script:
|
||||
- docker system prune -af
|
||||
build-app:
|
||||
tags:
|
||||
- frontbuild
|
||||
extends: .build_template
|
||||
variables:
|
||||
DOCKER_BUILD_PATH: "./Dockerfile"
|
||||
STAGING_BRANCH: "main"
|
||||
STAGING_BRANCH: "staging"
|
||||
PRODUCTION_BRANCH: "main"
|
||||
|
||||
deploy-to-staging:
|
||||
extends: .deploy_template
|
||||
variables:
|
||||
DEPLOY_TO: "staging"
|
||||
BRANCH: "main"
|
||||
rules:
|
||||
- if: "$CI_COMMIT_BRANCH == $STAGING_BRANCH"
|
||||
tags:
|
||||
- front
|
||||
- staging
|
||||
|
||||
deploy-to-prod:
|
||||
extends: .deploy_template
|
||||
rules:
|
||||
- if: "$CI_COMMIT_BRANCH == $PRODUCTION_BRANCH"
|
||||
tags:
|
||||
- front
|
||||
- prod
|
||||
|
@ -10,5 +10,6 @@ RUN yarn build
|
||||
|
||||
FROM nginx:latest as result
|
||||
WORKDIR /usr/share/nginx/html
|
||||
COPY --from=build /usr/app/build/ /usr/share/nginx/html
|
||||
COPY --from=build /usr/app/dist/ /usr/share/nginx/html
|
||||
COPY pub.js /usr/share/nginx/html/export/pub.js
|
||||
COPY hub.conf /etc/nginx/conf.d/default.conf
|
||||
|
8
deployments/main/docker-compose.yaml
Normal file
8
deployments/main/docker-compose.yaml
Normal file
@ -0,0 +1,8 @@
|
||||
services:
|
||||
respondent:
|
||||
container_name: respondent
|
||||
restart: unless-stopped
|
||||
image: $CI_REGISTRY_IMAGE/main:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
hostname: respondent
|
||||
tty: true
|
||||
|
@ -2,12 +2,7 @@ services:
|
||||
respondent:
|
||||
container_name: respondent
|
||||
restart: unless-stopped
|
||||
image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
networks:
|
||||
- marketplace_penahub_frontend
|
||||
image: $CI_REGISTRY_IMAGE/staging:$CI_COMMIT_REF_SLUG.$CI_PIPELINE_ID
|
||||
hostname: respondent
|
||||
tty: true
|
||||
networks:
|
||||
marketplace_penahub_frontend:
|
||||
external: true
|
||||
|
||||
|
@ -2,9 +2,12 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
<title>Quiz</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
@ -20,9 +20,9 @@
|
||||
"@mui/x-date-pickers": "^6.16.1",
|
||||
"@types/node": "^16.7.13",
|
||||
"axios": "^1.5.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"emoji-mart": "^5.5.2",
|
||||
"immer": "^10.0.3",
|
||||
"moment": "^2.30.1",
|
||||
"nanoid": "^5.0.3",
|
||||
"notistack": "^3.0.1",
|
||||
"react": "^18.2.0",
|
||||
|
1
pub.js
Normal file
1
pub.js
Normal file
@ -0,0 +1 @@
|
||||
console.log("PEHA NUB")
|
@ -1,16 +1,15 @@
|
||||
import { Box, CssBaseline, ThemeProvider } from "@mui/material";
|
||||
import { LocalizationProvider } from "@mui/x-date-pickers";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
||||
import { ruRU } from '@mui/x-date-pickers/locales';
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/ru";
|
||||
import moment from "moment";
|
||||
import { SnackbarProvider } from 'notistack';
|
||||
import { SWRConfig } from "swr";
|
||||
import { ViewPage } from "./pages/ViewPublicationPage";
|
||||
import lightTheme from "./utils/themes/light";
|
||||
|
||||
|
||||
dayjs.locale("ru");
|
||||
moment.locale("ru");
|
||||
const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText;
|
||||
|
||||
interface Props {
|
||||
@ -24,7 +23,7 @@ export default function App({ widget = false }: Props) {
|
||||
revalidateOnFocus: false,
|
||||
shouldRetryOnError: false,
|
||||
}}>
|
||||
<LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="ru" localeText={localeText}>
|
||||
<LocalizationProvider dateAdapter={AdapterMoment} adapterLocale="ru" localeText={localeText}>
|
||||
<ThemeProvider theme={lightTheme}>
|
||||
<SnackbarProvider
|
||||
preventDuplicate={true}
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 7.7 KiB After Width: | Height: | Size: 7.7 KiB |
@ -5,10 +5,11 @@ type InfoProps = {
|
||||
height?: number;
|
||||
sx?: SxProps;
|
||||
onClick?: any;
|
||||
className?: string
|
||||
className?: string;
|
||||
color?: string
|
||||
};
|
||||
|
||||
export default function Info({ width = 20, height = 20, sx, onClick, className }: InfoProps) {
|
||||
export default function Info({ width = 20, height = 20, sx, onClick, className, color = "#7e2aea" }: InfoProps) {
|
||||
return (
|
||||
<IconButton
|
||||
sx={sx}
|
||||
@ -24,21 +25,21 @@ export default function Info({ width = 20, height = 20, sx, onClick, className }
|
||||
>
|
||||
<path
|
||||
d="M10 19C14.9706 19 19 14.9706 19 10C19 5.02944 14.9706 1 10 1C5.02944 1 1 5.02944 1 10C1 14.9706 5.02944 19 10 19Z"
|
||||
stroke="#7E2AEA"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M9.25 9.25H10V14.5H10.75"
|
||||
stroke="#7E2AEA"
|
||||
stroke={color}
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M9.8125 7C10.4338 7 10.9375 6.49632 10.9375 5.875C10.9375 5.25368 10.4338 4.75 9.8125 4.75C9.19118 4.75 8.6875 5.25368 8.6875 5.875C8.6875 6.49632 9.19118 7 9.8125 7Z"
|
||||
fill="#7E2AEA"
|
||||
fill={color}
|
||||
/>
|
||||
</svg>
|
||||
</IconButton>
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "dayjs/locale/ru";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
|
||||
|
@ -5,7 +5,6 @@ import type {
|
||||
} from "./shared";
|
||||
|
||||
export const UPLOAD_FILE_TYPES_MAP = {
|
||||
all: "Все типы файлов",
|
||||
picture: "Изображения",
|
||||
video: "Видео",
|
||||
audio: "Аудио",
|
||||
|
@ -87,6 +87,7 @@ export interface QuizConfig {
|
||||
text: FCField;
|
||||
address: FCField;
|
||||
button: string;
|
||||
fields: Record<FormContactFieldName, FormContactFieldData>;
|
||||
};
|
||||
info: {
|
||||
phonenumber: string;
|
||||
@ -98,6 +99,21 @@ export interface QuizConfig {
|
||||
meta: string;
|
||||
}
|
||||
|
||||
export type FormContactFieldName =
|
||||
| "name"
|
||||
| "email"
|
||||
| "phone"
|
||||
| "text"
|
||||
| "address";
|
||||
|
||||
type FormContactFieldData = {
|
||||
text: string;
|
||||
innerText: string;
|
||||
key: string;
|
||||
required: boolean;
|
||||
used: boolean;
|
||||
};
|
||||
|
||||
export interface QuizItems {
|
||||
description: string;
|
||||
id: number;
|
||||
|
@ -1,6 +1,6 @@
|
||||
import AddressIcon from "@icons/ContactFormIcon/AddressIcon";
|
||||
import EmailIcon from "@icons/ContactFormIcon/EmailIcon";
|
||||
import NameIcon from "@icons/ContactFormIcon/NameIcon";
|
||||
import EmailIcon from "@icons/ContactFormIcon/EmailIcon";
|
||||
import PhoneIcon from "@icons/ContactFormIcon/PhoneIcon";
|
||||
import TextIcon from "@icons/ContactFormIcon/TextIcon";
|
||||
import { Box, Button, InputAdornment, Link, TextField as MuiTextField, TextFieldProps, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
@ -20,16 +20,8 @@ import { useQuestionsStore } from "@stores/quizData/store";
|
||||
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>; // temporary fix ts(2590)
|
||||
|
||||
const EMAIL_REGEXP = /^(([^<>()[\].,:\s@"]+(\.[^<>()[\].,:\s@"]+)*)|(".+"))@(([^<>()[\].,:\s@"]+\.)+[^<>()[\].,:\s@"]{2,})$/iu;
|
||||
|
||||
type ContactType =
|
||||
| "name"
|
||||
| "email"
|
||||
| "phone"
|
||||
| "text"
|
||||
| "adress";
|
||||
|
||||
type ContactFormProps = {
|
||||
currentQuestion: any;
|
||||
showResultForm: boolean;
|
||||
@ -37,6 +29,44 @@ type ContactFormProps = {
|
||||
setShowResultForm: (show: boolean) => void;
|
||||
};
|
||||
|
||||
const icons = [
|
||||
{
|
||||
type: "name",
|
||||
icon: NameIcon,
|
||||
defaultText: "Введите имя",
|
||||
defaultTitle: "имя",
|
||||
backendName: "name",
|
||||
},
|
||||
{
|
||||
type: "email",
|
||||
icon: EmailIcon,
|
||||
defaultText: "Введите Email",
|
||||
defaultTitle: "Email",
|
||||
backendName: "email",
|
||||
},
|
||||
{
|
||||
type: "phone",
|
||||
icon: PhoneIcon,
|
||||
defaultText: "Введите номер телефона",
|
||||
defaultTitle: "номер телефона",
|
||||
backendName: "phone",
|
||||
},
|
||||
{
|
||||
type: "text",
|
||||
icon: TextIcon,
|
||||
defaultText: "Введите фамилию",
|
||||
defaultTitle: "фамилию",
|
||||
backendName: "adress",
|
||||
},
|
||||
{
|
||||
type: "address",
|
||||
icon: AddressIcon,
|
||||
defaultText: "Введите адрес",
|
||||
defaultTitle: "адрес",
|
||||
backendName: "adress",
|
||||
},
|
||||
];
|
||||
|
||||
export const ContactForm = ({
|
||||
currentQuestion,
|
||||
showResultForm,
|
||||
@ -44,73 +74,94 @@ export const ContactForm = ({
|
||||
setShowResultForm,
|
||||
}: ContactFormProps) => {
|
||||
const theme = useTheme();
|
||||
const { settings, items } = useQuestionsStore()
|
||||
const { settings, items } = useQuestionsStore();
|
||||
|
||||
const [ready, setReady] = useState(false)
|
||||
const [name, setName] = useState("")
|
||||
const [email, setEmail] = useState("")
|
||||
const [phone, setPhone] = useState("")
|
||||
const [text, setText] = useState("")
|
||||
const [adress, setAdress] = useState("")
|
||||
const [ready, setReady] = useState(false);
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [text, setText] = useState("");
|
||||
const [adress, setAdress] = useState("");
|
||||
|
||||
const fireOnce = useRef(true)
|
||||
const [fire, setFire] = useState(false)
|
||||
const fireOnce = useRef(true);
|
||||
const [fire, setFire] = useState(false);
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
||||
|
||||
const resultQuestion: QuizQuestionResult = items.find((question): question is QuizQuestionResult => {
|
||||
if (settings?.cfg.haveRoot) { //ветвимся
|
||||
const followNextForm = () => {
|
||||
setShowContactForm(false);
|
||||
setShowResultForm(true);
|
||||
};
|
||||
|
||||
//@ts-ignore
|
||||
const resultQuestion: QuizQuestionResult = items.find((question) => {
|
||||
if (settings?.cfg.haveRoot) {
|
||||
//ветвимся
|
||||
return (
|
||||
question.type === "result" &&
|
||||
//@ts-ignore
|
||||
question.content.rule.parentId === currentQuestion.content.id
|
||||
);
|
||||
} else {// не ветвимся
|
||||
} else {
|
||||
// не ветвимся
|
||||
return (
|
||||
question.type === "result" &&
|
||||
question.content.rule.parentId === "line"
|
||||
question.type === "result" && question.content.rule.parentId === "line"
|
||||
);
|
||||
}
|
||||
})!;
|
||||
});
|
||||
|
||||
const inputHC = async () => {
|
||||
if (!settings) return;
|
||||
|
||||
const body: Partial<Record<ContactType, string>> = {};
|
||||
|
||||
//@ts-ignore
|
||||
const FC = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||
const body = {};
|
||||
//@ts-ignore
|
||||
if (name.length > 0) body.name = name;
|
||||
//@ts-ignore
|
||||
if (email.length > 0) body.email = email;
|
||||
//@ts-ignore
|
||||
if (phone.length > 0) body.phone = phone;
|
||||
if (text.length > 0) body.text = text;
|
||||
if (adress.length > 0) body.adress = adress;
|
||||
//@ts-ignore
|
||||
if (adress.length > 0) body.address = adress;
|
||||
//@ts-ignore
|
||||
if (text.length > 0) body.customs = { [FC.text.text || "Фамилия"]: text };
|
||||
|
||||
if (Object.keys(body).length > 0) {
|
||||
try {
|
||||
await sendFC({
|
||||
questionId: resultQuestion?.id,
|
||||
body: body,
|
||||
qid: settings.qid
|
||||
})
|
||||
qid: settings.qid,
|
||||
});
|
||||
|
||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||
localStorage.setItem(
|
||||
"sessions",
|
||||
JSON.stringify({ ...sessions, [settings.qid]: new Date().getTime() })
|
||||
);
|
||||
} catch (e) {
|
||||
enqueueSnackbar("ответ не был засчитан")
|
||||
enqueueSnackbar("ответ не был засчитан");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
//@ts-ignore
|
||||
const FCcopy: any = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||
let FCcopy: any = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||
|
||||
const filteredFC: any = {};
|
||||
for (const i in FCcopy) {
|
||||
const field = FCcopy[i];
|
||||
let filteredFC: any = {};
|
||||
for (let i in FCcopy) {
|
||||
let field = FCcopy[i];
|
||||
console.log(filteredFC);
|
||||
if (field.used) {
|
||||
filteredFC[i] = field;
|
||||
}
|
||||
}
|
||||
const isWide = Object.keys(filteredFC).length > 2;
|
||||
let isWide = Object.keys(filteredFC).length > 2;
|
||||
|
||||
if (!settings) throw new Error("settings is null");
|
||||
if (!resultQuestion) return <ApologyPage message="не получилось найти результат для этой ветки :(" />
|
||||
if (!resultQuestion)
|
||||
return (
|
||||
<ApologyPage message="не получилось найти результат для этой ветки :(" />
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
@ -121,17 +172,21 @@ export const ContactForm = ({
|
||||
backgroundColor: theme.palette.background.default,
|
||||
height: "100vh",
|
||||
overflow: "auto",
|
||||
"&::-webkit-scrollbar": { width: "0", display: "none", msOverflowStyle: "none" },
|
||||
"&::-webkit-scrollbar": {
|
||||
width: "0",
|
||||
display: "none",
|
||||
msOverflowStyle: "none",
|
||||
},
|
||||
scrollbarWidth: "none",
|
||||
msOverflowStyle: "none"
|
||||
msOverflowStyle: "none",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: isWide && !isMobile ? "100%" : (isMobile ? undefined : "530px"),
|
||||
width: isWide && !isMobile ? "100%" : isMobile ? undefined : "530px",
|
||||
borderRadius: "4px",
|
||||
height: "90vh",
|
||||
display: isWide && !isMobile ? "flex" : undefined
|
||||
display: isWide && !isMobile ? "flex" : undefined,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
@ -141,7 +196,7 @@ export const ContactForm = ({
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
borderRight: isWide && !isMobile ? "1px solid gray" : undefined
|
||||
borderRight: isWide && !isMobile ? "1px solid gray" : undefined,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
@ -149,28 +204,26 @@ export const ContactForm = ({
|
||||
textAlign: "center",
|
||||
m: "20px 0",
|
||||
fontSize: "28px",
|
||||
color: theme.palette.text.primary
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
{settings?.cfg.formContact.title || "Заполните форму, чтобы получить результаты теста"}
|
||||
|
||||
{settings?.cfg.formContact.title ||
|
||||
"Заполните форму, чтобы получить результаты теста"}
|
||||
</Typography>
|
||||
{
|
||||
settings?.cfg.formContact.desc &&
|
||||
{settings?.cfg.formContact.desc && (
|
||||
<Typography
|
||||
sx={{
|
||||
color: theme.palette.text.primary,
|
||||
textAlign: "center",
|
||||
m: "20px 0",
|
||||
fontSize: "18px"
|
||||
fontSize: "18px",
|
||||
}}
|
||||
>
|
||||
{settings?.cfg.formContact.desc}
|
||||
</Typography>
|
||||
}
|
||||
)}
|
||||
</Box>
|
||||
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -178,77 +231,92 @@ export const ContactForm = ({
|
||||
justifyContent: "center",
|
||||
flexDirection: "column",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
p: "30px"
|
||||
}}>
|
||||
|
||||
p: "30px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
my: "20px"
|
||||
my: "20px",
|
||||
}}
|
||||
>
|
||||
<Inputs
|
||||
name={name} setName={setName}
|
||||
email={email} setEmail={setEmail}
|
||||
phone={phone} setPhone={setPhone}
|
||||
text={text} setText={setText}
|
||||
adress={adress} setAdress={setAdress}
|
||||
name={name}
|
||||
setName={setName}
|
||||
email={email}
|
||||
setEmail={setEmail}
|
||||
phone={phone}
|
||||
setPhone={setPhone}
|
||||
text={text}
|
||||
setText={setText}
|
||||
adress={adress}
|
||||
setAdress={setAdress}
|
||||
/>
|
||||
|
||||
|
||||
</Box>
|
||||
|
||||
{
|
||||
// resultQuestion &&
|
||||
// settings?.cfg.resultInfo.when === "after" &&
|
||||
(
|
||||
<Button
|
||||
disabled={!(ready && !fire)}
|
||||
variant="contained"
|
||||
onClick={async () => {
|
||||
//@ts-ignore
|
||||
const FC: any = settings?.cfg.formContact.fields || settings?.cfg.formContact
|
||||
if (FC["email"].used === EMAIL_REGEXP.test(email)) {//почта валидна
|
||||
setFire(true)
|
||||
// settings?.cfg.resultInfo.when === "after" &&
|
||||
<Button
|
||||
disabled={!(ready && !fire)}
|
||||
variant="contained"
|
||||
onClick={async () => {
|
||||
//@ts-ignore
|
||||
const FC: any = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||
if (FC["email"].used === EMAIL_REGEXP.test(email)) {
|
||||
//почта валидна
|
||||
setFire(true);
|
||||
|
||||
|
||||
if (fireOnce.current) {
|
||||
if (
|
||||
name.length > 0 ||
|
||||
email.length > 0 ||
|
||||
phone.length > 0 ||
|
||||
text.length > 0 ||
|
||||
adress.length > 0
|
||||
) {
|
||||
|
||||
try {
|
||||
await inputHC()
|
||||
fireOnce.current = false
|
||||
enqueueSnackbar("Данные успешно отправлены")
|
||||
} catch (e) {
|
||||
enqueueSnackbar("повторите попытку позже")
|
||||
}
|
||||
|
||||
if ((settings?.cfg.resultInfo.showResultForm === "after" || settings?.cfg.resultInfo.when === "email") && !checkEmptyData({ resultData: resultQuestion })) {
|
||||
setShowContactForm(false)
|
||||
setShowResultForm(true)
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar("Пожалуйста, заполните поля")
|
||||
if (fireOnce.current) {
|
||||
if (
|
||||
name.length > 0 ||
|
||||
email.length > 0 ||
|
||||
phone.length > 0 ||
|
||||
text.length > 0 ||
|
||||
adress.length > 0
|
||||
) {
|
||||
try {
|
||||
await inputHC();
|
||||
fireOnce.current = false;
|
||||
const QID =
|
||||
process.env.NODE_ENV === "production"
|
||||
? window.location.pathname.replace(/\//g, "")
|
||||
: "ef836ff8-35b1-4031-9acf-af5766bac2b2";
|
||||
const sessions: any = JSON.parse(
|
||||
localStorage.getItem("sessions") || "{}"
|
||||
);
|
||||
sessions[QID] = Date.now();
|
||||
localStorage.setItem(
|
||||
"sessions",
|
||||
JSON.stringify(sessions)
|
||||
);
|
||||
enqueueSnackbar("Данные успешно отправлены");
|
||||
} catch (e) {
|
||||
enqueueSnackbar("повторите попытку позже");
|
||||
}
|
||||
}
|
||||
|
||||
setFire(false)
|
||||
} else {
|
||||
enqueueSnackbar("введена некорректная почта")
|
||||
if (
|
||||
settings?.cfg.resultInfo.showResultForm === "after" &&
|
||||
!checkEmptyData({ resultData: resultQuestion })
|
||||
) {
|
||||
setShowContactForm(false);
|
||||
setShowResultForm(true);
|
||||
}
|
||||
} else {
|
||||
enqueueSnackbar("Пожалуйста, заполните поля");
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
{settings?.cfg.formContact?.button || "Получить результаты"}
|
||||
</Button>
|
||||
)}
|
||||
setFire(false);
|
||||
} else {
|
||||
enqueueSnackbar("введена некорректная почта");
|
||||
}
|
||||
}}
|
||||
>
|
||||
{settings?.cfg.formContact?.button || "Получить результаты"}
|
||||
</Button>
|
||||
}
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
@ -257,96 +325,169 @@ export const ContactForm = ({
|
||||
width: isMobile ? "300px" : "450px",
|
||||
}}
|
||||
>
|
||||
<CustomCheckbox label="" handleChange={({ target }) => { setReady(target.checked) }} checked={ready} colorIcon={theme.palette.primary.main} />
|
||||
<CustomCheckbox
|
||||
label=""
|
||||
handleChange={({ target }) => {
|
||||
setReady(target.checked);
|
||||
}}
|
||||
checked={ready}
|
||||
colorIcon={theme.palette.primary.main}
|
||||
/>
|
||||
<Typography sx={{ color: theme.palette.text.primary }}>
|
||||
С 
|
||||
<Link href={"https://shub.pena.digital/ppdd"} target="_blank">
|
||||
Положением об обработке персональных данных </Link>
|
||||
Положением об обработке персональных данных{" "}
|
||||
</Link>
|
||||
 и 
|
||||
<Link href={"https://shub.pena.digital/docs/privacy"} target="_blank"> Политикой конфиденциальности </Link>
|
||||
<Link
|
||||
href={"https://shub.pena.digital/docs/privacy"}
|
||||
target="_blank"
|
||||
>
|
||||
{" "}
|
||||
Политикой конфиденциальности{" "}
|
||||
</Link>
|
||||
 ознакомлен
|
||||
</Typography>
|
||||
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
mt: "20px",
|
||||
gap: "15px"
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<NameplateLogo style={{
|
||||
fontSize: "34px",
|
||||
color: quizThemes[settings.cfg.theme].isLight ? "#151515" : "#FFFFFF"
|
||||
}} />
|
||||
<Typography sx={{
|
||||
fontSize: "20px",
|
||||
color: quizThemes[settings.cfg.theme].isLight ? "#4D4D4D" : "#F5F7FF", whiteSpace: "nowrap"
|
||||
}}>
|
||||
<NameplateLogo
|
||||
style={{
|
||||
fontSize: "34px",
|
||||
//@ts-ignore
|
||||
color: mode[settings.cfg.theme] ? "#151515" : "#FFFFFF",
|
||||
}}
|
||||
/>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "20px",
|
||||
//@ts-ignore
|
||||
color: mode[settings.cfg.theme] ? "#4D4D4D" : "#F5F7FF",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
Сделано на PenaQuiz
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box >
|
||||
</Box >
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Inputs = ({
|
||||
name, setName,
|
||||
email, setEmail,
|
||||
phone, setPhone,
|
||||
text, setText,
|
||||
adress, setAdress
|
||||
name,
|
||||
setName,
|
||||
email,
|
||||
setEmail,
|
||||
phone,
|
||||
setPhone,
|
||||
text,
|
||||
setText,
|
||||
adress,
|
||||
setAdress,
|
||||
}: any) => {
|
||||
const { settings } = useQuestionsStore()
|
||||
const { settings } = useQuestionsStore();
|
||||
|
||||
// @ts-ignore
|
||||
const FC = settings?.cfg.formContact.fields || settings?.cfg.formContact;
|
||||
|
||||
if (!FC) return null;
|
||||
|
||||
//@ts-ignore
|
||||
const FC: any = settings?.cfg.formContact.fields || settings?.cfg.formContact
|
||||
|
||||
const Name = (
|
||||
<CustomInput
|
||||
//@ts-ignore
|
||||
onChange={({ target }) => setName(target.value)}
|
||||
id={name}
|
||||
title={FC["name"].innerText || "Введите имя"}
|
||||
desc={FC["name"].text || "имя"}
|
||||
Icon={NameIcon}
|
||||
/>
|
||||
);
|
||||
//@ts-ignore
|
||||
const Name = <CustomInput onChange={({ target }) => setName(target.value)} id={name} title={FC["name"].innerText || "Введите имя"} desc={FC["name"].text || "имя"} Icon={NameIcon} />
|
||||
const Email = (
|
||||
<CustomInput
|
||||
error={!EMAIL_REGEXP.test(email)}
|
||||
label={!EMAIL_REGEXP.test(email) ? "" : "Некорректная почта"}
|
||||
//@ts-ignore
|
||||
onChange={({ target }) => setEmail(target.value)}
|
||||
id={email}
|
||||
title={FC["email"].innerText || "Введите Email"}
|
||||
desc={FC["email"].text || "Email"}
|
||||
Icon={EmailIcon}
|
||||
/>
|
||||
);
|
||||
const Phone = (
|
||||
<CustomInput
|
||||
//@ts-ignore
|
||||
onChange={({ target }) => setPhone(target.value)}
|
||||
id={phone}
|
||||
title={FC["phone"].innerText || "Введите номер телефона"}
|
||||
desc={FC["phone"].text || "номер телефона"}
|
||||
Icon={PhoneIcon}
|
||||
/>
|
||||
);
|
||||
//@ts-ignore
|
||||
const Email = <CustomInput
|
||||
|
||||
error = {!EMAIL_REGEXP.test(email)}
|
||||
label={!EMAIL_REGEXP.test(email) ? "" : "Некорректная почта"}
|
||||
//@ts-ignore
|
||||
onChange={({ target }) => setEmail(target.value)} id={email} title={FC["email"].innerText || "Введите Email"} desc={FC["email"].text || "Email"} Icon={EmailIcon} />
|
||||
const Text = (
|
||||
<CustomInput
|
||||
//@ts-ignore
|
||||
onChange={({ target }) => setText(target.value)}
|
||||
id={text}
|
||||
title={FC["text"].text || "Введите фамилию"}
|
||||
desc={FC["text"].innerText || "фамилию"}
|
||||
Icon={TextIcon}
|
||||
/>
|
||||
);
|
||||
//@ts-ignore
|
||||
const Phone = <CustomInput onChange={({ target }) => setPhone(target.value)} id={phone} title={FC["phone"].innerText || "Введите номер телефона"} desc={FC["phone"].text || "номер телефона"} Icon={PhoneIcon} />
|
||||
//@ts-ignore
|
||||
const Text = <CustomInput onChange={({ target }) => setText(target.value)} id={text} title={FC["text"].innerText || "Введите фамилию"} desc={FC["text"].text || "фамилию"} Icon={TextIcon} />
|
||||
//@ts-ignore
|
||||
const Adress = <CustomInput onChange={({ target }) => setAdress(target.value)} id={adress} title={FC["address"].innerText || "Введите адрес"} desc={FC["address"].text || "адрес"} Icon={AddressIcon} />
|
||||
const Adress = (
|
||||
<CustomInput
|
||||
//@ts-ignore
|
||||
onChange={({ target }) => setAdress(target.value)}
|
||||
id={adress}
|
||||
title={FC["address"].innerText || "Введите адрес"}
|
||||
desc={FC["address"].text || "адрес"}
|
||||
Icon={AddressIcon}
|
||||
/>
|
||||
);
|
||||
|
||||
//@ts-ignore
|
||||
if (Object.values(FC).some((data) => data.used)) {
|
||||
return <>
|
||||
{FC["name"].used ? Name : <></>}
|
||||
{FC["email"].used ? Email : <></>}
|
||||
{FC["phone"].used ? Phone : <></>}
|
||||
{FC["text"].used ? Text : <></>}
|
||||
{FC["address"].used ? Adress : <></>}
|
||||
</>
|
||||
return (
|
||||
<>
|
||||
{FC["name"].used ? Name : <></>}
|
||||
{FC["email"].used ? Email : <></>}
|
||||
{FC["phone"].used ? Phone : <></>}
|
||||
{FC["text"].used ? Text : <></>}
|
||||
{FC["address"].used ? Adress : <></>}
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return <>
|
||||
{Name}
|
||||
{Email}
|
||||
{Phone}
|
||||
</>
|
||||
return (
|
||||
<>
|
||||
{Name}
|
||||
{Email}
|
||||
{Phone}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const CustomInput = ({ title, desc, Icon, onChange }: any) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
//@ts-ignore
|
||||
return (
|
||||
<Box m="15px 0">
|
||||
<Typography mb="7px" color={theme.palette.text.primary}>{title}</Typography>
|
||||
<Typography mb="7px" color={theme.palette.text.primary}>
|
||||
{title}
|
||||
</Typography>
|
||||
|
||||
<TextField
|
||||
onChange={onChange}
|
||||
@ -355,9 +496,13 @@ const CustomInput = ({ title, desc, Icon, onChange }: any) => {
|
||||
}}
|
||||
placeholder={desc}
|
||||
InputProps={{
|
||||
startAdornment: <InputAdornment position="start"><Icon color="gray" /></InputAdornment>,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Icon color="gray" />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -11,15 +11,16 @@ import useSWR from "swr";
|
||||
import { ApologyPage } from "./ApologyPage";
|
||||
import { Question } from "./Question";
|
||||
import { StartPageViewPublication } from "./StartPageViewPublication";
|
||||
import { replaceSpacesToEmptyLines } from "./tools/replaceSpacesToEmptyLines";
|
||||
import { parseQuizData } from "@model/api/getQuizData";
|
||||
|
||||
|
||||
import { parseQuizData } from "@model/api/getQuizData";
|
||||
import { replaceSpacesToEmptyLines } from "./tools/replaceSpacesToEmptyLines";
|
||||
|
||||
const QID =
|
||||
import.meta.env.PROD ?
|
||||
window.location.pathname.replace(/\//g, '')
|
||||
:
|
||||
"0bed8483-3016-4bca-b8e0-a72c3146f18b";
|
||||
"ef836ff8-35b1-4031-9acf-af5766bac2b2";
|
||||
|
||||
|
||||
export const ViewPage = () => {
|
||||
@ -36,6 +37,8 @@ export const ViewPage = () => {
|
||||
if (link && settings.cfg.startpage.favIcon) {
|
||||
link.setAttribute("href", settings?.cfg.startpage.favIcon);
|
||||
}
|
||||
//установка заголовка страницы
|
||||
document.title = settings.name;
|
||||
|
||||
setVisualStartPage(!settings.cfg.noStartPage);
|
||||
}, [settings]);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import dayjs from "dayjs";
|
||||
import moment from "moment";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
|
||||
@ -24,8 +24,8 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
||||
const answer = answers.find(
|
||||
({ questionId }) => questionId === currentQuestion.id
|
||||
)?.answer as string;
|
||||
const [day, month, year] = answer?.split(".") || [];
|
||||
|
||||
const currentAnswer = moment(answer) || moment();
|
||||
|
||||
if (!settings) throw new Error("settings is null");
|
||||
|
||||
return (
|
||||
@ -52,12 +52,9 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
||||
/>
|
||||
),
|
||||
}}
|
||||
value={dayjs(
|
||||
month && day && year
|
||||
? new window.Date(`${month}.${day}.${year}`)
|
||||
: new window.Date()
|
||||
)}
|
||||
value={ currentAnswer }
|
||||
onChange={async (date) => {
|
||||
console.log(date)
|
||||
if (!date) {
|
||||
return;
|
||||
}
|
||||
@ -65,26 +62,13 @@ export const Date = ({ currentQuestion }: DateProps) => {
|
||||
try {
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: new window.Date(date.toDate()).toLocaleDateString(
|
||||
"ru-RU",
|
||||
{
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
}
|
||||
),
|
||||
body: moment(date).format("YYYY.MM.DD"),
|
||||
qid: settings.qid,
|
||||
});
|
||||
|
||||
updateAnswer(
|
||||
currentQuestion.id,
|
||||
String(
|
||||
new window.Date(date.toDate()).toLocaleDateString("ru-RU", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
})
|
||||
)
|
||||
date
|
||||
);
|
||||
} catch (e) {
|
||||
enqueueSnackbar("ответ не был засчитан");
|
||||
|
@ -111,7 +111,7 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
|
||||
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: currentQuestion.content.variants[index].answer,
|
||||
body: currentQuestion.content.variants[index].extendedText + " " + currentQuestion.content.variants[index].answer,
|
||||
qid: settings.qid
|
||||
})
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
ButtonBase,
|
||||
useTheme,
|
||||
IconButton, useMediaQuery,
|
||||
Box,
|
||||
Typography,
|
||||
ButtonBase,
|
||||
useTheme,
|
||||
IconButton, useMediaQuery, Modal,
|
||||
} from "@mui/material";
|
||||
import { useQuizViewStore, updateAnswer } from "@stores/quizView/store";
|
||||
import { UPLOAD_FILE_TYPES_MAP } from "../tools/File";
|
||||
@ -11,23 +11,97 @@ import { UPLOAD_FILE_TYPES_MAP } from "../tools/File";
|
||||
import UploadIcon from "@icons/UploadIcon";
|
||||
import CloseBold from "@icons/CloseBold";
|
||||
|
||||
import type { ChangeEvent } from "react";
|
||||
import { useState, type ChangeEvent } from "react";
|
||||
import type { QuizQuestionFile } from "../../../model/questionTypes/file";
|
||||
import type { DragEvent } from "react";
|
||||
import type { UploadFileType } from "@model/questionTypes/file";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { sendFile } from "@api/quizRelase";
|
||||
import { sendAnswer, sendFile } from "@api/quizRelase";
|
||||
import { useQuestionsStore } from "@stores/quizData/store"
|
||||
import Info from "@icons/Info";
|
||||
|
||||
type FileProps = {
|
||||
currentQuestion: QuizQuestionFile;
|
||||
};
|
||||
|
||||
const CurrentModal = ({ status }: { status: "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "" }) => {
|
||||
switch (status) {
|
||||
case 'errorType':
|
||||
return (<>
|
||||
<Typography>Выбран некорректный тип файла</Typography>
|
||||
</>)
|
||||
case 'errorSize':
|
||||
return (<>
|
||||
<Typography>Файл слишком большой. Максимальный размер 50 МБ</Typography>
|
||||
</>)
|
||||
default:
|
||||
return (<>
|
||||
<Typography>Допустимые расширения файлов:</Typography>
|
||||
<Typography>{
|
||||
//@ts-ignore
|
||||
ACCEPT_SEND_FILE_TYPES_MAP[status].join(" ")}</Typography>
|
||||
</>)
|
||||
}
|
||||
}
|
||||
|
||||
const ACCEPT_SEND_FILE_TYPES_MAP = {
|
||||
picture: [
|
||||
".jpeg",
|
||||
".jpg",
|
||||
".png",
|
||||
".ico",
|
||||
".gif",
|
||||
".tiff",
|
||||
".webp",
|
||||
".eps",
|
||||
".svg"
|
||||
],
|
||||
video: [
|
||||
".mp4",
|
||||
".mov",
|
||||
".wmv",
|
||||
".avi",
|
||||
".avchd",
|
||||
".flv",
|
||||
".f4v",
|
||||
".swf",
|
||||
".mkv",
|
||||
".webm",
|
||||
".mpeg-2"
|
||||
],
|
||||
audio: [
|
||||
".aac",
|
||||
".aiff",
|
||||
".dsd",
|
||||
".flac",
|
||||
".mp3",
|
||||
".mqa",
|
||||
".ogg",
|
||||
".wav",
|
||||
".wma"
|
||||
],
|
||||
document: [
|
||||
".doc",
|
||||
".docx",
|
||||
".dotx",
|
||||
".rtf",
|
||||
".odt",
|
||||
".pdf",
|
||||
".txt",
|
||||
".xls",
|
||||
".ppt",
|
||||
".xlsx",
|
||||
".pptx",
|
||||
".pages",
|
||||
],
|
||||
|
||||
}
|
||||
|
||||
|
||||
const UPLOAD_FILE_DESCRIPTIONS_MAP: Record<
|
||||
UploadFileType,
|
||||
{ title: string; description: string }
|
||||
> = {
|
||||
all: { title: "Добавить файл", description: "Принимает любые файлы" },
|
||||
picture: {
|
||||
title: "Добавить изображение",
|
||||
description: "Принимает изображения",
|
||||
@ -41,171 +115,230 @@ const UPLOAD_FILE_DESCRIPTIONS_MAP: Record<
|
||||
} as const;
|
||||
|
||||
export const File = ({ currentQuestion }: FileProps) => {
|
||||
const theme = useTheme();
|
||||
const { settings } = useQuestionsStore()
|
||||
const { answers } = useQuizViewStore();
|
||||
|
||||
const [statusModal, setStatusModal] = useState<"errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | "">("")
|
||||
|
||||
const answer = answers.find(
|
||||
({ questionId }) => questionId === currentQuestion.id
|
||||
)?.answer as string;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(500));
|
||||
const uploadFile = async ({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||
if (!settings) return;
|
||||
|
||||
const file = target.files?.[0];
|
||||
if (file) {
|
||||
if (file.size <= 52428800) {
|
||||
//проверяем на соответствие
|
||||
console.log(file.name.toLowerCase())
|
||||
if (ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].find((ednding => {
|
||||
console.log(ednding)
|
||||
console.log(file.name.toLowerCase().endsWith(ednding))
|
||||
return file.name.toLowerCase().endsWith(ednding)
|
||||
}))) {
|
||||
|
||||
try {
|
||||
//Нужный формат
|
||||
console.log(file)
|
||||
try {
|
||||
|
||||
await sendFile({
|
||||
questionId: currentQuestion.id,
|
||||
body: {
|
||||
file: `${file.name}|${URL.createObjectURL(file)}`,
|
||||
name: file.name
|
||||
},
|
||||
qid: settings.qid
|
||||
})
|
||||
const data = await sendFile({
|
||||
questionId: currentQuestion.id,
|
||||
body: {
|
||||
file: file,
|
||||
name: file.name
|
||||
},
|
||||
qid: settings.qid
|
||||
})
|
||||
console.log(data)
|
||||
|
||||
updateAnswer(
|
||||
currentQuestion.id,
|
||||
`${file.name}|${URL.createObjectURL(file)}`
|
||||
);
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
//@ts-ignore
|
||||
body: `https://storage.yandexcloud.net/squizanswer/${settings.qid}/${currentQuestion.id}/${data.data.fileIDMap[currentQuestion.id]}`,
|
||||
//@ts-ignore
|
||||
qid: settings.qid
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
enqueueSnackbar("ответ не был засчитан")
|
||||
}
|
||||
updateAnswer(
|
||||
currentQuestion.id,
|
||||
`${file.name}|${URL.createObjectURL(file)}`
|
||||
);
|
||||
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
enqueueSnackbar("ответ не был засчитан")
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
//неподходящий формат
|
||||
setStatusModal("errorType")
|
||||
}
|
||||
} else {
|
||||
|
||||
setStatusModal("errorSize")
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary}>{currentQuestion.title}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
marginTop: "20px",
|
||||
maxWidth: answer?.split("|")[0] ? "640px" : "550px",
|
||||
}}
|
||||
>
|
||||
{answer?.split("|")[0] && (
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
||||
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "5px 5px 5px 16px",
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
borderRadius: "8px",
|
||||
color: "#FFFFFF",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
overflow: "hidden",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{answer?.split("|")[0]}</Typography>
|
||||
<IconButton
|
||||
sx={{ p: 0 }}
|
||||
onClick={() => {
|
||||
updateAnswer(currentQuestion.id, "");
|
||||
<>
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary}>{currentQuestion.title}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
marginTop: "20px",
|
||||
maxWidth: answer?.split("|")[0] ? "640px" : "600px",
|
||||
}}
|
||||
>
|
||||
{answer?.split("|")[0] && (
|
||||
<Box sx={{ display: "flex", alignItems: "center", gap: "15px" }}>
|
||||
<Typography color={theme.palette.text.primary}>Вы загрузили:</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
padding: "5px 5px 5px 16px",
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
borderRadius: "8px",
|
||||
color: "#FFFFFF",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
overflow: "hidden",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<CloseBold />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{!answer?.split("|")[0] && (
|
||||
<ButtonBase component="label" sx={{ justifyContent: "flex-start" }}>
|
||||
<input
|
||||
onChange={uploadFile}
|
||||
hidden
|
||||
accept={UPLOAD_FILE_TYPES_MAP[currentQuestion.content.type]}
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<Box
|
||||
onDragOver={(event: DragEvent<HTMLDivElement>) =>
|
||||
event.preventDefault()
|
||||
}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: isMobile ? undefined : "120px",
|
||||
display: "flex",
|
||||
gap: "50px",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
padding: "33px 44px 33px 55px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid #9A9AAF`,
|
||||
// border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<UploadIcon />
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#9A9AAF",
|
||||
// color: theme.palette.grey2.main,
|
||||
fontWeight: 500,
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
}}
|
||||
>
|
||||
{
|
||||
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
||||
.title
|
||||
}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#9A9AAF",
|
||||
// color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
{answer?.split("|")[0]}</Typography>
|
||||
<IconButton
|
||||
sx={{ p: 0 }}
|
||||
onClick={() => {
|
||||
updateAnswer(currentQuestion.id, "");
|
||||
}}
|
||||
>
|
||||
{
|
||||
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
||||
.description
|
||||
}
|
||||
</Typography>
|
||||
<CloseBold />
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
)}
|
||||
{answer && currentQuestion.content.type === "picture" && (
|
||||
<img
|
||||
src={answer.split("|")[1]}
|
||||
alt=""
|
||||
style={{
|
||||
marginTop: "15px",
|
||||
maxWidth: "300px",
|
||||
maxHeight: "300px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{answer && currentQuestion.content.type === "video" && (
|
||||
<video
|
||||
src={answer.split("|")[1]}
|
||||
style={{
|
||||
marginTop: "15px",
|
||||
maxWidth: "300px",
|
||||
maxHeight: "300px",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
)}
|
||||
|
||||
{!answer?.split("|")[0] && (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
alignItems: "center"
|
||||
}}>
|
||||
<ButtonBase component="label" sx={{ justifyContent: "flex-start", width: "100%" }}>
|
||||
<input
|
||||
onChange={uploadFile}
|
||||
hidden
|
||||
accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")}
|
||||
multiple
|
||||
type="file"
|
||||
/>
|
||||
<Box
|
||||
onDragOver={(event: DragEvent<HTMLDivElement>) =>
|
||||
event.preventDefault()
|
||||
}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: isMobile ? undefined : "120px",
|
||||
display: "flex",
|
||||
gap: "50px",
|
||||
justifyContent: "flex-start",
|
||||
alignItems: "center",
|
||||
padding: "33px 44px 33px 55px",
|
||||
backgroundColor: theme.palette.background.default,
|
||||
border: `1px solid #9A9AAF`,
|
||||
// border: `1px solid ${theme.palette.grey2.main}`,
|
||||
borderRadius: "8px",
|
||||
}}
|
||||
>
|
||||
<UploadIcon />
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#9A9AAF",
|
||||
// color: theme.palette.grey2.main,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{
|
||||
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
||||
.title
|
||||
}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
color: "#9A9AAF",
|
||||
// color: theme.palette.grey2.main,
|
||||
fontSize: "16px",
|
||||
lineHeight: "19px",
|
||||
}}
|
||||
>
|
||||
{
|
||||
UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type]
|
||||
.description
|
||||
}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
<Info sx={{ width: "40px", height: "40px" }} color={theme.palette.primary.main} onClick={() => setStatusModal(currentQuestion.content.type)} />
|
||||
</Box>
|
||||
)}
|
||||
{answer && currentQuestion.content.type === "picture" && (
|
||||
<img
|
||||
src={answer.split("|")[1]}
|
||||
alt=""
|
||||
style={{
|
||||
marginTop: "15px",
|
||||
maxWidth: "300px",
|
||||
maxHeight: "300px",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{answer && currentQuestion.content.type === "video" && (
|
||||
<video
|
||||
src={answer.split("|")[1]}
|
||||
style={{
|
||||
marginTop: "15px",
|
||||
maxWidth: "300px",
|
||||
maxHeight: "300px",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
<Modal
|
||||
open={Boolean(statusModal)}
|
||||
onClose={() => setStatusModal("")}
|
||||
>
|
||||
<Box sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
width: isMobile ? 300 : 400,
|
||||
bgcolor: 'background.paper',
|
||||
borderRadius: 3,
|
||||
boxShadow: 24,
|
||||
p: 4,
|
||||
}}>
|
||||
<CurrentModal status={statusModal} />
|
||||
</Box>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
|
||||
};
|
||||
|
@ -78,7 +78,7 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
|
||||
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: currentQuestion.content.variants[index].answer,
|
||||
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
||||
qid: settings.qid
|
||||
})
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { useQuizViewStore, updateAnswer } from "@stores/quizView/store";
|
||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { sendAnswer } from "@api/quizRelase";
|
||||
|
||||
|
||||
import { quizThemes } from "@utils/themes/Publication/themePublication";
|
||||
import { useQuestionsStore } from "@stores/quizData/store";
|
||||
|
||||
@ -19,74 +19,89 @@ type NumberProps = {
|
||||
};
|
||||
|
||||
export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
const { settings } = useQuestionsStore()
|
||||
const { settings } = useQuestionsStore();
|
||||
const [inputValue, setInputValue] = useState<string>("0");
|
||||
const [minRange, setMinRange] = useState<string>("0");
|
||||
const [maxRange, setMaxRange] = useState<string>("100000000000");
|
||||
const theme = useTheme();
|
||||
const { answers } = useQuizViewStore();
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||
|
||||
|
||||
const updateMinRangeDebounced = useDebouncedCallback(async (value, crowded = false) => {
|
||||
if (!settings) return null;
|
||||
|
||||
if (crowded) {
|
||||
setMinRange(maxRange);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: value,
|
||||
qid: settings.qid
|
||||
})
|
||||
|
||||
updateAnswer(currentQuestion.id, value);
|
||||
|
||||
} catch (e) {
|
||||
enqueueSnackbar("ответ не был засчитан")
|
||||
}
|
||||
|
||||
|
||||
}, 1000);
|
||||
const updateMaxRangeDebounced = useDebouncedCallback(async (value, crowded = false) => {
|
||||
if (!settings) return null;
|
||||
|
||||
if (crowded) {
|
||||
setMaxRange(minRange);
|
||||
}
|
||||
try {
|
||||
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: value,
|
||||
qid: settings.qid
|
||||
})
|
||||
|
||||
updateAnswer(currentQuestion.id, value);
|
||||
|
||||
} catch (e) {
|
||||
enqueueSnackbar("ответ не был засчитан")
|
||||
}
|
||||
|
||||
}, 1000);
|
||||
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string;
|
||||
|
||||
const min = window.Number(currentQuestion.content.range.split("—")[0]);
|
||||
const max = window.Number(currentQuestion.content.range.split("—")[1]);
|
||||
|
||||
const sendAnswerToBackend = async (value: string) => {
|
||||
try {
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: value,
|
||||
//@ts-ignore
|
||||
qid: settings.qid,
|
||||
});
|
||||
|
||||
updateAnswer(currentQuestion.id, value);
|
||||
} catch (e) {
|
||||
enqueueSnackbar("ответ не был засчитан");
|
||||
}
|
||||
};
|
||||
|
||||
const updateValueDebounced = useDebouncedCallback(async (value: string) => {
|
||||
const newValue =
|
||||
window.Number(value) < window.Number(minRange)
|
||||
? minRange
|
||||
: window.Number(value) > window.Number(maxRange)
|
||||
? maxRange
|
||||
: value;
|
||||
|
||||
setInputValue(newValue);
|
||||
await sendAnswerToBackend(newValue);
|
||||
}, 1000);
|
||||
const updateMinRangeDebounced = useDebouncedCallback(
|
||||
async (value: string, crowded = false) => {
|
||||
const newMinValue = crowded
|
||||
? maxRange
|
||||
: window.Number(value.split("—")[0]) < min
|
||||
? String(min)
|
||||
: value.split("—")[0];
|
||||
|
||||
setMinRange(newMinValue);
|
||||
await sendAnswerToBackend(`${newMinValue}—${value.split("—")[1]}`);
|
||||
},
|
||||
1000
|
||||
);
|
||||
const updateMaxRangeDebounced = useDebouncedCallback(
|
||||
async (value: string, crowded = false) => {
|
||||
const newMaxValue = crowded
|
||||
? minRange
|
||||
: window.Number(value.split("—")[1]) > max
|
||||
? String(max)
|
||||
: value.split("—")[1];
|
||||
|
||||
setMaxRange(newMaxValue);
|
||||
await sendAnswerToBackend(`${value.split("—")[0]}—${newMaxValue}`);
|
||||
},
|
||||
1000
|
||||
);
|
||||
const answer = answers.find(
|
||||
({ questionId }) => questionId === currentQuestion.id
|
||||
)?.answer as string;
|
||||
|
||||
const sliderValue = answer || currentQuestion.content.start + "—" + max;
|
||||
|
||||
useEffect(() => {
|
||||
if (answer) {
|
||||
setMinRange(answer.split("—")[0]);
|
||||
setMaxRange(answer.split("—")[1]);
|
||||
if (answer.includes("—")) {
|
||||
setMinRange(answer.split("—")[0]);
|
||||
setMaxRange(answer.split("—")[1]);
|
||||
} else {
|
||||
setInputValue(answer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!answer) {
|
||||
setMinRange(String(currentQuestion.content.start));
|
||||
setMaxRange(String(max));
|
||||
setInputValue(String(currentQuestion.content.start));
|
||||
}
|
||||
}, []);
|
||||
|
||||
@ -94,7 +109,9 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h5" color={theme.palette.text.primary}>{currentQuestion.title}</Typography>
|
||||
<Typography variant="h5" color={theme.palette.text.primary}>
|
||||
{currentQuestion.title}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
@ -102,7 +119,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
width: "100%",
|
||||
marginTop: "20px",
|
||||
gap: "30px",
|
||||
paddingRight: isMobile ? "10px" : undefined
|
||||
paddingRight: isMobile ? "10px" : undefined,
|
||||
}}
|
||||
>
|
||||
<CustomSlider
|
||||
@ -116,50 +133,51 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
min={min}
|
||||
max={max}
|
||||
step={currentQuestion.content.step || 1}
|
||||
onChange={async (_, value) => {
|
||||
|
||||
|
||||
|
||||
const range = String(value).replace(",", "—").replace (/\D/, '');
|
||||
|
||||
onChange={(_, value) => {
|
||||
const range = Array.isArray(value)
|
||||
? `${value[0]}—${value[1]}`
|
||||
: String(value);
|
||||
|
||||
updateAnswer(currentQuestion.id, range);
|
||||
updateMinRangeDebounced(range, true);
|
||||
|
||||
}}
|
||||
onChangeCommitted={(_, value) => {
|
||||
if (currentQuestion.content.chooseRange) {
|
||||
const range = value as number[];
|
||||
onChangeCommitted={async (_, value) => {
|
||||
if (currentQuestion.content.chooseRange && Array.isArray(value)) {
|
||||
setMinRange(String(value[0]));
|
||||
setMaxRange(String(value[1]));
|
||||
await sendAnswerToBackend(`${value[0]}—${value[1]}`);
|
||||
|
||||
setMinRange(String(range[0]));
|
||||
setMaxRange(String(range[1]));
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue(String(value));
|
||||
await sendAnswerToBackend(String(value));
|
||||
}}
|
||||
//@ts-ignore
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
"& .MuiSlider-valueLabel": {
|
||||
background: theme.palette.primary.main,
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
{!currentQuestion.content.chooseRange && (
|
||||
<CustomTextField
|
||||
placeholder="0"
|
||||
value={answer}
|
||||
onChange={async ({ target }) => {
|
||||
updateMinRangeDebounced(window.Number(target.value.replace (/\D/, '')) > max
|
||||
? String(max)
|
||||
: window.Number(target.value) < min
|
||||
? String(min)
|
||||
: target.value, true);
|
||||
value={inputValue}
|
||||
onChange={({ target }) => {
|
||||
const value = target.value.replace(/\D/g, "");
|
||||
setInputValue(value);
|
||||
updateValueDebounced(value);
|
||||
}}
|
||||
sx={{
|
||||
maxWidth: "80px",
|
||||
borderColor: theme.palette.text.primary,
|
||||
"& .MuiInputBase-input": {
|
||||
textAlign: "center",
|
||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
||||
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@ -178,22 +196,25 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
placeholder="0"
|
||||
value={minRange}
|
||||
onChange={({ target }) => {
|
||||
setMinRange(target.value.replace (/\D/, ''));
|
||||
const newValue = target.value.replace(/\D/g, "");
|
||||
setMinRange(newValue);
|
||||
|
||||
if (window.Number(target.value) >= window.Number(maxRange)) {
|
||||
if (window.Number(newValue) >= window.Number(maxRange)) {
|
||||
updateMinRangeDebounced(`${maxRange}—${maxRange}`, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateMinRangeDebounced(`${target.value}—${maxRange}`);
|
||||
updateMinRangeDebounced(`${newValue}—${maxRange}`);
|
||||
}}
|
||||
sx={{
|
||||
maxWidth: "80px",
|
||||
borderColor: theme.palette.text.primary,
|
||||
"& .MuiInputBase-input": {
|
||||
textAlign: "center",
|
||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
||||
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@ -202,22 +223,25 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
placeholder="0"
|
||||
value={maxRange}
|
||||
onChange={({ target }) => {
|
||||
setMaxRange(target.value.replace (/\D/, ''));
|
||||
const newValue = target.value.replace(/\D/g, "");
|
||||
setMaxRange(newValue);
|
||||
|
||||
if (window.Number(target.value) <= window.Number(minRange)) {
|
||||
if (window.Number(newValue) <= window.Number(minRange)) {
|
||||
updateMaxRangeDebounced(`${minRange}—${minRange}`, true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
updateMaxRangeDebounced(`${minRange}—${target.value}`);
|
||||
updateMaxRangeDebounced(`${minRange}—${newValue}`);
|
||||
}}
|
||||
sx={{
|
||||
maxWidth: "80px",
|
||||
borderColor: theme.palette.text.primary,
|
||||
"& .MuiInputBase-input": {
|
||||
textAlign: "center",
|
||||
backgroundColor: quizThemes[settings.cfg.theme].isLight ? "white" : theme.palette.background.default,
|
||||
backgroundColor: quizThemes[settings.cfg.theme].isLight
|
||||
? "white"
|
||||
: theme.palette.background.default,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
@ -226,5 +250,4 @@ export const Number = ({ currentQuestion }: NumberProps) => {
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
};
|
||||
|
@ -99,7 +99,7 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
|
||||
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: String(value),
|
||||
body: String(value) + " из " + currentQuestion.content.steps,
|
||||
qid: settings.qid
|
||||
})
|
||||
|
||||
|
@ -92,7 +92,7 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
|
||||
|
||||
await sendAnswer({
|
||||
questionId: currentQuestion.id,
|
||||
body: currentQuestion.content.variants[index].answer,
|
||||
body: `${currentQuestion.content.variants[index].answer} <img style="width:100%; max-width:250px; max-height:250px" src="${currentQuestion.content.variants[index].extendedText}"/>`,
|
||||
qid: settings.qid
|
||||
})
|
||||
|
||||
|
@ -7,7 +7,6 @@ import type {
|
||||
} from "model/questionTypes/file";
|
||||
|
||||
export const UPLOAD_FILE_TYPES_MAP: Record<UploadFileType, string> = {
|
||||
all: "file",
|
||||
picture: "image/*",
|
||||
video: "video/*",
|
||||
audio: "audio/*",
|
||||
@ -23,7 +22,7 @@ export default function File({ question }: Props) {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [acceptedType, setAcceptedType] = useState<any>(
|
||||
UPLOAD_FILE_TYPES_MAP.all
|
||||
UPLOAD_FILE_TYPES_MAP.picture
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -3,10 +3,11 @@ import { produce } from "immer";
|
||||
import { nanoid } from "nanoid";
|
||||
import { create } from "zustand";
|
||||
import { devtools } from "zustand/middleware";
|
||||
import type { Moment } from "moment";
|
||||
|
||||
type Answer = {
|
||||
questionId: string;
|
||||
answer: string | string[];
|
||||
answer: string | string[] | Moment;
|
||||
};
|
||||
|
||||
type OwnVariant = {
|
||||
@ -40,7 +41,10 @@ function setProducedState<A extends string | { type: string; }>(
|
||||
useQuizViewStore.setState(state => produce(state, recipe), false, action);
|
||||
}
|
||||
|
||||
export const updateAnswer = (questionId: string, answer: string | string[]) => setProducedState(state => {
|
||||
export const updateAnswer = (
|
||||
questionId: string,
|
||||
answer: string | string[] | Moment
|
||||
) => setProducedState(state => {
|
||||
const index = state.answers.findIndex(answer => questionId === answer.questionId);
|
||||
|
||||
if (index < 0) {
|
||||
|
69
src/ui_kit/LabeledDatePicker.tsx
Normal file
69
src/ui_kit/LabeledDatePicker.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import CalendarIcon from "@icons/CalendarIcon";
|
||||
import { Typography, Box, SxProps, Theme, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import moment from "moment";
|
||||
|
||||
interface Props {
|
||||
label?: string;
|
||||
sx?: SxProps<Theme>;
|
||||
value?: moment.Moment;
|
||||
onChange?: (value: string | null) => void;
|
||||
}
|
||||
|
||||
export default function LabeledDatePicker({ label, value = moment(), onChange, sx }: Props) {
|
||||
const theme = useTheme();
|
||||
const upLg = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
{label && (
|
||||
<Typography
|
||||
sx={{
|
||||
fontWeight: 500,
|
||||
fontSize: "16px",
|
||||
lineHeight: "20px",
|
||||
color: "#4D4D4D",
|
||||
mb: "10px",
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</Typography>
|
||||
)}
|
||||
<DatePicker
|
||||
//@ts-ignore
|
||||
value={value._d}
|
||||
onChange={onChange}
|
||||
slots={{
|
||||
openPickerIcon: () => <CalendarIcon />,
|
||||
}}
|
||||
slotProps={{
|
||||
openPickerButton: {
|
||||
sx: {
|
||||
p: 0,
|
||||
},
|
||||
"data-cy": "open-datepicker",
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
"& .MuiInputBase-root": {
|
||||
backgroundColor: "#F2F3F7",
|
||||
borderRadius: "10px",
|
||||
pr: "22px",
|
||||
"& input": {
|
||||
py: "11px",
|
||||
pl: upLg ? "20px" : "13px",
|
||||
lineHeight: "19px",
|
||||
},
|
||||
"& fieldset": {
|
||||
borderColor: "#9A9AAF",
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
56
src/utils/hooks/useGetSettings.ts
Normal file
56
src/utils/hooks/useGetSettings.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react"
|
||||
import { useQuestionsStore } from "@stores/quizData/store";
|
||||
|
||||
import { getData } from "@api/quizRelase"
|
||||
|
||||
interface SettingsGetter {
|
||||
quizId: string
|
||||
}
|
||||
|
||||
export function useGetSettings(quizId: string) {
|
||||
|
||||
const [fetchState, setFetchState] = useState<"fetching" | "idle" | "all fetched">("idle")
|
||||
|
||||
useEffect(() => {
|
||||
async function get() {
|
||||
const data = await getData(quizId)
|
||||
//@ts-ignore
|
||||
const settings = data.settings
|
||||
const parseData = {
|
||||
settings: {
|
||||
fp: settings.fp,
|
||||
rep: settings.rep,
|
||||
name: settings.name,
|
||||
cfg: JSON.parse(settings?.cfg),
|
||||
lim: settings.lim,
|
||||
due: settings.due,
|
||||
delay: settings.delay,
|
||||
pausable: settings.pausable
|
||||
},
|
||||
//@ts-ignore
|
||||
items: data.items.map((item) => {
|
||||
const content = JSON.parse(item.c)
|
||||
return {
|
||||
description: item.desc,
|
||||
id: item.id,
|
||||
page: item.p,
|
||||
required: item.req,
|
||||
title: item.title,
|
||||
type: item.typ,
|
||||
content
|
||||
}
|
||||
}),
|
||||
//@ts-ignore
|
||||
cnt: data.cnt
|
||||
}
|
||||
//@ts-ignore
|
||||
useQuestionsStore.setState(parseData)
|
||||
}
|
||||
get()
|
||||
// const controller = new AbortController()
|
||||
|
||||
|
||||
}, [])
|
||||
return
|
||||
// return
|
||||
}
|
@ -1471,7 +1471,7 @@ dashdash@^1.12.0:
|
||||
dependencies:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
dayjs@^1.10.4, dayjs@^1.11.10:
|
||||
dayjs@^1.10.4:
|
||||
version "1.11.10"
|
||||
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.10.tgz#68acea85317a6e164457d6d6947564029a6a16a0"
|
||||
integrity sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==
|
||||
@ -2444,6 +2444,11 @@ minimist@^1.2.8:
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
||||
moment@^2.30.1:
|
||||
version "2.30.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
|
||||
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
|
||||
|
Loading…
Reference in New Issue
Block a user