Funktionen-Reiter: Abschaltgrund und Löschdatum des Terminplaners

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-04 18:44:23 +00:00
1 parent 78bc99a73b
commit 6f0371283e
3 files changed
+58 -2

No files matched your search

@@ -4,7 +4,7 @@ import { FaCheck, FaCopy, FaSpinner } from 'react-icons/fa6'
import { Card, Badge, Button, EmptyState } from '../ui' import { Card, Badge, Button, EmptyState } from '../ui'
import { copyText } from '../CopyableCredential' import { copyText } from '../CopyableCredential'
import { getFunktionen, setFeature } from '../../lib/teamApi' import { getFunktionen, setFeature } from '../../lib/teamApi'
import { FUNKTIONEN, ZUSTAND, funktionOf, aktionen, mergeFunktion, siteKeyOf } from '../../lib/funktionenView' import { FUNKTIONEN, ZUSTAND, funktionOf, aktionen, mergeFunktion, siteKeyOf, abgeschaltetText } from '../../lib/funktionenView'
import '../../styles/admin-areas.css' import '../../styles/admin-areas.css'
// Reiter "Funktionen": News-Pinnwand und Terminplaner je Projekt an/aus wie im frueheren Admin-Panel (Kundenliste, Zeile "Funktionen") // Reiter "Funktionen": News-Pinnwand und Terminplaner je Projekt an/aus wie im frueheren Admin-Panel (Kundenliste, Zeile "Funktionen")
@@ -94,6 +94,7 @@ export default function CustomerFunktionen({ customerId }) {
<span>{f.label}</span> <span>{f.label}</span>
<span> <span>
<Badge tone={tone}>{label}</Badge> <Badge tone={tone}>{label}</Badge>
{abgeschaltetText(feature) && <><br /><span className="area-muted">{abgeschaltetText(feature)}</span></>}
{feature.adminNote && <><br /><span className="area-muted" title={feature.adminNote}>{short(feature.adminNote)}</span></>} {feature.adminNote && <><br /><span className="area-muted" title={feature.adminNote}>{short(feature.adminNote)}</span></>}
{siteKeyOf(feature, f.key) && <><br /><SiteKey value={siteKeyOf(feature, f.key)} /></>} {siteKeyOf(feature, f.key) && <><br /><SiteKey value={siteKeyOf(feature, f.key)} /></>}
</span> </span>
+21
View File
@@ -1,6 +1,8 @@
// Kunden-Reiter "Funktionen": Register der Funktionen je Projekt (News, Terminplaner), Aktionen je Zustand und // Kunden-Reiter "Funktionen": Register der Funktionen je Projekt (News, Terminplaner), Aktionen je Zustand und
// Einarbeiten der Portal-Antwort. Rein, unter Node testbar. // Einarbeiten der Portal-Antwort. Rein, unter Node testbar.
import { fmtYmd } from './format'
/** Funktionen in Anzeigereihenfolge; key = Schluessel im Portal (features[key], POST /funktionen/:customerId/:key). */ /** Funktionen in Anzeigereihenfolge; key = Schluessel im Portal (features[key], POST /funktionen/:customerId/:key). */
export const FUNKTIONEN = [ export const FUNKTIONEN = [
{ {
@@ -30,6 +32,25 @@ export const ZUSTAND = {
disabled: ['muted', 'Abgeschaltet'], disabled: ['muted', 'Abgeschaltet'],
} }
/** Abschaltgrund (Portal: features[key].disabledReason) -> Zusatz hinter "Abgeschaltet"; unbekannte Gruende gelten als "ohne Grund". */
const ABGESCHALTET_GRUND = {
abo_ende: ' (Abo beendet)',
abo_ende_vor_einrichtung: ' (Abo endete vor Einrichtung, Erstattung prüfen)',
team: ' (Team)',
kunde_geloescht: ' (Kunde gelöscht)',
}
/**
* Zeile unter dem Abzeichen "Abgeschaltet": Grund und, falls das Portal ein Loeschdatum nennt (feature.loeschungAm, Berliner
* Kalendertag; nur Terminplaner), "Löschung am TT.MM.JJJJ". Ausserhalb von "disabled" ''.
*/
export function abgeschaltetText(feature) {
if (feature?.state !== 'disabled') return ''
const grund = Object.prototype.hasOwnProperty.call(ABGESCHALTET_GRUND, feature.disabledReason) ? ABGESCHALTET_GRUND[feature.disabledReason] : ''
const loeschung = feature.loeschungAm ? `, Löschung am ${fmtYmd(feature.loeschungAm)}` : ''
return `Abgeschaltet${grund}${loeschung}`
}
/** Ansicht einer Funktion im Projekt (Portal: project.features[key]); ohne Eintrag gilt "off". */ /** Ansicht einer Funktion im Projekt (Portal: project.features[key]); ohne Eintrag gilt "off". */
export const funktionOf = (project, key) => project?.features?.[key] || { state: 'off' } export const funktionOf = (project, key) => project?.features?.[key] || { state: 'off' }
+35 -1
View File
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict'
import { loadModule } from './load.mjs' import { loadModule } from './load.mjs'
// Kunden-Reiter "Funktionen": Register der Funktionen (News, Terminplaner), Aktionen je Zustand, Antwort einarbeiten // Kunden-Reiter "Funktionen": Register der Funktionen (News, Terminplaner), Aktionen je Zustand, Antwort einarbeiten
const { FUNKTIONEN, ZUSTAND, funktionOf, aktionen, mergeFunktion, siteKeyOf } = await loadModule('src/lib/funktionenView.js') const { FUNKTIONEN, ZUSTAND, funktionOf, aktionen, mergeFunktion, siteKeyOf, abgeschaltetText } = await loadModule('src/lib/funktionenView.js')
test('FUNKTIONEN: News und Terminplaner mit den Bestätigungstexten', () => { test('FUNKTIONEN: News und Terminplaner mit den Bestätigungstexten', () => {
assert.deepEqual(FUNKTIONEN, [ assert.deepEqual(FUNKTIONEN, [
@@ -101,3 +101,37 @@ test('siteKeyOf: nur der Terminplaner zeigt seinen Site-Key, nur wenn das Portal
assert.equal(siteKeyOf(feature, 'termine'), '', JSON.stringify(feature)) assert.equal(siteKeyOf(feature, 'termine'), '', JSON.stringify(feature))
} }
}) })
test('abgeschaltetText: Grund und Löschdatum im Zustand "disabled"', () => {
const text = (disabledReason, loeschungAm) => abgeschaltetText({ state: 'disabled', disabledReason, loeschungAm })
assert.equal(text('abo_ende', '2026-11-04'), 'Abgeschaltet (Abo beendet), Löschung am 04.11.2026')
assert.equal(text('abo_ende', undefined), 'Abgeschaltet (Abo beendet)')
assert.equal(text('abo_ende_vor_einrichtung', '2026-11-04'), 'Abgeschaltet (Abo endete vor Einrichtung, Erstattung prüfen), Löschung am 04.11.2026')
assert.equal(text('abo_ende_vor_einrichtung', null), 'Abgeschaltet (Abo endete vor Einrichtung, Erstattung prüfen)')
assert.equal(text('team', '2026-12-01'), 'Abgeschaltet (Team), Löschung am 01.12.2026')
assert.equal(text('team', null), 'Abgeschaltet (Team)')
assert.equal(text('kunde_geloescht', '2026-12-31'), 'Abgeschaltet (Kunde gelöscht), Löschung am 31.12.2026')
assert.equal(text('kunde_geloescht', ''), 'Abgeschaltet (Kunde gelöscht)')
})
test('abgeschaltetText: ohne Grund nur "Abgeschaltet", mit Datum "Abgeschaltet, Löschung am …"', () => {
assert.equal(abgeschaltetText({ state: 'disabled' }), 'Abgeschaltet')
assert.equal(abgeschaltetText({ state: 'disabled', disabledReason: null, loeschungAm: null }), 'Abgeschaltet')
assert.equal(abgeschaltetText({ state: 'disabled', disabledReason: '', loeschungAm: '' }), 'Abgeschaltet')
assert.equal(abgeschaltetText({ state: 'disabled', disabledReason: null, loeschungAm: '2026-11-04' }), 'Abgeschaltet, Löschung am 04.11.2026')
assert.equal(abgeschaltetText({ state: 'disabled', loeschungAm: '2026-01-09' }), 'Abgeschaltet, Löschung am 09.01.2026')
})
test('abgeschaltetText: unbekannter Grund wird wie "ohne Grund" behandelt', () => {
assert.equal(abgeschaltetText({ state: 'disabled', disabledReason: 'frist' }), 'Abgeschaltet')
assert.equal(abgeschaltetText({ state: 'disabled', disabledReason: 'frist', loeschungAm: '2026-11-04' }), 'Abgeschaltet, Löschung am 04.11.2026')
assert.equal(abgeschaltetText({ state: 'disabled', disabledReason: 'constructor' }), 'Abgeschaltet', 'kein Treffer über die Prototypenkette')
assert.equal(abgeschaltetText({ state: 'disabled', disabledReason: 5 }), 'Abgeschaltet')
})
test('abgeschaltetText: außerhalb von "disabled" leer, auch mit Grund und Datum', () => {
for (const state of ['active', 'setup', 'ordered', 'off', undefined, null]) {
assert.equal(abgeschaltetText({ state, disabledReason: 'abo_ende', loeschungAm: '2026-11-04' }), '', String(state))
}
for (const feature of [{}, null, undefined]) assert.equal(abgeschaltetText(feature), '', JSON.stringify(feature))
})