Files
experiments-database/frontend/src/pages/Dashboard.jsx
T

163 lines
5.6 KiB
React

import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { experimentsApi } from '../api/client';
import Button from '../components/ui/Button';
import Modal from '../components/ui/Modal';
import ConfirmDialog from '../components/ui/ConfirmDialog';
import ExperimentForm from '../components/ExperimentForm';
import Alert from '../components/ui/Alert';
import { format } from 'date-fns';
import AuditLogSection from '../components/AuditLogSection';
export default function Dashboard() {
const navigate = useNavigate();
const [experiments, setExperiments] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [showCreateModal, setShowCreateModal] = useState(false);
const [editTarget, setEditTarget] = useState(null);
const [deleteTarget, setDeleteTarget] = useState(null);
const [deleteLoading, setDeleteLoading] = useState(false);
const [successMsg, setSuccessMsg] = useState(null);
async function loadExperiments() {
setLoading(true);
setError(null);
try {
const data = await experimentsApi.list();
setExperiments(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
useEffect(() => { loadExperiments(); }, []);
function flash(msg) {
setSuccessMsg(msg);
setTimeout(() => setSuccessMsg(null), 3000);
}
function handleCreated(exp) {
setShowCreateModal(false);
loadExperiments();
flash(`Experiment "${exp.title}" created.`);
}
function handleUpdated(exp) {
setEditTarget(null);
loadExperiments();
flash(`Experiment "${exp.title}" updated.`);
}
async function handleDelete() {
setDeleteLoading(true);
try {
await experimentsApi.delete(deleteTarget.id);
setDeleteTarget(null);
loadExperiments();
flash('Experiment deleted.');
} catch (err) {
setError(err.message);
setDeleteTarget(null);
} finally {
setDeleteLoading(false);
}
}
return (
<div className="max-w-5xl mx-auto px-4 py-8">
<div className="flex items-center justify-between mb-6">
<div>
<h1 className="text-2xl font-bold text-gray-900">Experiments</h1>
<p className="text-sm text-gray-500 mt-0.5">Manage all animal experimental studies</p>
</div>
<Button onClick={() => setShowCreateModal(true)}>
+ New Experiment
</Button>
</div>
{successMsg && <Alert type="success" message={successMsg} className="mb-4" />}
{error && <Alert type="error" message={error} onDismiss={() => setError(null)} />}
{loading && (
<div className="text-center py-16 text-gray-400">Loading experiments</div>
)}
{!loading && experiments.length === 0 && (
<div className="text-center py-20 border-2 border-dashed border-gray-200 rounded-xl">
<p className="text-gray-400 text-lg mb-2">No experiments yet</p>
<p className="text-gray-400 text-sm mb-4">Create your first experiment to get started.</p>
<Button onClick={() => setShowCreateModal(true)}>+ New Experiment</Button>
</div>
)}
{!loading && experiments.length > 0 && (
<div className="grid gap-4">
{experiments.map((exp) => (
<div
key={exp.id}
className="bg-white border border-gray-200 rounded-xl p-5 flex items-center justify-between hover:border-blue-300 hover:shadow-sm transition-all cursor-pointer group"
onClick={() => navigate(`/experiments/${exp.id}`)}
>
<div>
<h2 className="font-semibold text-gray-900 group-hover:text-blue-700 transition-colors">
{exp.title}
</h2>
<div className="text-sm text-gray-500 mt-0.5 flex gap-4">
<span>{exp._count?.animals ?? 0} animal{exp._count?.animals !== 1 ? 's' : ''}</span>
<span>Created {format(new Date(exp.created_at), 'MMM d, yyyy')}</span>
</div>
</div>
<div className="flex gap-2" onClick={(e) => e.stopPropagation()}>
<Button
size="sm"
variant="secondary"
onClick={() => setEditTarget(exp)}
>
Edit
</Button>
<Button
size="sm"
variant="danger"
onClick={() => setDeleteTarget(exp)}
>
Delete
</Button>
</div>
</div>
))}
</div>
)}
<AuditLogSection tableName="experiments" />
{/* Create modal */}
<Modal isOpen={showCreateModal} onClose={() => setShowCreateModal(false)} title="New Experiment">
<ExperimentForm onSuccess={handleCreated} onCancel={() => setShowCreateModal(false)} />
</Modal>
{/* Edit modal */}
<Modal isOpen={!!editTarget} onClose={() => setEditTarget(null)} title="Edit Experiment">
<ExperimentForm
existing={editTarget}
onSuccess={handleUpdated}
onCancel={() => setEditTarget(null)}
/>
</Modal>
{/* Delete confirmation */}
<ConfirmDialog
isOpen={!!deleteTarget}
onClose={() => setDeleteTarget(null)}
onConfirm={handleDelete}
loading={deleteLoading}
title="Delete Experiment"
message={`Are you sure you want to delete "${deleteTarget?.title}"? All associated animals and daily statuses will be permanently removed.`}
/>
</div>
);
}