New document hub for centralized file storage — replaces scattered drives and USB sticks for non-technical SMBs. Three new tables: storage_folder (nested hierarchy), storage_folder_permission (role and user-level access control), storage_file. Backend: folder CRUD with nested paths, file upload/download via signed URLs, permission checks (view/edit/admin with inheritance from parent folders), public/private toggle, breadcrumb navigation, file search. Frontend: two-panel file manager — collapsible folder tree on left, icon grid view on right. Folder icons by type, file size display, upload button, context menu for download/delete. Breadcrumb nav. Files sidebar link added.
30 lines
1.6 KiB
TypeScript
30 lines
1.6 KiB
TypeScript
import { FileText, Image, FileSpreadsheet, File, FileType, Film } from 'lucide-react'
|
|
|
|
const ICON_MAP: Record<string, { icon: typeof FileText; color: string }> = {
|
|
'application/pdf': { icon: FileText, color: 'text-red-500' },
|
|
'image/jpeg': { icon: Image, color: 'text-blue-500' },
|
|
'image/png': { icon: Image, color: 'text-blue-500' },
|
|
'image/webp': { icon: Image, color: 'text-blue-500' },
|
|
'image/gif': { icon: Image, color: 'text-blue-500' },
|
|
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': { icon: FileType, color: 'text-blue-600' },
|
|
'application/msword': { icon: FileType, color: 'text-blue-600' },
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': { icon: FileSpreadsheet, color: 'text-green-600' },
|
|
'application/vnd.ms-excel': { icon: FileSpreadsheet, color: 'text-green-600' },
|
|
'text/csv': { icon: FileSpreadsheet, color: 'text-green-600' },
|
|
'text/plain': { icon: FileText, color: 'text-muted-foreground' },
|
|
'video/mp4': { icon: Film, color: 'text-purple-500' },
|
|
}
|
|
|
|
export function FileIcon({ contentType, className = 'h-8 w-8' }: { contentType: string; className?: string }) {
|
|
const match = ICON_MAP[contentType] ?? { icon: File, color: 'text-muted-foreground' }
|
|
const Icon = match.icon
|
|
return <Icon className={`${className} ${match.color}`} />
|
|
}
|
|
|
|
export function formatFileSize(bytes: number): string {
|
|
if (bytes < 1024) return `${bytes} B`
|
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
|
|
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
|
|
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
|
|
}
|