sciagent code + Gitea Actions CI/CD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Database, Search } from 'lucide-react';
|
||||
|
||||
import {
|
||||
Badge,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Input,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
listDatasets,
|
||||
VISIBILITY_LABEL,
|
||||
} from '@ump/shared';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
/**
|
||||
* Clinical data repository — every imaging dataset on the platform (admin scope=all).
|
||||
* Read-only inventory for milestone 1; per-dataset drill-down lands in a later slice.
|
||||
*/
|
||||
export function DatasetsPage() {
|
||||
const [q, setQ] = useState('');
|
||||
const { data, isLoading, isError } = useQuery({
|
||||
queryKey: ['imagehub', 'datasets', 'all'],
|
||||
queryFn: () => listDatasets({ scope: 'all' }),
|
||||
});
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const needle = q.trim().toLowerCase();
|
||||
if (!needle) return data ?? [];
|
||||
return (data ?? []).filter(
|
||||
(d) =>
|
||||
d.name.toLowerCase().includes(needle) ||
|
||||
(d.ownerEmail ?? '').toLowerCase().includes(needle) ||
|
||||
d.modalityTags.some((t) => t.toLowerCase().includes(needle)),
|
||||
);
|
||||
}, [data, q]);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="font-serif text-2xl font-semibold text-foreground">Kho dữ liệu lâm sàng</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Toàn bộ bộ dữ liệu hình ảnh trên hệ thống của các nhà nghiên cứu.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between gap-3 space-y-0">
|
||||
<CardTitle className="text-base">
|
||||
{isLoading ? 'Đang tải…' : `${filtered.length} bộ dữ liệu`}
|
||||
</CardTitle>
|
||||
<div className="relative w-64 max-w-full">
|
||||
<Search className="pointer-events-none absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
|
||||
<Input
|
||||
value={q}
|
||||
onChange={(e) => setQ(e.target.value)}
|
||||
placeholder="Tìm theo tên, chủ sở hữu, nhãn…"
|
||||
className="pl-8"
|
||||
/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isError && <p className="text-sm text-destructive">Không tải được kho dữ liệu.</p>}
|
||||
{!isError && !isLoading && filtered.length === 0 && (
|
||||
<div className="py-12 text-center">
|
||||
<Database className="mx-auto mb-3 h-10 w-10 text-muted-foreground" />
|
||||
<p className="text-sm text-muted-foreground">Chưa có bộ dữ liệu nào.</p>
|
||||
</div>
|
||||
)}
|
||||
{filtered.length > 0 && (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Tên bộ dữ liệu</TableHead>
|
||||
<TableHead>Chủ sở hữu</TableHead>
|
||||
<TableHead>Hiển thị</TableHead>
|
||||
<TableHead className="text-right">Tệp</TableHead>
|
||||
<TableHead className="text-right">Phiên bản</TableHead>
|
||||
<TableHead>Cập nhật</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filtered.map((d) => (
|
||||
<TableRow key={d.id}>
|
||||
<TableCell className="font-medium">
|
||||
<div className="flex items-center gap-2">
|
||||
<Database className="h-4 w-4 shrink-0 text-muted-foreground" />
|
||||
<div>
|
||||
<div>{d.name}</div>
|
||||
{d.modalityTags.length > 0 && (
|
||||
<div className="mt-0.5 flex flex-wrap gap-1">
|
||||
{d.modalityTags.map((t) => (
|
||||
<Badge key={t} variant="secondary" className="text-[10px]">
|
||||
{t}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-muted-foreground">{d.ownerEmail ?? '—'}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant="outline">{VISIBILITY_LABEL[d.visibility]}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">{d.fileCount}</TableCell>
|
||||
<TableCell className="text-right">{d.versionCount}</TableCell>
|
||||
<TableCell className="text-muted-foreground">
|
||||
{d.updatedAt ? new Date(d.updatedAt).toLocaleDateString('vi-VN') : '—'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user