Kundenseite: Abo-Status und Funktionen; Seite E-Mail-Speicher
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
91d526e909
commit
cef0d5465e
8 files changed
+297
-4
No files matched your search
@@ -0,0 +1,99 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { FaSpinner, FaRotateRight } from 'react-icons/fa6'
|
||||
import { PageHeader, Card, Badge, Button } from '../components/ui'
|
||||
import { listStorageTasks, markStorageDone } from '../lib/teamApi'
|
||||
import { fmtDate, fmtDateTime } from '../lib/format'
|
||||
import { changeText, failedText, isOverdue, orderedKind, partialText, scopeText, summaryText, taskText } from '../lib/storageFormat'
|
||||
import '../styles/admin-areas.css'
|
||||
|
||||
// E-Mail-Speicher (Kunden -> E-Mail-Speicher): Zusatzspeicher-Aufgaben in konsoleH wie im frueheren Admin-Panel
|
||||
export default function StorageTasksPage() {
|
||||
const [data, setData] = useState(null)
|
||||
const [error, setError] = useState(null)
|
||||
const [busyId, setBusyId] = useState('')
|
||||
|
||||
const load = useCallback(() => {
|
||||
setError(null)
|
||||
listStorageTasks()
|
||||
.then(setData)
|
||||
.catch((err) => setError(err.message || 'Speicher-Aufgaben konnten nicht geladen werden.'))
|
||||
}, [])
|
||||
useEffect(() => { load() }, [load])
|
||||
|
||||
const done = async (task) => {
|
||||
if (task.partial && !window.confirm('Stripe-Menge von Hand korrigiert?')) return
|
||||
setBusyId(task.id)
|
||||
try {
|
||||
await markStorageDone(task.id)
|
||||
load()
|
||||
} catch (err) {
|
||||
window.alert(`Aufgabe konnte nicht als erledigt gespeichert werden: ${err.message}`)
|
||||
} finally {
|
||||
setBusyId('')
|
||||
}
|
||||
}
|
||||
|
||||
const summary = data ? summaryText(data) : null
|
||||
return (
|
||||
<div className="page">
|
||||
<PageHeader title="E-Mail-Speicher" subtitle="Zusatzspeicher der Abos in konsoleH einrichten und senken" />
|
||||
{error && (
|
||||
<div className="area-error area-inline">
|
||||
{error} <Button size="sm" variant="ghost" onClick={load}><FaRotateRight /> Erneut versuchen</Button>
|
||||
</div>
|
||||
)}
|
||||
{!data && !error && <FaSpinner className="spinner" />}
|
||||
{data && (
|
||||
<>
|
||||
<div className={`area-summary ${summary.warn ? 'warn' : ''}`}>{summary.text}</div>
|
||||
<Card pad={false}>
|
||||
<div className="area-rows">
|
||||
<div className="area-row storage-row head"><span>Fällig</span><span>Kunde</span><span>Aufgabe in konsoleH</span><span>Bestellt</span><span /></div>
|
||||
{data.tasks.length === 0 && <div className="area-row"><span className="area-muted">Keine offenen Speicher-Aufgaben.</span></div>}
|
||||
{data.tasks.map((t) => (
|
||||
<div key={t.id} className="area-row storage-row">
|
||||
<span>
|
||||
{t.kind === 'einrichten'
|
||||
? <><Badge tone="warn">jetzt einrichten</Badge><br /><span className="area-muted">bestellt {fmtDateTime(t.createdAt)}</span></>
|
||||
: <><Badge tone={isOverdue(t) ? 'warn' : 'muted'}>zum Periodenende</Badge><br />{fmtDate(t.dueAt)}</>}
|
||||
</span>
|
||||
<span>
|
||||
<Link to={`/customers/${t.customerId}`}><strong>{t.customerName}</strong></Link>
|
||||
{t.customerCode && <><br /><span className="area-muted">Kd.-Nr. {t.customerCode}</span></>}
|
||||
{t.email && <><br /><span className="area-muted">{t.email}</span></>}
|
||||
<br /><span className="area-muted">{t.productLabel}</span>
|
||||
</span>
|
||||
<span>
|
||||
<strong>{taskText(t)}</strong>
|
||||
<br /><span className="area-muted">{scopeText(t)}</span>
|
||||
<br /><span className="area-muted">{changeText(t)}</span>
|
||||
{t.partial && <><br /><span className="area-danger">{partialText(t)}</span></>}
|
||||
</span>
|
||||
<span>{fmtDateTime(t.createdAt)}<br /><span className="area-muted">{orderedKind(t)}</span></span>
|
||||
<span>
|
||||
<Button size="sm" variant="primary" data-storage-done={t.id} onClick={() => done(t)} disabled={busyId === t.id}>
|
||||
{busyId === t.id ? <FaSpinner className="spinner" /> : 'Erledigt'}
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
{data.failed.length > 0 && (
|
||||
<Card title="Gescheiterte Änderungen (letzte 30 Tage)">
|
||||
{data.failed.map((f) => (
|
||||
<div key={f.id} style={{ marginBottom: 6 }}>
|
||||
{fmtDateTime(f.createdAt)} · <strong>{f.customerName}</strong> · <span className="area-muted">{failedText(f)}</span>
|
||||
</div>
|
||||
))}
|
||||
</Card>
|
||||
)}
|
||||
<p className="area-muted" style={{ marginTop: 12 }}>
|
||||
Jedes Abo bringt 5 GB E-Mail-Speicher mit, jedes Zusatzpaket 5 GB dazu. „Einrichten“: Postfachgrenze in konsoleH jetzt auf den Zielwert setzen. „Zum Periodenende“: erst am genannten Tag auf den Zielwert senken. Die Grenzen gelten für den Gesamtspeicher des Kunden (5 GB + 5 GB je Paket); er verteilt ihn nach seiner Vorgabe auf seine Postfächer (§ 3 Abs. 4). Angezeigt wird die Domain der Kontakt-Adresse, eine eigene Mail-Domain ist im Portal nicht gespeichert. „Vergeben“ zählt, was konsoleH gerade hält: eine Verringerung zählt erst nach „Erledigt“. Danach „Erledigt“ drücken – der Kunde hat seine Bestätigung schon bekommen.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user