tahamajs/IE / Projects /front /src /components /data-hub /RepositoryBrowser.tsx
tahamajs's picture
download
raw
5.49 kB
import { useState } from "react";
import { useResearchObjectRepository } from "@/hooks/useResearchObjectRepository";
import { ObjectUploader } from "./ObjectUploader";
import { Search, Plus, Grid3X3, List, Download, Eye, Trash2, ExternalLink, Database } from "lucide-react";
// โœ… Single definition and export
export const TYPE_ICONS: Record<string, string> = {
dataset: "๐Ÿ“Š",
code: "๐Ÿ’ป",
software: "๐Ÿงฉ",
presentation: "๐Ÿ“ฝ๏ธ",
poster: "๐Ÿ–ผ๏ธ",
video: "๐ŸŽฌ",
image: "๐Ÿ“ท",
document: "๐Ÿ“„",
paper: "๐Ÿ“„",
other: "๐Ÿ“",
};
export function RepositoryBrowser() {
const { objects, loading, filter, setFilter, search, setSearch, uploading, uploadProgress, uploadObject, deleteObject } = useResearchObjectRepository();
const [showUpload, setShowUpload] = useState(false);
const [view, setView] = useState<"grid" | "list">("grid");
if (loading) return <div className="text-center py-8">Loading research objects...</div>;
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-xl font-bold flex items-center gap-2">
<Database className="w-5 h-5 text-primary" /> Research Objects ({objects.length})
</h2>
<div className="flex gap-2">
<button onClick={() => setView("grid")} className={`p-1.5 rounded ${view === "grid" ? "bg-primary/10 text-primary" : "text-gray-400"}`}>
<Grid3X3 className="w-4 h-4" />
</button>
<button onClick={() => setView("list")} className={`p-1.5 rounded ${view === "list" ? "bg-primary/10 text-primary" : "text-gray-400"}`}>
<List className="w-4 h-4" />
</button>
<button onClick={() => setShowUpload(true)} className="btn btn-primary btn-sm gap-1.5">
<Plus className="w-4 h-4" /> Upload
</button>
</div>
</div>
<div className="flex gap-2">
<div className="relative flex-1 max-w-xs">
<Search className="absolute left-2 top-1/2 -translate-y-1/2 w-4 h-4 text-gray-400" />
<input
type="text"
value={search}
onChange={e => setSearch(e.target.value)}
placeholder="Search..."
className="w-full pl-8 pr-3 py-1.5 text-sm border rounded-lg bg-white/10"
/>
</div>
<select aria-label="Select option" value={filter} onChange={e => setFilter(e.target.value as any)} className="text-sm border rounded-lg px-3 py-1.5 bg-white/10">
<option value="all">All Types</option>
{Object.keys(TYPE_ICONS).map(k => <option key={k} value={k}>{k}</option>)}
</select>
</div>
{view === "grid" ? (
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{objects.map(obj => (
<div key={obj.id} className="group relative bg-white/5 rounded-xl border border-white/10 overflow-hidden hover:shadow-md transition">
<div className="aspect-video bg-white/10 flex items-center justify-center text-4xl">{TYPE_ICONS[obj.type] || "๐Ÿ“"}</div>
<div className="p-3">
<p className="text-sm font-semibold truncate">{obj.title}</p>
<p className="text-xs text-gray-500 mt-1">{(obj.authors || []).slice(0, 2).join(", ")}</p>
<div className="flex items-center gap-2 mt-2 text-xs text-gray-400">
<Eye className="w-3 h-3" /> {obj.viewCount || 0} ยท <Download className="w-3 h-3" /> {obj.downloadCount || 0}
</div>
{obj.doi && (
<a href={`https://doi.org/${obj.doi}`} target="_blank" rel="noopener noreferrer" className="text-[10px] text-primary hover:underline">
<ExternalLink className="w-3 h-3 inline" /> DOI
</a>
)}
</div>
<button onClick={() => deleteObject(obj.id)} className="absolute top-2 right-2 p-1 bg-red-500/80 text-white rounded-full opacity-0 group-hover:opacity-100 transition">
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
))}
</div>
) : (
<div className="space-y-2">
{objects.map(obj => (
<div key={obj.id} className="flex items-center justify-between p-3 rounded-lg bg-white/5 hover:bg-white/10 transition">
<div className="flex items-center gap-3">
<span className="text-2xl">{TYPE_ICONS[obj.type] || "๐Ÿ“"}</span>
<div>
<p className="font-semibold text-sm">{obj.title}</p>
<p className="text-xs text-gray-500">{(obj.authors || []).join(", ")} ยท v{obj.version || 1}</p>
</div>
</div>
<div className="flex gap-1">
<a href={obj.fileUrl} target="_blank" rel="noopener noreferrer" className="btn btn-ghost btn-xs">
<Download className="w-3.5 h-3.5" />
</a>
<button onClick={() => deleteObject(obj.id)} className="btn btn-ghost btn-xs text-red-500">
<Trash2 className="w-3.5 h-3.5" />
</button>
</div>
</div>
))}
</div>
)}
{showUpload && (
<ObjectUploader
onUpload={uploadObject}
onClose={() => setShowUpload(false)}
uploading={uploading}
progress={uploadProgress}
/>
)}
</div>
);
}

Xet Storage Details

Size:
5.49 kB
ยท
Xet hash:
2fb628192b08e31d00d7816c4aa6167a5e033c802713f2b34f63ec9f4a5a0d09

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.