"use client"; import React, { useState, useEffect, useRef } from "react"; import { useTranslation } from "@/lib/i18n"; export default function NoteForm({ projectId, onNoteAdded }) { const { t } = useTranslation(); const [note, setNote] = useState(""); const [status, setStatus] = useState(null); const [projects, setProjects] = useState([]); const [showDropdown, setShowDropdown] = useState(false); const [filteredProjects, setFilteredProjects] = useState([]); const [cursorPosition, setCursorPosition] = useState(0); const [triggerChar, setTriggerChar] = useState(null); const [selectedIndex, setSelectedIndex] = useState(0); const textareaRef = useRef(null); useEffect(() => { async function fetchProjects() { try { const res = await fetch("/api/projects"); if (res.ok) { const data = await res.json(); setProjects(data); } } catch (error) { console.error("Failed to fetch projects:", error); } } fetchProjects(); }, []); useEffect(() => { if (note && cursorPosition > 0) { const beforeCursor = note.slice(0, cursorPosition); const match = beforeCursor.match(/([@#])([^@#]*)$/); if (match) { const char = match[1]; const query = match[2].toLowerCase(); setTriggerChar(char); const filtered = projects.filter(project => project.project_name.toLowerCase().includes(query) ); setFilteredProjects(filtered); setShowDropdown(filtered.length > 0); setSelectedIndex(0); } else { setShowDropdown(false); } } else { setShowDropdown(false); } }, [note, cursorPosition, projects]); async function handleSubmit(e) { e.preventDefault(); const res = await fetch("/api/notes", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ project_id: projectId, note }), }); if (res.ok) { const newNote = await res.json(); setNote(""); setStatus(t("common.addNoteSuccess")); // Call the callback to add the new note to the parent component's state if (onNoteAdded) { onNoteAdded(newNote); } // Clear status message after 3 seconds setTimeout(() => setStatus(null), 3000); } else { setStatus(t("common.addNoteError")); } } function handleKeyDown(e) { if (e.ctrlKey && e.key === 'Enter') { e.preventDefault(); handleSubmit(e); } if (showDropdown) { if (e.key === 'ArrowDown') { e.preventDefault(); setSelectedIndex((prev) => (prev + 1) % filteredProjects.length); } else if (e.key === 'ArrowUp') { e.preventDefault(); setSelectedIndex((prev) => (prev - 1 + filteredProjects.length) % filteredProjects.length); } else if (e.key === 'Enter') { e.preventDefault(); if (filteredProjects.length > 0) { selectProject(filteredProjects[selectedIndex]); } } else if (e.key === 'Escape') { setShowDropdown(false); } } } function handleInputChange(e) { setNote(e.target.value); setCursorPosition(e.target.selectionStart); } function handleInputBlur() { // Delay hiding dropdown to allow for clicks on dropdown items setTimeout(() => setShowDropdown(false), 150); } function selectProject(project) { const beforeCursor = note.slice(0, cursorPosition); const afterCursor = note.slice(cursorPosition); const match = beforeCursor.match(/([@#])([^@#]*)$/); if (match) { const start = match.index; const end = cursorPosition; const link = `[${triggerChar}${project.project_name}](/projects/${project.project_id})`; const newNote = note.slice(0, start) + link + afterCursor; setNote(newNote); setShowDropdown(false); // Set cursor after the inserted link setTimeout(() => { textareaRef.current.focus(); textareaRef.current.setSelectionRange(start + link.length, start + link.length); }, 0); } } return (
); }