303 lines
9.7 KiB
TypeScript
303 lines
9.7 KiB
TypeScript
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>
|
||
);
|
||
}
|
||
);
|