ocd-website/frontend/src/hooks/useDocuments.ts
TheMaddax ea521e92c6 This commit simplifies the document management system to improve usability and user experience:
- Model changes:
  - Remove 'category' field from Document model
  - Replace complex 'status' field with simple 'isPublic' boolean flag
  - Enhance document types to be more specific and user-friendly

- UI improvements:
  - Update DocumentForm component to use simplified model
  - Improve document listing page with clearer public/private status
  - Enhance document detail page with better PDF preview using <object> tag
  - Fix document edit page to work with new model

- API fixes:
  - Fix uploads API route to properly handle PDF files
  - Add proper content types for various document formats
  - Fix params.path handling to prevent errors

- UX enhancements:
  - Simplify form with clearer options
  - Make public/private toggle more intuitive
  - Improve document preview to avoid unwanted downloads
  - Enhance file type handling for better compatibility

- Documentation:
  - Update memory bank (activeContext.md and progress.md)

This change improves the overall user experience by making the document management
system more intuitive while maintaining all necessary functionality.
2025-03-26 18:45:32 -05:00

300 lines
8.7 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
import axios from 'axios';
// Define Document interface
export interface Document {
_id: string;
title: string;
description: string;
filePath: string;
originalFilename: string;
fileType: string;
fileSize: number;
documentType: 'meeting_minutes' | 'board_meeting_minutes' | 'committee_meeting_minutes' | 'annual_meeting_minutes' |
'bylaws' | 'financial_report' | 'annual_report' | 'board_report' | 'committee_report' |
'meeting_agenda' | 'board_meeting_agenda' | 'committee_meeting_agenda' | 'program_document';
isPublic: boolean;
meetingDate?: Date;
uploadDate: Date;
lastModified: Date;
accessibilityChecked: boolean;
hasTextVersion: boolean;
textVersionPath?: string;
createdAt: string;
updatedAt: string;
}
// Define filter interface
export interface DocumentFilter {
documentType?: string;
search?: string;
isPublic?: boolean;
page?: number;
limit?: number;
}
// Define pagination interface
export interface Pagination {
total: number;
page: number;
limit: number;
pages: number;
}
// Define hook return interface
export interface UseDocumentsReturn {
documents: Document[];
loading: boolean;
error: Error | null;
pagination: Pagination | null;
fetchDocuments: (filters?: DocumentFilter) => Promise<void>;
getDocumentById: (id: string) => Promise<Document | null>;
createDocument: (formData: FormData) => Promise<Document | null>;
updateDocument: (id: string, documentData: Partial<Document>) => Promise<Document | null>;
deleteDocument: (id: string) => Promise<boolean>;
uploadTextVersion: (id: string, formData: FormData) => Promise<Document | null>;
updatePublicStatus: (id: string, isPublic: boolean) => Promise<Document | null>;
markAccessibilityChecked: (id: string, checked: boolean) => Promise<Document | null>;
}
// API base URL
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000/api';
/**
* Hook for managing documents data
*/
export function useDocuments(): UseDocumentsReturn {
const [documents, setDocuments] = useState<Document[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [error, setError] = useState<Error | null>(null);
const [pagination, setPagination] = useState<Pagination | null>(null);
/**
* Fetch documents with optional filtering
*/
const fetchDocuments = useCallback(async (filters?: DocumentFilter) => {
setLoading(true);
setError(null);
try {
// Build query string from filters
const queryParams = new URLSearchParams();
if (filters) {
Object.entries(filters).forEach(([key, value]) => {
if (value !== undefined && value !== null && value !== '') {
queryParams.append(key, String(value));
}
});
}
const response = await axios.get(`${API_URL}/documents?${queryParams.toString()}`);
setDocuments(response.data.documents);
setPagination(response.data.pagination);
} catch (err) {
console.error('Error fetching documents:', err);
setError(err instanceof Error ? err : new Error('Failed to fetch documents'));
} finally {
setLoading(false);
}
}, []);
/**
* Get a single document by ID
*/
const getDocumentById = useCallback(async (id: string): Promise<Document | null> => {
try {
const response = await axios.get(`${API_URL}/documents/${id}`);
return response.data.document;
} catch (err) {
console.error(`Error fetching document with ID ${id}:`, err);
return null;
}
}, []);
/**
* Create a new document
*/
const createDocument = useCallback(async (formData: FormData): Promise<Document | null> => {
try {
const response = await axios.post(`${API_URL}/documents`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
});
// Refresh documents list after creating
fetchDocuments();
return response.data.document;
} catch (err) {
console.error('Error creating document:', err);
// Rethrow the error so it can be caught and handled by the component
throw err;
}
}, [fetchDocuments]);
/**
* Update an existing document
*/
const updateDocument = useCallback(async (id: string, documentData: Partial<Document>): Promise<Document | null> => {
try {
const response = await axios.put(`${API_URL}/documents/${id}`, documentData, {
headers: {
'Content-Type': 'application/json',
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
});
// Update local state
setDocuments(prevDocuments =>
prevDocuments.map(document =>
document._id === id ? { ...document, ...response.data.document } : document
)
);
return response.data.document;
} catch (err) {
console.error(`Error updating document with ID ${id}:`, err);
// Rethrow the error so it can be caught and handled by the component
throw err;
}
}, []);
/**
* Delete a document
*/
const deleteDocument = useCallback(async (id: string): Promise<boolean> => {
try {
await axios.delete(`${API_URL}/documents/${id}`, {
headers: {
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
});
// Update local state
setDocuments(prevDocuments => prevDocuments.filter(document => document._id !== id));
return true;
} catch (err) {
console.error(`Error deleting document with ID ${id}:`, err);
return false;
}
}, []);
/**
* Upload text version for a document
*/
const uploadTextVersion = useCallback(async (id: string, formData: FormData): Promise<Document | null> => {
try {
const response = await axios.post(
`${API_URL}/documents/${id}/textversion`,
formData,
{
headers: {
'Content-Type': 'multipart/form-data',
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
}
);
// Update local state
setDocuments(prevDocuments =>
prevDocuments.map(document =>
document._id === id ? { ...document, ...response.data.document } : document
)
);
return response.data.document;
} catch (err) {
console.error(`Error uploading text version for document with ID ${id}:`, err);
return null;
}
}, []);
/**
* Update document public status
*/
const updatePublicStatus = useCallback(async (id: string, isPublic: boolean): Promise<Document | null> => {
try {
const response = await axios.put(
`${API_URL}/documents/${id}/public`,
{ isPublic },
{
headers: {
'Content-Type': 'application/json',
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
}
);
// Update local state
setDocuments(prevDocuments =>
prevDocuments.map(document =>
document._id === id ? { ...document, ...response.data.document } : document
)
);
return response.data.document;
} catch (err) {
console.error(`Error updating public status for document with ID ${id}:`, err);
return null;
}
}, []);
/**
* Mark document as accessibility checked
*/
const markAccessibilityChecked = useCallback(async (id: string, accessibilityChecked: boolean): Promise<Document | null> => {
try {
const response = await axios.put(
`${API_URL}/documents/${id}/accessibility`,
{ accessibilityChecked },
{
headers: {
'Content-Type': 'application/json',
// Add authorization header if needed
// 'Authorization': `Bearer ${token}`
}
}
);
// Update local state
setDocuments(prevDocuments =>
prevDocuments.map(document =>
document._id === id ? { ...document, ...response.data.document } : document
)
);
return response.data.document;
} catch (err) {
console.error(`Error marking document with ID ${id} as accessibility checked:`, err);
return null;
}
}, []);
// Load documents on initial render
useEffect(() => {
fetchDocuments();
}, [fetchDocuments]);
return {
documents,
loading,
error,
pagination,
fetchDocuments,
getDocumentById,
createDocument,
updateDocument,
deleteDocument,
uploadTextVersion,
updatePublicStatus,
markAccessibilityChecked
};
}