Compare commits
2
Commits
36eb55b81b
...
65e9931f2c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65e9931f2c | ||
|
|
426a5d6654 |
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { FaCheck, FaCopy, FaEye, FaEyeSlash } from 'react-icons/fa6'
|
||||
|
||||
async function copyText(text) {
|
||||
export async function copyText(text) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(text)
|
||||
} catch {
|
||||
|
||||
@@ -1,27 +1,41 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { FaSpinner } from 'react-icons/fa6'
|
||||
import { Fragment, useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { FaCheck, FaCopy, FaSpinner } from 'react-icons/fa6'
|
||||
import { Card, Badge, Button, EmptyState } from '../ui'
|
||||
import { getFunktionen, setNewsFeature } from '../../lib/teamApi'
|
||||
import { copyText } from '../CopyableCredential'
|
||||
import { getFunktionen, setFeature } from '../../lib/teamApi'
|
||||
import { FUNKTIONEN, ZUSTAND, funktionOf, aktionen, mergeFunktion, siteKeyOf } from '../../lib/funktionenView'
|
||||
import '../../styles/admin-areas.css'
|
||||
|
||||
// Reiter "Funktionen": News-Pinnwand je Projekt an/aus wie im frueheren Admin-Panel (Kundenliste, Zeile "Funktionen")
|
||||
const STATE = {
|
||||
off: ['muted', 'Aus'],
|
||||
ordered: ['warn', 'Bestellt'],
|
||||
setup: ['info', 'Einrichtung'],
|
||||
active: ['ok', 'Aktiv'],
|
||||
disabled: ['muted', 'Abgeschaltet'],
|
||||
}
|
||||
const CONFIRM = {
|
||||
activate: 'News ohne Zahlung aktivieren?',
|
||||
disable: 'News abschalten? Die Website zeigt dann keine Beiträge mehr.',
|
||||
}
|
||||
// Reiter "Funktionen": News-Pinnwand und Terminplaner je Projekt an/aus wie im frueheren Admin-Panel (Kundenliste, Zeile "Funktionen")
|
||||
const short = (text) => (text.length > 60 ? `${text.slice(0, 60)}…` : text)
|
||||
|
||||
// Site-Key des Terminplaners (steht im Einbau-Code der Kundenwebsite): kleine Schrift mit fester Breite und Kopierknopf
|
||||
function SiteKey({ value }) {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const timer = useRef(null)
|
||||
useEffect(() => () => clearTimeout(timer.current), [])
|
||||
const copy = async () => {
|
||||
await copyText(value)
|
||||
setCopied(true)
|
||||
clearTimeout(timer.current)
|
||||
timer.current = setTimeout(() => setCopied(false), 1800)
|
||||
}
|
||||
return (
|
||||
<span className="funktionen-sitekey">
|
||||
<span className="area-muted">Site-Key</span>
|
||||
<code>{value}</code>
|
||||
<button type="button" className="funktionen-sitekey-copy" onClick={copy} title={copied ? 'Kopiert' : 'Site-Key kopieren'} aria-label={copied ? 'Site-Key kopiert' : 'Site-Key kopieren'}>
|
||||
{copied ? <FaCheck className="funktionen-sitekey-ok" /> : <FaCopy />}
|
||||
</button>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
export default function CustomerFunktionen({ customerId }) {
|
||||
const [projects, setProjects] = useState(null)
|
||||
const [error, setError] = useState(null)
|
||||
const [confirm, setConfirm] = useState(null) // { projectId, action }
|
||||
const [confirm, setConfirm] = useState(null) // { projectId, key, action }
|
||||
const [note, setNote] = useState('')
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [status, setStatus] = useState(null) // { ok, text }
|
||||
@@ -38,9 +52,10 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
setBusy(true)
|
||||
setStatus(null)
|
||||
try {
|
||||
const { projects: changed } = await setNewsFeature(customerId, { projectId: confirm.projectId, action: confirm.action, note: note.trim() })
|
||||
const { projects: changed } = await setFeature(customerId, confirm.key, { projectId: confirm.projectId, action: confirm.action, note: note.trim() })
|
||||
// Die Antwort enthaelt das ganze Projekt; uebernommen wird nur die geaenderte Funktion
|
||||
const updated = changed?.[0]
|
||||
if (updated) setProjects((list) => list.map((p) => (p.projectId === updated.projectId ? updated : p)))
|
||||
if (updated) setProjects((list) => mergeFunktion(list, updated.projectId, confirm.key, updated))
|
||||
setConfirm(null)
|
||||
setNote('')
|
||||
setStatus({ ok: true, text: 'Gespeichert.' })
|
||||
@@ -52,6 +67,8 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
}
|
||||
}
|
||||
|
||||
const ask = (projectId, key, action) => { setNote(''); setConfirm({ projectId, key, action }) }
|
||||
|
||||
if (error) return <Card><div className="area-error">{error}</div></Card>
|
||||
if (!projects) return <Card><FaSpinner className="spinner" /></Card>
|
||||
if (!projects.length) return <Card><EmptyState title="Keine Projekte für diesen Kunden." /></Card>
|
||||
@@ -59,44 +76,53 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
return (
|
||||
<Card title="Funktionen" pad={false}>
|
||||
<div className="area-rows">
|
||||
<div className="area-row funktionen-row head"><span>Projekt</span><span>News</span><span>Ticket</span><span>Aktionen</span></div>
|
||||
{projects.map((p) => {
|
||||
const news = p.features?.news || { state: 'off' }
|
||||
const [tone, label] = STATE[news.state] || ['muted', news.state]
|
||||
const isConfirming = confirm?.projectId === p.projectId
|
||||
<div className="area-row funktionen-row head"><span>Projekt</span><span>Funktion</span><span>Status</span><span>Ticket</span><span>Aktionen</span></div>
|
||||
{projects.flatMap((p) => FUNKTIONEN.map((f, index) => {
|
||||
const feature = funktionOf(p, f.key)
|
||||
const [tone, label] = ZUSTAND[feature.state] || ['muted', feature.state]
|
||||
const isConfirming = confirm?.projectId === p.projectId && confirm?.key === f.key
|
||||
return (
|
||||
<div key={p.projectId} className="area-row funktionen-row">
|
||||
<span>
|
||||
<strong>{p.projectName}</strong>
|
||||
{p.liveUrl && <><br /><a href={p.liveUrl} target="_blank" rel="noreferrer" className="area-muted">{p.liveUrl.replace(/^https?:\/\//, '')}</a></>}
|
||||
</span>
|
||||
<div key={`${p.projectId}:${f.key}`} className="area-row funktionen-row">
|
||||
{index === 0 ? (
|
||||
<span>
|
||||
<strong>{p.projectName}</strong>
|
||||
{p.liveUrl && <><br /><a href={p.liveUrl} target="_blank" rel="noreferrer" className="area-muted">{p.liveUrl.replace(/^https?:\/\//, '')}</a></>}
|
||||
</span>
|
||||
) : (
|
||||
<span className="funktionen-leer" />
|
||||
)}
|
||||
<span>{f.label}</span>
|
||||
<span>
|
||||
<Badge tone={tone}>{label}</Badge>
|
||||
{news.adminNote && <><br /><span className="area-muted" title={news.adminNote}>{short(news.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)} /></>}
|
||||
</span>
|
||||
<span>
|
||||
{feature.ticketMissing
|
||||
? <Badge tone="danger">Ticket fehlt</Badge>
|
||||
: feature.ticketWoid
|
||||
? <Link to={`/tickets?woid=${encodeURIComponent(feature.ticketWoid)}`}>#{feature.ticketWoid}</Link>
|
||||
: '–'}
|
||||
</span>
|
||||
<span>{news.ticketMissing ? <Badge tone="danger">Ticket fehlt</Badge> : news.ticketWoid ? `#${news.ticketWoid}` : '–'}</span>
|
||||
<span className="area-inline">
|
||||
{isConfirming ? (
|
||||
<>
|
||||
<span>{CONFIRM[confirm.action]}</span>
|
||||
<Fragment>
|
||||
<span>{f.bestaetigen[confirm.action]}</span>
|
||||
<input className="form-control" placeholder="Notiz (optional)" maxLength={120} value={note} onChange={(e) => setNote(e.target.value)} style={{ maxWidth: 220 }} />
|
||||
<Button size="sm" variant="primary" onClick={save} disabled={busy}>{busy ? <FaSpinner className="spinner" /> : 'Ja'}</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => setConfirm(null)} disabled={busy}>Abbrechen</Button>
|
||||
</>
|
||||
</Fragment>
|
||||
) : (
|
||||
<>
|
||||
{news.state !== 'active' && (
|
||||
<Button size="sm" variant="ghost" onClick={() => { setNote(''); setConfirm({ projectId: p.projectId, action: 'activate' }) }}>Aktivieren (ohne Zahlung)</Button>
|
||||
)}
|
||||
{['ordered', 'setup', 'active'].includes(news.state) && (
|
||||
<Button size="sm" variant="ghost" onClick={() => { setNote(''); setConfirm({ projectId: p.projectId, action: 'disable' }) }}>Abschalten</Button>
|
||||
)}
|
||||
</>
|
||||
aktionen(feature.state).map((action) => (
|
||||
<Button key={action} size="sm" variant="ghost" onClick={() => ask(p.projectId, f.key, action)}>
|
||||
{action === 'activate' ? 'Aktivieren (ohne Zahlung)' : 'Abschalten'}
|
||||
</Button>
|
||||
))
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
}))}
|
||||
</div>
|
||||
{status && <div className={status.ok ? 'area-message' : 'area-error'} style={{ margin: 12 }}>{status.text}</div>}
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Kunden-Reiter "Funktionen": Register der Funktionen je Projekt (News, Terminplaner), Aktionen je Zustand und
|
||||
// Einarbeiten der Portal-Antwort. Rein, unter Node testbar.
|
||||
|
||||
/** Funktionen in Anzeigereihenfolge; key = Schluessel im Portal (features[key], POST /funktionen/:customerId/:key). */
|
||||
export const FUNKTIONEN = [
|
||||
{
|
||||
key: 'news',
|
||||
label: 'News',
|
||||
bestaetigen: {
|
||||
activate: 'News ohne Zahlung aktivieren?',
|
||||
disable: 'News abschalten? Die Website zeigt dann keine Beiträge mehr.',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'termine',
|
||||
label: 'Terminplaner',
|
||||
bestaetigen: {
|
||||
activate: 'Terminplaner ohne Zahlung aktivieren?',
|
||||
disable: 'Terminplaner abschalten? Auf der Website sind dann keine Buchungen mehr möglich.',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
/** Zustand einer Funktion -> [Ton des Abzeichens, Beschriftung]. */
|
||||
export const ZUSTAND = {
|
||||
off: ['muted', 'Aus'],
|
||||
ordered: ['warn', 'Bestellt'],
|
||||
setup: ['info', 'Einrichtung'],
|
||||
active: ['ok', 'Aktiv'],
|
||||
disabled: ['muted', 'Abgeschaltet'],
|
||||
}
|
||||
|
||||
/** Ansicht einer Funktion im Projekt (Portal: project.features[key]); ohne Eintrag gilt "off". */
|
||||
export const funktionOf = (project, key) => project?.features?.[key] || { state: 'off' }
|
||||
|
||||
/**
|
||||
* Site-Key (Schluessel fuer den Einbau auf der Kundenwebsite) einer Funktion: nur beim Terminplaner und nur, wenn das Portal
|
||||
* einen nennt (feature.siteKey); sonst ''.
|
||||
*/
|
||||
export function siteKeyOf(feature, key) {
|
||||
const wert = feature?.siteKey
|
||||
return key === 'termine' && typeof wert === 'string' ? wert.trim() : ''
|
||||
}
|
||||
|
||||
/** Moegliche Aktionen je Zustand: Aktivieren (ohne Zahlung) ausser bei "active", Abschalten solange bestellt, in Einrichtung oder aktiv. */
|
||||
export function aktionen(state) {
|
||||
const list = []
|
||||
if (state !== 'active') list.push('activate')
|
||||
if (['ordered', 'setup', 'active'].includes(state)) list.push('disable')
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
* Antwort des Portals (das eine geaenderte Projekt) in die Projektliste einarbeiten: uebernommen wird nur
|
||||
* aktualisiert.features[key]; Projektfelder und die anderen Funktionen bleiben. Gibt eine neue Liste zurueck.
|
||||
*/
|
||||
export function mergeFunktion(projects, projectId, key, aktualisiert) {
|
||||
const view = aktualisiert?.features?.[key]
|
||||
if (!view) return projects
|
||||
return projects.map((p) => (p.projectId === projectId ? { ...p, features: { ...p.features, [key]: view } } : p))
|
||||
}
|
||||
+5
-3
@@ -89,11 +89,13 @@ export const listStorageTasks = () => request('/storage-tasks')
|
||||
export const getStorageOpenCount = () => request('/storage-tasks/open-count')
|
||||
export const markStorageDone = (id) => request(`/storage-tasks/${enc(id)}/done`, { method: 'POST', body: {} })
|
||||
|
||||
// --- Funktionen (News-Pinnwand je Projekt) ---
|
||||
// --- Funktionen (News-Pinnwand, Terminplaner je Projekt) ---
|
||||
|
||||
export const getFunktionen = (customerId) => request(`/funktionen/${enc(customerId)}`)
|
||||
export const setNewsFeature = (customerId, { projectId, action, note }) =>
|
||||
request(`/funktionen/${enc(customerId)}/news`, { method: 'POST', body: { projectId, action, note } })
|
||||
/** Aktivieren (ohne Zahlung) oder Abschalten einer Funktion (key: news | termine); Antwort { projects: [das eine Projekt] }. */
|
||||
export const setFeature = (customerId, key, { projectId, action, note }) =>
|
||||
request(`/funktionen/${enc(customerId)}/${enc(key)}`, { method: 'POST', body: { projectId, action, note } })
|
||||
export const setNewsFeature = (customerId, body) => setFeature(customerId, 'news', body)
|
||||
|
||||
// --- Abo-Status (alle Mitarbeiter) ---
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ const LABELS = {
|
||||
news_bild: 'News-Foto hochgeladen',
|
||||
news_geaendert: 'News geändert',
|
||||
news_geloescht: 'News gelöscht',
|
||||
termin_einstellungen: 'Terminplaner-Einstellungen geändert',
|
||||
termin_aktion: 'Termin bearbeitet',
|
||||
termin_manuell: 'Termin eingetragen',
|
||||
}
|
||||
|
||||
/** Deutsche Beschriftung einer Aktion; unbekannte Aktionen erscheinen roh. */
|
||||
|
||||
@@ -21,10 +21,19 @@
|
||||
.area-filter { display: inline-flex; gap: 4px; margin-bottom: var(--sp-3); }
|
||||
.area-inline { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
|
||||
.storage-row { grid-template-columns: 150px 220px minmax(0, 1fr) 150px 110px; }
|
||||
.funktionen-row { grid-template-columns: minmax(0, 1.2fr) 200px 110px minmax(0, 1.4fr); }
|
||||
.funktionen-row { grid-template-columns: minmax(0, 1.2fr) 110px 200px 110px minmax(0, 1.4fr); }
|
||||
.funktionen-sitekey { display: inline-flex; align-items: center; gap: 6px; margin-top: 4px; }
|
||||
.funktionen-sitekey code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--text); user-select: all; }
|
||||
.funktionen-sitekey-copy {
|
||||
display: inline-flex; align-items: center; justify-content: center; width: 25px; height: 25px; flex: 0 0 25px;
|
||||
border: 0; border-radius: 4px; background: transparent; color: var(--text-faint); cursor: pointer;
|
||||
}
|
||||
.funktionen-sitekey-copy:hover { background: var(--surface-2); color: var(--text); }
|
||||
.funktionen-sitekey-ok { color: var(--ok); }
|
||||
@media (max-width: 720px) {
|
||||
.area-row { grid-template-columns: minmax(0, 1fr) !important; }
|
||||
.area-row.head { display: none; }
|
||||
.funktionen-leer { display: none; }
|
||||
.area-kv { grid-template-columns: minmax(0, 1fr); }
|
||||
.area-kv dt { margin-top: 6px; }
|
||||
}
|
||||
@@ -48,4 +57,5 @@
|
||||
@container (max-width: 960px) {
|
||||
.area-row { grid-template-columns: minmax(0, 1fr) !important; gap: 4px; }
|
||||
.area-row.head { display: none; }
|
||||
.funktionen-leer { display: none; }
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { loadModule } from './load.mjs'
|
||||
|
||||
// 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')
|
||||
|
||||
test('FUNKTIONEN: News und Terminplaner mit den Bestätigungstexten', () => {
|
||||
assert.deepEqual(FUNKTIONEN, [
|
||||
{
|
||||
key: 'news',
|
||||
label: 'News',
|
||||
bestaetigen: {
|
||||
activate: 'News ohne Zahlung aktivieren?',
|
||||
disable: 'News abschalten? Die Website zeigt dann keine Beiträge mehr.',
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'termine',
|
||||
label: 'Terminplaner',
|
||||
bestaetigen: {
|
||||
activate: 'Terminplaner ohne Zahlung aktivieren?',
|
||||
disable: 'Terminplaner abschalten? Auf der Website sind dann keine Buchungen mehr möglich.',
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('ZUSTAND: Ton und Beschriftung je Zustand', () => {
|
||||
assert.deepEqual(ZUSTAND, {
|
||||
off: ['muted', 'Aus'],
|
||||
ordered: ['warn', 'Bestellt'],
|
||||
setup: ['info', 'Einrichtung'],
|
||||
active: ['ok', 'Aktiv'],
|
||||
disabled: ['muted', 'Abgeschaltet'],
|
||||
})
|
||||
})
|
||||
|
||||
test('funktionOf: Ansicht der Funktion, ohne Eintrag "off"', () => {
|
||||
const project = { projectId: 'p1', features: { news: { state: 'active', ticketWoid: '100' }, termine: { state: 'setup' } } }
|
||||
assert.deepEqual(funktionOf(project, 'news'), { state: 'active', ticketWoid: '100' })
|
||||
assert.deepEqual(funktionOf(project, 'termine'), { state: 'setup' })
|
||||
assert.deepEqual(funktionOf({ projectId: 'p2', features: { news: { state: 'active' } } }, 'termine'), { state: 'off' })
|
||||
assert.deepEqual(funktionOf({ projectId: 'p3' }, 'news'), { state: 'off' })
|
||||
assert.deepEqual(funktionOf(null, 'news'), { state: 'off' })
|
||||
})
|
||||
|
||||
test('aktionen: Aktivieren außer bei "active", Abschalten bei Bestellt, Einrichtung und Aktiv', () => {
|
||||
assert.deepEqual(aktionen('off'), ['activate'])
|
||||
assert.deepEqual(aktionen('ordered'), ['activate', 'disable'])
|
||||
assert.deepEqual(aktionen('setup'), ['activate', 'disable'])
|
||||
assert.deepEqual(aktionen('active'), ['disable'])
|
||||
assert.deepEqual(aktionen('disabled'), ['activate'])
|
||||
assert.deepEqual(aktionen(undefined), ['activate'])
|
||||
})
|
||||
|
||||
const projects = () => [
|
||||
{ projectId: 'p1', projectName: 'Eins', features: { news: { state: 'off' }, termine: { state: 'active', siteKey: 'abc' } } },
|
||||
{ projectId: 'p2', projectName: 'Zwei', features: { news: { state: 'active' }, termine: { state: 'off' } } },
|
||||
]
|
||||
|
||||
test('mergeFunktion: übernimmt nur die gewählte Funktion, die andere bleibt', () => {
|
||||
const antwort = {
|
||||
projectId: 'p1',
|
||||
projectName: 'Eins (neu)',
|
||||
features: { news: { state: 'setup', ticketWoid: '7' }, termine: { state: 'off' } },
|
||||
}
|
||||
const merged = mergeFunktion(projects(), 'p1', 'news', antwort)
|
||||
assert.deepEqual(merged[0].features.news, { state: 'setup', ticketWoid: '7' })
|
||||
assert.deepEqual(merged[0].features.termine, { state: 'active', siteKey: 'abc' })
|
||||
assert.equal(merged[0].projectName, 'Eins')
|
||||
assert.deepEqual(merged[1], projects()[1])
|
||||
})
|
||||
|
||||
test('mergeFunktion: Terminplaner überschreibt News nicht', () => {
|
||||
const antwort = { projectId: 'p2', features: { news: { state: 'off' }, termine: { state: 'ordered' } } }
|
||||
const merged = mergeFunktion(projects(), 'p2', 'termine', antwort)
|
||||
assert.deepEqual(merged[1].features, { news: { state: 'active' }, termine: { state: 'ordered' } })
|
||||
})
|
||||
|
||||
test('mergeFunktion: verändert die Eingabe nicht, ohne Antwort oder ohne Funktion bleibt alles', () => {
|
||||
const list = projects()
|
||||
const merged = mergeFunktion(list, 'p1', 'news', { projectId: 'p1', features: { news: { state: 'active' } } })
|
||||
assert.notEqual(merged, list)
|
||||
assert.deepEqual(list, projects())
|
||||
assert.deepEqual(mergeFunktion(list, 'p1', 'news', undefined), projects())
|
||||
assert.deepEqual(mergeFunktion(list, 'p1', 'news', { projectId: 'p1', features: {} }), projects())
|
||||
assert.deepEqual(mergeFunktion(list, 'unbekannt', 'news', { features: { news: { state: 'active' } } }), projects())
|
||||
})
|
||||
|
||||
test('mergeFunktion: Projekt ohne bisherige features bekommt die Funktion', () => {
|
||||
const merged = mergeFunktion([{ projectId: 'p9' }], 'p9', 'termine', { features: { termine: { state: 'setup' } } })
|
||||
assert.deepEqual(merged, [{ projectId: 'p9', features: { termine: { state: 'setup' } } }])
|
||||
})
|
||||
|
||||
test('siteKeyOf: nur der Terminplaner zeigt seinen Site-Key, nur wenn das Portal einen nennt', () => {
|
||||
assert.equal(siteKeyOf({ state: 'active', siteKey: 'abcdefghijklmnop' }, 'termine'), 'abcdefghijklmnop')
|
||||
assert.equal(siteKeyOf({ state: 'active', siteKey: ' abcdefghijklmnop ' }, 'termine'), 'abcdefghijklmnop', 'getrimmt')
|
||||
assert.equal(siteKeyOf({ state: 'active', siteKey: 'abcdefghijklmnop' }, 'news'), '', 'News: kein Site-Key in der Tabelle')
|
||||
for (const feature of [{ state: 'off' }, { state: 'active', siteKey: null }, { state: 'active', siteKey: '' }, { state: 'active', siteKey: 5 }, null, undefined]) {
|
||||
assert.equal(siteKeyOf(feature, 'termine'), '', JSON.stringify(feature))
|
||||
}
|
||||
})
|
||||
@@ -5,10 +5,16 @@ import { loadModule } from './load.mjs'
|
||||
// Protokoll der Kundenansicht: Aktionen des Portals (customerViewLog.action) als deutsche Beschriftung
|
||||
const { viewLogLabel, formatViewLogTime } = await loadModule('src/lib/viewLog.js')
|
||||
|
||||
test('viewLogLabel: alle sieben Aktionen und Rohwert', () => {
|
||||
test('viewLogLabel: alle zehn Aktionen und Rohwert', () => {
|
||||
assert.deepEqual(
|
||||
['geoeffnet', 'beendet', 'abgelaufen', 'news_angelegt', 'news_bild', 'news_geaendert', 'news_geloescht'].map(viewLogLabel),
|
||||
['Kundenansicht geöffnet', 'Kundenansicht beendet', 'Kundenansicht abgelaufen', 'News angelegt', 'News-Foto hochgeladen', 'News geändert', 'News gelöscht']
|
||||
[
|
||||
'geoeffnet', 'beendet', 'abgelaufen', 'news_angelegt', 'news_bild', 'news_geaendert', 'news_geloescht',
|
||||
'termin_einstellungen', 'termin_aktion', 'termin_manuell',
|
||||
].map(viewLogLabel),
|
||||
[
|
||||
'Kundenansicht geöffnet', 'Kundenansicht beendet', 'Kundenansicht abgelaufen', 'News angelegt', 'News-Foto hochgeladen', 'News geändert', 'News gelöscht',
|
||||
'Terminplaner-Einstellungen geändert', 'Termin bearbeitet', 'Termin eingetragen',
|
||||
]
|
||||
)
|
||||
assert.equal(viewLogLabel('neu_irgendwas'), 'neu_irgendwas')
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user