import React, { useState, useEffect, useRef, useCallback } from 'react'; import { Note } from '../../entities/Note'; import { useToast } from '../Shared/ToastContext'; import TagInput from '../Tag/TagInput'; import MarkdownRenderer from '../Shared/MarkdownRenderer'; import { Tag } from '../../entities/Tag'; import { fetchTags } from '../../utils/tagsService'; import { useTranslation } from 'react-i18next'; import { EyeIcon, PencilIcon } from '@heroicons/react/24/outline'; interface NoteModalProps { isOpen: boolean; onClose: () => void; note?: Note | null; onSave: (noteData: Note) => Promise; } const NoteModal: React.FC = ({ isOpen, onClose, note, onSave }) => { const { t } = useTranslation(); const [formData, setFormData] = useState({ id: note?.id || 0, title: note?.title || '', content: note?.content || '', tags: note?.tags || [], }); const [tags, setTags] = useState(note?.tags?.map((tag) => tag.name) || []); const [availableTags, setAvailableTags] = useState([]); const [error, setError] = useState(null); const modalRef = useRef(null); const [isSubmitting, setIsSubmitting] = useState(false); const [isClosing, setIsClosing] = useState(false); const [activeTab, setActiveTab] = useState<'edit' | 'preview'>('edit'); const { showSuccessToast, showErrorToast } = useToast(); useEffect(() => { const loadTags = async () => { try { const data = await fetchTags(); setAvailableTags(data); } catch (error) { console.error('Failed to fetch tags', error); showErrorToast(t('errors.failedToLoadTags')); } }; if (isOpen) { loadTags(); } }, [isOpen, showErrorToast]); useEffect(() => { if (isOpen) { // Extract tag names for display const tagNames = note?.tags?.map((tag) => tag.name) || []; setFormData({ id: note?.id || 0, title: note?.title || '', content: note?.content || '', tags: note?.tags || [], }); setTags(tagNames); setError(null); } }, [isOpen, note]); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( modalRef.current && !modalRef.current.contains(event.target as Node) ) { handleClose(); } }; if (isOpen) { document.addEventListener('mousedown', handleClickOutside); } return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, [isOpen]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { if (event.key === 'Escape') { handleClose(); } }; if (isOpen) { document.addEventListener('keydown', handleKeyDown); } return () => { document.removeEventListener('keydown', handleKeyDown); }; }, [isOpen]); const handleChange = ( e: React.ChangeEvent ) => { const { name, value } = e.target; setFormData((prev) => ({ ...prev, [name]: value, })); }; const handleTagsChange = useCallback((newTags: string[]) => { setTags(newTags); setFormData((prev) => ({ ...prev, tags: newTags.map((name) => ({ name })), })); }, []); const handleSubmit = async () => { if (!formData.title.trim()) { setError(t('errors.noteTitleRequired')); return; } setIsSubmitting(true); setError(null); try { // Convert string tags to tag objects const noteTags: Tag[] = tags.map(tagName => ({ name: tagName })); // Create final form data with the tags const finalFormData = { ...formData, tags: noteTags }; await onSave(finalFormData); showSuccessToast(formData.id && formData.id !== 0 ? t('success.noteUpdated') : t('success.noteCreated')); handleClose(); } catch (err) { setError((err as Error).message); showErrorToast(t('errors.failedToSaveNote')); } finally { setIsSubmitting(false); } }; const handleClose = () => { setIsClosing(true); setTimeout(() => { onClose(); setIsClosing(false); }, 300); }; if (!isOpen) return null; return (
{activeTab === 'edit' ? (