"use client"; import React, { useState, useEffect, useRef } from 'react'; import { Image, FileText, Link, List, Plus } from 'lucide-react'; import { useRouter } from 'next/navigation'; import { useSession } from 'next-auth/react'; import { noteContentCache } from '@/lib/cache-utils'; import { HealthForm } from './health-form'; interface Note { id: string; title: string; content: string; lastModified: string; type: string; mime: string; etag: string; } interface EditorProps { note?: Note | null; onSave?: (note: Note) => void; currentFolder?: string; onRefresh?: () => void; } export const Editor: React.FC = ({ note, onSave, currentFolder = 'Notes', onRefresh }) => { const [title, setTitle] = useState(note?.title || ''); const [content, setContent] = useState(note?.content || ''); const [isSaving, setIsSaving] = useState(false); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); const saveTimeout = useRef(); const router = useRouter(); const { data: session, status } = useSession(); useEffect(() => { // Redirect to login if not authenticated if (status === 'unauthenticated') { router.push('/signin'); } }, [status, router]); useEffect(() => { const fetchNoteContent = async () => { if (note?.id) { // Skip fetching if id is temporary (starts with 'temp-') // Temporary ids are used for new notes that haven't been saved yet if (note.id.startsWith('temp-')) { console.log(`Skipping fetch for temporary note id: ${note.id}`); setIsLoading(false); setContent(note.content || ''); return; } // If content is already provided (e.g., for mission files), use it directly if (note.content !== undefined && note.content !== '') { setContent(note.content); setIsLoading(false); return; } setIsLoading(true); setError(null); // Check cache first const cachedContent = noteContentCache.get(note.id); if (cachedContent) { console.log(`Using cached content for note ${note.title}`); setContent(cachedContent); setIsLoading(false); return; } // For mission files, don't try to fetch via storage API if (note.id.startsWith('missions/')) { console.warn('Mission file content should be provided directly, not fetched'); setIsLoading(false); return; } // If cache miss, fetch from API try { const response = await fetch(`/api/storage/files/content?path=${encodeURIComponent(note.id)}`); if (response.status === 401) { console.error('Authentication error, redirecting to login'); router.push('/signin'); return; } if (response.status === 404) { // File not found - this is normal for new notes that haven't been saved yet console.log(`Note not found (404) for id: ${note.id}, treating as new note`); setContent(''); setIsLoading(false); return; } if (response.status === 403) { // Unauthorized access - don't show error, just clear content console.warn(`Unauthorized access to note: ${note.id}`); setContent(''); setError(null); // Don't show error for unauthorized access setIsLoading(false); return; } if (!response.ok) { const errorData = await response.json().catch(() => ({})); const errorMessage = errorData.message || errorData.error || `Failed to fetch note content: ${response.status}`; // Only show error for non-404/403 errors if (response.status !== 404 && response.status !== 403) { throw new Error(errorMessage); } else { setContent(''); setIsLoading(false); return; } } const data = await response.json(); setContent(data.content || ''); // Update cache noteContentCache.set(note.id, data.content || ''); } catch (error) { console.error('Error fetching note content:', error); // Only show error if it's not a 404 or 403 (which we handle above) const errorMessage = error instanceof Error ? error.message : 'Failed to load note content. Please try again later.'; // Don't show error for file not found or unauthorized - these are handled above if (!errorMessage.includes('404') && !errorMessage.includes('403') && !errorMessage.includes('File not found') && !errorMessage.includes('Unauthorized')) { setError(errorMessage); } else { setError(null); setContent(''); } } finally { setIsLoading(false); } } }; if (note) { // For Diary and Health, if it's a new note (no id), set title to today's date if ((currentFolder === 'Diary' || currentFolder === 'Health') && !note.id) { const today = new Date(); // Use French locale for date formatting: "16 janvier 2026" const dateStr = today.toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric' }); setTitle(dateStr); } else { setTitle(note.title || ''); } // Set content from note if provided, otherwise it will be fetched if (note.content !== undefined) { setContent(note.content); } if (note.id) { fetchNoteContent(); } else { if (note.content === undefined) { setContent(''); } } } else { setTitle(''); setContent(''); } }, [note, router, currentFolder]); const handleTitleChange = (e: React.ChangeEvent) => { setTitle(e.target.value); debouncedSave(); }; const handleContentChange = (e: React.ChangeEvent) => { setContent(e.target.value); debouncedSave(); }; // Handle content change from HealthForm (direct string update) const handleHealthContentChange = (newContent: string) => { setContent(newContent); // For Health folder, pass the newContent directly to debouncedSave to ensure we use the latest content // This is important because setContent is async and content state might not be updated when debouncedSave fires debouncedSave(newContent); }; const debouncedSave = (contentToSave?: string) => { if (saveTimeout.current) { clearTimeout(saveTimeout.current); } saveTimeout.current = setTimeout(() => { console.log(`[Editor] debouncedSave triggered for folder: ${currentFolder}`); // Use the provided contentToSave if available, otherwise use state content handleSave(contentToSave); }, 1000); // Save after 1 second of inactivity }; const handleSave = async (contentToSave?: string) => { // Use the provided contentToSave if available, otherwise use state content // This ensures we use the latest content from HealthForm even if state hasn't updated yet const contentToUse = contentToSave !== undefined ? contentToSave : content; console.log(`[Editor] handleSave called - folder: ${currentFolder}, title: "${title}", content length: ${contentToUse?.length || 0}`); if (!title || !contentToUse) { console.log(`[Editor] handleSave: Skipping save - title: "${title}", content: "${contentToUse}"`); return; } if (!session) { console.error('No active session, cannot save'); setError('You must be logged in to save notes'); return; } // For Health folder, don't save if content is just empty JSON if (currentFolder === 'Health') { try { const parsed = JSON.parse(contentToUse); const isEmpty = Object.keys(parsed).length === 0 || (Object.keys(parsed).length === 1 && parsed.date); if (isEmpty) { console.log('[Editor] handleSave: Skipping save for empty Health form'); return; // Don't save empty health forms } } catch { // If not valid JSON, continue with save } } setIsSaving(true); setError(null); // Instead of saving directly, construct a Note object and pass it to onSave // This ensures handleSaveNote in page.tsx handles all the logic consistently try { // Construct a Note object with current state const noteToSave: Note = { id: note?.id || '', // Use existing note.id if available, otherwise empty (will be determined in handleSaveNote) title: title, // Use the title from local state content: contentToUse, // Use the provided content or state content lastModified: note?.lastModified || new Date().toISOString(), type: note?.type || 'file', mime: note?.mime || 'text/markdown', etag: note?.etag || '' }; console.log('[Editor] Calling onSave with note (delegating to handleSaveNote):', { ...noteToSave, content: `[${contentToUse.length} chars]` }); // Pass the note to onSave callback, which will call handleSaveNote in page.tsx // handleSaveNote will handle the actual API call with proper duplicate detection onSave?.(noteToSave); // Note: onRefresh is called, but handleSaveNote already calls fetchNotes // So we don't need to call onRefresh here to avoid double fetch // onRefresh?.(); } catch (error) { console.error('Error saving note:', error); } finally { setIsSaving(false); } }; if (!session && status !== 'loading') { return (

Authentication Required

Please log in to access your notes

); } if (!note) { return (

Sélectionnez une note

Choisissez une note existante ou créez-en une nouvelle

); } return (
{/* Title Bar */}
{/* Error Display */} {error && (
{error}
)} {/* Editor Area */}
{isLoading ? (
) : currentFolder === 'Health' ? ( // Use HealthForm for Health folder ) : (