467 lines
16 KiB
TypeScript
467 lines
16 KiB
TypeScript
// @widgets/LeftWidgetTab.tsx
|
||
import { Box, Button, TextField, Paper, Typography } from "@mui/material";
|
||
import {
|
||
BackButton,
|
||
TabPanel,
|
||
languageStore,
|
||
SelectMediaDialog,
|
||
editSightStore,
|
||
SelectArticleModal,
|
||
UploadMediaDialog,
|
||
Language,
|
||
articlesStore,
|
||
} from "@shared";
|
||
import {
|
||
LanguageSwitcher,
|
||
ReactMarkdownComponent,
|
||
ReactMarkdownEditor,
|
||
MediaArea,
|
||
MediaViewer,
|
||
DeleteModal,
|
||
} from "@widgets";
|
||
import { Trash2, ImagePlus, Unlink, Plus, Save, Search } 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,
|
||
unlinkLeftArticle,
|
||
updateSight,
|
||
deleteLeftArticle,
|
||
createLeftArticle,
|
||
deleteMedia,
|
||
uploadMediaOpen,
|
||
setUploadMediaOpen,
|
||
|
||
setFileToUpload,
|
||
createLinkWithArticle,
|
||
} = editSightStore;
|
||
|
||
const { language } = languageStore;
|
||
const data = sight[language];
|
||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||
const token = localStorage.getItem("token");
|
||
const [isSelectMediaDialogOpen, setIsSelectMediaDialogOpen] =
|
||
useState(false);
|
||
const [isSelectArticleDialogOpen, setIsSelectArticleDialogOpen] =
|
||
useState(false);
|
||
|
||
const handleDeleteLeftArticle = useCallback(() => {
|
||
deleteLeftArticle(sight.common.left_article);
|
||
setIsDeleteModalOpen(false);
|
||
}, [deleteLeftArticle, sight.common.left_article]);
|
||
|
||
const handleMediaSelected = useCallback(
|
||
async (media: {
|
||
id: string;
|
||
filename: string;
|
||
media_name?: string;
|
||
media_type: number;
|
||
}) => {
|
||
await createLinkWithArticle(media);
|
||
setIsSelectMediaDialogOpen(false);
|
||
},
|
||
[createLinkWithArticle]
|
||
);
|
||
|
||
const handleCloseMediaDialog = useCallback(() => {
|
||
setIsSelectMediaDialogOpen(false);
|
||
}, []);
|
||
|
||
const handleCloseArticleDialog = useCallback(() => {
|
||
setIsSelectArticleDialogOpen(false);
|
||
}, []);
|
||
|
||
const handleSelectArticle = useCallback(
|
||
async (
|
||
articleId: number
|
||
// heading: string,
|
||
// body: string,
|
||
// media: { id: string; media_type: number; filename: string }[]
|
||
) => {
|
||
setIsSelectArticleDialogOpen(false);
|
||
|
||
const ruArticle = await articlesStore.getArticle(articleId, "ru");
|
||
const enArticle = await articlesStore.getArticle(articleId, "en");
|
||
const zhArticle = await articlesStore.getArticle(articleId, "zh");
|
||
|
||
updateSightInfo("ru", {
|
||
left: {
|
||
heading: ruArticle.data.heading,
|
||
body: ruArticle.data.body,
|
||
media: ruArticle.data.media || [],
|
||
},
|
||
});
|
||
updateSightInfo("en", {
|
||
left: {
|
||
heading: enArticle.data.heading,
|
||
body: enArticle.data.body,
|
||
media: enArticle.data.media || [],
|
||
},
|
||
});
|
||
updateSightInfo("zh", {
|
||
left: {
|
||
heading: zhArticle.data.heading,
|
||
body: zhArticle.data.body,
|
||
media: zhArticle.data.media || [],
|
||
},
|
||
});
|
||
|
||
updateSightInfo(
|
||
languageStore.language,
|
||
{
|
||
left_article: articleId,
|
||
},
|
||
true
|
||
);
|
||
},
|
||
[]
|
||
);
|
||
|
||
return (
|
||
<>
|
||
<TabPanel value={value} index={index}>
|
||
<LanguageSwitcher />
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 3,
|
||
paddingBottom: "70px",
|
||
position: "relative",
|
||
}}
|
||
>
|
||
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
|
||
<BackButton />
|
||
<h1 className="text-3xl break-words">{sight[language].name}</h1>
|
||
</div>
|
||
|
||
<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.common.left_article ? (
|
||
<>
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
size="small"
|
||
style={{ transition: "0" }}
|
||
startIcon={<Unlink size={18} />}
|
||
onClick={() => {
|
||
unlinkLeftArticle();
|
||
toast.success("Статья откреплена");
|
||
}}
|
||
>
|
||
Открепить
|
||
</Button>
|
||
<Button
|
||
variant="outlined"
|
||
color="error"
|
||
style={{ transition: "0" }}
|
||
startIcon={<Trash2 size={18} />}
|
||
size="small"
|
||
onClick={() => {
|
||
setIsDeleteModalOpen(true);
|
||
}}
|
||
>
|
||
Удалить
|
||
</Button>
|
||
</>
|
||
) : (
|
||
<>
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
size="small"
|
||
startIcon={<Search color="white" size={18} />}
|
||
onClick={() => setIsSelectArticleDialogOpen(true)}
|
||
>
|
||
Выбрать статью
|
||
</Button>
|
||
<Button
|
||
variant="contained"
|
||
color="primary"
|
||
size="small"
|
||
style={{ transition: "0" }}
|
||
startIcon={<Plus color="white" size={18} />}
|
||
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: any) =>
|
||
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",
|
||
maxWidth: "320px",
|
||
gap: 0.5,
|
||
}}
|
||
>
|
||
<Paper
|
||
elevation={3}
|
||
sx={{
|
||
width: "100%",
|
||
minWidth: 320,
|
||
background:
|
||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||
overflowY: "auto",
|
||
display: "flex",
|
||
|
||
flexDirection: "column",
|
||
borderRadius: "10px",
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
overflow: "hidden",
|
||
width: "100%",
|
||
minHeight: 100,
|
||
padding: "3px",
|
||
position: "relative",
|
||
display: "flex",
|
||
alignItems: "center",
|
||
justifyContent: "center",
|
||
|
||
"& img": {
|
||
borderTopLeftRadius: "10px",
|
||
borderTopRightRadius: "10px",
|
||
width: "100%",
|
||
height: "auto",
|
||
objectFit: "contain",
|
||
},
|
||
}}
|
||
>
|
||
{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,
|
||
}}
|
||
fullWidth
|
||
/>
|
||
{sight.common.watermark_lu && (
|
||
<img
|
||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||
sight.common.watermark_lu
|
||
}/download?token=${token}`}
|
||
alt="preview"
|
||
className="absolute top-4 left-4 z-10"
|
||
style={{
|
||
width: "30px",
|
||
height: "30px",
|
||
objectFit: "contain",
|
||
}}
|
||
/>
|
||
)}
|
||
|
||
{sight.common.watermark_rd && (
|
||
<img
|
||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||
sight.common.watermark_rd
|
||
}/download?token=${token}`}
|
||
alt="preview"
|
||
className="absolute bottom-4 right-4 z-10"
|
||
style={{
|
||
width: "30px",
|
||
height: "30px",
|
||
objectFit: "contain",
|
||
}}
|
||
/>
|
||
)}
|
||
</>
|
||
) : (
|
||
<ImagePlus size={48} color="white" />
|
||
)}
|
||
</Box>
|
||
|
||
<Box
|
||
sx={{
|
||
background:
|
||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||
color: "white",
|
||
margin: "5px 0px 5px 0px",
|
||
display: "flex",
|
||
flexDirection: "column",
|
||
gap: 1,
|
||
padding: 1,
|
||
}}
|
||
>
|
||
<Typography
|
||
variant="h5"
|
||
component="h2"
|
||
sx={{
|
||
wordBreak: "break-word",
|
||
fontSize: "24px",
|
||
fontWeight: 700,
|
||
lineHeight: "120%",
|
||
}}
|
||
>
|
||
{data?.left?.heading || "Название информации"}
|
||
</Typography>
|
||
<Typography
|
||
variant="h6"
|
||
component="h2"
|
||
sx={{
|
||
wordBreak: "break-word",
|
||
fontSize: "18px",
|
||
|
||
lineHeight: "120%",
|
||
}}
|
||
>
|
||
{sight[language as Language].address}
|
||
</Typography>
|
||
</Box>
|
||
|
||
{data?.left?.body && (
|
||
<Box
|
||
sx={{
|
||
padding: 1,
|
||
maxHeight: "300px",
|
||
overflowY: "auto",
|
||
width: "100%",
|
||
"&::-webkit-scrollbar": {
|
||
display: "none",
|
||
},
|
||
"&": {
|
||
scrollbarWidth: "none",
|
||
},
|
||
background:
|
||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||
flexGrow: 1,
|
||
}}
|
||
>
|
||
<ReactMarkdownComponent value={data?.left?.body} />
|
||
</Box>
|
||
)}
|
||
</Paper>
|
||
</Box>
|
||
</Box>
|
||
)}
|
||
|
||
<Box sx={{ position: "absolute", bottom: 0, right: 0, padding: 2 }}>
|
||
<Button
|
||
variant="contained"
|
||
color="success"
|
||
startIcon={<Save color="white" size={18} />}
|
||
onClick={async () => {
|
||
await updateSight();
|
||
toast.success("Достопримечательность сохранена");
|
||
}}
|
||
>
|
||
Сохранить
|
||
</Button>
|
||
</Box>
|
||
</Box>
|
||
</TabPanel>
|
||
<UploadMediaDialog
|
||
open={uploadMediaOpen}
|
||
onClose={() => setUploadMediaOpen(false)}
|
||
contextObjectName={sight[languageStore.language].name}
|
||
contextType="sight"
|
||
isArticle={true}
|
||
articleName={
|
||
sight[languageStore.language].left.heading || "Левая статья"
|
||
}
|
||
afterUpload={async (media) => {
|
||
setUploadMediaOpen(false);
|
||
setFileToUpload(null);
|
||
await createLinkWithArticle(media);
|
||
}}
|
||
/>
|
||
<SelectMediaDialog
|
||
open={isSelectMediaDialogOpen}
|
||
onClose={handleCloseMediaDialog}
|
||
onSelectMedia={handleMediaSelected}
|
||
/>
|
||
<SelectArticleModal
|
||
open={isSelectArticleDialogOpen}
|
||
onClose={handleCloseArticleDialog}
|
||
onSelectArticle={handleSelectArticle}
|
||
/>
|
||
<DeleteModal
|
||
open={isDeleteModalOpen}
|
||
onDelete={handleDeleteLeftArticle}
|
||
onCancel={() => setIsDeleteModalOpen(false)}
|
||
/>
|
||
</>
|
||
);
|
||
}
|
||
);
|