Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
165 lines
5.1 KiB
TypeScript
165 lines
5.1 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useTransition } from 'react'
|
|
import { caseText } from '@/lib/i18n/cases'
|
|
import { actionErrorsText, clearanceText, commonText } from '@/lib/i18n/common'
|
|
import { documentText } from '@/lib/i18n/documents'
|
|
import {
|
|
finalizeDocumentAction,
|
|
reclassifyDocumentAction,
|
|
retractDocumentAction,
|
|
} from '../actions'
|
|
|
|
/**
|
|
* Ausfertigen, Zurückziehen, Umklassifizieren.
|
|
*
|
|
* Wie beim Fall bewusst **nicht** optimistisch: alle drei sind folgenreich.
|
|
* Das Ausfertigen fragt nach, das Zurückziehen verlangt eine Begründung - es
|
|
* gibt kein Löschen, der Grabstein bleibt stehen.
|
|
*/
|
|
export function DocumentActions({
|
|
documentNumber,
|
|
classification,
|
|
canFinalize,
|
|
canRetract,
|
|
canReclassify,
|
|
levels,
|
|
}: {
|
|
documentNumber: string
|
|
classification: number
|
|
canFinalize: boolean
|
|
canRetract: boolean
|
|
canReclassify: boolean
|
|
levels: { level: number; name: string }[]
|
|
}) {
|
|
const [reason, setReason] = useState('')
|
|
const [retracting, setRetracting] = useState(false)
|
|
const [nextLevel, setNextLevel] = useState(classification)
|
|
const [levelReason, setLevelReason] = useState('')
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [pending, startTransition] = useTransition()
|
|
|
|
if (!canFinalize && !canRetract && !canReclassify) return null
|
|
|
|
const lowering = nextLevel < classification
|
|
|
|
function run(action: () => Promise<{ ok: boolean; error?: string }>) {
|
|
setError(null)
|
|
startTransition(async () => {
|
|
const result = await action()
|
|
if (!result.ok) setError(result.error ?? actionErrorsText.default!)
|
|
else setRetracting(false)
|
|
})
|
|
}
|
|
|
|
return (
|
|
<div className="mt-6 flex flex-wrap gap-x-10 gap-y-5 border-t border-line pt-5 text-xs">
|
|
{canFinalize ? (
|
|
<div>
|
|
<button
|
|
type="button"
|
|
disabled={pending}
|
|
onClick={() => {
|
|
if (!confirm(documentText.finalizeHint)) return
|
|
run(() => finalizeDocumentAction({ documentNumber }))
|
|
}}
|
|
className="btn btn-primary"
|
|
>
|
|
{documentText.finalize}
|
|
</button>
|
|
</div>
|
|
) : null}
|
|
|
|
{canReclassify ? (
|
|
<div>
|
|
<p className="font-condensed text-text-dim">{documentText.reclassify}</p>
|
|
<div className="mt-1.5 flex flex-wrap items-center gap-2">
|
|
<select
|
|
value={nextLevel}
|
|
onChange={(event) => setNextLevel(Number(event.target.value))}
|
|
aria-label={clearanceText.classification}
|
|
className="field"
|
|
>
|
|
{levels.map((level) => (
|
|
<option key={level.level} value={level.level}>
|
|
{level.level} · {level.name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<button
|
|
type="button"
|
|
disabled={pending || nextLevel === classification}
|
|
onClick={() =>
|
|
run(() =>
|
|
reclassifyDocumentAction({
|
|
documentNumber,
|
|
to: nextLevel,
|
|
reason: levelReason,
|
|
}),
|
|
)
|
|
}
|
|
className="btn btn-primary"
|
|
>
|
|
{commonText.save}
|
|
</button>
|
|
</div>
|
|
{lowering ? (
|
|
<textarea
|
|
value={levelReason}
|
|
onChange={(event) => setLevelReason(event.target.value)}
|
|
rows={2}
|
|
placeholder={caseText.reclassifyReason}
|
|
aria-label={caseText.reclassifyReason}
|
|
className="mt-2 block w-full max-w-sm field text-2xs"
|
|
/>
|
|
) : null}
|
|
</div>
|
|
) : null}
|
|
|
|
{canRetract ? (
|
|
<div>
|
|
{retracting ? (
|
|
<div>
|
|
<textarea
|
|
value={reason}
|
|
onChange={(event) => setReason(event.target.value)}
|
|
rows={2}
|
|
placeholder={documentText.retractReason}
|
|
aria-label={documentText.retractReason}
|
|
className="block w-full max-w-sm field text-2xs"
|
|
/>
|
|
<div className="mt-2 flex gap-3">
|
|
<button
|
|
type="button"
|
|
disabled={pending || reason.trim().length < 10}
|
|
onClick={() => run(() => retractDocumentAction({ documentNumber, reason }))}
|
|
className="btn btn-sm text-stamp-4"
|
|
>
|
|
{documentText.retract}
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => setRetracting(false)}
|
|
className="btn btn-sm"
|
|
>
|
|
{commonText.cancel}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<button
|
|
type="button"
|
|
onClick={() => setRetracting(true)}
|
|
className="btn btn-sm"
|
|
>
|
|
{documentText.retract}
|
|
</button>
|
|
)}
|
|
</div>
|
|
) : null}
|
|
|
|
{error ? <p className="w-full font-condensed text-2xs text-stamp-4">{error}</p> : null}
|
|
</div>
|
|
)
|
|
}
|