feat: fix sight edit

This commit is contained in:
2025-06-07 15:16:29 +03:00
parent 1104e94ba8
commit 0fe4683683
17 changed files with 438 additions and 275 deletions

View File

@ -1,5 +1,10 @@
import { Box, Tab, Tabs } from "@mui/material";
import { articlesStore, cityStore, languageStore } from "@shared";
import {
articlesStore,
cityStore,
createSightStore,
languageStore,
} from "@shared";
import {
CreateInformationTab,
CreateLeftTab,
@ -22,13 +27,15 @@ export const CreateSightPage = observer(() => {
const [value, setValue] = useState(0);
const { getCities } = cityStore;
const { getArticles } = articlesStore;
const { needLeaveAgree } = createSightStore;
const handleChange = (_: React.SyntheticEvent, newValue: number) => {
setValue(newValue);
};
let blocker = useBlocker(
({ currentLocation, nextLocation }) =>
true && currentLocation.pathname !== nextLocation.pathname
needLeaveAgree && currentLocation.pathname !== nextLocation.pathname
);
useEffect(() => {

View File

@ -20,7 +20,7 @@ function a11yProps(index: number) {
export const EditSightPage = observer(() => {
const [value, setValue] = useState(0);
const { sight, getSightInfo } = editSightStore;
const { sight, getSightInfo, needLeaveAgree } = editSightStore;
const { getArticles } = articlesStore;
const { language } = languageStore;
const { id } = useParams();
@ -28,7 +28,7 @@ export const EditSightPage = observer(() => {
let blocker = useBlocker(
({ currentLocation, nextLocation }) =>
true && currentLocation.pathname !== nextLocation.pathname
needLeaveAgree && currentLocation.pathname !== nextLocation.pathname
);
const handleChange = (_: React.SyntheticEvent, newValue: number) => {

View File

@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
import { observer } from "mobx-react-lite";
import { Pencil, Trash2 } from "lucide-react";
import { useNavigate } from "react-router-dom";
import { DeleteModal, LanguageSwitcher } from "@widgets";
import { CreateButton, DeleteModal, LanguageSwitcher } from "@widgets";
export const SightListPage = observer(() => {
const { sights, getSights, deleteListSight } = sightsStore;
@ -69,6 +69,13 @@ export const SightListPage = observer(() => {
<LanguageSwitcher />
<div className="w-full">
<div className="flex justify-between items-center mb-10">
<h1 className="text-2xl">Достопримечательности</h1>
<CreateButton
label="Создать достопримечательность"
path="/sight/create"
/>
</div>
<DataGrid
rows={rows}
columns={columns}

View File

@ -202,6 +202,7 @@ export const SelectArticleModal = observer(
)}
</List>
</Paper>
<Box className="max-h-[80%] overflow-y-scroll mx-auto rounded-[10px]">
<Paper
elevation={3}
sx={{
@ -220,12 +221,22 @@ export const SelectArticleModal = observer(
>
<Box
sx={{
overflow: "hidden",
width: "100%",
height: 175,
minHeight: 100,
padding: "3px",
display: "flex",
alignItems: "center",
justifyContent: "center",
padding: "3px",
"& img": {
borderTopLeftRadius: "10px",
borderTopRightRadius: "10px",
width: "100%",
height: "auto",
objectFit: "contain",
},
}}
>
{articlesStore.articleMedia ? (
@ -235,6 +246,7 @@ export const SelectArticleModal = observer(
media_type: articlesStore.articleMedia.media_type,
filename: articlesStore.articleMedia.filename,
}}
fullWidth={true}
/>
) : (
<ImagePlus size={48} color="white" />
@ -283,6 +295,7 @@ export const SelectArticleModal = observer(
</Box>
)}
</Paper>
</Box>
</DialogContent>
<DialogActions sx={{ p: 2 }}>
<Button onClick={onClose}>Отмена</Button>

View File

@ -1,4 +1,4 @@
import { MEDIA_TYPE_LABELS, editSightStore } from "@shared";
import { MEDIA_TYPE_LABELS, MEDIA_TYPE_VALUES, editSightStore } from "@shared";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import {
@ -31,6 +31,7 @@ interface UploadMediaDialogProps {
media_type: number;
}) => void;
afterUploadSight?: (id: string) => void;
hardcodeType?: "thumbnail" | "watermark_lu" | "watermark_rd" | null;
}
export const UploadMediaDialog = observer(
@ -39,6 +40,7 @@ export const UploadMediaDialog = observer(
onClose,
afterUpload,
afterUploadSight,
hardcodeType,
}: UploadMediaDialogProps) => {
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
@ -165,10 +167,12 @@ export const UploadMediaDialog = observer(
<FormControl fullWidth sx={{ width: "50%" }}>
<InputLabel>Тип медиа</InputLabel>
<Select
value={mediaType}
value={
hardcodeType ? MEDIA_TYPE_VALUES[hardcodeType] : mediaType
}
disabled={!!hardcodeType}
label="Тип медиа"
onChange={(e) => setMediaType(Number(e.target.value))}
disabled={isLoading}
>
{availableMediaTypes.map((type) => (
<MenuItem key={type} value={type}>

View File

@ -98,18 +98,22 @@ class CreateSightStore {
heading: "New Heading (EN)",
body: "New Text (EN)",
};
const articleZhData = { heading: "新标题 (ZH)", body: "新文本 (ZH)" };
const articleZhData = {
heading: "Новый заголовок (ZH)",
body: "Новый текст (ZH)",
};
try {
const articleRes = await languageInstance("ru").post(
"/article",
articleRuData
);
this.needLeaveAgree = true;
const articleRes = await authInstance.post("/article", {
translation: {
ru: articleRuData,
en: articleEnData,
zh: articleZhData,
},
});
const { id } = articleRes.data; // New article's ID
await languageInstance("en").patch(`/article/${id}`, articleEnData);
await languageInstance("zh").patch(`/article/${id}`, articleZhData);
runInAction(() => {
const newArticleEntry = { id, media: [] };
this.sight.ru.right.push({ ...newArticleEntry, ...articleRuData });
@ -376,6 +380,7 @@ class CreateSightStore {
// --- General Store Methods ---
clearCreateSight = () => {
this.needLeaveAgree = false;
this.sight = JSON.parse(JSON.stringify(initialSightState)); // Reset to initial
};
@ -383,6 +388,7 @@ class CreateSightStore {
content: Partial<SightLanguageInfo | SightCommonInfo>, // Corrected types
language?: Language
) => {
this.needLeaveAgree = true;
if (language) {
this.sight[language] = { ...this.sight[language], ...content };
} else {
@ -587,10 +593,31 @@ class CreateSightStore {
}
};
updateRightArticles = async (articles: any[], language: Language) => {
runInAction(() => {
this.sight[language].right = articles;
});
updateRightArticles = async (articles: any[]) => {
const articlesIds = articles.map((article) => article.id);
const sortArticles = (existing: any[]) => {
const articleMap = new Map(
existing.map((article) => [article.id, article])
);
return articlesIds
.map((id) => articleMap.get(id))
.filter(
(article): article is (typeof existing)[number] =>
article !== undefined
);
};
this.sight.ru.right = sortArticles(this.sight.ru.right);
this.sight.en.right = sortArticles(this.sight.en.right);
this.sight.zh.right = sortArticles(this.sight.zh.right); // теперь zh тоже сортируется одинаково
this.needLeaveAgree = true;
};
needLeaveAgree = false;
setNeedLeaveAgree = (need: boolean) => {
this.needLeaveAgree = need;
};
}

View File

@ -170,6 +170,7 @@ class EditSightStore {
};
clearSightInfo = () => {
this.needLeaveAgree = false;
this.sight = {
common: {
id: 0,
@ -215,6 +216,7 @@ class EditSightStore {
content: Partial<SightLanguageInfo | SightCommonInfo>,
common: boolean = false
) => {
this.needLeaveAgree = true;
if (common) {
this.sight.common = {
...this.sight.common,
@ -262,60 +264,71 @@ class EditSightStore {
this.sight.common.left_article != 0 &&
this.sight.common.left_article != null
) {
await languageInstance("ru").patch(
`/article/${this.sight.common.left_article}`,
{
await authInstance.patch(`/article/${this.sight.common.left_article}`, {
translation: {
ru: {
heading: this.sight.ru.left.heading,
body: this.sight.ru.left.body,
}
);
await languageInstance("en").patch(
`/article/${this.sight.common.left_article}`,
{
},
en: {
heading: this.sight.en.left.heading,
body: this.sight.en.left.body,
}
);
await languageInstance("zh").patch(
`/article/${this.sight.common.left_article}`,
{
},
zh: {
heading: this.sight.zh.left.heading,
body: this.sight.zh.left.body,
}
);
},
},
});
}
await languageInstance("ru").patch(`/sight/${this.sight.common.id}`, {
await authInstance.patch(`/sight/${this.sight.common.id}`, {
...this.sight.common,
translation: {
ru: {
name: this.sight.ru.name,
address: this.sight.ru.address,
left_article: createdLeftArticleId,
});
await languageInstance("en").patch(`/sight/${this.sight.common.id}`, {
...this.sight.common,
},
en: {
name: this.sight.en.name,
address: this.sight.en.address,
left_article: createdLeftArticleId,
});
await languageInstance("zh").patch(`/sight/${this.sight.common.id}`, {
...this.sight.common,
},
zh: {
name: this.sight.zh.name,
address: this.sight.zh.address,
left_article: createdLeftArticleId,
},
},
});
for (const language of ["ru", "en", "zh"] as Language[]) {
for (const article of this.sight[language].right) {
for (const article of this.sight.ru.right) {
if (article.id == 0 || article.id == null) {
continue;
}
await languageInstance(language).patch(`/article/${article.id}`, {
await authInstance.patch(`/article/${article.id}`, {
translation: {
ru: {
heading: article.heading,
body: article.body,
},
en: {
heading: article.heading,
body: article.body,
},
zh: {
heading: article.heading,
body: article.body,
},
},
});
}
}
const articleIdsInObject = this.sight.ru.right.map((article) => ({
id: article.id,
}));
await authInstance.post(`/sight/${this.sight.common.id}/article/order`, {
articles: articleIdsInObject,
});
// await languageInstance("ru").patch(
// `/sight/${this.sight.common.left_article}/article`,
@ -578,19 +591,27 @@ class EditSightStore {
};
createNewRightArticle = async () => {
const articleId = await languageInstance("ru").post("/article", {
heading: "Введите русский заголовок",
body: "Введите русский текст",
const articleRuData = {
heading: "Новый заголовок (RU)",
body: "Новый текст (RU)",
};
const articleEnData = {
heading: "New Heading (EN)",
body: "New Text (EN)",
};
const articleZhData = {
heading: "Новый заголовок (ZH)",
body: "Новый текст (ZH)",
};
const articleId = await authInstance.post("/article", {
translation: {
ru: articleRuData,
en: articleEnData,
zh: articleZhData,
},
});
const { id } = articleId.data;
await languageInstance("en").patch(`/article/${id}`, {
heading: "Enter the English heading",
body: "Enter the English text",
});
await languageInstance("zh").patch(`/article/${id}`, {
heading: "Введите китайский заголовок",
body: "Введите китайский текст",
});
await authInstance.post(`/sight/${this.sight.common.id}/article`, {
article_id: id,
page_num: this.sight.ru.right.length + 1,
@ -598,20 +619,20 @@ class EditSightStore {
this.sight.ru.right.push({
id: id,
heading: "Введите русский заголовок",
body: "Введите русский текст",
heading: articleRuData.heading,
body: articleRuData.body,
media: [],
});
this.sight.en.right.push({
id: id,
heading: "Enter the English heading",
body: "Enter the English text",
heading: articleEnData.heading,
body: articleEnData.body,
media: [],
});
this.sight.zh.right.push({
id: id,
heading: "Введите китайский заголовок",
body: "Введите китайский текст",
heading: articleZhData.heading,
body: articleZhData.body,
media: [],
});
};
@ -671,14 +692,31 @@ class EditSightStore {
this.sight[language].right[index].body = body;
};
updateRightArticles = async (articles: any[], language: Language) => {
this.sight[language].right = articles;
const articleIdsInObject = articles.map((article) => ({
id: article.id,
}));
await authInstance.post(`/sight/${this.sight.common.id}/article/order`, {
articles: articleIdsInObject,
});
updateRightArticles = async (articles: any[]) => {
const articlesIds = articles.map((article) => article.id);
const sortArticles = (existing: any[]) => {
const articleMap = new Map(
existing.map((article) => [article.id, article])
);
return articlesIds
.map((id) => articleMap.get(id))
.filter(
(article): article is (typeof existing)[number] =>
article !== undefined
);
};
this.sight.ru.right = sortArticles(this.sight.ru.right);
this.sight.en.right = sortArticles(this.sight.en.right);
this.sight.zh.right = sortArticles(this.sight.zh.right); // теперь zh тоже сортируется одинаково
this.needLeaveAgree = true;
};
needLeaveAgree = false;
setNeedLeaveAgree = (need: boolean) => {
this.needLeaveAgree = need;
};
}

View File

@ -1,8 +1,8 @@
import React, { useRef, useState, DragEvent, useEffect } from "react";
import { Paper, Box, Typography, Button, Tooltip } from "@mui/material";
import { X, Info, MousePointer } from "lucide-react"; // Assuming lucide-react for icons
import { X, Info, Plus } from "lucide-react"; // Assuming lucide-react for icons
import { editSightStore } from "@shared";
import { toast } from "react-toastify";
interface ImageUploadCardProps {
title: string;
imageKey?: "thumbnail" | "watermark_lu" | "watermark_rd";
@ -12,16 +12,18 @@ interface ImageUploadCardProps {
onSelectFileClick: () => void;
setUploadMediaOpen: (open: boolean) => void;
tooltipText?: string;
setHardcodeType?: (type: string) => void;
}
export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
title,
imageKey,
imageUrl,
onImageClick,
onDeleteImageClick,
onSelectFileClick,
setUploadMediaOpen,
setHardcodeType,
tooltipText,
}) => {
const fileInputRef = useRef<HTMLInputElement>(null);
@ -46,6 +48,9 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
if (file) {
setFileToUpload(file);
setUploadMediaOpen(true);
if (imageKey && setHardcodeType) {
setHardcodeType(imageKey);
}
}
// Reset the input value so selecting the same file again triggers change
event.target.value = "";
@ -73,8 +78,12 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
const files = event.dataTransfer.files;
if (files && files.length > 0) {
const file = files[0];
if (file.type.startsWith("image/")) {
setFileToUpload(file);
setUploadMediaOpen(true);
} else {
toast.error("Пожалуйста, выберите изображение");
}
}
};
@ -159,7 +168,7 @@ export const ImageUploadCard: React.FC<ImageUploadCardProps> = ({
<Button
variant="contained"
color="primary"
startIcon={<MousePointer color="white" size={18} />}
startIcon={<Plus color="white" size={18} />}
onClick={(e) => {
e.stopPropagation(); // Prevent `handleZoneClick` from firing
onSelectFileClick(); // This button might trigger a different modal

View File

@ -96,10 +96,10 @@ export const MediaArea = observer(
</Button>
</div>
<div className="w-full flex flex-start flex-wrap gap-2 mt-4">
<div className="w-full flex flex-start flex-wrap gap-2 mt-4 py-10">
{mediaIds.map((m) => (
<button
className="relative w-40 h-40"
className="relative w-20 h-20"
key={m.id}
onClick={() => handleMediaModal(m.id)}
>

View File

@ -12,29 +12,17 @@ export interface MediaData {
export function MediaViewer({
media,
className,
}: Readonly<{ media?: MediaData; className?: string }>) {
fullWidth,
}: Readonly<{ media?: MediaData; className?: string; fullWidth?: boolean }>) {
const token = localStorage.getItem("token");
return (
<Box
sx={{
width: "100%",
height: "100%",
display: "flex",
flexGrow: 1,
}}
className={className}
>
<Box className={className} width={fullWidth ? "100%" : "auto"}>
{media?.media_type === 1 && (
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
media?.id
}/download?token=${token}`}
alt={media?.filename}
style={{
width: "100%",
height: "100%",
objectFit: "contain",
}}
/>
)}
@ -46,7 +34,7 @@ export function MediaViewer({
style={{
width: "100%",
height: "100%",
objectFit: "contain",
objectFit: "cover",
borderRadius: 8,
}}
controls
@ -60,12 +48,6 @@ export function MediaViewer({
media?.id
}/download?token=${token}`}
alt={media?.filename}
style={{
width: "100%",
height: "100%",
objectFit: "contain",
borderRadius: 8,
}}
/>
)}
{media?.media_type === 4 && (
@ -75,10 +57,7 @@ export function MediaViewer({
}/download?token=${token}`}
alt={media?.filename}
style={{
width: "100%",
height: "100%",
objectFit: "contain",
borderRadius: 8,
objectFit: "cover",
}}
/>
)}

View File

@ -25,13 +25,20 @@ const StyledMarkdownEditor = styled("div")(({ theme }) => ({
color: theme.palette.text.primary,
borderColor: theme.palette.divider,
height: "auto",
minHeight: "200px",
minHeight: "400px",
maxHeight: "500px",
overflow: "auto",
overflowY: "auto",
overflowX: "hidden",
wordWrap: "break-word", // ✅ добавлено
whiteSpace: "pre-wrap", // ✅ добавлено
},
"& .CodeMirror-scroll": {
minHeight: "200px",
minHeight: "400px",
maxHeight: "500px",
overflowY: "auto",
overflowX: "hidden",
wordBreak: "break-word", // ✅ добавлено
},
"& .CodeMirror-selected": {
backgroundColor: `${theme.palette.action.selected} !important`,
@ -76,6 +83,9 @@ export const ReactMarkdownEditor = ({
const mergedOptions = useMemo(() => {
return {
...incomingOptions,
codeMirrorOptions: {
lineWrapping: true,
},
forceSync: true,
spellChecker: false,
toolbar: [

View File

@ -48,6 +48,9 @@ export const CreateInformationTab = observer(
"thumbnail" | "watermark_lu" | "watermark_rd" | null
>(null);
const [isAddMediaOpen, setIsAddMediaOpen] = useState(false);
const [hardcodeType, setHardcodeType] = useState<
"thumbnail" | "watermark_lu" | "watermark_rd" | null
>(null);
// const handleMenuOpen = (
// event: React.MouseEvent<HTMLElement>,
@ -260,10 +263,15 @@ export const CreateInformationTab = observer(
setIsUploadMediaOpen(true);
setActiveMenuType("thumbnail");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
<ImageUploadCard
title="Водяной знак (л)"
title="Водяной знак (леввый верхний)"
imageKey="watermark_lu"
imageUrl={sight.watermark_lu}
onImageClick={() => {
@ -284,10 +292,15 @@ export const CreateInformationTab = observer(
setIsUploadMediaOpen(true);
setActiveMenuType("watermark_lu");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
<ImageUploadCard
title="Водяной знак (п)"
title="Водяной знак (правый нижний)"
imageKey="watermark_rd"
imageUrl={sight.watermark_rd}
onImageClick={() => {
@ -308,6 +321,11 @@ export const CreateInformationTab = observer(
setIsUploadMediaOpen(true);
setActiveMenuType("watermark_rd");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
</Box>
</Box>
@ -397,6 +415,7 @@ export const CreateInformationTab = observer(
setActiveMenuType(null);
setIsUploadMediaOpen(false);
}}
hardcodeType={hardcodeType}
/>
</>
);

View File

@ -9,6 +9,7 @@ import {
createSightStore,
SelectArticleModal,
UploadMediaDialog,
Language,
} from "@shared";
import {
LanguageSwitcher,
@ -308,8 +309,8 @@ export const CreateLeftTab = observer(
flex: 1,
display: "flex",
flexDirection: "column",
gap: 1.5,
maxWidth: "320px",
gap: 0.5,
}}
>
<Paper
@ -317,49 +318,32 @@ export const CreateLeftTab = 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",
flexDirection: "column",
borderRadius: "10px",
}}
>
{/* {data.left.media?.filename ? (
<Box
sx={{
overflow: "hidden",
width: "100%",
height: 200,
backgroundColor: "grey.300",
minHeight: 100,
padding: "3px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<img
src={data.left.media?.filename ?? ""}
alt="Превью медиа"
style={{
objectFit: "cover",
width: "100%",
height: "100%",
}}
/>
</Box>
) : (
)} */}
<Box
sx={{
"& img": {
borderTopLeftRadius: "10px",
borderTopRightRadius: "10px",
width: "100%",
height: 200,
backgroundColor: "grey.300",
display: "flex",
alignItems: "center",
justifyContent: "center",
height: "auto",
objectFit: "contain",
},
}}
>
{sight[language].left.media.length > 0 ? (
@ -370,33 +354,59 @@ export const CreateLeftTab = observer(
sight[language].left.media[0].media_type,
filename: sight[language].left.media[0].filename,
}}
fullWidth
/>
) : (
<ImagePlus size={48} color="grey" />
<ImagePlus size={48} color="white" />
)}
</Box>
{/* Заголовок в превью */}
<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%",
}}
>
{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: 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,
}}
>
@ -404,6 +414,7 @@ export const CreateLeftTab = observer(
value={sight[language].left.body}
/>
</Box>
)}
</Paper>
</Box>
</Box>

View File

@ -239,7 +239,7 @@ export const CreateRightTab = observer(
// 4. Update the store with the new order:
// This will typically trigger a re-render of the component with the updated list.
updateRightArticles(newRightArticles, language);
updateRightArticles(newRightArticles);
};
return (
@ -511,7 +511,7 @@ export const CreateRightTab = observer(
<Paper
className="flex-1 flex flex-col max-w-[500px]"
sx={{
borderRadius: "16px",
borderRadius: "10px",
overflow: "hidden",
}}
elevation={2}
@ -531,13 +531,17 @@ export const CreateRightTab = observer(
0 ? (
<Box
sx={{
overflow: "hidden",
width: "100%",
maxHeight: "290px",
flexShrink: 0,
padding: "2px 2px 0px 2px",
display: "flex",
alignItems: "center",
justifyContent: "center",
"& img": {
borderTopLeftRadius: "10px",
borderTopRightRadius: "10px",
width: "100%",
height: "auto",
objectFit: "contain",
},
}}
>
<MediaViewer
@ -571,6 +575,7 @@ export const CreateRightTab = observer(
fontWeight: 700,
lineHeight: "120%",
backdropFilter: "blur(12px)",
borderBottom: "1px solid #A89F90",
boxShadow:
"inset 4px 4px 12px 0 rgba(255,255,255,0.12)",
background:

View File

@ -50,7 +50,9 @@ export const InformationTab = observer(
"thumbnail" | "watermark_lu" | "watermark_rd" | null
>(null);
const [isAddMediaOpen, setIsAddMediaOpen] = useState(false);
const [hardcodeType, setHardcodeType] = useState<
"thumbnail" | "watermark_lu" | "watermark_rd" | null
>(null);
useEffect(() => {
// Показывать только при инициализации (не менять при ошибках пользователя)
if (sight.common.latitude !== 0 || sight.common.longitude !== 0) {
@ -260,9 +262,14 @@ export const InformationTab = observer(
setIsUploadMediaOpen(true);
setActiveMenuType("thumbnail");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
<ImageUploadCard
title="Водяной знак (л)"
title="Водяной знак (левый верхний)"
imageKey="watermark_lu"
imageUrl={sight.common.watermark_lu}
onImageClick={() => {
@ -287,9 +294,14 @@ export const InformationTab = observer(
setIsUploadMediaOpen(true);
setActiveMenuType("watermark_lu");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
<ImageUploadCard
title="Водяной знак (п)"
title="Водяной знак (правый нижний)"
imageKey="watermark_rd"
imageUrl={sight.common.watermark_rd}
onImageClick={() => {
@ -314,6 +326,11 @@ export const InformationTab = observer(
setIsUploadMediaOpen(true);
setActiveMenuType("watermark_rd");
}}
setHardcodeType={(type) => {
setHardcodeType(
type as "thumbnail" | "watermark_lu" | "watermark_rd"
);
}}
/>
</Box>
</Box>
@ -399,6 +416,7 @@ export const InformationTab = observer(
setActiveMenuType(null);
setIsUploadMediaOpen(false);
}}
hardcodeType={hardcodeType}
/>
<PreviewMediaDialog
open={isPreviewMediaOpen}

View File

@ -260,22 +260,32 @@ export const LeftWidgetTab = observer(
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",
padding: 0,
display: "flex",
flexDirection: "column",
borderRadius: "10px",
}}
>
<Box
sx={{
overflow: "hidden",
width: "100%",
height: 175,
minHeight: 100,
padding: "3px",
display: "flex",
alignItems: "center",
padding: "3px",
justifyContent: "center",
"& img": {
borderTopLeftRadius: "10px",
borderTopRightRadius: "10px",
width: "100%",
height: "auto",
objectFit: "contain",
},
}}
>
{data.left.media.length > 0 ? (
@ -285,9 +295,10 @@ export const LeftWidgetTab = observer(
media_type: data.left.media[0].media_type,
filename: data.left.media[0].filename,
}}
fullWidth
/>
) : (
<ImagePlus size={48} color="grey" />
<ImagePlus size={48} color="white" />
)}
</Box>

View File

@ -182,7 +182,7 @@ export const RightWidgetTab = observer(
// 4. Update the store with the new order:
// This will typically trigger a re-render of the component with the updated list.
updateRightArticles(newRightArticles, language);
updateRightArticles(newRightArticles);
};
return (
@ -347,7 +347,7 @@ export const RightWidgetTab = observer(
value={
sight[language].right[activeArticleIndex].body
}
onChange={(value) =>
onChange={(value: any) =>
updateRightArticleInfo(
activeArticleIndex,
language,
@ -381,11 +381,11 @@ export const RightWidgetTab = observer(
</Box>
)}
{type === "media" && (
<Box className="w-[80%] border border-gray-300 rounded-2xl relative">
<Box className="w-[80%] border border-gray-300 rounded-2xl relative flex justify-center items-center">
{sight.common.preview_media && (
<>
{type === "media" && (
<Box className="w-[80%] h-full rounded-2xl relative flex items-center justify-center">
<Box className="w-full h-full rounded-2xl relative flex items-center justify-center">
{previewMedia && (
<>
<Box className="absolute top-4 right-4 z-10">
@ -397,7 +397,7 @@ export const RightWidgetTab = observer(
</button>
</Box>
<Box className="w-1/2 h-1/2">
<Box className="w-1/2 h-1/2 flex justify-center items-center">
<MediaViewer
media={{
id: previewMedia.id || "",
@ -453,7 +453,7 @@ export const RightWidgetTab = observer(
<Paper
className="flex-1 flex flex-col max-w-[500px]"
sx={{
borderRadius: "16px",
borderRadius: "10px",
overflow: "hidden",
}}
elevation={2}
@ -473,13 +473,17 @@ export const RightWidgetTab = observer(
0 ? (
<Box
sx={{
overflow: "hidden",
width: "100%",
maxHeight: "290px",
flexShrink: 0,
padding: "2px 2px 0px 2px",
display: "flex",
alignItems: "center",
justifyContent: "center",
"& img": {
borderTopLeftRadius: "10px",
borderTopRightRadius: "10px",
width: "100%",
height: "auto",
objectFit: "contain",
},
}}
>
<MediaViewer
@ -513,6 +517,7 @@ export const RightWidgetTab = observer(
fontWeight: 700,
lineHeight: "120%",
backdropFilter: "blur(12px)",
borderBottom: "1px solid #A89F90",
boxShadow:
"inset 4px 4px 12px 0 rgba(255,255,255,0.12)",
background: