удаление без реактивности, выведен корректный список линков

This commit is contained in:
Nastya 2025-06-02 00:27:22 +03:00
parent c2d79c04cc
commit e7121cb06a
7 changed files with 212 additions and 144 deletions

@ -47,13 +47,13 @@ export interface AuditoryAddParams {
}
// API calls
export const auditoryGet = async ({ quizId }: AuditoryGetParams): Promise<[AuditoryResponse | null, string?]> => {
export const auditoryGet = async ({ quizId }: AuditoryGetParams): Promise<[AuditoryItem[] | null, string?]> => {
if (!quizId) {
return [null, "Quiz ID is required"];
}
try {
const response = await makeRequest<AuditoryGetRequest, AuditoryResponse>({
const response = await makeRequest<AuditoryGetRequest, AuditoryItem[]>({
url: `${API_URL}/quiz/${quizId}/auditory`,
method: "GET",
});
@ -72,10 +72,7 @@ export const auditoryDelete = async ({ quizId, auditoryId }: AuditoryDeleteParam
try {
const response = await makeRequest<AuditoryDeleteRequest, AuditoryResponse>({
url: `${API_URL}/quiz/${quizId}/auditory`,
body: {
id: auditoryId
},
url: `${API_URL}/quiz/${quizId}/auditory/${auditoryId}`,
method: "DELETE",
});

@ -0,0 +1,74 @@
import { AuditoryItem } from "@/api/auditory";
import CopyIcon from "@/assets/icons/CopyIcon";
import Trash from "@/assets/icons/trash";
import { InfoPopover } from "@/ui_kit/InfoPopover";
import TooltipClickInfo from "@/ui_kit/Toolbars/TooltipClickInfo";
import { useDomainDefine } from "@/utils/hooks/useDomainDefine";
import { IconButton, ListItem, Typography, useTheme } from "@mui/material";
interface AuditoryLinkProps {
item: AuditoryItem;
index: number;
deleteModal: (id:number) => void
}
export const AuditoryLink = ({ item, index, deleteModal }: AuditoryLinkProps) => {
const theme = useTheme();
const { isTestServer } = useDomainDefine();
const handleCopy = (text: string) => {
navigator.clipboard.writeText(text);
};
const linkText = `${isTestServer ? "https://s.hbpn.link/" : "https://hbpn.link/"}?_paud=${item.id}`;
return (
<ListItem
key={index}
disablePadding
sx={{
bgcolor: "#F2F3F7",
borderRadius: "10px",
p: "13px 14px 13px 20px",
mb: "8px",
maxWidth: "756px",
display: "flex",
alignItems: "center",
transition: 'background 0.2s, border 0.2s',
'& .MuiListItemSecondaryAction-root': {
display: 'flex',
alignItems: 'center',
gap: '12px',
width: "70px",
justifyContent: "space-between",
},
}}
secondaryAction={
<>
<IconButton onClick={() => deleteModal(item.id)} edge="end" aria-label="info" sx={{ color: theme.palette.brightPurple.main, p: 0, width: 18, height: 18 }}>
<Trash sx={{
"& path": {
stroke: theme.palette.brightPurple.main,
}
}} />
</IconButton>
<IconButton edge="end" aria-label="info" sx={{ color: theme.palette.brightPurple.main, p: 0, width: 18, height: 18 }}>
<TooltipClickInfo title={`Возраст: ${item.age}\n Пол: ${item.sex ? "мужской" : "женский"}`} />
</IconButton>
<IconButton
edge="end"
aria-label="copy"
sx={{ color: theme.palette.brightPurple.main, p: 0, width: 18, height: 18, marginRight: "-2px" }}
onClick={() => handleCopy(linkText)}
>
<CopyIcon color={theme.palette.brightPurple.main} />
</IconButton>
</>
}
>
<Typography sx={{ color: 'black', fontWeight: 400, fontSize: "16px" }}>
{linkText}
</Typography>
</ListItem>
);
};

@ -1,37 +1,17 @@
import { auditoryGet, AuditoryResponse, AuditoryItem } from "@/api/auditory";
import ArrowDownIcon from "@/assets/icons/ArrowDownIcon";
import CopyIcon from "@/assets/icons/CopyIcon";
import { useCurrentQuiz } from "@/stores/quizes/hooks";
import { InfoPopover } from "@/ui_kit/InfoPopover";
import { useDomainDefine } from "@/utils/hooks/useDomainDefine";
import { Box, Collapse, IconButton, List, ListItem, Typography, useTheme } from "@mui/material";
import { Box, Collapse, IconButton, List, Typography, useTheme } from "@mui/material";
import { useEffect, useState } from "react";
import { AuditoryLink } from "./AuditoryLink";
const PURPLE = "#7E2AEA";
export const AuditoryList = () => {
export const AuditoryList = ({auditory, deleteModal}:{auditory:AuditoryItem[], deleteModal:(id:number) => void}) => {
const theme = useTheme();
const quiz = useCurrentQuiz();
const { isTestServer } = useDomainDefine();
const [linksOpen, setLinksOpen] = useState(true);
const [auditory, setAuditory] = useState<AuditoryItem[]>([]);
const handleCopy = (text: string) => {
navigator.clipboard.writeText(text);
};
useEffect(() => {
(async () => {
if (quiz?.backendId) {
const [result, error] = await auditoryGet({ quizId: quiz.backendId });
console.log("result-___---_------__---__-__---_------__---__-__---_------__---__-__---_------__---__-____--__")
console.log(result)
if (result) {
setAuditory(result);
}
}
})();
}, [quiz]);
console.log("auditory-___---_auditory__---__-__auditory_------__---__-__---_------__---__-__---_------__---__-____--__")
console.log(auditory)
@ -51,7 +31,7 @@ export const AuditoryList = () => {
Ваши сохраненные ссылки
</Typography>
<IconButton
sx={{ cursor: 'pointer', color: PURPLE, display: 'flex', alignItems: 'center', transition: 'transform 0.2s', transform: linksOpen ? 'rotate(0deg)' : 'rotate(180deg)' }}
sx={{ cursor: 'pointer', color: theme.palette.brightPurple.main, display: 'flex', alignItems: 'center', transition: 'transform 0.2s', transform: linksOpen ? 'rotate(0deg)' : 'rotate(180deg)' }}
onClick={() => setLinksOpen((prev) => !prev)}
size="large"
>
@ -60,52 +40,9 @@ export const AuditoryList = () => {
</Box>
<Collapse in={linksOpen} timeout="auto" unmountOnExit sx={{ mt: "3px" }}>
<List sx={{ gap: '8px', p: 0, m: 0 }}>
{auditory.map((item, idx) => {
const linkText = `${isTestServer ? "https://s.hbpn.link/" : "https://hbpn.link/"}?_paud=${item.id}`;
console.log(item)
return (
<ListItem
key={idx}
disablePadding
sx={{
bgcolor: "#F2F3F7",
borderRadius: "10px",
p: "13px 14px 13px 20px",
mb: "8px",
maxWidth: "756px",
display: "flex",
alignItems: "center",
transition: 'background 0.2s, border 0.2s',
'& .MuiListItemSecondaryAction-root': {
display: 'flex',
alignItems: 'center',
gap: '12px',
width: "60px",
justifyContent: "space-between",
},
}}
secondaryAction={
<>
<IconButton edge="end" aria-label="info" sx={{ color: PURPLE, p: 0, width: 18, height: 18 }}>
<InfoPopover />
</IconButton>
<IconButton
edge="end"
aria-label="copy"
sx={{ color: PURPLE, p: 0, width: 18, height: 18, marginRight: "-2px" }}
onClick={() => handleCopy(linkText)}
>
<CopyIcon color={PURPLE} />
</IconButton>
</>
}
>
<Typography sx={{ color: 'black', fontWeight: 400, fontSize: "16px" }}>
{linkText}
</Typography>
</ListItem>
);
})}
{auditory.map((item, idx) => (
<AuditoryLink deleteModal={deleteModal} key={idx} item={item} index={idx} />
))}
</List>
</Collapse>
</Box>

@ -1,4 +1,4 @@
import { Box, FormControl, FormLabel, RadioGroup, FormControlLabel, Radio, Select, MenuItem, useTheme } from "@mui/material";
import { Box, FormControl, FormLabel, RadioGroup, FormControlLabel, Radio, Select, MenuItem, useTheme, Button } from "@mui/material";
import { InfoPopover } from '@ui_kit/InfoPopover';
import CheckboxIcon from "@icons/Checkbox";
@ -7,6 +7,7 @@ interface GenderAndAgeSelectorProps {
setGender: (gender: string) => void;
}
export default function GenderAndAgeSelector({ gender, setGender }: GenderAndAgeSelectorProps) {
const theme = useTheme();
return (
@ -167,6 +168,21 @@ export default function GenderAndAgeSelector({ gender, setGender }: GenderAndAge
</Select>
</FormControl>
<Button
variant="contained"
sx={{
bgcolor: theme.palette.brightPurple.main,
borderRadius: "8px",
width: "130px",
height: "48px",
boxShadow: "none",
textTransform: "none",
fontSize: "18px",
'&:hover': { bgcolor: theme.palette.brightPurple.main },
}}
>
Ок
</Button>
</Box>
);
}

@ -1,4 +1,4 @@
import { Box, Container, Typography, TextField, Button, List, ListItem, IconButton } from "@mui/material";
import { Box, Container, Typography, TextField, Button, List, ListItem, IconButton, Modal } from "@mui/material";
import { InfoPopover } from '@ui_kit/InfoPopover';
import CopyIcon from "@/assets/icons/CopyIcon";
import GenderAndAgeSelector from "./GenderAndAgeSelector";
@ -7,60 +7,80 @@ import CustomTextField from "@ui_kit/CustomTextField";
import Collapse from '@mui/material/Collapse';
import { ArrowDownIcon } from "../../assets/icons/questionsPage/ArrowDownIcon";
import { useTheme } from "@mui/material";
import { auditoryAdd, auditoryGet } from "@/api/auditory";
import { AuditoryItem, auditoryAdd, auditoryDelete, auditoryGet } from "@/api/auditory";
import { useCurrentQuiz } from "@/stores/quizes/hooks";
import { AuditoryList } from "./AuditoryList";
const PURPLE = "#7E2AEA";
export default function PersonalizationAI() {
const theme = useTheme();
const [gender, setGender] = useState('');
const [auditory, setAuditory] = useState<AuditoryItem[]>([]);
const [deleteModal, setDeleteModal] = useState<number>(0);
const quiz = useCurrentQuiz();
useEffect(() => {
(async () => {
if (quiz?.backendId) {
const [result, error] = await auditoryGet({ quizId: quiz.backendId });
console.log("result-___---_------__---__-__---_------__---__-__---_------__---__-__---_------__---__-____--__")
console.log(result)
if (result) {
setAuditory(result);
}
}
})();
}, [quiz]);
const handleDelete = () => {
setDeleteModal(0)
auditoryDelete({ quizId: quiz?.backendId, auditoryId: deleteModal })
}
return (
<Container id="PersonalizationAI" maxWidth={false} sx={{ minHeight: "100%", p: "20px" }}>
<Typography variant="h5" color={theme.palette.grey3.main} fontWeight={700} sx={{ fontSize: 24, letterSpacing: "-0.2px" }}>
Персонализация вопросов с помощью AI
</Typography>
<Typography sx={{
color: theme.palette.grey3.main, fontSize: "18px", maxWidth: 796, m: 0,
mt: "19px",
letterSpacing: "0.009px",
wordSpacing: "0.1px",
lineHeight: "21.4px"
}}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</Typography>
<>
<Container id="PersonalizationAI" maxWidth={false} sx={{ minHeight: "100%", p: "20px" }}>
<Typography variant="h5" color={theme.palette.grey3.main} fontWeight={700} sx={{ fontSize: 24, letterSpacing: "-0.2px" }}>
Персонализация вопросов с помощью AI
</Typography>
<Typography sx={{
color: theme.palette.grey3.main, fontSize: "18px", maxWidth: 796, m: 0,
mt: "19px",
letterSpacing: "0.009px",
wordSpacing: "0.1px",
lineHeight: "21.4px"
}}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
</Typography>
{/* Первый белый блок */}
<Box sx={{
bgcolor: "#fff",
borderRadius: "12px",
mt: "40px",
p: "20px 20px 30px",
boxShadow: "none",
maxWidth: "796px"
}}>
<GenderAndAgeSelector gender={gender} setGender={setGender} />
{/* Первый белый блок */}
<Box sx={{
bgcolor: "#fff",
borderRadius: "12px",
mt: "40px",
p: "20px 20px 30px",
boxShadow: "none",
maxWidth: "796px"
}}>
<GenderAndAgeSelector gender={gender} setGender={setGender} />
{/* Ссылка */}
<Box sx={{ mt: "34px" }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<Typography sx={{ color: theme.palette.grey3.main, fontSize: "18px", fontWeight: 500 }}>Ссылка</Typography>
<InfoPopover />
</Box>
<Typography
sx={{
fontSize: "14px",
lineHeight: "100%",
letterSpacing: "0 %",
color: theme.palette.grey2.main,
mt: "16px"
}}
>
Вставьте ссылку со всеми utm-метками
</Typography>
<Box sx={{ display: 'flex', gap: 2, alignItems: 'center', mt: "6px" }}>
{/* Ссылка */}
<Box sx={{ mt: "34px" }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<Typography sx={{ color: theme.palette.grey3.main, fontSize: "18px", fontWeight: 500 }}>Ссылка</Typography>
<InfoPopover />
</Box>
<Typography
sx={{
fontSize: "14px",
lineHeight: "100%",
letterSpacing: "0 %",
color: theme.palette.grey2.main,
mt: "16px"
}}
>
Вставьте ссылку со всеми utm-метками
</Typography>
<CustomTextField
placeholder="linkexample.com"
maxLength={5}
@ -69,27 +89,41 @@ export default function PersonalizationAI() {
}}
/>
<Button
variant="contained"
sx={{
bgcolor: PURPLE,
borderRadius: "8px",
width: "130px",
height: "48px",
boxShadow: "none",
textTransform: "none",
fontSize: "18px",
'&:hover': { bgcolor: PURPLE },
}}
>
Ок
</Button>
</Box>
</Box>
</Box>
<AuditoryList />
<AuditoryList deleteModal={setDeleteModal} auditory={auditory} />
</Container>
</Container>
<Modal
open={Boolean(deleteModal)}
onClose={() => setDeleteModal(0)}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<Box
sx={{
position: "absolute" as "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
maxWidth: "620px",
width: "100%",
bgcolor: "background.paper",
borderRadius: "12px",
boxShadow: 24,
p: "20px",
display: "flex",
flexDirection: "column",
alignItems: "center"
}}
>
<Typography sx={{ width: "100%" ,textAlign: "center", mb: "25px"}}>Уверены, что хотите удалить ссылку?</Typography>
<Button sx={{mb: "20px"}} onClick={handleDelete}>Удалить</Button>
<Button variant="contained" onClick={() => setDeleteModal(0)} >Отмена</Button>
</Box>
</Modal>
</>
);
}

@ -1,9 +1,17 @@
import { useState, MouseEvent } from "react";
import { useState, MouseEvent, ReactNode } from "react";
import Info from "@icons/Info";
import { Paper, Popover, SxProps, Typography } from "@mui/material";
export const InfoPopover = ({ blink = false, sx }: {blink?: boolean, sx?: SxProps}) => {
export const InfoPopover = ({
blink = false,
sx,
children = "подсказка"
}: {
blink?: boolean,
sx?: SxProps,
children?: ReactNode
}) => {
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
@ -43,9 +51,7 @@ export const InfoPopover = ({ blink = false, sx }: {blink?: boolean, sx?: SxProp
flexDirection: "column",
}}
>
<Typography>
подсказка
</Typography>
{children}
</Paper>
</Popover>
</>

@ -26,6 +26,10 @@ export default function TooltipClickInfo({ title }: { title: string }) {
disableFocusListener
disableHoverListener
disableTouchListener
sx={{
fontSize: "12px",
p:"10px"
}}
title={title}
>
<IconButton onClick={handleTooltipOpen}>