WhiteNightsAdminPanel/src/shared/modals/SelectArticleDialog/index.tsx

303 lines
9.7 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 { articlesStore } from "@shared";
import { observer } from "mobx-react-lite";
import { useEffect, useState } from "react";
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
TextField,
List,
ListItemButton,
ListItemText,
Paper,
Box,
Typography,
InputAdornment,
} from "@mui/material";
import { ImagePlus, Search } from "lucide-react";
import { ReactMarkdownComponent } from "@widgets";
interface SelectArticleModalProps {
open: boolean;
onClose: () => void;
onSelectArticle: (articleId: string) => void;
linkedArticleIds?: string[]; // Add optional prop for linked articles
}
export const SelectArticleModal = observer(
({
open,
onClose,
onSelectArticle,
linkedArticleIds = [],
}: SelectArticleModalProps) => {
const { articles, getArticle, getArticleMedia } = articlesStore;
const [searchQuery, setSearchQuery] = useState("");
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
null
);
const [isLoading, setIsLoading] = useState(false);
// Reset selection when modal opens/closes
useEffect(() => {
if (open) {
setSelectedArticleId(null);
articlesStore.articleData = null;
articlesStore.articleMedia = null;
}
}, [open]);
useEffect(() => {
const handleKeyPress = (event: KeyboardEvent) => {
if (event.key.toLowerCase() === "enter") {
event.preventDefault();
if (selectedArticleId) {
onSelectArticle(selectedArticleId);
onClose();
}
}
};
window.addEventListener("keydown", handleKeyPress);
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, [selectedArticleId, onSelectArticle, onClose]);
const handleArticleClick = async (articleId: string) => {
if (selectedArticleId === articleId) return;
setSelectedArticleId(articleId);
setIsLoading(true);
try {
await Promise.all([
getArticle(Number(articleId)),
getArticleMedia(Number(articleId)),
]);
} catch (error) {
console.error("Failed to fetch article data:", error);
// Reset article data on error
articlesStore.articleData = null;
articlesStore.articleMedia = null;
} finally {
setIsLoading(false);
}
};
// @ts-ignore
const filteredArticles = articles
// @ts-ignore
.filter((article) => !linkedArticleIds.includes(article.id))
// @ts-ignore
.filter((article) =>
article.service_name.toLowerCase().includes(searchQuery.toLowerCase())
);
const token = localStorage.getItem("token");
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="lg"
fullWidth
PaperProps={{
sx: {
minHeight: "80vh",
maxHeight: "90vh",
},
}}
>
<DialogTitle>Выберите существующую статью</DialogTitle>
<DialogContent
className="flex gap-4"
dividers
sx={{
height: "600px",
display: "flex",
flexDirection: "row",
p: 2,
}}
>
<Paper className="w-[66%] flex flex-col" elevation={2}>
<TextField
fullWidth
placeholder="Поиск статей..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
sx={{ mb: 2, mt: 1, px: 2 }}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search size={20} />
</InputAdornment>
),
}}
/>
<List sx={{ flexGrow: 1, overflowY: "auto", px: 2 }}>
{filteredArticles.length === 0 ? (
<Typography
variant="body2"
color="text.secondary"
sx={{ p: 2, textAlign: "center" }}
>
{searchQuery ? "Статьи не найдены" : "Нет доступных статей"}
</Typography>
) : (
// @ts-ignore
filteredArticles.map((article) => (
<ListItemButton
key={article.id}
onClick={() => handleArticleClick(article.id)}
onDoubleClick={() => onSelectArticle(article.id)}
selected={selectedArticleId === article.id}
disabled={isLoading}
sx={{
borderRadius: 1,
mb: 0.5,
"&:hover": {
backgroundColor: "action.hover",
},
"&.Mui-selected": {
backgroundColor: "primary.main",
color: "primary.contrastText",
"&:hover": {
backgroundColor: "primary.dark",
},
},
}}
>
<ListItemText
primary={article.service_name}
primaryTypographyProps={{
fontWeight:
selectedArticleId === article.id ? "bold" : "normal",
}}
/>
</ListItemButton>
))
)}
</List>
</Paper>
<Paper className="flex-1 flex flex-col" elevation={2}>
<Box
className="rounded-2xl overflow-hidden"
sx={{
width: "100%",
height: "100%",
background: "#877361",
borderColor: "grey.300",
display: "flex",
flexDirection: "column",
}}
>
{isLoading ? (
<Box
sx={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Typography color="white">Загрузка...</Typography>
</Box>
) : (
<>
{articlesStore.articleMedia && (
<Box sx={{ p: 2, backgroundColor: "rgba(0,0,0,0.1)" }}>
<img
src={`${import.meta.env.VITE_KRBL_MEDIA}${
articlesStore.articleMedia.id
}/download?token=${token}`}
alt={articlesStore.articleMedia.filename}
style={{
maxWidth: "100%",
height: "auto",
maxHeight: "300px",
objectFit: "contain",
borderRadius: 8,
}}
/>
</Box>
)}
{!articlesStore.articleMedia && (
<Box
sx={{
width: "100%",
height: 200,
flexShrink: 0,
backgroundColor: "rgba(0,0,0,0.1)",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<ImagePlus size={48} color="white" />
</Box>
)}
<Box
sx={{
width: "100%",
minHeight: "70px",
background: "#877361",
display: "flex",
flexShrink: 0,
alignItems: "center",
borderBottom: "1px solid rgba(255,255,255,0.1)",
px: 2,
}}
>
<Typography variant="h6" color="white">
{articlesStore.articleData?.heading || "Выберите статью"}
</Typography>
</Box>
<Box
sx={{
px: 2,
flexGrow: 1,
overflowY: "auto",
backgroundColor: "#877361",
color: "white",
py: 1,
}}
>
{articlesStore.articleData?.body ? (
<ReactMarkdownComponent
value={articlesStore.articleData.body}
/>
) : (
<Typography
color="rgba(255,255,255,0.7)"
sx={{ textAlign: "center", mt: 4 }}
>
Предпросмотр статьи появится здесь
</Typography>
)}
</Box>
</>
)}
</Box>
</Paper>
</DialogContent>
<DialogActions sx={{ p: 2 }}>
<Button onClick={onClose}>Отмена</Button>
<Button
variant="contained"
onClick={() =>
selectedArticleId && onSelectArticle(selectedArticleId)
}
disabled={!selectedArticleId || isLoading}
>
Выбрать
</Button>
</DialogActions>
</Dialog>
);
}
);