import { Box, Button, Paper, Typography, Menu, MenuItem, TextField, } from "@mui/material"; import { BackButton, createSightStore, languageStore, SelectArticleModal, TabPanel, SelectMediaDialog, // Import UploadMediaDialog, Media, // Import } from "@shared"; import { LanguageSwitcher, MediaArea, // Import MediaAreaForSight, // Import ReactMarkdownComponent, ReactMarkdownEditor, DeleteModal, } from "@widgets"; 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"; import { DragDropContext, Droppable, Draggable } from "@hello-pangea/dnd"; type MediaItemShared = { // Define if not already available from @shared id: string; filename: string; media_name?: string; media_type: number; }; export const CreateRightTab = observer( ({ value, index }: { value: number; index: number }) => { const [anchorEl, setAnchorEl] = useState(null); const { sight, createNewRightArticle, updateRightArticleInfo, linkPreviewMedia, unlinkPreviewMedia, createLinkWithRightArticle, deleteRightArticleMedia, setFileToUpload, // From store setUploadMediaOpen, // From store uploadMediaOpen, // From store unlinkRightAritcle, // Corrected spelling deleteRightArticle, linkExistingRightArticle, createSight, clearCreateSight, // For resetting form updateRightArticles, } = createSightStore; const { language } = languageStore; const [selectArticleDialogOpen, setSelectArticleDialogOpen] = useState(false); const [activeArticleIndex, setActiveArticleIndex] = useState( 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(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 && activeArticleIndex >= sight[language].right.length ) { setActiveArticleIndex(null); setType("media"); // Default back to media preview if selected article disappears } }, [language, sight[language].right, activeArticleIndex]); const openMenu = Boolean(anchorEl); const handleClickMenu = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const handleCloseMenu = () => { setAnchorEl(null); }; const handleSave = async () => { try { await createSight(language); toast.success("Достопримечательность успешно создана!"); clearCreateSight(); // Reset form setActiveArticleIndex(null); setType("media"); // Potentially navigate away: history.push('/sights-list'); } catch (error) { console.error("Failed to save sight:", error); toast.error("Ошибка при создании достопримечательности."); } }; const handleDisplayArticleFromList = (idx: number) => { setActiveArticleIndex(idx); setType("article"); }; const handleCreateNewLocalArticle = async () => { handleCloseMenu(); try { const newArticleId = await createNewRightArticle(); // Automatically select the new article if ID is returned const newIndex = sight[language].right.findIndex( (a) => a.id === newArticleId ); if (newIndex > -1) { setActiveArticleIndex(newIndex); setType("article"); } else { // Fallback if findIndex fails (should not happen if store updates correctly) setActiveArticleIndex(sight[language].right.length - 1); setType("article"); } } catch (error) { toast.error("Не удалось создать новую статью."); } }; const handleSelectExistingArticleAndLink = async ( selectedArticleId: number ) => { try { await linkExistingRightArticle(selectedArticleId); setSelectArticleDialogOpen(false); // Close dialog const newIndex = sight[language].right.findIndex( (a) => a.id === selectedArticleId ); if (newIndex > -1) { setActiveArticleIndex(newIndex); setType("article"); } } catch (error) { toast.error("Не удалось привязать существующую статью."); } }; const currentRightArticle = activeArticleIndex !== null && sight[language].right[activeArticleIndex] ? sight[language].right[activeArticleIndex] : null; // Media Handling for Dialogs const handleOpenUploadMedia = () => { setUploadMediaOpen(true); }; const handleOpenSelectMediaDialog = ( target: "sightPreview" | "rightArticle" ) => { setMediaTarget(target); setIsSelectMediaDialogOpen(true); }; const handleMediaSelectedFromDialog = async (media: MediaItemShared) => { setIsSelectMediaDialogOpen(false); if (mediaTarget === "sightPreview") { await linkPreviewMedia(media.id); } else if (mediaTarget === "rightArticle" && currentRightArticle) { await createLinkWithRightArticle(media, currentRightArticle.id); } setMediaTarget(null); }; const handleUnlinkPreviewMedia = async () => { await unlinkPreviewMedia(); setPreviewMedia(null); }; const handleMediaUploaded = async (media: MediaItemShared) => { // After UploadMediaDialog finishes setUploadMediaOpen(false); setFileToUpload(null); if (mediaTarget === "sightPreview") { linkPreviewMedia(media.id); } else if (mediaTarget === "rightArticle" && currentRightArticle) { await createLinkWithRightArticle(media, currentRightArticle.id); } setMediaTarget(null); // Reset target }; const handleDragEnd = (result: any) => { const { source, destination } = result; // 1. Guard clause: If dropped outside any droppable area, do nothing. if (!destination) return; // Extract source and destination indices const sourceIndex = source.index; const destinationIndex = destination.index; // 2. Guard clause: If dropped in the same position, do nothing. if (sourceIndex === destinationIndex) return; // 3. Create a new array with reordered articles: // - Create a shallow copy of the current articles array. // This is important for immutability and triggering re-renders. const newRightArticles = [...sight[language].right]; // - Remove the dragged article from its original position. // `splice` returns an array of removed items, so we destructure the first (and only) one. const [movedArticle] = newRightArticles.splice(sourceIndex, 1); // - Insert the moved article into its new position. newRightArticles.splice(destinationIndex, 0, movedArticle); // 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); }; return ( {/* Left Column: Navigation & Article List */} { setType("media"); // setActiveArticleIndex(null); // Optional: deselect article when switching to general media view }} className={`w-full p-4 rounded-2xl cursor-pointer text-sm hover:bg-gray-300 transition-all duration-300 ${ type === "media" ? "bg-green-300 font-semibold" : "bg-green-200" }`} > Предпросмотр медиа {(provided) => ( {sight[language].right.length > 0 ? sight[language].right.map( (article, index) => ( {(provided, snapshot) => ( { handleDisplayArticleFromList( index ); setType("article"); }} > {article.heading} )} ) ) : null} {provided.placeholder} )} Создать новую { setSelectArticleDialogOpen(true); handleCloseMenu(); }} > Выбрать существующую статью {/* Main content area: Article Editor or Sight Media Preview */} {type === "article" && currentRightArticle ? ( activeArticleIndex !== null && updateRightArticleInfo( activeArticleIndex, language, e.target.value, currentRightArticle.body ) } variant="outlined" fullWidth /> activeArticleIndex !== null && updateRightArticleInfo( activeArticleIndex, language, currentRightArticle.heading, mdValue || "" ) } /> { if (files.length > 0) { setFileToUpload(files[0]); setMediaTarget("rightArticle"); handleOpenUploadMedia(); } }} deleteMedia={deleteRightArticleMedia} setSelectMediaDialogOpen={() => handleOpenSelectMediaDialog("rightArticle") } /> ) : type === "media" ? ( {sight.preview_media && ( <> {type === "media" && ( {previewMedia && ( <> )} {!previewMedia && ( { linkPreviewMedia(mediaId); }} onFilesDrop={() => {}} /> )} )} )} {!previewMedia && ( { linkPreviewMedia(mediaId); }} onFilesDrop={() => {}} /> )} ) : ( Выберите статью слева или секцию "Предпросмотр медиа" )} {/* Right Column: Live Preview */} {type === "article" && activeArticleIndex !== null && ( {sight[language].right[activeArticleIndex].media.length > 0 ? ( ) : ( )} {sight[language].right[activeArticleIndex].heading || "Выберите статью"} {sight[language].right[activeArticleIndex].body ? ( ) : ( Предпросмотр статьи появится здесь )} {sight[language].right.length > 0 && sight[language].right.map((article, index) => ( ))} )} {/* Sticky Save Button Footer */} {/* Modals */} setSelectArticleDialogOpen(false)} onSelectArticle={handleSelectExistingArticleAndLink} // Pass IDs of already linked/added right articles to exclude them from selection linkedArticleIds={sight[language].right.map((article) => article.id)} /> { setUploadMediaOpen(false); setFileToUpload(null); // Clear file if dialog is closed without upload setMediaTarget(null); }} afterUpload={handleMediaUploaded} // This will use the mediaTarget /> { setIsSelectMediaDialogOpen(false); setMediaTarget(null); }} onSelectMedia={handleMediaSelectedFromDialog} /> { try { await deleteRightArticle(currentRightArticle?.id || 0); setActiveArticleIndex(null); setType("media"); toast.success("Статья удалена"); } catch { toast.error("Не удалось удалить статью"); } }} onCancel={() => setIsDeleteModalOpen(false)} /> ); } );