feat: Sight Page update

This commit is contained in:
2025-06-01 23:18:21 +03:00
parent 87386c6a73
commit a8777a974a
26 changed files with 3460 additions and 727 deletions

View File

@@ -1,326 +1,345 @@
// @widgets/LeftWidgetTab.tsx
import { Box, Button, TextField, Paper, Typography } from "@mui/material";
import {
articlesStore,
BackButton,
TabPanel,
languageStore,
SelectMediaDialog,
editSightStore,
SelectArticleModal,
UploadMediaDialog,
} from "@shared";
import {
LanguageSwitcher,
ReactMarkdownComponent,
ReactMarkdownEditor,
MediaArea,
MediaViewer,
} from "@widgets";
import { Unlink, Trash2, ImagePlus } from "lucide-react";
import { Trash2, ImagePlus } from "lucide-react";
import { useState, useCallback } from "react";
import { observer } from "mobx-react-lite";
import { toast } from "react-toastify";
export const LeftWidgetTab = observer(
({ value, index }: { value: number; index: number }) => {
const { sight, updateSightInfo } = editSightStore;
const { getArticleByArticleId } = articlesStore;
const {
sight,
updateSightInfo,
unlinkLeftArticle,
updateSight,
deleteLeftArticle,
createLeftArticle,
deleteMedia,
uploadMediaOpen,
setUploadMediaOpen,
const linkedArticle = getArticleByArticleId.get(); // Получаем связанную статью
const data = sight[languageStore.language]; // Получаем данные для текущего языка
setFileToUpload,
createLinkWithArticle,
} = editSightStore;
const { language } = languageStore;
const data = sight[language];
const [isSelectMediaDialogOpen, setIsSelectMediaDialogOpen] =
useState(false);
const [isSelectArticleDialogOpen, setIsSelectArticleDialogOpen] =
useState(false);
const handleMediaSelected = useCallback(() => {
// При выборе медиа, обновляем данные для ТЕКУЩЕГО ЯЗЫКА
// сохраняя текущие heading и body.
updateSightInfo(
languageStore.language,
{
left: {
heading: data.left.heading,
body: data.left.body,
},
},
false
);
setIsSelectMediaDialogOpen(false);
}, [
languageStore.language,
{
left: {
heading: data.left.heading,
body: data.left.body,
},
const handleMediaSelected = useCallback(
async (media: {
id: string;
filename: string;
media_name?: string;
media_type: number;
}) => {
await createLinkWithArticle(media);
setIsSelectMediaDialogOpen(false);
},
false,
]);
[createLinkWithArticle]
);
const handleCloseMediaDialog = useCallback(() => {
setIsSelectMediaDialogOpen(false);
}, []);
// ... (остальной JSX код остался почти без изменений)
return (
<TabPanel value={value} index={index}>
<LanguageSwitcher />
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 3,
paddingBottom: "70px",
position: "relative",
}}
>
<BackButton />
const handleCloseArticleDialog = useCallback(() => {
setIsSelectArticleDialogOpen(false);
}, []);
<Paper
elevation={2}
const handleSelectArticle = useCallback(
(
articleId: number,
heading: string,
body: string,
media: { id: string; media_type: number; filename: string }[]
) => {
setIsSelectArticleDialogOpen(false);
updateSightInfo(languageStore.language, {
left: {
heading,
body,
media,
},
});
updateSightInfo(
languageStore.language,
{
left_article: articleId,
},
true
);
},
[]
);
return (
<>
<TabPanel value={value} index={index}>
<LanguageSwitcher />
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
paddingX: 2.5,
paddingY: 1.5,
borderRadius: 2,
border: "1px solid",
borderColor: "divider",
flexDirection: "column",
gap: 3,
paddingBottom: "70px",
position: "relative",
}}
>
<Typography variant="h6">Левая статья</Typography>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
{linkedArticle && (
<Button
variant="outlined"
color="primary"
startIcon={<Unlink size={18} />}
size="small"
>
Открепить
</Button>
)}
<Button
variant="outlined"
color="error"
startIcon={<Trash2 size={18} />}
size="small"
>
Удалить
</Button>
</Box>
</Paper>
<BackButton />
<Box sx={{ display: "flex", gap: 3, flexGrow: 1 }}>
{/* Левая колонка: Редактирование */}
<Box
sx={{ flex: 2, display: "flex", flexDirection: "column", gap: 2 }}
>
<TextField
label="Название информации"
value={data?.left?.heading}
onChange={(e) =>
updateSightInfo(
languageStore.language,
{
left: {
heading: e.target.value,
body: data.left.body,
},
},
false
)
}
variant="outlined"
fullWidth
/>
<ReactMarkdownEditor
value={data?.left?.body}
onChange={(value) =>
updateSightInfo(
languageStore.language,
{
left: {
heading: data.left.heading,
body: value,
},
},
false
)
}
/>
{/* Блок МЕДИА для статьи */}
{/* <Paper elevation={1} sx={{ padding: 2, mt: 1 }}>
<Typography variant="h6" gutterBottom>
МЕДИА
</Typography>
{data.left.media ? (
<Box sx={{ mb: 1 }}>
<img
src={data.left.media.filename}
alt="Selected media"
style={{
maxWidth: "100%",
maxHeight: "150px",
objectFit: "contain",
}}
/>
</Box>
) : (
<Box
sx={{
width: "100%",
height: 100,
backgroundColor: "grey.100",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 1,
mb: 1,
border: "2px dashed",
borderColor: "grey.300",
}}
>
<Typography color="text.secondary">Нет медиа</Typography>
</Box>
)}
<Button
variant="contained"
startIcon={<ImagePlus size={18} />}
onClick={handleOpenMediaDialog}
>
Выбрать/Загрузить медиа
</Button>
{data.left.media && (
<Button
variant="outlined"
color="error"
size="small"
sx={{ ml: 1 }}
onClick={() =>
updateSightInfo(
languageStore.language,
{
left: {
heading: data.left.heading,
body: data.left.body,
media: null,
},
},
false
)
}
>
Удалить медиа
</Button>
)}
</Paper> */}
</Box>
{/* Правая колонка: Предпросмотр */}
<Box
<Paper
elevation={2}
sx={{
flex: 1,
display: "flex",
flexDirection: "column",
gap: 1.5,
alignItems: "center",
justifyContent: "space-between",
paddingX: 2.5,
paddingY: 1.5,
borderRadius: 2,
border: "1px solid",
borderColor: "divider",
}}
>
<Paper
elevation={3}
sx={{
width: "100%",
minWidth: 320,
maxWidth: 400,
height: "auto",
minHeight: 500,
backgroundColor: "#877361",
overflowY: "auto",
padding: 0,
display: "flex",
flexDirection: "column",
}}
>
{/* {data.left.media?.filename ? (
<Box
<Typography variant="h6">Левая статья</Typography>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
{sight.common.left_article ? (
<>
<Button
variant="contained"
color="primary"
size="small"
style={{ transition: "0" }}
onClick={() => {
unlinkLeftArticle();
toast.success("Статья откреплена");
}}
>
Открепить
</Button>
<Button
variant="outlined"
color="error"
style={{ transition: "0" }}
startIcon={<Trash2 size={18} />}
size="small"
onClick={() => {
deleteLeftArticle(sight.common.left_article);
toast.success("Статья откреплена");
}}
>
Удалить
</Button>
</>
) : (
<>
<Button
variant="contained"
color="primary"
size="small"
onClick={() => setIsSelectArticleDialogOpen(true)}
>
Выбрать статью
</Button>
<Button
variant="contained"
color="primary"
size="small"
style={{ transition: "0" }}
onClick={() => {
createLeftArticle();
toast.success("Статья создана");
}}
>
Создать статью
</Button>
</>
)}
</Box>
</Paper>
{sight.common.left_article > 0 && (
<Box sx={{ display: "flex", gap: 3, flexGrow: 1 }}>
<Box
sx={{
flex: 2,
display: "flex",
flexDirection: "column",
gap: 2,
}}
>
<TextField
label="Название информации"
value={data?.left?.heading}
onChange={(e) =>
updateSightInfo(languageStore.language, {
left: {
heading: e.target.value,
body: sight[languageStore.language].left.body,
media: data.left.media,
},
})
}
variant="outlined"
fullWidth
/>
<ReactMarkdownEditor
value={data?.left?.body}
onChange={(value) =>
updateSightInfo(languageStore.language, {
left: {
heading: sight[languageStore.language].left.heading,
body: value,
media: data.left.media,
},
})
}
/>
<MediaArea
articleId={sight.common.left_article}
mediaIds={data.left.media}
deleteMedia={deleteMedia}
setSelectMediaDialogOpen={setIsSelectMediaDialogOpen}
onFilesDrop={(files) => {
setFileToUpload(files[0]);
setUploadMediaOpen(true);
}}
/>
</Box>
<Box
sx={{
flex: 1,
display: "flex",
flexDirection: "column",
gap: 1.5,
}}
>
<Paper
elevation={3}
sx={{
width: "100%",
height: 200,
backgroundColor: "grey.300",
minWidth: 320,
maxWidth: 400,
height: "auto",
minHeight: 500,
backgroundColor: "#877361",
overflowY: "auto",
padding: 0,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
}}
>
<img
src={data.left.media?.filename ?? ""}
alt="Превью медиа"
style={{
objectFit: "cover",
<Box
sx={{
width: "100%",
height: "100%",
height: 200,
backgroundColor: "grey.300",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
/>
</Box>
) : (
)} */}
>
{data.left.media.length > 0 ? (
<MediaViewer
media={{
id: data.left.media[0].id,
media_type: data.left.media[0].media_type,
filename: data.left.media[0].filename,
}}
/>
) : (
<ImagePlus size={48} color="grey" />
)}
</Box>
<Box
sx={{
width: "100%",
height: 200,
backgroundColor: "grey.300",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<ImagePlus size={48} color="grey" />
</Box>
<Box
sx={{
backgroundColor: "#877361",
color: "white",
padding: 1.5,
}}
>
<Typography
variant="h5"
component="h2"
sx={{ wordBreak: "break-word" }}
>
{data?.left?.heading || "Название информации"}
</Typography>
</Box>
{/* Заголовок в превью */}
<Box
sx={{
backgroundColor: "#877361",
color: "white",
padding: 1.5,
}}
>
<Typography
variant="h5"
component="h2"
sx={{ wordBreak: "break-word" }}
>
{data?.left?.heading || "Название информации"}
</Typography>
{data?.left?.body && (
<Box
sx={{
padding: 2,
flexGrow: 1,
}}
>
<ReactMarkdownComponent value={data?.left?.body} />
</Box>
)}
</Paper>
</Box>
</Box>
)}
{/* Текст статьи в превью */}
<Box
sx={{
padding: 2,
flexGrow: 1,
}}
>
<ReactMarkdownComponent value={data?.left?.body} />
</Box>
</Paper>
<Box sx={{ position: "absolute", bottom: 0, right: 0, padding: 2 }}>
<Button
variant="contained"
color="success"
onClick={async () => {
await updateSight();
toast.success("Достопримечательность сохранена");
}}
>
Сохранить
</Button>
</Box>
</Box>
<Box sx={{ position: "absolute", bottom: 0, right: 0, padding: 2 }}>
<Button variant="contained" color="success">
Сохранить
</Button>
</Box>
</Box>
</TabPanel>
<UploadMediaDialog
open={uploadMediaOpen}
onClose={() => setUploadMediaOpen(false)}
afterUpload={async (media) => {
setUploadMediaOpen(false);
setFileToUpload(null);
await createLinkWithArticle(media);
}}
/>
<SelectMediaDialog
open={isSelectMediaDialogOpen}
onClose={handleCloseMediaDialog}
onSelectMedia={handleMediaSelected}
/>
</TabPanel>
<SelectArticleModal
open={isSelectArticleDialogOpen}
onClose={handleCloseArticleDialog}
onSelectArticle={handleSelectArticle}
/>
</>
);
}
);