From 79bfae4ba873bcc3c5814999089c2db3911141bf Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Mon, 6 Oct 2025 00:43:59 +0200 Subject: [PATCH] Ajout de la visualisation des pdf dans le front --- src/frontend/README.md | 95 +++++++- src/frontend/src/App.jsx | 4 +- .../documents/DeleteConfirmModal.jsx | 68 ++++++ .../src/components/documents/DocumentCard.jsx | 193 +++++++++++++++ .../documents/DocumentDetailView.jsx | 164 +++++++++++++ .../components/documents/DocumentGallery.jsx | 180 ++++++++++++++ .../documents/EditDocumentModal.jsx | 225 ++++++++++++++++++ .../components/documents/ViewModeSwitcher.jsx | 46 ++++ src/frontend/src/hooks/useDocuments.js | 85 +++++++ src/frontend/src/pages/DocumentsPage.jsx | 21 ++ src/frontend/src/services/documentService.js | 90 +++++++ src/frontend/src/utils/mockData.js | 155 ++++++++++++ 12 files changed, 1318 insertions(+), 8 deletions(-) create mode 100644 src/frontend/src/components/documents/DeleteConfirmModal.jsx create mode 100644 src/frontend/src/components/documents/DocumentCard.jsx create mode 100644 src/frontend/src/components/documents/DocumentDetailView.jsx create mode 100644 src/frontend/src/components/documents/DocumentGallery.jsx create mode 100644 src/frontend/src/components/documents/EditDocumentModal.jsx create mode 100644 src/frontend/src/components/documents/ViewModeSwitcher.jsx create mode 100644 src/frontend/src/hooks/useDocuments.js create mode 100644 src/frontend/src/pages/DocumentsPage.jsx create mode 100644 src/frontend/src/services/documentService.js create mode 100644 src/frontend/src/utils/mockData.js diff --git a/src/frontend/README.md b/src/frontend/README.md index 7059a96..02c435c 100644 --- a/src/frontend/README.md +++ b/src/frontend/README.md @@ -1,12 +1,93 @@ -# React + Vite -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. +# MyDocManager Frontend -Currently, two official plugins are available: +## Overview +MyDocManager Frontend is a modern web application built with React and Vite that serves as the user interface for the MyDocManager document management system. The application provides a seamless experience for users to manage, process, and organize their documents with an intuitive and responsive interface. -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +## Project Structure +frontend/ +├── public/ # Public assets and static files +├── src/ # Source code +│ ├── assets/ # Icons, images, and other static assets +│ ├── components/ # Reusable UI components +│ │ ├── auth/ # Authentication-related components +│ │ └── common/ # Shared components (Header, Layout, etc.) +│ ├── contexts/ # React contexts for state management +│ ├── hooks/ # Custom React hooks +│ ├── pages/ # Page components representing full views +│ ├── services/ # API service interfaces +│ └── utils/ # Utility functions and helpers +├── Dockerfile # Container configuration for deployment +├── package.json # Dependencies and scripts +├── tailwind.config.js # Tailwind CSS configuration +└── vite.config.js # Vite bundler configuration -## Expanding the ESLint configuration -If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project. + +## Key Components + +### Authentication +- **AuthContext**: Provides authentication state and methods throughout the application +- **AuthLayout**: Layout wrapper specifically for authentication screens +- **LoginForm**: Form component for user authentication +- **ProtectedRoute**: Route guard that ensures authenticated access to protected pages + +### UI Components +- **Layout**: Main application layout structure with menu and content areas +- **Header**: Application header with navigation and user controls +- **Menu**: Side navigation menu with application links +- **ThemeSwitcher**: Toggle for switching between light and dark themes + +### Pages +- **LoginPage**: User authentication page +- **DashboardPage**: Main dashboard view for authenticated users + +### Services +- **authService**: Handles API communication for authentication operations +- **api**: Base API utility for making HTTP requests to the backend + +## Getting Started + +### Prerequisites +- Node.js (latest LTS version) +- npm or yarn package manager + +### Installation +1. Clone the repository +2. Navigate to the frontend directory +3. Install dependencies: +``` +npm install +``` + +### Development +Run the development server: +``` +npm run dev +``` +This will start the application in development mode at http://localhost:5173 + +### Building for Production +Create a production build: +``` +npm run build +``` + + +## Technologies +- React 19.1.1 +- Vite 7.1.2 +- Tailwind CSS 4.1.13 +- DaisyUI 5.1.24 +- React Router 7.9.3 +- Axios for API requests + +## Features +- Responsive design with Tailwind CSS +- Authentication and authorization +- Light/dark theme support +- Document management interface +- Secure API communication + +## Project Integration +This frontend application works in conjunction with the backend services and workers defined in other parts of the MyDocManager project to provide a complete document management solution. \ No newline at end of file diff --git a/src/frontend/src/App.jsx b/src/frontend/src/App.jsx index 0c9c15a..aca3153 100644 --- a/src/frontend/src/App.jsx +++ b/src/frontend/src/App.jsx @@ -4,6 +4,7 @@ import ProtectedRoute from './components/common/ProtectedRoute'; import Layout from './components/common/Layout'; import LoginPage from './pages/LoginPage'; import DashboardPage from './pages/DashboardPage'; +import DocumentsPage from './pages/DocumentsPage'; function App() { return ( @@ -16,7 +17,8 @@ function App() { {/* Protected Routes */} }> - } /> + } /> + } /> } /> Documents Page - Coming Soon} /> User Management - Coming Soon} /> diff --git a/src/frontend/src/components/documents/DeleteConfirmModal.jsx b/src/frontend/src/components/documents/DeleteConfirmModal.jsx new file mode 100644 index 0000000..121dde0 --- /dev/null +++ b/src/frontend/src/components/documents/DeleteConfirmModal.jsx @@ -0,0 +1,68 @@ +/** + * DeleteConfirmModal Component + * Modal dialog to confirm document deletion + */ + +import React from 'react'; + +/** + * DeleteConfirmModal component + * @param {Object} props + * @param {boolean} props.isOpen - Whether the modal is open + * @param {Object|null} props.document - Document to delete + * @param {function(): void} props.onClose - Callback when modal is closed + * @param {function(): void} props.onConfirm - Callback when deletion is confirmed + * @param {boolean} props.isDeleting - Whether deletion is in progress + * @returns {JSX.Element} + */ +const DeleteConfirmModal = ({ + isOpen, + document, + onClose, + onConfirm, + isDeleting = false + }) => { + if (!isOpen || !document) return null; + + return ( + +
+

