From 97941de7bfc880218f9ddef7a15f76aeecf05f19 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Wed, 20 May 2026 18:22:39 +0000 Subject: [PATCH] Changes Co-authored-by: Reifonas <211114984+Reifonas@users.noreply.github.com> --- src/components/RecentFilesList.tsx | 131 +++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/components/RecentFilesList.tsx diff --git a/src/components/RecentFilesList.tsx b/src/components/RecentFilesList.tsx new file mode 100644 index 0000000..62b18b3 --- /dev/null +++ b/src/components/RecentFilesList.tsx @@ -0,0 +1,131 @@ +import { useEffect, useState, useCallback } from 'react'; +import { Clock, Trash2, Loader2, FileBox } from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { listRecentFiles, loadRecentFile, removeRecentFile, type RecentFileMeta } from '@/lib/recentFiles'; +import { useModelStore } from '@/stores/useModelStore'; +import { toast } from 'sonner'; + +function formatSize(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 formatWhen(ts: number): string { + const diff = Date.now() - ts; + const m = Math.floor(diff / 60000); + if (m < 1) return 'agora'; + if (m < 60) return `${m} min`; + const h = Math.floor(m / 60); + if (h < 24) return `${h} h`; + const d = Math.floor(h / 24); + return `${d} d`; +} + +export function RecentFilesList() { + const [items, setItems] = useState([]); + const [loadingId, setLoadingId] = useState(null); + const addModel = useModelStore((s) => s.addModel); + const models = useModelStore((s) => s.models); + const maxModels = useModelStore((s) => s.maxModels); + + const refresh = useCallback(() => setItems(listRecentFiles()), []); + + useEffect(() => { + refresh(); + const onStorage = (e: StorageEvent) => { + if (e.key === 'tsxr_recent_files_v1') refresh(); + }; + window.addEventListener('storage', onStorage); + // Polling leve para refletir adições no mesmo tab + const t = setInterval(refresh, 2000); + return () => { window.removeEventListener('storage', onStorage); clearInterval(t); }; + }, [refresh]); + + const handleLoad = useCallback(async (meta: RecentFileMeta) => { + if (models.length >= maxModels) { + toast.error(`Limite de ${maxModels} peças simultâneas atingido`); + return; + } + setLoadingId(meta.id); + try { + const res = await loadRecentFile(meta.id); + if (!res) { + toast.error('Arquivo recente não encontrado — pode ter sido limpo'); + await removeRecentFile(meta.id); + refresh(); + return; + } + addModel({ fileName: meta.fileName, fileSize: meta.fileSize, url: res.url }); + toast.success(`"${meta.fileName}" carregado dos recentes`); + } catch (e) { + console.error(e); + toast.error('Falha ao carregar arquivo recente'); + } finally { + setLoadingId(null); + } + }, [addModel, models.length, maxModels, refresh]); + + const handleRemove = useCallback(async (e: React.MouseEvent, meta: RecentFileMeta) => { + e.stopPropagation(); + await removeRecentFile(meta.id); + refresh(); + }, [refresh]); + + if (items.length === 0) { + return ( +
+ +

+ Nenhum arquivo recente — os modelos carregados aparecerão aqui +

+
+ ); + } + + return ( +
+
+ + + Recentes + + + {items.length}/6 + +
+
+ {items.map((r) => { + const isLoading = loadingId === r.id; + return ( +
!isLoading && handleLoad(r)} + title="Clique para carregar" + > + {isLoading + ? + : } +
+

{r.fileName}

+

+ {formatSize(r.fileSize)} · {formatWhen(r.addedAt)} atrás +

+
+ +
+ ); + })} +
+
+ ); +}