feat: Sight Page update
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user