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.
137 lines
4.4 KiB
TypeScript
137 lines
4.4 KiB
TypeScript
'use client'
|
|
|
|
import { useState, useTransition } from 'react'
|
|
import Link from 'next/link'
|
|
import { commonText } from '@/lib/i18n/common'
|
|
import { participantText } from '@/lib/i18n/cases'
|
|
import { PARTICIPANT_ROLES } from '@/lib/cases/terms'
|
|
import { AddPanel, PanelSelect } from '@/components/AddPanel'
|
|
import { addParticipantAction, removeParticipantAction } from '../actions'
|
|
|
|
type Beteiligter = {
|
|
id: string
|
|
role: string
|
|
userId: string | null
|
|
displayName: string | null
|
|
personnelFileId: string | null
|
|
}
|
|
|
|
/**
|
|
* Beteiligte einer Akte.
|
|
*
|
|
* Der Hinweis über dem Formular ist wichtig und steht deshalb dort und nicht
|
|
* in einer Hilfeseite: wer hier eingetragen wird, liest die Akte unabhängig
|
|
* von seiner Freigabestufe.
|
|
*/
|
|
export function Participants({
|
|
caseNumber,
|
|
participants,
|
|
canEdit,
|
|
members,
|
|
files,
|
|
}: {
|
|
caseNumber: string
|
|
participants: Beteiligter[]
|
|
canEdit: boolean
|
|
members: { id: string; displayName: string }[]
|
|
files: { id: string; displayName: string }[]
|
|
}) {
|
|
const [role, setRole] = useState<string>('INVESTIGATOR')
|
|
const [target, setTarget] = useState('')
|
|
const [fehler, setFehler] = useState<string | null>(null)
|
|
const [pending, startTransition] = useTransition()
|
|
|
|
return (
|
|
<div>
|
|
{participants.length === 0 ? (
|
|
<p className="text-text-dim">{participantText.empty}</p>
|
|
) : (
|
|
<ul className="border-t border-line">
|
|
{participants.map((person) => (
|
|
<li
|
|
key={person.id}
|
|
className="record-row border-b border-line py-2"
|
|
>
|
|
<span className="record-label">
|
|
{participantText.roles[person.role] ?? person.role}
|
|
</span>
|
|
<span className="record-title text-sm">
|
|
{person.personnelFileId ? (
|
|
<Link href={`/personalakten/${person.personnelFileId}`}>
|
|
{person.displayName ?? commonText.unknown}
|
|
</Link>
|
|
) : (
|
|
(person.displayName ?? commonText.unknown)
|
|
)}
|
|
</span>
|
|
{canEdit ? (
|
|
<button
|
|
type="button"
|
|
disabled={pending}
|
|
onClick={() =>
|
|
startTransition(async () => {
|
|
const ergebnis = await removeParticipantAction({
|
|
caseNumber,
|
|
participantId: person.id,
|
|
})
|
|
if (!ergebnis.ok) setFehler(ergebnis.error)
|
|
})
|
|
}
|
|
className="btn btn-sm"
|
|
>
|
|
{participantText.remove}
|
|
</button>
|
|
) : null}
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
|
|
{fehler ? <p className="mt-2 font-condensed text-2xs text-stamp-4">{fehler}</p> : null}
|
|
|
|
{canEdit ? (
|
|
<AddPanel
|
|
label={participantText.add}
|
|
hint={participantText.hint}
|
|
disabled={!target}
|
|
onSubmit={async () => {
|
|
const [art, id] = target.split(':')
|
|
const ergebnis = await addParticipantAction({
|
|
caseNumber,
|
|
role,
|
|
...(art === 'user' ? { userId: id } : { personnelFileId: id }),
|
|
})
|
|
if (ergebnis.ok) setTarget('')
|
|
return ergebnis
|
|
}}
|
|
>
|
|
<PanelSelect value={role} onChange={setRole} label={participantText.role}>
|
|
{PARTICIPANT_ROLES.map((value) => (
|
|
<option key={value} value={value}>
|
|
{participantText.roles[value] ?? value}
|
|
</option>
|
|
))}
|
|
</PanelSelect>
|
|
<PanelSelect value={target} onChange={setTarget} label={participantText.person}>
|
|
<option value="">-</option>
|
|
<optgroup label={participantText.pickUser}>
|
|
{members.map((member) => (
|
|
<option key={member.id} value={`user:${member.id}`}>
|
|
{member.displayName}
|
|
</option>
|
|
))}
|
|
</optgroup>
|
|
<optgroup label={participantText.pickPersonnel}>
|
|
{files.map((file) => (
|
|
<option key={file.id} value={`file:${file.id}`}>
|
|
{file.displayName}
|
|
</option>
|
|
))}
|
|
</optgroup>
|
|
</PanelSelect>
|
|
</AddPanel>
|
|
) : null}
|
|
</div>
|
|
)
|
|
}
|