Confirm Deletion

+

+ Are you sure you want to delete "{document.name}"? +

+

+ This action cannot be undone. +

+
+ + +
+
+
+ +
+
+ ); +}; + +export default DeleteConfirmModal; \ No newline at end of file diff --git a/src/frontend/src/components/documents/DocumentCard.jsx b/src/frontend/src/components/documents/DocumentCard.jsx new file mode 100644 index 0000000..426fafe --- /dev/null +++ b/src/frontend/src/components/documents/DocumentCard.jsx @@ -0,0 +1,193 @@ +/** + * DocumentCard Component + * Displays a document as a DaisyUI card with thumbnail and metadata + * Supports different view modes: small, large, and detail + */ + +import React, { memo } from 'react'; + +/** + * Formats file size to human-readable format + * @param {number} bytes - File size in bytes + * @returns {string} Formatted file size + */ +const formatFileSize = (bytes) => { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; +}; + +/** + * Formats date to localized string + * @param {string} dateString - ISO date string + * @returns {string} Formatted date + */ +const formatDate = (dateString) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); +}; + +/** + * DocumentCard component + * @param {Object} props + * @param {Object} props.document - Document object + * @param {'small'|'large'|'detail'} props.viewMode - Current view mode + * @param {function(): void} props.onEdit - Callback when edit is clicked + * @param {function(): void} props.onDelete - Callback when delete is clicked + * @returns {JSX.Element} + */ +const DocumentCard = memo(({ document, viewMode, onEdit, onDelete }) => { + const { name, originalFileType, thumbnailUrl, pageCount, fileSize, createdAt, tags, categories } = document; + + // Determine card classes based on view mode + const getCardClasses = () => { + const baseClasses = 'card bg-base-100 shadow-xl hover:shadow-2xl transition-shadow group relative'; + + switch (viewMode) { + case 'small': + return `${baseClasses} w-full`; + case 'large': + return `${baseClasses} w-full`; + case 'detail': + return `${baseClasses} w-full`; + default: + return baseClasses; + } + }; + + // Render thumbnail with hover actions + const renderThumbnail = () => ( +
+ {`${name} + + {/* Hover overlay with actions */} +
+ + +
+ + {/* File type badge */} +
+ {originalFileType} +
+
+ ); + + // Render card body based on view mode + const renderCardBody = () => { + if (viewMode === 'small') { + return ( +
+

{name}

+

{pageCount} page{pageCount > 1 ? 's' : ''}

+
+ ); + } + + if (viewMode === 'large') { + return ( +
+

{name}

+
+ {tags.slice(0, 3).map(tag => ( + {tag} + ))} + {tags.length > 3 && ( + +{tags.length - 3} + )} +
+
+

{pageCount} page{pageCount > 1 ? 's' : ''}

+

{formatFileSize(fileSize)}

+
+
+ ); + } + + // Detail mode + return ( +
+

{name}

+ + {/* Tags */} + {tags.length > 0 && ( +
+ {tags.map(tag => ( + {tag} + ))} +
+ )} + + {/* Categories */} + {categories.length > 0 && ( +
+ {categories.map(category => ( + {category} + ))} +
+ )} + + {/* Metadata */} +
+
+ Pages: + {pageCount} +
+
+ Size: + {formatFileSize(fileSize)} +
+
+ Type: + {originalFileType} +
+
+ Date: + {formatDate(createdAt)} +
+
+
+ ); + }; + + return ( +
+ {renderThumbnail()} + {renderCardBody()} +
+ ); +}); + +DocumentCard.displayName = 'DocumentCard'; + +export default DocumentCard; \ No newline at end of file diff --git a/src/frontend/src/components/documents/DocumentDetailView.jsx b/src/frontend/src/components/documents/DocumentDetailView.jsx new file mode 100644 index 0000000..712efee --- /dev/null +++ b/src/frontend/src/components/documents/DocumentDetailView.jsx @@ -0,0 +1,164 @@ +/** + * DocumentDetailView Component + * Displays a document in detail mode with all pages visible + * This is a placeholder that shows multiple page thumbnails + * When real PDF backend is ready, this can be replaced with actual PDF rendering + */ + +import React from 'react'; + +/** + * Formats file size to human-readable format + * @param {number} bytes - File size in bytes + * @returns {string} Formatted file size + */ +const formatFileSize = (bytes) => { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i]; +}; + +/** + * Formats date to localized string + * @param {string} dateString - ISO date string + * @returns {string} Formatted date + */ +const formatDate = (dateString) => { + return new Date(dateString).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); +}; + +/** + * DocumentDetailView component + * @param {Object} props + * @param {Object} props.document - Document object + * @param {function(): void} props.onEdit - Callback when edit is clicked + * @param {function(): void} props.onDelete - Callback when delete is clicked + * @returns {JSX.Element} + */ +const DocumentDetailView = ({ document, onEdit, onDelete }) => { + const { + name, + originalFileType, + thumbnailUrl, + pageCount, + fileSize, + createdAt, + tags, + categories + } = document; + + // Generate placeholder pages (in real implementation, these would be actual PDF pages) + const pages = Array.from({ length: pageCount }, (_, i) => ({ + pageNumber: i + 1, + thumbnailUrl: thumbnailUrl.replace('Page+1', `Page+${i + 1}`) + })); + + return ( +
+ {/* Header with actions */} +
+
+
+

{name}

+ + {/* Tags */} + {tags.length > 0 && ( +
+ Tags: + {tags.map(tag => ( + {tag} + ))} +
+ )} + + {/* Categories */} + {categories.length > 0 && ( +
+ Categories: + {categories.map(category => ( + {category} + ))} +
+ )} +
+ + {/* Action buttons */} +
+ + +
+
+ + {/* Metadata grid */} +
+
+ Original Type +

{originalFileType}

+
+
+ Pages +

{pageCount}

+
+
+ File Size +

{formatFileSize(fileSize)}

+
+
+ Created +

{formatDate(createdAt)}

+
+
+ + {/* Pages preview */} +
+

Document Pages ({pageCount})

+
+ {pages.map((page) => ( +
+
+ {`Page +
+
+ Page {page.pageNumber} +
+
+ ))} +
+
+
+
+ ); +}; + +export default DocumentDetailView; \ No newline at end of file diff --git a/src/frontend/src/components/documents/DocumentGallery.jsx b/src/frontend/src/components/documents/DocumentGallery.jsx new file mode 100644 index 0000000..8a5f0a3 --- /dev/null +++ b/src/frontend/src/components/documents/DocumentGallery.jsx @@ -0,0 +1,180 @@ +/** + * DocumentGallery Component + * Main container for displaying documents in different view modes + */ + +import React, { useState } from 'react'; +import DocumentCard from './DocumentCard'; +import DocumentDetailView from './DocumentDetailView'; +import ViewModeSwitcher from './ViewModeSwitcher'; +import EditDocumentModal from './EditDocumentModal'; +import DeleteConfirmModal from './DeleteConfirmModal'; +import { useDocuments } from '../../hooks/useDocuments'; + +/** + * DocumentGallery component + * @returns {JSX.Element} + */ +const DocumentGallery = () => { + const { documents, loading, error, updateDocument, deleteDocument } = useDocuments(); + const [viewMode, setViewMode] = useState('large'); + const [editingDocument, setEditingDocument] = useState(null); + const [deletingDocument, setDeletingDocument] = useState(null); + const [isSaving, setIsSaving] = useState(false); + const [isDeleting, setIsDeleting] = useState(false); + + /** + * Handles opening the edit modal + * @param {Object} document - Document to edit + */ + const handleEditClick = (document) => { + setEditingDocument(document); + }; + + /** + * Handles opening the delete confirmation modal + * @param {Object} document - Document to delete + */ + const handleDeleteClick = (document) => { + setDeletingDocument(document); + }; + + /** + * Handles saving document changes + * @param {Object} updates - Updates object with tags and categories + */ + const handleSaveEdit = async (updates) => { + if (!editingDocument) return; + + setIsSaving(true); + const success = await updateDocument(editingDocument.id, updates); + setIsSaving(false); + + if (success) { + setEditingDocument(null); + } + }; + + /** + * Handles confirming document deletion + */ + const handleConfirmDelete = async () => { + if (!deletingDocument) return; + + setIsDeleting(true); + const success = await deleteDocument(deletingDocument.id); + setIsDeleting(false); + + if (success) { + setDeletingDocument(null); + } + }; + + /** + * Gets grid classes based on view mode + * @returns {string} Tailwind CSS classes + */ + const getGridClasses = () => { + switch (viewMode) { + case 'small': + return 'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4'; + case 'large': + return 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6'; + case 'detail': + return 'flex flex-col gap-6'; + default: + return 'grid grid-cols-1 gap-4'; + } + }; + + // Loading state + if (loading) { + return ( +
+ +
+ ); + } + + // Error state + if (error) { + return ( +
+ + + + Error loading documents: {error} +
+ ); + } + + // Empty state + if (documents.length === 0) { + return ( +
+ + + +

