feat: DND for Articles

This commit is contained in:
Илья Куприец 2025-06-04 22:01:20 +03:00
parent 89488d9921
commit 1973ff4304
5 changed files with 477 additions and 194 deletions

View File

@ -586,6 +586,12 @@ class CreateSightStore {
throw error; throw error;
} }
}; };
updateRightArticles = async (articles: any[], language: Language) => {
runInAction(() => {
this.sight[language].right = articles;
});
};
} }
export const createSightStore = new CreateSightStore(); export const createSightStore = new CreateSightStore();

View File

@ -121,35 +121,32 @@ class EditSightStore {
let responseEn = await languageInstance("en").get(`/sight/${id}/article`); let responseEn = await languageInstance("en").get(`/sight/${id}/article`);
let responseZh = await languageInstance("zh").get(`/sight/${id}/article`); let responseZh = await languageInstance("zh").get(`/sight/${id}/article`);
// Function to fetch media for a given set of articles // Create a map of article IDs to their media
const fetchMediaForArticles = async (articles: any[]) => { const mediaMap = new Map();
const articlesWithMedia = []; for (const article of responseRu.data) {
for (const article of articles) { const responseMedia = await authInstance.get(
const responseMedia = await authInstance.get( `/article/${article.id}/media`
`/article/${article.id}/media` );
); mediaMap.set(article.id, responseMedia.data);
articlesWithMedia.push({ }
...article,
media: responseMedia.data,
});
}
return articlesWithMedia;
};
// Fetch media for articles in each language // Function to add media to articles
const ruArticlesWithMedia = await fetchMediaForArticles(responseRu.data); const addMediaToArticles = (articles: any[]) => {
const enArticlesWithMedia = await fetchMediaForArticles(responseEn.data); return articles.map((article) => ({
const zhArticlesWithMedia = await fetchMediaForArticles(responseZh.data); ...article,
media: mediaMap.get(article.id),
}));
};
const data = { const data = {
ru: { ru: {
right: ruArticlesWithMedia, right: addMediaToArticles(responseRu.data),
}, },
en: { en: {
right: enArticlesWithMedia, right: addMediaToArticles(responseEn.data),
}, },
zh: { zh: {
right: zhArticlesWithMedia, right: addMediaToArticles(responseZh.data),
}, },
}; };
@ -673,6 +670,16 @@ class EditSightStore {
this.sight[language].right[index].heading = heading; this.sight[language].right[index].heading = heading;
this.sight[language].right[index].body = body; this.sight[language].right[index].body = body;
}; };
updateRightArticles = async (articles: any[], language: Language) => {
this.sight[language].right = articles;
const articleIdsInObject = articles.map((article) => ({
id: article.id,
}));
await authInstance.post(`/sight/${this.sight.common.id}/article/order`, {
articles: articleIdsInObject,
});
};
} }
export const editSightStore = new EditSightStore(); export const editSightStore = new EditSightStore();

View File

