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
198 lines
6.5 KiB
TypeScript
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;
|