fix: Language cache sight

This commit is contained in:
2025-05-31 21:17:27 +03:00
parent 2e6917406e
commit 0d9bbb140f
28 changed files with 2760 additions and 1013 deletions

View File

@@ -1,242 +1,348 @@
// @widgets/LeftWidgetTab.tsx
import { Box, Button, TextField, Paper, Typography } from "@mui/material";
import { BackButton, Sight, TabPanel } from "@shared";
import { ReactMarkdownComponent, ReactMarkdownEditor } from "@widgets";
import {
articlesStore,
BackButton,
TabPanel,
languageStore,
SelectMediaDialog,
editSightStore,
} from "@shared";
import {
LanguageSwitcher,
ReactMarkdownComponent,
ReactMarkdownEditor,
} from "@widgets";
import { Unlink, Trash2, ImagePlus } from "lucide-react";
import { useState } from "react";
import { useState, useEffect, useCallback } from "react";
import { observer } from "mobx-react-lite";
export const LeftWidgetTab = ({
value,
index,
data,
}: {
value: number;
index: number;
data?: Sight;
}) => {
const [articleTitle, setArticleTitle] = useState("");
const [markdownContent, setMarkdownContent] = useState("");
const [articleMedia, setArticleMedia] = useState<string | null>(null); // Для превью медиа
export const LeftWidgetTab = observer(
({ value, index }: { value: number; index: number }) => {
const { sightInfo, updateSightInfo, loadSightInfo } = editSightStore;
const { articleLoading, getArticleByArticleId } = articlesStore;
const { language } = languageStore;
const linkedArticle = getArticleByArticleId.get(); // Получаем связанную статью
const data = sightInfo[languageStore.language]; // Получаем данные для текущего языка
const handleSelectMediaForArticle = () => {
// Логика открытия модального окна для выбора медиа для статьи
console.log("Select media fo r left article");
// Для примера, установим моковое изображение
// setArticleMedia("https://via.placeholder.com/350x200.png?text=Article+Media");
};
useEffect(() => {
// Этот useEffect должен загружать данные ИЗ СВЯЗАННОЙ СТАТЬИ
// ТОЛЬКО ЕСЛИ данные для ТЕКУЩЕГО ЯЗЫКА еще не были загружены
// или если sightInfo.left_article изменился (т.е. привязали новую статью).
const handleUnlinkArticle = () => {
console.log("Unlink left article");
};
// Мы также должны учитывать, что linkedArticle может измениться (т.е. новую статью привязали)
// или language изменился.
// Если для текущего языка данные еще не "загружены" (`loaded: false`),
// тогда мы берем их из `linkedArticle` и инициализируем.
console.log("data.left.loaded", data.left.loaded);
if (!data.left.loaded) {
// <--- КЛЮЧЕВОЕ УСЛОВИЕ
if (linkedArticle && !articleLoading) {
console.log("loadSightInfo", linkedArticle, language);
loadSightInfo(
languageStore.language,
linkedArticle.heading,
linkedArticle.body || "",
null
);
}
}
// Зависимости: linkedArticle (для реакции на изменение привязанной статьи),
// languageStore.language (для реакции на изменение языка),
// loadSightInfo (чтобы useEffect знал об изменениях в функции),
// data.left.loaded (чтобы useEffect перепроверил условие, когда этот флаг изменится).
// Важно: если data.left.loaded становится true, то этот эффект не будет
// перезапускаться для того же языка.
}, [
linkedArticle?.heading,
language,
loadSightInfo,
data.left.loaded,
articleLoading,
]);
const handleDeleteArticle = () => {
console.log("Delete left article");
};
const [isSelectMediaDialogOpen, setIsSelectMediaDialogOpen] =
useState(false);
const handleSave = () => {
console.log("Saving left widget...");
};
const handleOpenMediaDialog = useCallback(() => {
setIsSelectMediaDialogOpen(true);
}, []);
return (
<TabPanel value={value} index={index}>
<Box
sx={{
display: "flex",
flexDirection: "column",
gap: 3,
paddingBottom: "70px",
position: "relative",
}}
>
<BackButton />
const handleMediaSelected = useCallback(
(selectedMedia: any) => {
// При выборе медиа, обновляем данные для ТЕКУЩЕГО ЯЗЫКА
// сохраняя текущие heading и body.
updateSightInfo(
languageStore.language,
data.left.heading,
data.left.body,
selectedMedia
);
setIsSelectMediaDialogOpen(false);
},
[
languageStore.language,
data.left.heading,
data.left.body,
updateSightInfo,
]
);
<Paper
elevation={2}
const handleCloseMediaDialog = useCallback(() => {
setIsSelectMediaDialogOpen(false);
}, []);
// ... (остальной JSX код остался почти без изменений)
return (
<TabPanel value={value} index={index}>
<LanguageSwitcher />
<Box
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
paddingX: 2.5,
paddingY: 1.5,
borderRadius: 2,
border: "1px solid",
borderColor: "divider",
flexDirection: "column",
gap: 3,
paddingBottom: "70px",
position: "relative",
}}
>
<Typography variant="h6">Левая статья</Typography>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
<Button
variant="outlined"
color="primary"
startIcon={<Unlink size={18} />}
onClick={handleUnlinkArticle}
size="small"
>
Открепить
</Button>
<Button
variant="outlined"
color="error"
startIcon={<Trash2 size={18} />}
onClick={handleDeleteArticle}
size="small"
>
Удалить
</Button>
</Box>
</Paper>
<BackButton />
<Box sx={{ display: "flex", gap: 3, flexGrow: 1 }}>
{/* Левая колонка: Редактирование */}
<Box
sx={{ flex: 2, display: "flex", flexDirection: "column", gap: 2 }}
<Paper
elevation={2}
sx={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
paddingX: 2.5,
paddingY: 1.5,
borderRadius: 2,
border: "1px solid",
borderColor: "divider",
}}
>
<TextField
label="Название информации" // На макете "Название" для статьи, потом "Информация"
value={articleTitle}
onChange={(e) => setArticleTitle(e.target.value)}
variant="outlined"
sx={{ width: "100%" }} // Примерная ширина как на макете
/>
<ReactMarkdownEditor
value={markdownContent}
onChange={setMarkdownContent}
/>
{/* Блок МЕДИА для статьи */}
<Paper elevation={1} sx={{ padding: 2, mt: 1 }}>
<Typography variant="h6" gutterBottom>
МЕДИА
</Typography>
{/* Здесь будет UI для управления медиа статьи */}
{articleMedia ? (
<Box sx={{ mb: 1 }}>
<img
src={articleMedia}
alt="Article media"
style={{
maxWidth: "100%",
maxHeight: "150px",
borderRadius: "4px",
}}
/>
</Box>
) : (
<Box
sx={{
width: "100%",
height: 100,
backgroundColor: "grey.100",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: 1,
mb: 1,
border: "2px dashed",
}}
<Typography variant="h6">Левая статья</Typography>
<Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
{linkedArticle && (
<Button
variant="outlined"
color="primary"
startIcon={<Unlink size={18} />}
size="small"
>
<Typography color="text.secondary">Нет медиа</Typography>
</Box>
Открепить
</Button>
)}
<Button variant="contained" onClick={handleSelectMediaForArticle}>
Выбрать/Загрузить медиа
<Button
variant="outlined"
color="error"
startIcon={<Trash2 size={18} />}
size="small"
>
Удалить
</Button>
</Paper>
</Box>
</Box>
</Paper>
{/* Правая колонка: Предпросмотр */}
<Box sx={{ display: "flex", flexDirection: "column", gap: 1.5 }}>
<Typography variant="h6">Предпросмотр</Typography>
<Paper
elevation={3}
<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,
e.target.value,
data.left.body,
data.left.media
)
}
variant="outlined"
fullWidth
/>
<ReactMarkdownEditor
value={data.left.body}
onChange={(value) =>
updateSightInfo(
languageStore.language,
data.left.heading,
value,
data.left.media
)
}
/>
{/* Блок МЕДИА для статьи */}
<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,
data.left.heading,
data.left.body,
null
)
}
>
Удалить медиа
</Button>
)}
</Paper>
</Box>
{/* Правая колонка: Предпросмотр */}
<Box
sx={{
width: "100%", // Ширина как на макете ~350px
minWidth: 320,
maxWidth: 400,
height: "auto", // Автоматическая высота или можно задать minHeight
minHeight: 500,
backgroundColor: "#877361", // Желтоватый фон
overflowY: "auto",
padding: 0,
flex: 1,
display: "flex",
flexDirection: "column",
gap: 1.5,
}}
>
{/* Медиа в превью (если есть) */}
{articleMedia && (
<Box
sx={{
width: "100%",
height: 200,
backgroundColor: "grey.300",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<img
src={articleMedia}
alt="Превью медиа"
style={{
objectFit: "cover",
width: "100%",
height: "100%",
}}
/>
</Box>
)}
{!articleMedia && (
<Box
sx={{
width: "100%",
height: 200,
backgroundColor: "grey.300",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<ImagePlus size={48} color="grey" />
</Box>
)}
{/* Заголовок в превью */}
<Box
<Typography variant="h6">Предпросмотр</Typography>
<Paper
elevation={3}
sx={{
width: "100%",
minWidth: 320,
maxWidth: 400,
height: "auto",
minHeight: 500,
backgroundColor: "#877361",
color: "white",
padding: 1.5,
overflowY: "auto",
padding: 0,
display: "flex",
flexDirection: "column",
}}
>
<Typography
variant="h5"
component="h2"
sx={{ wordBreak: "break-word" }}
{/* Медиа в превью (если есть) */}
{data.left.media ? (
<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",
}}
>
<ImagePlus size={48} color="grey" />
</Box>
)}
{/* Заголовок в превью */}
<Box
sx={{
backgroundColor: "#877361",
color: "white",
padding: 1.5,
}}
>
{articleTitle || "Название информации"}
</Typography>
</Box>
<Typography
variant="h5"
component="h2"
sx={{ wordBreak: "break-word" }}
>
{data.left.heading || "Название информации"}
</Typography>
</Box>
{/* Текст статьи в превью */}
<Box
sx={{
padding: 2,
{/* Текст статьи в превью */}
<Box
sx={{
padding: 2,
flexGrow: 1,
}}
>
<ReactMarkdownComponent value={data.left.body} />
</Box>
</Paper>
</Box>
</Box>
flexGrow: 1,
}}
>
<ReactMarkdownComponent value={markdownContent} />
</Box>
</Paper>
<Box sx={{ position: "absolute", bottom: 0, right: 0, padding: 2 }}>
<Button variant="contained" color="success">
Сохранить
</Button>
</Box>
</Box>
<Box sx={{ position: "absolute", bottom: 0, right: 0, padding: 2 }}>
<Button variant="contained" color="success" onClick={handleSave}>
Сохранить
</Button>
</Box>
</Box>
</TabPanel>
);
};
<SelectMediaDialog
open={isSelectMediaDialogOpen}
onClose={handleCloseMediaDialog}
onSelectMedia={handleMediaSelected}
/>
</TabPanel>
);
}
);