@ -33,7 +33,7 @@ export function MediaViewer({
alt={media?.filename} alt={media?.filename}
style={{ style={{
width: "100%", width: "100%",
height: "100%",
objectFit: "cover", objectFit: "cover",
}} }}
/> />
@ -45,9 +45,10 @@ export function MediaViewer({
media?.id media?.id
}/download?token=${token}`} }/download?token=${token}`}
style={{ style={{
width: "100%",
height: "100%", height: "100%",
objectFit: "contain", objectFit: "cover",
borderRadius: 30, borderRadius: 8,
}} }}
controls controls
autoPlay autoPlay
@ -63,7 +64,7 @@ export function MediaViewer({
style={{ style={{
width: "100%", width: "100%",
height: "100%", height: "100%",
objectFit: "contain", objectFit: "cover",
borderRadius: 8, borderRadius: 8,
}} }}
/> />
@ -77,7 +78,7 @@ export function MediaViewer({
style={{ style={{
width: "100%", width: "100%",
height: "100%", height: "100%",
objectFit: "contain", objectFit: "cover",
borderRadius: 8, borderRadius: 8,
}} }}
/> />

View File

@ -31,6 +31,7 @@ import { useState, useEffect } from "react"; // Added useEffect
import { MediaViewer } from "../../MediaViewer/index"; import { MediaViewer } from "../../MediaViewer/index";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { authInstance } from "@shared"; import { authInstance } from "@shared";
import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd";
type MediaItemShared = { type MediaItemShared = {
// Define if not already available from @shared // Define if not already available from @shared
@ -59,6 +60,7 @@ export const CreateRightTab = observer(
linkExistingRightArticle, linkExistingRightArticle,
createSight, createSight,
clearCreateSight, // For resetting form clearCreateSight, // For resetting form
updateRightArticles,
} = createSightStore; } = createSightStore;
const { language } = languageStore; const { language } = languageStore;
@ -210,6 +212,36 @@ export const CreateRightTab = observer(
setMediaTarget(null); // Reset target 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 ( return (
<TabPanel value={value} index={index}> <TabPanel value={value} index={index}>
<LanguageSwitcher /> <LanguageSwitcher />
@ -245,22 +277,56 @@ export const CreateRightTab = observer(
> >
<Typography>Предпросмотр медиа</Typography> <Typography>Предпросмотр медиа</Typography>
</Box> </Box>
<Box>
{sight[language].right.map((article, artIdx) => ( <DragDropContext onDragEnd={handleDragEnd}>
<Box <Droppable droppableId="articles">
key={article.id || artIdx} // article.id should be preferred {(provided) => (
className={`w-full p-4 rounded-2xl text-sm cursor-pointer hover:bg-gray-300 transition-all duration-300 ${ <Box
type === "article" && activeArticleIndex === artIdx ref={provided.innerRef}
? "bg-blue-300 font-semibold" {...provided.droppableProps}
: "bg-gray-200" className="flex flex-col gap-2"
}`} >
onClick={() => handleDisplayArticleFromList(artIdx)} {sight[language].right.length > 0
> ? sight[language].right.map(
<Typography noWrap title={article.heading}> (article, index) => (
{article.heading || "Без названия"} <Draggable
</Typography> key={article.id.toString()}
</Box> 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> </Box>
<button <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" className="w-10 h-10 bg-blue-500 rounded-full absolute bottom-5 left-5 flex items-center justify-center hover:bg-blue-600"
@ -298,7 +364,7 @@ export const CreateRightTab = observer(
<Box className="w-[80%] border border-gray-300 p-3 flex flex-col gap-2 overflow-hidden"> <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"> <Box className="flex justify-end gap-2 mb-1 flex-shrink-0">
<Button <Button
variant="outlined" variant="contained"
color="primary" color="primary"
size="small" size="small"
startIcon={<Unlink color="white" size={18} />} startIcon={<Unlink color="white" size={18} />}
@ -381,40 +447,52 @@ export const CreateRightTab = observer(
</Box> </Box>
</Box> </Box>
) : type === "media" ? ( ) : type === "media" ? (
<Box className="w-[80%] h-[70vh] border border-gray-300 rounded-2xl p-3 relative flex justify-center items-center"> <Box className="w-[80%] border border-gray-300 rounded-2xl relative flex items-center justify-center">
{type === "media" && ( {sight.preview_media && (
<Box className="w-[80%] border border-gray-300 rounded-2xl relative flex items-center justify-center"> <>
{previewMedia && ( {type === "media" && (
<> <Box className="w-[80%] h-full rounded-2xl relative flex items-center justify-center">
<Box className="absolute top-4 right-4 z-10"> {previewMedia && (
<button <>
className="w-10 h-10 flex items-center justify-center z-10" <Box className="absolute top-4 right-4 z-10">
onClick={handleUnlinkPreviewMedia} <button
> className="w-10 h-10 flex items-center justify-center z-10"
<X size={20} color="red" /> onClick={handleUnlinkPreviewMedia}
</button> >
</Box> <X size={20} color="red" />
</button>
</Box>
<Box sx={{}}> <Box className="w-1/2 h-1/2">
<MediaViewer <MediaViewer
media={{ media={{
id: previewMedia.id || "", id: previewMedia.id || "",
media_type: previewMedia.media_type, media_type: previewMedia.media_type,
filename: previewMedia.filename || "", filename: previewMedia.filename || "",
}}
/>
</Box>
</>
)}
{!previewMedia && (
<MediaAreaForSight
onFinishUpload={(mediaId) => {
linkPreviewMedia(mediaId);
}} }}
onFilesDrop={() => {}}
/> />
</Box> )}
</> </Box>
)} )}
{!previewMedia && ( </>
<MediaAreaForSight )}
onFinishUpload={(mediaId) => { {!previewMedia && (
linkPreviewMedia(mediaId); <MediaAreaForSight
}} onFinishUpload={(mediaId) => {
onFilesDrop={() => {}} linkPreviewMedia(mediaId);
/> }}
)} onFilesDrop={() => {}}
</Box> />
)} )}
</Box> </Box>
) : ( ) : (
@ -429,31 +507,52 @@ export const CreateRightTab = observer(
{/* Right Column: Live Preview */} {/* Right Column: Live Preview */}
<Box className="w-[25%] mr-10"> <Box className="w-[25%] mr-10">
{type === "article" && currentRightArticle && ( {type === "article" && activeArticleIndex !== null && (
<Paper <Paper
className="flex-1 flex flex-col rounded-2xl" className="flex-1 flex flex-col max-w-[500px]"
sx={{
borderRadius: "16px",
overflow: "hidden",
}}
elevation={2} elevation={2}
sx={{ height: "75vh", overflow: "hidden" }}
> >
<Box <Box
className="rounded-2xl overflow-hidden" className=" overflow-hidden"
sx={{ sx={{
width: "100%", width: "100%",
height: "100%",
background: "#877361", // Theme background background: "#877361",
borderColor: "grey.300",
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
}} }}
> >
{currentRightArticle.media && {sight[language].right[activeArticleIndex].media.length >
currentRightArticle.media.length > 0 ? ( 0 ? (
<MediaViewer media={currentRightArticle.media[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 <Box
sx={{ sx={{
width: "100%", width: "100%",
height: 200, height: 200,
flexShrink: 0, flexShrink: 0,
backgroundColor: "rgba(0,0,0,0.1)", backgroundColor: "rgba(0,0,0,0.1)",
display: "flex", display: "flex",
alignItems: "center", alignItems: "center",
@ -463,57 +562,85 @@ export const CreateRightTab = observer(
<ImagePlus size={48} color="white" /> <ImagePlus size={48} color="white" />
</Box> </Box>
)} )}
<Box <Box
sx={{ sx={{
width: "100%", p: 1,
minHeight: "70px", // Fixed height for heading container wordBreak: "break-word",
background: "#877361", // Consistent with theme fontSize: "24px",
display: "flex", fontWeight: 700,
flexShrink: 0, lineHeight: "120%",
flex: 1, backdropFilter: "blur(12px)",
alignItems: "center", boxShadow:
borderBottom: "1px solid rgba(255,255,255,0.1)", "inset 4px 4px 12px 0 rgba(255,255,255,0.12)",
px: 2, background:
py: 1, "#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
}} }}
> >
<Typography <Typography variant="h6" color="white">
variant="h6" {sight[language].right[activeArticleIndex].heading ||
color="white" "Выберите статью"}
noWrap
title={currentRightArticle.heading}
>
{currentRightArticle.heading || "Заголовок"}
</Typography> </Typography>
</Box> </Box>
<Box <Box
sx={{ sx={{
px: 2, padding: 1,
py: 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, flexGrow: 1,
overflowY: "auto",
backgroundColor: "#877361",
color: "white",
"&::-webkit-scrollbar": { width: "8px" },
"&::-webkit-scrollbar-thumb": {
backgroundColor: "rgba(255,255,255,0.3)",
borderRadius: "4px",
},
}} }}
> >
{currentRightArticle.body ? ( {sight[language].right[activeArticleIndex].body ? (
<ReactMarkdownComponent <ReactMarkdownComponent
value={currentRightArticle.body} value={sight[language].right[activeArticleIndex].body}
/> />
) : ( ) : (
<Typography <Typography
color="rgba(255,255,255,0.7)" color="rgba(255,255,255,0.7)"
sx={{ textAlign: "center", mt: 4 }} sx={{ textAlign: "center", mt: 4 }}
> >
Содержимое статьи... Предпросмотр статьи появится здесь
</Typography> </Typography>
)} )}
</Box> </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> </Box>
</Paper> </Paper>
)} )}

View File

@ -8,6 +8,7 @@ import {
TextField, TextField,
} from "@mui/material"; } from "@mui/material";
import { import {
authInstance,
BackButton, BackButton,
editSightStore, editSightStore,
languageStore, languageStore,
@ -17,6 +18,7 @@ import {
UploadMediaDialog, UploadMediaDialog,
} from "@shared"; } from "@shared";
import { import {
DeleteModal,
LanguageSwitcher, LanguageSwitcher,
MediaArea, MediaArea,
MediaAreaForSight, MediaAreaForSight,
@ -28,6 +30,12 @@ import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { MediaViewer } from "../../MediaViewer/index"; import { MediaViewer } from "../../MediaViewer/index";
import {
DragDropContext,
Droppable,
Draggable,
DropResult,
} from "@hello-pangea/dnd";
export const RightWidgetTab = observer( export const RightWidgetTab = observer(
({ value, index }: { value: number; index: number }) => { ({ value, index }: { value: number; index: number }) => {
@ -47,14 +55,21 @@ export const RightWidgetTab = observer(
createLinkWithRightArticle, createLinkWithRightArticle,
setFileToUpload, setFileToUpload,
createNewRightArticle, createNewRightArticle,
updateRightArticles,
} = editSightStore; } = editSightStore;
const [previewMedia, setPreviewMedia] = useState<any | null>(null); const [previewMedia, setPreviewMedia] = useState<any | null>(null);
useEffect(() => { useEffect(() => {
if (sight.common.preview_media) { const fetchPreviewMedia = async () => {
setPreviewMedia(sight.common.preview_media); if (sight.common.preview_media) {
} const response = await authInstance.get(
`/media/${sight.common.preview_media}`
);
setPreviewMedia(response.data);
}
};
fetchPreviewMedia();
}, [sight.common.preview_media]); }, [sight.common.preview_media]);
const handleUnlinkPreviewMedia = () => { const handleUnlinkPreviewMedia = () => {
@ -80,8 +95,15 @@ export const RightWidgetTab = observer(
); );
const [isSelectModalOpen, setIsSelectModalOpen] = useState(false); const [isSelectModalOpen, setIsSelectModalOpen] = useState(false);
const [isSelectMediaModalOpen, setIsSelectMediaModalOpen] = useState(false); const [isSelectMediaModalOpen, setIsSelectMediaModalOpen] = useState(false);
const [isDeleteArticleModalOpen, setIsDeleteArticleModalOpen] =
useState(false);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const handleDeleteArticle = () => {
deleteRightArticle(sight[language].right[activeArticleIndex || 0].id);
setActiveArticleIndex(null);
};
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
@ -133,6 +155,36 @@ export const RightWidgetTab = observer(
console.log(sight[language].right); console.log(sight[language].right);
}, [sight[language].right]); }, [sight[language].right]);
const handleDragEnd = (result: DropResult) => {
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 ( return (
<TabPanel value={value} index={index}> <TabPanel value={value} index={index}>
<LanguageSwitcher /> <LanguageSwitcher />
@ -153,26 +205,61 @@ export const RightWidgetTab = observer(
<Box className="flex flex-col w-[75%] gap-2"> <Box className="flex flex-col w-[75%] gap-2">
<Box className="w-full flex 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="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 className="flex flex-col gap-3 max-h-[60vh] overflow-y-auto">
<Box <Box
onClick={() => setType("media")} onClick={() => setType("media")}
className="w-full bg-green-200 p-4 rounded-2xl cursor-pointer text-sm hover:bg-gray-300 transition-all duration-300" className="w-full bg-green-200 p-4 rounded-2xl cursor-pointer text-sm hover:bg-gray-300 transition-all duration-300"
> >
<Typography>Предпросмотр медиа</Typography> <Typography>Предпросмотр медиа</Typography>
</Box> </Box>
{sight[language].right.length > 0 && <Box>
sight[language].right.map((article, index) => ( <DragDropContext onDragEnd={handleDragEnd}>
<Box <Droppable droppableId="articles">
key={index} {(provided) => (
className="w-full bg-gray-200 p-4 rounded-2xl text-sm cursor-pointer hover:bg-gray-300 transition-all duration-300" <Box
onClick={() => { ref={provided.innerRef}
handleSelectArticle(index); {...provided.droppableProps}
setType("article"); className="flex flex-col gap-2"
}} >
> {sight[language].right.length > 0
<Typography>{article.heading}</Typography> ? sight[language].right.map(
</Box> (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={() => {
handleSelectArticle(index);
setType("article");
}}
>
<Box {...provided.dragHandleProps}>
<Typography>
{article.heading}
</Typography>
</Box>
</Box>
)}
</Draggable>
)
)
: null}
{provided.placeholder}
</Box>
)}
</Droppable>
</DragDropContext>
</Box>
</Box> </Box>
<button <button
className="w-10 h-10 bg-blue-500 rounded-full absolute bottom-5 left-5 flex items-center justify-center" className="w-10 h-10 bg-blue-500 rounded-full absolute bottom-5 left-5 flex items-center justify-center"
@ -200,7 +287,7 @@ export const RightWidgetTab = observer(
</Box> </Box>
{type === "article" && ( {type === "article" && (
<Box className="w-[80%] border border-gray-300 rounded-2xl p-3"> <Box className="w-[80%] border border-gray-300 p-3">
{activeArticleIndex !== null && ( {activeArticleIndex !== null && (
<> <>
<Box className="flex justify-end gap-2 mb-3"> <Box className="flex justify-end gap-2 mb-3">
@ -222,10 +309,7 @@ export const RightWidgetTab = observer(
color="error" color="error"
startIcon={<Trash2 size={18} />} startIcon={<Trash2 size={18} />}
onClick={() => { onClick={() => {
deleteRightArticle( setIsDeleteArticleModalOpen(true);
sight[language].right[activeArticleIndex].id
);
setActiveArticleIndex(null);
}} }}
> >
Удалить Удалить
@ -300,42 +384,40 @@ export const RightWidgetTab = observer(
<Box className="w-[80%] border border-gray-300 rounded-2xl relative"> <Box className="w-[80%] border border-gray-300 rounded-2xl relative">
{sight.common.preview_media && ( {sight.common.preview_media && (
<> <>
<Box className="w-[80%] h-[70vh] border border-gray-300 rounded-2xl p-3 relative flex justify-center items-center"> {type === "media" && (
{type === "media" && ( <Box className="w-[80%] h-full rounded-2xl relative flex items-center justify-center">
<Box className="w-[80%] border border-gray-300 rounded-2xl relative flex items-center justify-center"> {previewMedia && (
{previewMedia && ( <>
<> <Box className="absolute top-4 right-4 z-10">
<Box className="absolute top-4 right-4 z-10"> <button
<button className="w-10 h-10 flex items-center justify-center z-10"
className="w-10 h-10 flex items-center justify-center z-10" onClick={handleUnlinkPreviewMedia}
onClick={handleUnlinkPreviewMedia} >
> <X size={20} color="red" />
<X size={20} color="red" /> </button>
</button> </Box>
</Box>
<Box sx={{}}> <Box className="w-1/2 h-1/2">
<MediaViewer <MediaViewer
media={{ media={{
id: previewMedia.id || "", id: previewMedia.id || "",
media_type: previewMedia.media_type, media_type: previewMedia.media_type,
filename: previewMedia.filename || "", filename: previewMedia.filename || "",
}} }}
/> />
</Box> </Box>
</> </>
)} )}
{!previewMedia && ( {!previewMedia && (
<MediaAreaForSight <MediaAreaForSight
onFinishUpload={(mediaId) => { onFinishUpload={(mediaId) => {
linkPreviewMedia(mediaId); linkPreviewMedia(mediaId);
}} }}
onFilesDrop={() => {}} onFilesDrop={() => {}}
/> />
)} )}
</Box> </Box>
)} )}
</Box>
</> </>
)} )}
@ -369,14 +451,18 @@ export const RightWidgetTab = observer(
<Box className="w-[25%] mr-10"> <Box className="w-[25%] mr-10">
{activeArticleIndex !== null && ( {activeArticleIndex !== null && (
<Paper <Paper
className="flex-1 flex flex-col rounded-2xl" className="flex-1 flex flex-col max-w-[500px]"
sx={{
borderRadius: "16px",
overflow: "hidden",
}}
elevation={2} elevation={2}
> >
<Box <Box
className="rounded-2xl overflow-hidden" className=" overflow-hidden"
sx={{ sx={{
width: "100%", width: "100%",
height: "75vh",
background: "#877361", background: "#877361",
borderColor: "grey.300", borderColor: "grey.300",
display: "flex", display: "flex",
@ -385,11 +471,23 @@ export const RightWidgetTab = observer(
> >
{sight[language].right[activeArticleIndex].media.length > {sight[language].right[activeArticleIndex].media.length >
0 ? ( 0 ? (
<MediaViewer <Box
media={ sx={{
sight[language].right[activeArticleIndex].media[0] width: "100%",
} maxHeight: "290px",
/> flexShrink: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<MediaViewer
media={
sight[language].right[activeArticleIndex].media[0]
}
/>
</Box>
) : ( ) : (
<Box <Box
sx={{ sx={{
@ -409,15 +507,16 @@ export const RightWidgetTab = observer(
<Box <Box
sx={{ sx={{
width: "100%", p: 1,
height: "70px", wordBreak: "break-word",
background: "#877361", fontSize: "24px",
display: "flex", fontWeight: 700,
flexShrink: 0, lineHeight: "120%",
backdropFilter: "blur(12px)",
alignItems: "center", boxShadow:
borderBottom: "1px solid rgba(255,255,255,0.1)", "inset 4px 4px 12px 0 rgba(255,255,255,0.12)",
px: 2, 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"> <Typography variant="h6" color="white">
@ -428,14 +527,14 @@ export const RightWidgetTab = observer(
<Box <Box
sx={{ sx={{
px: 2, 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, flexGrow: 1,
flex: 1,
minHeight: "300px",
overflowY: "auto",
backgroundColor: "#877361",
color: "white",
py: 1,
}} }}
> >
{sight[language].right[activeArticleIndex].body ? ( {sight[language].right[activeArticleIndex].body ? (
@ -453,6 +552,39 @@ export const RightWidgetTab = observer(
</Typography> </Typography>
)} )}
</Box> </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={() => {
handleSelectArticle(index);
setType("article");
}}
>
{article.heading}
</button>
))}
</Box>
</Box> </Box>
</Paper> </Paper>
)} )}
@ -496,10 +628,20 @@ export const RightWidgetTab = observer(
}} }}
/> />
<DeleteModal
open={isDeleteArticleModalOpen}
onCancel={() => setIsDeleteArticleModalOpen(false)}
onDelete={() => {
handleDeleteArticle();
setIsDeleteArticleModalOpen(false);
}}
/>
<SelectArticleModal <SelectArticleModal
open={isSelectModalOpen} open={isSelectModalOpen}
onClose={handleCloseSelectModal} onClose={handleCloseSelectModal}
onSelectArticle={handleArticleSelect} onSelectArticle={handleArticleSelect}
linkedArticleIds={sight[language].right.map((article) => article.id)}
/> />
<SelectMediaDialog <SelectMediaDialog
open={isSelectMediaModalOpen} open={isSelectMediaModalOpen}