No documents yet

+

Upload your first document to get started

+
+ ); + } + + return ( +
+ {/* Header with view mode switcher */} +
+
+

Documents

+

{documents.length} document{documents.length !== 1 ? 's' : ''}

+
+ +
+ + {/* Document grid/list */} +
+ {documents.map(document => ( + viewMode === 'detail' ? ( + handleEditClick(document)} + onDelete={() => handleDeleteClick(document)} + /> + ) : ( + handleEditClick(document)} + onDelete={() => handleDeleteClick(document)} + /> + ) + ))} +
+ + {/* Modals */} + setEditingDocument(null)} + onSave={handleSaveEdit} + isSaving={isSaving} + /> + + setDeletingDocument(null)} + onConfirm={handleConfirmDelete} + isDeleting={isDeleting} + /> +
+ ); +}; + +export default DocumentGallery; \ No newline at end of file diff --git a/src/frontend/src/components/documents/EditDocumentModal.jsx b/src/frontend/src/components/documents/EditDocumentModal.jsx new file mode 100644 index 0000000..f813daf --- /dev/null +++ b/src/frontend/src/components/documents/EditDocumentModal.jsx @@ -0,0 +1,225 @@ +/** + * EditDocumentModal Component + * Modal dialog for editing document tags and categories + */ + +import React, { useState, useEffect } from 'react'; +import { getAvailableTags, getAvailableCategories } from '../../services/documentService'; + +/** + * EditDocumentModal component + * @param {Object} props + * @param {boolean} props.isOpen - Whether the modal is open + * @param {Object|null} props.document - Document to edit + * @param {function(): void} props.onClose - Callback when modal is closed + * @param {function(Object): void} props.onSave - Callback when changes are saved + * @param {boolean} props.isSaving - Whether save is in progress + * @returns {JSX.Element} + */ +const EditDocumentModal = ({ + isOpen, + document, + onClose, + onSave, + isSaving = false +}) => { + const [selectedTags, setSelectedTags] = useState([]); + const [selectedCategories, setSelectedCategories] = useState([]); + const [availableTags, setAvailableTags] = useState([]); + const [availableCategories, setAvailableCategories] = useState([]); + const [newTag, setNewTag] = useState(''); + const [newCategory, setNewCategory] = useState(''); + + // Load available tags and categories + useEffect(() => { + const loadOptions = async () => { + const [tags, categories] = await Promise.all([ + getAvailableTags(), + getAvailableCategories() + ]); + setAvailableTags(tags); + setAvailableCategories(categories); + }; + loadOptions(); + }, []); + + // Initialize selected values when document changes + useEffect(() => { + if (document) { + setSelectedTags(document.tags || []); + setSelectedCategories(document.categories || []); + } + }, [document]); + + const handleAddTag = (tag) => { + if (tag && !selectedTags.includes(tag)) { + setSelectedTags([...selectedTags, tag]); + } + setNewTag(''); + }; + + const handleRemoveTag = (tag) => { + setSelectedTags(selectedTags.filter(t => t !== tag)); + }; + + const handleAddCategory = (category) => { + if (category && !selectedCategories.includes(category)) { + setSelectedCategories([...selectedCategories, category]); + } + setNewCategory(''); + }; + + const handleRemoveCategory = (category) => { + setSelectedCategories(selectedCategories.filter(c => c !== category)); + }; + + const handleSave = () => { + onSave({ + tags: selectedTags, + categories: selectedCategories + }); + }; + + if (!isOpen || !document) return null; + + return ( + +
+

Edit Document

+ +
+

+ Document: {document.name} +

+
+ + {/* Tags Section */} +
+ + + {/* Selected Tags */} +
+ {selectedTags.map(tag => ( +
+ {tag} + +
+ ))} +
+ + {/* Add Tag */} +
+ + +
+
+ + {/* Categories Section */} +
+ + + {/* Selected Categories */} +
+ {selectedCategories.map(category => ( +
+ {category} + +
+ ))} +
+ + {/* Add Category */} +
+ + +
+
+ +
+ + +
+
+
+ +
+
+ ); +}; + +export default EditDocumentModal; \ No newline at end of file diff --git a/src/frontend/src/components/documents/ViewModeSwitcher.jsx b/src/frontend/src/components/documents/ViewModeSwitcher.jsx new file mode 100644 index 0000000..9aefc5e --- /dev/null +++ b/src/frontend/src/components/documents/ViewModeSwitcher.jsx @@ -0,0 +1,46 @@ +/** + * ViewModeSwitcher Component + * Allows users to switch between different view modes (small, large, detail) + */ + +import React from 'react'; + +/** + * @typedef {'small' | 'large' | 'detail'} ViewMode + */ + +/** + * ViewModeSwitcher component + * @param {Object} props + * @param {ViewMode} props.currentMode - Current active view mode + * @param {function(ViewMode): void} props.onModeChange - Callback when mode changes + * @returns {JSX.Element} + */ +const ViewModeSwitcher = ({ currentMode, onModeChange }) => { + const modes = [ + { id: 'small', label: 'Small', icon: '⊞' }, + { id: 'large', label: 'Large', icon: '⊡' }, + { id: 'detail', label: 'Detail', icon: '☰' } + ]; + + return ( +
+ {modes.map(mode => ( + + ))} +
+ ); +}; + +export default ViewModeSwitcher; \ No newline at end of file diff --git a/src/frontend/src/hooks/useDocuments.js b/src/frontend/src/hooks/useDocuments.js new file mode 100644 index 0000000..42b4870 --- /dev/null +++ b/src/frontend/src/hooks/useDocuments.js @@ -0,0 +1,85 @@ +/** + * Custom hook for managing documents + * Handles fetching, updating, and deleting documents + */ + +import { useState, useEffect, useCallback } from 'react'; +import * as documentService from '../services/documentService'; + +/** + * Hook for managing documents state and operations + * @returns {Object} Documents state and operations + */ +export const useDocuments = () => { + const [documents, setDocuments] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + /** + * Fetches all documents from the service + */ + const fetchDocuments = useCallback(async () => { + try { + setLoading(true); + setError(null); + const data = await documentService.getAllDocuments(); + setDocuments(data); + } catch (err) { + setError(err.message); + console.error('Error fetching documents:', err); + } finally { + setLoading(false); + } + }, []); + + /** + * Updates a document's tags and categories + * @param {string} id - Document ID + * @param {Object} updates - Updates object + * @returns {Promise} Success status + */ + const updateDocument = useCallback(async (id, updates) => { + try { + const updatedDoc = await documentService.updateDocument(id, updates); + setDocuments(prevDocs => + prevDocs.map(doc => (doc.id === id ? updatedDoc : doc)) + ); + return true; + } catch (err) { + setError(err.message); + console.error('Error updating document:', err); + return false; + } + }, []); + + /** + * Deletes a document + * @param {string} id - Document ID + * @returns {Promise} Success status + */ + const deleteDocument = useCallback(async (id) => { + try { + await documentService.deleteDocument(id); + setDocuments(prevDocs => prevDocs.filter(doc => doc.id !== id)); + return true; + } catch (err) { + setError(err.message); + console.error('Error deleting document:', err); + return false; + } + }, []); + + // Fetch documents on mount + useEffect(() => { + fetchDocuments(); + }, [fetchDocuments]); + + return { + documents, + loading, + error, + fetchDocuments, + updateDocument, + deleteDocument + }; +}; \ No newline at end of file diff --git a/src/frontend/src/pages/DocumentsPage.jsx b/src/frontend/src/pages/DocumentsPage.jsx new file mode 100644 index 0000000..4647e90 --- /dev/null +++ b/src/frontend/src/pages/DocumentsPage.jsx @@ -0,0 +1,21 @@ +/** + * DocumentsPage Component + * Main page for displaying and managing documents + */ + +import React from 'react'; +import DocumentGallery from '../components/documents/DocumentGallery'; + +/** + * DocumentsPage component + * @returns {JSX.Element} + */ +const DocumentsPage = () => { + return ( +
+ +
+ ); +}; + +export default DocumentsPage; \ No newline at end of file diff --git a/src/frontend/src/services/documentService.js b/src/frontend/src/services/documentService.js new file mode 100644 index 0000000..6464089 --- /dev/null +++ b/src/frontend/src/services/documentService.js @@ -0,0 +1,90 @@ +/** + * Document Service + * Handles all API calls related to documents + * Currently using mock data for development + */ + +import { mockDocuments, availableTags, availableCategories } from '../utils/mockData'; + +// Simulate network delay +const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +/** + * Fetches all documents + * @returns {Promise} Array of document objects + */ +export const getAllDocuments = async () => { + await delay(500); // Simulate network latency + return [...mockDocuments]; +}; + +/** + * Fetches a single document by ID + * @param {string} id - Document ID + * @returns {Promise} Document object or null if not found + */ +export const getDocumentById = async (id) => { + await delay(300); + const document = mockDocuments.find(doc => doc.id === id); + return document || null; +}; + +/** + * Updates a document's tags and categories + * @param {string} id - Document ID + * @param {Object} updates - Object containing tags and/or categories + * @param {Array} updates.tags - New tags array + * @param {Array} updates.categories - New categories array + * @returns {Promise} Updated document object + */ +export const updateDocument = async (id, updates) => { + await delay(400); + + const index = mockDocuments.findIndex(doc => doc.id === id); + if (index === -1) { + throw new Error('Document not found'); + } + + // Update the document + mockDocuments[index] = { + ...mockDocuments[index], + ...updates + }; + + return mockDocuments[index]; +}; + +/** + * Deletes a document + * @param {string} id - Document ID + * @returns {Promise} True if deletion was successful + */ +export const deleteDocument = async (id) => { + await delay(300); + + const index = mockDocuments.findIndex(doc => doc.id === id); + if (index === -1) { + throw new Error('Document not found'); + } + + mockDocuments.splice(index, 1); + return true; +}; + +/** + * Gets all available tags + * @returns {Promise>} Array of tag strings + */ +export const getAvailableTags = async () => { + await delay(200); + return [...availableTags]; +}; + +/** + * Gets all available categories + * @returns {Promise>} Array of category strings + */ +export const getAvailableCategories = async () => { + await delay(200); + return [...availableCategories]; +}; \ No newline at end of file diff --git a/src/frontend/src/utils/mockData.js b/src/frontend/src/utils/mockData.js new file mode 100644 index 0000000..8cd4e0a --- /dev/null +++ b/src/frontend/src/utils/mockData.js @@ -0,0 +1,155 @@ +/** + * Mock data for PDF documents + * This file provides sample data for development and testing purposes + */ + +/** + * Generates a placeholder thumbnail URL + * @param {number} index - Document index for unique colors + * @returns {string} Placeholder image URL + */ +const generateThumbnailUrl = (index) => { + const colors = ['3B82F6', '10B981', 'F59E0B', 'EF4444', '8B5CF6', 'EC4899']; + const color = colors[index % colors.length]; + return `https://via.placeholder.com/300x400/${color}/FFFFFF?text=Page+1`; +}; + +/** + * Mock documents data + * @type {Array} + */ +export const mockDocuments = [ + { + id: 'doc-001', + name: 'Contrat-2025.pdf', + originalFileType: 'DOCX', + createdAt: '2025-10-01T10:30:00Z', + fileSize: 2048576, // 2 MB + pageCount: 12, + thumbnailUrl: generateThumbnailUrl(0), + pdfUrl: '/mock/contrat-2025.pdf', + tags: ['contrat', '2025'], + categories: ['legal'] + }, + { + id: 'doc-002', + name: 'Facture-Janvier.pdf', + originalFileType: 'XLSX', + createdAt: '2025-09-15T14:20:00Z', + fileSize: 512000, // 512 KB + pageCount: 3, + thumbnailUrl: generateThumbnailUrl(1), + pdfUrl: '/mock/facture-janvier.pdf', + tags: ['facture', 'comptabilité'], + categories: ['finance'] + }, + { + id: 'doc-003', + name: 'Présentation-Projet.pdf', + originalFileType: 'PPTX', + createdAt: '2025-09-28T09:15:00Z', + fileSize: 5242880, // 5 MB + pageCount: 24, + thumbnailUrl: generateThumbnailUrl(2), + pdfUrl: '/mock/presentation-projet.pdf', + tags: ['présentation', 'projet'], + categories: ['marketing'] + }, + { + id: 'doc-004', + name: 'Photo-Identité.pdf', + originalFileType: 'JPG', + createdAt: '2025-10-05T16:45:00Z', + fileSize: 204800, // 200 KB + pageCount: 1, + thumbnailUrl: generateThumbnailUrl(3), + pdfUrl: '/mock/photo-identite.pdf', + tags: ['photo', 'identité'], + categories: ['personnel'] + }, + { + id: 'doc-005', + name: 'Manuel-Utilisateur.pdf', + originalFileType: 'PDF', + createdAt: '2025-09-20T11:00:00Z', + fileSize: 3145728, // 3 MB + pageCount: 45, + thumbnailUrl: generateThumbnailUrl(4), + pdfUrl: '/mock/manuel-utilisateur.pdf', + tags: ['manuel', 'documentation'], + categories: ['technique'] + }, + { + id: 'doc-006', + name: 'Rapport-Annuel.pdf', + originalFileType: 'DOCX', + createdAt: '2025-08-30T13:30:00Z', + fileSize: 4194304, // 4 MB + pageCount: 67, + thumbnailUrl: generateThumbnailUrl(5), + pdfUrl: '/mock/rapport-annuel.pdf', + tags: ['rapport', 'annuel'], + categories: ['finance', 'management'] + }, + { + id: 'doc-007', + name: 'CV-Candidat.pdf', + originalFileType: 'DOCX', + createdAt: '2025-10-02T08:00:00Z', + fileSize: 153600, // 150 KB + pageCount: 2, + thumbnailUrl: generateThumbnailUrl(0), + pdfUrl: '/mock/cv-candidat.pdf', + tags: ['cv', 'recrutement'], + categories: ['rh'] + }, + { + id: 'doc-008', + name: 'Devis-Travaux.pdf', + originalFileType: 'XLSX', + createdAt: '2025-09-25T15:20:00Z', + fileSize: 409600, // 400 KB + pageCount: 5, + thumbnailUrl: generateThumbnailUrl(1), + pdfUrl: '/mock/devis-travaux.pdf', + tags: ['devis', 'travaux'], + categories: ['finance'] + } +]; + +/** + * Available tags for documents + * @type {Array} + */ +export const availableTags = [ + 'contrat', + 'facture', + 'présentation', + 'photo', + 'manuel', + 'rapport', + 'cv', + 'devis', + 'comptabilité', + 'projet', + 'identité', + 'documentation', + 'annuel', + 'recrutement', + 'travaux', + '2025' +]; + +/** + * Available categories for documents + * @type {Array} + */ +export const availableCategories = [ + 'legal', + 'finance', + 'marketing', + 'personnel', + 'technique', + 'management', + 'rh' +]; \ No newline at end of file