Merge remote-tracking branch 'origin/dev' into video-upload-modal
This commit is contained in:
commit
f8a1a21444
21621
package-lock.json
generated
21621
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,7 @@
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@frontend/kitui": "^1.0.82",
|
||||
"@frontend/squzanswerer": "^1.0.45",
|
||||
"@frontend/squzanswerer": "^1.0.51",
|
||||
"@mui/icons-material": "^5.10.14",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@mui/x-charts": "^6.19.5",
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { QuestionKeys } from "@/pages/IntegrationsPage/IntegrationsModal/types";
|
||||
import { makeRequest } from "@api/makeRequest";
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
|
||||
@ -11,24 +12,18 @@ const API_URL = `${process.env.REACT_APP_DOMAIN}/squiz/amocrm`;
|
||||
// получение информации об аккаунте
|
||||
|
||||
export type AccountResponse = {
|
||||
ID: number;
|
||||
AmoUserID: number;
|
||||
AccountID: string;
|
||||
AmoID: number;
|
||||
Name: string;
|
||||
Email: string;
|
||||
Role: string;
|
||||
Group: number;
|
||||
Deleted: boolean;
|
||||
CreatedAt: number;
|
||||
Subdomain: string;
|
||||
Amoiserid: number;
|
||||
Country: string;
|
||||
id: number;
|
||||
accountID: string;
|
||||
amoID: number;
|
||||
name: string;
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
subdomain: string;
|
||||
country: string;
|
||||
driveURL: string;
|
||||
};
|
||||
|
||||
export const getAccount = async (): Promise<
|
||||
[AccountResponse | null, string?]
|
||||
> => {
|
||||
export const getAccount = async (): Promise<[AccountResponse | null, string?]> => {
|
||||
try {
|
||||
const response = await makeRequest<void, AccountResponse>({
|
||||
method: "GET",
|
||||
@ -98,10 +93,7 @@ export type TagsResponse = {
|
||||
items: Tag[];
|
||||
};
|
||||
|
||||
export const getTags = async ({
|
||||
page,
|
||||
size,
|
||||
}: PaginationRequest): Promise<[TagsResponse | null, string?]> => {
|
||||
export const getTags = async ({ page, size }: PaginationRequest): Promise<[TagsResponse | null, string?]> => {
|
||||
try {
|
||||
const tagsResponse = await makeRequest<PaginationRequest, TagsResponse>({
|
||||
method: "GET",
|
||||
@ -117,18 +109,18 @@ export const getTags = async ({
|
||||
//получение списка пользователей
|
||||
|
||||
export type User = {
|
||||
ID: number;
|
||||
AccountID: string;
|
||||
AmoID: number;
|
||||
Name: string;
|
||||
Email: string;
|
||||
Role: string;
|
||||
Group: number;
|
||||
Deleted: boolean;
|
||||
CreatedAt: number;
|
||||
Subdomain: string;
|
||||
Amoiserid: number;
|
||||
Country: string;
|
||||
id: number;
|
||||
amoID: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: number;
|
||||
group: number;
|
||||
deleted: boolean;
|
||||
createdAt: string;
|
||||
amoUserID: number;
|
||||
|
||||
// Subdomain: string;
|
||||
// AccountID: string;
|
||||
};
|
||||
|
||||
export type UsersResponse = {
|
||||
@ -136,10 +128,7 @@ export type UsersResponse = {
|
||||
items: User[];
|
||||
};
|
||||
|
||||
export const getUsers = async ({
|
||||
page,
|
||||
size,
|
||||
}: PaginationRequest): Promise<[UsersResponse | null, string?]> => {
|
||||
export const getUsers = async ({ page, size }: PaginationRequest): Promise<[UsersResponse | null, string?]> => {
|
||||
try {
|
||||
const usersResponse = await makeRequest<PaginationRequest, UsersResponse>({
|
||||
method: "GET",
|
||||
@ -174,14 +163,9 @@ export const getSteps = async ({
|
||||
page,
|
||||
size,
|
||||
pipelineId,
|
||||
}: PaginationRequest & { pipelineId: number }): Promise<
|
||||
[StepsResponse | null, string?]
|
||||
> => {
|
||||
}: PaginationRequest & { pipelineId: number }): Promise<[StepsResponse | null, string?]> => {
|
||||
try {
|
||||
const stepsResponse = await makeRequest<
|
||||
PaginationRequest & { pipelineId: number },
|
||||
StepsResponse
|
||||
>({
|
||||
const stepsResponse = await makeRequest<PaginationRequest & { pipelineId: number }, StepsResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/steps?page=${page}&size=${size}&pipelineID=${pipelineId}`,
|
||||
});
|
||||
@ -209,15 +193,9 @@ export type PipelinesResponse = {
|
||||
items: Pipeline[];
|
||||
};
|
||||
|
||||
export const getPipelines = async ({
|
||||
page,
|
||||
size,
|
||||
}: PaginationRequest): Promise<[PipelinesResponse | null, string?]> => {
|
||||
export const getPipelines = async ({ page, size }: PaginationRequest): Promise<[PipelinesResponse | null, string?]> => {
|
||||
try {
|
||||
const pipelinesResponse = await makeRequest<
|
||||
PaginationRequest,
|
||||
PipelinesResponse
|
||||
>({
|
||||
const pipelinesResponse = await makeRequest<PaginationRequest, PipelinesResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/pipelines?page=${page}&size=${size}`,
|
||||
});
|
||||
@ -229,28 +207,23 @@ export const getPipelines = async ({
|
||||
};
|
||||
|
||||
//получение настроек интеграции
|
||||
export type QuestionID = Record<string, number>;
|
||||
|
||||
export type IntegrationRules = {
|
||||
ID: number;
|
||||
AccountID: number;
|
||||
QuizID: number;
|
||||
PerformerID: number;
|
||||
PipelineID: number;
|
||||
StepID: number;
|
||||
UTMs: number[];
|
||||
FieldsRule: {
|
||||
Lead: { QuestionID: number }[];
|
||||
Contact: { ContactRuleMap: string }[];
|
||||
Company: { QuestionID: number }[];
|
||||
Customer: { QuestionID: number }[];
|
||||
PerformerID?: number;
|
||||
FieldsRule: FieldsRule;
|
||||
TagsToAdd: {
|
||||
Lead: number[] | null;
|
||||
Contact: number[] | null;
|
||||
Company: number[] | null;
|
||||
Customer: number[] | null;
|
||||
};
|
||||
Deleted: boolean;
|
||||
CreatedAt: number;
|
||||
};
|
||||
export type FieldsRule = Record<Partial<QuestionKeys>, null | [{ QuestionID: QuestionID }]>;
|
||||
|
||||
export const getIntegrationRules = async (
|
||||
quizID: string,
|
||||
): Promise<[IntegrationRules | null, string?]> => {
|
||||
export const getIntegrationRules = async (quizID: string): Promise<[IntegrationRules | null, string?]> => {
|
||||
try {
|
||||
const settingsResponse = await makeRequest<void, IntegrationRules>({
|
||||
method: "GET",
|
||||
@ -271,7 +244,7 @@ export type IntegrationRulesUpdate = {
|
||||
PipelineID: number;
|
||||
StepID: number;
|
||||
Utms: number[];
|
||||
Fieldsrule: {
|
||||
FieldsRule: {
|
||||
Lead: { QuestionID: number }[];
|
||||
Contact: { ContactRuleMap: string }[];
|
||||
Company: { QuestionID: number }[];
|
||||
@ -281,7 +254,7 @@ export type IntegrationRulesUpdate = {
|
||||
|
||||
export const setIntegrationRules = async (
|
||||
quizID: string,
|
||||
settings: IntegrationRulesUpdate,
|
||||
settings: IntegrationRulesUpdate
|
||||
): Promise<[string | null, string?]> => {
|
||||
try {
|
||||
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
|
||||
@ -297,7 +270,7 @@ export const setIntegrationRules = async (
|
||||
};
|
||||
export const updateIntegrationRules = async (
|
||||
quizID: string,
|
||||
settings: IntegrationRulesUpdate,
|
||||
settings: IntegrationRulesUpdate
|
||||
): Promise<[string | null, string?]> => {
|
||||
try {
|
||||
const updateResponse = await makeRequest<IntegrationRulesUpdate, string>({
|
||||
@ -332,13 +305,10 @@ export type CustomFieldsResponse = {
|
||||
};
|
||||
|
||||
export const getCustomFields = async (
|
||||
pagination: PaginationRequest,
|
||||
pagination: PaginationRequest
|
||||
): Promise<[CustomFieldsResponse | null, string?]> => {
|
||||
try {
|
||||
const fieldsResponse = await makeRequest<
|
||||
PaginationRequest,
|
||||
CustomFieldsResponse
|
||||
>({
|
||||
const fieldsResponse = await makeRequest<PaginationRequest, CustomFieldsResponse>({
|
||||
method: "GET",
|
||||
url: `${API_URL}/fields?page=${pagination.page}&size=${pagination.size}`,
|
||||
});
|
||||
@ -362,4 +332,4 @@ export const removeAmoAccount = async (): Promise<[void | null, string?]> => {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
return [null, `Не удалось отвязать аккаунт. ${error}`];
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,177 +1,58 @@
|
||||
import * as React from "react";
|
||||
import { FC, useEffect, useMemo, useRef, useState } from "react";
|
||||
import Radio from "@mui/material/Radio";
|
||||
import RadioGroup from "@mui/material/RadioGroup";
|
||||
import FormControlLabel from "@mui/material/FormControlLabel";
|
||||
import Box from "@mui/material/Box";
|
||||
import { FC, useMemo } from "react";
|
||||
import CheckboxIcon from "@icons/Checkbox";
|
||||
import { Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import {
|
||||
getPipelines,
|
||||
getSteps,
|
||||
getTags,
|
||||
PaginationRequest,
|
||||
Pipeline,
|
||||
Step,
|
||||
Tag,
|
||||
} from "@api/integration";
|
||||
import type { TagQuestionObject } from "@/pages/IntegrationsPage/IntegrationsModal/AmoCRMModal"
|
||||
import { SelectChangeEvent, Typography, useTheme, Box, FormControlLabel, RadioGroup, Radio } from "@mui/material";
|
||||
import { MinifiedData, TagKeys } from "@/pages/IntegrationsPage/IntegrationsModal/types";
|
||||
|
||||
type CustomRadioGroupProps = {
|
||||
type?: string;
|
||||
selectedValue: string | null;
|
||||
setSelectedValue: (value: string | null) => void;
|
||||
pipelineId?: number | null;
|
||||
items: TagQuestionObject[]
|
||||
tags: Tag[]
|
||||
setTags?: (setValueFunc: (value: Tag[]) => Tag[]) => void;
|
||||
items: MinifiedData[] | [];
|
||||
selectedItemId?: string | null;
|
||||
setSelectedItem: (value: string | null) => void;
|
||||
handleScroll: () => void;
|
||||
activeScope?: TagKeys;
|
||||
};
|
||||
|
||||
const SIZE = 25;
|
||||
|
||||
export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
||||
type,
|
||||
selectedValue,
|
||||
setSelectedValue,
|
||||
pipelineId,
|
||||
items,
|
||||
tags = [],
|
||||
setTags,
|
||||
selectedItemId = "",
|
||||
setSelectedItem,
|
||||
handleScroll,
|
||||
activeScope,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(500));
|
||||
const currentItem = useMemo(() => {
|
||||
if (selectedItemId !== null && selectedItemId.length > 0) {
|
||||
return items.find((item) => item.id === selectedItemId) || null;
|
||||
}
|
||||
return null;
|
||||
}, [selectedItemId, items]);
|
||||
|
||||
const [currentValue, setCurrentValue] = useState<string | null>(
|
||||
selectedValue,
|
||||
);
|
||||
const [page, setPage] = useState(1);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const filteredItems = useMemo(() => {
|
||||
let newArray = items;
|
||||
if (activeScope !== undefined)
|
||||
newArray = newArray.filter((item) => {
|
||||
return item.entity === activeScope;
|
||||
});
|
||||
return newArray;
|
||||
}, items);
|
||||
|
||||
const [steps, setSteps] = useState<Step[]>([]);
|
||||
const [pipelines, setPipelines] = useState<Pipeline[]>([]);
|
||||
const [hasMoreItems, setHasMoreItems] = useState(true);
|
||||
const boxRef = useRef(null);
|
||||
|
||||
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
console.log("применился хенделчейндж")
|
||||
console.log((event.target as HTMLInputElement).value)
|
||||
setSelectedValue((event.target as HTMLInputElement).value);
|
||||
setCurrentValue((event.target as HTMLInputElement).value);
|
||||
};
|
||||
|
||||
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const onScroll = React.useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
||||
const scrollHeight = e.currentTarget.scrollHeight;
|
||||
const scrollTop = e.currentTarget.scrollTop;
|
||||
const clientHeight = e.currentTarget.clientHeight;
|
||||
const scrolledToBottom = scrollTop / (scrollHeight - clientHeight) > 0.9;
|
||||
|
||||
if (scrolledToBottom && !isLoading && hasMoreItems) {
|
||||
setPage((prevPage) => prevPage + 1);
|
||||
if (scrolledToBottom) {
|
||||
handleScroll();
|
||||
}
|
||||
};
|
||||
console.log(type)
|
||||
console.log(items)
|
||||
console.log(type === "typeQuestions" && items && items.length !== 0)
|
||||
|
||||
useEffect(() => {
|
||||
if (type === "typeTags" && hasMoreItems && setTags !== undefined) {
|
||||
setIsLoading(true);
|
||||
const pagination: PaginationRequest = {
|
||||
page: page,
|
||||
size: SIZE,
|
||||
};
|
||||
getTags(pagination).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
setTags((prevItems:Tag[]) => [...prevItems, ...response.items]);
|
||||
if (response.items.length < SIZE) {
|
||||
setHasMoreItems(false);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
if (type === "typeSteps" && hasMoreItems && pipelineId) {
|
||||
setIsLoading(true);
|
||||
const pagination: PaginationRequest & { pipelineId: number } = {
|
||||
page: page,
|
||||
size: SIZE,
|
||||
pipelineId: pipelineId,
|
||||
};
|
||||
getSteps(pagination).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
setSteps((prevItems) => [...prevItems, ...response.items]);
|
||||
if (response.items.length < SIZE) {
|
||||
setHasMoreItems(false);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
if (type === "typePipelines" && hasMoreItems) {
|
||||
setIsLoading(true);
|
||||
const pagination: PaginationRequest = {
|
||||
page: page,
|
||||
size: SIZE,
|
||||
};
|
||||
getPipelines(pagination).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
setPipelines((prevItems) => [...prevItems, ...response.items]);
|
||||
if (response.items.length < SIZE) {
|
||||
setHasMoreItems(false);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, [page, type, hasMoreItems, pipelineId]);
|
||||
}, []);
|
||||
|
||||
const formControlLabels = useMemo(() => {
|
||||
if (type === "typeTags" && tags && tags.length !== 0) {
|
||||
return tags.map((item) => (
|
||||
if (filteredItems.length !== 0) {
|
||||
return filteredItems.map((item) => (
|
||||
<FormControlLabel
|
||||
key={item.AmoID}
|
||||
sx={{
|
||||
padding: "15px",
|
||||
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
borderRadius: "12px",
|
||||
margin: 0,
|
||||
backgroundColor:
|
||||
currentValue === Number(item.AmoID)
|
||||
? theme.palette.background.default
|
||||
: theme.palette.common.white,
|
||||
}}
|
||||
value={Number(item.AmoID)}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={
|
||||
<CheckboxIcon
|
||||
checked
|
||||
isRounded
|
||||
color={theme.palette.brightPurple.main}
|
||||
/>
|
||||
}
|
||||
icon={<CheckboxIcon isRounded />}
|
||||
/>
|
||||
}
|
||||
label={
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography sx={{ color: `${item.Color}` }}>
|
||||
{item.Name}
|
||||
</Typography>
|
||||
<Typography>{item.Entity}</Typography>
|
||||
</Box>
|
||||
}
|
||||
labelPlacement={"start"}
|
||||
/>
|
||||
));
|
||||
}
|
||||
if (type === "typeSteps" && steps && steps.length !== 0) {
|
||||
return steps.map((step) => (
|
||||
<FormControlLabel
|
||||
key={step.ID}
|
||||
key={item.id}
|
||||
sx={{
|
||||
color: "black",
|
||||
padding: "15px",
|
||||
@ -181,11 +62,14 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
||||
borderRadius: "12px",
|
||||
margin: 0,
|
||||
backgroundColor:
|
||||
currentValue === step.Name
|
||||
? theme.palette.background.default
|
||||
: theme.palette.common.white,
|
||||
currentItem?.id === item.id ? theme.palette.background.default : theme.palette.common.white,
|
||||
"&.MuiFormControlLabel-root > .MuiTypography-root": {
|
||||
width: "200px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
}}
|
||||
value={step.AmoID}
|
||||
value={item.id}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={
|
||||
@ -198,87 +82,7 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
||||
icon={<CheckboxIcon isRounded />}
|
||||
/>
|
||||
}
|
||||
label={step.Name}
|
||||
labelPlacement={"start"}
|
||||
/>
|
||||
));
|
||||
}
|
||||
if (type === "typePipelines" && pipelines && pipelines.length !== 0) {
|
||||
return pipelines.map((pipeline) => (
|
||||
<FormControlLabel
|
||||
key={pipeline.ID}
|
||||
sx={{
|
||||
color: "black",
|
||||
padding: "15px",
|
||||
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
borderRadius: "12px",
|
||||
margin: 0,
|
||||
backgroundColor:
|
||||
currentValue === pipeline.Name
|
||||
? theme.palette.background.default
|
||||
: theme.palette.common.white,
|
||||
"&.MuiFormControlLabel-root > .MuiTypography-root": {
|
||||
width: "200px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
}
|
||||
}}
|
||||
value={pipeline.AmoID}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={
|
||||
<CheckboxIcon
|
||||
checked
|
||||
isRounded
|
||||
color={theme.palette.brightPurple.main}
|
||||
/>
|
||||
}
|
||||
icon={<CheckboxIcon isRounded />}
|
||||
/>
|
||||
}
|
||||
label={pipeline.Name}
|
||||
labelPlacement={"start"}
|
||||
/>
|
||||
));
|
||||
}
|
||||
if (type === "typeQuestions" && items && items.length !== 0) {
|
||||
return items.map(({ backendId, title }) => (
|
||||
<FormControlLabel
|
||||
key={backendId}
|
||||
sx={{
|
||||
color: "black",
|
||||
padding: "15px",
|
||||
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
borderRadius: "12px",
|
||||
margin: 0,
|
||||
backgroundColor:
|
||||
currentValue === Number(backendId)
|
||||
? theme.palette.background.default
|
||||
: theme.palette.common.white,
|
||||
"&.MuiFormControlLabel-root > .MuiTypography-root": {
|
||||
width: "200px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
}
|
||||
}}
|
||||
value={Number(backendId)}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={
|
||||
<CheckboxIcon
|
||||
checked
|
||||
isRounded
|
||||
color={theme.palette.brightPurple.main}
|
||||
/>
|
||||
}
|
||||
icon={<CheckboxIcon isRounded />}
|
||||
/>
|
||||
}
|
||||
label={title}
|
||||
label={item.title}
|
||||
labelPlacement={"start"}
|
||||
/>
|
||||
));
|
||||
@ -296,11 +100,10 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
||||
<Typography>Нет элементов</Typography>
|
||||
</Box>
|
||||
);
|
||||
}, [tags, steps, currentValue, type, pipelines]);
|
||||
}, [filteredItems, selectedItemId]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
ref={boxRef}
|
||||
onScroll={handleScroll}
|
||||
sx={{
|
||||
border: `1px solid ${theme.palette.grey2.main}`,
|
||||
@ -313,13 +116,9 @@ export const CustomRadioGroup: FC<CustomRadioGroupProps> = ({
|
||||
<RadioGroup
|
||||
aria-labelledby="demo-controlled-radio-buttons-group"
|
||||
name="controlled-radio-buttons-group"
|
||||
value={currentValue}
|
||||
onChange={(e) => {
|
||||
console.log("klick")
|
||||
console.log(e.target.value)
|
||||
console.log(typeof e.target.value)
|
||||
handleChange(e)
|
||||
}}
|
||||
value={selectedItemId}
|
||||
onChange={({ target }: SelectChangeEvent<string>) => setSelectedItem(target.value)}
|
||||
onScroll={onScroll}
|
||||
>
|
||||
{formControlLabels}
|
||||
</RadioGroup>
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
.MuiOutlinedInput-root .MuiOutlinedInput-notchedOutline {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.MuiPaper-root.MuiMenu-paper {
|
||||
padding-top: 50px;
|
||||
margin-top: -50px;
|
||||
border-radius: 28px;
|
||||
}
|
||||
|
||||
.MuiInputBase-root.MuiOutlinedInput-root {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.MuiInputBase-root.MuiOutlinedInput-root > div:first-child,
|
||||
.MuiInputBase-root.MuiOutlinedInput-root .MuiSelect-icon {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.MuiMenu-root.MuiModal-root {
|
||||
z-index: 0;
|
||||
}
|
||||
@ -1,101 +1,46 @@
|
||||
import {
|
||||
Avatar,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import Box from "@mui/material/Box";
|
||||
import * as React from "react";
|
||||
import { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import "./CustomSelect.css";
|
||||
import { FC, useCallback, useMemo, useRef, useState } from "react";
|
||||
import { Avatar, MenuItem, Select, SelectChangeEvent, Typography, useMediaQuery, useTheme, Box } from "@mui/material";
|
||||
import arrow_down from "../../assets/icons/arrow_down.svg";
|
||||
import { getUsers, PaginationRequest, User } from "@api/integration";
|
||||
import { MinifiedData } from "@/pages/IntegrationsPage/IntegrationsModal/types";
|
||||
|
||||
type CustomSelectProps = {
|
||||
selectedItem: string | null;
|
||||
items: MinifiedData[] | [];
|
||||
selectedItemId: string | null;
|
||||
setSelectedItem: (value: string | null) => void;
|
||||
type?: string;
|
||||
handleScroll: () => void;
|
||||
};
|
||||
|
||||
|
||||
export const CustomSelect: FC<CustomSelectProps> = ({
|
||||
selectedItem,
|
||||
setSelectedItem,
|
||||
type,
|
||||
}) => {
|
||||
export const CustomSelect: FC<CustomSelectProps> = ({ items, selectedItemId, setSelectedItem, handleScroll }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
const [opened, setOpened] = useState<boolean>(false);
|
||||
const [currentValue, setCurrentValue] = useState<string | null>(selectedItem);
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [page, setPage] = useState(1);
|
||||
const [hasMoreItems, setHasMoreItems] = useState(true);
|
||||
const SIZE = 25;
|
||||
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const selectWidth = ref.current ? ref.current.offsetWidth : undefined;
|
||||
|
||||
const [savedValue, setSavedValue] = useState<number | null>(null);
|
||||
|
||||
const onSelectItem = useCallback(
|
||||
(event: SelectChangeEvent<HTMLDivElement>) => {
|
||||
const newValue = event.target.value.toString();
|
||||
const selectedUser = users.find((user) => user.Name === newValue);
|
||||
if (selectedUser) {
|
||||
//для сохранения ID выбранного пользователя в стейт или конфиг...
|
||||
setSavedValue(selectedUser.ID);
|
||||
}
|
||||
setCurrentValue(newValue);
|
||||
setSelectedItem(newValue);
|
||||
},
|
||||
[setSelectedItem, setCurrentValue, setSavedValue, users],
|
||||
);
|
||||
const [opened, setOpened] = useState<boolean>(false);
|
||||
|
||||
const toggleOpened = useCallback(() => {
|
||||
setOpened((isOpened) => !isOpened);
|
||||
}, []);
|
||||
|
||||
const handleScroll = (e: React.UIEvent<HTMLDivElement>) => {
|
||||
const onScroll = useCallback((e: React.UIEvent<HTMLDivElement>) => {
|
||||
const scrollHeight = e.currentTarget.scrollHeight;
|
||||
const scrollTop = e.currentTarget.scrollTop;
|
||||
const clientHeight = e.currentTarget.clientHeight;
|
||||
const scrolledToBottom = scrollTop / (scrollHeight - clientHeight) > 0.9;
|
||||
|
||||
if (scrolledToBottom && !isLoading && hasMoreItems) {
|
||||
setPage((prevPage) => prevPage + 1);
|
||||
if (scrolledToBottom) {
|
||||
handleScroll();
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (type === "typeUsers" && hasMoreItems) {
|
||||
setIsLoading(true);
|
||||
const pagination: PaginationRequest = {
|
||||
page: page,
|
||||
size: SIZE,
|
||||
};
|
||||
getUsers(pagination).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
setUsers((prevItems) => [...prevItems, ...response.items]);
|
||||
if (response.items.length < SIZE) {
|
||||
setHasMoreItems(false);
|
||||
}
|
||||
}
|
||||
setIsLoading(false);
|
||||
});
|
||||
}
|
||||
}, [page, type, hasMoreItems]);
|
||||
const currentItem = useMemo(() => items.find((item) => item.id === selectedItemId) || null, [selectedItemId, items]);
|
||||
|
||||
const menuItems = useMemo(() => {
|
||||
if (type === "typeUsers" && users && users.length !== 0) {
|
||||
console.log(type)
|
||||
console.log(users)
|
||||
return users.map((user) => (
|
||||
if (items.length !== 0) {
|
||||
return items.map((item) => (
|
||||
<MenuItem
|
||||
key={user.ID}
|
||||
value={user.AmocrmID}
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
sx={{
|
||||
padding: "6px 0",
|
||||
zIndex: 2,
|
||||
@ -116,45 +61,36 @@ export const CustomSelect: FC<CustomSelectProps> = ({
|
||||
<Typography
|
||||
sx={{
|
||||
width: "33%",
|
||||
borderRight: isMobile
|
||||
? "none"
|
||||
: "1px solid rgba(154, 154, 175, 0.1)",
|
||||
borderRight: isMobile ? "none" : "1px solid rgba(154, 154, 175, 0.1)",
|
||||
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
|
||||
}}
|
||||
>
|
||||
{user.Name}
|
||||
{item.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
width: "33%",
|
||||
borderRight: isMobile
|
||||
? "none"
|
||||
: "1px solid rgba(154, 154, 175, 0.1)",
|
||||
borderRight: isMobile ? "none" : "1px solid rgba(154, 154, 175, 0.1)",
|
||||
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
|
||||
color: isMobile ? "#9A9AAF" : "#4D4D4D",
|
||||
}}
|
||||
>
|
||||
{user.Email}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
width: "33%",
|
||||
padding: isMobile ? "5px 0 5px 20px" : "10px 0 10px 20px",
|
||||
color: isMobile ? "#9A9AAF" : "#4D4D4D",
|
||||
}}
|
||||
>
|
||||
{user.Role}
|
||||
{item.subTitle}
|
||||
</Typography>
|
||||
</Box>
|
||||
</MenuItem>
|
||||
));
|
||||
}
|
||||
return (
|
||||
<MenuItem key={"-1"} disabled sx={{ padding: "12px", zIndex: 2 }}>
|
||||
<MenuItem
|
||||
key={"-1"}
|
||||
disabled
|
||||
sx={{ padding: "12px", zIndex: 2 }}
|
||||
>
|
||||
нет данных
|
||||
</MenuItem>
|
||||
);
|
||||
}, [users, type]);
|
||||
}, [items, selectedItemId]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
@ -165,10 +101,7 @@ export const CustomSelect: FC<CustomSelectProps> = ({
|
||||
width: "100%",
|
||||
height: "56px",
|
||||
padding: "5px",
|
||||
color:
|
||||
currentValue === null
|
||||
? theme.palette.grey2.main
|
||||
: theme.palette.brightPurple.main,
|
||||
color: currentItem === null ? theme.palette.grey2.main : theme.palette.brightPurple.main,
|
||||
border: `2px solid ${theme.palette.common.white}`,
|
||||
borderRadius: "30px",
|
||||
background: "#EFF0F5",
|
||||
@ -176,13 +109,15 @@ export const CustomSelect: FC<CustomSelectProps> = ({
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={() => ref.current?.click()}
|
||||
onClick={() => {
|
||||
if (ref.current !== null) ref.current?.click();
|
||||
}}
|
||||
>
|
||||
<Avatar sx={{ width: 46, height: 46, marginRight: 1 }} />
|
||||
<Typography
|
||||
sx={{
|
||||
marginLeft: isMobile ? "10px" : "20px",
|
||||
fontWeight: currentValue === null ? "400" : "500",
|
||||
fontWeight: currentItem === null ? "400" : "500",
|
||||
fontSize: isMobile ? "14px" : "16px",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
@ -190,7 +125,7 @@ export const CustomSelect: FC<CustomSelectProps> = ({
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{currentValue || "Выберите ответственного за сделку"}
|
||||
{currentItem?.title || "Выберите ответственного за сделку"}
|
||||
</Typography>
|
||||
<img
|
||||
src={arrow_down}
|
||||
@ -209,23 +144,39 @@ export const CustomSelect: FC<CustomSelectProps> = ({
|
||||
<Select
|
||||
ref={ref}
|
||||
className="select"
|
||||
value={""}
|
||||
value={selectedItemId || ""}
|
||||
open={opened}
|
||||
MenuProps={{
|
||||
disablePortal: true,
|
||||
PaperProps: {
|
||||
onScroll: handleScroll,
|
||||
onScroll: onScroll,
|
||||
style: {
|
||||
zIndex: 2,
|
||||
maxHeight: "300px",
|
||||
overflow: "auto",
|
||||
overflowX: "auto",
|
||||
maxWidth: selectWidth,
|
||||
paddingTop: "50px",
|
||||
marginTop: "-50px",
|
||||
borderRadius: "28px",
|
||||
},
|
||||
},
|
||||
}}
|
||||
sx={{}}
|
||||
onChange={onSelectItem}
|
||||
sx={{
|
||||
display: "block",
|
||||
"& .MuiSelect-select.MuiSelect-outlined.MuiInputBase-input": {
|
||||
display: "none",
|
||||
},
|
||||
"& .MuiSelect-icon": {
|
||||
display: "none",
|
||||
},
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
border: 0,
|
||||
},
|
||||
"& .MuiMenu-root.MuiModal-root": {
|
||||
zIndex: 0,
|
||||
},
|
||||
}}
|
||||
onChange={({ target }: SelectChangeEvent<string>) => setSelectedItem(target.value)}
|
||||
onClick={toggleOpened}
|
||||
>
|
||||
{menuItems}
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import type {
|
||||
QuizQuestionBase,
|
||||
QuestionBranchingRuleMain,
|
||||
} from "../model/questionTypes/shared";
|
||||
import { QuizQuestionBase } from "@frontend/squzanswerer";
|
||||
|
||||
export const QUIZ_QUESTION_BASE: Omit<QuizQuestionBase, "id" | "backendId"> = {
|
||||
quizId: 0,
|
||||
@ -21,7 +18,7 @@ export const QUIZ_QUESTION_BASE: Omit<QuizQuestionBase, "id" | "backendId"> = {
|
||||
},
|
||||
rule: {
|
||||
children: [],
|
||||
main: [] as QuestionBranchingRuleMain[],
|
||||
main: [],
|
||||
parentId: "",
|
||||
default: "",
|
||||
},
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { QuizQuestionDate } from "@frontend/squzanswerer";
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionDate } from "../model/questionTypes/date";
|
||||
|
||||
export const QUIZ_QUESTION_DATE: Omit<QuizQuestionDate, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "date",
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { QUIZ_QUESTION_DATE } from "./date";
|
||||
import { QUIZ_QUESTION_EMOJI } from "./emoji";
|
||||
import { QUIZ_QUESTION_FILE } from "./file";
|
||||
@ -12,11 +11,9 @@ import { QUIZ_QUESTION_TEXT } from "./text";
|
||||
import { QUIZ_QUESTION_VARIANT } from "./variant";
|
||||
import { QUIZ_QUESTION_VARIMG } from "./varimg";
|
||||
import { QUIZ_QUESTION_RESULT } from "./result";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
export const defaultQuestionByType: Record<
|
||||
QuestionType,
|
||||
Omit<AnyTypedQuizQuestion, "id" | "backendId">
|
||||
> = {
|
||||
export const defaultQuestionByType: Record<QuestionType, Omit<AnyTypedQuizQuestion, "id" | "backendId">> = {
|
||||
date: QUIZ_QUESTION_DATE,
|
||||
emoji: QUIZ_QUESTION_EMOJI,
|
||||
file: QUIZ_QUESTION_FILE,
|
||||
|
||||
@ -1,27 +1,26 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionEmoji } from "../model/questionTypes/emoji";
|
||||
import type { QuizQuestionEmoji } from "@frontend/squzanswerer";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const QUIZ_QUESTION_EMOJI: Omit<QuizQuestionEmoji, "id" | "backendId"> =
|
||||
{
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "emoji",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
multi: false,
|
||||
own: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
required: false,
|
||||
variants: [
|
||||
{
|
||||
id: nanoid(),
|
||||
answer: "",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
originalImageUrl: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
export const QUIZ_QUESTION_EMOJI: Omit<QuizQuestionEmoji, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "emoji",
|
||||
content: {
|
||||
...QUIZ_QUESTION_BASE.content,
|
||||
multi: false,
|
||||
own: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
required: false,
|
||||
variants: [
|
||||
{
|
||||
id: nanoid(),
|
||||
answer: "",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
originalImageUrl: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionFile } from "../model/questionTypes/file";
|
||||
import type { QuizQuestionFile } from "@frontend/squzanswerer";
|
||||
|
||||
export const QUIZ_QUESTION_FILE: Omit<QuizQuestionFile, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionImages } from "../model/questionTypes/images";
|
||||
import type { QuizQuestionImages } from "@frontend/squzanswerer";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const QUIZ_QUESTION_IMAGES: Omit<
|
||||
QuizQuestionImages,
|
||||
"id" | "backendId"
|
||||
> = {
|
||||
export const QUIZ_QUESTION_IMAGES: Omit<QuizQuestionImages, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "images",
|
||||
content: {
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionNumber } from "../model/questionTypes/number";
|
||||
import type { QuizQuestionNumber } from "@frontend/squzanswerer";
|
||||
|
||||
export const QUIZ_QUESTION_NUMBER: Omit<
|
||||
QuizQuestionNumber,
|
||||
"id" | "backendId"
|
||||
> = {
|
||||
export const QUIZ_QUESTION_NUMBER: Omit<QuizQuestionNumber, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "number",
|
||||
content: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionPage } from "../model/questionTypes/page";
|
||||
import type { QuizQuestionPage } from "@frontend/squzanswerer";
|
||||
|
||||
export const QUIZ_QUESTION_PAGE: Omit<QuizQuestionPage, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
@ -13,5 +13,6 @@ export const QUIZ_QUESTION_PAGE: Omit<QuizQuestionPage, "id" | "backendId"> = {
|
||||
picture: "",
|
||||
originalPicture: "",
|
||||
video: "",
|
||||
useImage: true,
|
||||
},
|
||||
};
|
||||
|
||||
@ -1,290 +0,0 @@
|
||||
import type { AnyTypedQuizQuestion } from "../model/questionTypes/shared";
|
||||
|
||||
export const QUESTIONS_DUMMY: AnyTypedQuizQuestion[] = [
|
||||
{
|
||||
id: "1",
|
||||
title: "",
|
||||
type: "variant",
|
||||
expanded: true,
|
||||
required: false,
|
||||
deleted: false,
|
||||
deleteTimeoutId: 0,
|
||||
backendId: 1111,
|
||||
description: "",
|
||||
openedModalSettings: false,
|
||||
page: 1,
|
||||
quizId: 1,
|
||||
content: {
|
||||
hint: {
|
||||
text: "",
|
||||
video: "",
|
||||
},
|
||||
rule: {
|
||||
main: [],
|
||||
default: "1",
|
||||
},
|
||||
back: "",
|
||||
originalBack: "",
|
||||
autofill: false,
|
||||
largeCheck: false,
|
||||
multi: false,
|
||||
own: false,
|
||||
innerNameCheck: false,
|
||||
required: false,
|
||||
innerName: "",
|
||||
variants: [],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Вы идёте в школу",
|
||||
type: "page",
|
||||
expanded: true,
|
||||
required: false,
|
||||
deleted: false,
|
||||
deleteTimeoutId: 0,
|
||||
backendId: 1112,
|
||||
description: "",
|
||||
openedModalSettings: false,
|
||||
page: 1,
|
||||
quizId: 1,
|
||||
content: {
|
||||
hint: {
|
||||
text: "",
|
||||
video: "",
|
||||
},
|
||||
rule: {
|
||||
main: [],
|
||||
default: "1",
|
||||
},
|
||||
back: "",
|
||||
originalBack: "",
|
||||
autofill: false,
|
||||
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
text: "",
|
||||
picture: "",
|
||||
originalPicture: "",
|
||||
video: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "Вы немного опоздали, как поступите?",
|
||||
type: "variant",
|
||||
expanded: true,
|
||||
required: true,
|
||||
deleted: false,
|
||||
deleteTimeoutId: 0,
|
||||
backendId: 1113,
|
||||
description: "",
|
||||
openedModalSettings: false,
|
||||
page: 1,
|
||||
quizId: 1,
|
||||
content: {
|
||||
hint: {
|
||||
text: "",
|
||||
video: "",
|
||||
},
|
||||
rule: {
|
||||
main: [],
|
||||
default: "2",
|
||||
},
|
||||
back: "",
|
||||
originalBack: "",
|
||||
autofill: false,
|
||||
largeCheck: false,
|
||||
multi: false,
|
||||
own: false,
|
||||
innerNameCheck: false,
|
||||
required: false,
|
||||
innerName: "",
|
||||
variants: [
|
||||
{
|
||||
id: "answer1",
|
||||
answer: "Извинюсь за опоздание и займу своё место",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
},
|
||||
{
|
||||
id: "answer2",
|
||||
answer:
|
||||
"Вынесу дверь с ноги и распинав всех направлюсь к месту у розетки",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
},
|
||||
{
|
||||
id: "answer3",
|
||||
answer: "Влечу в кабинет, пробегу его и выпрыгну в окно",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
title: "Вы открываете дверь кабинета, приготовившись исполнить задуманное",
|
||||
type: "page",
|
||||
expanded: true,
|
||||
required: false,
|
||||
deleted: false,
|
||||
deleteTimeoutId: 0,
|
||||
backendId: 1114,
|
||||
description: "",
|
||||
openedModalSettings: false,
|
||||
page: 1,
|
||||
quizId: 1,
|
||||
content: {
|
||||
hint: {
|
||||
text: "",
|
||||
video: "",
|
||||
},
|
||||
rule: {
|
||||
main: [
|
||||
//момент выбора куда мы пойдём
|
||||
{
|
||||
next: "7",
|
||||
or: true,
|
||||
rules: [
|
||||
{
|
||||
question: "2",
|
||||
answers: ["answer1"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
next: "6",
|
||||
or: true,
|
||||
rules: [
|
||||
{
|
||||
question: "2",
|
||||
answers: ["answer2"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
next: "5",
|
||||
or: true,
|
||||
rules: [
|
||||
{
|
||||
question: "2",
|
||||
answers: ["answer3"],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
default: "2",
|
||||
},
|
||||
back: "",
|
||||
originalBack: "",
|
||||
autofill: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
text: "",
|
||||
picture: "",
|
||||
originalPicture: "",
|
||||
video: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
title: "Вы умерли",
|
||||
type: "page",
|
||||
expanded: true,
|
||||
required: false,
|
||||
deleted: false,
|
||||
deleteTimeoutId: 0,
|
||||
backendId: 1115,
|
||||
description: "",
|
||||
openedModalSettings: false,
|
||||
page: 1,
|
||||
quizId: 1,
|
||||
content: {
|
||||
hint: {
|
||||
text: "",
|
||||
video: "",
|
||||
},
|
||||
rule: {
|
||||
main: [],
|
||||
default: "",
|
||||
},
|
||||
back: "",
|
||||
originalBack: "",
|
||||
autofill: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
text: "",
|
||||
picture: "",
|
||||
originalPicture: "",
|
||||
video: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
title: "Вас вызвали к директору",
|
||||
type: "page",
|
||||
expanded: true,
|
||||
required: false,
|
||||
deleted: false,
|
||||
deleteTimeoutId: 0,
|
||||
backendId: 1116,
|
||||
description: "",
|
||||
openedModalSettings: false,
|
||||
page: 1,
|
||||
quizId: 1,
|
||||
content: {
|
||||
hint: {
|
||||
text: "",
|
||||
video: "",
|
||||
},
|
||||
rule: {
|
||||
main: [],
|
||||
default: "",
|
||||
},
|
||||
back: "",
|
||||
originalBack: "",
|
||||
autofill: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
text: "",
|
||||
picture: "",
|
||||
originalPicture: "",
|
||||
video: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "7",
|
||||
title: "Вы получили отлично",
|
||||
type: "page",
|
||||
expanded: true,
|
||||
required: false,
|
||||
deleted: false,
|
||||
deleteTimeoutId: 0,
|
||||
backendId: 1117,
|
||||
description: "",
|
||||
openedModalSettings: false,
|
||||
page: 1,
|
||||
quizId: 1,
|
||||
content: {
|
||||
hint: {
|
||||
text: "",
|
||||
video: "",
|
||||
},
|
||||
rule: {
|
||||
main: [],
|
||||
default: "",
|
||||
},
|
||||
back: "",
|
||||
originalBack: "",
|
||||
autofill: false,
|
||||
innerNameCheck: false,
|
||||
innerName: "",
|
||||
text: "",
|
||||
picture: "",
|
||||
originalPicture: "",
|
||||
video: "",
|
||||
},
|
||||
},
|
||||
];
|
||||
@ -1,11 +1,8 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionRating } from "../model/questionTypes/rating";
|
||||
import type { QuizQuestionRating } from "@frontend/squzanswerer";
|
||||
|
||||
export const QUIZ_QUESTION_RATING: Omit<
|
||||
QuizQuestionRating,
|
||||
"id" | "backendId"
|
||||
> = {
|
||||
export const QUIZ_QUESTION_RATING: Omit<QuizQuestionRating, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "rating",
|
||||
content: {
|
||||
|
||||
@ -1,12 +1,8 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionResult } from "../model/questionTypes/result";
|
||||
import { nanoid } from "nanoid";
|
||||
import type { QuizQuestionResult } from "@frontend/squzanswerer";
|
||||
|
||||
export const QUIZ_QUESTION_RESULT: Omit<
|
||||
QuizQuestionResult,
|
||||
"id" | "backendId"
|
||||
> = {
|
||||
export const QUIZ_QUESTION_RESULT: Omit<QuizQuestionResult, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "result",
|
||||
content: {
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionSelect } from "../model/questionTypes/select";
|
||||
import type { QuizQuestionSelect } from "@frontend/squzanswerer";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const QUIZ_QUESTION_SELECT: Omit<
|
||||
QuizQuestionSelect,
|
||||
"id" | "backendId"
|
||||
> = {
|
||||
export const QUIZ_QUESTION_SELECT: Omit<QuizQuestionSelect, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "select",
|
||||
content: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionText } from "../model/questionTypes/text";
|
||||
import type { QuizQuestionText } from "@frontend/squzanswerer";
|
||||
|
||||
export const QUIZ_QUESTION_TEXT: Omit<QuizQuestionText, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionVariant } from "../model/questionTypes/variant";
|
||||
import type { QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const QUIZ_QUESTION_VARIANT: Omit<
|
||||
QuizQuestionVariant,
|
||||
"id" | "backendId"
|
||||
> = {
|
||||
export const QUIZ_QUESTION_VARIANT: Omit<QuizQuestionVariant, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "variant",
|
||||
content: {
|
||||
|
||||
@ -1,12 +1,9 @@
|
||||
import { QUIZ_QUESTION_BASE } from "./base";
|
||||
|
||||
import type { QuizQuestionVarImg } from "../model/questionTypes/varimg";
|
||||
import type { QuizQuestionVarImg } from "@frontend/squzanswerer";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const QUIZ_QUESTION_VARIMG: Omit<
|
||||
QuizQuestionVarImg,
|
||||
"id" | "backendId"
|
||||
> = {
|
||||
export const QUIZ_QUESTION_VARIMG: Omit<QuizQuestionVarImg, "id" | "backendId"> = {
|
||||
...QUIZ_QUESTION_BASE,
|
||||
type: "varimg",
|
||||
content: {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { QuestionType } from "./question";
|
||||
|
||||
export interface EditQuestionRequest {
|
||||
@ -15,9 +15,7 @@ export interface EditQuestionResponse {
|
||||
updated: number;
|
||||
}
|
||||
|
||||
export function questionToEditQuestionRequest(
|
||||
question: AnyTypedQuizQuestion,
|
||||
): EditQuestionRequest {
|
||||
export function questionToEditQuestionRequest(question: AnyTypedQuizQuestion): EditQuestionRequest {
|
||||
return {
|
||||
id: question.backendId,
|
||||
title: question.title,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { defaultQuestionByType } from "../../constants/default";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
@ -44,21 +44,15 @@ export interface RawQuestion {
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export function rawQuestionToQuestion(
|
||||
rawQuestion: RawQuestion,
|
||||
): AnyTypedQuizQuestion {
|
||||
export function rawQuestionToQuestion(rawQuestion: RawQuestion): AnyTypedQuizQuestion {
|
||||
let content = defaultQuestionByType[rawQuestion.type].content;
|
||||
const frontId = nanoid();
|
||||
|
||||
try {
|
||||
content = JSON.parse(rawQuestion.content);
|
||||
if (content.id.length === 0 || content.id.length === undefined)
|
||||
content.id = frontId;
|
||||
if (content.id.length === 0 || content.id.length === undefined) content.id = frontId;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"Cannot parse question content from string, using default content",
|
||||
error,
|
||||
);
|
||||
console.warn("Cannot parse question content from string, using default content", error);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared";
|
||||
|
||||
export interface QuizQuestionDate extends QuizQuestionBase {
|
||||
type: "date";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
dateRange: boolean;
|
||||
time: boolean;
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
};
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import type {
|
||||
QuizQuestionBase,
|
||||
QuestionVariant,
|
||||
QuestionHint,
|
||||
PreviewRule,
|
||||
} from "./shared";
|
||||
|
||||
export interface QuizQuestionEmoji extends QuizQuestionBase {
|
||||
type: "emoji";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Можно несколько" */
|
||||
multi: boolean;
|
||||
/** Чекбокс "Вариант "свой ответ"" */
|
||||
own: boolean;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
variants: QuestionVariant[];
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
};
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared";
|
||||
|
||||
export const UPLOAD_FILE_TYPES_MAP = {
|
||||
picture: "Изображения",
|
||||
video: "Видео",
|
||||
audio: "Аудио",
|
||||
document: "Документ",
|
||||
} as const;
|
||||
|
||||
export type UploadFileType = keyof typeof UPLOAD_FILE_TYPES_MAP;
|
||||
|
||||
export interface QuizQuestionFile extends QuizQuestionBase {
|
||||
type: "file";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
/** Чекбокс "Автозаполнение адреса" */
|
||||
autofill: boolean;
|
||||
type: UploadFileType;
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
};
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import type {
|
||||
QuestionHint,
|
||||
QuestionVariant,
|
||||
QuizQuestionBase,
|
||||
PreviewRule,
|
||||
} from "./shared";
|
||||
|
||||
export interface QuizQuestionImages extends QuizQuestionBase {
|
||||
type: "images";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Вариант "свой ответ"" */
|
||||
own: boolean;
|
||||
/** Чекбокс "Можно несколько" */
|
||||
multi: boolean;
|
||||
/** Пропорции */
|
||||
xy: "1:1" | "1:2" | "2:1";
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
/** Чекбокс "Большие картинки" */
|
||||
large: boolean;
|
||||
/** Форма */
|
||||
format: "carousel" | "masonry";
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
/** Варианты (картинки) */
|
||||
variants: QuestionVariant[];
|
||||
hint: QuestionHint;
|
||||
rule: QuestionBranchingRule;
|
||||
back: string | null;
|
||||
originalBack: string | null;
|
||||
autofill: boolean;
|
||||
largeCheck: boolean;
|
||||
};
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared";
|
||||
|
||||
export interface QuizQuestionNumber extends QuizQuestionBase {
|
||||
type: "number";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
/** Диапазон */
|
||||
range: string;
|
||||
/** Начальное значение */
|
||||
start: number;
|
||||
/** Начальное значение */
|
||||
defaultValue: number;
|
||||
/** Шаг */
|
||||
step: number;
|
||||
steps: number;
|
||||
/** Чекбокс "Выбор диапазона (два ползунка)" */
|
||||
chooseRange: boolean;
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
form: "star" | "trophie" | "flag" | "heart" | "like" | "bubble" | "hashtag";
|
||||
};
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared";
|
||||
|
||||
export interface QuizQuestionPage extends QuizQuestionBase {
|
||||
type: "page";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
text: string;
|
||||
picture: string;
|
||||
originalPicture: string;
|
||||
useImage: boolean;
|
||||
video: string | null;
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
};
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared";
|
||||
|
||||
export interface QuizQuestionRating extends QuizQuestionBase {
|
||||
type: "rating";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
steps: number;
|
||||
ratingExpanded: boolean;
|
||||
/** Форма иконки */
|
||||
form: string;
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
/** Позитивное описание рейтинга */
|
||||
ratingPositiveDescription: string;
|
||||
/** Негативное описание рейтинга */
|
||||
ratingNegativeDescription: string;
|
||||
};
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
import type { QuizQuestionBase, QuestionBranchingRule, QuestionHint } from "./shared";
|
||||
|
||||
export interface QuizQuestionResult extends QuizQuestionBase {
|
||||
type: "result";
|
||||
content: {
|
||||
id: string;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
video: string | null;
|
||||
innerName: string;
|
||||
text: string;
|
||||
price: [number] | [number, number];
|
||||
useImage: boolean;
|
||||
rule: QuestionBranchingRule;
|
||||
hint: QuestionHint;
|
||||
autofill: boolean;
|
||||
usage: boolean;
|
||||
redirect: string;
|
||||
};
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import type {
|
||||
QuizQuestionBase,
|
||||
QuestionVariant,
|
||||
QuestionHint,
|
||||
PreviewRule,
|
||||
} from "./shared";
|
||||
|
||||
export interface QuizQuestionSelect extends QuizQuestionBase {
|
||||
type: "select";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Можно несколько" */
|
||||
multi: boolean;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
/** Поле "Текст в выпадающем списке" */
|
||||
default: string;
|
||||
variants: QuestionVariant[];
|
||||
rule: PreviewRule;
|
||||
hint: QuestionHint;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
};
|
||||
}
|
||||
@ -1,77 +1,6 @@
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import type { QuizQuestionDate } from "./date";
|
||||
import type { QuizQuestionEmoji } from "./emoji";
|
||||
import type { QuizQuestionFile } from "./file";
|
||||
import type { QuizQuestionImages } from "./images";
|
||||
import type { QuizQuestionNumber } from "./number";
|
||||
import type { QuizQuestionPage } from "./page";
|
||||
import type { QuizQuestionRating } from "./rating";
|
||||
import type { QuizQuestionSelect } from "./select";
|
||||
import type { QuizQuestionText } from "./text";
|
||||
import type { QuizQuestionVariant } from "./variant";
|
||||
import type { QuizQuestionVarImg } from "./varimg";
|
||||
import type { QuizQuestionResult } from "./result";
|
||||
import { QuestionBranchingRuleMain, QuestionVariant } from "@frontend/squzanswerer";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export interface QuestionBranchingRuleMain {
|
||||
next: string;
|
||||
or: boolean;
|
||||
rules: {
|
||||
question: string; //id родителя (пока что)
|
||||
answers: string[];
|
||||
}[];
|
||||
}
|
||||
export interface QuestionBranchingRule {
|
||||
children: string[];
|
||||
//список условий
|
||||
main: QuestionBranchingRuleMain[];
|
||||
parentId: string | null | "root";
|
||||
default: string;
|
||||
}
|
||||
|
||||
export interface QuestionHint {
|
||||
/** Текст подсказки */
|
||||
text: string;
|
||||
/** URL видео подсказки */
|
||||
video: string;
|
||||
}
|
||||
|
||||
export type QuestionVariant = {
|
||||
id: string;
|
||||
/** Текст */
|
||||
answer: string;
|
||||
/** Текст подсказки */
|
||||
hints: string;
|
||||
/** Дополнительное поле для текста, emoji, ссылки на картинку */
|
||||
extendedText: string;
|
||||
/** Оригинал изображения (до кропа) */
|
||||
originalImageUrl: string;
|
||||
};
|
||||
|
||||
export interface QuizQuestionBase {
|
||||
backendId: number;
|
||||
/** Stable id, generated on client */
|
||||
id: string;
|
||||
quizId: number;
|
||||
title: string;
|
||||
description: string;
|
||||
page: number;
|
||||
type?: QuestionType | null;
|
||||
expanded: boolean;
|
||||
openedModalSettings: boolean;
|
||||
deleted: boolean;
|
||||
deleteTimeoutId: number;
|
||||
required: boolean;
|
||||
content: {
|
||||
id: string;
|
||||
hint: QuestionHint;
|
||||
rule: QuestionBranchingRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UntypedQuizQuestion {
|
||||
type: null;
|
||||
id: string;
|
||||
@ -82,50 +11,25 @@ export interface UntypedQuizQuestion {
|
||||
deleted: boolean;
|
||||
}
|
||||
|
||||
export type AnyTypedQuizQuestion =
|
||||
| QuizQuestionVariant
|
||||
| QuizQuestionImages
|
||||
| QuizQuestionVarImg
|
||||
| QuizQuestionEmoji
|
||||
| QuizQuestionText
|
||||
| QuizQuestionSelect
|
||||
| QuizQuestionDate
|
||||
| QuizQuestionNumber
|
||||
| QuizQuestionFile
|
||||
| QuizQuestionPage
|
||||
| QuizQuestionRating
|
||||
| QuizQuestionResult;
|
||||
|
||||
export type AllTypesQuestion =
|
||||
| AnyTypedQuizQuestion
|
||||
| UntypedQuizQuestion;
|
||||
|
||||
type FilterQuestionsWithVariants<T> = T extends {
|
||||
content: { variants: QuestionVariant[] };
|
||||
export function createBranchingRuleMain(targetId: string, parentId: string): QuestionBranchingRuleMain {
|
||||
return {
|
||||
next: targetId,
|
||||
or: false,
|
||||
rules: [
|
||||
{
|
||||
question: parentId,
|
||||
answers: [],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
? T
|
||||
: never;
|
||||
|
||||
export type QuizQuestionsWithVariants =
|
||||
FilterQuestionsWithVariants<AnyTypedQuizQuestion>;
|
||||
|
||||
export const createBranchingRuleMain: (
|
||||
targetId: string,
|
||||
parentId: string,
|
||||
) => QuestionBranchingRuleMain = (targetId, parentId) => ({
|
||||
next: targetId,
|
||||
or: false,
|
||||
rules: [
|
||||
{
|
||||
question: parentId,
|
||||
answers: [] as string[],
|
||||
},
|
||||
],
|
||||
});
|
||||
export const createQuestionVariant: () => QuestionVariant = () => ({
|
||||
id: nanoid(),
|
||||
answer: "",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
originalImageUrl: "",
|
||||
});
|
||||
export function createQuestionVariant(): QuestionVariant {
|
||||
return {
|
||||
id: nanoid(),
|
||||
answer: "",
|
||||
extendedText: "",
|
||||
hints: "",
|
||||
originalImageUrl: "",
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
import type { PreviewRule, QuestionHint, QuizQuestionBase } from "./shared";
|
||||
|
||||
export interface QuizQuestionText extends QuizQuestionBase {
|
||||
type: "text";
|
||||
content: {
|
||||
id: string;
|
||||
placeholder: string;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
/** Чекбокс "Автозаполнение адреса" */
|
||||
autofill: boolean;
|
||||
answerType: "single" | "multi" | "numberOnly";
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
onlyNumbers: boolean;
|
||||
};
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import type {
|
||||
QuizQuestionBase,
|
||||
QuestionVariant,
|
||||
QuestionHint,
|
||||
PreviewRule,
|
||||
} from "./shared";
|
||||
|
||||
export interface QuizQuestionVariant extends QuizQuestionBase {
|
||||
type: "variant";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Длинный текстовый ответ" */
|
||||
largeCheck: boolean;
|
||||
/** Чекбокс "Можно несколько" */
|
||||
multi: boolean;
|
||||
/** Чекбокс "Вариант "свой ответ"" */
|
||||
own: boolean;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
/** Варианты ответов */
|
||||
variants: QuestionVariant[];
|
||||
hint: QuestionHint;
|
||||
rule: PreviewRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
};
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import type {
|
||||
QuestionHint,
|
||||
QuestionVariant,
|
||||
QuizQuestionBase,
|
||||
PreviewRule,
|
||||
} from "./shared";
|
||||
|
||||
export interface QuizQuestionVarImg extends QuizQuestionBase {
|
||||
type: "varimg";
|
||||
content: {
|
||||
id: string;
|
||||
/** Чекбокс "Вариант "свой ответ"" */
|
||||
own: boolean;
|
||||
/** Чекбокс "Внутреннее название вопроса" */
|
||||
innerNameCheck: boolean;
|
||||
/** Поле "Внутреннее название вопроса" */
|
||||
innerName: string;
|
||||
/** Чекбокс "Необязательный вопрос" */
|
||||
required: boolean;
|
||||
variants: QuestionVariant[];
|
||||
hint: QuestionHint;
|
||||
rule: QuestionBranchingRule;
|
||||
back: string;
|
||||
originalBack: string;
|
||||
autofill: boolean;
|
||||
largeCheck: boolean;
|
||||
replText: string;
|
||||
};
|
||||
}
|
||||
@ -26,7 +26,7 @@ export interface CreateQuizRequest {
|
||||
/** true if it is allowed for pause quiz */
|
||||
pausable: boolean;
|
||||
/** count of questions */
|
||||
question_cnt?: number;
|
||||
questions_count?: number;
|
||||
/** set true if squiz realize group functionality */
|
||||
super: boolean;
|
||||
/** group of new quiz */
|
||||
|
||||
@ -30,7 +30,7 @@ export interface EditQuizRequest {
|
||||
/** allow to pause quiz to user */
|
||||
pausable: boolean;
|
||||
/** count of questions */
|
||||
question_cnt?: number;
|
||||
questions_count?: number;
|
||||
/** set true if squiz realize group functionality */
|
||||
super?: boolean;
|
||||
/** group of new quiz */
|
||||
@ -58,7 +58,7 @@ export function quizToEditQuizRequest(quiz: Quiz): EditQuizRequest {
|
||||
due_to: quiz.due_to,
|
||||
time_of_passing: quiz.time_of_passing,
|
||||
pausable: quiz.pausable,
|
||||
question_cnt: quiz.question_cnt,
|
||||
questions_count: quiz.questions_count,
|
||||
super: quiz.super,
|
||||
group_id: quiz.group_id,
|
||||
};
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { QuizConfig, defaultQuizConfig } from "@model/quizSettings";
|
||||
import { QuizConfig } from "@frontend/squzanswerer";
|
||||
import { defaultQuizConfig } from "@model/quizSettings";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export interface Quiz {
|
||||
@ -47,7 +48,7 @@ export interface Quiz {
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
/** count of questions */
|
||||
question_cnt: number;
|
||||
questions_count: number;
|
||||
/** count passings */
|
||||
passed_count: number;
|
||||
session_count: number;
|
||||
@ -55,8 +56,6 @@ export interface Quiz {
|
||||
average_time: number;
|
||||
/** set true if squiz realize group functionality */
|
||||
super: boolean;
|
||||
|
||||
questions_count: number;
|
||||
/** group of new quiz */
|
||||
group_id: number;
|
||||
}
|
||||
@ -106,7 +105,7 @@ export interface RawQuiz {
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
/** count of questions */
|
||||
question_cnt: number;
|
||||
questions_count: number;
|
||||
/** count passings */
|
||||
passed_count: number;
|
||||
session_count: number;
|
||||
@ -124,10 +123,7 @@ export function rawQuizToQuiz(rawQuiz: RawQuiz): Quiz {
|
||||
try {
|
||||
config = JSON.parse(rawQuiz.config);
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
"Cannot parse quiz config from string, using default config",
|
||||
error,
|
||||
);
|
||||
console.warn("Cannot parse quiz config from string, using default config", error);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { QuizConfig } from "@frontend/squzanswerer";
|
||||
import ChartPieIcon from "@icons/ChartPieIcon";
|
||||
import ContactBookIcon from "@icons/ContactBookIcon";
|
||||
import FlowArrowIcon from "@icons/FlowArrowIcon";
|
||||
@ -65,80 +66,12 @@ export type QuizTheme =
|
||||
| "Design9"
|
||||
| "Design10";
|
||||
|
||||
export interface QuizConfig {
|
||||
spec: undefined | true;
|
||||
type: QuizType;
|
||||
noStartPage: boolean;
|
||||
startpageType: QuizStartpageType;
|
||||
results: QuizResultsType;
|
||||
haveRoot: string | null;
|
||||
theme: QuizTheme;
|
||||
design: boolean;
|
||||
resultInfo: {
|
||||
when: "email" | "";
|
||||
share: true | false;
|
||||
replay: true | false;
|
||||
theme: string;
|
||||
reply: string;
|
||||
replname: string;
|
||||
showResultForm: "before" | "after";
|
||||
};
|
||||
startpage: {
|
||||
description: string;
|
||||
button: string;
|
||||
position: QuizStartpageAlignType;
|
||||
favIcon: string | null;
|
||||
logo: string | null;
|
||||
originalLogo: string | null;
|
||||
background: {
|
||||
type: null | "image" | "video";
|
||||
desktop: string | null;
|
||||
originalDesktop: string | null;
|
||||
mobile: string | null;
|
||||
originalMobile: string | null;
|
||||
video: string | null;
|
||||
cycle: boolean;
|
||||
};
|
||||
};
|
||||
formContact: {
|
||||
title: string;
|
||||
desc: string;
|
||||
fields: Record<FormContactFieldName, FormContactFieldData>;
|
||||
button: string;
|
||||
};
|
||||
info: {
|
||||
phonenumber: string;
|
||||
clickable: boolean;
|
||||
orgname: string;
|
||||
site: string;
|
||||
law?: string;
|
||||
};
|
||||
meta: string;
|
||||
antifraud: boolean;
|
||||
showfc: boolean;
|
||||
yandexMetricsNumber: number | undefined;
|
||||
vkMetricsNumber: number | undefined;
|
||||
}
|
||||
|
||||
export enum QuizMetricType {
|
||||
yandex = "yandexMetricsNumber",
|
||||
vk = "vkMetricsNumber",
|
||||
}
|
||||
|
||||
export type FormContactFieldName =
|
||||
| "name"
|
||||
| "email"
|
||||
| "phone"
|
||||
| "text"
|
||||
| "address";
|
||||
|
||||
type FormContactFieldData = {
|
||||
text: string;
|
||||
innerText: string;
|
||||
key: string;
|
||||
required: boolean;
|
||||
used: boolean;
|
||||
};
|
||||
export type FormContactFieldName = "name" | "email" | "phone" | "text" | "address";
|
||||
|
||||
export type FieldSettingsDrawerState = {
|
||||
field: FormContactFieldName | "all" | "";
|
||||
|
||||
@ -8,10 +8,7 @@ type AmoAccountInfoProps = {
|
||||
accountInfo: AccountResponse;
|
||||
};
|
||||
|
||||
export const AmoAccountInfo: FC<AmoAccountInfoProps> = ({
|
||||
handleNextStep,
|
||||
accountInfo,
|
||||
}) => {
|
||||
export const AmoAccountInfo: FC<AmoAccountInfoProps> = ({ handleNextStep, accountInfo }) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
@ -24,9 +21,7 @@ export const AmoAccountInfo: FC<AmoAccountInfoProps> = ({
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: isMobile ? "100%" : "45%" }}>
|
||||
<Typography sx={{ color: theme.palette.grey2.main }}>
|
||||
{title}:
|
||||
</Typography>
|
||||
<Typography sx={{ color: theme.palette.grey2.main }}>{title}:</Typography>
|
||||
</Box>
|
||||
<Box sx={{ width: isMobile ? "100%" : "45%" }}>
|
||||
<Typography>{value || "нет данных"}</Typography>
|
||||
@ -43,12 +38,15 @@ export const AmoAccountInfo: FC<AmoAccountInfoProps> = ({
|
||||
}}
|
||||
>
|
||||
<Box sx={{ width: isMobile ? "100%" : "45%" }}>
|
||||
<Typography sx={{ color: theme.palette.grey2.main }}>
|
||||
{title}:
|
||||
</Typography>
|
||||
<Typography sx={{ color: theme.palette.grey2.main }}>{title}:</Typography>
|
||||
</Box>
|
||||
<Box sx={{ width: isMobile ? "100%" : "45%" }}>
|
||||
<a target="_blank" href={link}>{link}</a>
|
||||
<a
|
||||
target="_blank"
|
||||
href={link}
|
||||
>
|
||||
{link}
|
||||
</a>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
@ -71,12 +69,12 @@ export const AmoAccountInfo: FC<AmoAccountInfoProps> = ({
|
||||
gap: isMobile ? "10px" : "20px",
|
||||
}}
|
||||
>
|
||||
{infoItem("Amo ID", accountInfo.AmoUserID)}
|
||||
{infoItem("Имя аккаунта", accountInfo.Name)}
|
||||
{infoItem("Email аккаунта", accountInfo.Email)}
|
||||
{infoItemLink("ЛК в amo", `https://${accountInfo.Subdomain}.amocrm.ru/dashboard/`)}
|
||||
{infoItemLink("Профиль пользователя в amo", `https://${accountInfo.Subdomain}.amocrm.ru/settings/users/`)}
|
||||
{infoItem("Страна пользователя", accountInfo.Country)}
|
||||
{infoItem("Amo ID", accountInfo.amoUserID)}
|
||||
{infoItem("Имя аккаунта", accountInfo.name)}
|
||||
{infoItem("Email аккаунта", accountInfo.email)}
|
||||
{infoItemLink("ЛК в amo", `https://${accountInfo.subdomain}.amocrm.ru/dashboard/`)}
|
||||
{infoItemLink("Профиль пользователя в amo", `https://${accountInfo.subdomain}.amocrm.ru/settings/users/`)}
|
||||
{infoItem("Страна пользователя", accountInfo.country)}
|
||||
</Box>
|
||||
<StepButtonsBlock
|
||||
isSmallBtnDisabled={true}
|
||||
|
||||
@ -1,35 +1,42 @@
|
||||
import {
|
||||
Dialog,
|
||||
IconButton,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import React, { FC, useEffect, useMemo, useState } from "react";
|
||||
import Box from "@mui/material/Box";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import { AmoRemoveAccount } from "./AmoRemoveAccount/AmoRemoveAccount";
|
||||
import { AmoLogin } from "./AmoLogin/AmoLogin";
|
||||
import { AmoStep2 } from "./AmoStep2/AmoStep2";
|
||||
import { AmoStep3 } from "./AmoStep3/AmoStep3";
|
||||
import { AmoStep4 } from "./AmoStep4/AmoStep4";
|
||||
import { AmoStep6 } from "./IntegrationStep6/AmoStep6";
|
||||
import { AmoStep7 } from "./IntegrationStep7/AmoStep7";
|
||||
import { AmoModalTitle } from "./AmoModalTitle/AmoModalTitle";
|
||||
import { AmoSettingsBlock } from "./SettingsBlock/AmoSettingsBlock";
|
||||
import { AmoAccountInfo } from "./AmoAccountInfo/AmoAccountInfo";
|
||||
import { AccountResponse, IntegrationRules, getAccount, getIntegrationRules, setIntegrationRules, updateIntegrationRules } from "@api/integration";
|
||||
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Dialog, IconButton, Typography, useMediaQuery, useTheme, Box } from "@mui/material";
|
||||
import { useQuestions } from "@/stores/questions/hooks";
|
||||
import { redirect } from "react-router-dom";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
export type TitleKeys = "Contact" | "Company" | "Lead" | "Customer";
|
||||
export type TagQuestionObject = {
|
||||
backendId: string;
|
||||
title: string;
|
||||
};
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
|
||||
import { AmoRemoveAccount } from "./AmoRemoveAccount/AmoRemoveAccount";
|
||||
import { AmoDeleteTagQuestion } from "./AmoRemoveAccount/AmoDeleteTagQuestion";
|
||||
import { AmoLogin } from "./AmoLogin/AmoLogin";
|
||||
import { Pipelines } from "./Pipelines/Pipelines";
|
||||
import { PipelineSteps } from "./PipelineSteps/PipelineSteps";
|
||||
import { DealPerformers } from "./DealPerformers/DealPerformers";
|
||||
import { AmoTags } from "./AmoTags/AmoTags";
|
||||
import { AmoQuestions } from "./AmoQuestions/AmoQuestions";
|
||||
import { AmoModalTitle } from "./AmoModalTitle/AmoModalTitle";
|
||||
import { AmoSettingsBlock } from "./SettingsBlock/AmoSettingsBlock";
|
||||
import { AmoAccountInfo } from "./AmoAccountInfo/AmoAccountInfo";
|
||||
import {
|
||||
AccountResponse,
|
||||
FieldsRule,
|
||||
IntegrationRules,
|
||||
Pipeline,
|
||||
Step,
|
||||
User,
|
||||
getAccount,
|
||||
getIntegrationRules,
|
||||
getPipelines,
|
||||
getSteps,
|
||||
getTags,
|
||||
getUsers,
|
||||
setIntegrationRules,
|
||||
updateIntegrationRules,
|
||||
} from "@api/integration";
|
||||
import type { QuestionID } from "@api/integration";
|
||||
import { useAmoIntegration } from "./useAmoIntegration";
|
||||
import { QuestionKeys, TagKeys, TagQuestionHC } from "./types";
|
||||
|
||||
export type TQuestionEntity = Record<TitleKeys, number[] | []>;
|
||||
type IntegrationsModalProps = {
|
||||
isModalOpen: boolean;
|
||||
handleCloseModal: () => void;
|
||||
@ -37,85 +44,108 @@ type IntegrationsModalProps = {
|
||||
quizID: number | undefined;
|
||||
};
|
||||
|
||||
export type TagKeys = "Contact" | "Company" | "Lead" | "Customer";
|
||||
export type TTags = Record<TagKeys, number[] | []>;
|
||||
export const AmoCRMModal: FC<IntegrationsModalProps> = ({ isModalOpen, handleCloseModal, companyName, quizID }) => {
|
||||
//Если нет контекста квиза, то и делать на этой страничке нечего
|
||||
if (quizID === undefined) {
|
||||
redirect("/list");
|
||||
return null;
|
||||
}
|
||||
|
||||
export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
isModalOpen,
|
||||
handleCloseModal,
|
||||
companyName,
|
||||
quizID,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
|
||||
const { questions } = useQuestions();
|
||||
const minifiedQuestions = useMemo(
|
||||
() =>
|
||||
questions
|
||||
.filter((q) => q.type !== "result" && q.type !== null)
|
||||
.map(({ backendId, title }) => ({
|
||||
id: backendId.toString() as string,
|
||||
title,
|
||||
})),
|
||||
[questions]
|
||||
);
|
||||
|
||||
const [step, setStep] = useState<number>(0);
|
||||
const [isSettingsBlock, setIsSettingsBlock] = useState<boolean>(false);
|
||||
const [isRemoveAccount, setIsRemoveAccount] = useState<boolean>(false);
|
||||
const [isTryRemoveAccount, setIsTryRemoveAccount] = useState<boolean>(false);
|
||||
const [openDelete, setOpenDelete] = useState<TagQuestionHC | null>(null);
|
||||
|
||||
const [firstRules, setFirstRules] = useState<boolean>(false);
|
||||
|
||||
const [accountInfo, setAccountInfo] = useState<AccountResponse | null>(null);
|
||||
const [selectedPipelinePerformer, setSelectedPipelinePerformer] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [selectedPipeline, setSelectedPipeline] = useState<string | null>(null);
|
||||
const [selectedStepsPerformer, setSelectedStepsPerformer] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [selectedStep, setSelectedStep] = useState<string | null>(null);
|
||||
const [selectedDealPerformer, setSelectedDealPerformer] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [questionEntity, setQuestionEntity] = useState<TQuestionEntity>({
|
||||
Lead: [],
|
||||
Contact: [],
|
||||
Company: [],
|
||||
Customer: [],
|
||||
const {
|
||||
isloadingPage,
|
||||
firstRules,
|
||||
accountInfo,
|
||||
arrayOfPipelines,
|
||||
arrayOfPipelinesSteps,
|
||||
arrayOfUsers,
|
||||
arrayOfTags,
|
||||
selectedPipeline,
|
||||
setSelectedPipeline,
|
||||
selectedPipelineStep,
|
||||
setSelectedPipelineStep,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
questionsBackend,
|
||||
selectedTags,
|
||||
setSelectedTags,
|
||||
selectedQuestions,
|
||||
setSelectedQuestions,
|
||||
setPageOfPipelines,
|
||||
setPageOfPipelinesSteps,
|
||||
setPageOfUsers,
|
||||
setPageOfTags,
|
||||
} = useAmoIntegration({
|
||||
quizID,
|
||||
isModalOpen,
|
||||
isTryRemoveAccount,
|
||||
});
|
||||
const [tags, setTags] = useState<TTags>({
|
||||
Lead: [],
|
||||
Contact: [],
|
||||
Company: [],
|
||||
Customer: [],
|
||||
});
|
||||
console.log(questionEntity)
|
||||
console.log(tags)
|
||||
|
||||
useEffect(() => {
|
||||
if (isModalOpen && quizID !== undefined && !isRemoveAccount) {
|
||||
const fetchAccount = async () => {
|
||||
const [account, error] = await getAccount();
|
||||
const handleAddTagQuestion = useCallback(
|
||||
(scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => {
|
||||
if (!scope || !id) return;
|
||||
|
||||
if (error) {
|
||||
if (!error.includes("Not Found")) enqueueSnackbar(error)
|
||||
setAccountInfo(null);
|
||||
}
|
||||
if (account) {
|
||||
setAccountInfo(account);
|
||||
}
|
||||
};
|
||||
const fetchRules = async () => {
|
||||
const [settingsResponse, error] = await getIntegrationRules(quizID.toString());
|
||||
if (type === "tag") {
|
||||
setSelectedTags((prevState) => ({
|
||||
...prevState,
|
||||
[scope]: [...prevState[scope as TagKeys], id],
|
||||
}));
|
||||
}
|
||||
|
||||
if (error) {
|
||||
if (error === "first") setFirstRules(true);
|
||||
if (!error.includes("Not Found") && !error.includes("first")) enqueueSnackbar(error);
|
||||
}
|
||||
if (settingsResponse) {
|
||||
console.log(settingsResponse);
|
||||
setFirstRules(false);
|
||||
}
|
||||
|
||||
};
|
||||
if (type === "question") {
|
||||
setSelectedQuestions((prevState) => ({
|
||||
...prevState,
|
||||
[scope]: [...prevState[scope as QuestionKeys], id],
|
||||
}));
|
||||
}
|
||||
},
|
||||
[setSelectedQuestions, setSelectedTags]
|
||||
);
|
||||
const handleDeleteTagQuestion = useCallback(() => {
|
||||
if (openDelete === null || !openDelete.scope || !openDelete.id || !openDelete.type) return;
|
||||
if (openDelete.type === "tag") {
|
||||
let newArray = selectedTags[openDelete.scope];
|
||||
const index = newArray.indexOf(openDelete.id);
|
||||
if (index !== -1) newArray.splice(index, 1);
|
||||
|
||||
fetchAccount();
|
||||
fetchRules();
|
||||
setSelectedTags((prevState) => ({
|
||||
...prevState,
|
||||
[openDelete.scope]: newArray,
|
||||
}));
|
||||
}
|
||||
}, [isModalOpen, isRemoveAccount]);
|
||||
|
||||
if (openDelete.type === "question") {
|
||||
let newArray = selectedQuestions[openDelete.scope as QuestionKeys];
|
||||
const index = newArray.indexOf(openDelete.id);
|
||||
if (index !== -1) newArray.splice(index, 1);
|
||||
|
||||
setSelectedQuestions((prevState) => ({
|
||||
...prevState,
|
||||
[openDelete.scope]: newArray,
|
||||
}));
|
||||
}
|
||||
setOpenDelete(null);
|
||||
}, [openDelete]);
|
||||
|
||||
const handleNextStep = () => {
|
||||
setStep((prevState) => prevState + 1);
|
||||
@ -124,60 +154,53 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
setStep((prevState) => prevState - 1);
|
||||
};
|
||||
const handleSave = () => {
|
||||
console.log("На отправку")
|
||||
console.log({
|
||||
PerformerID: selectedDealPerformer,
|
||||
PipelineID: selectedPipeline,
|
||||
StepID: selectedStep,
|
||||
Fieldsrule: {
|
||||
...questionEntity
|
||||
},
|
||||
TagsToAdd: {
|
||||
...tags
|
||||
}
|
||||
})
|
||||
if (quizID === undefined) return
|
||||
if (selectedPipeline?.toString().length === 0) return enqueueSnackbar("Выберите воронку")
|
||||
if (selectedPipeline?.toString().length === 0) return enqueueSnackbar("Выберите этап воронки")
|
||||
if (quizID === undefined) return;
|
||||
if (selectedPipeline === null) return enqueueSnackbar("Выберите воронку");
|
||||
if (selectedPipeline === null) return enqueueSnackbar("Выберите этап воронки");
|
||||
|
||||
const body = {
|
||||
PipelineID: Number(selectedPipeline),
|
||||
StepID: Number(selectedPipelineStep),
|
||||
PerformerID: Number(selectedDealUser),
|
||||
// FieldsRule: questionsBackend,
|
||||
TagsToAdd: selectedTags,
|
||||
};
|
||||
|
||||
const FieldsRule = {
|
||||
Company: [{ QuestionID: {} }],
|
||||
Lead: [{ QuestionID: {} }],
|
||||
Customer: [{ QuestionID: {} }],
|
||||
};
|
||||
|
||||
console.log("selectedQuestions");
|
||||
console.log(selectedQuestions);
|
||||
for (let key in FieldsRule) {
|
||||
console.log("current key ", key);
|
||||
selectedQuestions[key as QuestionKeys].forEach((id) => {
|
||||
FieldsRule[key as QuestionKeys][0].QuestionID[id] = 0;
|
||||
});
|
||||
}
|
||||
for (let key in body.TagsToAdd) {
|
||||
body.TagsToAdd[key as TagKeys] = body.TagsToAdd[key as TagKeys].map((id) => Number(id));
|
||||
}
|
||||
body.FieldsRule = FieldsRule;
|
||||
|
||||
console.log("На отправку");
|
||||
console.log(body);
|
||||
|
||||
if (firstRules) {
|
||||
setIntegrationRules(quizID.toString(), {
|
||||
PerformerID: Number(selectedDealPerformer),
|
||||
PipelineID: Number(selectedPipeline),
|
||||
StepID: Number(selectedStep),
|
||||
Fieldsrule: {
|
||||
...questionEntity
|
||||
},
|
||||
TagsToAdd: {
|
||||
...tags
|
||||
}
|
||||
})
|
||||
setIntegrationRules(quizID.toString(), body);
|
||||
} else {
|
||||
const body = {
|
||||
|
||||
Fieldsrule: {
|
||||
...questionEntity
|
||||
},
|
||||
TagsToAdd: {
|
||||
...tags
|
||||
}
|
||||
};
|
||||
if (selectedDealPerformer?.toString() > 0) body.PerformerID = Number(selectedDealPerformer)
|
||||
if (selectedPipeline?.toString() > 0) body.PipelineID = Number(selectedPipeline)
|
||||
if (selectedStep?.toString() > 0) body.StepID = Number(selectedStep)
|
||||
|
||||
updateIntegrationRules(quizID.toString(), body)
|
||||
updateIntegrationRules(quizID.toString(), body);
|
||||
}
|
||||
|
||||
// handleCloseModal();
|
||||
// setStep(1);
|
||||
handleCloseModal();
|
||||
setStep(1);
|
||||
};
|
||||
const steps = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: accountInfo
|
||||
? "Информация об аккаунте"
|
||||
: "Авторизация в аккаунте",
|
||||
title: accountInfo ? "Информация об аккаунте" : "Авторизация в аккаунте",
|
||||
isSettingsAvailable: false,
|
||||
component: accountInfo ? (
|
||||
<AmoAccountInfo
|
||||
@ -192,11 +215,12 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
title: "Выбор воронки",
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<AmoStep2
|
||||
<Pipelines
|
||||
users={arrayOfUsers}
|
||||
pipelines={arrayOfPipelines}
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleNextStep}
|
||||
selectedPipelinePerformer={selectedDealPerformer}
|
||||
setSelectedPipelinePerformer={setSelectedDealPerformer}
|
||||
selectedDealUser={selectedDealUser}
|
||||
setSelectedDealPerformer={setSelectedDealPerformer}
|
||||
selectedPipeline={selectedPipeline}
|
||||
setSelectedPipeline={setSelectedPipeline}
|
||||
@ -207,14 +231,15 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
title: "Выбор этапа воронки",
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<AmoStep3
|
||||
<PipelineSteps
|
||||
users={arrayOfUsers}
|
||||
selectedDealUser={selectedDealUser}
|
||||
selectedStep={selectedPipelineStep}
|
||||
steps={arrayOfPipelinesSteps}
|
||||
setSelectedDealPerformer={setSelectedDealPerformer}
|
||||
setSelectedStep={setSelectedPipelineStep}
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleNextStep}
|
||||
selectedStepsPerformer={selectedDealPerformer}
|
||||
setSelectedStepsPerformer={setSelectedDealPerformer}
|
||||
selectedStep={selectedStep}
|
||||
setSelectedStep={setSelectedStep}
|
||||
pipelineId={selectedPipeline}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -222,10 +247,11 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
title: "Сделка",
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<AmoStep4
|
||||
<DealPerformers
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleNextStep}
|
||||
selectedDealPerformer={selectedDealPerformer}
|
||||
users={arrayOfUsers}
|
||||
selectedDealUser={selectedDealUser}
|
||||
setSelectedDealPerformer={setSelectedDealPerformer}
|
||||
/>
|
||||
),
|
||||
@ -234,11 +260,14 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
title: "Добавление тегов",
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<AmoStep6
|
||||
tagsNames={tags}
|
||||
<AmoTags
|
||||
tagsItems={arrayOfTags}
|
||||
selectedTags={selectedTags}
|
||||
openDelete={setOpenDelete}
|
||||
handleScroll={() => {}}
|
||||
handleAddTag={handleAddTagQuestion}
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleNextStep}
|
||||
setTagsNames={setTags}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@ -246,33 +275,37 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
title: "Соотнесение вопросов и сущностей",
|
||||
isSettingsAvailable: true,
|
||||
component: (
|
||||
<AmoStep7
|
||||
questionEntity={questionEntity}
|
||||
setQuestionEntity={setQuestionEntity}
|
||||
<AmoQuestions
|
||||
questionsItems={minifiedQuestions}
|
||||
selectedQuestions={selectedQuestions}
|
||||
openDelete={setOpenDelete}
|
||||
handleAddQuestion={handleAddTagQuestion}
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleSave}
|
||||
questions={questions}
|
||||
/>
|
||||
),
|
||||
},
|
||||
],
|
||||
[
|
||||
accountInfo,
|
||||
questionEntity,
|
||||
selectedPipelinePerformer,
|
||||
arrayOfPipelines,
|
||||
arrayOfPipelinesSteps,
|
||||
arrayOfUsers,
|
||||
arrayOfTags,
|
||||
selectedPipeline,
|
||||
selectedStepsPerformer,
|
||||
selectedStep,
|
||||
selectedDealPerformer,
|
||||
tags,
|
||||
],
|
||||
selectedPipelineStep,
|
||||
selectedDealUser,
|
||||
selectedQuestions,
|
||||
selectedTags,
|
||||
arrayOfPipelines,
|
||||
arrayOfPipelinesSteps,
|
||||
arrayOfUsers,
|
||||
minifiedQuestions,
|
||||
arrayOfTags,
|
||||
]
|
||||
);
|
||||
|
||||
const stepTitles = steps.map((step) => step.title);
|
||||
|
||||
//Если нет контекста квиза, то и делать на этой страничке нечего
|
||||
if (quizID === undefined) redirect("/list")
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isModalOpen}
|
||||
@ -315,9 +348,7 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
top: "15px",
|
||||
}}
|
||||
>
|
||||
<CloseIcon
|
||||
sx={{ width: "12px", height: "12px", transform: "scale(1.5)" }}
|
||||
/>
|
||||
<CloseIcon sx={{ width: "12px", height: "12px", transform: "scale(1.5)" }} />
|
||||
</IconButton>
|
||||
<Box
|
||||
sx={{
|
||||
@ -335,31 +366,36 @@ export const AmoCRMModal: FC<IntegrationsModalProps> = ({
|
||||
isSettingsBlock={isSettingsBlock}
|
||||
setIsSettingsBlock={setIsSettingsBlock}
|
||||
setStep={setStep}
|
||||
startRemoveAccount={() => setIsRemoveAccount(true)}
|
||||
startRemoveAccount={() => setIsTryRemoveAccount(true)}
|
||||
/>
|
||||
{isRemoveAccount && (
|
||||
<AmoRemoveAccount
|
||||
stopThisPage={() => setIsRemoveAccount(false)}
|
||||
{openDelete !== null ? (
|
||||
<AmoDeleteTagQuestion
|
||||
close={() => setOpenDelete(null)}
|
||||
deleteItem={handleDeleteTagQuestion}
|
||||
/>
|
||||
)}
|
||||
{isSettingsBlock && (
|
||||
<Box sx={{ flexGrow: 1, width: "100%" }}>
|
||||
<AmoSettingsBlock
|
||||
stepTitles={stepTitles}
|
||||
setIsSettingsBlock={setIsSettingsBlock}
|
||||
setStep={setStep}
|
||||
selectedDealPerformer={selectedDealPerformer}
|
||||
selectedFunnelPerformer={selectedPipelinePerformer}
|
||||
selectedFunnel={selectedPipeline}
|
||||
selectedStagePerformer={selectedStepsPerformer}
|
||||
selectedStage={selectedStep}
|
||||
questionEntity={questionEntity}
|
||||
tags={tags}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{!isSettingsBlock && !isRemoveAccount && (
|
||||
<Box sx={{ flexGrow: 1, width: "100%" }}>{steps[step].component}</Box>
|
||||
) : (
|
||||
<>
|
||||
{isTryRemoveAccount && <AmoRemoveAccount stopThisPage={() => setIsTryRemoveAccount(false)} />}
|
||||
{isSettingsBlock && (
|
||||
<Box sx={{ flexGrow: 1, width: "100%" }}>
|
||||
<AmoSettingsBlock
|
||||
stepTitles={stepTitles}
|
||||
setIsSettingsBlock={setIsSettingsBlock}
|
||||
setStep={setStep}
|
||||
selectedDealUser={arrayOfUsers.find((u) => u.id === selectedDealUser)?.title || "не указан"}
|
||||
selectedFunnel={arrayOfPipelines.find((p) => p.id === selectedPipeline)?.title || "нет данных"}
|
||||
selectedStage={
|
||||
arrayOfPipelinesSteps.find((s) => s.id === selectedPipelineStep)?.title || "нет данных"
|
||||
}
|
||||
selectedQuestions={selectedQuestions}
|
||||
selectedTags={selectedTags}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
{!isSettingsBlock && !isTryRemoveAccount && (
|
||||
<Box sx={{ flexGrow: 1, width: "100%" }}>{steps[step].component}</Box>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Dialog>
|
||||
|
||||
@ -27,7 +27,6 @@ export const AmoLogin: FC<IntegrationStep1Props> = ({ handleNextStep }) => {
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
const onAmoClick = async () => {
|
||||
console.log("Amo click");
|
||||
const [url, error] = await connectAmo();
|
||||
if (url && !error) {
|
||||
window.open(url, "_blank");
|
||||
@ -131,9 +130,8 @@ export const AmoLogin: FC<IntegrationStep1Props> = ({ handleNextStep }) => {
|
||||
lineHeight: "1",
|
||||
}}
|
||||
>
|
||||
После нажатия на кнопку - "Подключить", вас переадресует на страницу
|
||||
подключения интеграции в ваш аккаунт AmoCRM. Пожалуйста, согласитесь
|
||||
на всё, что мы предлагаем, иначе чуда не случится.
|
||||
После нажатия на кнопку - "Подключить", вас переадресует на страницу подключения интеграции в ваш аккаунт
|
||||
AmoCRM. Пожалуйста, согласитесь на всё, что мы предлагаем, иначе чуда не случится.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ marginTop: "50px" }}>
|
||||
|
||||
@ -19,16 +19,15 @@ export const AmoModalTitle: FC<AmoModalTitleProps> = ({
|
||||
setIsSettingsBlock,
|
||||
isSettingsBlock,
|
||||
setStep,
|
||||
startRemoveAccount
|
||||
startRemoveAccount,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
console.log(isSettingsBlock)
|
||||
const handleClick = useCallback(async () => {
|
||||
if (isSettingsBlock) {
|
||||
startRemoveAccount();
|
||||
setIsSettingsBlock(false)
|
||||
setIsSettingsBlock(false);
|
||||
setStep(0);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
import { FC, useState } from "react";
|
||||
import { ItemsSelectionView } from "./ItemsSelectionView/ItemsSelectionView";
|
||||
import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView";
|
||||
import { Box } from "@mui/material";
|
||||
import { QuestionKeys, SelectedQuestions, TagKeys, TagQuestionHC } from "../types";
|
||||
|
||||
type MinifiedData = {
|
||||
id: string;
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
type Props = {
|
||||
questionsItems: MinifiedData[] | [];
|
||||
selectedQuestions: SelectedQuestions;
|
||||
handleAddQuestion: (scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => void;
|
||||
openDelete: (data: TagQuestionHC) => void;
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
};
|
||||
|
||||
export const AmoQuestions: FC<Props> = ({
|
||||
questionsItems,
|
||||
selectedQuestions,
|
||||
handleAddQuestion,
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
openDelete,
|
||||
}) => {
|
||||
const [isSelection, setIsSelection] = useState<boolean>(false);
|
||||
const [activeScope, setActiveScope] = useState<QuestionKeys | null>(null);
|
||||
const [selectedQuestion, setSelectedQuestion] = useState<string | null>(null);
|
||||
|
||||
const handleAdd = () => {
|
||||
if (activeScope === null || selectedQuestion === null) return;
|
||||
setActiveScope(null);
|
||||
handleAddQuestion(activeScope, selectedQuestion, "question");
|
||||
};
|
||||
const handleDelete = (id: string, scope: QuestionKeys) => {
|
||||
openDelete({
|
||||
id,
|
||||
scope,
|
||||
type: "question",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{isSelection && activeScope !== null ? (
|
||||
// Здесь выбираем элемент в табличку
|
||||
<ItemsSelectionView
|
||||
items={questionsItems}
|
||||
selectedItemId={selectedQuestion}
|
||||
setSelectedItem={setSelectedQuestion}
|
||||
onSmallBtnClick={() => {
|
||||
setActiveScope(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
onLargeBtnClick={() => {
|
||||
handleAdd();
|
||||
setActiveScope(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
// Табличка
|
||||
<ItemDetailsView
|
||||
items={questionsItems}
|
||||
setActiveScope={setActiveScope}
|
||||
selectedQuestions={selectedQuestions}
|
||||
setIsSelection={setIsSelection}
|
||||
handleLargeBtn={handleNextStep}
|
||||
handleSmallBtn={handlePrevStep}
|
||||
deleteHC={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
69
src/pages/IntegrationsPage/IntegrationsModal/AmoQuestions/Item/AnswerItem/AnswerItem.tsx
Normal file
69
src/pages/IntegrationsPage/IntegrationsModal/AmoQuestions/Item/AnswerItem/AnswerItem.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import { Box, IconButton, Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import Trash from "@icons/trash";
|
||||
|
||||
type AnswerItemProps = {
|
||||
fieldName: string;
|
||||
fieldValue: string;
|
||||
deleteHC: () => void;
|
||||
};
|
||||
|
||||
export const AnswerItem: FC<AnswerItemProps> = ({ fieldName, fieldValue, deleteHC }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "10px 20px",
|
||||
height: "140px",
|
||||
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexDirection: "column",
|
||||
justifyContent: "space-between",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
</Typography>
|
||||
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 400,
|
||||
color: theme.palette.grey3.main,
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden",
|
||||
width: "100%",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
{fieldValue}
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton
|
||||
sx={{
|
||||
m: "auto",
|
||||
}}
|
||||
onClick={deleteHC}
|
||||
>
|
||||
<Trash />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -1,19 +1,19 @@
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { Box, IconButton, Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { IconBtnAdd } from "./IconBtnAdd/IconBtnAdd";
|
||||
import { AnswerItem } from "./AnswerItem/AnswerItem";
|
||||
import { TagKeys, TitleKeys, TQuestionEntity, TTags } from "../../AmoCRMModal";
|
||||
import { QuestionKeys, SelectedQuestions, TagKeys, SelectedTags, MinifiedData } from "../../types";
|
||||
|
||||
type ItemProps = {
|
||||
title: TitleKeys | TagKeys;
|
||||
items: MinifiedData[];
|
||||
title: QuestionKeys | TagKeys;
|
||||
onAddBtnClick: () => void;
|
||||
data: TQuestionEntity | TTags;
|
||||
data: SelectedTags | SelectedQuestions;
|
||||
deleteHC: (id: string, scope: QuestionKeys | TagKeys) => void;
|
||||
};
|
||||
export const Item: FC<ItemProps> = ({ title, onAddBtnClick, data }) => {
|
||||
export const Item: FC<ItemProps> = ({ items, title, onAddBtnClick, data, deleteHC }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
console.log("title")
|
||||
console.log(data)
|
||||
const titleDictionary = {
|
||||
Company: "Компания",
|
||||
Lead: "Сделка",
|
||||
@ -44,16 +44,15 @@ export const Item: FC<ItemProps> = ({ title, onAddBtnClick, data }) => {
|
||||
height: "40px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ fontSize: "16px", fontWeight: 500 }}>
|
||||
{translatedTitle}
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: "16px", fontWeight: 500 }}>{translatedTitle}</Typography>
|
||||
</Box>
|
||||
{selectedOptions &&
|
||||
selectedOptions.map((text, index) => (
|
||||
selectedOptions.map((id, index) => (
|
||||
<AnswerItem
|
||||
key={text + index}
|
||||
key={id + index}
|
||||
fieldValue={"Значение поля"}
|
||||
fieldName={text}
|
||||
fieldName={items.find((e) => e.id === id)?.title || id}
|
||||
deleteHC={() => deleteHC(selectedOptions[index], title)}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -2,24 +2,28 @@ import { Box, useTheme } from "@mui/material";
|
||||
import { Item } from "../Item/Item";
|
||||
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
|
||||
import { FC } from "react";
|
||||
import { TQuestionEntity } from "../../AmoCRMModal";
|
||||
import { MinifiedData, QuestionKeys, SelectedQuestions } from "../../types";
|
||||
|
||||
type TitleKeys = "Contact" | "Company" | "Lead" | "Customer";
|
||||
|
||||
type ItemDetailsViewProps = {
|
||||
items: MinifiedData[];
|
||||
setIsSelection: (value: boolean) => void;
|
||||
handleSmallBtn: () => void;
|
||||
handleLargeBtn: () => void;
|
||||
questionEntity: TQuestionEntity;
|
||||
setActiveItem: (value: string | null) => void;
|
||||
selectedQuestions: SelectedQuestions;
|
||||
setActiveScope: (value: QuestionKeys | null) => void;
|
||||
deleteHC: (id: string, scope: QuestionKeys) => void;
|
||||
};
|
||||
|
||||
export const ItemDetailsView: FC<ItemDetailsViewProps> = ({
|
||||
items,
|
||||
handleSmallBtn,
|
||||
handleLargeBtn,
|
||||
questionEntity,
|
||||
setActiveItem,
|
||||
selectedQuestions,
|
||||
setIsSelection,
|
||||
setActiveScope,
|
||||
deleteHC,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@ -48,16 +52,18 @@ export const ItemDetailsView: FC<ItemDetailsViewProps> = ({
|
||||
justifyContent: "start",
|
||||
}}
|
||||
>
|
||||
{questionEntity &&
|
||||
Object.keys(questionEntity).map((item) => (
|
||||
{selectedQuestions &&
|
||||
Object.keys(selectedQuestions).map((item) => (
|
||||
<Item
|
||||
key={item}
|
||||
title={item as TitleKeys}
|
||||
title={item as QuestionKeys}
|
||||
onAddBtnClick={() => {
|
||||
setIsSelection(true);
|
||||
setActiveItem(item);
|
||||
setActiveScope(item as QuestionKeys);
|
||||
}}
|
||||
data={questionEntity}
|
||||
items={items}
|
||||
data={selectedQuestions}
|
||||
deleteHC={deleteHC}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
@ -2,33 +2,32 @@ import { Box } from "@mui/material";
|
||||
import { CustomRadioGroup } from "../../../../../components/CustomRadioGroup/CustomRadioGroup";
|
||||
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
|
||||
import { FC } from "react";
|
||||
import { AllTypesQuestion } from "@/model/questionTypes/shared";
|
||||
import type { TagQuestionObject } from "@/pages/IntegrationsPage/IntegrationsModal/AmoCRMModal"
|
||||
import { Tag } from "@api/integration";
|
||||
import { TagKeys } from "../../types";
|
||||
|
||||
type MinifiedData = {
|
||||
id: string;
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
};
|
||||
type ItemsSelectionViewProps = {
|
||||
type?: string;
|
||||
items?: TagQuestionObject[];
|
||||
selectedValue: string | null;
|
||||
setSelectedValue: (value: string | null) => void;
|
||||
items: MinifiedData[] | [];
|
||||
selectedItemId?: string | null;
|
||||
setSelectedItem: (value: string | null) => void;
|
||||
handleScroll?: () => void;
|
||||
onLargeBtnClick: () => void;
|
||||
onSmallBtnClick: () => void;
|
||||
setTags: (setValueFunc: (value: Tag[]) => Tag[]) => void;
|
||||
parentTags: Tag[]
|
||||
activeScope: TagKeys;
|
||||
};
|
||||
|
||||
export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
|
||||
items,
|
||||
selectedValue,
|
||||
setSelectedValue,
|
||||
selectedItemId,
|
||||
setSelectedItem,
|
||||
handleScroll,
|
||||
onLargeBtnClick,
|
||||
onSmallBtnClick,
|
||||
type,
|
||||
parentTags,
|
||||
setTags
|
||||
activeScope,
|
||||
}) => {
|
||||
console.log("items тегов")
|
||||
console.log(items)
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -48,12 +47,11 @@ export const ItemsSelectionView: FC<ItemsSelectionViewProps> = ({
|
||||
}}
|
||||
>
|
||||
<CustomRadioGroup
|
||||
type={type}
|
||||
items={items}
|
||||
tags={parentTags}
|
||||
setTags={setTags}
|
||||
selectedValue={selectedValue}
|
||||
setSelectedValue={setSelectedValue}
|
||||
selectedItemId={selectedItemId}
|
||||
setSelectedItem={setSelectedItem}
|
||||
handleScroll={handleScroll}
|
||||
activeScope={activeScope}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
48
src/pages/IntegrationsPage/IntegrationsModal/AmoRemoveAccount/AmoDeleteTagQuestion.tsx
Normal file
48
src/pages/IntegrationsPage/IntegrationsModal/AmoRemoveAccount/AmoDeleteTagQuestion.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import { FC } from "react";
|
||||
import { Button, Typography, useTheme, Box } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
deleteItem: () => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export const AmoDeleteTagQuestion: FC<Props> = ({ close, deleteItem }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
mt: "30px",
|
||||
}}
|
||||
>
|
||||
<Typography textAlign="center">Вы хотите удалить элемент?</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-evenly",
|
||||
flexWrap: "wrap",
|
||||
margin: "30px auto",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
width: "150px",
|
||||
}}
|
||||
onClick={close}
|
||||
>
|
||||
отмена
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{
|
||||
width: "150px",
|
||||
}}
|
||||
onClick={deleteItem}
|
||||
>
|
||||
удалить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,84 @@
|
||||
import { FC, useState } from "react";
|
||||
import { Box } from "@mui/material";
|
||||
import { ItemsSelectionView } from "../AmoQuestions/ItemsSelectionView/ItemsSelectionView";
|
||||
import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView";
|
||||
import { MinifiedData, QuestionKeys, SelectedTags, TagKeys, TagQuestionHC } from "../types";
|
||||
|
||||
type Props = {
|
||||
tagsItems: MinifiedData[] | [];
|
||||
selectedTags: SelectedTags;
|
||||
handleAddTag: (scope: QuestionKeys | TagKeys, id: string, type: "question" | "tag") => void;
|
||||
openDelete: (data: TagQuestionHC) => void;
|
||||
handleScroll: () => void;
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
};
|
||||
|
||||
export const AmoTags: FC<Props> = ({
|
||||
tagsItems,
|
||||
selectedTags,
|
||||
handleAddTag,
|
||||
openDelete,
|
||||
handleScroll,
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
}) => {
|
||||
const [isSelection, setIsSelection] = useState<boolean>(false);
|
||||
const [activeScope, setActiveScope] = useState<TagKeys | null>(null);
|
||||
const [selectedTag, setSelectedTag] = useState<string | null>(null);
|
||||
|
||||
const handleAdd = () => {
|
||||
if (activeScope === null || selectedTag === null) return;
|
||||
setActiveScope(null);
|
||||
handleAddTag(activeScope, selectedTag, "tag");
|
||||
};
|
||||
const handleDelete = (id: string, scope: TagKeys) => {
|
||||
openDelete({
|
||||
id,
|
||||
scope,
|
||||
type: "tag",
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{isSelection && activeScope !== null ? (
|
||||
// Здесь выбираем элемент в табличку
|
||||
<ItemsSelectionView
|
||||
items={tagsItems}
|
||||
selectedItemId={selectedTag}
|
||||
setSelectedItem={setSelectedTag}
|
||||
handleScroll={handleScroll}
|
||||
activeScope={activeScope}
|
||||
onSmallBtnClick={() => {
|
||||
setActiveScope(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
onLargeBtnClick={() => {
|
||||
handleAdd();
|
||||
setActiveScope(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
// Табличка
|
||||
<TagsDetailsView
|
||||
items={tagsItems}
|
||||
setActiveScope={setActiveScope}
|
||||
selectedTags={selectedTags}
|
||||
setIsSelection={setIsSelection}
|
||||
handleNextStep={handleNextStep}
|
||||
handlePrevStep={handlePrevStep}
|
||||
deleteHC={handleDelete}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -1,23 +1,27 @@
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { StepButtonsBlock } from "../../StepButtonsBlock/StepButtonsBlock";
|
||||
import { FC } from "react";
|
||||
import { TagKeys, TTags } from "../../AmoCRMModal";
|
||||
import { Item } from "../../IntegrationStep7/Item/Item";
|
||||
import { Item } from "../../AmoQuestions/Item/Item";
|
||||
import { MinifiedData, SelectedTags, TagKeys } from "../../types";
|
||||
|
||||
type TagsDetailsViewProps = {
|
||||
items: MinifiedData[];
|
||||
setIsSelection: (value: boolean) => void;
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
tagsNames: TTags;
|
||||
setActiveItem: (value: string | null) => void;
|
||||
setActiveScope: (value: TagKeys | null) => void;
|
||||
selectedTags: SelectedTags;
|
||||
deleteHC: (id: string, scope: TagKeys) => void;
|
||||
};
|
||||
|
||||
export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
|
||||
items,
|
||||
setActiveScope,
|
||||
selectedTags,
|
||||
setIsSelection,
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
tagsNames,
|
||||
setActiveItem,
|
||||
setIsSelection,
|
||||
deleteHC,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@ -52,11 +56,7 @@ export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{ fontSize: "14px", color: theme.palette.grey2.main }}
|
||||
>
|
||||
Результат
|
||||
</Typography>
|
||||
<Typography sx={{ fontSize: "14px", color: theme.palette.grey2.main }}>Результат</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@ -68,16 +68,18 @@ export const TagsDetailsView: FC<TagsDetailsViewProps> = ({
|
||||
justifyContent: "start",
|
||||
}}
|
||||
>
|
||||
{tagsNames &&
|
||||
Object.keys(tagsNames).map((item) => (
|
||||
{selectedTags &&
|
||||
Object.keys(selectedTags).map((item) => (
|
||||
<Item
|
||||
key={item}
|
||||
items={items}
|
||||
title={item as TagKeys}
|
||||
onAddBtnClick={() => {
|
||||
setIsSelection(true);
|
||||
setActiveItem(item);
|
||||
setActiveScope(item as TagKeys);
|
||||
}}
|
||||
data={tagsNames}
|
||||
data={selectedTags}
|
||||
deleteHC={deleteHC}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
@ -2,18 +2,21 @@ import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
|
||||
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
|
||||
import { MinifiedData } from "../types";
|
||||
|
||||
type AmoStep4Props = {
|
||||
type Props = {
|
||||
users: MinifiedData[];
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
selectedDealPerformer: string | null;
|
||||
selectedDealUser: string | null;
|
||||
setSelectedDealPerformer: (value: string | null) => void;
|
||||
};
|
||||
|
||||
export const AmoStep4: FC<AmoStep4Props> = ({
|
||||
export const DealPerformers: FC<Props> = ({
|
||||
users,
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
selectedDealPerformer,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
@ -32,9 +35,10 @@ export const AmoStep4: FC<AmoStep4Props> = ({
|
||||
>
|
||||
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
|
||||
<CustomSelect
|
||||
selectedItem={selectedDealPerformer}
|
||||
items={users}
|
||||
selectedItemId={selectedDealUser}
|
||||
setSelectedItem={setSelectedDealPerformer}
|
||||
type={"typeUsers"}
|
||||
handleScroll={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
@ -1,87 +0,0 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
import {
|
||||
Dispatch,
|
||||
FC,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { TagKeys, TTags } from "../AmoCRMModal";
|
||||
import Box from "@mui/material/Box";
|
||||
import { ItemsSelectionView } from "../IntegrationStep7/ItemsSelectionView/ItemsSelectionView";
|
||||
import { TagsDetailsView } from "./TagsDetailsView/TagsDetailsView";
|
||||
import { Tag } from "@api/integration";
|
||||
|
||||
type Props = {
|
||||
handleNextStep: () => void;
|
||||
handlePrevStep: () => void;
|
||||
tagsNames: TTags;
|
||||
setTagsNames: Dispatch<SetStateAction<TTags>>;
|
||||
};
|
||||
|
||||
export const AmoStep6: FC<Props> = ({
|
||||
handleNextStep,
|
||||
handlePrevStep,
|
||||
tagsNames,
|
||||
setTagsNames
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [isSelection, setIsSelection] = useState<boolean>(false);
|
||||
const [activeItem, setActiveItem] = useState<string | null>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
||||
const [tags, setTags] = useState<Tag[]>([]);
|
||||
const handleAdd = useCallback(() => {
|
||||
if (!activeItem || !selectedValue) return;
|
||||
|
||||
setTagsNames((prevState) => ({
|
||||
...prevState,
|
||||
[activeItem]: [...prevState[activeItem as TagKeys], Number(selectedValue)],
|
||||
}));
|
||||
}, [activeItem, setTagsNames, selectedValue]);
|
||||
|
||||
const items = useMemo(
|
||||
() => ["#тег с результатом 1", "#еще один тег с результатом 2", "#тег"],
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{isSelection ? (
|
||||
<ItemsSelectionView
|
||||
parentTags={tags}
|
||||
setTags={setTags}
|
||||
selectedValue={selectedValue}
|
||||
setSelectedValue={setSelectedValue}
|
||||
type={"typeTags"}
|
||||
onSmallBtnClick={() => {
|
||||
setActiveItem(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
onLargeBtnClick={() => {
|
||||
handleAdd();
|
||||
setActiveItem(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<TagsDetailsView
|
||||
setIsSelection={setIsSelection}
|
||||
handlePrevStep={handlePrevStep}
|
||||
handleNextStep={handleNextStep}
|
||||
tagsNames={tagsNames}
|
||||
setActiveItem={setActiveItem}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -1,110 +0,0 @@
|
||||
import { useTheme } from "@mui/material";
|
||||
import {
|
||||
Dispatch,
|
||||
FC,
|
||||
SetStateAction,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useState,
|
||||
} from "react";
|
||||
import { ItemsSelectionView } from "./ItemsSelectionView/ItemsSelectionView";
|
||||
import { ItemDetailsView } from "./ItemDetailsView/ItemDetailsView";
|
||||
import { TagQuestionObject, TitleKeys, TQuestionEntity } from "../AmoCRMModal";
|
||||
import Box from "@mui/material/Box";
|
||||
import type { AllTypesQuestion } from "@model/questionTypes/shared"
|
||||
import { getQuestionById } from "@/stores/questions/actions";
|
||||
import { useQuestionsStore } from "@/stores/questions/store";
|
||||
|
||||
type Props = {
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
questionEntity: TQuestionEntity;
|
||||
setQuestionEntity: Dispatch<SetStateAction<TQuestionEntity>>;
|
||||
questions: AllTypesQuestion[];
|
||||
};
|
||||
|
||||
export const AmoStep7: FC<Props> = ({
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
questionEntity,
|
||||
setQuestionEntity,
|
||||
questions,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [isSelection, setIsSelection] = useState<boolean>(false);
|
||||
const [activeItem, setActiveItem] = useState<string | null>(null);
|
||||
const [selectedValue, setSelectedValue] = useState<string | null>(null);
|
||||
|
||||
const handleAdd = useCallback(() => {
|
||||
if (!activeItem || !selectedValue) return;
|
||||
|
||||
setQuestionEntity((prevState) => ({
|
||||
...prevState,
|
||||
[activeItem]: [...prevState[activeItem as TitleKeys], Number(selectedValue)],
|
||||
}));
|
||||
}, [activeItem, setQuestionEntity, selectedValue]);
|
||||
|
||||
const items: TagQuestionObject[] = useMemo(
|
||||
() => Object.values(questions)
|
||||
.filter(({ type }) =>
|
||||
type !== "result"
|
||||
&& type !== null)
|
||||
.map(({ backendId, title }) => ({
|
||||
backendId: backendId,
|
||||
title
|
||||
})),
|
||||
[],
|
||||
);
|
||||
const translatedQuestionEntity = useMemo(() => {
|
||||
const translated = {} as TQuestionEntity;
|
||||
|
||||
for (let key in questionEntity) {
|
||||
translated[key] = questionEntity[key].map((id) =>
|
||||
questions.find((q) => q.backendId === Number(id))?.title || id
|
||||
)
|
||||
}
|
||||
|
||||
console.log("questionEntity", questionEntity)
|
||||
console.log("translated", translated)
|
||||
return translated
|
||||
},
|
||||
[questionEntity],
|
||||
)
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
{isSelection ? (
|
||||
<ItemsSelectionView
|
||||
items={items}
|
||||
type="typeQuestions"
|
||||
selectedValue={selectedValue}
|
||||
setSelectedValue={setSelectedValue}
|
||||
onSmallBtnClick={() => {
|
||||
setActiveItem(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
onLargeBtnClick={() => {
|
||||
handleAdd();
|
||||
setActiveItem(null);
|
||||
setIsSelection(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ItemDetailsView
|
||||
setIsSelection={setIsSelection}
|
||||
handleLargeBtn={handleNextStep}
|
||||
handleSmallBtn={handlePrevStep}
|
||||
questionEntity={translatedQuestionEntity}
|
||||
setActiveItem={setActiveItem}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -1,39 +0,0 @@
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import { FC } from "react";
|
||||
|
||||
type AnswerItemProps = {
|
||||
fieldName: string;
|
||||
fieldValue: string;
|
||||
};
|
||||
|
||||
export const AnswerItem: FC<AnswerItemProps> = ({ fieldName, fieldValue }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "10px 20px",
|
||||
height: "140px",
|
||||
borderBottom: `1px solid ${theme.palette.background.default}`,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 500,
|
||||
color: theme.palette.grey3.main,
|
||||
}}
|
||||
>
|
||||
{fieldName}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "14px",
|
||||
fontWeight: 400,
|
||||
color: theme.palette.grey3.main,
|
||||
}}
|
||||
>
|
||||
{fieldValue}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@ -3,25 +3,30 @@ import { FC } from "react";
|
||||
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
|
||||
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
|
||||
import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup";
|
||||
import { MinifiedData } from "../types";
|
||||
|
||||
type AmoStep3Props = {
|
||||
type Props = {
|
||||
users: MinifiedData[];
|
||||
steps: MinifiedData[];
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
selectedStepsPerformer: string | null;
|
||||
setSelectedStepsPerformer: (value: string | null) => void;
|
||||
selectedDealUser: string | null;
|
||||
setSelectedDealPerformer: (value: string | null) => void;
|
||||
selectedStep: string | null;
|
||||
setSelectedStep: (value: string | null) => void;
|
||||
pipelineId: string | null;
|
||||
};
|
||||
|
||||
export const AmoStep3: FC<AmoStep3Props> = ({
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
selectedStepsPerformer,
|
||||
setSelectedStepsPerformer,
|
||||
export const PipelineSteps: FC<Props> = ({
|
||||
users,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
|
||||
steps,
|
||||
selectedStep,
|
||||
setSelectedStep,
|
||||
pipelineId,
|
||||
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
@ -39,9 +44,10 @@ export const AmoStep3: FC<AmoStep3Props> = ({
|
||||
>
|
||||
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
|
||||
<CustomSelect
|
||||
selectedItem={selectedStepsPerformer}
|
||||
type={"typeUsers"}
|
||||
setSelectedItem={setSelectedStepsPerformer}
|
||||
items={users}
|
||||
selectedItemId={selectedDealUser}
|
||||
setSelectedItem={setSelectedDealPerformer}
|
||||
handleScroll={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
@ -53,11 +59,10 @@ export const AmoStep3: FC<AmoStep3Props> = ({
|
||||
}}
|
||||
>
|
||||
<CustomRadioGroup
|
||||
// @ts-ignore
|
||||
pipelineId={pipelineId}
|
||||
type={"typeSteps"}
|
||||
selectedValue={selectedStep}
|
||||
setSelectedValue={setSelectedStep}
|
||||
items={steps}
|
||||
selectedItemId={selectedStep}
|
||||
setSelectedItem={setSelectedStep}
|
||||
handleScroll={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
@ -3,24 +3,30 @@ import { FC } from "react";
|
||||
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
|
||||
import { CustomSelect } from "../../../../components/CustomSelect/CustomSelect";
|
||||
import { CustomRadioGroup } from "../../../../components/CustomRadioGroup/CustomRadioGroup";
|
||||
import { MinifiedData } from "../types";
|
||||
|
||||
type AmoStep2Props = {
|
||||
type Props = {
|
||||
pipelines: MinifiedData[];
|
||||
users: MinifiedData[];
|
||||
handlePrevStep: () => void;
|
||||
handleNextStep: () => void;
|
||||
selectedPipelinePerformer: string | null;
|
||||
setSelectedPipelinePerformer: (value: string | null) => void;
|
||||
selectedDealUser: string | null;
|
||||
setSelectedDealPerformer: (value: string | null) => void;
|
||||
selectedPipeline: string | null;
|
||||
setSelectedPipeline: (value: string | null) => void;
|
||||
|
||||
};
|
||||
|
||||
export const AmoStep2: FC<AmoStep2Props> = ({
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
selectedPipelinePerformer,
|
||||
setSelectedPipelinePerformer,
|
||||
export const Pipelines: FC<Props> = ({
|
||||
pipelines,
|
||||
selectedPipeline,
|
||||
setSelectedPipeline,
|
||||
|
||||
users,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
|
||||
handlePrevStep,
|
||||
handleNextStep,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
@ -37,9 +43,10 @@ export const AmoStep2: FC<AmoStep2Props> = ({
|
||||
>
|
||||
<Box sx={{ width: "100%", marginTop: "20px", zIndex: 3 }}>
|
||||
<CustomSelect
|
||||
selectedItem={selectedPipelinePerformer}
|
||||
setSelectedItem={setSelectedPipelinePerformer}
|
||||
type={"typeUsers"}
|
||||
items={users}
|
||||
selectedItemId={selectedDealUser}
|
||||
setSelectedItem={setSelectedDealPerformer}
|
||||
handleScroll={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
@ -51,9 +58,10 @@ export const AmoStep2: FC<AmoStep2Props> = ({
|
||||
}}
|
||||
>
|
||||
<CustomRadioGroup
|
||||
selectedValue={selectedPipeline}
|
||||
setSelectedValue={setSelectedPipeline}
|
||||
type={"typePipelines"}
|
||||
items={pipelines}
|
||||
selectedItemId={selectedPipeline}
|
||||
setSelectedItem={setSelectedPipeline}
|
||||
handleScroll={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
@ -2,32 +2,28 @@ import { FC } from "react";
|
||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { StepButtonsBlock } from "../StepButtonsBlock/StepButtonsBlock";
|
||||
import { SettingItem } from "./SettingItem/SettingItem";
|
||||
import { TQuestionEntity, TTags } from "../AmoCRMModal";
|
||||
import { SelectedQuestions, SelectedTags } from "../types";
|
||||
|
||||
type AmoSettingsBlockProps = {
|
||||
stepTitles: string[];
|
||||
setStep: (value: number) => void;
|
||||
setIsSettingsBlock: (value: boolean) => void;
|
||||
selectedFunnelPerformer: string | null;
|
||||
selectedFunnel: string | null;
|
||||
selectedStagePerformer: string | null;
|
||||
selectedStage: string | null;
|
||||
selectedDealPerformer: string | null;
|
||||
questionEntity: TQuestionEntity;
|
||||
tags: TTags;
|
||||
selectedDealUser: string | null;
|
||||
selectedQuestions: SelectedQuestions;
|
||||
selectedTags: SelectedTags;
|
||||
};
|
||||
|
||||
export const AmoSettingsBlock: FC<AmoSettingsBlockProps> = ({
|
||||
stepTitles,
|
||||
setStep,
|
||||
setIsSettingsBlock,
|
||||
selectedFunnelPerformer,
|
||||
selectedFunnel,
|
||||
selectedStagePerformer,
|
||||
selectedDealPerformer,
|
||||
selectedDealUser,
|
||||
selectedStage,
|
||||
questionEntity,
|
||||
tags,
|
||||
selectedQuestions,
|
||||
selectedTags,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
@ -61,13 +57,11 @@ export const AmoSettingsBlock: FC<AmoSettingsBlockProps> = ({
|
||||
title={title}
|
||||
setIsSettingsBlock={setIsSettingsBlock}
|
||||
setStep={setStep}
|
||||
selectedFunnelPerformer={selectedFunnelPerformer}
|
||||
selectedDealUser={selectedDealUser}
|
||||
selectedFunnel={selectedFunnel}
|
||||
selectedStagePerformer={selectedStagePerformer}
|
||||
selectedDealPerformer={selectedDealPerformer}
|
||||
selectedStage={selectedStage}
|
||||
questionEntity={questionEntity}
|
||||
tags={tags}
|
||||
selectedQuestions={selectedQuestions}
|
||||
selectedTags={selectedTags}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
|
||||
@ -4,7 +4,7 @@ import { Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { SettingItemHeader } from "./SettingItemHeader/SettingItemHeader";
|
||||
import { ResponsiblePerson } from "./ResponsiblePerson/ResponsiblePerson";
|
||||
import { SelectedParameter } from "./SelectedParameter/SelectedParameter";
|
||||
import { TQuestionEntity, TTags } from "../../AmoCRMModal";
|
||||
import { SelectedQuestions, SelectedTags } from "../../types";
|
||||
|
||||
type SettingItemProps = {
|
||||
step: number;
|
||||
@ -14,10 +14,10 @@ type SettingItemProps = {
|
||||
selectedFunnelPerformer: string | null;
|
||||
selectedFunnel: string | null;
|
||||
selectedStagePerformer: string | null;
|
||||
selectedDealPerformer: string | null;
|
||||
selectedDealUser: string | null;
|
||||
selectedStage: string | null;
|
||||
questionEntity: TQuestionEntity;
|
||||
tags: TTags;
|
||||
selectedQuestions: SelectedQuestions;
|
||||
selectedTags: SelectedTags;
|
||||
};
|
||||
|
||||
export const SettingItem: FC<SettingItemProps> = ({
|
||||
@ -28,14 +28,13 @@ export const SettingItem: FC<SettingItemProps> = ({
|
||||
selectedFunnelPerformer,
|
||||
selectedFunnel,
|
||||
selectedStagePerformer,
|
||||
selectedDealPerformer,
|
||||
selectedDealUser,
|
||||
selectedStage,
|
||||
questionEntity,
|
||||
tags,
|
||||
selectedQuestions,
|
||||
selectedTags,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
if (step === 0) {
|
||||
return;
|
||||
}
|
||||
@ -44,7 +43,7 @@ export const SettingItem: FC<SettingItemProps> = ({
|
||||
if (step === 1) {
|
||||
return (
|
||||
<>
|
||||
<ResponsiblePerson performer={selectedFunnelPerformer} />
|
||||
<ResponsiblePerson performer={selectedDealUser} />
|
||||
<SelectedParameter parameter={selectedFunnel} />
|
||||
</>
|
||||
);
|
||||
@ -52,7 +51,7 @@ export const SettingItem: FC<SettingItemProps> = ({
|
||||
if (step === 2) {
|
||||
return (
|
||||
<>
|
||||
<ResponsiblePerson performer={selectedStagePerformer} />
|
||||
<ResponsiblePerson performer={selectedDealUser} />
|
||||
<SelectedParameter parameter={selectedStage} />
|
||||
</>
|
||||
);
|
||||
@ -60,14 +59,12 @@ export const SettingItem: FC<SettingItemProps> = ({
|
||||
if (step === 3) {
|
||||
return (
|
||||
<>
|
||||
<ResponsiblePerson performer={selectedDealPerformer} />
|
||||
<ResponsiblePerson performer={selectedDealUser} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
if (step === 4) {
|
||||
const isFilled = Object.values(questionEntity).some(
|
||||
(array) => array.length > 0,
|
||||
);
|
||||
const isFilled = Object.values(selectedTags).some((array) => array.length > 0);
|
||||
const status = isFilled ? "Заполнено" : "Не заполнено";
|
||||
|
||||
return (
|
||||
@ -97,7 +94,7 @@ export const SettingItem: FC<SettingItemProps> = ({
|
||||
);
|
||||
}
|
||||
if (step === 5) {
|
||||
const isFilled = Object.values(tags).some((array) => array.length > 0);
|
||||
const isFilled = Object.values(selectedQuestions).some((array) => array.length > 0);
|
||||
const status = isFilled ? "Заполнено" : "Не заполнено";
|
||||
|
||||
return (
|
||||
@ -132,10 +129,10 @@ export const SettingItem: FC<SettingItemProps> = ({
|
||||
selectedFunnelPerformer,
|
||||
selectedFunnel,
|
||||
selectedStagePerformer,
|
||||
selectedDealPerformer,
|
||||
selectedDealUser,
|
||||
selectedStage,
|
||||
questionEntity,
|
||||
tags,
|
||||
selectedQuestions,
|
||||
selectedTags,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
||||
18
src/pages/IntegrationsPage/IntegrationsModal/types.ts
Normal file
18
src/pages/IntegrationsPage/IntegrationsModal/types.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export type TagKeys = "Contact" | "Company" | "Lead" | "Customer";
|
||||
export type SelectedTags = Record<TagKeys, number[]>;
|
||||
|
||||
export type QuestionKeys = "Company" | "Lead" | "Customer";
|
||||
export type SelectedQuestions = Record<QuestionKeys, string[]>;
|
||||
|
||||
export type MinifiedData = {
|
||||
id: string;
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
entity?: TagKeys;
|
||||
};
|
||||
|
||||
export type TagQuestionHC = {
|
||||
scope: QuestionKeys | TagKeys;
|
||||
id: string;
|
||||
type: "question" | "tag";
|
||||
};
|
||||
@ -0,0 +1,250 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import type { TagKeys, SelectedTags, QuestionKeys, SelectedQuestions, MinifiedData } from "./types";
|
||||
import {
|
||||
AccountResponse,
|
||||
getIntegrationRules,
|
||||
getPipelines,
|
||||
getSteps,
|
||||
getTags,
|
||||
getUsers,
|
||||
getAccount,
|
||||
FieldsRule,
|
||||
} from "@/api/integration";
|
||||
|
||||
const SIZE = 75;
|
||||
|
||||
interface Props {
|
||||
isModalOpen: boolean;
|
||||
isTryRemoveAccount: boolean;
|
||||
quizID: number;
|
||||
}
|
||||
|
||||
export const useAmoIntegration = ({ isModalOpen, isTryRemoveAccount, quizID }: Props) => {
|
||||
const [isloadingPage, setIsLoadingPage] = useState<boolean>(true);
|
||||
const [firstRules, setFirstRules] = useState<boolean>(false);
|
||||
const [accountInfo, setAccountInfo] = useState<AccountResponse | null>(null);
|
||||
|
||||
const [arrayOfPipelines, setArrayOfPipelines] = useState<MinifiedData[]>([]);
|
||||
const [arrayOfPipelinesSteps, setArrayOfPipelinesSteps] = useState<MinifiedData[]>([]);
|
||||
const [arrayOfUsers, setArrayOfUsers] = useState<MinifiedData[]>([]);
|
||||
const [arrayOfTags, setArrayOfTags] = useState<MinifiedData[]>([]);
|
||||
|
||||
const [selectedPipeline, setSelectedPipeline] = useState<string | null>(null);
|
||||
const [selectedPipelineStep, setSelectedPipelineStep] = useState<string | null>(null);
|
||||
const [selectedDealUser, setSelectedDealPerformer] = useState<string | null>(null);
|
||||
|
||||
const [questionsBackend, setQuestionsBackend] = useState<FieldsRule>({} as FieldsRule);
|
||||
const [selectedTags, setSelectedTags] = useState<SelectedTags>({
|
||||
Lead: [],
|
||||
Contact: [],
|
||||
Company: [],
|
||||
Customer: [],
|
||||
});
|
||||
const [selectedQuestions, setSelectedQuestions] = useState<SelectedQuestions>({
|
||||
Lead: [],
|
||||
Company: [],
|
||||
Customer: [],
|
||||
});
|
||||
|
||||
const [pageOfPipelines, setPageOfPipelines] = useState(1);
|
||||
const [pageOfPipelinesSteps, setPageOfPipelinesSteps] = useState(1);
|
||||
const [pageOfUsers, setPageOfUsers] = useState(1);
|
||||
const [pageOfTags, setPageOfTags] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
if (isModalOpen && !isTryRemoveAccount) {
|
||||
const fetchAccountRules = async () => {
|
||||
setIsLoadingPage(true);
|
||||
const [account, accountError] = await getAccount();
|
||||
|
||||
if (accountError) {
|
||||
if (!accountError.includes("Not Found")) enqueueSnackbar(accountError);
|
||||
setAccountInfo(null);
|
||||
}
|
||||
if (account) {
|
||||
setAccountInfo(account);
|
||||
}
|
||||
const [settingsResponse, rulesError] = await getIntegrationRules(quizID.toString());
|
||||
|
||||
if (rulesError) {
|
||||
if (rulesError === "first") setFirstRules(true);
|
||||
if (!rulesError.includes("Not Found") && !rulesError.includes("first")) enqueueSnackbar(rulesError);
|
||||
}
|
||||
if (settingsResponse) {
|
||||
if (settingsResponse.PipelineID) setSelectedPipeline(settingsResponse.PipelineID.toString());
|
||||
if (settingsResponse.StepID) setSelectedPipelineStep(settingsResponse.StepID.toString());
|
||||
if (settingsResponse.PerformerID) setSelectedDealPerformer(settingsResponse.PerformerID.toString());
|
||||
|
||||
if (Boolean(settingsResponse.FieldsRule) && Object.keys(settingsResponse?.FieldsRule).length > 0) {
|
||||
const gottenQuestions = { ...selectedQuestions };
|
||||
setQuestionsBackend(settingsResponse.FieldsRule);
|
||||
|
||||
for (let key in settingsResponse.FieldsRule) {
|
||||
if (
|
||||
settingsResponse.FieldsRule[key as QuestionKeys] !== null &&
|
||||
Array.isArray(settingsResponse.FieldsRule[key as QuestionKeys])
|
||||
) {
|
||||
const gottenList = settingsResponse.FieldsRule[key as QuestionKeys];
|
||||
|
||||
if (gottenList !== null) gottenQuestions[key as QuestionKeys] = Object.keys(gottenList[0].QuestionID);
|
||||
}
|
||||
}
|
||||
setSelectedQuestions(gottenQuestions);
|
||||
}
|
||||
|
||||
if (Boolean(settingsResponse.TagsToAdd) && Object.keys(settingsResponse.TagsToAdd).length > 0) {
|
||||
const gottenTags = { ...selectedTags };
|
||||
|
||||
for (let key in settingsResponse.TagsToAdd) {
|
||||
const gottenList = settingsResponse.TagsToAdd[key as TagKeys];
|
||||
if (gottenList !== null && Array.isArray(gottenList)) {
|
||||
gottenTags[key as TagKeys] = gottenList.map((e) => e.toString());
|
||||
}
|
||||
}
|
||||
setSelectedTags(gottenTags);
|
||||
}
|
||||
setFirstRules(false);
|
||||
}
|
||||
setIsLoadingPage(false);
|
||||
};
|
||||
|
||||
fetchAccountRules();
|
||||
} else {
|
||||
//Вот по-хорошему компонент должен размонтироваться и стереть всё. Но это будет сделано позже
|
||||
setArrayOfPipelines([]);
|
||||
setArrayOfPipelinesSteps([]);
|
||||
setArrayOfUsers([]);
|
||||
setArrayOfTags([]);
|
||||
setSelectedPipeline(null);
|
||||
setSelectedPipelineStep(null);
|
||||
setSelectedDealPerformer(null);
|
||||
setQuestionsBackend({} as FieldsRule);
|
||||
setSelectedTags({
|
||||
Lead: [],
|
||||
Contact: [],
|
||||
Company: [],
|
||||
Customer: [],
|
||||
});
|
||||
setSelectedQuestions({
|
||||
Lead: [],
|
||||
Company: [],
|
||||
Customer: [],
|
||||
});
|
||||
setPageOfPipelines(1);
|
||||
setPageOfPipelinesSteps(1);
|
||||
setPageOfUsers(1);
|
||||
setPageOfTags(1);
|
||||
}
|
||||
}, [isModalOpen, isTryRemoveAccount]);
|
||||
|
||||
useEffect(() => {
|
||||
getPipelines({
|
||||
page: pageOfPipelines,
|
||||
size: SIZE,
|
||||
}).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
const minifiedPipelines: MinifiedData[] = [];
|
||||
|
||||
response.items.forEach((step) => {
|
||||
minifiedPipelines.push({
|
||||
id: step.AmoID.toString(),
|
||||
title: step.Name,
|
||||
});
|
||||
});
|
||||
setArrayOfPipelines((prevItems) => [...prevItems, ...minifiedPipelines]);
|
||||
setPageOfPipelinesSteps(1);
|
||||
}
|
||||
});
|
||||
}, [pageOfPipelines]);
|
||||
useEffect(() => {
|
||||
const oldData = pageOfPipelinesSteps === 1 ? [] : arrayOfPipelinesSteps;
|
||||
if (selectedPipeline !== null)
|
||||
getSteps({
|
||||
page: pageOfPipelinesSteps,
|
||||
size: SIZE,
|
||||
pipelineId: Number(selectedPipeline),
|
||||
}).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
const minifiedSteps: MinifiedData[] = [];
|
||||
|
||||
response.items.forEach((step) => {
|
||||
minifiedSteps.push({
|
||||
id: step.AmoID.toString(),
|
||||
title: step.Name,
|
||||
});
|
||||
});
|
||||
setArrayOfPipelinesSteps([...oldData, ...minifiedSteps]);
|
||||
}
|
||||
});
|
||||
}, [selectedPipeline, pageOfPipelinesSteps]);
|
||||
useEffect(() => {
|
||||
getUsers({
|
||||
page: pageOfUsers,
|
||||
size: SIZE,
|
||||
}).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
const minifiedUsers: MinifiedData[] = [];
|
||||
|
||||
response.items.forEach((step) => {
|
||||
minifiedUsers.push({
|
||||
id: step.amoUserID.toString(),
|
||||
title: step.name,
|
||||
});
|
||||
});
|
||||
setArrayOfUsers((prevItems) => [...prevItems, ...minifiedUsers]);
|
||||
}
|
||||
});
|
||||
}, [pageOfUsers]);
|
||||
useEffect(() => {
|
||||
getTags({
|
||||
page: pageOfTags,
|
||||
size: SIZE,
|
||||
}).then(([response]) => {
|
||||
if (response && response.items !== null) {
|
||||
const minifiedTags: MinifiedData[] = [];
|
||||
|
||||
response.items.forEach((step) => {
|
||||
minifiedTags.push({
|
||||
id: step.AmoID.toString(),
|
||||
title: step.Name,
|
||||
entity:
|
||||
step.Entity === "leads"
|
||||
? "Lead"
|
||||
: step.Entity === "contacts"
|
||||
? "Contact"
|
||||
: step.Entity === "companies"
|
||||
? "Company"
|
||||
: "Customer",
|
||||
});
|
||||
});
|
||||
setArrayOfTags((prevItems) => [...prevItems, ...minifiedTags]);
|
||||
}
|
||||
});
|
||||
}, [pageOfTags]);
|
||||
|
||||
return {
|
||||
isloadingPage,
|
||||
firstRules,
|
||||
accountInfo,
|
||||
arrayOfPipelines,
|
||||
arrayOfPipelinesSteps,
|
||||
arrayOfUsers,
|
||||
arrayOfTags,
|
||||
selectedPipeline,
|
||||
setSelectedPipeline,
|
||||
selectedPipelineStep,
|
||||
setSelectedPipelineStep,
|
||||
selectedDealUser,
|
||||
setSelectedDealPerformer,
|
||||
questionsBackend,
|
||||
selectedTags,
|
||||
setSelectedTags,
|
||||
selectedQuestions,
|
||||
setSelectedQuestions,
|
||||
setPageOfPipelines,
|
||||
setPageOfPipelinesSteps,
|
||||
setPageOfUsers,
|
||||
setPageOfTags,
|
||||
};
|
||||
};
|
||||
@ -11,13 +11,13 @@ import { useCurrentQuiz } from "@/stores/quizes/hooks";
|
||||
const AnalyticsModal = lazy(() =>
|
||||
import("./AnalyticsModal/AnalyticsModal").then((module) => ({
|
||||
default: module.AnalyticsModal,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
|
||||
const AmoCRMModal = lazy(() =>
|
||||
import("../IntegrationsModal/AmoCRMModal").then((module) => ({
|
||||
default: module.AmoCRMModal,
|
||||
})),
|
||||
}))
|
||||
);
|
||||
|
||||
type PartnersBoardProps = {
|
||||
@ -122,7 +122,7 @@ export const PartnersBoard: FC<PartnersBoardProps> = ({
|
||||
/>
|
||||
</Suspense>
|
||||
)}
|
||||
{companyName && (
|
||||
{companyName && isAmoCrmModalOpen && (
|
||||
<Suspense>
|
||||
<AmoCRMModal
|
||||
isModalOpen={isAmoCrmModalOpen}
|
||||
|
||||
@ -10,21 +10,11 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
addQuestionVariant,
|
||||
deleteQuestionVariant,
|
||||
setQuestionVariantField,
|
||||
} from "@root/questions/actions";
|
||||
import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField } from "@root/questions/actions";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import {
|
||||
memo,
|
||||
type ChangeEvent,
|
||||
type FC,
|
||||
type KeyboardEvent,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { memo, type ChangeEvent, type FC, type KeyboardEvent, type ReactNode } from "react";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import type { QuestionVariant } from "../../../model/questionTypes/shared";
|
||||
import type { QuestionVariant } from "@frontend/squzanswerer";
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||
|
||||
@ -39,22 +29,20 @@ type AnswerItemProps = {
|
||||
};
|
||||
|
||||
const AnswerItem = memo<AnswerItemProps>(
|
||||
({
|
||||
index,
|
||||
variant,
|
||||
questionId,
|
||||
largeCheck = false,
|
||||
additionalContent,
|
||||
additionalMobile,
|
||||
disableKeyDown,
|
||||
}) => {
|
||||
({ index, variant, questionId, largeCheck = false, additionalContent, additionalMobile, disableKeyDown }) => {
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
return (
|
||||
<Draggable draggableId={String(index)} index={index}>
|
||||
<Draggable
|
||||
draggableId={String(index)}
|
||||
index={index}
|
||||
>
|
||||
{(provided) => (
|
||||
<Box ref={provided.innerRef} {...provided.draggableProps}>
|
||||
<Box
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
>
|
||||
<FormControl
|
||||
key={index}
|
||||
fullWidth
|
||||
@ -74,12 +62,7 @@ const AnswerItem = memo<AnswerItemProps>(
|
||||
multiline={largeCheck}
|
||||
onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
|
||||
if (target.value.length <= 1000) {
|
||||
setQuestionVariantField(
|
||||
questionId,
|
||||
variant.id,
|
||||
"answer",
|
||||
target.value || " ",
|
||||
);
|
||||
setQuestionVariantField(questionId, variant.id, "answer", target.value || " ");
|
||||
}
|
||||
}}
|
||||
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
@ -96,9 +79,7 @@ const AnswerItem = memo<AnswerItemProps>(
|
||||
{...provided.dragHandleProps}
|
||||
position="start"
|
||||
>
|
||||
<PointsIcon
|
||||
style={{ color: "#9A9AAF", fontSize: "30px" }}
|
||||
/>
|
||||
<PointsIcon style={{ color: "#9A9AAF", fontSize: "30px" }} />
|
||||
</InputAdornment>
|
||||
{additionalContent}
|
||||
</>
|
||||
@ -107,9 +88,7 @@ const AnswerItem = memo<AnswerItemProps>(
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
sx={{ padding: "0" }}
|
||||
onClick={() =>
|
||||
deleteQuestionVariant(questionId, variant.id)
|
||||
}
|
||||
onClick={() => deleteQuestionVariant(questionId, variant.id)}
|
||||
>
|
||||
<DeleteIcon
|
||||
style={{
|
||||
@ -153,7 +132,7 @@ const AnswerItem = memo<AnswerItemProps>(
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
AnswerItem.displayName = "AnswerItem";
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { clearRuleForAll } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateRootContentId } from "@root/quizes/actions";
|
||||
@ -14,18 +14,18 @@ import {
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
import type { Core } from "cytoscape";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import CytoscapeComponent from "react-cytoscapejs";
|
||||
import { withErrorBoundary } from "react-error-boundary";
|
||||
import { DeleteNodeModal } from "../DeleteNodeModal";
|
||||
import CsNodeButtons from "./CsNodeButtons";
|
||||
import { InfoBanner } from "./InfoBanner/InfoBanner";
|
||||
import { PositionControl } from "./PositionControl/PositionControl";
|
||||
import { ZoomControl } from "./ZoomControl/ZoomControl";
|
||||
import { addNode, layoutOptions, storeToNodes } from "./helper";
|
||||
import { useRemoveNode } from "./hooks/useRemoveNode";
|
||||
import "./style/styles.css";
|
||||
import { stylesheet } from "./style/stylesheet";
|
||||
import { Box, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { InfoBanner } from "./InfoBanner/InfoBanner";
|
||||
import { PositionControl } from "./PositionControl/PositionControl";
|
||||
import { ZoomControl } from "./ZoomControl/ZoomControl";
|
||||
|
||||
function CsComponent() {
|
||||
const theme = useTheme();
|
||||
@ -93,7 +93,10 @@ function CsComponent() {
|
||||
width: "100%",
|
||||
}}
|
||||
>
|
||||
<CsNodeButtons csElements={csElements} cyRef={cyRef} />
|
||||
<CsNodeButtons
|
||||
csElements={csElements}
|
||||
cyRef={cyRef}
|
||||
/>
|
||||
<Box sx={{ position: "relative" }}>
|
||||
{isBannerVisible && <InfoBanner setBannerVisible={setBannerVisible} />}
|
||||
<PositionControl cyRef={cyRef} />
|
||||
|
||||
@ -1,15 +1,7 @@
|
||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||
import {
|
||||
AnyTypedQuizQuestion,
|
||||
QuestionBranchingRule,
|
||||
QuestionBranchingRuleMain,
|
||||
UntypedQuizQuestion,
|
||||
} from "@model/questionTypes/shared";
|
||||
import {
|
||||
createResult,
|
||||
getQuestionByContentId,
|
||||
updateQuestion,
|
||||
} from "@root/questions/actions";
|
||||
import { AnyTypedQuizQuestion, QuestionBranchingRule, QuestionBranchingRuleMain } from "@frontend/squzanswerer";
|
||||
import { QuizQuestionResult } from "@frontend/squzanswerer";
|
||||
import { UntypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { createResult, getQuestionByContentId, updateQuestion } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useQuizStore } from "@root/quizes/store";
|
||||
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
||||
@ -61,10 +53,7 @@ export const storeToNodes = (questions: AnyTypedQuizQuestion[]) => {
|
||||
const parentQuestion = {
|
||||
...getQuestionByContentId(question.content.rule.parentId),
|
||||
} as AnyTypedQuizQuestion;
|
||||
let label =
|
||||
question.title === "" || question.title === " "
|
||||
? "noname"
|
||||
: question.title;
|
||||
let label = question.title === "" || question.title === " " ? "noname" : question.title;
|
||||
label = label.trim().replace(/\s+/g, " ");
|
||||
if (label.length > 20) label = label.slice(0, 20).toLowerCase() + "…";
|
||||
|
||||
@ -73,10 +62,7 @@ export const storeToNodes = (questions: AnyTypedQuizQuestion[]) => {
|
||||
isRoot: question.content.rule.parentId === "root",
|
||||
id: question.content.id,
|
||||
label,
|
||||
qtype:
|
||||
question.content.rule.parentId === "root"
|
||||
? "root"
|
||||
: parentQuestion.type,
|
||||
qtype: question.content.rule.parentId === "root" ? "root" : parentQuestion.type,
|
||||
type: question.type,
|
||||
children: question.content.rule.children.length,
|
||||
},
|
||||
@ -130,15 +116,10 @@ export function clearDataAfterAddNode({
|
||||
//смотрим не добавлен ли родителю result. Если да - делаем его неактивным. Веточкам result не нужен
|
||||
useQuestionsStore
|
||||
.getState()
|
||||
.questions.filter(
|
||||
(question): question is QuizQuestionResult => question.type === "result",
|
||||
)
|
||||
.questions.filter((question): question is QuizQuestionResult => question.type === "result")
|
||||
.forEach((targetQuestion) => {
|
||||
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {
|
||||
updateQuestion<QuizQuestionResult>(
|
||||
targetQuestion.id,
|
||||
(q) => (q.content.usage = false),
|
||||
);
|
||||
updateQuestion<QuizQuestionResult>(targetQuestion.id, (q) => (q.content.usage = false));
|
||||
}
|
||||
});
|
||||
|
||||
@ -156,14 +137,9 @@ export function clearDataAfterAddNode({
|
||||
//предупреждаем родителя о новом потомке (если он ещё не знает о нём)
|
||||
if (!parentQuestion.content.rule.children.includes(targetQuestion.content.id))
|
||||
updateQuestion(parentNodeContentId, (question) => {
|
||||
question.content.rule.children = [
|
||||
...question.content.rule.children,
|
||||
targetQuestion.content.id,
|
||||
];
|
||||
question.content.rule.children = [...question.content.rule.children, targetQuestion.content.id];
|
||||
//единственному ребёнку даём дефолт по-умолчанию
|
||||
question.content.rule.default = noChild
|
||||
? targetQuestion.content.id
|
||||
: question.content.rule.default;
|
||||
question.content.rule.default = noChild ? targetQuestion.content.id : question.content.rule.default;
|
||||
});
|
||||
|
||||
if (!noChild) {
|
||||
@ -194,9 +170,7 @@ export function clearDataAfterRemoveNode({
|
||||
|
||||
//Делаем результат родителя активным
|
||||
const parentResult = trashQuestions.find(
|
||||
(q): q is QuizQuestionResult =>
|
||||
q.type === "result" &&
|
||||
q.content.rule.parentId === parentQuestionContentId,
|
||||
(q): q is QuizQuestionResult => q.type === "result" && q.content.rule.parentId === parentQuestionContentId
|
||||
);
|
||||
if (parentResult) {
|
||||
updateQuestion<QuizQuestionResult>(parentResult.content.id, (q) => {
|
||||
@ -212,21 +186,14 @@ export function clearDataAfterRemoveNode({
|
||||
}
|
||||
|
||||
const newChildren = [...parentQuestion.content.rule.children];
|
||||
newChildren.splice(
|
||||
parentQuestion.content.rule.children.indexOf(targetQuestionContentId),
|
||||
1,
|
||||
);
|
||||
newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1);
|
||||
|
||||
const newRule: QuestionBranchingRule = {
|
||||
children: newChildren,
|
||||
default:
|
||||
parentQuestion.content.rule.default === targetQuestionContentId
|
||||
? ""
|
||||
: parentQuestion.content.rule.default,
|
||||
default: parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default,
|
||||
//удаляем условия перехода от родителя к этому вопросу,
|
||||
main: parentQuestion.content.rule.main.filter(
|
||||
(data: QuestionBranchingRuleMain) =>
|
||||
data.next !== targetQuestionContentId,
|
||||
(data: QuestionBranchingRuleMain) => data.next !== targetQuestionContentId
|
||||
),
|
||||
parentId: parentQuestion.content.rule.parentId,
|
||||
};
|
||||
@ -243,8 +210,7 @@ export function calcNodePosition(node: any) {
|
||||
node.removeData("lastChild");
|
||||
|
||||
if (incomming.length === 0) {
|
||||
if (node.cy().data("firstNode") === undefined)
|
||||
node.cy().data("firstNode", "root");
|
||||
if (node.cy().data("firstNode") === undefined) node.cy().data("firstNode", "root");
|
||||
node.data("root", true);
|
||||
const children = node.cy().edges(`[source="${id}"]`).targets();
|
||||
node.data("layer", layer);
|
||||
@ -257,15 +223,10 @@ export function calcNodePosition(node: any) {
|
||||
const task = queue.pop();
|
||||
task.task.data("layer", task.layer);
|
||||
task.task.removeData("subtreeWidth");
|
||||
const children = node
|
||||
.cy()
|
||||
.edges(`[source="${task.task.id()}"]`)
|
||||
.targets();
|
||||
const children = node.cy().edges(`[source="${task.task.id()}"]`).targets();
|
||||
task.task.data("children", children.length);
|
||||
if (children.length !== 0) {
|
||||
children.forEach((n: any) =>
|
||||
queue.push({ task: n, layer: task.layer + 1 }),
|
||||
);
|
||||
children.forEach((n: any) => queue.push({ task: n, layer: task.layer + 1 }));
|
||||
}
|
||||
}
|
||||
queue.push({ parent: node, children: children });
|
||||
@ -291,7 +252,7 @@ export function calcNodePosition(node: any) {
|
||||
|
||||
task?.parent.data(
|
||||
"subtreeWidth",
|
||||
task.children.reduce((p: any, n: any) => p + n.data("subtreeWidth"), 0),
|
||||
task.children.reduce((p: any, n: any) => p + n.data("subtreeWidth"), 0)
|
||||
);
|
||||
}
|
||||
|
||||
@ -366,23 +327,16 @@ export const addNode = ({
|
||||
|
||||
//если есть инфо о выбранном вопросе из модалки - берём родителя из инфо модалки. Иначе из значения дропа
|
||||
const targetQuestion = {
|
||||
...getQuestionByContentId(
|
||||
targetNodeContentId || useUiTools.getState().dragQuestionContentId,
|
||||
),
|
||||
...getQuestionByContentId(targetNodeContentId || useUiTools.getState().dragQuestionContentId),
|
||||
} as AnyTypedQuizQuestion;
|
||||
|
||||
if (Object.keys(targetQuestion).length !== 0 && parentNodeContentId) {
|
||||
clearDataAfterAddNode({ parentNodeContentId, targetQuestion });
|
||||
createResult(useQuizStore.getState().editQuizId, targetQuestion.content.id);
|
||||
} else {
|
||||
enqueueSnackbar(
|
||||
"Добавляемый вопрос не найден. Перетащите вопрос из списка",
|
||||
);
|
||||
enqueueSnackbar("Добавляемый вопрос не найден. Перетащите вопрос из списка");
|
||||
}
|
||||
};
|
||||
|
||||
export const isQuestionProhibited = (parentQType: string) =>
|
||||
parentQType === "text" ||
|
||||
parentQType === "date" ||
|
||||
parentQType === "number" ||
|
||||
parentQType === "page";
|
||||
parentQType === "text" || parentQType === "date" || parentQType === "number" || parentQType === "page";
|
||||
|
||||
@ -1,18 +1,10 @@
|
||||
import { devlog } from "@frontend/kitui";
|
||||
import { QuizQuestionResult } from "@model/questionTypes/result";
|
||||
import {
|
||||
clearRuleForAll,
|
||||
getQuestionByContentId,
|
||||
updateQuestion,
|
||||
} from "@root/questions/actions";
|
||||
import { QuizQuestionResult } from "@frontend/squzanswerer";
|
||||
import { clearRuleForAll, getQuestionByContentId, updateQuestion } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateRootContentId } from "@root/quizes/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import type {
|
||||
CollectionReturnValue,
|
||||
Core,
|
||||
SingularElementArgument,
|
||||
} from "cytoscape";
|
||||
import type { CollectionReturnValue, Core } from "cytoscape";
|
||||
import type { MutableRefObject } from "react";
|
||||
import { clearDataAfterRemoveNode } from "../helper";
|
||||
|
||||
@ -52,11 +44,7 @@ export const useRemoveNode = ({ cyRef }: UseRemoveNodeArgs) => {
|
||||
|
||||
const targetQuestion = getQuestionByContentId(targetNodeContentId);
|
||||
|
||||
if (
|
||||
targetQuestion?.type &&
|
||||
targetQuestion.content.rule.parentId === "root" &&
|
||||
quiz
|
||||
) {
|
||||
if (targetQuestion?.type && targetQuestion.content.rule.parentId === "root" && quiz) {
|
||||
updateRootContentId(quiz.id, "");
|
||||
updateQuestion(targetNodeContentId, (question) => {
|
||||
question.content.rule.parentId = "";
|
||||
@ -71,10 +59,7 @@ export const useRemoveNode = ({ cyRef }: UseRemoveNodeArgs) => {
|
||||
?.toArray()?.[0]
|
||||
?.data()?.source;
|
||||
if (targetNodeContentId && parentQuestionContentId) {
|
||||
if (
|
||||
quiz &&
|
||||
cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0
|
||||
) {
|
||||
if (quiz && cy?.edges(`[source="${parentQuestionContentId}"]`).length === 0) {
|
||||
devlog(parentQuestionContentId);
|
||||
//createFrontResult(quiz.backendId, parentQuestionContentId);
|
||||
}
|
||||
@ -103,8 +88,7 @@ export const useRemoveNode = ({ cyRef }: UseRemoveNodeArgs) => {
|
||||
if (
|
||||
qr.type === "result" &&
|
||||
(deleteNodes.includes(qr.content.rule.parentId || "") ||
|
||||
(targetQuestion?.type &&
|
||||
qr.content.rule.parentId === targetQuestion.content.id))
|
||||
(targetQuestion?.type && qr.content.rule.parentId === targetQuestion.content.id))
|
||||
) {
|
||||
updateQuestion<QuizQuestionResult>(qr.content.id, (q) => {
|
||||
q.content.usage = false;
|
||||
|
||||
@ -11,37 +11,28 @@ import {
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
AnyTypedQuizQuestion,
|
||||
createBranchingRuleMain,
|
||||
} from "../../../model/questionTypes/shared";
|
||||
import { createBranchingRuleMain } from "../../../model/questionTypes/shared";
|
||||
import InfoIcon from "@icons/Info";
|
||||
|
||||
import { TypeSwitch } from "./Settings";
|
||||
import {
|
||||
getQuestionByContentId,
|
||||
getQuestionById,
|
||||
updateQuestion,
|
||||
} from "@root/questions/actions";
|
||||
import { getQuestionByContentId, getQuestionById, updateQuestion } from "@root/questions/actions";
|
||||
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
export default function BranchingQuestions() {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||
const isSmallMobile = useMediaQuery(theme.breakpoints.down(400));
|
||||
const { openedModalSettingsId } = useUiTools();
|
||||
const [targetQuestion, setTargetQuestion] =
|
||||
useState<AnyTypedQuizQuestion | null>(
|
||||
getQuestionById(openedModalSettingsId) ||
|
||||
getQuestionByContentId(openedModalSettingsId),
|
||||
);
|
||||
const [parentQuestion, setParentQuestion] =
|
||||
useState<AnyTypedQuizQuestion | null>(
|
||||
getQuestionByContentId(targetQuestion?.content.rule.parentId),
|
||||
);
|
||||
const [targetQuestion, setTargetQuestion] = useState<AnyTypedQuizQuestion | null>(
|
||||
getQuestionById(openedModalSettingsId) || getQuestionByContentId(openedModalSettingsId)
|
||||
);
|
||||
const [parentQuestion, setParentQuestion] = useState<AnyTypedQuizQuestion | null>(
|
||||
getQuestionByContentId(targetQuestion?.content.rule.parentId)
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (parentQuestion === null) return;
|
||||
@ -70,10 +61,7 @@ export default function BranchingQuestions() {
|
||||
|
||||
const saveData = () => {
|
||||
if (parentQuestion !== null) {
|
||||
updateQuestion(
|
||||
parentQuestion.content.id,
|
||||
(question) => (question.content = parentQuestion.content),
|
||||
);
|
||||
updateQuestion(parentQuestion.content.id, (question) => (question.content = parentQuestion.content));
|
||||
}
|
||||
handleClose();
|
||||
};
|
||||
@ -84,7 +72,10 @@ export default function BranchingQuestions() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal open={openedModalSettingsId !== null} onClose={handleClose}>
|
||||
<Modal
|
||||
open={openedModalSettingsId !== null}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -120,22 +111,14 @@ export default function BranchingQuestions() {
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
maxWidth: isSmallMobile
|
||||
? "250px"
|
||||
: isMobile
|
||||
? "350px"
|
||||
: "450px",
|
||||
maxWidth: isSmallMobile ? "250px" : isMobile ? "350px" : "450px",
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
{targetQuestion.title}
|
||||
</Typography>
|
||||
{isMobile ? (
|
||||
<TooltipClickInfo
|
||||
title={
|
||||
"Настройте условия, при которых данный вопрос будет отображаться в quiz."
|
||||
}
|
||||
/>
|
||||
<TooltipClickInfo title={"Настройте условия, при которых данный вопрос будет отображаться в quiz."} />
|
||||
) : (
|
||||
<Tooltip
|
||||
title="Настройте условия, при которых данный вопрос будет отображаться в quiz."
|
||||
@ -197,10 +180,7 @@ export default function BranchingQuestions() {
|
||||
onClick={() => {
|
||||
const mutate = JSON.parse(JSON.stringify(parentQuestion));
|
||||
mutate.content.rule.main.push(
|
||||
createBranchingRuleMain(
|
||||
targetQuestion.content.id,
|
||||
parentQuestion.content.id,
|
||||
),
|
||||
createBranchingRuleMain(targetQuestion.content.id, parentQuestion.content.id)
|
||||
);
|
||||
setParentQuestion(mutate);
|
||||
}}
|
||||
@ -214,10 +194,7 @@ export default function BranchingQuestions() {
|
||||
sx={{
|
||||
margin: 0,
|
||||
}}
|
||||
checked={
|
||||
parentQuestion.content.rule.default ===
|
||||
targetQuestion.content.id
|
||||
}
|
||||
checked={parentQuestion.content.rule.default === targetQuestion.content.id}
|
||||
onClick={() => {
|
||||
let mutate = JSON.parse(JSON.stringify(parentQuestion));
|
||||
|
||||
@ -227,8 +204,7 @@ export default function BranchingQuestions() {
|
||||
} else {
|
||||
//Изменять чекбокс можно только если много потомков
|
||||
mutate.content.rule.default =
|
||||
parentQuestion.content.rule.default ===
|
||||
targetQuestion.content.id
|
||||
parentQuestion.content.rule.default === targetQuestion.content.id
|
||||
? ""
|
||||
: targetQuestion.content.id;
|
||||
}
|
||||
|
||||
@ -1,40 +1,31 @@
|
||||
import CalendarIcon from "@icons/CalendarIcon";
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import {
|
||||
Box,
|
||||
MenuItem,
|
||||
FormControl,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
MenuItem,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Typography,
|
||||
useTheme,
|
||||
Select,
|
||||
useMediaQuery,
|
||||
IconButton,
|
||||
TextField,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { SelectChangeEvent } from "@mui/material/Select";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
import { QuizQuestionBase } from "model/questionTypes/shared";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { updateQuestion, getQuestionById } from "@root/questions/actions";
|
||||
import { SelectChangeEvent } from "@mui/material/Select";
|
||||
import CalendarIcon from "@icons/CalendarIcon";
|
||||
import { DatePicker } from "@mui/x-date-pickers";
|
||||
import moment from "moment";
|
||||
import { TimePicker } from "@mui/x-date-pickers/TimePicker";
|
||||
import InfoIcon from "@icons/Info";
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import type { AnyTypedQuizQuestion, QuizQuestionNumber } from "@frontend/squzanswerer";
|
||||
|
||||
import type { AnyTypedQuizQuestion } from "../../../model/questionTypes/shared";
|
||||
import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
|
||||
|
||||
const CONDITIONS = [
|
||||
"Все условия обязательны",
|
||||
"Обязательно хотя бы одно условие",
|
||||
];
|
||||
const CONDITIONS = ["Все условия обязательны", "Обязательно хотя бы одно условие"];
|
||||
interface Props {
|
||||
parentQuestion: AnyTypedQuizQuestion;
|
||||
targetQuestion: AnyTypedQuizQuestion;
|
||||
@ -43,12 +34,7 @@ interface Props {
|
||||
}
|
||||
|
||||
// Этот компонент вызывается 1 раз на каждое условие родителя для перехода к этому вопросу. Поэтому для изменения стора мы знаем индекс
|
||||
export const TypeSwitch = ({
|
||||
parentQuestion,
|
||||
targetQuestion,
|
||||
ruleIndex,
|
||||
setParentQuestion,
|
||||
}: Props) => {
|
||||
export const TypeSwitch = ({ parentQuestion, targetQuestion, ruleIndex, setParentQuestion }: Props) => {
|
||||
switch (parentQuestion.type) {
|
||||
case "variant":
|
||||
case "images":
|
||||
@ -91,9 +77,7 @@ export const TypeSwitch = ({
|
||||
break;
|
||||
|
||||
case "page":
|
||||
return (
|
||||
<BlockRule text={"У такого родителя может быть только один потомок"} />
|
||||
);
|
||||
return <BlockRule text={"У такого родителя может быть только один потомок"} />;
|
||||
break;
|
||||
|
||||
case "text":
|
||||
@ -147,12 +131,7 @@ export const BlockRule = ({ text }: { text: string }) => {
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
const SelectorType = ({
|
||||
parentQuestion,
|
||||
targetQuestion,
|
||||
ruleIndex,
|
||||
setParentQuestion,
|
||||
}: Props) => {
|
||||
const SelectorType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQuestion }: Props) => {
|
||||
const theme = useTheme();
|
||||
const quizId = Number(useParams().quizId);
|
||||
return (
|
||||
@ -174,9 +153,7 @@ const SelectorType = ({
|
||||
pb: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Новое условие
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Новое условие</Typography>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
@ -196,23 +173,15 @@ const SelectorType = ({
|
||||
pb: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Дан ответ
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", pl: "10px" }}>
|
||||
(Укажите один или несколько вариантов)
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Дан ответ</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", pl: "10px" }}>(Укажите один или несколько вариантов)</Typography>
|
||||
</Box>
|
||||
<Select
|
||||
multiple
|
||||
value={
|
||||
parentQuestion.content?.rule?.main[ruleIndex]?.rules[0]?.answers || []
|
||||
}
|
||||
value={parentQuestion.content?.rule?.main[ruleIndex]?.rules[0]?.answers || []}
|
||||
onChange={(event: SelectChangeEvent) => {
|
||||
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion));
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = (
|
||||
event.target as HTMLSelectElement
|
||||
).value;
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = (event.target as HTMLSelectElement).value;
|
||||
setParentQuestion(newParentQuestion);
|
||||
}}
|
||||
sx={{
|
||||
@ -247,7 +216,10 @@ const SelectorType = ({
|
||||
sx={{ color: theme.palette.grey2.main }}
|
||||
value={Boolean(Number(totalIndex))}
|
||||
control={
|
||||
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
label={condition}
|
||||
/>
|
||||
@ -257,12 +229,7 @@ const SelectorType = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const DateInputsType = ({
|
||||
parentQuestion,
|
||||
targetQuestion,
|
||||
ruleIndex,
|
||||
setParentQuestion,
|
||||
}: Props) => {
|
||||
const DateInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQuestion }: Props) => {
|
||||
const theme = useTheme();
|
||||
const upLg = useMediaQuery(theme.breakpoints.up("md"));
|
||||
|
||||
@ -277,8 +244,7 @@ const DateInputsType = ({
|
||||
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion));
|
||||
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0] = time;
|
||||
if (newParentQuestion.content.dateRange)
|
||||
parentQuestion.content.rule.main[ruleIndex].rules[0].answers[1] = time;
|
||||
if (newParentQuestion.content.dateRange) parentQuestion.content.rule.main[ruleIndex].rules[0].answers[1] = time;
|
||||
|
||||
setParentQuestion(newParentQuestion);
|
||||
}, [firstDate, secondDate, firstTime, secondTime]);
|
||||
@ -302,9 +268,7 @@ const DateInputsType = ({
|
||||
pb: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Новое условие
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Новое условие</Typography>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
@ -324,12 +288,8 @@ const DateInputsType = ({
|
||||
pb: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Дан ответ
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", pl: "10px" }}>
|
||||
(Укажите один или несколько вариантов)
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Дан ответ</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", pl: "10px" }}>(Укажите один или несколько вариантов)</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@ -339,15 +299,11 @@ const DateInputsType = ({
|
||||
}}
|
||||
>
|
||||
{parentQuestion.content.dateRange && (
|
||||
<Typography sx={{ color: "#4D4D4D", p: "10px" }}>
|
||||
(Начало периода)
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", p: "10px" }}>(Начало периода)</Typography>
|
||||
)}
|
||||
<DatePicker
|
||||
defaultValue={moment(
|
||||
new Date(
|
||||
parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0],
|
||||
).toLocaleDateString(),
|
||||
new Date(parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]).toLocaleDateString()
|
||||
)}
|
||||
onChange={(dateString) => {
|
||||
const date = dateString?.toDate().toLocaleDateString("ru-RU", {
|
||||
@ -356,9 +312,7 @@ const DateInputsType = ({
|
||||
day: "2-digit",
|
||||
});
|
||||
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion));
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [
|
||||
date,
|
||||
];
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [date];
|
||||
setParentQuestion(newParentQuestion);
|
||||
}}
|
||||
slots={{
|
||||
@ -392,9 +346,7 @@ const DateInputsType = ({
|
||||
/>
|
||||
{parentQuestion.content.time && (
|
||||
<TimePicker
|
||||
value={
|
||||
parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]
|
||||
}
|
||||
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]}
|
||||
sx={{
|
||||
p: "10px",
|
||||
"& .MuiInputBase-root": {
|
||||
@ -424,14 +376,10 @@ const DateInputsType = ({
|
||||
}}
|
||||
>
|
||||
{parentQuestion.content.dateRange && (
|
||||
<Typography sx={{ color: "#4D4D4D", p: "10px" }}>
|
||||
(Конец периода)
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", p: "10px" }}>(Конец периода)</Typography>
|
||||
)}
|
||||
<DatePicker
|
||||
value={
|
||||
parentQuestion.content.rule.main[ruleIndex].rules[0].answers[1]
|
||||
}
|
||||
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[1]}
|
||||
onChange={() => {}}
|
||||
slots={{
|
||||
openPickerIcon: () => <CalendarIcon />,
|
||||
@ -464,9 +412,7 @@ const DateInputsType = ({
|
||||
/>
|
||||
{parentQuestion.content.time && (
|
||||
<TimePicker
|
||||
value={
|
||||
parentQuestion.content.rule.main[ruleIndex].rules[0].answers[1]
|
||||
}
|
||||
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[1]}
|
||||
sx={{
|
||||
p: "10px",
|
||||
"& .MuiInputBase-root": {
|
||||
@ -491,12 +437,7 @@ const DateInputsType = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const NumberInputsType = ({
|
||||
parentQuestion,
|
||||
targetQuestion,
|
||||
ruleIndex,
|
||||
setParentQuestion,
|
||||
}: Props) => {
|
||||
const NumberInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQuestion }: Props) => {
|
||||
const theme = useTheme();
|
||||
const quizId = Number(useParams().quizId);
|
||||
|
||||
@ -519,9 +460,7 @@ const NumberInputsType = ({
|
||||
pb: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Новое условие
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Новое условие</Typography>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
@ -541,35 +480,23 @@ const NumberInputsType = ({
|
||||
pb: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Дан ответ
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", pl: "10px" }}>
|
||||
(Укажите один или несколько вариантов)
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Дан ответ</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", pl: "10px" }}>(Укажите один или несколько вариантов)</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<TextField
|
||||
sx={{ marginTop: "20px", width: "100%" }}
|
||||
placeholder="от"
|
||||
value={
|
||||
parentQuestion.content.rule.main[
|
||||
ruleIndex
|
||||
].rules[0].answers[0]?.split("—")[0]
|
||||
}
|
||||
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]?.split("—")[0]}
|
||||
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newParentQuestion = JSON.parse(
|
||||
JSON.stringify(parentQuestion),
|
||||
);
|
||||
const previousValue =
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0]
|
||||
.answers[0];
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0] =
|
||||
(parentQuestion as QuizQuestionNumber).content.chooseRange
|
||||
? previousValue
|
||||
? `${target.value}—${previousValue.split("—")[1] || 0}`
|
||||
: `${target.value}—0`
|
||||
: target.value;
|
||||
const newParentQuestion = JSON.parse(JSON.stringify(parentQuestion));
|
||||
const previousValue = newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0];
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0] = (parentQuestion as QuizQuestionNumber)
|
||||
.content.chooseRange
|
||||
? previousValue
|
||||
? `${target.value}—${previousValue.split("—")[1] || 0}`
|
||||
: `${target.value}—0`
|
||||
: target.value;
|
||||
setParentQuestion(newParentQuestion);
|
||||
}}
|
||||
/>
|
||||
@ -577,21 +504,11 @@ const NumberInputsType = ({
|
||||
<TextField
|
||||
placeholder="до"
|
||||
sx={{ marginTop: "20px", width: "100%" }}
|
||||
value={
|
||||
parentQuestion.content.rule.main[
|
||||
ruleIndex
|
||||
].rules[0].answers[0]?.split("—")[1]
|
||||
}
|
||||
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]?.split("—")[1]}
|
||||
onChange={({ target }: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newParentQuestion = JSON.parse(
|
||||
JSON.stringify(parentQuestion),
|
||||
);
|
||||
const previousValue =
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0]
|
||||
.answers[0];
|
||||
newParentQuestion.content.rule.main[
|
||||
ruleIndex
|
||||
].rules[0].answers[0] = previousValue
|
||||
const newParentQuestion = JSON.parse(JSON.stringify(parentQuestion));
|
||||
const previousValue = newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0];
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers[0] = previousValue
|
||||
? `${previousValue.split("—")[0] || 0}—${target.value}`
|
||||
: `0—${target.value}`;
|
||||
setParentQuestion(newParentQuestion);
|
||||
@ -602,12 +519,7 @@ const NumberInputsType = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const TextInputsType = ({
|
||||
parentQuestion,
|
||||
targetQuestion,
|
||||
ruleIndex,
|
||||
setParentQuestion,
|
||||
}: Props) => {
|
||||
const TextInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQuestion }: Props) => {
|
||||
const theme = useTheme();
|
||||
const quizId = Number(useParams().quizId);
|
||||
|
||||
@ -630,9 +542,7 @@ const TextInputsType = ({
|
||||
pb: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Новое условие
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Новое условие</Typography>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
@ -652,12 +562,9 @@ const TextInputsType = ({
|
||||
pb: "10px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Дан ответ
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Дан ответ</Typography>
|
||||
<Typography sx={{ color: "#7E2AEA", pl: "10px", fontSize: "12px" }}>
|
||||
(Укажите текст, при совпадении с которым пользователь попадёт на этот
|
||||
вопрос)
|
||||
(Укажите текст, при совпадении с которым пользователь попадёт на этот вопрос)
|
||||
</Typography>
|
||||
</Box>
|
||||
<TextField
|
||||
@ -668,21 +575,14 @@ const TextInputsType = ({
|
||||
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>) => {
|
||||
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion));
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [
|
||||
(event.target as HTMLInputElement).value,
|
||||
];
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [(event.target as HTMLInputElement).value];
|
||||
setParentQuestion(newParentQuestion);
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const FileInputsType = ({
|
||||
parentQuestion,
|
||||
targetQuestion,
|
||||
ruleIndex,
|
||||
setParentQuestion,
|
||||
}: Props) => {
|
||||
const FileInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQuestion }: Props) => {
|
||||
const theme = useTheme();
|
||||
const quizId = Number(useParams().quizId);
|
||||
|
||||
@ -705,9 +605,7 @@ const FileInputsType = ({
|
||||
pb: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Новое условие
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Новое условие</Typography>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
@ -737,15 +635,12 @@ const FileInputsType = ({
|
||||
sx={{
|
||||
margin: 0,
|
||||
}}
|
||||
checked={
|
||||
parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]
|
||||
}
|
||||
checked={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>) => {
|
||||
let newParentQuestion = JSON.parse(
|
||||
JSON.stringify(parentQuestion),
|
||||
);
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers =
|
||||
[(event.target as HTMLInputElement).checked];
|
||||
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion));
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [
|
||||
(event.target as HTMLInputElement).checked,
|
||||
];
|
||||
setParentQuestion(newParentQuestion);
|
||||
}}
|
||||
/>
|
||||
@ -755,12 +650,7 @@ const FileInputsType = ({
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const RatingInputsType = ({
|
||||
parentQuestion,
|
||||
targetQuestion,
|
||||
ruleIndex,
|
||||
setParentQuestion,
|
||||
}: Props) => {
|
||||
const RatingInputsType = ({ parentQuestion, targetQuestion, ruleIndex, setParentQuestion }: Props) => {
|
||||
const theme = useTheme();
|
||||
const quizId = Number(useParams().quizId);
|
||||
|
||||
@ -783,9 +673,7 @@ const RatingInputsType = ({
|
||||
pb: "5px",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>
|
||||
Новое условие
|
||||
</Typography>
|
||||
<Typography sx={{ color: "#4D4D4D", fontWeight: "500" }}>Новое условие</Typography>
|
||||
<IconButton
|
||||
sx={{ borderRadius: "6px", padding: "2px" }}
|
||||
onClick={() => {
|
||||
@ -817,16 +705,9 @@ const RatingInputsType = ({
|
||||
value={parentQuestion.content.rule.main[ruleIndex].rules[0].answers[0]}
|
||||
onChange={(event: React.FormEvent<HTMLInputElement>) => {
|
||||
let newParentQuestion = JSON.parse(JSON.stringify(parentQuestion));
|
||||
let valueNumber = Number(
|
||||
(event.target as HTMLInputElement).value.replace(/[^0-9,\s]/g, ""),
|
||||
);
|
||||
valueNumber =
|
||||
valueNumber > parentQuestion.content.steps
|
||||
? parentQuestion.content.steps
|
||||
: valueNumber;
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [
|
||||
valueNumber,
|
||||
];
|
||||
let valueNumber = Number((event.target as HTMLInputElement).value.replace(/[^0-9,\s]/g, ""));
|
||||
valueNumber = valueNumber > parentQuestion.content.steps ? parentQuestion.content.steps : valueNumber;
|
||||
newParentQuestion.content.rule.main[ruleIndex].rules[0].answers = [valueNumber];
|
||||
setParentQuestion(newParentQuestion);
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -10,12 +10,9 @@ import {
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { useUiTools } from "@root/uiTools/store";
|
||||
import {
|
||||
setModalQuestionTargetContentId,
|
||||
setOpenedModalQuestions,
|
||||
} from "@root/uiTools/actions";
|
||||
import { setModalQuestionTargetContentId, setOpenedModalQuestions } from "@root/uiTools/actions";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
@ -26,20 +23,16 @@ export const BranchingQuestionsModal = () => {
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(650));
|
||||
|
||||
const trashQuestions = useQuestionsStore().questions;
|
||||
const questions = trashQuestions.filter(
|
||||
(question) => question.type !== "result",
|
||||
);
|
||||
const questions = trashQuestions.filter((question) => question.type !== "result");
|
||||
|
||||
const openedModalQuestions = useUiTools(
|
||||
(state) => state.openedModalQuestions,
|
||||
);
|
||||
const openedModalQuestions = useUiTools((state) => state.openedModalQuestions);
|
||||
|
||||
const handleClose = () => {
|
||||
setOpenedModalQuestions(false);
|
||||
};
|
||||
|
||||
const typedQuestions: AnyTypedQuizQuestion[] = questions.filter(
|
||||
(question) => question.type && question.type !== "result",
|
||||
(question) => question.type && question.type !== "result"
|
||||
) as AnyTypedQuizQuestion[];
|
||||
|
||||
if (typedQuestions.length === 0) return <></>;
|
||||
@ -64,7 +57,10 @@ export const BranchingQuestionsModal = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={openedModalQuestions} onClose={handleClose}>
|
||||
<Modal
|
||||
open={openedModalQuestions}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -79,9 +75,7 @@ export const BranchingQuestionsModal = () => {
|
||||
boxShadow: 24,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ width: "100%", background: theme.palette.background.default }}
|
||||
>
|
||||
<Box sx={{ width: "100%", background: theme.palette.background.default }}>
|
||||
<Typography
|
||||
sx={{
|
||||
padding: "25px 0 25px 20px",
|
||||
@ -110,13 +104,7 @@ export const BranchingQuestionsModal = () => {
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck />}
|
||||
icon={
|
||||
question.content.rule.parentId ? (
|
||||
<RoundedCheckedIcon />
|
||||
) : (
|
||||
<RadioIcon />
|
||||
)
|
||||
}
|
||||
icon={question.content.rule.parentId ? <RoundedCheckedIcon /> : <RadioIcon />}
|
||||
/>
|
||||
}
|
||||
label={question.title || "нет заголовка"}
|
||||
@ -125,10 +113,7 @@ export const BranchingQuestionsModal = () => {
|
||||
padding: "8px 12px",
|
||||
margin: 0,
|
||||
width: "100%",
|
||||
backgroundColor:
|
||||
index % 2 === 0
|
||||
? theme.palette.common.white
|
||||
: "rgba(242, 243, 247, 0.5)",
|
||||
backgroundColor: index % 2 === 0 ? theme.palette.common.white : "rgba(242, 243, 247, 0.5)",
|
||||
color: theme.palette.grey3.main,
|
||||
}}
|
||||
/>
|
||||
|
||||
@ -1,19 +1,7 @@
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import type { SxProps } from "@mui/material";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
IconButton,
|
||||
Modal,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
copyQuestion,
|
||||
deleteQuestion,
|
||||
deleteQuestionWithTimeout,
|
||||
} from "@root/questions/actions";
|
||||
import { Box, Button, IconButton, Modal, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { copyQuestion, deleteQuestion, deleteQuestionWithTimeout } from "@root/questions/actions";
|
||||
import { useQuestionsStore } from "@root/questions/store";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
|
||||
@ -24,8 +12,7 @@ import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
|
||||
import Branching from "../../assets/icons/questionsPage/branching";
|
||||
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
|
||||
import { QuestionType } from "@model/question/question";
|
||||
|
||||
import type { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import type { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
interface Props {
|
||||
switchState: string;
|
||||
@ -52,9 +39,7 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
const isWrappMiniButtonSetting = useMediaQuery(theme.breakpoints.down(920));
|
||||
const quiz = useCurrentQuiz();
|
||||
const [openDelete, setOpenDelete] = useState<boolean>(false);
|
||||
const isQuestionFirst = useQuestionsStore(
|
||||
(state) => state.questions[0]?.id === questionId,
|
||||
);
|
||||
const isQuestionFirst = useQuestionsStore((state) => state.questions[0]?.id === questionId);
|
||||
|
||||
if (!quiz) return null;
|
||||
|
||||
@ -70,24 +55,12 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
myFunc?: any;
|
||||
}[] = [
|
||||
{
|
||||
icon: (
|
||||
<SettingIcon
|
||||
color={
|
||||
switchState === "setting" ? "#ffffff" : theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
),
|
||||
icon: <SettingIcon color={switchState === "setting" ? "#ffffff" : theme.palette.grey3.main} />,
|
||||
title: "Настройки",
|
||||
value: "setting",
|
||||
},
|
||||
{
|
||||
icon: (
|
||||
<Branching
|
||||
color={
|
||||
switchState === "branching" ? "#ffffff" : theme.palette.grey3.main
|
||||
}
|
||||
/>
|
||||
),
|
||||
icon: <Branching color={switchState === "branching" ? "#ffffff" : theme.palette.grey3.main} />,
|
||||
title: "Ветвление",
|
||||
value: "branching",
|
||||
myFunc: (question: AnyTypedQuizQuestion) => {
|
||||
@ -119,9 +92,7 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
{buttonSetting.map(({ icon, title, value, myFunc }) => (
|
||||
<Box key={value}>
|
||||
{value === "branching" ? (
|
||||
["page", "text", "date", "number"].includes(
|
||||
questionType,
|
||||
) ? null : (
|
||||
["page", "text", "date", "number"].includes(questionType) ? null : (
|
||||
<MiniButtonSetting
|
||||
key={title}
|
||||
onClick={() => {
|
||||
@ -129,14 +100,8 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
}}
|
||||
sx={{
|
||||
display: quiz.config.type === "form" ? "none" : "flex",
|
||||
backgroundColor:
|
||||
switchState === value
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
switchState === value
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main,
|
||||
backgroundColor: switchState === value ? theme.palette.brightPurple.main : "transparent",
|
||||
color: switchState === value ? "#ffffff" : theme.palette.grey3.main,
|
||||
minWidth: isWrappMiniButtonSetting ? "30px" : "64px",
|
||||
height: "30px",
|
||||
"&:hover": {
|
||||
@ -157,14 +122,8 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
myFunc();
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
switchState === value
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
switchState === value
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main,
|
||||
backgroundColor: switchState === value ? theme.palette.brightPurple.main : "transparent",
|
||||
color: switchState === value ? "#ffffff" : theme.palette.grey3.main,
|
||||
minWidth: isWrappMiniButtonSetting ? "30px" : "64px",
|
||||
height: "30px",
|
||||
"&:hover": {
|
||||
@ -203,9 +162,7 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
if (questionHasParent) {
|
||||
setOpenDelete(true);
|
||||
} else {
|
||||
deleteQuestionWithTimeout(questionId, () =>
|
||||
DeleteFunction(questionId),
|
||||
);
|
||||
deleteQuestionWithTimeout(questionId, () => DeleteFunction(questionId));
|
||||
}
|
||||
}}
|
||||
data-cy="delete-question"
|
||||
@ -213,7 +170,10 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
<DeleteIcon color={"#4D4D4D"} />
|
||||
</IconButton>
|
||||
)}
|
||||
<Modal open={openDelete} onClose={() => setOpenDelete(false)}>
|
||||
<Modal
|
||||
open={openDelete}
|
||||
onClose={() => setOpenDelete(false)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -225,9 +185,12 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6" sx={{ textAlign: "center" }}>
|
||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки
|
||||
потеряют данные ветвления. Вы уверены, что хотите удалить вопрос?
|
||||
<Typography
|
||||
variant="h6"
|
||||
sx={{ textAlign: "center" }}
|
||||
>
|
||||
Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы уверены, что
|
||||
хотите удалить вопрос?
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
@ -248,9 +211,7 @@ const ButtonsOptions = memo<Props>(function ({
|
||||
variant="contained"
|
||||
sx={{ minWidth: "150px" }}
|
||||
onClick={() => {
|
||||
deleteQuestionWithTimeout(questionId, () =>
|
||||
DeleteFunction(questionId),
|
||||
);
|
||||
deleteQuestionWithTimeout(questionId, () => DeleteFunction(questionId));
|
||||
}}
|
||||
>
|
||||
Подтвердить
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
import {
|
||||
Box,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchData from "./switchData";
|
||||
import { QuizQuestionDate } from "@model/questionTypes/date";
|
||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||
import { QuizQuestionDate } from "@frontend/squzanswerer";
|
||||
|
||||
interface Props {
|
||||
question: QuizQuestionDate;
|
||||
@ -18,11 +12,7 @@ interface Props {
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function DataOptions({
|
||||
question,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
export default function DataOptions({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
@ -61,7 +51,10 @@ export default function DataOptions({
|
||||
{isMobile ? (
|
||||
<TooltipClickInfo title={"Выбор даты."} />
|
||||
) : (
|
||||
<Tooltip title="Выбор даты." placement="top">
|
||||
<Tooltip
|
||||
title="Выбор даты."
|
||||
placement="top"
|
||||
>
|
||||
<Box>
|
||||
<InfoIcon />
|
||||
</Box>
|
||||
@ -75,10 +68,13 @@ export default function DataOptions({
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
questionHasParent={question.content.rule.parentId.length !== 0}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
/>
|
||||
<SwitchData switchState={switchState} question={question} />
|
||||
<SwitchData
|
||||
switchState={switchState}
|
||||
question={question}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import type { QuizQuestionDate } from "../../../model/questionTypes/date";
|
||||
import type { QuizQuestionDate } from "@frontend/squzanswerer";
|
||||
|
||||
type SettingsDataProps = {
|
||||
questionId: string;
|
||||
@ -12,7 +12,6 @@ type SettingsDataProps = {
|
||||
|
||||
export default function SettingsData({ questionId, isRequired, isDateRange, isTime }: SettingsDataProps) {
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuizQuestionDate } from "@model/questionTypes/date";
|
||||
import { QuizQuestionDate } from "@frontend/squzanswerer";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingData from "./settingData";
|
||||
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import {
|
||||
AnyTypedQuizQuestion,
|
||||
UntypedQuizQuestion,
|
||||
} from "@model/questionTypes/shared";
|
||||
import { UntypedQuizQuestion } from "@model/questionTypes/shared";
|
||||
import { Box, ListItem, Typography, useTheme } from "@mui/material";
|
||||
import { cancelQuestionDeletion } from "@root/questions/actions";
|
||||
import { updateEditSomeQuestion } from "@root/uiTools/actions";
|
||||
@ -9,6 +6,7 @@ import { useUiTools } from "@root/uiTools/store";
|
||||
import { memo, useEffect } from "react";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import QuestionsPageCard from "./QuestionPageCard";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
type Props = {
|
||||
question: AnyTypedQuizQuestion | UntypedQuizQuestion;
|
||||
@ -48,7 +46,10 @@ const DraggableListItem = memo<Props>(function ({
|
||||
}, [editSomeQuestion]);
|
||||
|
||||
return (
|
||||
<Draggable draggableId={question.id.toString()} index={index}>
|
||||
<Draggable
|
||||
draggableId={question.id.toString()}
|
||||
index={index}
|
||||
>
|
||||
{(provided) => (
|
||||
<ListItem
|
||||
id={question.type === null ? "" : question.content.id}
|
||||
|
||||
@ -3,13 +3,11 @@ import { createUntypedQuestion } from "@root/questions/actions";
|
||||
import { useState } from "react";
|
||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||
import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg";
|
||||
import type {
|
||||
AnyTypedQuizQuestion,
|
||||
UntypedQuizQuestion,
|
||||
} from "../../../model/questionTypes/shared";
|
||||
import type { UntypedQuizQuestion } from "../../../model/questionTypes/shared";
|
||||
import SwitchQuestionsPage from "../SwitchQuestionsPage";
|
||||
import TypeQuestions from "../TypeQuestions";
|
||||
import QuestionPageCardTitle from "./QuestionPageCardTitle";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
interface Props {
|
||||
question: AnyTypedQuizQuestion | UntypedQuizQuestion;
|
||||
@ -52,11 +50,7 @@ export default function QuestionsPageCard({
|
||||
questionType={question.type}
|
||||
page={"page" in question ? question.page : null}
|
||||
isExpanded={question.expanded}
|
||||
questionHasParent={
|
||||
"content" in question
|
||||
? question.content.rule.parentId.length > 0
|
||||
: false
|
||||
}
|
||||
questionHasParent={"content" in question ? question.content.rule.parentId.length > 0 : false}
|
||||
/>
|
||||
{question.expanded && (
|
||||
<Box
|
||||
@ -108,8 +102,7 @@ export default function QuestionsPageCard({
|
||||
backgroundPosition: "bottom",
|
||||
backgroundRepeat: "repeat-x",
|
||||
backgroundSize: "20px 1px",
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)",
|
||||
backgroundImage: "radial-gradient(circle, #7E2AEA 6px, #F2F3F7 1px)",
|
||||
}}
|
||||
/>
|
||||
<PlusIcon />
|
||||
|
||||
@ -6,7 +6,7 @@ import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchDropDown from "./switchDropDown";
|
||||
|
||||
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
||||
import type { QuizQuestionSelect } from "@frontend/squzanswerer";
|
||||
import AnswerItem from "../AnswerDraggableList/AnswerItem";
|
||||
|
||||
interface Props {
|
||||
@ -15,11 +15,7 @@ interface Props {
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function DropDown({
|
||||
question,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
export default function DropDown({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const onClickAddAnAnswer = useAddAnswer();
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
@ -108,10 +104,13 @@ export default function DropDown({
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
questionHasParent={question.content.rule.parentId.length !== 0}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
/>
|
||||
<SwitchDropDown switchState={switchState} question={question} />
|
||||
<SwitchDropDown
|
||||
switchState={switchState}
|
||||
question={question}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { memo } from "react";
|
||||
import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
|
||||
import type { QuizQuestionSelect } from "@frontend/squzanswerer";
|
||||
|
||||
type SettingDropDownProps = {
|
||||
questionId: string;
|
||||
@ -11,11 +11,7 @@ type SettingDropDownProps = {
|
||||
isRequired: boolean;
|
||||
};
|
||||
|
||||
const SettingDropDown = memo<SettingDropDownProps>(function ({
|
||||
questionId,
|
||||
questionContentDefault,
|
||||
isRequired,
|
||||
}) {
|
||||
const SettingDropDown = memo<SettingDropDownProps>(function ({ questionId, questionContentDefault, isRequired }) {
|
||||
const theme = useTheme();
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuizQuestionSelect } from "@model/questionTypes/select";
|
||||
import { QuizQuestionSelect } from "@frontend/squzanswerer";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingDropDown from "./settingDropDown";
|
||||
|
||||
@ -7,10 +7,7 @@ interface Props {
|
||||
question: QuizQuestionSelect;
|
||||
}
|
||||
|
||||
export default function SwitchDropDown({
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
export default function SwitchDropDown({ switchState = "setting", question }: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return (
|
||||
|
||||
@ -1,16 +1,9 @@
|
||||
import {
|
||||
Box,
|
||||
Link,
|
||||
Popover,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Link, Popover, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import { EmojiPicker } from "@ui_kit/EmojiPicker";
|
||||
import { useState } from "react";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
||||
import type { QuizQuestionEmoji } from "@frontend/squzanswerer";
|
||||
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
@ -23,17 +16,11 @@ interface Props {
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function Emoji({
|
||||
question,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
export default function Emoji({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const [switchState, setSwitchState] = useState<string>("setting");
|
||||
const onClickAddAnAnswer = useAddAnswer();
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(
|
||||
null,
|
||||
);
|
||||
const [anchorElement, setAnchorElement] = useState<HTMLDivElement | null>(null);
|
||||
const [selectedVariant, setSelectedVariant] = useState<string | null>(null);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
@ -79,9 +66,7 @@ export default function Emoji({
|
||||
updateQuestion(question.id, (question) => {
|
||||
if (question.type !== "emoji") return;
|
||||
|
||||
const variant = question.content.variants.find(
|
||||
(v) => v.id === selectedVariant,
|
||||
);
|
||||
const variant = question.content.variants.find((v) => v.id === selectedVariant);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = native;
|
||||
@ -134,10 +119,13 @@ export default function Emoji({
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
questionHasParent={question.content.rule.parentId.length !== 0}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
/>
|
||||
<SwitchEmoji switchState={switchState} question={question} />
|
||||
<SwitchEmoji
|
||||
switchState={switchState}
|
||||
question={question}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import type { QuizQuestionEmoji, QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
|
||||
import { memo } from "react";
|
||||
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
|
||||
type SettingEmojiProps = {
|
||||
questionId: string;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuizQuestionEmoji } from "@model/questionTypes/emoji";
|
||||
import { QuizQuestionEmoji } from "@frontend/squzanswerer";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingEmoji from "./settingEmoji";
|
||||
|
||||
|
||||
@ -15,11 +15,9 @@ import {
|
||||
import { changeQuestionType } from "@root/questions/actions";
|
||||
import type { RefObject } from "react";
|
||||
import { useState } from "react";
|
||||
import type {
|
||||
AnyTypedQuizQuestion,
|
||||
UntypedQuizQuestion,
|
||||
} from "../../../../model/questionTypes/shared";
|
||||
import type { UntypedQuizQuestion } from "../../../../model/questionTypes/shared";
|
||||
import { BUTTON_TYPE_QUESTIONS } from "../../TypeQuestions";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
type ChooseAnswerModalProps = {
|
||||
open: boolean;
|
||||
@ -29,13 +27,7 @@ type ChooseAnswerModalProps = {
|
||||
questionType: QuestionType | null;
|
||||
};
|
||||
|
||||
export const ChooseAnswerModal = ({
|
||||
open,
|
||||
onClose,
|
||||
anchorRef,
|
||||
question,
|
||||
questionType,
|
||||
}: ChooseAnswerModalProps) => {
|
||||
export const ChooseAnswerModal = ({ open, onClose, anchorRef, question, questionType }: ChooseAnswerModalProps) => {
|
||||
const [openModal, setOpenModal] = useState<boolean>(false);
|
||||
const [selectedValue, setSelectedValue] = useState<QuestionType>("text");
|
||||
const theme = useTheme();
|
||||
@ -69,10 +61,7 @@ export const ChooseAnswerModal = ({
|
||||
<Box>{icon}</Box>
|
||||
<Typography
|
||||
sx={{
|
||||
color:
|
||||
value === questionType
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey2.main,
|
||||
color: value === questionType ? theme.palette.brightPurple.main : theme.palette.grey2.main,
|
||||
}}
|
||||
>
|
||||
{title}
|
||||
@ -85,7 +74,10 @@ export const ChooseAnswerModal = ({
|
||||
</Grow>
|
||||
)}
|
||||
</Popper>
|
||||
<Modal open={openModal} onClose={() => setOpenModal(false)}>
|
||||
<Modal
|
||||
open={openModal}
|
||||
onClose={() => setOpenModal(false)}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
@ -97,9 +89,7 @@ export const ChooseAnswerModal = ({
|
||||
background: "#FFFFFF",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h6">
|
||||
Все настройки, кроме заголовка вопроса будут сброшены
|
||||
</Typography>
|
||||
<Typography variant="h6">Все настройки, кроме заголовка вопроса будут сброшены</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
marginTop: "30px",
|
||||
|
||||
@ -2,13 +2,11 @@ import { Box, ListItem, Typography, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import { memo } from "react";
|
||||
import { Draggable } from "react-beautiful-dnd";
|
||||
import {
|
||||
AnyTypedQuizQuestion,
|
||||
UntypedQuizQuestion,
|
||||
} from "../../../../model/questionTypes/shared";
|
||||
import { UntypedQuizQuestion } from "../../../../model/questionTypes/shared";
|
||||
import QuestionsPageCard from "./QuestionPageCard";
|
||||
|
||||
import type { DraggableProvided } from "react-beautiful-dnd";
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
|
||||
type FormDraggableListItemProps = {
|
||||
question: AnyTypedQuizQuestion | UntypedQuizQuestion;
|
||||
@ -19,11 +17,7 @@ type FormItemProps = FormDraggableListItemProps & {
|
||||
provided?: DraggableProvided;
|
||||
};
|
||||
|
||||
export const FormItem = ({
|
||||
question,
|
||||
questionIndex,
|
||||
provided,
|
||||
}: FormItemProps) => {
|
||||
export const FormItem = ({ question, questionIndex, provided }: FormItemProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
@ -85,24 +79,25 @@ export const FormItem = ({
|
||||
);
|
||||
};
|
||||
|
||||
export const FormDraggableListItem = memo(
|
||||
({ question, questionIndex }: FormDraggableListItemProps) => (
|
||||
<Draggable draggableId={String(question.id)} index={questionIndex}>
|
||||
{(provided) => (
|
||||
<ListItem
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
sx={{ userSelect: "none", padding: 0 }}
|
||||
>
|
||||
<FormItem
|
||||
question={question}
|
||||
questionIndex={questionIndex}
|
||||
provided={provided}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
</Draggable>
|
||||
),
|
||||
);
|
||||
export const FormDraggableListItem = memo(({ question, questionIndex }: FormDraggableListItemProps) => (
|
||||
<Draggable
|
||||
draggableId={String(question.id)}
|
||||
index={questionIndex}
|
||||
>
|
||||
{(provided) => (
|
||||
<ListItem
|
||||
ref={provided.innerRef}
|
||||
{...provided.draggableProps}
|
||||
sx={{ userSelect: "none", padding: 0 }}
|
||||
>
|
||||
<FormItem
|
||||
question={question}
|
||||
questionIndex={questionIndex}
|
||||
provided={provided}
|
||||
/>
|
||||
</ListItem>
|
||||
)}
|
||||
</Draggable>
|
||||
));
|
||||
|
||||
FormDraggableListItem.displayName = "FormDraggableListItem";
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
|
||||
import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon";
|
||||
import { CopyIcon } from "@icons/questionsPage/CopyIcon";
|
||||
import { PointsIcon } from "@icons/questionsPage/PointsIcon";
|
||||
import Answer from "@icons/questionsPage/answer";
|
||||
import AnswerGroup from "@icons/questionsPage/answerGroup";
|
||||
import Date from "@icons/questionsPage/date";
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import Download from "@icons/questionsPage/download";
|
||||
import DropDown from "@icons/questionsPage/drop_down";
|
||||
import Emoji from "@icons/questionsPage/emoji";
|
||||
@ -11,19 +14,19 @@ import OptionsPict from "@icons/questionsPage/options_pict";
|
||||
import Page from "@icons/questionsPage/page";
|
||||
import RatingIcon from "@icons/questionsPage/rating";
|
||||
import Slider from "@icons/questionsPage/slider";
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
import {
|
||||
Box,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormControlLabel,
|
||||
IconButton,
|
||||
InputAdornment,
|
||||
Paper,
|
||||
TextField as MuiTextField,
|
||||
Paper,
|
||||
TextFieldProps,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
TextFieldProps,
|
||||
} from "@mui/material";
|
||||
import {
|
||||
copyQuestion,
|
||||
@ -33,28 +36,12 @@ import {
|
||||
updateQuestion,
|
||||
updateUntypedQuestion,
|
||||
} from "@root/questions/actions";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import { FC, useRef, useState } from "react";
|
||||
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
|
||||
import { useDebouncedCallback } from "use-debounce";
|
||||
import type {
|
||||
AnyTypedQuizQuestion,
|
||||
UntypedQuizQuestion,
|
||||
} from "../../../../model/questionTypes/shared";
|
||||
import type { UntypedQuizQuestion } from "../../../../model/questionTypes/shared";
|
||||
import SwitchQuestionsPage from "../../SwitchQuestionsPage";
|
||||
import { ChooseAnswerModal } from "./ChooseAnswerModal";
|
||||
import FormTypeQuestions from "../FormTypeQuestions";
|
||||
import { QuestionType } from "@model/question/question";
|
||||
import { CrossedEyeIcon } from "@icons/CrossedEyeIcon";
|
||||
import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon";
|
||||
import { CopyIcon } from "@icons/questionsPage/CopyIcon";
|
||||
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import { HideIcon } from "@icons/questionsPage/hideIcon";
|
||||
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
||||
import {
|
||||
NoLuggageOutlined,
|
||||
SignalCellularNullOutlined,
|
||||
} from "@mui/icons-material";
|
||||
import { ChooseAnswerModal } from "./ChooseAnswerModal";
|
||||
|
||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||
|
||||
@ -64,11 +51,7 @@ interface Props {
|
||||
draggableProps: DraggableProvidedDragHandleProps | null | undefined;
|
||||
}
|
||||
|
||||
export default function QuestionsPageCard({
|
||||
question,
|
||||
questionIndex,
|
||||
draggableProps,
|
||||
}: Props) {
|
||||
export default function QuestionsPageCard({ question, questionIndex, draggableProps }: Props) {
|
||||
const maxLengthTextField = 225;
|
||||
const [open, setOpen] = useState<boolean>(false);
|
||||
const [isTextFieldtActive, setIsTextFieldtActive] = useState(false);
|
||||
@ -78,8 +61,7 @@ export default function QuestionsPageCard({
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
const setTitle = (title: string) => {
|
||||
const updateQuestionFn =
|
||||
question.type === null ? updateUntypedQuestion : updateQuestion;
|
||||
const updateQuestionFn = question.type === null ? updateUntypedQuestion : updateQuestion;
|
||||
|
||||
updateQuestionFn(question.id, (question) => {
|
||||
question.title = title;
|
||||
@ -148,9 +130,7 @@ export default function QuestionsPageCard({
|
||||
margin: isMobile ? "10px 0" : 0,
|
||||
"& .MuiInputBase-root": {
|
||||
color: "#000000",
|
||||
backgroundColor: question.expanded
|
||||
? theme.palette.background.default
|
||||
: "transparent",
|
||||
backgroundColor: question.expanded ? theme.palette.background.default : "transparent",
|
||||
height: "48px",
|
||||
borderRadius: "10px",
|
||||
".MuiOutlinedInput-notchedOutline": {
|
||||
@ -186,27 +166,22 @@ export default function QuestionsPageCard({
|
||||
/>
|
||||
</Box>
|
||||
),
|
||||
endAdornment: isTextFieldtActive &&
|
||||
question.title.length >= maxLengthTextField - 7 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
marginTop: "5px",
|
||||
marginLeft: "auto",
|
||||
position: "absolute",
|
||||
bottom: "-28px",
|
||||
right: "0",
|
||||
}}
|
||||
>
|
||||
<Typography fontSize="14px">
|
||||
{question.title.length}
|
||||
</Typography>
|
||||
<span>/</span>
|
||||
<Typography fontSize="14px">
|
||||
{maxLengthTextField}
|
||||
</Typography>
|
||||
</Box>
|
||||
),
|
||||
endAdornment: isTextFieldtActive && question.title.length >= maxLengthTextField - 7 && (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
marginTop: "5px",
|
||||
marginLeft: "auto",
|
||||
position: "absolute",
|
||||
bottom: "-28px",
|
||||
right: "0",
|
||||
}}
|
||||
>
|
||||
<Typography fontSize="14px">{question.title.length}</Typography>
|
||||
<span>/</span>
|
||||
<Typography fontSize="14px">{maxLengthTextField}</Typography>
|
||||
</Box>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@ -268,9 +243,7 @@ export default function QuestionsPageCard({
|
||||
sx={{ padding: "0" }}
|
||||
onClick={() => copyQuestion(question.id, question.quizId)}
|
||||
>
|
||||
<CopyIcon
|
||||
style={{ color: theme.palette.brightPurple.main }}
|
||||
/>
|
||||
<CopyIcon style={{ color: theme.palette.brightPurple.main }} />
|
||||
</IconButton>
|
||||
{questionIndex > 0 && (
|
||||
<IconButton
|
||||
@ -281,14 +254,10 @@ export default function QuestionsPageCard({
|
||||
margin: "0 5px 0 10px",
|
||||
}}
|
||||
onClick={() => {
|
||||
deleteQuestionWithTimeout(question.id, () =>
|
||||
deleteQuestion(question.id),
|
||||
);
|
||||
deleteQuestionWithTimeout(question.id, () => deleteQuestion(question.id));
|
||||
}}
|
||||
>
|
||||
<DeleteIcon
|
||||
style={{ color: theme.palette.brightPurple.main }}
|
||||
/>
|
||||
<DeleteIcon style={{ color: theme.palette.brightPurple.main }} />
|
||||
</IconButton>
|
||||
)}
|
||||
</Box>
|
||||
@ -304,12 +273,8 @@ export default function QuestionsPageCard({
|
||||
marginLeft: "3px",
|
||||
borderRadius: "50%",
|
||||
fontSize: "16px",
|
||||
color: question.expanded
|
||||
? theme.palette.brightPurple.main
|
||||
: "#FFF",
|
||||
background: question.expanded
|
||||
? "#EEE4FC"
|
||||
: theme.palette.brightPurple.main,
|
||||
color: question.expanded ? theme.palette.brightPurple.main : "#FFF",
|
||||
background: question.expanded ? "#EEE4FC" : theme.palette.brightPurple.main,
|
||||
}}
|
||||
>
|
||||
{questionIndex + 1}
|
||||
@ -349,10 +314,18 @@ export default function QuestionsPageCard({
|
||||
const IconAndrom = (questionType: QuestionType | null) => {
|
||||
switch (questionType) {
|
||||
case "variant":
|
||||
return <Answer color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Answer
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "images":
|
||||
return (
|
||||
<OptionsPict color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
<OptionsPict
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "varimg":
|
||||
return (
|
||||
@ -362,26 +335,60 @@ const IconAndrom = (questionType: QuestionType | null) => {
|
||||
/>
|
||||
);
|
||||
case "emoji":
|
||||
return <Emoji color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Emoji
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "text":
|
||||
return <Input color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Input
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "select":
|
||||
return (
|
||||
<DropDown color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
<DropDown
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "date":
|
||||
return <Date color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Date
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "number":
|
||||
return <Slider color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Slider
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "file":
|
||||
return (
|
||||
<Download color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
<Download
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "page":
|
||||
return <Page color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />;
|
||||
return (
|
||||
<Page
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
case "rating":
|
||||
return (
|
||||
<RatingIcon color="#9A9AAF" sx={{ height: "22px", width: "20px" }} />
|
||||
<RatingIcon
|
||||
color="#9A9AAF"
|
||||
sx={{ height: "22px", width: "20px" }}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import {
|
||||
addQuestionVariant,
|
||||
clearQuestionImages,
|
||||
uploadQuestionImage,
|
||||
} from "@root/questions/actions";
|
||||
import { addQuestionVariant, clearQuestionImages, uploadQuestionImage } from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||
import { useEffect, useState } from "react";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||
import type { QuizQuestionVarImg } from "@frontend/squzanswerer";
|
||||
import { useDisclosure } from "../../../utils/useDisclosure";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
import ImageEditAnswerItem from "../AnswerDraggableList/ImageEditAnswerItem";
|
||||
@ -22,50 +18,31 @@ interface Props {
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function OptionsAndPicture({
|
||||
question,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
export default function OptionsAndPicture({ question, setOpenBranchingPage }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const quizQid = useCurrentQuiz()?.qid;
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] =
|
||||
useDisclosure();
|
||||
const {
|
||||
isCropModalOpen,
|
||||
openCropModal,
|
||||
closeCropModal,
|
||||
imageBlob,
|
||||
originalImageUrl,
|
||||
setCropModalImageBlob,
|
||||
} = useCropModalState();
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
|
||||
const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
|
||||
useCropModalState();
|
||||
|
||||
const handleImageUpload = async (file: File) => {
|
||||
if (!selectedVariantId) return;
|
||||
|
||||
setPictureUploading(true);
|
||||
|
||||
const url = await uploadQuestionImage(
|
||||
question.id,
|
||||
quizQid,
|
||||
file,
|
||||
(question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
);
|
||||
if (!variant) return;
|
||||
const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
variant.originalImageUrl = url;
|
||||
},
|
||||
);
|
||||
variant.extendedText = url;
|
||||
variant.originalImageUrl = url;
|
||||
});
|
||||
closeImageUploadModal();
|
||||
openCropModal(file, url);
|
||||
|
||||
@ -78,9 +55,7 @@ export default function OptionsAndPicture({
|
||||
uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
);
|
||||
const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
@ -127,8 +102,7 @@ export default function OptionsAndPicture({
|
||||
onClose={closeCropModal}
|
||||
onSaveImageClick={handleCropModalSaveClick}
|
||||
onDeleteClick={() => {
|
||||
if (selectedVariantId)
|
||||
clearQuestionImages(question.id, selectedVariantId);
|
||||
if (selectedVariantId) clearQuestionImages(question.id, selectedVariantId);
|
||||
}}
|
||||
cropAspectRatio={{ width: 300, height: 300 }}
|
||||
/>
|
||||
@ -183,12 +157,15 @@ export default function OptionsAndPicture({
|
||||
SSHC={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionHasParent={question.content.rule.parentId.length !== 0}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
questionType={question.type}
|
||||
openBranchingPage={false}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
/>
|
||||
<SwitchOptionsAndPict switchState={switchState} question={question} />
|
||||
<SwitchOptionsAndPict
|
||||
switchState={switchState}
|
||||
question={question}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
import type { QuizQuestionVarImg, QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
|
||||
import { memo } from "react";
|
||||
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
|
||||
type SettingOptionsAndPictProps = {
|
||||
questionId: string;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuizQuestionVarImg } from "@model/questionTypes/varimg";
|
||||
import { QuizQuestionVarImg } from "@frontend/squzanswerer";
|
||||
import UploadImage from "../UploadImage";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingOptionsAndPict from "./SettingOptionsAndPict";
|
||||
@ -28,7 +28,12 @@ export default function SwitchOptionsAndPict({ switchState = "setting", question
|
||||
/>
|
||||
);
|
||||
case "image":
|
||||
return <UploadImage question={question} cropAspectRatio={{ width: 380, height: 300 }} />;
|
||||
return (
|
||||
<UploadImage
|
||||
question={question}
|
||||
cropAspectRatio={{ width: 380, height: 300 }}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -1,13 +1,10 @@
|
||||
import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import {
|
||||
clearQuestionImages,
|
||||
uploadQuestionImage,
|
||||
} from "@root/questions/actions";
|
||||
import { clearQuestionImages, uploadQuestionImage } from "@root/questions/actions";
|
||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||
import { useState } from "react";
|
||||
import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon";
|
||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
||||
import type { QuizQuestionImages } from "@frontend/squzanswerer";
|
||||
import { useAddAnswer } from "../../../utils/hooks/useAddAnswer";
|
||||
import { useDisclosure } from "../../../utils/useDisclosure";
|
||||
import { AnswerDraggableList } from "../AnswerDraggableList";
|
||||
@ -22,52 +19,32 @@ interface Props {
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function OptionsPicture({
|
||||
question,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
export default function OptionsPicture({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const theme = useTheme();
|
||||
const onClickAddAnAnswer = useAddAnswer();
|
||||
const quizQid = useCurrentQuiz()?.qid;
|
||||
const [pictureUploding, setPictureUploading] = useState<boolean>(false);
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [selectedVariantId, setSelectedVariantId] = useState<string | null>(null);
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] =
|
||||
useDisclosure();
|
||||
const {
|
||||
isCropModalOpen,
|
||||
openCropModal,
|
||||
closeCropModal,
|
||||
imageBlob,
|
||||
originalImageUrl,
|
||||
setCropModalImageBlob,
|
||||
} = useCropModalState();
|
||||
const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure();
|
||||
const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } =
|
||||
useCropModalState();
|
||||
|
||||
const handleImageUpload = async (file: File) => {
|
||||
if (!selectedVariantId) return;
|
||||
|
||||
setPictureUploading(true);
|
||||
|
||||
const url = await uploadQuestionImage(
|
||||
question.id,
|
||||
quizQid,
|
||||
file,
|
||||
(question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
);
|
||||
if (!variant) return;
|
||||
const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
variant.originalImageUrl = url;
|
||||
},
|
||||
);
|
||||
variant.extendedText = url;
|
||||
variant.originalImageUrl = url;
|
||||
});
|
||||
|
||||
closeImageUploadModal();
|
||||
openCropModal(file, url);
|
||||
@ -81,9 +58,7 @@ export default function OptionsPicture({
|
||||
uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => {
|
||||
if (!("variants" in question.content)) return;
|
||||
|
||||
const variant = question.content.variants.find(
|
||||
(variant) => variant.id === selectedVariantId,
|
||||
);
|
||||
const variant = question.content.variants.find((variant) => variant.id === selectedVariantId);
|
||||
if (!variant) return;
|
||||
|
||||
variant.extendedText = url;
|
||||
@ -124,8 +99,7 @@ export default function OptionsPicture({
|
||||
onClose={closeCropModal}
|
||||
onSaveImageClick={handleCropModalSaveClick}
|
||||
onDeleteClick={() => {
|
||||
if (selectedVariantId)
|
||||
clearQuestionImages(question.id, selectedVariantId);
|
||||
if (selectedVariantId) clearQuestionImages(question.id, selectedVariantId);
|
||||
}}
|
||||
cropAspectRatio={{ width: 452, height: 300 }}
|
||||
/>
|
||||
@ -167,10 +141,13 @@ export default function OptionsPicture({
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
questionHasParent={question.content.rule.parentId.length !== 0}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
/>
|
||||
<SwitchAnswerOptionsPict switchState={switchState} question={question} />
|
||||
<SwitchAnswerOptionsPict
|
||||
switchState={switchState}
|
||||
question={question}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import type { QuizQuestionImages, QuizQuestionVariant } from "@frontend/squzanswerer";
|
||||
import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import { memo } from "react";
|
||||
import FormatIcon1 from "../../../assets/icons/questionsPage/FormatIcon1";
|
||||
import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2";
|
||||
import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11";
|
||||
import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIcon12";
|
||||
import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21";
|
||||
import type { QuizQuestionImages } from "../../../model/questionTypes/images";
|
||||
import { memo } from "react";
|
||||
import type { QuizQuestionVariant } from "@model/questionTypes/variant";
|
||||
import FormatIcon1 from "../../../assets/icons/questionsPage/FormatIcon1";
|
||||
import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2";
|
||||
|
||||
type Proportion = "1:1" | "2:1" | "1:2";
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuizQuestionImages } from "@model/questionTypes/images";
|
||||
import { QuizQuestionImages } from "@frontend/squzanswerer";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingOpytionsPict from "./settingOpytionsPict";
|
||||
|
||||
|
||||
@ -1,16 +1,10 @@
|
||||
import {
|
||||
Box,
|
||||
Tooltip,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, Tooltip, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomTextField from "@ui_kit/CustomTextField";
|
||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||
import { useEffect, useState } from "react";
|
||||
import InfoIcon from "../../../assets/icons/InfoIcon";
|
||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||
import type { QuizQuestionText } from "@frontend/squzanswerer";
|
||||
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
|
||||
import SwitchTextField from "./switchTextField";
|
||||
|
||||
@ -20,11 +14,7 @@ interface Props {
|
||||
setOpenBranchingPage: (a: boolean) => void;
|
||||
}
|
||||
|
||||
export default function OwnTextField({
|
||||
question,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
export default function OwnTextField({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
@ -87,9 +77,7 @@ export default function OwnTextField({
|
||||
Пользователю будет дано поле для ввода значения
|
||||
</Typography>
|
||||
{isMobile ? (
|
||||
<TooltipClickInfo
|
||||
title={"Будет использоваться для ввода значения."}
|
||||
/>
|
||||
<TooltipClickInfo title={"Будет использоваться для ввода значения."} />
|
||||
) : (
|
||||
<Tooltip
|
||||
title="Будет использоваться для ввода значения."
|
||||
@ -107,12 +95,15 @@ export default function OwnTextField({
|
||||
SSHC={setSwitchState}
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionHasParent={question.content.rule.parentId.length !== 0}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
questionType={question.type}
|
||||
openBranchingPage={openBranchingPage}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
/>
|
||||
<SwitchTextField switchState={switchState} question={question} />
|
||||
<SwitchTextField
|
||||
switchState={switchState}
|
||||
question={question}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Box, FormControlLabel, Radio, RadioGroup, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import type { QuizQuestionText } from "../../../model/questionTypes/text";
|
||||
import type { QuizQuestionText } from "@frontend/squzanswerer";
|
||||
import { memo } from "react";
|
||||
import RadioCheck from "@ui_kit/RadioCheck";
|
||||
import RadioIcon from "@ui_kit/RadioIcon";
|
||||
@ -28,7 +28,6 @@ type SettingTextFieldProps = {
|
||||
|
||||
const SettingTextField = memo<SettingTextFieldProps>(function ({ questionId, isRequired, isAutofill, answerType }) {
|
||||
const theme = useTheme();
|
||||
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(900));
|
||||
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
|
||||
@ -81,7 +80,12 @@ const SettingTextField = memo<SettingTextFieldProps>(function ({ questionId, isR
|
||||
{ANSWER_TYPES.map((answerTypeItem, index) => (
|
||||
<FormControlLabel
|
||||
value={answerTypeItem.value}
|
||||
control={<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />}
|
||||
control={
|
||||
<Radio
|
||||
checkedIcon={<RadioCheck />}
|
||||
icon={<RadioIcon />}
|
||||
/>
|
||||
}
|
||||
label={answerTypeItem.name}
|
||||
sx={{
|
||||
color: theme.palette.grey2.main,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuizQuestionText } from "@model/questionTypes/text";
|
||||
import { QuizQuestionText } from "@frontend/squzanswerer";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingTextField from "./settingTextField";
|
||||
import UploadImage from "../UploadImage";
|
||||
@ -16,12 +16,16 @@ export default function SwitchTextField({ switchState = "setting", question }: P
|
||||
questionId={question.id}
|
||||
isRequired={question.content.required}
|
||||
isAutofill={question.content.autofill}
|
||||
isOnlyNumbers={question.content.onlyNumbers}
|
||||
answerType={question.content.answerType}
|
||||
/>
|
||||
);
|
||||
case "image":
|
||||
return <UploadImage question={question} cropAspectRatio={{ width: 400, height: 300 }} />;
|
||||
return (
|
||||
<UploadImage
|
||||
question={question}
|
||||
cropAspectRatio={{ width: 400, height: 300 }}
|
||||
/>
|
||||
);
|
||||
case "help":
|
||||
return (
|
||||
<HelpQuestions
|
||||
|
||||
@ -7,7 +7,7 @@ import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
|
||||
import { MediaSelectionAndDisplay } from "@ui_kit/MediaSelectionAndDisplay";
|
||||
import { DeleteFunction } from "@utils/deleteFunc";
|
||||
import { useState } from "react";
|
||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||
import type { QuizQuestionPage } from "@frontend/squzanswerer";
|
||||
|
||||
type Props = {
|
||||
disableInput?: boolean;
|
||||
@ -91,7 +91,7 @@ export default function PageOptions({ disableInput, question }: Props) {
|
||||
if (question.type === null) {
|
||||
deleteQuestion(question.id);
|
||||
}
|
||||
if (question.content.rule.parentId.length !== 0) {
|
||||
if (question.content.rule.parentId?.length !== 0) {
|
||||
setOpenDelete(true);
|
||||
} else {
|
||||
deleteQuestionWithTimeout(question.id, () => DeleteFunction(question.id));
|
||||
|
||||
@ -1,13 +1,11 @@
|
||||
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
|
||||
import type { QuizQuestionPage } from "@frontend/squzanswerer";
|
||||
|
||||
type SettingPageOptionsProps = {
|
||||
question: QuizQuestionPage;
|
||||
};
|
||||
|
||||
export default function SettingPageOptions({
|
||||
question,
|
||||
}: SettingPageOptionsProps) {
|
||||
export default function SettingPageOptions({ question }: SettingPageOptionsProps) {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(790));
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { QuizQuestionPage } from "@model/questionTypes/page";
|
||||
import { QuizQuestionPage } from "@frontend/squzanswerer";
|
||||
import HelpQuestions from "../helpQuestions";
|
||||
import SettingPageOptions from "./SettingPageOptions";
|
||||
|
||||
@ -7,10 +7,7 @@ interface Props {
|
||||
question: QuizQuestionPage;
|
||||
}
|
||||
|
||||
export default function SwitchPageOptions({
|
||||
switchState = "setting",
|
||||
question,
|
||||
}: Props) {
|
||||
export default function SwitchPageOptions({ switchState = "setting", question }: Props) {
|
||||
switch (switchState) {
|
||||
case "setting":
|
||||
return <SettingPageOptions question={question} />;
|
||||
|
||||
@ -1,11 +1,4 @@
|
||||
import {
|
||||
Box,
|
||||
TextField as MuiTextField,
|
||||
TextFieldProps,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { Box, TextField as MuiTextField, TextFieldProps, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import { FC, useLayoutEffect, useRef, useState } from "react";
|
||||
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
|
||||
@ -15,7 +8,7 @@ import HeartIcon from "../../../assets/icons/questionsPage/heartIcon";
|
||||
import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon";
|
||||
import LikeIcon from "../../../assets/icons/questionsPage/likeIcon";
|
||||
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
|
||||
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
|
||||
import type { QuizQuestionRating } from "@frontend/squzanswerer";
|
||||
import ButtonsOptions from "../ButtonsOptions";
|
||||
import SwitchRating from "./switchRating";
|
||||
|
||||
@ -32,11 +25,7 @@ export type ButtonRatingFrom = {
|
||||
icon: JSX.Element;
|
||||
};
|
||||
|
||||
export default function RatingOptions({
|
||||
question,
|
||||
openBranchingPage,
|
||||
setOpenBranchingPage,
|
||||
}: Props) {
|
||||
export default function RatingOptions({ question, openBranchingPage, setOpenBranchingPage }: Props) {
|
||||
const [switchState, setSwitchState] = useState("setting");
|
||||
const [negativeTextWidth, setNegativeTextWidth] = useState<number>(0);
|
||||
const [positiveTextWidth, setPositiveTextWidth] = useState<number>(0);
|
||||
@ -156,10 +145,7 @@ export default function RatingOptions({
|
||||
gap: isMobile ? "8px" : "30px",
|
||||
}}
|
||||
>
|
||||
{Array.from(
|
||||
{ length: question.content.steps },
|
||||
(_, index) => index,
|
||||
).map((itemNumber) => (
|
||||
{Array.from({ length: question.content.steps }, (_, index) => index).map((itemNumber) => (
|
||||
<Box
|
||||
key={itemNumber}
|
||||
{...(itemNumber === 0 || itemNumber === question.content.steps - 1
|
||||
@ -182,11 +168,7 @@ export default function RatingOptions({
|
||||
}
|
||||
: { sx: { transform: "scale(1.5)" } })}
|
||||
>
|
||||
{
|
||||
buttonRatingForm.find(
|
||||
({ name }) => question.content.form === name,
|
||||
)?.icon
|
||||
}
|
||||
{buttonRatingForm.find(({ name }) => question.content.form === name)?.icon}
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
@ -311,10 +293,13 @@ export default function RatingOptions({
|
||||
questionId={question.id}
|
||||
questionContentId={question.content.id}
|
||||
questionType={question.type}
|
||||
questionHasParent={question.content.rule.parentId.length !== 0}
|
||||
questionHasParent={question.content.rule.parentId?.length !== 0}
|
||||
setOpenBranchingPage={setOpenBranchingPage}
|
||||
/>
|
||||
<SwitchRating switchState={switchState} question={question} />
|
||||
<SwitchRating
|
||||
switchState={switchState}
|
||||
question={question}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,12 +1,5 @@
|
||||
import { QuizQuestionRating } from "@model/questionTypes/rating";
|
||||
import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
Slider,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
import { QuizQuestionRating } from "@frontend/squzanswerer";
|
||||
import { Box, ButtonBase, Slider, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { updateQuestion } from "@root/questions/actions";
|
||||
import CustomCheckbox from "@ui_kit/CustomCheckbox";
|
||||
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
|
||||
@ -92,14 +85,8 @@ export default function SettingSlider({ question }: SettingSliderProps) {
|
||||
});
|
||||
}}
|
||||
sx={{
|
||||
backgroundColor:
|
||||
question.content.form === name
|
||||
? theme.palette.brightPurple.main
|
||||
: "transparent",
|
||||
color:
|
||||
question.content.form === name
|
||||
? "#ffffff"
|
||||
: theme.palette.grey3.main,
|
||||
backgroundColor: question.content.form === name ? theme.palette.brightPurple.main : "transparent",
|
||||
color: question.content.form === name ? "#ffffff" : theme.palette.grey3.main,
|
||||
width: "40px",
|
||||
height: "40px",
|
||||
borderRadius: "4px",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user