import { useState, useEffect, useRef } from "react"; import type { Project, ProjectFile } from "@/lib/api"; import { getProjectFiles, uploadProjectFile, updateProjectFile, deleteProjectFile, getProjectFileUrl, } from "@/lib/api"; interface Props { project: Project; } function formatFileSize(bytes: number): string { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / 1024 / 1024).toFixed(1)} MB`; } function getFileIcon(mimeType: string | null): string { if (!mimeType) return "📄"; if (mimeType.startsWith("image/")) return "📷"; if (mimeType.startsWith("video/")) return "📹"; if (mimeType.startsWith("audio/")) return "🎵"; if (mimeType.includes("pdf")) return "📕"; if (mimeType.includes("word") || mimeType.includes("document")) return "📝"; if (mimeType.includes("spreadsheet") || mimeType.includes("excel")) return "📊"; if (mimeType.includes("presentation") || mimeType.includes("powerpoint")) return "📋"; if (mimeType.includes("zip") || mimeType.includes("archive")) return "📦"; if (mimeType.includes("text")) return "📄"; return "📁"; } function isImage(mimeType: string | null): boolean { return !!mimeType && mimeType.startsWith("image/"); } export default function ProjectFiles({ project }: Props) { const [files, setFiles] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [uploadLoading, setUploadLoading] = useState(false); const [editingDescription, setEditingDescription] = useState(null); const [editDescription, setEditDescription] = useState(""); const [isDragOver, setIsDragOver] = useState(false); const fileInputRef = useRef(null); const uploadDescriptionRef = useRef(null); const loadFiles = async () => { try { const result = await getProjectFiles(project.slug, search || undefined); setFiles(result); } catch (err) { console.error("Failed to load files:", err); } finally { setLoading(false); } }; useEffect(() => { loadFiles(); }, [project.slug, search]); const handleFileUpload = async (file: File, description?: string) => { setUploadLoading(true); try { await uploadProjectFile(project.slug, file, description); await loadFiles(); // Refresh list alert("Файл загружен успешно!"); } catch (err) { console.error("Upload failed:", err); alert(`Ошибка загрузки: ${err}`); } finally { setUploadLoading(false); } }; const handleFileSelect = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { const description = uploadDescriptionRef.current?.value.trim(); handleFileUpload(file, description || undefined); event.target.value = ""; if (uploadDescriptionRef.current) { uploadDescriptionRef.current.value = ""; } } }; const handleDrop = (event: React.DragEvent) => { event.preventDefault(); setIsDragOver(false); const file = event.dataTransfer.files[0]; if (file) { const description = uploadDescriptionRef.current?.value.trim(); handleFileUpload(file, description || undefined); if (uploadDescriptionRef.current) { uploadDescriptionRef.current.value = ""; } } }; const handleDragOver = (event: React.DragEvent) => { event.preventDefault(); setIsDragOver(true); }; const handleDragLeave = (event: React.DragEvent) => { event.preventDefault(); setIsDragOver(false); }; const handleDownload = (file: ProjectFile) => { const url = getProjectFileUrl(project.slug, file.id); window.open(url, "_blank"); }; const handleEditDescription = (file: ProjectFile) => { setEditingDescription(file.id); setEditDescription(file.description || ""); }; const handleSaveDescription = async (file: ProjectFile) => { try { await updateProjectFile(project.slug, file.id, { description: editDescription.trim() || undefined, }); await loadFiles(); setEditingDescription(null); } catch (err) { console.error("Failed to update description:", err); alert(`Ошибка обновления: ${err}`); } }; const handleDeleteFile = async (file: ProjectFile) => { if (!confirm(`Удалить файл "${file.filename}"?`)) return; try { await deleteProjectFile(project.slug, file.id); await loadFiles(); alert("Файл удален!"); } catch (err) { console.error("Failed to delete file:", err); alert(`Ошибка удаления: ${err}`); } }; if (loading) { return (
Загрузка файлов...
); } return (
{/* Header with search */}
setSearch(e.target.value)} className="w-full px-3 py-2 bg-[var(--surface)] border border-[var(--border)] rounded text-[var(--fg)] placeholder-[var(--muted)] focus:outline-none focus:border-[var(--accent)]" />
{files.length} файл{files.length === 1 ? "" : files.length < 5 ? "а" : "ов"}
{/* Upload area */}
{uploadLoading ? (
Загрузка...
) : ( <>
📁
Перетащите файл сюда или
Максимальный размер: 50 МБ
)}
{/* Files list */}
{files.length === 0 ? (
📂
Файлов пока нет
Загрузите первый файл проекта
) : (
{files.map((file) => (
{/* File icon & preview */}
{isImage(file.mime_type) ? ( {file.filename} ) : ( {getFileIcon(file.mime_type)} )}
{/* File info */}
{formatFileSize(file.size)}
{/* Description */} {editingDescription === file.id ? (
setEditDescription(e.target.value)} placeholder="Описание файла..." className="flex-1 px-2 py-1 text-sm bg-[var(--bg)] border border-[var(--border)] rounded text-[var(--fg)] placeholder-[var(--muted)] focus:outline-none focus:border-[var(--accent)]" autoFocus onKeyDown={(e) => { if (e.key === "Enter") { handleSaveDescription(file); } else if (e.key === "Escape") { setEditingDescription(null); } }} />
) : (
{file.description || "Без описания"}
)}
Загрузил: {file.uploader?.name || "Unknown"} {new Date(file.created_at).toLocaleString("ru")}
{/* Actions */}
))}
)}
); }