Files
WhiteNightsAdminPanel/src/widgets/SightTabs/CreateLeftTab/index.tsx

510 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Box, Button, TextField, Paper, Typography } from "@mui/material";
import {
BackButton,
TabPanel,
languageStore,
SelectMediaDialog,
editSightStore,
createSightStore,
SelectArticleModal,
UploadMediaDialog,
Language,
} from "@shared";
import {
LanguageSwitcher,
MediaArea,
ReactMarkdownComponent,
ReactMarkdownEditor,
MediaViewer,
DeleteModal,
} from "@widgets";
import { Trash2, ImagePlus, Unlink, Plus, Save, Search } from "lucide-react";
import { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
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,
createLinkWithLeftArticle,
} = createSightStore;
const {
deleteMedia,
setFileToUpload,
uploadMediaOpen,
setUploadMediaOpen,
} = editSightStore;
const navigate = useNavigate();
const { language } = languageStore;
const token = localStorage.getItem("token");
const [isSelectArticleDialogOpen, setIsSelectArticleDialogOpen] =
useState(false);
const [isSelectMediaDialogOpen, setIsSelectMediaDialogOpen] =
useState(false);
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
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 createLinkWithLeftArticle(media);
setIsSelectMediaDialogOpen(false);
},
[createLinkWithLeftArticle]
);
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",
}}
>
<div className="flex gap-10 items-center mb-5 max-w-[80%]">
<BackButton />
<h1 className="text-3xl break-words">{sight[language].name.replace(/\n/g, " ")}</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.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"
startIcon={<Plus color="white" size={18} />}
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: any) =>
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",
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",
position: "relative",
width: "100%",
minHeight: 100,
padding: "3px",
display: "flex",
alignItems: "center",
justifyContent: "center",
"& img": {
borderTopLeftRadius: "10px",
borderTopRightRadius: "10px",
width: "100%",
height: "auto",
objectFit: "contain",
},
}}
>
{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,
}}
fullWidth
/>
{sight.watermark_lu && (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
sight.watermark_lu
}/download?token=${token}`}
alt="preview"
className="absolute top-4 left-4 z-10"
style={{
width: "30px",
height: "30px",
objectFit: "contain",
}}
/>
)}
{sight.watermark_rd && (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
sight.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%",
}}
>
{sight[language].left.heading || "Название информации"}
</Typography>
<Typography
variant="h6"
component="h2"
sx={{
wordBreak: "break-word",
fontSize: "18px",
lineHeight: "120%",
}}
>
{sight[language as Language].address}
</Typography>
</Box>
{sight[language].left.body && (
<Box
sx={{
padding: 1,
maxHeight: "300px",
overflowY: "auto",
"&::-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={sight[language].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 () => {
try {
const newSightId = await createSight(language);
toast.success("Страница создана");
navigate(`/sight/${newSightId}/edit`);
} 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)}
contextObjectName={sight[language].name}
contextType="sight"
isArticle={true}
articleName={sight[language].left.heading || "Левая статья"}
afterUpload={async (media) => {
setUploadMediaOpen(false);
setFileToUpload(null);
await createLinkWithLeftArticle(media);
}}
/>
<SelectArticleModal
open={isSelectArticleDialogOpen}
onClose={handleCloseArticleDialog}
onSelectArticle={handleArticleSelect}
/>
<DeleteModal
open={isDeleteModalOpen}
onDelete={() => {
deleteLeftArticle(sight.left_article);
setIsDeleteModalOpen(false);
toast.success("Статья откреплена");
}}
onCancel={() => setIsDeleteModalOpen(false)}
/>
</TabPanel>
);
}
);