feat: edit and delete pairs in GenerateIt

- Backend: PATCH /api/directus/db-pairs/<id> updates level, statement, question
  (creates question if new, removes if cleared)
- Backend: DELETE /api/directus/db-pairs/<id> removes pair + all junctions,
  questions and statements
- Frontend: inline edit form per pair (level slider, statement, question)
- Frontend: delete button per pair with confirm dialog
- api.ts: updateDbPair, deleteDbPair

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 11:58:57 +02:00
parent 1bbe64db66
commit 8bcb3b9168
3 changed files with 196 additions and 27 deletions

View File

@@ -453,3 +453,24 @@ export async function createDbPair(
if (!res.ok) throw new Error(data.error || 'Fehler beim Erstellen des Pairs')
return data
}
export async function updateDbPair(
pairId: string,
payload: { level?: number; question_de?: string; statement_de?: string },
token: string
): Promise<void> {
const res = await fetch(`/api/directus/db-pairs/${pairId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
body: JSON.stringify(payload),
})
if (!res.ok) throw new Error('Fehler beim Aktualisieren des Pairs')
}
export async function deleteDbPair(pairId: string, token: string): Promise<void> {
const res = await fetch(`/api/directus/db-pairs/${pairId}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
})
if (!res.ok) throw new Error('Fehler beim Löschen des Pairs')
}

View File

@@ -7,6 +7,8 @@ import {
getDbObjects,
getDbObjectPairs,
createDbPair,
updateDbPair,
deleteDbPair,
directusAssetUrl,
} from '../api'
import { useAuth } from '../context/AuthContext'
@@ -207,14 +209,44 @@ interface PairsListProps {
function PairsList({ pairs, loading, objectId, token, onRefresh }: PairsListProps) {
const [showForm, setShowForm] = useState(false)
const [editingId, setEditingId] = useState<string | null>(null)
const [editLevel, setEditLevel] = useState(50)
const [editQuestion, setEditQuestion] = useState('')
const [editStatement, setEditStatement] = useState('')
const [saving, setSaving] = useState(false)
const handleSaved = () => {
setShowForm(false)
const startEdit = (pair: DbPair) => {
setEditingId(pair.id)
setEditLevel(pair.level)
setEditStatement(pair.statements[0]?.statement_de ?? '')
setEditQuestion(pair.questions[0]?.question_de ?? '')
}
const saveEdit = async () => {
if (!editingId) return
setSaving(true)
try {
await updateDbPair(editingId, {
level: editLevel,
statement_de: editStatement,
question_de: editQuestion,
}, token)
setEditingId(null)
onRefresh()
} finally {
setSaving(false)
}
}
const handleDelete = async (pairId: string) => {
if (!confirm('Pair wirklich löschen?')) return
await deleteDbPair(pairId, token)
onRefresh()
}
if (loading) return <div className="empty-state">Lade</div>
const handleSaved = () => { setShowForm(false); onRefresh() }
if (loading) return <div className="empty-state">Lade</div>
if (!objectId) return <div className="empty-state">Kein Objekt gewählt.</div>
return (
@@ -228,31 +260,65 @@ function PairsList({ pairs, loading, objectId, token, onRefresh }: PairsListProp
{pairs.map(pair => (
<div key={pair.id} style={{ padding: '8px 0', borderBottom: '1px solid var(--border)' }}>
{/* Level badge */}
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<span style={{
fontSize: 10, fontWeight: 700, color: 'var(--primary)',
background: 'var(--primary-muted)', borderRadius: 'var(--r-full)',
padding: '1px 6px', border: '1px solid color-mix(in srgb, var(--primary) 30%, transparent)',
}}>L{pair.level}</span>
<span style={{ fontSize: 10, color: 'var(--text-2)' }}>{pair.status}</span>
</div>
{/* Statements */}
{pair.statements.map(s => (
<div key={s.id} style={{ fontSize: 12, color: 'var(--text-1)', marginBottom: 3, paddingLeft: 4 }}>
<span style={{ fontSize: 10, color: 'var(--text-2)', marginRight: 4 }}>Aussage:</span>
{s.statement_de}
{editingId === pair.id ? (
/* ── Inline-Edit-Formular ── */
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<label style={{ fontSize: 11, color: 'var(--text-2)', whiteSpace: 'nowrap' }}>Level {editLevel}</label>
<input type="range" min={1} max={100} value={editLevel}
onChange={e => setEditLevel(Number(e.target.value))}
style={{ flex: 1 }} />
</div>
<textarea
value={editStatement}
onChange={e => setEditStatement(e.target.value)}
rows={2}
placeholder="Aussage (Pflicht)…"
style={{ width: '100%', padding: '5px 8px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', background: 'var(--surface-2)', color: 'var(--text-1)', fontSize: 12, resize: 'vertical', boxSizing: 'border-box' }}
/>
<textarea
value={editQuestion}
onChange={e => setEditQuestion(e.target.value)}
rows={2}
placeholder="Frage (optional)…"
style={{ width: '100%', padding: '5px 8px', borderRadius: 'var(--r-sm)', border: '1px solid var(--border)', background: 'var(--surface-2)', color: 'var(--text-1)', fontSize: 12, resize: 'vertical', boxSizing: 'border-box' }}
/>
<div style={{ display: 'flex', gap: 4 }}>
<button className="btn-primary btn-sm" style={{ flex: 1 }} onClick={saveEdit} disabled={saving || !editStatement.trim()}>
{saving ? 'Speichere…' : 'Speichern'}
</button>
<button className="btn-ghost btn-sm" onClick={() => setEditingId(null)}>Abbrechen</button>
</div>
</div>
))}
{/* Questions */}
{pair.questions.map(q => (
<div key={q.id} style={{ fontSize: 12, color: 'var(--text-2)', fontStyle: 'italic', paddingLeft: 4 }}>
<span style={{ fontSize: 10, marginRight: 4, fontStyle: 'normal' }}>Frage:</span>
{q.question_de}
</div>
))}
) : (
/* ── Anzeige-Modus ── */
<>
<div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 4 }}>
<span style={{
fontSize: 10, fontWeight: 700, color: 'var(--primary)',
background: 'var(--primary-muted)', borderRadius: 'var(--r-full)',
padding: '1px 6px', border: '1px solid color-mix(in srgb, var(--primary) 30%, transparent)',
}}>L{pair.level}</span>
<span style={{ fontSize: 10, color: 'var(--text-2)' }}>{pair.status}</span>
<div style={{ marginLeft: 'auto', display: 'flex', gap: 4 }}>
<button onClick={() => startEdit(pair)}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: 'var(--text-2)', padding: '0 3px' }} title="Bearbeiten"></button>
<button onClick={() => handleDelete(pair.id)}
style={{ background: 'none', border: 'none', cursor: 'pointer', fontSize: 12, color: 'var(--danger, #dc2626)', padding: '0 3px' }} title="Löschen"></button>
</div>
</div>
{pair.statements.map(s => (
<div key={s.id} style={{ fontSize: 12, color: 'var(--text-1)', marginBottom: 3, paddingLeft: 4 }}>
<span style={{ fontSize: 10, color: 'var(--text-2)', marginRight: 4 }}>Aussage:</span>{s.statement_de}
</div>
))}
{pair.questions.map(q => (
<div key={q.id} style={{ fontSize: 12, color: 'var(--text-2)', fontStyle: 'italic', paddingLeft: 4 }}>
<span style={{ fontSize: 10, marginRight: 4, fontStyle: 'normal' }}>Frage:</span>{q.question_de}
</div>
))}
</>
)}
</div>
))}