import { Fragment, useCallback, useEffect, useRef, useState } from 'react'
import { Link } from 'react-router-dom'
import { FaCheck, FaCopy, FaRotateRight, FaSpinner } from 'react-icons/fa6'
import { Card, Badge, Button, EmptyState } from '../ui'
import { copyText } from '../CopyableCredential'
import { getFunktionen, setFeature, getTermineAnleitung } from '../../lib/teamApi'
import {
FUNKTIONEN, ZUSTAND, AKTION_LABEL, funktionOf, aktionen, mergeFunktion, siteKeyOf, abgeschaltetText,
hindernisText, panelModell, anleitungOffen,
} from '../../lib/funktionenView'
import '../../styles/admin-areas.css'
// 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 (
Site-Key
{value}
)
}
// Kopierknopf mit "Kopiert"-Rueckmeldung (wie SiteKey)
function KopierKnopf({ text, label }) {
const [copied, setCopied] = useState(false)
const timer = useRef(null)
useEffect(() => () => clearTimeout(timer.current), [])
const copy = async () => {
await copyText(text)
setCopied(true)
clearTimeout(timer.current)
timer.current = setTimeout(() => setCopied(false), 1800)
}
return (
)
}
// Einbauanleitung des Terminplaners unter der Zeile. Inhalte und Entscheidungen kommen aus funktionenView (panelModell); Texte
// stehen ausschliesslich als React-Textknoten da (nie als HTML oder Link). Laedt beim Oeffnen und bei jedem neuen reloadKey
// (nach einer Aktion aendern sich Version und Hindernis) neu.
function AnleitungPanel({ customerId, projectId, feature, reloadKey }) {
const [antwort, setAntwort] = useState(null)
const [fehler, setFehler] = useState(null)
const [retry, setRetry] = useState(0)
useEffect(() => {
let aktuell = true
setAntwort(null)
setFehler(null)
getTermineAnleitung(customerId, projectId)
.then((res) => { if (aktuell) setAntwort(res) })
.catch((err) => { if (aktuell) setFehler(err?.message || 'Anleitung konnte nicht geladen werden.') })
return () => { aktuell = false }
}, [customerId, projectId, reloadKey, retry])
const hindernis = hindernisText(feature.hindernis)
if (fehler || !antwort) {
return (
{fehler ?
{fehler}
:
}
{hindernis &&
{hindernis}
}
{fehler &&
}
)
}
const modell = panelModell(antwort, feature)
return (
Einbauanleitung
{modell.kopf && {modell.kopf}}
{modell.ticketPfad && Zum Ticket}
{modell.warnungen.length > 0 && (
{modell.warnungen.map((text, i) => - {text}
)}
)}
{modell.hinweis &&
{modell.hinweis}
}
{modell.kopien.length > 0 && (
{modell.kopien.map((k) => )}
)}
{modell.abschnitte.map((a) => (
{a.titel}
{a.text}
))}
)
}
export default function CustomerFunktionen({ customerId }) {
const [projects, setProjects] = useState(null)
const [error, setError] = useState(null)
const [confirm, setConfirm] = useState(null) // { projectId, key, action }
const [note, setNote] = useState('')
const [busy, setBusy] = useState(false)
const [status, setStatus] = useState(null) // { ok, text }
const [panel, setPanel] = useState({}) // projectId -> true/false (Knopf "Anleitung"); ohne Eintrag entscheidet die Adresse (?anleitung=)
const [panelReload, setPanelReload] = useState({}) // projectId -> Zaehler, nach jeder Aktion hochgezaehlt
const load = useCallback(() => {
setError(null)
getFunktionen(customerId)
.then(({ projects: list }) => setProjects(list || []))
.catch((err) => setError(err.message || 'Funktionen konnten nicht geladen werden.'))
}, [customerId])
useEffect(() => { load() }, [load])
const save = async () => {
const { projectId, key, action } = confirm
setBusy(true)
setStatus(null)
try {
const { projects: changed } = await setFeature(customerId, key, { projectId, action, note: note.trim() })
// Die Antwort enthaelt das ganze Projekt; uebernommen wird nur die geaenderte Funktion
const updated = changed?.[0]
if (updated) setProjects((list) => mergeFunktion(list, updated.projectId, key, updated))
setConfirm(null)
setNote('')
setStatus({ ok: true, text: 'Gespeichert.' })
} catch (err) {
setConfirm(null)
setStatus({ ok: false, text: err.message })
} finally {
setBusy(false)
// Version und Hindernis der Anleitung haengen vom Zustand ab: Panel dieser Zeile neu laden
if (key === 'termine') setPanelReload((r) => ({ ...r, [projectId]: (r[projectId] || 0) + 1 }))
}
}
const ask = (projectId, key, action) => { setNote(''); setConfirm({ projectId, key, action }) }
const panelOffen = (projectId) => (Object.prototype.hasOwnProperty.call(panel, projectId) ? panel[projectId] : anleitungOffen(window.location.search, projectId))
const togglePanel = (projectId) => setPanel((p) => ({ ...p, [projectId]: !panelOffen(projectId) }))
if (error) return {error}
if (!projects) return
if (!projects.length) return
return (
ProjektFunktionStatusTicketAktionen
{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
const hatAnleitung = f.key === 'termine' && feature.state !== 'off'
const anleitungOffenHier = hatAnleitung && panelOffen(p.projectId)
return (
{index === 0 ? (
{p.projectName}
{p.liveUrl && <>
{p.liveUrl.replace(/^https?:\/\//, '')}>}
) : (
)}
{f.label}
{label}
{feature.state === 'setup' && hindernisText(feature.hindernis) && <>
{hindernisText(feature.hindernis)}>}
{abgeschaltetText(feature) && <>
{abgeschaltetText(feature)}>}
{feature.adminNote && <>
{short(feature.adminNote)}>}
{siteKeyOf(feature, f.key) && <>
>}
{feature.ticketMissing
? Ticket fehlt
: feature.ticketWoid
? #{feature.ticketWoid}
: '–'}
{isConfirming ? (
{f.bestaetigen[confirm.action]}
setNote(e.target.value)} style={{ maxWidth: 220 }} />
) : (
{aktionen(feature.state, f.key, { ticketMissing: feature.ticketMissing }).map((action) => (
))}
{hatAnleitung && (
)}
)}
{anleitungOffenHier && (
)}
)
}))}
{status && {status.text}
}
)
}