tududi/app/frontend/components/Note/NoteModal.tsx
Chris Veleris dfcb97a355 Move to React
Add .gitignore

Removed node_modules from previous commit

Fix task modes

Fix task modes

Fix task modes

Remove node_modules

Update basic task modal

Add notes functionality

Improve UI

Setup views

Add scopes

Fix projects layout

Restructure

Fix rest of the UI issues

Cleanup old views

Add .env to .gitignore
2024-10-25 21:03:43 +03:00

198 lines
6.5 KiB
TypeScript

import React, { useState, useEffect, useRef, useCallback } from 'react';
import TagInput from '../../TagInput'; // Adjust the import path
import { Note } from '../../entities/Note'; // Import the centralized Note type
import { useDataContext } from '../../contexts/DataContext'; // Use DataContext
interface NoteModalProps {
isOpen: boolean;
onClose: () => void;
note?: Note | null; // If null, it's for new note creation
onSave?: (note: Note) => void; // Optional callback for saving
}
const NoteModal: React.FC<NoteModalProps> = ({ isOpen, onClose, note }) => {
const { createNote, updateNote } = useDataContext(); // Use create and update methods from DataContext
const [formData, setFormData] = useState<Note>(
note || {
title: '',
content: '',
tags: [],
}
);
const [availableTags, setAvailableTags] = useState<string[]>([]);
const [error, setError] = useState<string | null>(null);
const modalRef = useRef<HTMLDivElement>(null);
// Fetch available tags when the modal opens
useEffect(() => {
if (isOpen) {
const fetchAvailableTags = async () => {
try {
const response = await fetch('/api/tags', {
credentials: 'include',
headers: {
Accept: 'application/json',
},
});
if (response.ok) {
const data = await response.json();
setAvailableTags(data.map((tag: { name: string }) => tag.name));
} else {
console.error('Failed to fetch available tags');
}
} catch (err) {
console.error('Error fetching available tags:', err);
}
};
fetchAvailableTags();
}
}, [isOpen]);
// Close modal if clicked outside
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
onClose();
}
};
if (isOpen) {
document.addEventListener('mousedown', handleClickOutside);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
}, [isOpen, onClose]);
// Handle input change
const handleChange = (
e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
};
// Handle tags change
const handleTagsChange = useCallback((newTags: string[]) => {
setFormData((prev) => ({
...prev,
tags: newTags.map((tagName) => ({ id: null, name: tagName })),
}));
}, []);
// Handle form submit
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!formData.title.trim() || !formData.content.trim()) {
setError('Title and Content are required.');
return;
}
try {
if (note?.id) {
await updateNote(note.id, formData); // Call updateNote if editing
} else {
await createNote(formData); // Call createNote if creating
}
onClose(); // Close modal after saving
} catch (err) {
console.error('Error saving note:', err);
setError('Failed to save note.');
}
};
if (!isOpen) return null;
return (
<div className="fixed inset-0 flex items-center justify-center bg-gray-900 bg-opacity-80 z-50">
<div
ref={modalRef}
className="bg-white dark:bg-gray-800 rounded-lg shadow-lg w-full max-w-2xl mx-auto overflow-hidden"
>
<form onSubmit={handleSubmit}>
<fieldset>
<div className="p-4 space-y-4">
{/* Note Title */}
<div>
<label
htmlFor="noteTitle"
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Note Title
</label>
<input
type="text"
id="noteTitle"
name="title"
value={formData.title}
onChange={handleChange}
required
className="mt-1 block w-full border border-gray-300 dark:border-gray-700 rounded-md shadow-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="Enter note title"
/>
</div>
{/* Note Content */}
<div>
<label
htmlFor="noteContent"
className="block text-sm font-medium text-gray-700 dark:text-gray-300"
>
Content
</label>
<textarea
id="noteContent"
name="content"
value={formData.content}
onChange={handleChange}
required
rows={5}
className="mt-1 block w-full border border-gray-300 dark:border-gray-700 rounded-md shadow-sm px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 dark:text-gray-100"
placeholder="Enter note content"
/>
</div>
{/* Tags Input */}
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300">
Tags
</label>
<TagInput
initialTags={formData?.tags?.map((tag) => tag.name) || []}
onTagsChange={handleTagsChange}
availableTags={availableTags}
/>
</div>
{/* Error Message */}
{error && <div className="text-red-500 mb-4">{error}</div>}
</div>
{/* Modal Actions */}
<div className="flex justify-end items-center p-4 border-t border-gray-200 dark:border-gray-700">
<button
type="button"
onClick={onClose}
className="px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600 mr-2"
>
Cancel
</button>
<button
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600"
>
{note?.id ? 'Update Note' : 'Create Note'}
</button>
</div>
</fieldset>
</form>
</div>
</div>
);
};
export default NoteModal;