sciagent code + Gitea Actions CI/CD
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Thinh Lam
2026-06-30 09:38:30 +07:00
commit 688fac73e9
1167 changed files with 158244 additions and 0 deletions
+126
View File
@@ -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 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>
);
}