Öffnungszeiten: Team-Panel, Ersatz-HTML und Rechtliches-Karte
Panel unter der Zeile Öffnungszeiten live (Header-Selektor, Kontakt-/Hinweis-Leiste aus, Speichern mit teamVersion, Ersatz-HTML kopieren je Plan), Meldung zum Ersatz-Ticket nach dem Abschalten, Hindernis an aktionen, Statustext der Zeiten, Team-API (GET ersatz, PATCH team), Karte Leistungsbeschreibung Öffnungszeiten live in setup-legal (Schlüssel höchstens 36 Zeichen). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
994347408d
commit
3d67aa88cf
10 files changed
+498
-6
No files matched your search
@@ -10,6 +10,8 @@ import {
|
||||
} from '../../lib/funktionenView'
|
||||
import { CHANNELS, channelLabel } from '../../lib/analyseView'
|
||||
import { berlinYmd } from '../../lib/format'
|
||||
import { zeitenStatusText, mergeTeam, abschaltMeldung } from '../../lib/zeitenView'
|
||||
import ZeitenPanel from './ZeitenPanel'
|
||||
import '../../styles/admin-areas.css'
|
||||
|
||||
// Reiter "Funktionen": News-Pinnwand und Terminplaner je Projekt an/aus wie im frueheren Admin-Panel (Kundenliste, Zeile "Funktionen")
|
||||
@@ -186,7 +188,7 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
setStatus(null)
|
||||
try {
|
||||
const mitTextform = textformNoetig(key, action, termine)
|
||||
const { projects: changed } = await setFeature(customerId, key, {
|
||||
const { projects: changed, ersatzTicket } = await setFeature(customerId, key, {
|
||||
projectId, action, note: note.trim(),
|
||||
...(mitTextform ? { textformDate: textform.date, textformChannel: textform.channel } : {}),
|
||||
})
|
||||
@@ -196,7 +198,8 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
setConfirm(null)
|
||||
setNote('')
|
||||
setTextform(LEERE_TEXTFORM)
|
||||
setStatus({ ok: true, text: 'Gespeichert.' })
|
||||
// Abschalten der Zeiten: das Portal meldet, ob das Ticket "Ersatz-Tabelle aktualisieren" angelegt wurde (bei 'fehler' warnen)
|
||||
setStatus(key === 'zeiten' && action === 'disable' ? abschaltMeldung(ersatzTicket) : { ok: true, text: 'Gespeichert.' })
|
||||
} catch (err) {
|
||||
setConfirm(null)
|
||||
setStatus({ ok: false, text: err.message })
|
||||
@@ -207,6 +210,7 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
}
|
||||
}
|
||||
|
||||
const teamGespeichert = (projectId, antwort) => setProjects((list) => mergeTeam(list, projectId, antwort))
|
||||
const ask = (projectId, key, action) => { setNote(''); setTextform(LEERE_TEXTFORM); setConfirm({ projectId, key, action }) }
|
||||
const heute = berlinYmd(new Date())
|
||||
const panelOffen = (projectId) => (Object.prototype.hasOwnProperty.call(panel, projectId) ? panel[projectId] : anleitungOffen(window.location.search, projectId))
|
||||
@@ -228,6 +232,8 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
const anleitungOffenHier = hatAnleitung && panelOffen(p.projectId)
|
||||
const braucheTextform = isConfirming && textformNoetig(f.key, confirm.action, termine)
|
||||
const textformFehlt = braucheTextform && !textformBereit({ ...textform, today: heute }, termine)
|
||||
const hatZeitenPanel = f.key === 'zeiten' && feature.state !== 'off'
|
||||
const zeitenText = f.key === 'zeiten' ? zeitenStatusText(feature) : ''
|
||||
return (
|
||||
<Fragment key={`${p.projectId}:${f.key}`}>
|
||||
<div className="area-row funktionen-row">
|
||||
@@ -243,6 +249,7 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
<span>
|
||||
<Badge tone={tone}>{label}</Badge>
|
||||
{feature.state === 'setup' && hindernisText(feature.hindernis) && <><br /><span className="area-warn">{hindernisText(feature.hindernis)}</span></>}
|
||||
{zeitenText && <><br /><span className={feature.hindernis ? 'area-warn' : 'area-muted'}>{zeitenText}</span></>}
|
||||
{abgeschaltetText(feature) && <><br /><span className="area-muted">{abgeschaltetText(feature)}</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)} /></>}
|
||||
@@ -264,7 +271,7 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
{aktionen(feature.state, f.key, { ticketMissing: feature.ticketMissing }).map((action) => (
|
||||
{aktionen(feature.state, f.key, { ticketMissing: feature.ticketMissing, hindernis: feature.hindernis }).map((action) => (
|
||||
<Button key={action} size="sm" variant="ghost" onClick={() => ask(p.projectId, f.key, action)}>
|
||||
{AKTION_LABEL[action]}
|
||||
</Button>
|
||||
@@ -281,6 +288,9 @@ export default function CustomerFunktionen({ customerId }) {
|
||||
{braucheTextform && (
|
||||
<TextformPanel termine={termine} value={textform} onChange={setTextform} projectId={p.projectId} today={heute} disabled={busy} />
|
||||
)}
|
||||
{hatZeitenPanel && (
|
||||
<ZeitenPanel customerId={customerId} projectId={p.projectId} feature={feature} onSaved={teamGespeichert} onReload={load} />
|
||||
)}
|
||||
{anleitungOffenHier && (
|
||||
<AnleitungPanel customerId={customerId} projectId={p.projectId} feature={feature} reloadKey={panelReload[p.projectId] || 0} />
|
||||
)}
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { FaCopy, FaSpinner } from 'react-icons/fa6'
|
||||
import { Button } from '../ui'
|
||||
import { getZeitenErsatz, patchZeitenTeam } from '../../lib/teamApi'
|
||||
import {
|
||||
KEINE_ZEITEN_TEXT, ersatzKnoepfe, ersatzKopieren, ersatzMeldung, navSelektorFehler, teamBody, teamFormular, teamGeaendert,
|
||||
teamSpeichernFehler,
|
||||
} from '../../lib/zeitenView'
|
||||
|
||||
// Panel "Öffnungszeiten live" unter der Zeile im Reiter "Funktionen": Team-Einstellungen der Website-Leisten (nur das Team ändert sie)
|
||||
// und "Ersatz-HTML kopieren" je Plan. Entscheidungen, Texte und Prüfungen stehen in zeitenView (rein, getestet); hier wird nur verdrahtet.
|
||||
// Texte stehen ausschließlich als React-Textknoten da (nie als HTML). Die Hülle (area-row funktionen-panel) liegt als eigene Zeile außerhalb
|
||||
// des Fünf-Spalten-Rasters, wie die Einbauanleitung des Terminplaners.
|
||||
export default function ZeitenPanel({ customerId, projectId, feature, onSaved, onReload }) {
|
||||
const start = teamFormular(feature)
|
||||
const [form, setForm] = useState(start)
|
||||
const [busy, setBusy] = useState(false)
|
||||
const [meldung, setMeldung] = useState(null) // { ok, text, neuLaden? } zum Speichern
|
||||
const [ersatz, setErsatz] = useState({ kurz: null, busy: false, meldung: null, html: '' })
|
||||
|
||||
// Neuer Stand vom Server (nach dem Speichern oder Neuladen): Formular auf die gespeicherten Werte setzen
|
||||
useEffect(() => {
|
||||
setForm({ navSelektor: start.navSelektor, kontaktleisteAus: start.kontaktleisteAus, hinweisleisteAus: start.hinweisleisteAus })
|
||||
}, [feature.teamVersion, start.navSelektor, start.kontaktleisteAus, start.hinweisleisteAus])
|
||||
|
||||
// Eine Eingabe macht "Gespeichert." und alte Fehler unpassend; die Aufforderung zum Neuladen bleibt, bis sie befolgt wird
|
||||
const aendern = (teil) => {
|
||||
setMeldung((m) => (m?.neuLaden ? m : null))
|
||||
setForm((f) => ({ ...f, ...teil }))
|
||||
}
|
||||
const selektorFehler = navSelektorFehler(form.navSelektor)
|
||||
const geaendert = teamGeaendert(form, feature)
|
||||
|
||||
const speichern = async (event) => {
|
||||
event.preventDefault()
|
||||
if (selektorFehler || !geaendert) return
|
||||
setBusy(true)
|
||||
setMeldung(null)
|
||||
try {
|
||||
const antwort = await patchZeitenTeam(customerId, teamBody({ projectId, teamVersion: feature.teamVersion, ...form }))
|
||||
onSaved(projectId, antwort)
|
||||
setMeldung({ ok: true, text: 'Gespeichert.' })
|
||||
} catch (err) {
|
||||
const { text, neuLaden } = teamSpeichernFehler(err)
|
||||
setMeldung({ ok: false, text, neuLaden })
|
||||
} finally {
|
||||
setBusy(false)
|
||||
}
|
||||
}
|
||||
|
||||
const kopieren = async ({ kurz, bezeichnung }) => {
|
||||
setErsatz({ kurz, busy: true, meldung: null, html: '' })
|
||||
try {
|
||||
const { html } = await getZeitenErsatz(customerId, projectId, kurz)
|
||||
const ergebnis = await ersatzKopieren(html, typeof navigator === 'undefined' ? undefined : navigator.clipboard)
|
||||
setErsatz({ kurz, busy: false, meldung: ersatzMeldung(ergebnis, feature.plaene?.length > 1 ? bezeichnung : ''), html: ergebnis === 'manuell' ? html : '' })
|
||||
} catch (err) {
|
||||
setErsatz({ kurz, busy: false, meldung: { ok: false, text: err?.message || 'Ersatz-HTML konnte nicht geladen werden.' }, html: '' })
|
||||
}
|
||||
}
|
||||
|
||||
const knoepfe = ersatzKnoepfe(feature)
|
||||
|
||||
return (
|
||||
<div className="area-row funktionen-panel funktionen-panel-zeiten">
|
||||
<div className="funktionen-panel-head">
|
||||
<strong>Öffnungszeiten live</strong>
|
||||
<span className="area-muted">Einstellungen des Teams (der Kunde sieht und ändert sie nicht)</span>
|
||||
</div>
|
||||
|
||||
<form onSubmit={speichern} className="funktionen-panel-form">
|
||||
<label className="funktionen-panel-feld">
|
||||
<span>Header-Selektor</span>
|
||||
<input
|
||||
className="form-control"
|
||||
value={form.navSelektor}
|
||||
onChange={(e) => aendern({ navSelektor: e.target.value })}
|
||||
disabled={busy}
|
||||
placeholder="z. B. header.site-header (leer = Standard)"
|
||||
spellCheck={false}
|
||||
autoComplete="off"
|
||||
aria-invalid={selektorFehler ? 'true' : 'false'}
|
||||
/>
|
||||
</label>
|
||||
<div className="area-muted">
|
||||
Nur nötig, wenn die Hinweis-Leiste den festen Header der Website überlappt: CSS-Selektor des Headers.
|
||||
</div>
|
||||
{selektorFehler && <div className="area-error" role="alert">{selektorFehler}</div>}
|
||||
<div className="area-inline">
|
||||
<label className="area-inline">
|
||||
<input type="checkbox" checked={form.kontaktleisteAus} disabled={busy} onChange={(e) => aendern({ kontaktleisteAus: e.target.checked })} />
|
||||
Kontakt-Leiste aus
|
||||
</label>
|
||||
<label className="area-inline">
|
||||
<input type="checkbox" checked={form.hinweisleisteAus} disabled={busy} onChange={(e) => aendern({ hinweisleisteAus: e.target.checked })} />
|
||||
Hinweis-Leiste aus
|
||||
</label>
|
||||
</div>
|
||||
<div className="area-inline">
|
||||
<Button type="submit" size="sm" variant="primary" disabled={busy || Boolean(selektorFehler) || !geaendert}>
|
||||
{busy ? <FaSpinner className="spinner" /> : 'Speichern'}
|
||||
</Button>
|
||||
{meldung && <span role="status" className={meldung.ok ? 'area-message funktionen-panel-meldung' : 'area-error funktionen-panel-meldung'}>{meldung.text}</span>}
|
||||
{meldung?.neuLaden && (
|
||||
<Button type="button" size="sm" variant="ghost" onClick={() => { setMeldung(null); onReload() }}>Neu laden</Button>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div className="funktionen-panel-head">
|
||||
<strong>Ersatz-HTML</strong>
|
||||
<span className="area-muted">Wochentabelle des Plans für den Platzhalter der Website (Einbau)</span>
|
||||
</div>
|
||||
{knoepfe.length === 0 ? (
|
||||
<div className="area-muted">{KEINE_ZEITEN_TEXT}</div>
|
||||
) : (
|
||||
<div className="area-inline">
|
||||
{knoepfe.map((k) => (
|
||||
<Button key={k.kurz} type="button" size="sm" variant="ghost" disabled={ersatz.busy} onClick={() => kopieren(k)} title={`Plan „${k.kurz}“`}>
|
||||
{ersatz.busy && ersatz.kurz === k.kurz ? <FaSpinner className="spinner" /> : <FaCopy />} {k.label}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{ersatz.meldung && <div role="status" className={ersatz.meldung.ok ? 'area-message' : 'area-error'}>{ersatz.meldung.text}</div>}
|
||||
{ersatz.html && (
|
||||
<textarea
|
||||
readOnly
|
||||
className="form-control funktionen-panel-html"
|
||||
rows={8}
|
||||
value={ersatz.html}
|
||||
aria-label="Ersatz-HTML zum Kopieren"
|
||||
onFocus={(e) => e.target.select()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -60,6 +60,9 @@ export function createFinanceRequest({ baseUrl, createJWT, fetchImpl = (...args)
|
||||
// Nach dem Festschreiben (502) liegt die Rechnung schon als "offen" vor
|
||||
if (info.invoiceId) err.invoiceId = info.invoiceId
|
||||
if (info.invoiceNumber) err.invoiceNumber = info.invoiceNumber
|
||||
// Team-Einstellungen der Oeffnungszeiten (PATCH): code (409 ZEITEN_VERALTET) und fehler je Feld (400)
|
||||
if (typeof info.code === 'string') err.code = info.code
|
||||
if (info.fehler && typeof info.fehler === 'object') err.fehler = info.fehler
|
||||
throw err
|
||||
}
|
||||
return data ?? {}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { account } from './appwrite'
|
||||
import { createFinanceRequest } from './financeRequest'
|
||||
import { createJwtCache } from './serverpulsAuth'
|
||||
import { anleitungPfad, featureBody } from './funktionenView'
|
||||
import { ersatzPfad, zeitenTeamPfad } from './zeitenView'
|
||||
|
||||
/**
|
||||
* Client fuer den Team-Eingang des Kundenportals (/api/team): alle Mitarbeiter, Appwrite-JWT (Bearer).
|
||||
@@ -103,6 +104,13 @@ export const setNewsFeature = (customerId, body) => setFeature(customerId, 'news
|
||||
/** Einbauanleitung des Terminplaners: { stand, warnungen, abschnitte, text, state, ticketWoid, ticketVersion, hindernis } (Aufbereitung: panelModell). */
|
||||
export const getTermineAnleitung = (customerId, projectId) => request(anleitungPfad(customerId, projectId))
|
||||
|
||||
// --- Oeffnungszeiten live (Funktion zeiten): Ersatz-HTML und Team-Einstellungen ---
|
||||
|
||||
/** Ersatz-HTML (Wochentabelle) eines Plans fuer den Einbau: { html }; 404 unbekannter Plan, 409 "Der Kunde hat noch keine Zeiten eingetragen." */
|
||||
export const getZeitenErsatz = (customerId, projectId, plan) => request(ersatzPfad(customerId, projectId, plan))
|
||||
/** Team-Einstellungen (Rumpf: teamBody in zeitenView): { team, teamVersion }; 400 ungueltig, 409 veraltet (err.code ZEITEN_VERALTET). PATCH, weil das Ticketsystem kein PUT durchlaesst. */
|
||||
export const patchZeitenTeam = (customerId, body) => request(zeitenTeamPfad(customerId), { method: 'PATCH', body })
|
||||
|
||||
// --- Abo-Status (alle Mitarbeiter) ---
|
||||
|
||||
/** { productKey, active, status, storageUnits, startedAt, serviceStart, currentPeriodEnd, cancelAt } */
|
||||
|
||||
@@ -75,3 +75,112 @@ export function teamBody({ projectId, teamVersion, navSelektor, kontaktleisteAus
|
||||
hinweisleisteAus: hinweisleisteAus === true,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Panel unter der Zeile "Oeffnungszeiten live": Pfade der Team-Routen, Formular, Speichern, Ersatz-HTML, Meldung nach dem Abschalten ---
|
||||
|
||||
const enc = encodeURIComponent
|
||||
|
||||
/** Pfad (relativ zu /api/team) von GET .../zeiten/ersatz; ohne plan faellt der Parameter weg (Hauptplan), ein leerer plan bleibt stehen (Portal: 404). */
|
||||
export const ersatzPfad = (customerId, projectId, plan) =>
|
||||
`/funktionen/${enc(customerId)}/zeiten/ersatz?projectId=${enc(projectId)}${plan === undefined || plan === null ? '' : `&plan=${enc(plan)}`}`
|
||||
|
||||
/** Pfad (relativ zu /api/team) von PATCH .../zeiten/team. */
|
||||
export const zeitenTeamPfad = (customerId) => `/funktionen/${enc(customerId)}/zeiten/team`
|
||||
|
||||
/** Startwerte des Team-Formulars aus feature.team: Selektor als Text (null => leeres Feld), Schalter nur bei true an. */
|
||||
export function teamFormular(feature) {
|
||||
const team = feature?.team
|
||||
return {
|
||||
navSelektor: typeof team?.navSelektor === 'string' ? team.navSelektor : '',
|
||||
kontaktleisteAus: team?.kontaktleisteAus === true,
|
||||
hinweisleisteAus: team?.hinweisleisteAus === true,
|
||||
}
|
||||
}
|
||||
|
||||
/** true, wenn das Formular vom gespeicherten Stand (feature.team) abweicht; Leerraum um den Selektor zaehlt nicht. */
|
||||
export function teamGeaendert(form, feature) {
|
||||
const start = teamFormular(feature)
|
||||
return (
|
||||
String(form?.navSelektor ?? '').trim() !== start.navSelektor.trim() ||
|
||||
(form?.kontaktleisteAus === true) !== start.kontaktleisteAus ||
|
||||
(form?.hinweisleisteAus === true) !== start.hinweisleisteAus
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Antwort des Team-PATCH ({ team, teamVersion }) in die Projektliste einarbeiten: nur team und teamVersion von features.zeiten dieses
|
||||
* Projekts; alles andere bleibt. Eine unbrauchbare Antwort oder ein Projekt ohne Zeiten aendert nichts (gleiche Liste zurueck).
|
||||
*/
|
||||
export function mergeTeam(projects, projectId, antwort) {
|
||||
const { team, teamVersion } = antwort || {}
|
||||
if (!team || typeof team !== 'object' || !Number.isInteger(teamVersion)) return projects
|
||||
return projects.map((p) => (p.projectId === projectId && p.features?.zeiten ? { ...p, features: { ...p.features, zeiten: { ...p.features.zeiten, team, teamVersion } } } : p))
|
||||
}
|
||||
|
||||
const VERALTET_TEXT = 'Inzwischen geändert – bitte neu laden.'
|
||||
|
||||
/**
|
||||
* Fehler des Team-PATCH -> { text, neuLaden }. 409 ZEITEN_VERALTET: "Inzwischen geändert – bitte neu laden." (die Ansicht ist veraltet,
|
||||
* neuLaden); jeder andere 409 (z. B. Funktion nicht eingeschaltet) zeigt den Text des Portals und gilt ebenfalls als veraltet. Alles andere
|
||||
* (400, Netz) zeigt seinen Text unveraendert.
|
||||
*/
|
||||
export function teamSpeichernFehler(err) {
|
||||
if (err?.status === 409) return { text: err.code === 'ZEITEN_VERALTET' ? VERALTET_TEXT : err.message || VERALTET_TEXT, neuLaden: true }
|
||||
return { text: err?.message || 'Speichern fehlgeschlagen.', neuLaden: false }
|
||||
}
|
||||
|
||||
/** Text des Portals (409 von GET .../zeiten/ersatz), auch als Hinweis statt Knopf, solange feature.plaene leer ist. */
|
||||
export const KEINE_ZEITEN_TEXT = 'Der Kunde hat noch keine Zeiten eingetragen.'
|
||||
|
||||
/**
|
||||
* Knoepfe "Ersatz-HTML kopieren" je Plan aus feature.plaene (Portal: [{ kurz, name, haupt }], Hauptplan zuerst): { kurz, label, bezeichnung }.
|
||||
* Ein Plan: schlicht "Ersatz-HTML kopieren"; mehrere: mit "Name (kurz)" dahinter. Plaene ohne Kurznamen werden uebergangen.
|
||||
*/
|
||||
export function ersatzKnoepfe(feature) {
|
||||
const plaene = (Array.isArray(feature?.plaene) ? feature.plaene : []).filter((p) => p && typeof p.kurz === 'string' && p.kurz !== '')
|
||||
return plaene.map((p) => {
|
||||
const bezeichnung = typeof p.name === 'string' && p.name !== '' && p.name !== p.kurz ? `${p.name} (${p.kurz})` : p.kurz
|
||||
return { kurz: p.kurz, label: plaene.length > 1 ? `Ersatz-HTML kopieren: ${bezeichnung}` : 'Ersatz-HTML kopieren', bezeichnung }
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Schreibt das Ersatz-HTML in die Zwischenablage (clipboard = navigator.clipboard). 'kopiert'; 'manuell', wenn es die API nicht gibt oder der
|
||||
* Browser das Schreiben ablehnt (dann zeigt das Panel das HTML zum Markieren); 'leer' ohne Text (nie eine leere Zwischenablage melden).
|
||||
*/
|
||||
export async function ersatzKopieren(html, clipboard) {
|
||||
if (typeof html !== 'string' || html === '') return 'leer'
|
||||
if (!clipboard || typeof clipboard.writeText !== 'function') return 'manuell'
|
||||
try {
|
||||
await clipboard.writeText(html)
|
||||
return 'kopiert'
|
||||
} catch {
|
||||
return 'manuell'
|
||||
}
|
||||
}
|
||||
|
||||
export const ZWISCHENABLAGE_FEHLT = 'Die Zwischenablage ist in diesem Browser nicht verfügbar. Bitte das HTML unten markieren und von Hand kopieren.'
|
||||
|
||||
/** Sichtbare Rueckmeldung zum Kopieren: { ok, text }; bezeichnung (Plan) wird bei mehreren Plaenen genannt. */
|
||||
export function ersatzMeldung(ergebnis, bezeichnung = '') {
|
||||
if (ergebnis === 'kopiert') return { ok: true, text: bezeichnung ? `Ersatz-HTML kopiert: ${bezeichnung}.` : 'Ersatz-HTML kopiert.' }
|
||||
if (ergebnis === 'manuell') return { ok: false, text: ZWISCHENABLAGE_FEHLT }
|
||||
return { ok: false, text: 'Es gibt kein Ersatz-HTML zum Kopieren.' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Meldung nach dem Abschalten von Oeffnungszeiten live aus ersatzTicket der Portal-Antwort: 'angelegt' (Ticket "Ersatz-Tabelle aktualisieren"),
|
||||
* 'aktuell' (nichts zu tun), 'fehler' (Ticket nicht angelegt: Warnung, denn ein zweites Abschalten gibt es nicht, das Team legt es von Hand an).
|
||||
* Ohne oder mit unbekanntem Wert schlicht "Gespeichert.".
|
||||
*/
|
||||
export function abschaltMeldung(ersatzTicket) {
|
||||
if (ersatzTicket === 'angelegt') return { ok: true, text: 'Gespeichert. Ticket „Ersatz-Tabelle aktualisieren“ angelegt.' }
|
||||
if (ersatzTicket === 'aktuell') return { ok: true, text: 'Gespeichert. Die Ersatz-Tabelle ist aktuell, kein Ticket nötig.' }
|
||||
if (ersatzTicket === 'fehler') {
|
||||
return {
|
||||
ok: false,
|
||||
text: 'Gespeichert, aber das Ticket „Ersatz-Tabelle aktualisieren“ konnte nicht angelegt werden. Bitte von Hand anlegen: Die feste Ersatz-Tabelle der Website muss zum Wochenplan des Kunden passen (Ersatz-HTML dazu im Panel unter der Zeile kopieren).',
|
||||
}
|
||||
}
|
||||
return { ok: true, text: 'Gespeichert.' }
|
||||
}
|
||||
@@ -34,6 +34,10 @@
|
||||
.funktionen-panel-head { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-3); align-items: center; }
|
||||
.funktionen-panel-warnungen { margin: 0; padding-left: 32px; }
|
||||
.funktionen-panel .area-summary { margin-bottom: 0; }
|
||||
.funktionen-panel-zeiten .area-error, .funktionen-panel-zeiten .area-message { margin: 0; }
|
||||
.funktionen-panel-form { display: flex; flex-direction: column; gap: var(--sp-2); }
|
||||
.funktionen-panel-feld { display: flex; flex-direction: column; gap: 4px; max-width: 520px; }
|
||||
.funktionen-panel-html { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; min-height: 120px; }
|
||||
.funktionen-panel-abschnitt { border: 1px solid var(--border); border-radius: var(--r-sm); background: var(--surface-1); min-width: 0; }
|
||||
.funktionen-panel-abschnitt > summary { padding: 8px 12px; cursor: pointer; font-weight: 600; }
|
||||
.funktionen-panel-abschnitt pre {
|
||||
|
||||
Reference in new issue
Block a user