'use client'; import { useState, useEffect, useCallback } from 'react'; import { Document, Folder, DocumentType, DEFAULT_FOLDERS } from '@/types/documents'; const generateId = () => Math.random().toString(36).substring(2, 9); // Transform Supabase row to Document type const transformRowToDocument = (row: any): Document => ({ id: row.id, title: row.title, content: row.content, type: row.type as DocumentType, folder: row.folder || 'General', tags: row.tags || [], createdAt: row.created_at, updatedAt: row.updated_at, size: row.size || 0, description: row.description, }); const getInitialFolders = (): Folder[] => DEFAULT_FOLDERS.map((name) => ({ id: generateId(), name, count: 0, })); // Helper to fetch with timeout async function fetchWithTimeout(url: string, options: RequestInit = {}, timeoutMs = 10000) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetch(url, { ...options, signal: controller.signal, }); clearTimeout(timeoutId); return response; } catch (error) { clearTimeout(timeoutId); throw error; } } export function useDocuments() { const [documents, setDocuments] = useState([]); const [folders, setFolders] = useState([]); const [isLoaded, setIsLoaded] = useState(false); const [error, setError] = useState(null); // Load from API on mount useEffect(() => { let isMounted = true; async function loadDocuments() { try { console.log('[useDocuments] Starting fetch...'); const response = await fetchWithTimeout('/api/documents', { headers: { 'Cache-Control': 'no-cache', }, cache: 'no-store', }, 10000); console.log('[useDocuments] Response status:', response.status); // Try to parse JSON even if response is not ok (to get error details) let result; try { result = await response.json(); } catch (parseErr) { throw new Error(`Failed to parse response: ${response.statusText}`); } console.log('[useDocuments] API response:', result); if (!response.ok) { throw new Error(result.error || `HTTP error! status: ${response.status}`); } if (!isMounted) return; if (result.error) { console.error('[useDocuments] API returned error:', result.error); setError(result.error); setDocuments([]); } else if (result.documents && Array.isArray(result.documents)) { console.log(`[useDocuments] Loaded ${result.documents.length} documents`); setDocuments(result.documents.map(transformRowToDocument)); setError(null); } else { console.log('[useDocuments] No documents found or invalid format'); setDocuments([]); setError(null); } } catch (err: any) { console.error('[useDocuments] Exception:', err); if (!isMounted) return; if (err.name === 'AbortError') { setError('Request timed out. Please check your connection and try again.'); } else { setError(err.message || 'Failed to load documents'); } setDocuments([]); } finally { if (isMounted) { console.log('[useDocuments] Setting isLoaded to true'); setIsLoaded(true); } } } loadDocuments(); return () => { isMounted = false; }; }, []); // Initialize folders (local only for now) useEffect(() => { setFolders(getInitialFolders()); }, []); // Update folder counts whenever documents change useEffect(() => { setFolders((prev) => prev.map((folder) => ({ ...folder, count: documents.filter((d) => d.folder === folder.name).length, })) ); }, [documents]); const createDocument = useCallback( async (doc: Omit): Promise => { // TODO: Implement via API console.log('Create document not yet implemented'); return null; }, [] ); const updateDocument = useCallback( async (id: string, updates: Partial>): Promise => { // TODO: Implement via API console.log('Update document not yet implemented'); return null; }, [] ); const deleteDocument = useCallback(async (id: string): Promise => { try { const response = await fetchWithTimeout(`/api/documents?id=${encodeURIComponent(id)}`, { method: 'DELETE', }, 10000); let result: any = null; try { result = await response.json(); } catch { // Best effort parse; fall back to status text below. } if (!response.ok) { throw new Error(result?.error || `HTTP error! status: ${response.status}`); } setDocuments((prev) => prev.filter((doc) => doc.id !== id)); setError(null); return true; } catch (err: any) { console.error('[useDocuments] Delete failed:', err); setError(err?.message || 'Failed to delete document'); return false; } }, []); const createFolder = useCallback((name: string): Folder => { const newFolder: Folder = { id: generateId(), name, count: 0, }; setFolders((prev) => [...prev, newFolder]); return newFolder; }, []); const deleteFolder = useCallback( async (folderId: string): Promise => { const folder = folders.find((f) => f.id === folderId); if (!folder) return false; setFolders((prev) => prev.filter((f) => f.id !== folderId)); return true; }, [folders] ); const getDocumentById = useCallback( (id: string): Document | undefined => { return documents.find((d) => d.id === id); }, [documents] ); const getRecentDocuments = useCallback( (limit = 5): Document[] => { return [...documents] .sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()) .slice(0, limit); }, [documents] ); const getAllTags = useCallback((): string[] => { const tags = new Set(); documents.forEach((doc) => doc.tags.forEach((tag) => tags.add(tag))); return Array.from(tags).sort(); }, [documents]); // Refresh documents from API const refreshDocuments = useCallback(async () => { setIsLoaded(false); // Re-trigger the effect by toggling a state (simplified) window.location.reload(); }, []); return { documents, folders, isLoaded, error, createDocument, updateDocument, deleteDocument, createFolder, deleteFolder, getDocumentById, getRecentDocuments, getAllTags, refreshDocuments, }; }