/** * DMS file browser page. * Folder tree sidebar + file grid + upload + search + preview + share + bulk actions. */ import React, { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Card } from '@/components/ui/Card'; import { ConfirmDialog } from '@/components/ui/ConfirmDialog'; import { useToast } from '@/components/ui/Toast'; import { FolderTree } from '@/components/dms/FolderTree'; import { FileGrid } from '@/components/dms/FileGrid'; import { UploadDropzone } from '@/components/dms/UploadDropzone'; import { FilePreviewModal } from '@/components/dms/FilePreviewModal'; import { ShareDialog } from '@/components/dms/ShareDialog'; import { BulkActions } from '@/components/dms/BulkActions'; import { fetchFolders, createFolder, deleteFile, searchFiles, type DmsFolder, type DmsFile, } from '@/api/dms'; export function DmsPage() { const { t } = useTranslation(); const toast = useToast(); const [folders, setFolders] = useState([]); const [files, setFiles] = useState([]); const [loadingFolders, setLoadingFolders] = useState(true); const [loadingFiles, setLoadingFiles] = useState(true); const [error, setError] = useState(null); const [selectedFolderId, setSelectedFolderId] = useState(null); const [selectedFileIds, setSelectedFileIds] = useState>(new Set()); const [searchQuery, setSearchQuery] = useState(''); const [showUpload, setShowUpload] = useState(false); const [showNewFolder, setShowNewFolder] = useState(false); const [newFolderName, setNewFolderName] = useState(''); const [previewFile, setPreviewFile] = useState(null); const [shareFile, setShareFile] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [submittingFolder, setSubmittingFolder] = useState(false); // Load folders const loadFolders = useCallback(async () => { setLoadingFolders(true); try { const folderList = await fetchFolders(); setFolders(folderList); } catch (err) { const msg = err instanceof Error ? err.message : String(err); setError(msg); } setLoadingFolders(false); }, []); useEffect(() => { loadFolders(); }, [loadFolders]); // Load files (mock: in real app would fetch by folder or search) const loadFiles = useCallback(async () => { setLoadingFiles(true); try { if (searchQuery.trim()) { const result = await searchFiles(searchQuery); setFiles(result.files); } else { // For folder view, we would normally call a folder files endpoint // Since the API only has search and shared-with-me, we show empty or use search setFiles([]); } } catch (err) { const msg = err instanceof Error ? err.message : String(err); setError(msg); setFiles([]); } setLoadingFiles(false); }, [searchQuery]); useEffect(() => { const debounce = setTimeout(() => { loadFiles(); }, 300); return () => clearTimeout(debounce); }, [loadFiles, selectedFolderId]); const handleToggleSelect = useCallback((fileId: string) => { setSelectedFileIds((prev) => { const next = new Set(prev); if (next.has(fileId)) { next.delete(fileId); } else { next.add(fileId); } return next; }); }, []); const handleFileClick = useCallback((file: DmsFile) => { setPreviewFile(file); }, []); const handleFileShare = useCallback((file: DmsFile) => { setShareFile(file); }, []); const handleFileDelete = useCallback((file: DmsFile) => { setDeleteTarget(file); }, []); const handleFilePreview = useCallback((file: DmsFile) => { setPreviewFile(file); }, []); const handleConfirmDelete = useCallback(async () => { if (!deleteTarget) return; try { await deleteFile(deleteTarget.id); toast.success(t('dms.deleted')); setFiles((prev) => prev.filter((f) => f.id !== deleteTarget.id)); setDeleteTarget(null); } catch (err) { const msg = err instanceof Error ? err.message : String(err); toast.error(msg); } }, [deleteTarget, toast, t]); const handleCreateFolder = useCallback(async () => { if (!newFolderName.trim()) return; setSubmittingFolder(true); try { const folder = await createFolder({ name: newFolderName.trim(), parent_id: selectedFolderId, }); setFolders((prev) => [...prev, folder]); setNewFolderName(''); setShowNewFolder(false); toast.success(t('dms.createFolder')); } catch (err) { const msg = err instanceof Error ? err.message : String(err); toast.error(msg); } setSubmittingFolder(false); }, [newFolderName, selectedFolderId, toast, t]); const handleUploadComplete = useCallback(() => { loadFiles(); }, [loadFiles]); const handleClearSelection = useCallback(() => { setSelectedFileIds(new Set()); }, []); return (

{t('dms.title')}

{t('dms.trash')}
{error && (

{error}

)} {showNewFolder && (
setNewFolderName(e.target.value)} placeholder={t('dms.folderName')} />
)} {showUpload && (
)}
setSearchQuery(e.target.value)} placeholder={t('dms.searchPlaceholder')} />
{ handleClearSelection(); loadFiles(); }} />
setPreviewFile(null)} /> setShareFile(null)} onShared={handleUploadComplete} /> setDeleteTarget(null)} />
); }