'use client' import { useMemo, useState, useTransition } from 'react' import { ladderAdminText } from '@/lib/i18n/admin' import { resolveAll, type LadderRung } from '@/lib/auth/ladders' import { deleteLadderAction, setLadderRolesAction } from './actions' export type RoleOption = { discordRoleId: string label: string isPresent: boolean ladderId: string | null clearanceLevel: number permissions: string[] grantsMembership: boolean } /** * Eine Leiter bearbeiten: welche Rollen darin stehen und in welcher Ordnung. * * Angezeigt wird von oben nach unten, weil man ein Ranggefüge so liest - die * höchste Rolle zuerst. Gespeichert wird umgekehrt, `ladderRank` 0 ist unten. * Die Umkehr steht genau an einer Stelle, in `speichern`. * * Die Vorschau rechnet **im Browser** mit derselben Funktion wie der Server * (lib/auth/ladders.ts). Eine Vorschau, die den Stand vor dem Verschieben * zeigt, wäre schlimmer als keine: sie sähe richtig aus und wäre falsch. * * Bewusst nicht optimistisch. Eine Änderung hier wirkt sofort für alle * Träger dieser Rollen, deshalb Bestätigung und gesperrter Knopf statt einer * vorweggenommenen Rückmeldung. Siehe docs/09-performance-und-ux.md. */ export function LadderEditor({ ladder, order, allRoles, levels, }: { ladder: { id: string; name: string; note: string | null } /** Von unten nach oben, so wie es in der Datenbank steht. */ order: string[] allRoles: RoleOption[] levels: { level: number; name: string }[] }) { // Im Zustand von oben nach unten, so wie es dasteht. const [sprossen, setSprossen] = useState(() => [...order].reverse()) const [fehler, setFehler] = useState(null) const [gespeichert, setGespeichert] = useState(false) const [pending, startTransition] = useTransition() const nachId = useMemo( () => new Map(allRoles.map((rolle) => [rolle.discordRoleId, rolle])), [allRoles], ) const geaendert = sprossen.join() !== [...order].reverse().join() /** * Dieselbe Rechnung wie serverseitig, auf dem aktuellen Entwurf. `sprossen` * steht von oben nach unten, `ladderRank` zählt von unten - daher der * gedrehte Index. */ const aufgeloest = useMemo(() => { const rungs: LadderRung[] = sprossen.flatMap((id, index) => { const rolle = nachId.get(id) if (!rolle) return [] return [ { discordRoleId: rolle.discordRoleId, clearanceLevel: rolle.clearanceLevel, permissions: rolle.permissions, grantsMembership: rolle.grantsMembership, ladderId: ladder.id, ladderRank: sprossen.length - 1 - index, }, ] }) return resolveAll(rungs) }, [sprossen, nachId, ladder.id]) const verfuegbar = allRoles.filter( (rolle) => !sprossen.includes(rolle.discordRoleId) && rolle.ladderId === null, ) function verschieben(index: number, richtung: -1 | 1) { const ziel = index + richtung if (ziel < 0 || ziel >= sprossen.length) return const naechste = [...sprossen] const [herausgenommen] = naechste.splice(index, 1) naechste.splice(ziel, 0, herausgenommen!) setSprossen(naechste) setGespeichert(false) } function herausnehmen(id: string) { setSprossen((aktuell) => aktuell.filter((eintrag) => eintrag !== id)) setGespeichert(false) } function aufnehmen(id: string) { if (id === '') return setSprossen((aktuell) => [id, ...aktuell]) setGespeichert(false) } function speichern() { if (!confirm(ladderAdminText.confirmSave)) return setFehler(null) startTransition(async () => { // Zurück in die Reihenfolge der Datenbank: unten zuerst. const ergebnis = await setLadderRolesAction({ id: ladder.id, roleIds: [...sprossen].reverse(), }) if (ergebnis.ok) setGespeichert(true) else setFehler(ergebnis.error) }) } function aufloesen() { if (!confirm(ladderAdminText.confirmRemove)) return setFehler(null) startTransition(async () => { const ergebnis = await deleteLadderAction(ladder.id) if (!ergebnis.ok) setFehler(ergebnis.error) }) } return (

{ladder.name}

{ladder.note ? {ladder.note} : null}
{sprossen.length === 0 ? (

{ladderAdminText.emptyLadder}

) : (
    {sprossen.map((id, index) => { const rolle = nachId.get(id) if (!rolle) return null const ergebnis = aufgeloest.get(id) const geerbt = ergebnis ? [...ergebnis.inherited].sort() : [] const angehoben = ergebnis !== undefined && ergebnis.clearanceLevel > ergebnis.ownClearanceLevel const stufe = levels.find((l) => l.level === ergebnis?.clearanceLevel) return (
  1. {sprossen.length - index} {rolle.label} {!rolle.isPresent ? ( ! ) : null} {index === 0 && sprossen.length > 1 ? ladderAdminText.top : null} {index === sprossen.length - 1 ? ladderAdminText.bottom : null}

    {geerbt.length === 0 ? ( ladderAdminText.inheritsNothing ) : ( <> {ladderAdminText.inherits}{' '} {geerbt.join(', ')} )} {stufe ? ( <> {' · '} {ladderAdminText.effectiveClearance} {stufe.level} · {stufe.name} {angehoben ? ` (${ladderAdminText.raisedBy})` : ''} ) : null}

  2. ) })}
)}
{gespeichert && !geaendert ? ( {ladderAdminText.saved} ) : null} {fehler ? {fehler} : null}
) }