veza/apps/web/src/features/cloud/components/CloudBrowserView.tsx

107 lines
3.6 KiB
TypeScript
Raw Normal View History

import { useState, useEffect } from 'react';
import { CloudFolderTree } from './CloudFolderTree';
import { CloudFileList } from './CloudFileList';
import { cloudService, type CloudFolder, type CloudFile, type CloudQuota } from '../services/cloudService';
interface CloudBrowserViewProps {
onUploadClick?: () => void;
}
export function CloudBrowserView({ onUploadClick }: CloudBrowserViewProps) {
const [folders, setFolders] = useState<CloudFolder[]>([]);
const [files, setFiles] = useState<CloudFile[]>([]);
const [selectedFolderId, setSelectedFolderId] = useState<string | null>(null);
const [quota, setQuota] = useState<CloudQuota | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadData();
}, [selectedFolderId]);
async function loadData() {
setLoading(true);
try {
const [folderData, fileData, quotaData] = await Promise.all([
cloudService.listFolders(selectedFolderId ?? undefined),
cloudService.listFiles(selectedFolderId ?? undefined),
cloudService.getQuota(),
]);
setFolders((prev) => {
const otherFolders = prev.filter(
(f) => f.parent_id !== (selectedFolderId ?? null) || !selectedFolderId
);
return [...otherFolders, ...folderData].filter(
(f, i, arr) => arr.findIndex((x) => x.id === f.id) === i
);
});
setFiles(fileData);
setQuota(quotaData);
} catch {
// Errors handled silently
} finally {
setLoading(false);
}
}
return (
<div className="flex h-full gap-6">
<aside className="w-56 shrink-0 border-r border-border pr-4">
<CloudFolderTree
folders={folders}
selectedFolderId={selectedFolderId}
onSelectFolder={setSelectedFolderId}
onCreateFolder={async () => {
const name = prompt('Folder name:');
if (name) {
await cloudService.createFolder(name, selectedFolderId ?? undefined);
loadData();
}
}}
/>
{quota && (
<div className="mt-6 px-3">
<div className="text-xs text-muted-foreground mb-1">
Storage: {((quota.used_bytes / quota.max_bytes) * 100).toFixed(1)}%
</div>
<div className="h-1.5 bg-muted rounded-full overflow-hidden">
<div
className="h-full bg-primary rounded-full transition-all"
style={{ width: `${Math.min((quota.used_bytes / quota.max_bytes) * 100, 100)}%` }}
/>
</div>
<div className="text-xs text-muted-foreground mt-1">
{formatBytes(quota.used_bytes)} / {formatBytes(quota.max_bytes)}
</div>
</div>
)}
</aside>
<main className="flex-1 min-w-0">
{loading ? (
<div className="flex items-center justify-center py-16">
<div className="animate-spin h-8 w-8 border-2 border-primary border-t-transparent rounded-full" />
</div>
) : (
<CloudFileList
files={files}
onDeleteFile={async (id) => {
await cloudService.deleteFile(id);
loadData();
}}
onPublishFile={async (id) => {
await cloudService.publishAsTrack(id);
}}
/>
)}
</main>
</div>
);
}
function formatBytes(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`;
}