utm
This commit is contained in:
parent
e68447dc6e
commit
378eeb0068
@ -1,3 +1,4 @@
|
|||||||
|
1.0.13 _ 2025-10-18 _ новый визуал таймера с логикой + визуал овертайм
|
||||||
1.0.12 _ 2025-10-12 _ ютм с дизайном и беком, но без логики
|
1.0.12 _ 2025-10-12 _ ютм с дизайном и беком, но без логики
|
||||||
1.0.11 _ 2025-10-06 _ Merge branch 'staging'
|
1.0.11 _ 2025-10-06 _ Merge branch 'staging'
|
||||||
1.0.10 _ 2025-10-05 _ utm
|
1.0.10 _ 2025-10-05 _ utm
|
||||||
|
|||||||
@ -7,6 +7,8 @@ export type UtmRecordRaw = {
|
|||||||
id: number;
|
id: number;
|
||||||
quizID: number;
|
quizID: number;
|
||||||
utm: string;
|
utm: string;
|
||||||
|
name?: string;
|
||||||
|
category?: string; // e.g. 'google' | 'vk' | 'facebook' | 'yandex' | 'tiktok' | 'other'
|
||||||
deleted: boolean;
|
deleted: boolean;
|
||||||
createdAt: string; // ISO date
|
createdAt: string; // ISO date
|
||||||
};
|
};
|
||||||
@ -15,6 +17,8 @@ export type UtmRecord = {
|
|||||||
id: number;
|
id: number;
|
||||||
quiz_id: number;
|
quiz_id: number;
|
||||||
utm: string;
|
utm: string;
|
||||||
|
name?: string;
|
||||||
|
category?: string; // normalized value
|
||||||
deleted: boolean;
|
deleted: boolean;
|
||||||
created_at: string; // ISO date
|
created_at: string; // ISO date
|
||||||
};
|
};
|
||||||
@ -24,6 +28,8 @@ function mapUtm(r: UtmRecordRaw): UtmRecord {
|
|||||||
id: r.id,
|
id: r.id,
|
||||||
quiz_id: r.quizID,
|
quiz_id: r.quizID,
|
||||||
utm: r.utm,
|
utm: r.utm,
|
||||||
|
name: r.name,
|
||||||
|
category: r.category,
|
||||||
deleted: r.deleted,
|
deleted: r.deleted,
|
||||||
created_at: r.createdAt,
|
created_at: r.createdAt,
|
||||||
};
|
};
|
||||||
@ -43,7 +49,7 @@ export const utmApi = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async create(body: { quiz_id: number; utm: string }): Promise<[UtmRecord | null, string?]> {
|
async create(body: { quiz_id: number; utm: string; name?: string; category?: string }): Promise<[UtmRecord | null, string?]> {
|
||||||
try {
|
try {
|
||||||
const created = await makeRequest<typeof body, UtmRecordRaw>({
|
const created = await makeRequest<typeof body, UtmRecordRaw>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@ -11,26 +11,51 @@ import {
|
|||||||
TextFieldProps,
|
TextFieldProps,
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import Tooltip from "@mui/material/Tooltip";
|
||||||
import { useCurrentQuiz } from "@root/quizes/hooks";
|
import { useCurrentQuiz } from "@root/quizes/hooks";
|
||||||
import { FC, useState } from "react";
|
import { FC, useEffect, useMemo, useState } from "react";
|
||||||
import ArrowDown from "@/assets/icons/ArrowDownIcon";
|
import ArrowDown from "@/assets/icons/ArrowDownIcon";
|
||||||
import CopyIcon from "@/assets/icons/CopyIcon";
|
import CopyIcon from "@/assets/icons/CopyIcon";
|
||||||
import LinkIcon from "@/assets/icons/LinkIcon";
|
import LinkIcon from "@/assets/icons/LinkIcon";
|
||||||
import { ReactComponent as PlusIco } from "@/assets/icons/PlusIco.svg";
|
|
||||||
import { InfoPopover } from "@/ui_kit/InfoPopover";
|
import { InfoPopover } from "@/ui_kit/InfoPopover";
|
||||||
import { UtmsModal } from "./UtmsModal";
|
import { utmApi, type UtmRecord } from "@/api/utm";
|
||||||
|
import { UtmList } from "./UtmList";
|
||||||
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
|
||||||
|
|
||||||
|
|
||||||
export default function QuizMarkCreate() {
|
export default function QuizMarkCreate() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const quiz = useCurrentQuiz();
|
const quiz = useCurrentQuiz();
|
||||||
const [display, setDisplay] = useState("1");
|
const [category, setCategory] = useState<string>("google");
|
||||||
|
const [name, setName] = useState<string>("");
|
||||||
|
const [utm, setUtm] = useState<string>("");
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const [isUtmsOpen, setIsUtmsOpen] = useState(false);
|
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]);
|
||||||
|
|
||||||
const CopyLink = () => {
|
const CopyLink = () => {
|
||||||
let one = (document.getElementById("inputMarkLinkone") as HTMLInputElement)
|
let one = (document.getElementById("inputMarkLinkone") as HTMLInputElement)
|
||||||
@ -40,8 +65,26 @@ export default function QuizMarkCreate() {
|
|||||||
navigator.clipboard.writeText(one + text);
|
navigator.clipboard.writeText(one + text);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (event: SelectChangeEvent) => {
|
const handleChange = (event: SelectChangeEvent<string>) => {
|
||||||
setDisplay(event.target.value);
|
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("");
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!quiz) return null;
|
if (!quiz) return null;
|
||||||
@ -73,7 +116,7 @@ export default function QuizMarkCreate() {
|
|||||||
color={theme.palette.brightPurple.main}
|
color={theme.palette.brightPurple.main}
|
||||||
bgcolor={"#EEE4FC"}
|
bgcolor={"#EEE4FC"}
|
||||||
/>
|
/>
|
||||||
<Typography color="#4D4D4D">Создание/выбор utm меток</Typography>
|
<Typography color={theme.palette.grey3.main}>Создание/выбор utm меток</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
|
|
||||||
@ -91,7 +134,7 @@ export default function QuizMarkCreate() {
|
|||||||
<Select
|
<Select
|
||||||
id="display-select"
|
id="display-select"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={display}
|
value={category}
|
||||||
displayEmpty
|
displayEmpty
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
sx={{
|
sx={{
|
||||||
@ -111,6 +154,8 @@ export default function QuizMarkCreate() {
|
|||||||
mt: "8px",
|
mt: "8px",
|
||||||
p: "4px",
|
p: "4px",
|
||||||
borderRadius: "8px",
|
borderRadius: "8px",
|
||||||
|
width: "300px",
|
||||||
|
maxWidth: "300px",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
MenuListProps: {
|
MenuListProps: {
|
||||||
@ -123,6 +168,12 @@ export default function QuizMarkCreate() {
|
|||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
color: theme.palette.brightPurple.main,
|
color: theme.palette.brightPurple.main,
|
||||||
},
|
},
|
||||||
|
"& .MuiMenuItem-root": {
|
||||||
|
whiteSpace: "normal",
|
||||||
|
wordBreak: "break-word",
|
||||||
|
lineHeight: "20px",
|
||||||
|
maxWidth: "300px",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@ -143,7 +194,7 @@ export default function QuizMarkCreate() {
|
|||||||
|
|
||||||
>
|
>
|
||||||
<MenuItem
|
<MenuItem
|
||||||
value={1}
|
value={"google"}
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
gap: "20px",
|
gap: "20px",
|
||||||
@ -154,6 +205,11 @@ export default function QuizMarkCreate() {
|
|||||||
>
|
>
|
||||||
Google
|
Google
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<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>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Box>
|
</Box>
|
||||||
@ -162,9 +218,10 @@ export default function QuizMarkCreate() {
|
|||||||
<Box sx={{ display: "inline-flex" }}>
|
<Box sx={{ display: "inline-flex" }}>
|
||||||
<FormControl variant="standard" sx={{ p: 0 }}>
|
<FormControl variant="standard" sx={{ p: 0 }}>
|
||||||
<TextField
|
<TextField
|
||||||
disabled
|
|
||||||
id="inputMarkLinkone"
|
id="inputMarkLinkone"
|
||||||
placeholder="Название"
|
placeholder="Название"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
sx={{
|
sx={{
|
||||||
width: isTablet ? "100%" : "182px",
|
width: isTablet ? "100%" : "182px",
|
||||||
maxWidth: "182px",
|
maxWidth: "182px",
|
||||||
@ -181,6 +238,9 @@ export default function QuizMarkCreate() {
|
|||||||
<FormControl variant="standard" sx={{ p: 0 }}>
|
<FormControl variant="standard" sx={{ p: 0 }}>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="пример-ссылки-с-utm-метками"
|
placeholder="пример-ссылки-с-utm-метками"
|
||||||
|
id="inputMarkLink"
|
||||||
|
value={utm}
|
||||||
|
onChange={(e) => setUtm(e.target.value)}
|
||||||
|
|
||||||
sx={{
|
sx={{
|
||||||
|
|
||||||
@ -218,12 +278,26 @@ export default function QuizMarkCreate() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ display: "inline-flex", margin: "11px 0 0 0", alignItems: "flex-end" }}>
|
<Box sx={{ display: "inline-flex", margin: "11px 0 0 0", alignItems: "flex-end" }}>
|
||||||
<IconButton sx={{ borderRadius: "6px", padding: "0px 0px 0px 0px" }} onClick={() => setIsUtmsOpen(true)}>
|
<Tooltip title={!canSave ? (utmHasSpaces ? "уберите пробелы" : "заполните поля") : ""} disableHoverListener={canSave} arrow>
|
||||||
<PlusIco></PlusIco>
|
<span style={{ marginLeft: "auto" }}>
|
||||||
</IconButton>
|
<Button variant="contained" disabled={!canSave} onClick={handleSave} sx={{ width: "133px", height: "44px" }}>Сохранить</Button>
|
||||||
<Button variant="contained" sx={{ width: "133px", height: "44px", margin: "0px 0 0 auto" }}>Сохранить</Button>
|
</span>
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
<UtmsModal open={isUtmsOpen} onClose={() => setIsUtmsOpen(false)} quizBackendId={quiz?.backendId} />
|
|
||||||
|
{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);
|
||||||
|
}}
|
||||||
|
/>)}
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
166
src/pages/InstallQuiz/QuizInstallationCard/UtmList.tsx
Normal file
166
src/pages/InstallQuiz/QuizInstallationCard/UtmList.tsx
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import { FC, useEffect, useMemo, useState } from "react";
|
||||||
|
import { Box, IconButton, List, ListItem, Typography, useTheme, useMediaQuery, Pagination } from "@mui/material";
|
||||||
|
import TrashIcon from "@/assets/icons/TrashIcon";
|
||||||
|
import { type UtmRecord } from "@/api/utm";
|
||||||
|
import CopyIcon from "@/assets/icons/CopyIcon";
|
||||||
|
import { useDomainDefine } from "@/utils/hooks/useDomainDefine";
|
||||||
|
import CustomPagination from "@/ui_kit/CustomPagination";
|
||||||
|
|
||||||
|
type UtmListProps = {
|
||||||
|
items: UtmRecord[];
|
||||||
|
loading: boolean;
|
||||||
|
error: string | null;
|
||||||
|
onDelete: (id: number) => void | Promise<void>;
|
||||||
|
quizQid: string;
|
||||||
|
};
|
||||||
|
type ElementUTMProps = { u: UtmRecord; onDelete: (id: number) => void | Promise<void>; quizQid: string };
|
||||||
|
const ElementUTM: FC<ElementUTMProps> = ({ u, onDelete, quizQid }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
|
const { isTestServer } = useDomainDefine();
|
||||||
|
|
||||||
|
const getHost = () => `${isTestServer ? "s." : ""}hbpn.link`;
|
||||||
|
const extractParams = (text: string) => {
|
||||||
|
const raw = (text || "").trim();
|
||||||
|
try {
|
||||||
|
const maybeUrl = new URL(raw);
|
||||||
|
const q = maybeUrl.search; // already includes leading '?'
|
||||||
|
return q || "";
|
||||||
|
} catch {
|
||||||
|
if (!raw) return "";
|
||||||
|
if (raw.startsWith("?")) return raw; // уже есть знак начала параметров — не меняем
|
||||||
|
if (raw.startsWith("&")) return `?${raw.slice(1)}`; // приводим к валидной первой паре
|
||||||
|
return `?${raw}`; // добавляем знак начала параметров
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const buildCopyUrl = () => {
|
||||||
|
const host = getHost();
|
||||||
|
const base = `https://${host}/${quizQid}`;
|
||||||
|
const params = extractParams(u.utm);
|
||||||
|
return `${base}${params}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem key={u.id} sx={{ px: 0 }}>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", width: "100%", minWidth: 0 }}>
|
||||||
|
<Box sx={{ display: "flex", flex: 1, minWidth: 0, flexDirection: isMobile ? "column" : "row" }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: isMobile ? "100%" : "182px",
|
||||||
|
flex: isMobile ? "0 0 auto" : "0 0 182px",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: isMobile ? "8px 8px 0 0" : "8px 0 0 8px",
|
||||||
|
backgroundColor: "#F2F3F7",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
px: "12px",
|
||||||
|
border: "#9A9AAF 1px solid",
|
||||||
|
borderRight: isMobile ? undefined : "none",
|
||||||
|
borderBottom: isMobile ? "none" : undefined,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography noWrap sx={{
|
||||||
|
color: theme.palette.grey3.main,
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "100%",
|
||||||
|
}}>
|
||||||
|
{(u.name ?? "") || "Название"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
flex: isMobile ? "0 0 auto" : "1 1 auto",
|
||||||
|
width: isMobile ? "100%" : "auto",
|
||||||
|
minWidth: 0,
|
||||||
|
maxWidth: "100%",
|
||||||
|
height: "48px",
|
||||||
|
borderRadius: isMobile ? "0 0 8px 8px" : "0 8px 8px 0",
|
||||||
|
backgroundColor: "#F2F3F7",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
px: "12px",
|
||||||
|
ml: 0,
|
||||||
|
border: "#9A9AAF 1px solid",
|
||||||
|
borderLeft: isMobile ? undefined : "#9A9AAF 1px solid",
|
||||||
|
borderTop: isMobile ? "#9A9AAF 1px solid" : undefined,
|
||||||
|
overflow: "hidden",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography noWrap sx={{
|
||||||
|
color: theme.palette.grey3.main,
|
||||||
|
fontSize: "18px",
|
||||||
|
lineHeight: "21px",
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
display: "block",
|
||||||
|
width: "100%",
|
||||||
|
maxWidth: "100%",
|
||||||
|
}}>
|
||||||
|
{u.utm || "пример-ссылки-с-utm-метками"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ display: "inline-flex", gap: "6px", ml: "auto", flexDirection: isMobile ? "column" : "row", flexShrink: 0 }}>
|
||||||
|
<IconButton edge="end" aria-label="copy" onClick={() => navigator.clipboard.writeText(buildCopyUrl())} sx={{ borderRadius: "6px" }}>
|
||||||
|
<CopyIcon color={theme.palette.brightPurple.main} bgcolor={"#EEE4FC"} />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton edge="end" aria-label="delete" onClick={() => onDelete(u.id)}>
|
||||||
|
<TrashIcon color="#FB5607" />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</ListItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UtmList: FC<UtmListProps> = ({ items, loading, error, onDelete, quizQid }) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [page, setPage] = useState<number>(1);
|
||||||
|
const perPage = 3;
|
||||||
|
|
||||||
|
const totalPages = Math.max(1, Math.ceil((items?.length ?? 0) / perPage));
|
||||||
|
const pagedItems = useMemo(() => {
|
||||||
|
const start = (page - 1) * perPage;
|
||||||
|
return items.slice(start, start + perPage);
|
||||||
|
}, [items, page]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const nextPage = Math.min(page, totalPages);
|
||||||
|
if (nextPage !== page) setPage(nextPage || 1);
|
||||||
|
}, [items, totalPages, page]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ mt: 2 }}>
|
||||||
|
<Typography sx={{ fontWeight: 600, mb: 1, color: theme.palette.grey3.main }}>Сохранённые ссылки</Typography>
|
||||||
|
{loading ? (
|
||||||
|
<Typography sx={{ color: theme.palette.grey2.main }}>Загрузка…</Typography>
|
||||||
|
) : error ? (
|
||||||
|
<Typography sx={{ color: theme.palette.error.main }}>Ошибка: {error}</Typography>
|
||||||
|
) : items.length === 0 ? (
|
||||||
|
<Typography sx={{ color: theme.palette.grey2.main }}>Список пуст</Typography>
|
||||||
|
) : (
|
||||||
|
<List>
|
||||||
|
{pagedItems.map((u) => (
|
||||||
|
<ElementUTM key={u.id} u={u} onDelete={onDelete} quizQid={quizQid} />
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
|
{items.length > perPage && (
|
||||||
|
<Box sx={{ display: "flex", justifyContent: "end", mt: 1 }}>
|
||||||
|
<CustomPagination countPagination={totalPages} page={page} onChange={(e, v) => setPage(v)} size="small" />
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -60,7 +60,8 @@ export default function QuizLinkCard() {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
boxShadow:
|
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)",
|
"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)",
|
||||||
}}
|
height: "min-content"
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
@ -34,19 +34,8 @@ const OverTime = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<CustomTextField sx={{ height: "48px", width: "100%", maxWidth: "327px", mt: "9px" }}
|
<CustomTextField sx={{ height: "48px", width: "100%", maxWidth: "327px", mt: "9px" }}
|
||||||
placeholder="Квиз будет недоступен через:"
|
placeholder="Квиз будет недоступен через:"
|
||||||
|
maxLength={40}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ display: "flex", flexDirection: "row-reverse", height: "16px" }}>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontWeight: 400,
|
|
||||||
color: theme.palette.grey3.main,
|
|
||||||
fontSize: "12px",
|
|
||||||
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
0/40
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user