- add optional description to scenario and environment entities - expose description in create/update DTOs, MCP tools, and export DTOs - render description as markdown on detail pages - add description editor (CodeEditor) to create/edit forms for both resources
94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import { useState, SubmitEvent } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { X, Save, ClipboardList } from 'lucide-react';
|
|
import { scenarios } from '../../api';
|
|
import { Breadcrumbs, Button, Card, Input, CodeEditor, useToast } from '../../ui';
|
|
import styles from '../Page.module.css';
|
|
|
|
export function CreateScenarioPage() {
|
|
const { t } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const toast = useToast();
|
|
|
|
const [name, setName] = useState('');
|
|
const [description, setDescription] = useState('');
|
|
const [nameError, setNameError] = useState('');
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
const handleSubmit = async (e: SubmitEvent<HTMLFormElement>) => {
|
|
e.preventDefault();
|
|
if (!name.trim()) {
|
|
setNameError(t('scenarios.form_name_required'));
|
|
return;
|
|
}
|
|
setSaving(true);
|
|
try {
|
|
const scenario = await scenarios.create(name.trim(), description.trim() || undefined);
|
|
toast.success(t('scenarios.created'));
|
|
navigate(`/scenarios/${scenario.id}`);
|
|
} catch (err) {
|
|
const message = (err as Error).message;
|
|
toast.error(message);
|
|
} finally {
|
|
setSaving(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div>
|
|
<div className={styles.pageToolbar}>
|
|
<Breadcrumbs
|
|
items={[
|
|
{
|
|
label: t('scenarios.title'),
|
|
icon: <ClipboardList size={14} />,
|
|
onClick: () => navigate('/scenarios'),
|
|
},
|
|
{ label: t('scenarios.create_title') },
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} noValidate>
|
|
<Card className={styles.formCard}>
|
|
<div className={styles.formFields}>
|
|
<Input
|
|
label={t('scenarios.form_name')}
|
|
placeholder={t('scenarios.form_name_placeholder')}
|
|
value={name}
|
|
onChange={(e) => {
|
|
setName(e.target.value);
|
|
setNameError('');
|
|
}}
|
|
error={nameError || undefined}
|
|
required
|
|
autoFocus
|
|
/>
|
|
<div className={styles.formField}>
|
|
<label className={styles.fieldLabel}>{t('scenarios.form_description')}</label>
|
|
<CodeEditor
|
|
language="markdown"
|
|
value={description}
|
|
onChange={setDescription}
|
|
rows={8}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.formActions}>
|
|
<Button type="button" variant="secondary" onClick={() => navigate('/scenarios')}>
|
|
<X size={14} />
|
|
{t('scenarios.action_cancel')}
|
|
</Button>
|
|
<Button type="submit" loading={saving}>
|
|
<Save size={14} />
|
|
{t('scenarios.action_save')}
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|