diff --git a/package.json b/package.json index 274e4d00..60c2386d 100755 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "html-to-image": "^1.11.11", "immer": "^10.0.3", "jszip": "^3.10.1", + "moment": "^2.30.1", "nanoid": "^5.0.3", "notistack": "^3.0.1", "react": "^18.2.0", diff --git a/src/App.tsx b/src/App.tsx index 036f0f64..def36464 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -50,7 +50,7 @@ import { isAxiosError } from "axios"; import { useEffect, useLayoutEffect, useRef } from "react"; import RecoverPassword from "./pages/auth/RecoverPassword"; import OutdatedLink from "./pages/auth/OutdatedLink"; - +import { QuizAnswersPage } from "./pages/QuizAnswersPage/QuizAnswersPage"; export function useUserAccountFetcher({ onError, onNewUserAccount, @@ -238,6 +238,7 @@ export default function App() { } /> } /> } /> + } /> }> {routeslink.map((e, i) => ( ({ + url: process.env.REACT_APP_DOMAIN + `/squiz/results/getResults/${quizId}`, + method: "POST", + body: { page: 0, limit: 10, ...body }, + }); +} + +function deleteResult(resultId: number) { + return makeRequest({ + url: process.env.REACT_APP_DOMAIN + `/squiz/results/delete/${resultId}`, + body: {}, + method: "DELETE", + }); +} + +// export const obsolescenceResult = async (idResultArray: string[]) => { +// try { +// const response = await makeRequest({ +// url: process.env.REACT_APP_DOMAIN + `/squiz/result/seen`, +// body: { +// answers: idResultArray, +// }, +// method: "PATCH", +// }); +// return response; +// } catch (e) { +// console.log("ошибка", e); +// } +// }; + +function obsolescenceResult(idResultArray: string[]) { + return makeRequest({ + url: process.env.REACT_APP_DOMAIN + `/squiz/result/seen`, + body: { + answers: idResultArray, + }, + method: "PATCH", + }); +} + +function getAnswerResultList(resultId: number) { + return makeRequest({ + url: process.env.REACT_APP_DOMAIN + `/squiz/result/${resultId}`, + method: "GET", + }); +} + +function AnswerResultListEx(quizId: number, fromDate: string, toDate: string) { + return makeRequest({ + url: process.env.REACT_APP_DOMAIN + `/squiz/results/${quizId}/export`, + method: "POST", + body: { + to: new Date(toDate), + from: new Date(fromDate), + new: true, + }, + }); +} + +export const resultApi = { + getList: getResultList, + delete: deleteResult, + getAnswerList: getAnswerResultList, + export: AnswerResultListEx, + obsolescence: obsolescenceResult, +}; diff --git a/src/assets/icons/ClockWiseIcon.tsx b/src/assets/icons/ClockWiseIcon.tsx new file mode 100644 index 00000000..b43bb2cc --- /dev/null +++ b/src/assets/icons/ClockWiseIcon.tsx @@ -0,0 +1,14 @@ +export const ClockWiseIcon = () => ( + + + +); diff --git a/src/index.tsx b/src/index.tsx index 4f78a2a2..47fac85b 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -13,8 +13,10 @@ import "./index.css"; import lightTheme from "./utils/themes/light"; import { SWRConfig } from "swr"; import { BrowserRouter } from "react-router-dom"; +import moment from "moment"; dayjs.locale("ru"); +moment.locale("ru"); const localeText = ruRU.components.MuiLocalizationProvider.defaultProps.localeText; diff --git a/src/model/result/result.ts b/src/model/result/result.ts new file mode 100644 index 00000000..3f335462 --- /dev/null +++ b/src/model/result/result.ts @@ -0,0 +1,55 @@ +export interface RawResult { + total_count: number; + results: { + content: string; + id: number; + new: boolean; + created_at: string; + }; +} +export interface ResultContent { + name?: string; + email?: string; + phone?: string; + address?: string; + telegram?: string; + wechat?: string; + viber?: string; + vk?: string; + skype?: string; + whatsup?: string; + messenger?: string; +} + +export const defaultResultContent: ResultContent = { + name: "", + email: "", + phone: "", + address: "", + telegram: "", + wechat: "", + viber: "", + vk: "", + skype: "", + whatsup: "", + messenger: "", +}; + +export function rawResultToResult(rawResult): RawResult { + let content = defaultResultContent; + + try { + content = JSON.parse(rawResult.content); + console.log("Content", content); + } catch (error) { + console.warn( + "Cannot parse result from string, using default config", + error, + ); + } + + return { + ...rawResult, + content, + }; +} diff --git a/src/pages/Questions/Select.tsx b/src/pages/Questions/Select.tsx index 19d04a3a..cce14e37 100644 --- a/src/pages/Questions/Select.tsx +++ b/src/pages/Questions/Select.tsx @@ -85,7 +85,6 @@ export const Select = ({ borderRadius: "8px", "& .MuiOutlinedInput-notchedOutline": { border: `1px solid ${colorMain} !important`, - height: "48px", borderRadius: "10px", }, }} diff --git a/src/pages/QuizAnswersPage/CardAnswer.tsx b/src/pages/QuizAnswersPage/CardAnswer.tsx new file mode 100644 index 00000000..b599253e --- /dev/null +++ b/src/pages/QuizAnswersPage/CardAnswer.tsx @@ -0,0 +1,381 @@ +import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon"; +import { + Box, + Button, + IconButton, + Modal, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { FC, useState } from "react"; +import { ContactIcon } from "./icons/ContactIcon"; +import { MessageIcon } from "./icons/MessageIcon"; +import { PhoneIcon } from "./icons/PhoneIcon"; +import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; + +import homeImg from "./images/home.png"; +import videoFrame from "./images/videoFrame.png"; +import { EyeIcon } from "./icons/EyeIcon"; +import { deleteResult, obsolescenceResult } from "@root/results/actions"; +import { resultApi } from "@api/result"; +import { useQuizStore } from "@root/quizes/store"; +import { useQuestionsStore } from "@root/questions/store"; + +interface Props { + isNew: boolean; + idResult: string; + onClick: () => void; + dayResult: string; + timeResult: string; + name?: string; + phone?: string; + email?: string; + onLossNew?: (id: string) => void; +} + +export const CardAnswer = ({ + name, + phone, + email, + isNew = true, + idResult, + timeResult, + dayResult, + onLossNew, +}: Props) => { + const [isOpen, setIsOpen] = useState(false); + const [openDelete, setOpenDelete] = useState(false); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const [resultsAnswer, setResultsAnswer] = useState([]); + const { editQuizId } = useQuizStore(); + const { questions } = useQuestionsStore(); + console.log(questions); + // console.log(questions[0].backendId) + // console.log(questions[1].backendId) + // console.log(questions[2].backendId) + // console.log(questions[3].backendId) + return ( + { + obsolescenceResult(idResult, editQuizId); + }} + sx={{ + borderRadius: "12px", + maxWidth: isTablet ? "450px" : "auto", + width: "100%", + boxShadow: + "0px 2.767px 8.551px 0px rgba(210, 208, 225, 0.07), 0px 6.65px 20.549px 0px rgba(210, 208, 225, 0.10), 0px 12.522px 38.692px 0px rgba(210, 208, 225, 0.12), 0px 22.336px 69.019px 0px rgba(210, 208, 225, 0.14), 0px 41.778px 129.093px 0px rgba(210, 208, 225, 0.17), 0px 100px 309px 0px rgba(210, 208, 225, 0.24)", + }} + > + + + + + + {idResult} + + { + setIsOpen(!isOpen); + if (isOpen) { + let resAnswer = await resultApi.getAnswerList( + Number(idResult), + ); + resAnswer = resAnswer.filter((res) => res.Result !== true); + setResultsAnswer(resAnswer); + console.log("тут хранятся ответы", resAnswer); + } + }} + > + + + + + + {dayResult} в {timeResult} + + + + + {name && ( + + + + {name} + + + )} + {email && ( + + + + {email} + + + )} + {phone && ( + + + + {phone} + + + )} + + + {!isTablet && isNew && ( + + Новая + + )} + + + + {isTablet && isNew ? ( + <> + + Новая + + + setOpenDelete(true)}> + + + + + ) : ( + setOpenDelete(true)}> + + + )} + setOpenDelete(false)}> + + + Вы уверены, что хотите удалить этот результат? + + + + + + + + + + + {isOpen && ( + + + + Ответы + + + + + + {resultsAnswer.map((answer, id) => { + console.log(answer.QuizId); + + return ( + + + {id + 1}. + + + {answer.content} + + + ); + })} + + + + + + Результаты + + + + + + Заголовок + + Текст + + + + )} + + ); +}; diff --git a/src/pages/QuizAnswersPage/DeleteModal.tsx b/src/pages/QuizAnswersPage/DeleteModal.tsx new file mode 100644 index 00000000..bb828823 --- /dev/null +++ b/src/pages/QuizAnswersPage/DeleteModal.tsx @@ -0,0 +1,55 @@ +import { Box, Button, Modal, Typography } from "@mui/material"; + +interface Props { + openDelete: boolean; + setOpenDelete: (open: boolean) => void; +} + +export const DeleteModal = ({ openDelete, setOpenDelete }: Props) => { + return ( + setOpenDelete(false)}> + + + Вы уверены, что хотите удалить этот результат? + + + + + + + + ); +}; diff --git a/src/pages/QuizAnswersPage/QuizAnswersPage.tsx b/src/pages/QuizAnswersPage/QuizAnswersPage.tsx new file mode 100644 index 00000000..14012e2f --- /dev/null +++ b/src/pages/QuizAnswersPage/QuizAnswersPage.tsx @@ -0,0 +1,476 @@ +import { + Box, + Button, + IconButton, + Typography, + Select as MuiSelect, + MenuItem, + useTheme, + useMediaQuery, + Skeleton, + FormControl, + Select, +} from "@mui/material"; +import moment from "moment"; +import HeaderFull from "@ui_kit/Header/HeaderFull"; +import SectionWrapper from "@ui_kit/SectionWrapper"; +import { FC, useEffect, useState } from "react"; +import { FileExportIcon } from "./icons/FileExporIcon"; +import { UpdateIcon } from "./icons/UpdateIcon"; +// import { Select } from "../../pages/Questions/Select"; +import { CheckboxSelect } from "../../ui_kit/CheckboxSelect"; +import { CardAnswer } from "./CardAnswer"; +import { FilterIcon } from "./icons/FilterIcon"; +import { FilterModal } from "@ui_kit/Modal/FilterModal/FilterModal"; +import { ExportContactsModal } from "@ui_kit/Modal/ExportContactsModal"; + +import { resultApi } from "@api/result"; +import { useObsolescenceIdResult, useResultStore } from "@root/results/store"; +import { answerResultListExport, setResults } from "@root/results/actions"; + +import { quizApi } from "@api/quiz"; +import { setQuizes } from "@root/quizes/actions"; +import { useCurrentQuiz, useQuizes } from "@root/quizes/hooks"; +import { useQuizStore } from "@root/quizes/store"; +import { questionApi } from "@api/question"; +import { setQuestions } from "@root/questions/actions"; +import ArrowDown from "@icons/ArrowDownIcon"; + +const options = [ + { label: "Муром (1)", value: "option1" }, + { label: "Москва (1)", value: "option2" }, +]; + +const itemsTime = [ + "За все время", + "Сегодня", + "Вчера", + "Последние 7 дней", + "Последние 30 дней", + "Этот месяц", +]; + +// let lossDebouncer: null | ReturnType = null; +// let lossId: string[] = [] as string[]; +// +// const onLossNew = (id: string) => { +// //Если в массиве ещё нет такого id - добавляем +// if (!lossId.includes(id)) lossId.push(id); +// //Если таймер есть - сбрасываем +// if (typeof lossDebouncer === "number") clearTimeout(lossDebouncer); +// //Назначем новый таймер +// lossDebouncer = setTimeout(async () => { +// //стреляем на лишение новизны +// await obsolescenceResult(lossId); +// //сбрасываем массив +// lossId = []; +// }, 3000); +// }; + +const resetTime = (date: number) => { + console.log(date); + let a = Math.round(date / 86400) * 86400 - 97200; + console.log(a); + console.log(moment.unix(a).format("dddd, MMMM Do YYYY, h:mm:ss ")); + return moment.unix(a).format("dddd, MMMM Do YYYY, h:mm:ss "); +}; + +export const QuizAnswersPage: FC = () => { + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const isMobile = useMediaQuery(theme.breakpoints.down(600)); + + const [filterModalOpen, setFilterModalOpen] = useState(false); + const [exportContactsModalOpen, setExportContactsModalOpen] = + useState(false); + const [filterNew, setFilterNew] = useState("Все заявки"); + const [filterDate, setFilterDate] = useState("За все время"); + + const quizList = useQuizStore(); + const quiz = useCurrentQuiz(); + const { editQuizId } = useQuizStore(); + const { results } = useResultStore(); + const { total_count } = useResultStore(); + // const {idResultArray, addIdResult, clearIdResultArray} = useObsolescenceIdResult() + useEffect(() => { + const getData = async () => { + if (editQuizId !== null) { + const quizes = await quizApi.getList(); + setQuizes(quizes); + + const questions = await questionApi.getList({ quiz_id: editQuizId }); + setQuestions(questions); + + const result = await resultApi.getList(editQuizId); + setResults(result); + } + }; + getData(); + }, []); + + const DateDefinition = (result: string) => { + //определяем когда был получен результат - вчера, сегодня или число и месяц + let restime = new Date(result); + let timeCompleting = Date.parse(String(result)); + const timeNow = Date.now(); + let dayResult; + if (timeNow - timeCompleting < 86400000) { + dayResult = "Сегодня"; + } + if (172800000 > timeNow - timeCompleting > 86400000) { + dayResult = "Вчера"; + } else { + dayResult = restime.toLocaleDateString(); + } + return dayResult; + }; + const TimeDefinition = (result: string) => { + //достаём время + let timeResult = new Date(result).toLocaleTimeString().slice(0, -3); + return timeResult; + }; + + if (quiz === undefined) + return ( + + ); + + return ( + + + + + {quiz.name} + + + + + + Ответы на квиз + + + ({total_count}) + + + + + + { + // answerResultListExport(editQuizId) + setExportContactsModalOpen(true); + }} + sx={{ + width: "44px", + height: "44px", + borderRadius: "8px", + border: "1px solid #7E2AEA", + }} + > + + + { + const result = await resultApi.getList(editQuizId); + console.log(result); + setResults(result); + }} + > + + + + {isTablet && ( + setFilterModalOpen(true)} + sx={{ + background: "#fff", + width: "44px", + height: "44px", + borderRadius: "8px", + border: "1px solid #7E2AEA", + }} + > + + + )} + + + {!isTablet && ( + + + + + + + + {/* */} + + + + )} + + + + {!isTablet && ( + + + № заявки + + + Дата + + + Контакты + + + )} + + + {results.map((result) => { + const dataResult = new Date(result.created_at); + let dayResult = DateDefinition(result.created_at); + let timeResult = TimeDefinition(result.created_at); + return ( + + ); + })} + + + + setFilterModalOpen(false)} + filterNew={filterNew} + filterDate={filterDate} + setFilterNew={setFilterNew} + setFilterDate={setFilterDate} + /> + setExportContactsModalOpen(false)} + /> + + ); +}; diff --git a/src/pages/QuizAnswersPage/icons/ContactIcon.tsx b/src/pages/QuizAnswersPage/icons/ContactIcon.tsx new file mode 100644 index 00000000..549200a0 --- /dev/null +++ b/src/pages/QuizAnswersPage/icons/ContactIcon.tsx @@ -0,0 +1,23 @@ +export const ContactIcon = () => ( + + + + + + +); diff --git a/src/pages/QuizAnswersPage/icons/EyeIcon.tsx b/src/pages/QuizAnswersPage/icons/EyeIcon.tsx new file mode 100644 index 00000000..d4b309aa --- /dev/null +++ b/src/pages/QuizAnswersPage/icons/EyeIcon.tsx @@ -0,0 +1,16 @@ +export const EyeIcon = () => ( + + + + +); diff --git a/src/pages/QuizAnswersPage/icons/FileExporIcon.tsx b/src/pages/QuizAnswersPage/icons/FileExporIcon.tsx new file mode 100644 index 00000000..d3a5cf3e --- /dev/null +++ b/src/pages/QuizAnswersPage/icons/FileExporIcon.tsx @@ -0,0 +1,24 @@ +export const FileExportIcon = () => ( + + + + +); diff --git a/src/pages/QuizAnswersPage/icons/FilterIcon.tsx b/src/pages/QuizAnswersPage/icons/FilterIcon.tsx new file mode 100644 index 00000000..3518bdc9 --- /dev/null +++ b/src/pages/QuizAnswersPage/icons/FilterIcon.tsx @@ -0,0 +1,15 @@ +export const FilterIcon = () => ( + + + +); diff --git a/src/pages/QuizAnswersPage/icons/MessageIcon.tsx b/src/pages/QuizAnswersPage/icons/MessageIcon.tsx new file mode 100644 index 00000000..b95047ec --- /dev/null +++ b/src/pages/QuizAnswersPage/icons/MessageIcon.tsx @@ -0,0 +1,27 @@ +export const MessageIcon = () => ( + + + + + + +); diff --git a/src/pages/QuizAnswersPage/icons/PhoneIcon.tsx b/src/pages/QuizAnswersPage/icons/PhoneIcon.tsx new file mode 100644 index 00000000..5d762154 --- /dev/null +++ b/src/pages/QuizAnswersPage/icons/PhoneIcon.tsx @@ -0,0 +1,16 @@ +export const PhoneIcon = () => ( + + + + + +); diff --git a/src/pages/QuizAnswersPage/icons/UpdateIcon.tsx b/src/pages/QuizAnswersPage/icons/UpdateIcon.tsx new file mode 100644 index 00000000..752397f7 --- /dev/null +++ b/src/pages/QuizAnswersPage/icons/UpdateIcon.tsx @@ -0,0 +1,16 @@ +export const UpdateIcon = () => ( + + + +); diff --git a/src/pages/QuizAnswersPage/images/home.png b/src/pages/QuizAnswersPage/images/home.png new file mode 100644 index 00000000..f10be8a2 Binary files /dev/null and b/src/pages/QuizAnswersPage/images/home.png differ diff --git a/src/pages/QuizAnswersPage/images/videoFrame.png b/src/pages/QuizAnswersPage/images/videoFrame.png new file mode 100644 index 00000000..f6898f33 Binary files /dev/null and b/src/pages/QuizAnswersPage/images/videoFrame.png differ diff --git a/src/pages/createQuize/MyQuizzesFull.tsx b/src/pages/createQuize/MyQuizzesFull.tsx index 48286e12..23e7956a 100644 --- a/src/pages/createQuize/MyQuizzesFull.tsx +++ b/src/pages/createQuize/MyQuizzesFull.tsx @@ -34,7 +34,7 @@ export default function MyQuizzesFull({ return ( <> - + {quizes.length === 0 ? ( ) : ( diff --git a/src/pages/createQuize/QuizCard.tsx b/src/pages/createQuize/QuizCard.tsx index b45ee381..67e104b4 100755 --- a/src/pages/createQuize/QuizCard.tsx +++ b/src/pages/createQuize/QuizCard.tsx @@ -157,14 +157,18 @@ export default function QuizCard({ gap: isMobile ? "10px" : "20px", }} > - {/* */} + + + + + + + ); +}; diff --git a/src/ui_kit/Modal/FilterModal/FilterModal.tsx b/src/ui_kit/Modal/FilterModal/FilterModal.tsx new file mode 100644 index 00000000..b7b5f37f --- /dev/null +++ b/src/ui_kit/Modal/FilterModal/FilterModal.tsx @@ -0,0 +1,163 @@ +import { + Box, + Button, + Modal, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { CheckboxSelect } from "@ui_kit/CheckboxSelect"; +import { Select } from "../../../pages/Questions/Select"; +import { FC } from "react"; +import { ClockWiseIcon } from "@icons/ClockWiseIcon"; + +const options = [ + { label: "Муром (1)", value: "option1" }, + { label: "Москва (1)", value: "option2" }, +]; + +interface Iprops { + open: boolean; + handleClose: () => void; + filterNew: boolean; + filterDate: string; + setFilterNew: (a: string) => void; + setFilterDate: (a: string) => void; +} + +export const FilterModal: FC = ({ + open, + handleClose, + filterNew, + filterDate, + setFilterNew, + setFilterDate, +}) => { + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(650)); + + const style = { + position: "absolute" as "absolute", + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + width: "100%", + maxWidth: isMobile ? 343 : 620, + bgcolor: "background.paper", + boxShadow: 24, + borderRadius: "12px", + }; + + return ( + + + + + Настройте фильтры отображения + + + + + + + {/* */} + + + + + + + + + + + + + ); +}; diff --git a/yarn.lock b/yarn.lock index fb6d2079..54553512 100755 --- a/yarn.lock +++ b/yarn.lock @@ -7459,6 +7459,11 @@ mkdirp@~0.5.1: dependencies: minimist "^1.2.6" +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.0.0: version "2.0.0" resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz"