feat: Add admin experiment management pages and components

- Implemented ExperimentPage for viewing and editing individual experiments.
- Created ExperimentsAdminPage for managing all experiments with a list and a modal for creating new experiments.
- Developed AnnotatePage for annotating experiments with different views based on annotation tools.
- Added CreateExperimentModal for creating new experiments with dataset selection.
- Introduced DeleteExperimentButton for confirming and executing experiment deletions.
- Built EditableExperimentHeader for editing experiment details.
- Created ExperimentsList component to display a list of experiments with links to their details.
This commit is contained in:
averel10
2026-03-20 07:26:02 +01:00
parent 4b35e2392e
commit dd5d99ec24
16 changed files with 1593 additions and 60 deletions

View File

@@ -0,0 +1,168 @@
'use client';
import { useState } from 'react';
import { updateExperiment } from '@/app/actions/experiment';
import { Experiment } from '@/lib/model/experiment';
interface EditableExperimentHeaderProps {
experiment: Experiment;
}
const ANNOTATION_TOOLS = [
{ value: 'single-choice', label: 'Single Choice' },
];
export default function EditableExperimentHeader({
experiment,
}: EditableExperimentHeaderProps) {
const [isEditing, setIsEditing] = useState(false);
const [name, setName] = useState(experiment.name);
const [description, setDescription] = useState(experiment.description || '');
const [annotationTool, setAnnotationTool] = useState(experiment.annotationTool || 'single-choice');
const [published, setPublished] = useState(experiment.published || false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
async function handleSave() {
if (!name.trim()) {
setError('Experiment name cannot be empty');
return;
}
setLoading(true);
setError(null);
try {
await updateExperiment(experiment.id, {
name: name.trim(),
description: description.trim() || undefined,
annotationTool: annotationTool,
published: published
});
setIsEditing(false);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to update experiment');
} finally {
setLoading(false);
}
}
function handleCancel() {
setName(experiment.name);
setAnnotationTool(experiment.annotationTool || 'single-choice');
setPublished(experiment.published || false);
setDescription(experiment.description || '');
setError(null);
setIsEditing(false);
}
return (
<div className="bg-white rounded-lg shadow p-6 mb-6">
{isEditing ? (
<div>
<label htmlFor="name" className="block text-sm font-medium mb-2">
Experiment Name
</label>
<input
id="name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4"
disabled={loading}
/>
<label htmlFor="description" className="block text-sm font-medium mb-2">
Description
</label>
<textarea
id="description"
value={description}
onChange={(e) => setDescription(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none mb-4"
rows={3}
disabled={loading}
/>
<label htmlFor="annotationTool" className="block text-sm font-medium mb-2">
Annotation Tool
</label>
<select
id="annotationTool"
value={annotationTool}
onChange={(e) => setAnnotationTool(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 mb-4"
disabled={loading}
>
{ANNOTATION_TOOLS.map((tool) => (
<option key={tool.value} value={tool.value}>
{tool.label}
</option>
))}
</select>
<div className="flex items-center mb-4">
<input
id="published"
type="checkbox"
checked={published}
onChange={(e) => setPublished(e.target.checked)}
className="w-4 h-4 text-blue-500 rounded focus:ring-2 focus:ring-blue-500"
disabled={loading}
/>
<label htmlFor="published" className="ml-2 text-sm font-medium text-gray-700">
Published
</label>
</div>
{error && (
<div className="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded text-sm">
{error}
</div>
)}
<div className="flex gap-2">
<button
onClick={handleSave}
disabled={loading}
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:bg-gray-400"
>
{loading ? 'Saving...' : 'Save'}
</button>
<button
onClick={handleCancel}
disabled={loading}
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400 disabled:bg-gray-200"
>
Cancel
</button>
</div>
</div>
) : (
<div>
<h1 className="text-3xl font-bold mb-2">{experiment.name}</h1>
<p className="text-lg text-gray-600 mb-4">{experiment.description}</p>
<p className="text-sm text-gray-500 mb-4">
ID: {experiment.id} | Dataset ID: {experiment.datasetId}
</p>
<p className="text-sm text-gray-500 mb-4">
Annotation Tool: {ANNOTATION_TOOLS.find(t => t.value === (experiment.annotationTool || 'single-choice'))?.label || 'Single Choice'}
</p>
<p className="text-sm text-gray-500 mb-4">
Status: {experiment.published ? 'Published' : 'Draft'}
</p>
<p className="text-sm text-gray-500 mb-4">
Created: {new Date(experiment.createdAt).toLocaleDateString()} |
Updated: {new Date(experiment.updatedAt).toLocaleDateString()}
</p>
<button
onClick={() => setIsEditing(true)}
className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
>
Edit
</button>
</div>
)}
</div>
);
}