720 lines
28 KiB
TypeScript
720 lines
28 KiB
TypeScript
import {
|
||
Box,
|
||
Button,
|
||
Paper,
|
||
Typography,
|
||
Menu,
|
||
MenuItem,
|
||
TextField,
|
||
} from "@mui/material";
|
||
import {
|
||
BackButton,
|
||
createSightStore,
|
||
languageStore,
|
||
SelectArticleModal,
|
||
TabPanel,
|
||
SelectMediaDialog, // Import
|
||
UploadMediaDialog,
|
||
Media, // Import
|
||
} from "@shared";
|
||
import {
|
||
LanguageSwitcher,
|
||
MediaArea, // Import
|
||
MediaAreaForSight, // Import
|
||
ReactMarkdownComponent,
|
||
ReactMarkdownEditor,
|
||
DeleteModal,
|
||
} from "@widgets";
|
||
import { ImagePlus, Plus, Save, Trash2, Unlink, X } from "lucide-react"; // Import X
|
||
import { observer } from "mobx-react-lite";
|
||
import { useState, useEffect } from "react"; // Added useEffect
|
||
import { MediaViewer } from "../../MediaViewer/index";
|
||
import { toast } from "react-toastify";
|
||
import { authInstance } from "@shared";
|
||
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
|
||
|
||
type MediaItemShared = {
|
||
// Define if not already available from @shared
|
||
id: string;
|
||
filename: string;
|
||
media_name?: string;
|
||
media_type: number;
|
||
};
|
||
|
||
export const CreateRightTab = observer(
|
||
({ value, index }: { value: number; index: number }) => {
|
||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||
const {
|
||
sight,
|
||
createNewRightArticle,
|
||
updateRightArticleInfo,
|
||
linkPreviewMedia,
|
||
unlinkPreviewMedia,
|
||
createLinkWithRightArticle,
|
||
deleteRightArticleMedia,
|
||
setFileToUpload, // From store
|
||
setUploadMediaOpen, // From store
|
||
uploadMediaOpen, // From store
|
||
unlinkRightAritcle, // Corrected spelling
|
||
deleteRightArticle,
|
||
linkExistingRightArticle,
|
||
createSight,
|
||
clearCreateSight, // For resetting form
|
||
updateRightArticles,
|
||
} = createSightStore;
|
||
const { language } = languageStore;
|
||
|
||
const [selectArticleDialogOpen, setSelectArticleDialogOpen] =
|
||
useState(false);
|
||
const [activeArticleIndex, setActiveArticleIndex] = useState<number | null>(
|
||
null
|
||
);
|
||
const [type, setType] = useState<"article" | "media">("media");
|
||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||
const [isSelectMediaDialogOpen, setIsSelectMediaDialogOpen] =
|
||
useState(false);
|
||
const [mediaTarget, setMediaTarget] = useState<
|
||
"sightPreview" | "rightArticle" | null
|
||
>(null);
|
||
|
||
const [previewMedia, setPreviewMedia] = useState<Media | null>(null);
|
||
// Reset activeArticleIndex if language changes and index is out of bounds
|
||
useEffect(() => {
|
||
if (sight.preview_media) {
|
||
const fetchMedia = async () => {
|
||
const response = await authInstance.get(
|
||
`/media/${sight.preview_media}`
|
||
);
|
||
setPreviewMedia(response.data);
|
||
};
|
||
fetchMedia();
|
||
}
|
||
}, [sight.preview_media]);
|
||
|
||
useEffect(() => {
|
||
if (
|
||
activeArticleIndex !== null &&
|
||
activeArticleIndex >= sight[language].right.length
|
||
) {
|
||
setActiveArticleIndex(null);
|
||
setType("media"); // Default back to media preview if selected article disappears
|
||
}
|
||
}, [language, sight[language].right, activeArticleIndex]);
|
||
|
||
const openMenu = Boolean(anchorEl);
|
||
const handleClickMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||
setAnchorEl(event.currentTarget);
|
||
};
|
||
const handleCloseMenu = () => {
|
||
setAnchorEl(null);
|
||
};
|
||
|
||
const handleSave = async () => {
|
||
try {
|
||
await createSight(language);
|
||
toast.success("Достопримечательность успешно создана!");
|
||
clearCreateSight(); // Reset form
|
||
setActiveArticleIndex(null);
|
||
setType("media");
|
||
// Potentially navigate away: history.push('/sights-list');
|
||
} catch (error) {
|
||
console.error("Failed to save sight:", error);
|
||
toast.error("Ошибка при создании достопримечательности.");
|
||
}
|
||
};
|
||
|
||
const handleDisplayArticleFromList = (idx: number) => {
|
||
setActiveArticleIndex(idx);
|
||
setType("article");
|
||
};
|
||
|
||
const handleCreateNewLocalArticle = async () => {
|
||
handleCloseMenu();
|
||
try {
|
||
const newArticleId = await createNewRightArticle();
|
||
// Automatically select the new article if ID is returned
|
||
const newIndex = sight[language].right.findIndex(
|
||
(a) => a.id === newArticleId
|
||
);
|
||
if (newIndex > -1) {
|
||
setActiveArticleIndex(newIndex);
|
||
setType("article");
|
||
} else {
|
||
// Fallback if findIndex fails (should not happen if store updates correctly)
|
||
setActiveArticleIndex(sight[language].right.length - 1);
|
||
setType("article");
|
||
}
|
||
} catch (error) {
|
||
toast.error("Не удалось создать новую статью.");
|
||
}
|
||
};
|
||
|
||
const handleSelectExistingArticleAndLink = async (
|
||
selectedArticleId: number
|
||
) => {
|
||
try {
|
||
await linkExistingRightArticle(selectedArticleId);
|
||
setSelectArticleDialogOpen(false); // Close dialog
|
||
const newIndex = sight[language].right.findIndex(
|
||
(a) => a.id === selectedArticleId
|
||
);
|
||
if (newIndex > -1) {
|
||
setActiveArticleIndex(newIndex);
|
||
setType("article");
|
||
}
|
||
} catch (error) {
|
||
toast.error("Не удалось привязать существующую статью.");
|
||
}
|
||
};
|
||
|
||
const currentRightArticle =
|
||
activeArticleIndex !== null && sight[language].right[activeArticleIndex]
|
||
? sight[language].right[activeArticleIndex]
|
||
: null;
|
||
|
||
// Media Handling for Dialogs
|
||
const handleOpenUploadMedia = () => {
|
||
setUploadMediaOpen(true);
|
||
};
|
||
|
||
const handleOpenSelectMediaDialog = (
|
||
target: "sightPreview" | "rightArticle"
|
||
) => {
|
||
setMediaTarget(target);
|
||
setIsSelectMediaDialogOpen(true);
|
||
};
|
||
|
||
const handleMediaSelectedFromDialog = async (media: MediaItemShared) => {
|
||
setIsSelectMediaDialogOpen(false);
|
||
|
||
if (mediaTarget === "sightPreview") {
|
||
await linkPreviewMedia(media.id);
|
||
} else if (mediaTarget === "rightArticle" && currentRightArticle) {
|
||
await createLinkWithRightArticle(media, currentRightArticle.id);
|
||
}
|
||
setMediaTarget(null);
|
||
};
|
||
|
||
const handleUnlinkPreviewMedia = async () => {
|
||
await unlinkPreviewMedia();
|
||
setPreviewMedia(null);
|
||
};
|
||
|
||
const handleMediaUploaded = async (media: MediaItemShared) => {
|
||
// After UploadMediaDialog finishes
|
||
setUploadMediaOpen(false);
|
||
setFileToUpload(null);
|
||
if (mediaTarget === "sightPreview") {
|
||
linkPreviewMedia(media.id);
|
||
} else if (mediaTarget === "rightArticle" && currentRightArticle) {
|
||
await createLinkWithRightArticle(media, currentRightArticle.id);
|
||
}
|
||
setMediaTarget(null); // Reset target
|
||
};
|
||
|
||
const handleDragEnd = (result: any) => {
|
||
const { source, destination } = result;
|
||
|
||
// 1. Guard clause: If dropped outside any droppable area, do nothing.
|
||
if (!destination) return;
|
||
|
||
// Extract source and destination indices
|
||
const sourceIndex = source.index;
|
||
const destinationIndex = destination.index;
|
||
|
||
// 2. Guard clause: If dropped in the same position, do nothing.
|
||
if (sourceIndex === destinationIndex) return;
|
||
|
||
// 3. Create a new array with reordered articles:
|
||
// - Create a shallow copy of the current articles array.
|
||
// This is important for immutability and triggering re-renders.
|
||
const newRightArticles = [...sight[language].right];
|
||
|
||
// - Remove the dragged article from its original position.
|
||
// `splice` returns an array of removed items, so we destructure the first (and only) one.
|
||
const [movedArticle] = newRightArticles.splice(sourceIndex, 1);
|
||
|
||
// - Insert the moved article into its new position.
|
||
newRightArticles.splice(destinationIndex, 0, movedArticle);
|
||
|
||
// 4. Update the store with the new order:
|
||
// This will typically trigger a re-render of the component with the updated list.
|
||
updateRightArticles(newRightArticles, language);
|
||
};
|
||
|
||
return (
|
||
<TabPanel value={value} index={index}>
|
||
<LanguageSwitcher />
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
height: "100%",
|
||
minHeight: "calc(100vh - 200px)",
|
||
gap: 2,
|
||
paddingBottom: "70px", // Space for the save button
|
||
position: "relative",
|
||
}}
|
||
>
|
||
<BackButton />
|
||
|
||
<Box sx={{ display: "flex", flexGrow: 1, gap: 2.5 }}>
|
||
{/* Left Column: Navigation & Article List */}
|
||
<Box className="flex flex-col w-[75%] gap-2">
|
||
<Box className="w-full flex gap-2 ">
|
||
<Box className="relative w-[20%] h-[70vh] flex flex-col rounded-2xl overflow-y-auto gap-3 border border-gray-300 p-3">
|
||
<Box className="flex flex-col gap-3 max-h-[60vh] overflow-y-auto">
|
||
<Box
|
||
onClick={() => {
|
||
setType("media");
|
||
// setActiveArticleIndex(null); // Optional: deselect article when switching to general media view
|
||
}}
|
||
className={`w-full p-4 rounded-2xl cursor-pointer text-sm hover:bg-gray-300 transition-all duration-300 ${
|
||
type === "media"
|
||
? "bg-green-300 font-semibold"
|
||
: "bg-green-200"
|
||
}`}
|
||
>
|
||
<Typography>Предпросмотр медиа</Typography>
|
||
</Box>
|
||
<Box>
|
||
<DragDropContext onDragEnd={handleDragEnd}>
|
||
<Droppable droppableId="articles">
|
||
{(provided) => (
|
||
<Box
|
||
ref={provided.innerRef}
|
||
{...provided.droppableProps}
|
||
className="flex flex-col gap-2"
|
||
>
|
||
{sight[language].right.length > 0
|
||
? sight[language].right.map(
|
||
(article, index) => (
|
||
<Draggable
|
||
key={article.id.toString()}
|
||
draggableId={article.id.toString()}
|
||
index={index}
|
||
>
|
||
{(provided, snapshot) => (
|
||
<Box
|
||
ref={provided.innerRef}
|
||
{...provided.draggableProps}
|
||
className={`w-full bg-gray-200 p-4 rounded-2xl text-sm cursor-pointer hover:bg-gray-300 ${
|
||
snapshot.isDragging
|
||
? "shadow-lg"
|
||
: ""
|
||
}`}
|
||
onClick={() => {
|
||
handleDisplayArticleFromList(
|
||
index
|
||
);
|
||
setType("article");
|
||
}}
|
||
>
|
||
<Box {...provided.dragHandleProps}>
|
||
<Typography>
|
||
{article.heading}
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
)}
|
||
</Draggable>
|
||
)
|
||
)
|
||
: null}
|
||
{provided.placeholder}
|
||
</Box>
|
||
)}
|
||
</Droppable>
|
||
</DragDropContext>
|
||
</Box>
|
||
</Box>
|
||
<button
|
||
className="w-10 h-10 bg-blue-500 rounded-full absolute bottom-5 left-5 flex items-center justify-center hover:bg-blue-600"
|
||
onClick={handleClickMenu}
|
||
aria-controls={openMenu ? "add-article-menu" : undefined}
|
||
aria-haspopup="true"
|
||
aria-expanded={openMenu ? "true" : undefined}
|
||
>
|
||
<Plus size={20} color="white" />
|
||
</button>
|
||
<Menu
|
||
id="add-article-menu"
|
||
anchorEl={anchorEl}
|
||
open={openMenu}
|
||
onClose={handleCloseMenu}
|
||
MenuListProps={{ "aria-labelledby": "basic-button" }}
|
||
sx={{ mt: 1 }}
|
||
>
|
||
<MenuItem onClick={handleCreateNewLocalArticle}>
|
||
<Typography>Создать новую</Typography>
|
||
</MenuItem>
|
||
<MenuItem
|
||
onClick={() => {
|
||
setSelectArticleDialogOpen(true);
|
||
handleCloseMenu();
|
||
}}
|
||
>
|
||
<Typography>Выбрать существующую статью</Typography>
|
||
</MenuItem>
|
||
</Menu>
|
||
</Box>
|
||
|
||
{/* Main content area: Article Editor or Sight Media Preview */}
|
||
{type === "article" && currentRightArticle ? (
|
||
<Box className="w-[80%] border border-gray-300 p-3 flex flex-col gap-2 overflow-hidden">
|
||
<Box className="flex justify-end gap-2 mb-1 flex-shrink-0">
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
size="small"
|
||
startIcon={<Unlink color="white" size={18} />}
|
||
onClick={() => {
|
||
if (currentRightArticle) {
|
||
unlinkRightAritcle(currentRightArticle.id); // Corrected function name
|
||
setActiveArticleIndex(null);
|
||
setType("media");
|
||
}
|
||
}}
|
||
>
|
||
Открепить
|
||
</Button>
|
||
<Button
|
||
variant="contained"
|
||
color="error"
|
||
size="small"
|
||
startIcon={<Trash2 size={18} />}
|
||
onClick={async () => {
|
||
setIsDeleteModalOpen(true);
|
||
}}
|
||
>
|
||
Удалить
|
||
</Button>
|
||
</Box>
|
||
<Box
|
||
sx={{
|
||
flexGrow: 1,
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 2,
|
||
overflowY: "auto",
|
||
pt: 7,
|
||
}}
|
||
>
|
||
<TextField
|
||
label="Название информации (правый виджет)"
|
||
value={currentRightArticle.heading}
|
||
onChange={(e) =>
|
||
activeArticleIndex !== null &&
|
||
updateRightArticleInfo(
|
||
activeArticleIndex,
|
||
language,
|
||
e.target.value,
|
||
currentRightArticle.body
|
||
)
|
||
}
|
||
variant="outlined"
|
||
fullWidth
|
||
/>
|
||
<Box sx={{ minHeight: 200, flexGrow: 1 }}>
|
||
<ReactMarkdownEditor
|
||
value={currentRightArticle.body}
|
||
onChange={(mdValue) =>
|
||
activeArticleIndex !== null &&
|
||
updateRightArticleInfo(
|
||
activeArticleIndex,
|
||
language,
|
||
currentRightArticle.heading,
|
||
mdValue || ""
|
||
)
|
||
}
|
||
/>
|
||
</Box>
|
||
<MediaArea
|
||
articleId={currentRightArticle.id} // Needs a real ID
|
||
mediaIds={currentRightArticle.media || []}
|
||
onFilesDrop={(files) => {
|
||
if (files.length > 0) {
|
||
setFileToUpload(files[0]);
|
||
setMediaTarget("rightArticle");
|
||
handleOpenUploadMedia();
|
||
}
|
||
}}
|
||
deleteMedia={deleteRightArticleMedia}
|
||
setSelectMediaDialogOpen={() =>
|
||
handleOpenSelectMediaDialog("rightArticle")
|
||
}
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
) : type === "media" ? (
|
||
<Box className="w-[80%] border border-gray-300 rounded-2xl relative flex items-center justify-center">
|
||
{sight.preview_media && (
|
||
<>
|
||
{type === "media" && (
|
||
<Box className="w-[80%] h-full rounded-2xl relative flex items-center justify-center">
|
||
{previewMedia && (
|
||
<>
|
||
<Box className="absolute top-4 right-4 z-10">
|
||
<button
|
||
className="w-10 h-10 flex items-center justify-center z-10"
|
||
onClick={handleUnlinkPreviewMedia}
|
||
>
|
||
<X size={20} color="red" />
|
||
</button>
|
||
</Box>
|
||
|
||
<Box className="w-1/2 h-1/2">
|
||
<MediaViewer
|
||
media={{
|
||
id: previewMedia.id || "",
|
||
media_type: previewMedia.media_type,
|
||
filename: previewMedia.filename || "",
|
||
}}
|
||
/>
|
||
</Box>
|
||
</>
|
||
)}
|
||
{!previewMedia && (
|
||
<MediaAreaForSight
|
||
onFinishUpload={(mediaId) => {
|
||
linkPreviewMedia(mediaId);
|
||
}}
|
||
onFilesDrop={() => {}}
|
||
/>
|
||
)}
|
||
</Box>
|
||
)}
|
||
</>
|
||
)}
|
||
{!previewMedia && (
|
||
<MediaAreaForSight
|
||
onFinishUpload={(mediaId) => {
|
||
linkPreviewMedia(mediaId);
|
||
}}
|
||
onFilesDrop={() => {}}
|
||
/>
|
||
)}
|
||
</Box>
|
||
) : (
|
||
<Box className="w-[80%] h-[70vh] border border-gray-300 rounded-2xl p-3 flex justify-center items-center">
|
||
<Typography variant="h6" color="text.secondary">
|
||
Выберите статью слева или секцию "Предпросмотр медиа"
|
||
</Typography>
|
||
</Box>
|
||
)}
|
||
</Box>
|
||
</Box>
|
||
|
||
{/* Right Column: Live Preview */}
|
||
<Box className="w-[25%] mr-10">
|
||
{type === "article" && activeArticleIndex !== null && (
|
||
<Paper
|
||
className="flex-1 flex flex-col max-w-[500px]"
|
||
sx={{
|
||
borderRadius: "16px",
|
||
overflow: "hidden",
|
||
}}
|
||
elevation={2}
|
||
>
|
||
<Box
|
||
className=" overflow-hidden"
|
||
sx={{
|
||
width: "100%",
|
||
|
||
background: "#877361",
|
||
borderColor: "grey.300",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
}}
|
||
>
|
||
{sight[language].right[activeArticleIndex].media.length >
|
||
0 ? (
|
||
<Box
|
||
sx={{
|
||
width: "100%",
|
||
maxHeight: "290px",
|
||
flexShrink: 0,
|
||
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
}}
|
||
>
|
||
<MediaViewer
|
||
media={
|
||
sight[language].right[activeArticleIndex].media[0]
|
||
}
|
||
/>
|
||
</Box>
|
||
) : (
|
||
<Box
|
||
sx={{
|
||
width: "100%",
|
||
height: 200,
|
||
flexShrink: 0,
|
||
|
||
backgroundColor: "rgba(0,0,0,0.1)",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
}}
|
||
>
|
||
<ImagePlus size={48} color="white" />
|
||
</Box>
|
||
)}
|
||
|
||
<Box
|
||
sx={{
|
||
p: 1,
|
||
wordBreak: "break-word",
|
||
fontSize: "24px",
|
||
fontWeight: 700,
|
||
lineHeight: "120%",
|
||
backdropFilter: "blur(12px)",
|
||
boxShadow:
|
||
"inset 4px 4px 12px 0 rgba(255,255,255,0.12)",
|
||
background:
|
||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||
}}
|
||
>
|
||
<Typography variant="h6" color="white">
|
||
{sight[language].right[activeArticleIndex].heading ||
|
||
"Выберите статью"}
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Box
|
||
sx={{
|
||
padding: 1,
|
||
minHeight: "200px",
|
||
maxHeight: "300px",
|
||
overflowY: "scroll",
|
||
background:
|
||
"rgba(179, 165, 152, 0.4), linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.2) 100%)",
|
||
|
||
flexGrow: 1,
|
||
}}
|
||
>
|
||
{sight[language].right[activeArticleIndex].body ? (
|
||
<ReactMarkdownComponent
|
||
value={sight[language].right[activeArticleIndex].body}
|
||
/>
|
||
) : (
|
||
<Typography
|
||
color="rgba(255,255,255,0.7)"
|
||
sx={{ textAlign: "center", mt: 4 }}
|
||
>
|
||
Предпросмотр статьи появится здесь
|
||
</Typography>
|
||
)}
|
||
</Box>
|
||
<Box
|
||
sx={{
|
||
p: 2,
|
||
display: "flex",
|
||
justifyContent: "space-between",
|
||
fontSize: "24px",
|
||
fontWeight: 700,
|
||
lineHeight: "120%",
|
||
flexWrap: "wrap",
|
||
|
||
gap: 1,
|
||
backdropFilter: "blur(12px)",
|
||
boxShadow:
|
||
"inset 4px 4px 12px 0 rgba(255,255,255,0.12)",
|
||
background:
|
||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||
}}
|
||
>
|
||
{sight[language].right.length > 0 &&
|
||
sight[language].right.map((article, index) => (
|
||
<button
|
||
className={`inline-block text-left text-xs text-white ${
|
||
activeArticleIndex === index ? "underline" : ""
|
||
}`}
|
||
onClick={() => {
|
||
setActiveArticleIndex(index);
|
||
setType("article");
|
||
}}
|
||
>
|
||
{article.heading}
|
||
</button>
|
||
))}
|
||
</Box>
|
||
</Box>
|
||
</Paper>
|
||
)}
|
||
</Box>
|
||
</Box>
|
||
|
||
{/* Sticky Save Button Footer */}
|
||
<Box
|
||
sx={{
|
||
position: "absolute",
|
||
bottom: "-20px",
|
||
left: 0, // ensure it spans from left
|
||
right: 0,
|
||
padding: 2,
|
||
backgroundColor: "background.paper",
|
||
|
||
width: "100%",
|
||
display: "flex",
|
||
justifyContent: "flex-end",
|
||
}}
|
||
>
|
||
<Button
|
||
variant="contained"
|
||
color="success"
|
||
onClick={handleSave}
|
||
size="large"
|
||
startIcon={<Save color="white" size={18} />}
|
||
>
|
||
Сохранить
|
||
</Button>
|
||
</Box>
|
||
</Box>
|
||
|
||
{/* Modals */}
|
||
<SelectArticleModal
|
||
open={selectArticleDialogOpen}
|
||
onClose={() => setSelectArticleDialogOpen(false)}
|
||
onSelectArticle={handleSelectExistingArticleAndLink}
|
||
// Pass IDs of already linked/added right articles to exclude them from selection
|
||
linkedArticleIds={sight[language].right.map((article) => article.id)}
|
||
/>
|
||
<UploadMediaDialog
|
||
open={uploadMediaOpen} // From store
|
||
onClose={() => {
|
||
setUploadMediaOpen(false);
|
||
setFileToUpload(null); // Clear file if dialog is closed without upload
|
||
setMediaTarget(null);
|
||
}}
|
||
afterUpload={handleMediaUploaded} // This will use the mediaTarget
|
||
/>
|
||
<SelectMediaDialog
|
||
open={isSelectMediaDialogOpen}
|
||
onClose={() => {
|
||
setIsSelectMediaDialogOpen(false);
|
||
setMediaTarget(null);
|
||
}}
|
||
onSelectMedia={handleMediaSelectedFromDialog}
|
||
/>
|
||
<DeleteModal
|
||
open={isDeleteModalOpen}
|
||
onDelete={async () => {
|
||
try {
|
||
await deleteRightArticle(currentRightArticle?.id || 0);
|
||
setActiveArticleIndex(null);
|
||
setType("media");
|
||
toast.success("Статья удалена");
|
||
} catch {
|
||
toast.error("Не удалось удалить статью");
|
||
}
|
||
}}
|
||
onCancel={() => setIsDeleteModalOpen(false)}
|
||
/>
|
||
</TabPanel>
|
||
);
|
||
}
|
||
);
|