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

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