All checks were successful
Deploy Development / deploy (push) Successful in 46s
Test Suite / pytest-backend (push) Successful in 1m26s
Test Suite / lint-backend (push) Successful in 1s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 12s
Leitfrage oben, Maßnahmen mit verknüpftem Kontext aus dem Snapshot, vollständiges Meilenstein-Formular, Backlog/Entscheidungen/Nachweise unter Erweitert. Version 0.11.2-ap1.1b.
109 lines
3.1 KiB
JavaScript
109 lines
3.1 KiB
JavaScript
import { useState } from 'react'
|
|
import {
|
|
BLOCKER_STATUSES,
|
|
BLOCKER_STATUS_LABELS,
|
|
} from '../constants/status.js'
|
|
import { StatusBadge } from './StatusBadge.jsx'
|
|
import { EmptyState } from './EmptyState.jsx'
|
|
|
|
export function BlockersSection({
|
|
blockers,
|
|
canManage,
|
|
onCreate,
|
|
onUpdateStatus,
|
|
onDelete,
|
|
busy,
|
|
heading = 'Blocker',
|
|
lead = null,
|
|
emptyMessage = 'Keine Blocker.',
|
|
}) {
|
|
const [formTitle, setFormTitle] = useState('')
|
|
const [showForm, setShowForm] = useState(false)
|
|
|
|
async function handleSubmit(e) {
|
|
e.preventDefault()
|
|
if (!formTitle.trim()) return
|
|
await onCreate({ title: formTitle.trim() })
|
|
setFormTitle('')
|
|
setShowForm(false)
|
|
}
|
|
|
|
return (
|
|
<section className="card">
|
|
<div className="section-header">
|
|
<div>
|
|
<h2>{heading}</h2>
|
|
{lead && <p className="section-lead muted">{lead}</p>}
|
|
</div>
|
|
{canManage && (
|
|
<button
|
|
type="button"
|
|
className="btn btn-primary btn-block-mobile"
|
|
onClick={() => setShowForm((v) => !v)}
|
|
>
|
|
{showForm ? 'Abbrechen' : 'Blocker melden'}
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{showForm && canManage && (
|
|
<form className="inline-form-block" onSubmit={handleSubmit}>
|
|
<label>
|
|
Titel
|
|
<input
|
|
value={formTitle}
|
|
onChange={(e) => setFormTitle(e.target.value)}
|
|
maxLength={255}
|
|
required
|
|
/>
|
|
</label>
|
|
<button type="submit" className="btn btn-primary" disabled={busy}>
|
|
Anlegen
|
|
</button>
|
|
</form>
|
|
)}
|
|
|
|
{blockers.length === 0 && <EmptyState message={emptyMessage} />}
|
|
|
|
<ul className="item-list">
|
|
{blockers.map((blocker) => (
|
|
<li key={blocker.id} className="list-item card-list-item">
|
|
<div className="list-item-main">
|
|
<strong>{blocker.title}</strong>
|
|
{blocker.description && (
|
|
<p className="list-item-desc">{blocker.description}</p>
|
|
)}
|
|
</div>
|
|
<div className="list-item-meta action-controls">
|
|
<StatusBadge kind="blocker" status={blocker.status} />
|
|
{canManage && (
|
|
<>
|
|
<select
|
|
className="inline-select"
|
|
value={blocker.status}
|
|
onChange={(e) => onUpdateStatus(blocker.id, e.target.value)}
|
|
aria-label="Blocker-Status"
|
|
>
|
|
{BLOCKER_STATUSES.map((s) => (
|
|
<option key={s} value={s}>
|
|
{BLOCKER_STATUS_LABELS[s]}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<button
|
|
type="button"
|
|
className="btn btn-secondary"
|
|
onClick={() => onDelete(blocker.id)}
|
|
>
|
|
Löschen
|
|
</button>
|
|
</>
|
|
)}
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</section>
|
|
)
|
|
}
|