feat: Refactor old code with delete
modal and icons
for buttons
This commit is contained in:
33
src/widgets/DeleteModal/index.tsx
Normal file
33
src/widgets/DeleteModal/index.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
export const DeleteModal = ({
|
||||
onDelete,
|
||||
onCancel,
|
||||
open,
|
||||
}: {
|
||||
onDelete: () => void;
|
||||
onCancel: () => void;
|
||||
open: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`fixed top-0 left-0 w-screen h-screen flex justify-center items-center z-10000 bg-black/30 transition-all duration-300 ${
|
||||
open ? "block" : "hidden"
|
||||
}`}
|
||||
>
|
||||
<div className="bg-white p-4 w-100 rounded-lg flex flex-col gap-4 items-center">
|
||||
<p className="text-black w-100 text-center">
|
||||
Вы уверены, что хотите удалить этот элемент?
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Button variant="contained" color="error" onClick={onDelete}>
|
||||
Да
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={onCancel}>
|
||||
Нет
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import React, { useRef, useState, DragEvent, useEffect } from "react";
|
||||
import { Paper, Box, Typography, Button, Tooltip } from "@mui/material";
|
||||
import { X, Info } from "lucide-react"; // Assuming lucide-react for icons
|
||||
import { X, Info, MousePointer } from "lucide-react"; // Assuming lucide-react for icons
|
||||
import { editSightStore } from "@shared";
|
||||
|
||||
interface ImageUploadCardProps {
|
||||
@ -159,6 +159,7 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<MousePointer color="white" size={18} />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation(); // Prevent `handleZoneClick` from firing
|
||||
onSelectFileClick(); // This button might trigger a different modal
|
||||
|
21
src/widgets/LeaveAgree/index.tsx
Normal file
21
src/widgets/LeaveAgree/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Button } from "@mui/material";
|
||||
|
||||
export const LeaveAgree = ({ blocker }: { blocker: any }) => {
|
||||
return (
|
||||
<div className="fixed top-0 left-0 w-screen h-screen flex justify-center items-center z-10000 bg-black/30">
|
||||
<div className="bg-white p-4 w-100 rounded-lg flex flex-col gap-4 items-center">
|
||||
<p className="text-black w-100 text-center">
|
||||
При выходе со страницы, несохраненные данные будут потеряны.
|
||||
</p>
|
||||
<div className="flex gap-4 justify-center">
|
||||
<Button variant="contained" onClick={() => blocker.proceed()}>
|
||||
Да
|
||||
</Button>
|
||||
<Button variant="outlined" onClick={() => blocker.reset()}>
|
||||
Нет
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -69,7 +69,7 @@ export const MediaAreaForSight = observer(
|
||||
<Box className="w-full flex flex-col items-center justify-center border rounded-md p-4">
|
||||
<div className="w-full flex flex-col items-center justify-center">
|
||||
<div
|
||||
className={`w-full h-40 flex flex-col justify-center items-center text-gray-400 border-dashed border-2 rounded-md border-gray-400 p-4 cursor-pointer hover:bg-gray-50 ${
|
||||
className={`w-full h-40 flex text-center flex-col justify-center items-center text-gray-400 border-dashed border-2 rounded-md border-gray-400 p-4 cursor-pointer hover:bg-gray-50 ${
|
||||
isDragging ? "bg-blue-100 border-blue-400" : ""
|
||||
}`}
|
||||
onDrop={handleDrop}
|
||||
|
@ -17,13 +17,11 @@ export function MediaViewer({
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "80%",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
maxWidth: "600px",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
justifyContent: "center",
|
||||
margin: "0 auto",
|
||||
}}
|
||||
className={className}
|
||||
>
|
||||
@ -34,10 +32,9 @@ export function MediaViewer({
|
||||
}/download?token=${token}`}
|
||||
alt={media?.filename}
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
height: "auto",
|
||||
objectFit: "contain",
|
||||
borderRadius: 8,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
@ -48,9 +45,7 @@ export function MediaViewer({
|
||||
media?.id
|
||||
}/download?token=${token}`}
|
||||
style={{
|
||||
margin: "auto 0",
|
||||
height: "fit-content",
|
||||
width: "fit-content",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
borderRadius: 30,
|
||||
}}
|
||||
@ -66,7 +61,7 @@ export function MediaViewer({
|
||||
}/download?token=${token}`}
|
||||
alt={media?.filename}
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
borderRadius: 8,
|
||||
@ -80,7 +75,7 @@ export function MediaViewer({
|
||||
}/download?token=${token}`}
|
||||
alt={media?.filename}
|
||||
style={{
|
||||
maxWidth: "100%",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "contain",
|
||||
borderRadius: 8,
|
||||
|
@ -18,8 +18,10 @@ import {
|
||||
SightCommonInfo,
|
||||
createSightStore,
|
||||
UploadMediaDialog,
|
||||
MEDIA_TYPE_VALUES,
|
||||
} from "@shared";
|
||||
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
|
||||
import { Save } from "lucide-react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useEffect, useState } from "react";
|
||||
@ -331,6 +333,7 @@ export const CreateInformationTab = observer(
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={<Save color="white" size={18} />}
|
||||
onClick={async () => {
|
||||
await createSight(language);
|
||||
toast.success("Достопримечательность создана");
|
||||
@ -369,6 +372,13 @@ export const CreateInformationTab = observer(
|
||||
onSelectMedia={(media) => {
|
||||
handleMediaSelect(media, activeMenuType ?? "thumbnail");
|
||||
}}
|
||||
mediaType={
|
||||
activeMenuType
|
||||
? MEDIA_TYPE_VALUES[
|
||||
activeMenuType as keyof typeof MEDIA_TYPE_VALUES
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
<PreviewMediaDialog
|
||||
|
@ -16,8 +16,16 @@ import {
|
||||
ReactMarkdownComponent,
|
||||
ReactMarkdownEditor,
|
||||
MediaViewer,
|
||||
DeleteModal,
|
||||
} from "@widgets";
|
||||
import { Trash2, ImagePlus } from "lucide-react";
|
||||
import {
|
||||
Trash2,
|
||||
ImagePlus,
|
||||
Unlink,
|
||||
MousePointer,
|
||||
Plus,
|
||||
Save,
|
||||
} from "lucide-react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { toast } from "react-toastify";
|
||||
@ -47,7 +55,7 @@ export const CreateLeftTab = observer(
|
||||
useState(false);
|
||||
const [isSelectMediaDialogOpen, setIsSelectMediaDialogOpen] =
|
||||
useState(false);
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
// const handleMediaSelected = useCallback(() => {
|
||||
// // При выборе медиа, обновляем данные для ТЕКУЩЕГО ЯЗЫКА
|
||||
// // сохраняя текущие heading и body.
|
||||
@ -123,6 +131,7 @@ export const CreateLeftTab = observer(
|
||||
color="primary"
|
||||
size="small"
|
||||
style={{ transition: "0" }}
|
||||
startIcon={<Unlink size={18} />}
|
||||
onClick={() => {
|
||||
unlinkLeftArticle();
|
||||
toast.success("Статья откреплена");
|
||||
@ -136,10 +145,7 @@ export const CreateLeftTab = observer(
|
||||
style={{ transition: "0" }}
|
||||
startIcon={<Trash2 size={18} />}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
deleteLeftArticle(sight.left_article);
|
||||
toast.success("Статья откреплена");
|
||||
}}
|
||||
onClick={() => setIsDeleteModalOpen(true)}
|
||||
>
|
||||
Удалить
|
||||
</Button>
|
||||
@ -150,6 +156,7 @@ export const CreateLeftTab = observer(
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
startIcon={<MousePointer color="white" size={18} />}
|
||||
onClick={() => setIsSelectArticleDialogOpen(true)}
|
||||
>
|
||||
Выбрать статью
|
||||
@ -158,6 +165,7 @@ export const CreateLeftTab = observer(
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
startIcon={<Plus color="white" size={18} />}
|
||||
style={{ transition: "0" }}
|
||||
onClick={createLeftArticle}
|
||||
>
|
||||
@ -301,6 +309,7 @@ export const CreateLeftTab = observer(
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1.5,
|
||||
maxWidth: "320px",
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
@ -405,10 +414,11 @@ export const CreateLeftTab = observer(
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={<Save color="white" size={18} />}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await createSight(language);
|
||||
toast.success("Странца создана");
|
||||
toast.success("Страница создана");
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
@ -445,6 +455,14 @@ export const CreateLeftTab = observer(
|
||||
onClose={handleCloseArticleDialog}
|
||||
onSelectArticle={handleArticleSelect}
|
||||
/>
|
||||
<DeleteModal
|
||||
open={isDeleteModalOpen}
|
||||
onDelete={() => {
|
||||
deleteLeftArticle(sight.left_article);
|
||||
toast.success("Статья откреплена");
|
||||
}}
|
||||
onCancel={() => setIsDeleteModalOpen(false)}
|
||||
/>
|
||||
</TabPanel>
|
||||
);
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ import {
|
||||
SelectArticleModal,
|
||||
TabPanel,
|
||||
SelectMediaDialog, // Import
|
||||
UploadMediaDialog, // Import
|
||||
UploadMediaDialog,
|
||||
Media, // Import
|
||||
} from "@shared";
|
||||
import {
|
||||
LanguageSwitcher,
|
||||
@ -22,12 +23,14 @@ import {
|
||||
MediaAreaForSight, // Import
|
||||
ReactMarkdownComponent,
|
||||
ReactMarkdownEditor,
|
||||
DeleteModal,
|
||||
} from "@widgets";
|
||||
import { ImagePlus, Plus, X } from "lucide-react"; // Import X
|
||||
import { ImagePlus, Plus, Save, Trash2, Unlink, X } from "lucide-react"; // Import X
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useState, useEffect } from "react"; // Added useEffect
|
||||
import { MediaViewer } from "../../MediaViewer/index";
|
||||
import { toast } from "react-toastify";
|
||||
import { authInstance } from "@shared";
|
||||
|
||||
type MediaItemShared = {
|
||||
// Define if not already available from @shared
|
||||
@ -65,14 +68,27 @@ export const CreateRightTab = observer(
|
||||
null
|
||||
);
|
||||
const [type, setType] = useState<"article" | "media">("media");
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isSelectMediaDialogOpen, setIsSelectMediaDialogOpen] =
|
||||
useState(false);
|
||||
const [mediaTarget, setMediaTarget] = useState<
|
||||
"sightPreview" | "rightArticle" | null
|
||||
>(null);
|
||||
|
||||
const [previewMedia, setPreviewMedia] = useState<Media | null>(null);
|
||||
// Reset activeArticleIndex if language changes and index is out of bounds
|
||||
useEffect(() => {
|
||||
if (sight.preview_media) {
|
||||
const fetchMedia = async () => {
|
||||
const response = await authInstance.get(
|
||||
`/media/${sight.preview_media}`
|
||||
);
|
||||
setPreviewMedia(response.data);
|
||||
};
|
||||
fetchMedia();
|
||||
}
|
||||
}, [sight.preview_media]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
activeArticleIndex !== null &&
|
||||
@ -168,6 +184,7 @@ export const CreateRightTab = observer(
|
||||
|
||||
const handleMediaSelectedFromDialog = async (media: MediaItemShared) => {
|
||||
setIsSelectMediaDialogOpen(false);
|
||||
|
||||
if (mediaTarget === "sightPreview") {
|
||||
await linkPreviewMedia(media.id);
|
||||
} else if (mediaTarget === "rightArticle" && currentRightArticle) {
|
||||
@ -176,6 +193,11 @@ export const CreateRightTab = observer(
|
||||
setMediaTarget(null);
|
||||
};
|
||||
|
||||
const handleUnlinkPreviewMedia = async () => {
|
||||
await unlinkPreviewMedia();
|
||||
setPreviewMedia(null);
|
||||
};
|
||||
|
||||
const handleMediaUploaded = async (media: MediaItemShared) => {
|
||||
// After UploadMediaDialog finishes
|
||||
setUploadMediaOpen(false);
|
||||
@ -273,12 +295,13 @@ export const CreateRightTab = observer(
|
||||
|
||||
{/* Main content area: Article Editor or Sight Media Preview */}
|
||||
{type === "article" && currentRightArticle ? (
|
||||
<Box className="w-[80%] border border-gray-300 rounded-2xl p-3 flex flex-col gap-2 overflow-hidden">
|
||||
<Box className="w-[80%] border border-gray-300 p-3 flex flex-col gap-2 overflow-hidden">
|
||||
<Box className="flex justify-end gap-2 mb-1 flex-shrink-0">
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="warning"
|
||||
color="primary"
|
||||
size="small"
|
||||
startIcon={<Unlink color="white" size={18} />}
|
||||
onClick={() => {
|
||||
if (currentRightArticle) {
|
||||
unlinkRightAritcle(currentRightArticle.id); // Corrected function name
|
||||
@ -293,22 +316,9 @@ export const CreateRightTab = observer(
|
||||
variant="contained"
|
||||
color="error"
|
||||
size="small"
|
||||
startIcon={<Trash2 size={18} />}
|
||||
onClick={async () => {
|
||||
if (
|
||||
currentRightArticle &&
|
||||
window.confirm(
|
||||
`Удалить статью "${currentRightArticle.heading}" окончательно?`
|
||||
)
|
||||
) {
|
||||
try {
|
||||
await deleteRightArticle(currentRightArticle.id);
|
||||
setActiveArticleIndex(null);
|
||||
setType("media");
|
||||
toast.success("Статья удалена");
|
||||
} catch {
|
||||
toast.error("Не удалось удалить статью");
|
||||
}
|
||||
}
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
Удалить
|
||||
@ -373,28 +383,30 @@ export const CreateRightTab = observer(
|
||||
) : type === "media" ? (
|
||||
<Box className="w-[80%] h-[70vh] border border-gray-300 rounded-2xl p-3 relative flex justify-center items-center">
|
||||
{type === "media" && (
|
||||
<Box className="w-[80%] border border-gray-300 rounded-2xl relative">
|
||||
{sight.preview_media && (
|
||||
<Box className="w-[80%] border border-gray-300 rounded-2xl relative flex items-center justify-center">
|
||||
{previewMedia && (
|
||||
<>
|
||||
<Box className="absolute top-4 right-4">
|
||||
<Box className="absolute top-4 right-4 z-10">
|
||||
<button
|
||||
className="w-10 h-10 flex items-center justify-center"
|
||||
onClick={unlinkPreviewMedia}
|
||||
className="w-10 h-10 flex items-center justify-center z-10"
|
||||
onClick={handleUnlinkPreviewMedia}
|
||||
>
|
||||
<X size={20} color="red" />
|
||||
</button>
|
||||
</Box>
|
||||
|
||||
<MediaViewer
|
||||
media={{
|
||||
id: sight.preview_media || "",
|
||||
media_type: 1,
|
||||
filename: sight.preview_media || "",
|
||||
}}
|
||||
/>
|
||||
<Box sx={{}}>
|
||||
<MediaViewer
|
||||
media={{
|
||||
id: previewMedia.id || "",
|
||||
media_type: previewMedia.media_type,
|
||||
filename: previewMedia.filename || "",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{!sight.preview_media && (
|
||||
{!previewMedia && (
|
||||
<MediaAreaForSight
|
||||
onFinishUpload={(mediaId) => {
|
||||
linkPreviewMedia(mediaId);
|
||||
@ -505,33 +517,6 @@ export const CreateRightTab = observer(
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
{/* Optional: Preview for sight.preview_media when type === "media" */}
|
||||
{type === "media" && sight.preview_media && (
|
||||
<Paper
|
||||
className="flex-1 flex flex-col rounded-2xl"
|
||||
elevation={2}
|
||||
sx={{ height: "75vh", overflow: "hidden" }}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
background: "#877361",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<MediaViewer
|
||||
media={{
|
||||
id: sight.preview_media,
|
||||
filename: sight.preview_media,
|
||||
media_type: 1,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -539,17 +524,15 @@ export const CreateRightTab = observer(
|
||||
<Box
|
||||
sx={{
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
bottom: "-20px",
|
||||
left: 0, // ensure it spans from left
|
||||
right: 0,
|
||||
padding: 2,
|
||||
backgroundColor: "background.paper",
|
||||
borderTop: "1px solid", // Add a subtle top border
|
||||
borderColor: "divider", // Use theme's divider color
|
||||
|
||||
width: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
boxShadow: "0 -2px 5px rgba(0,0,0,0.1)", // Optional shadow
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
@ -557,8 +540,9 @@ export const CreateRightTab = observer(
|
||||
color="success"
|
||||
onClick={handleSave}
|
||||
size="large"
|
||||
startIcon={<Save color="white" size={18} />}
|
||||
>
|
||||
Сохранить достопримечательность
|
||||
Сохранить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
@ -588,6 +572,20 @@ export const CreateRightTab = observer(
|
||||
}}
|
||||
onSelectMedia={handleMediaSelectedFromDialog}
|
||||
/>
|
||||
<DeleteModal
|
||||
open={isDeleteModalOpen}
|
||||
onDelete={async () => {
|
||||
try {
|
||||
await deleteRightArticle(currentRightArticle?.id || 0);
|
||||
setActiveArticleIndex(null);
|
||||
setType("media");
|
||||
toast.success("Статья удалена");
|
||||
} catch {
|
||||
toast.error("Не удалось удалить статью");
|
||||
}
|
||||
}}
|
||||
onCancel={() => setIsDeleteModalOpen(false)}
|
||||
/>
|
||||
</TabPanel>
|
||||
);
|
||||
}
|
||||
|
@ -18,8 +18,10 @@ import {
|
||||
SightLanguageInfo,
|
||||
SightCommonInfo,
|
||||
UploadMediaDialog,
|
||||
MEDIA_TYPE_VALUES,
|
||||
} from "@shared";
|
||||
import { ImageUploadCard, LanguageSwitcher } from "@widgets";
|
||||
import { Save } from "lucide-react";
|
||||
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useEffect, useState } from "react";
|
||||
@ -335,6 +337,7 @@ export const InformationTab = observer(
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={<Save color="white" size={18} />}
|
||||
onClick={async () => {
|
||||
await updateSight();
|
||||
toast.success("Достопримечательность сохранена");
|
||||
@ -371,6 +374,13 @@ export const InformationTab = observer(
|
||||
setActiveMenuType(null);
|
||||
}}
|
||||
onSelectMedia={handleMediaSelect}
|
||||
mediaType={
|
||||
activeMenuType
|
||||
? MEDIA_TYPE_VALUES[
|
||||
activeMenuType as keyof typeof MEDIA_TYPE_VALUES
|
||||
]
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
|
||||
<UploadMediaDialog
|
||||
|
@ -8,6 +8,7 @@ import {
|
||||
editSightStore,
|
||||
SelectArticleModal,
|
||||
UploadMediaDialog,
|
||||
Language,
|
||||
} from "@shared";
|
||||
import {
|
||||
LanguageSwitcher,
|
||||
@ -15,8 +16,16 @@ import {
|
||||
ReactMarkdownEditor,
|
||||
MediaArea,
|
||||
MediaViewer,
|
||||
DeleteModal,
|
||||
} from "@widgets";
|
||||
import { Trash2, ImagePlus } from "lucide-react";
|
||||
import {
|
||||
Trash2,
|
||||
ImagePlus,
|
||||
Unlink,
|
||||
Plus,
|
||||
MousePointer,
|
||||
Save,
|
||||
} from "lucide-react";
|
||||
import { useState, useCallback } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { toast } from "react-toastify";
|
||||
@ -40,12 +49,18 @@ export const LeftWidgetTab = observer(
|
||||
|
||||
const { language } = languageStore;
|
||||
const data = sight[language];
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
|
||||
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;
|
||||
@ -130,6 +145,7 @@ export const LeftWidgetTab = observer(
|
||||
color="primary"
|
||||
size="small"
|
||||
style={{ transition: "0" }}
|
||||
startIcon={<Unlink size={18} />}
|
||||
onClick={() => {
|
||||
unlinkLeftArticle();
|
||||
toast.success("Статья откреплена");
|
||||
@ -144,8 +160,7 @@ export const LeftWidgetTab = observer(
|
||||
startIcon={<Trash2 size={18} />}
|
||||
size="small"
|
||||
onClick={() => {
|
||||
deleteLeftArticle(sight.common.left_article);
|
||||
toast.success("Статья откреплена");
|
||||
setIsDeleteModalOpen(true);
|
||||
}}
|
||||
>
|
||||
Удалить
|
||||
@ -157,6 +172,7 @@ export const LeftWidgetTab = observer(
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="small"
|
||||
startIcon={<MousePointer color="white" size={18} />}
|
||||
onClick={() => setIsSelectArticleDialogOpen(true)}
|
||||
>
|
||||
Выбрать статью
|
||||
@ -166,6 +182,7 @@ export const LeftWidgetTab = observer(
|
||||
color="primary"
|
||||
size="small"
|
||||
style={{ transition: "0" }}
|
||||
startIcon={<Plus color="white" size={18} />}
|
||||
onClick={() => {
|
||||
createLeftArticle();
|
||||
toast.success("Статья создана");
|
||||
@ -234,7 +251,8 @@ export const LeftWidgetTab = observer(
|
||||
flex: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1.5,
|
||||
maxWidth: "320px",
|
||||
gap: 0.5,
|
||||
}}
|
||||
>
|
||||
<Paper
|
||||
@ -242,10 +260,9 @@ export const LeftWidgetTab = observer(
|
||||
sx={{
|
||||
width: "100%",
|
||||
minWidth: 320,
|
||||
maxWidth: 400,
|
||||
height: "auto",
|
||||
minHeight: 500,
|
||||
backgroundColor: "#877361",
|
||||
|
||||
background:
|
||||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
overflowY: "auto",
|
||||
padding: 0,
|
||||
display: "flex",
|
||||
@ -255,11 +272,10 @@ export const LeftWidgetTab = observer(
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: 200,
|
||||
backgroundColor: "grey.300",
|
||||
height: 175,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
padding: "3px",
|
||||
}}
|
||||
>
|
||||
{data.left.media.length > 0 ? (
|
||||
@ -277,24 +293,50 @@ export const LeftWidgetTab = observer(
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: "#877361",
|
||||
background:
|
||||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
color: "white",
|
||||
padding: 1.5,
|
||||
margin: "5px 0px 5px 0px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: 1,
|
||||
padding: 1,
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant="h5"
|
||||
component="h2"
|
||||
sx={{ wordBreak: "break-word" }}
|
||||
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: 2,
|
||||
padding: 1,
|
||||
maxHeight: "300px",
|
||||
overflowY: "scroll",
|
||||
background:
|
||||
"#806c59 linear-gradient(90deg, rgba(255, 255, 255, 0.2) 12.5%, rgba(255, 255, 255, 0.2) 100%)",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
@ -310,6 +352,7 @@ export const LeftWidgetTab = observer(
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
startIcon={<Save color="white" size={18} />}
|
||||
onClick={async () => {
|
||||
await updateSight();
|
||||
toast.success("Достопримечательность сохранена");
|
||||
@ -339,6 +382,11 @@ export const LeftWidgetTab = observer(
|
||||
onClose={handleCloseArticleDialog}
|
||||
onSelectArticle={handleSelectArticle}
|
||||
/>
|
||||
<DeleteModal
|
||||
open={isDeleteModalOpen}
|
||||
onDelete={handleDeleteLeftArticle}
|
||||
onCancel={() => setIsDeleteModalOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import {
|
||||
ReactMarkdownComponent,
|
||||
ReactMarkdownEditor,
|
||||
} from "@widgets";
|
||||
import { ImagePlus, Plus, X } from "lucide-react";
|
||||
import { ImagePlus, Plus, Save, Trash2, Unlink, X } from "lucide-react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useEffect, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
@ -49,6 +49,19 @@ export const RightWidgetTab = observer(
|
||||
createNewRightArticle,
|
||||
} = editSightStore;
|
||||
|
||||
const [previewMedia, setPreviewMedia] = useState<any | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (sight.common.preview_media) {
|
||||
setPreviewMedia(sight.common.preview_media);
|
||||
}
|
||||
}, [sight.common.preview_media]);
|
||||
|
||||
const handleUnlinkPreviewMedia = () => {
|
||||
unlinkPreviewMedia();
|
||||
setPreviewMedia(null);
|
||||
};
|
||||
|
||||
const [uploadMediaOpen, setUploadMediaOpen] = useState(false);
|
||||
const { language } = languageStore;
|
||||
const [type, setType] = useState<"article" | "media">("media");
|
||||
@ -194,6 +207,7 @@ export const RightWidgetTab = observer(
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<Unlink color="white" size={18} />}
|
||||
onClick={() => {
|
||||
unlinkRightArticle(
|
||||
sight[language].right[activeArticleIndex].id
|
||||
@ -205,7 +219,8 @@ export const RightWidgetTab = observer(
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="success"
|
||||
color="error"
|
||||
startIcon={<Trash2 size={18} />}
|
||||
onClick={() => {
|
||||
deleteRightArticle(
|
||||
sight[language].right[activeArticleIndex].id
|
||||
@ -285,31 +300,65 @@ export const RightWidgetTab = observer(
|
||||
<Box className="w-[80%] border border-gray-300 rounded-2xl relative">
|
||||
{sight.common.preview_media && (
|
||||
<>
|
||||
<Box className="absolute top-4 right-4">
|
||||
<button
|
||||
className="w-10 h-10 flex items-center justify-center"
|
||||
onClick={unlinkPreviewMedia}
|
||||
>
|
||||
<X size={20} color="red" />
|
||||
</button>
|
||||
</Box>
|
||||
<Box className="w-[80%] h-[70vh] border border-gray-300 rounded-2xl p-3 relative flex justify-center items-center">
|
||||
{type === "media" && (
|
||||
<Box className="w-[80%] border border-gray-300 rounded-2xl relative flex items-center justify-center">
|
||||
{previewMedia && (
|
||||
<>
|
||||
<Box className="absolute top-4 right-4 z-10">
|
||||
<button
|
||||
className="w-10 h-10 flex items-center justify-center z-10"
|
||||
onClick={handleUnlinkPreviewMedia}
|
||||
>
|
||||
<X size={20} color="red" />
|
||||
</button>
|
||||
</Box>
|
||||
|
||||
<MediaViewer
|
||||
media={{
|
||||
id: sight.common.preview_media || "",
|
||||
media_type: 1,
|
||||
filename: sight.common.preview_media || "",
|
||||
}}
|
||||
/>
|
||||
<Box sx={{}}>
|
||||
<MediaViewer
|
||||
media={{
|
||||
id: previewMedia.id || "",
|
||||
media_type: previewMedia.media_type,
|
||||
filename: previewMedia.filename || "",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
{!previewMedia && (
|
||||
<MediaAreaForSight
|
||||
onFinishUpload={(mediaId) => {
|
||||
linkPreviewMedia(mediaId);
|
||||
}}
|
||||
onFilesDrop={() => {}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{!sight.common.preview_media && (
|
||||
<MediaAreaForSight
|
||||
onFinishUpload={(mediaId) => {
|
||||
linkPreviewMedia(mediaId);
|
||||
}}
|
||||
onFilesDrop={() => {}}
|
||||
/>
|
||||
<Box className="w-full h-full flex justify-center items-center">
|
||||
<Box
|
||||
sx={{
|
||||
maxWidth: "500px",
|
||||
maxHeight: "100%",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
margin: "0 auto",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<MediaAreaForSight
|
||||
onFinishUpload={(mediaId) => {
|
||||
linkPreviewMedia(mediaId);
|
||||
}}
|
||||
onFilesDrop={() => {}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
@ -423,8 +472,13 @@ export const RightWidgetTab = observer(
|
||||
justifyContent: "flex-end",
|
||||
}}
|
||||
>
|
||||
<Button variant="contained" color="success" onClick={handleSave}>
|
||||
Сохранить изменения
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<Save color="white" size={18} />}
|
||||
color="success"
|
||||
onClick={handleSave}
|
||||
>
|
||||
Сохранить
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -6,10 +6,10 @@ import TableHead from "@mui/material/TableHead";
|
||||
import TableRow from "@mui/material/TableRow";
|
||||
import Paper from "@mui/material/Paper";
|
||||
import { authInstance, cityStore, languageStore, sightsStore } from "@shared";
|
||||
import { useEffect } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { Button } from "@mui/material";
|
||||
import { LanguageSwitcher } from "@widgets";
|
||||
import { DeleteModal, LanguageSwitcher } from "@widgets";
|
||||
import { Pencil, Trash2 } from "lucide-react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
@ -29,6 +29,8 @@ export const SightsTable = observer(() => {
|
||||
const { language } = languageStore;
|
||||
const { sights, getSights } = sightsStore;
|
||||
const { cities, getCities } = cityStore;
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [rowId, setRowId] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@ -92,7 +94,10 @@ export const SightsTable = observer(() => {
|
||||
</button>
|
||||
<button
|
||||
className="rounded-md px-3 py-1.5 transition-transform transform hover:scale-105"
|
||||
onClick={() => handleDelete(row?.id)}
|
||||
onClick={() => {
|
||||
setIsDeleteModalOpen(true);
|
||||
setRowId(row?.id);
|
||||
}}
|
||||
>
|
||||
<Trash2 size={18} className="text-red-500" />
|
||||
</button>
|
||||
@ -103,6 +108,17 @@ export const SightsTable = observer(() => {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<DeleteModal
|
||||
open={isDeleteModalOpen}
|
||||
onDelete={async () => {
|
||||
if (rowId) {
|
||||
await handleDelete(rowId);
|
||||
}
|
||||
setIsDeleteModalOpen(false);
|
||||
setRowId(null);
|
||||
}}
|
||||
onCancel={() => setIsDeleteModalOpen(false)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -12,3 +12,5 @@ export * from "./MediaArea";
|
||||
export * from "./ModelViewer3D";
|
||||
export * from "./MediaAreaForSight";
|
||||
export * from "./ImageUploadCard";
|
||||
export * from "./LeaveAgree";
|
||||
export * from "./DeleteModal";
|
||||
|
Reference in New Issue
Block a user