2025-10-06 07:50:31 +00:00
|
|
|
|
import {
|
|
|
|
|
|
Box,
|
|
|
|
|
|
Button,
|
|
|
|
|
|
FormControl,
|
|
|
|
|
|
IconButton,
|
|
|
|
|
|
MenuItem,
|
|
|
|
|
|
TextField as MuiTextField,
|
|
|
|
|
|
Paper,
|
|
|
|
|
|
Select,
|
|
|
|
|
|
SelectChangeEvent,
|
|
|
|
|
|
TextFieldProps,
|
|
|
|
|
|
Typography,
|
|
|
|
|
|
useMediaQuery,
|
2025-10-18 18:58:52 +00:00
|
|
|
|
useTheme
|
2025-10-06 07:50:31 +00:00
|
|
|
|
} from "@mui/material";
|
2025-10-18 18:58:52 +00:00
|
|
|
|
import Tooltip from "@mui/material/Tooltip";
|
2025-10-06 07:50:31 +00:00
|
|
|
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
2025-10-18 18:58:52 +00:00
|
|
|
|
import { FC, useEffect, useMemo, useState } from "react";
|
2025-10-06 07:50:31 +00:00
|
|
|
|
import ArrowDown from "@/assets/icons/ArrowDownIcon";
|
|
|
|
|
|
import CopyIcon from "@/assets/icons/CopyIcon";
|
|
|
|
|
|
import LinkIcon from "@/assets/icons/LinkIcon";
|
|
|
|
|
|
import { InfoPopover } from "@/ui_kit/InfoPopover";
|
2025-10-18 18:58:52 +00:00
|
|
|
|
import { utmApi, type UtmRecord } from "@/api/utm";
|
|
|
|
|
|
import { UtmList } from "./UtmList";
|
2025-10-06 07:50:31 +00:00
|
|
|
|
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export default function QuizMarkCreate() {
|
|
|
|
|
|
const theme = useTheme();
|
|
|
|
|
|
const quiz = useCurrentQuiz();
|
2025-10-18 18:58:52 +00:00
|
|
|
|
const [category, setCategory] = useState<string>("google");
|
|
|
|
|
|
const [name, setName] = useState<string>("");
|
|
|
|
|
|
const [utm, setUtm] = useState<string>("");
|
2025-10-06 07:50:31 +00:00
|
|
|
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
|
|
|
|
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
2025-10-18 18:58:52 +00:00
|
|
|
|
const [items, setItems] = useState<UtmRecord[]>([]);
|
|
|
|
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
|
|
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
let cancelled = false;
|
|
|
|
|
|
async function load() {
|
|
|
|
|
|
if (!quiz?.backendId) return;
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
setError(null);
|
|
|
|
|
|
const [data, err] = await utmApi.getAll(quiz.backendId);
|
|
|
|
|
|
if (cancelled) return;
|
|
|
|
|
|
if (err) {
|
|
|
|
|
|
setError(err);
|
|
|
|
|
|
setItems([]);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
setItems((data ?? []).filter((i) => !i.deleted));
|
|
|
|
|
|
}
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
load();
|
|
|
|
|
|
return () => { cancelled = true; };
|
|
|
|
|
|
}, [quiz?.backendId]);
|
2025-10-06 07:50:31 +00:00
|
|
|
|
|
|
|
|
|
|
const CopyLink = () => {
|
|
|
|
|
|
let one = (document.getElementById("inputMarkLinkone") as HTMLInputElement)
|
|
|
|
|
|
?.value;
|
|
|
|
|
|
let text = (document.getElementById("inputMarkLink") as HTMLInputElement)
|
|
|
|
|
|
?.value;
|
|
|
|
|
|
navigator.clipboard.writeText(one + text);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-10-18 18:58:52 +00:00
|
|
|
|
const handleChange = (event: SelectChangeEvent<string>) => {
|
|
|
|
|
|
setCategory(event.target.value as string);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const filteredItems = useMemo(() => {
|
|
|
|
|
|
return items.filter((i) => (i.category ? i.category === category : true));
|
|
|
|
|
|
}, [items, category]);
|
|
|
|
|
|
|
|
|
|
|
|
const utmHasSpaces = /\s/.test(utm);
|
|
|
|
|
|
const canSave = Boolean(name.trim() && category && utm.trim() && !utmHasSpaces && quiz?.backendId);
|
|
|
|
|
|
|
|
|
|
|
|
const handleSave = async () => {
|
|
|
|
|
|
if (!canSave || !quiz?.backendId) return;
|
|
|
|
|
|
const body = { quiz_id: quiz.backendId, utm, name, category };
|
|
|
|
|
|
const [created, err] = await utmApi.create(body);
|
|
|
|
|
|
if (err || !created) return;
|
|
|
|
|
|
setItems((prev) => [created, ...prev]);
|
|
|
|
|
|
// Очистим только utm, оставим категорию; имя можно тоже очистить
|
|
|
|
|
|
setUtm("");
|
|
|
|
|
|
setName("");
|
2025-10-06 07:50:31 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
if (!quiz) return null;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Paper
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
boxSizing: "border-box",
|
|
|
|
|
|
maxWidth: "580px",
|
|
|
|
|
|
width: "100%",
|
|
|
|
|
|
padding: "22px 18px 20px 20px",
|
|
|
|
|
|
borderRadius: "12px",
|
|
|
|
|
|
display: "flex",
|
|
|
|
|
|
flexDirection: "column",
|
|
|
|
|
|
boxShadow:
|
|
|
|
|
|
"0px 100px 309px rgba(210, 208, 225, 0.24), 0px 41.7776px 129.093px rgba(210, 208, 225, 0.172525), 0px 22.3363px 69.0192px rgba(210, 208, 225, 0.143066), 0px 12.5216px 38.6916px rgba(210, 208, 225, 0.12), 0px 6.6501px 20.5488px rgba(210, 208, 225, 0.0969343), 0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Box
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
display: "flex",
|
|
|
|
|
|
alignItems: isMobile ? "flex-start" : "center",
|
|
|
|
|
|
gap: "10px",
|
|
|
|
|
|
flexDirection: isMobile ? "column" : "row",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
|
|
|
|
|
|
<LinkIcon
|
|
|
|
|
|
color={theme.palette.brightPurple.main}
|
|
|
|
|
|
bgcolor={"#EEE4FC"}
|
|
|
|
|
|
/>
|
2025-10-18 18:58:52 +00:00
|
|
|
|
<Typography color={theme.palette.grey3.main}>Создание/выбор utm меток</Typography>
|
2025-10-06 07:50:31 +00:00
|
|
|
|
</Box>
|
|
|
|
|
|
<Box>
|
|
|
|
|
|
|
|
|
|
|
|
<InfoPopover />
|
|
|
|
|
|
<FormControl
|
|
|
|
|
|
fullWidth
|
|
|
|
|
|
size="small"
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
|
|
|
|
|
|
width: "100%",
|
|
|
|
|
|
maxWidth: "118px",
|
|
|
|
|
|
height: "24px",
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Select
|
|
|
|
|
|
id="display-select"
|
|
|
|
|
|
variant="outlined"
|
2025-10-18 18:58:52 +00:00
|
|
|
|
value={category}
|
2025-10-06 07:50:31 +00:00
|
|
|
|
displayEmpty
|
|
|
|
|
|
onChange={handleChange}
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
height: "24px",
|
|
|
|
|
|
borderRadius: "8px",
|
|
|
|
|
|
"& .MuiOutlinedInput-notchedOutline": {
|
|
|
|
|
|
border: "none !important",
|
|
|
|
|
|
},
|
|
|
|
|
|
"& .MuiSelect-icon": {
|
|
|
|
|
|
position: 'relative',
|
|
|
|
|
|
marginBottom: '13px',
|
|
|
|
|
|
}
|
|
|
|
|
|
}}
|
|
|
|
|
|
MenuProps={{
|
|
|
|
|
|
PaperProps: {
|
|
|
|
|
|
sx: {
|
|
|
|
|
|
mt: "8px",
|
|
|
|
|
|
p: "4px",
|
|
|
|
|
|
borderRadius: "8px",
|
2025-10-18 18:58:52 +00:00
|
|
|
|
width: "300px",
|
|
|
|
|
|
maxWidth: "300px",
|
2025-10-06 07:50:31 +00:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
MenuListProps: {
|
|
|
|
|
|
sx: {
|
|
|
|
|
|
py: 0,
|
|
|
|
|
|
display: "flex",
|
|
|
|
|
|
flexDirection: "column",
|
|
|
|
|
|
gap: "8px",
|
|
|
|
|
|
"& .Mui-selected": {
|
|
|
|
|
|
backgroundColor: theme.palette.background.default,
|
|
|
|
|
|
color: theme.palette.brightPurple.main,
|
|
|
|
|
|
},
|
2025-10-18 18:58:52 +00:00
|
|
|
|
"& .MuiMenuItem-root": {
|
|
|
|
|
|
whiteSpace: "normal",
|
|
|
|
|
|
wordBreak: "break-word",
|
|
|
|
|
|
lineHeight: "20px",
|
|
|
|
|
|
maxWidth: "300px",
|
|
|
|
|
|
},
|
2025-10-06 07:50:31 +00:00
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
}}
|
|
|
|
|
|
inputProps={{
|
|
|
|
|
|
sx: {
|
|
|
|
|
|
color: theme.palette.brightPurple.main,
|
|
|
|
|
|
display: "flex",
|
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
|
px: "20px",
|
|
|
|
|
|
gap: "20px"
|
|
|
|
|
|
},
|
|
|
|
|
|
}}
|
|
|
|
|
|
IconComponent={(props) => <ArrowDown {...props} sx={{
|
|
|
|
|
|
position: 'relative',
|
|
|
|
|
|
top: '-5px', // Поднимаем вверх
|
|
|
|
|
|
right: '5px'
|
|
|
|
|
|
}} />}
|
|
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
<MenuItem
|
2025-10-18 18:58:52 +00:00
|
|
|
|
value={"google"}
|
2025-10-06 07:50:31 +00:00
|
|
|
|
sx={{
|
|
|
|
|
|
display: "flex",
|
|
|
|
|
|
gap: "20px",
|
|
|
|
|
|
p: "4px",
|
|
|
|
|
|
borderRadius: "5px",
|
|
|
|
|
|
color: theme.palette.grey2.main,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
Google
|
|
|
|
|
|
</MenuItem>
|
2025-10-18 18:58:52 +00:00
|
|
|
|
<MenuItem value={"vk"} sx={{ display: "flex", gap: "20px", p: "4px", borderRadius: "5px", color: theme.palette.grey2.main }}>VK</MenuItem>
|
|
|
|
|
|
<MenuItem value={"facebook"} sx={{ display: "flex", gap: "20px", p: "4px", borderRadius: "5px", color: theme.palette.grey2.main }}>Facebook* является экстремисткой организацией запрещённой на территории РФ</MenuItem>
|
|
|
|
|
|
<MenuItem value={"yandex"} sx={{ display: "flex", gap: "20px", p: "4px", borderRadius: "5px", color: theme.palette.grey2.main }}>Яндекс</MenuItem>
|
|
|
|
|
|
<MenuItem value={"tiktok"} sx={{ display: "flex", gap: "20px", p: "4px", borderRadius: "5px", color: theme.palette.grey2.main }}>TikTok</MenuItem>
|
|
|
|
|
|
<MenuItem value={"other"} sx={{ display: "flex", gap: "20px", p: "4px", borderRadius: "5px", color: theme.palette.grey2.main }}>другое</MenuItem>
|
2025-10-06 07:50:31 +00:00
|
|
|
|
</Select>
|
|
|
|
|
|
</FormControl>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
<Box sx={{ display: "inline-flex", alignItems: "center", justifyContent: "space-between", margin: "30px 0px 0px 0px" }}>
|
|
|
|
|
|
<Box sx={{ display: "inline-flex" }}>
|
|
|
|
|
|
<FormControl variant="standard" sx={{ p: 0 }}>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
id="inputMarkLinkone"
|
|
|
|
|
|
placeholder="Название"
|
2025-10-18 18:58:52 +00:00
|
|
|
|
value={name}
|
|
|
|
|
|
onChange={(e) => setName(e.target.value)}
|
2025-10-06 07:50:31 +00:00
|
|
|
|
sx={{
|
|
|
|
|
|
width: isTablet ? "100%" : "182px",
|
|
|
|
|
|
maxWidth: "182px",
|
|
|
|
|
|
"& .MuiInputBase-root": {
|
|
|
|
|
|
|
|
|
|
|
|
height: "48px",
|
|
|
|
|
|
color: "#525253",
|
|
|
|
|
|
borderRadius: "8px 0 0 8px",
|
|
|
|
|
|
backgroundColor: "#F2F3F7",
|
|
|
|
|
|
},
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</FormControl>
|
|
|
|
|
|
<FormControl variant="standard" sx={{ p: 0 }}>
|
|
|
|
|
|
<TextField
|
|
|
|
|
|
placeholder="пример-ссылки-с-utm-метками"
|
2025-10-18 18:58:52 +00:00
|
|
|
|
id="inputMarkLink"
|
|
|
|
|
|
value={utm}
|
|
|
|
|
|
onChange={(e) => setUtm(e.target.value)}
|
2025-10-06 07:50:31 +00:00
|
|
|
|
|
|
|
|
|
|
sx={{
|
|
|
|
|
|
|
|
|
|
|
|
"& .MuiInputBase-root": {
|
|
|
|
|
|
color: "#525253",
|
|
|
|
|
|
width: isTablet ? "100%" : "317px",
|
|
|
|
|
|
maxWidth: "317px",
|
|
|
|
|
|
height: "48px",
|
|
|
|
|
|
borderRadius: "0 8px 8px 0",
|
|
|
|
|
|
backgroundColor: "#F2F3F7"
|
|
|
|
|
|
},
|
|
|
|
|
|
}}
|
|
|
|
|
|
inputProps={{
|
|
|
|
|
|
sx: {
|
|
|
|
|
|
borderRadius: "0 10px 10px 0",
|
|
|
|
|
|
fontSize: "18px",
|
|
|
|
|
|
lineHeight: "21px",
|
|
|
|
|
|
py: 0,
|
|
|
|
|
|
},
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</FormControl>
|
|
|
|
|
|
|
|
|
|
|
|
<IconButton
|
|
|
|
|
|
onClick={CopyLink}
|
|
|
|
|
|
id={"copyLink"}
|
|
|
|
|
|
sx={{ borderRadius: "6px" }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<CopyIcon
|
|
|
|
|
|
color={theme.palette.brightPurple.main}
|
|
|
|
|
|
bgcolor={"#EEE4FC"}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</IconButton>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
|
|
|
|
|
|
<Box sx={{ display: "inline-flex", margin: "11px 0 0 0", alignItems: "flex-end" }}>
|
2025-10-18 18:58:52 +00:00
|
|
|
|
<Tooltip title={!canSave ? (utmHasSpaces ? "уберите пробелы" : "заполните поля") : ""} disableHoverListener={canSave} arrow>
|
|
|
|
|
|
<span style={{ marginLeft: "auto" }}>
|
|
|
|
|
|
<Button variant="contained" disabled={!canSave} onClick={handleSave} sx={{ width: "133px", height: "44px" }}>Сохранить</Button>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</Tooltip>
|
2025-10-06 07:50:31 +00:00
|
|
|
|
</Box>
|
2025-10-18 18:58:52 +00:00
|
|
|
|
|
|
|
|
|
|
{quiz?.qid && (
|
|
|
|
|
|
<UtmList
|
|
|
|
|
|
items={filteredItems}
|
|
|
|
|
|
loading={loading}
|
|
|
|
|
|
error={error}
|
|
|
|
|
|
quizQid={quiz.qid}
|
|
|
|
|
|
onDelete={async (id) => {
|
|
|
|
|
|
const prev = items;
|
|
|
|
|
|
setItems(prev.filter((it) => it.id !== id));
|
|
|
|
|
|
const [, err] = await utmApi.softDelete(id);
|
|
|
|
|
|
if (err) setItems(prev);
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>)}
|
2025-10-06 07:50:31 +00:00
|
|
|
|
</Paper>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
}
|