Design P6: Gutschriften-Liste als Tabelle mit Reitern und Spaltenprioritaet
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cb7ac6edf6
commit
c96aa870a9
3 files changed
+47
-38
No files matched your search
@@ -1,10 +1,8 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { FaSpinner } from 'react-icons/fa6'
|
import { Badge, EmptyState, Tabs } from '../ui'
|
||||||
import { Card, Badge, Button } from '../ui'
|
|
||||||
import { listCredits } from '../../lib/teamApi'
|
import { listCredits } from '../../lib/teamApi'
|
||||||
import { creditStatus } from '../../lib/creditFormat'
|
import { creditActionLabel, creditStatus, creditStatusNote } from '../../lib/creditFormat'
|
||||||
import { fmtDate, fmtDateTime, formatCents } from '../../lib/format'
|
import { fmtDate, fmtDateTime, formatCents } from '../../lib/format'
|
||||||
import '../../styles/admin-areas.css'
|
|
||||||
|
|
||||||
// Liste der Uebernahmegutschriften (Filter "Offen"/"Alle"), wie im frueheren Admin-Panel; optional nur ein Kunde
|
// Liste der Uebernahmegutschriften (Filter "Offen"/"Alle"), wie im frueheren Admin-Panel; optional nur ein Kunde
|
||||||
export default function CreditList({ customerId, selectedId, onSelect, reloadKey }) {
|
export default function CreditList({ customerId, selectedId, onSelect, reloadKey }) {
|
||||||
@@ -23,41 +21,52 @@ export default function CreditList({ customerId, selectedId, onSelect, reloadKey
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="area-filter seg">
|
<Tabs tabs={[{ id: 'open', label: 'Offen' }, { id: 'all', label: 'Alle' }]} active={filter} onChange={setFilter} />
|
||||||
<button type="button" className={filter === 'open' ? 'active' : ''} onClick={() => setFilter('open')}>Offen</button>
|
{error && <div className="msg err" role="alert">{error}</div>}
|
||||||
<button type="button" className={filter === 'all' ? 'active' : ''} onClick={() => setFilter('all')}>Alle</button>
|
{!rows && !error && <div className="loading">Wird geladen …</div>}
|
||||||
</div>
|
{rows && rows.length === 0 && (
|
||||||
<Card pad={false}>
|
<EmptyState title={filter === 'open' ? 'Keine offenen Anträge.' : 'Noch keine Anträge.'} />
|
||||||
{error && <div className="area-error" style={{ margin: 12 }}>{error}</div>}
|
)}
|
||||||
{!rows && !error && <div style={{ padding: 16 }}><FaSpinner className="spinner" /></div>}
|
{rows && rows.length > 0 && (
|
||||||
{rows && (
|
<table className="table table-wrap">
|
||||||
<div className="area-rows">
|
<thead>
|
||||||
<div className="area-row credit-row head"><span>Eingang</span><span>Kunde</span><span>Leistung / bisher bei</span><span>Rechnung</span><span>Bezahlt bis</span><span>Status</span><span /></div>
|
<tr>
|
||||||
{rows.length === 0 && <div className="area-row"><span className="area-muted">{filter === 'open' ? 'Keine offenen Anträge.' : 'Noch keine Anträge.'}</span></div>}
|
<th className="hide-sm">Eingang</th>
|
||||||
|
<th>Kunde</th>
|
||||||
|
<th className="hide-md">Leistung / bisher bei</th>
|
||||||
|
<th className="hide-sm right num">Rechnung</th>
|
||||||
|
<th className="hide-md">Bezahlt bis</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
{rows.map((r) => {
|
{rows.map((r) => {
|
||||||
const st = creditStatus(r.status)
|
const st = creditStatus(r.status)
|
||||||
|
const note = creditStatusNote(r)
|
||||||
return (
|
return (
|
||||||
<div key={r.id} className={`area-row credit-row ${r.id === selectedId ? 'active' : ''}`}>
|
<tr key={r.id} aria-current={r.id === selectedId ? 'true' : undefined}>
|
||||||
<span>{fmtDateTime(r.createdAt)}<br /><span className="area-muted">{r.ref}</span></span>
|
<td className="hide-sm">{fmtDateTime(r.createdAt)}<div className="cell-sub">{r.ref}</div></td>
|
||||||
<span><strong>{r.customerName}</strong>{r.customerCode && <><br /><span className="area-muted">Kd.-Nr. {r.customerCode}</span></>}</span>
|
<td>
|
||||||
<span>{r.services}<br /><span className="area-muted">{r.provider}{r.domain ? ` · ${r.domain}` : ''}</span></span>
|
<div className="cell-title">{r.customerName}</div>
|
||||||
<span>{formatCents(r.invoiceAmountCents)}<br /><span className="area-muted">für {r.billingMonths} Mon.</span></span>
|
{r.customerCode && <div className="cell-sub">Kd.-Nr. {r.customerCode}</div>}
|
||||||
<span>{fmtDate(r.paidUntil)}</span>
|
</td>
|
||||||
<span>
|
<td className="hide-md">{r.services}<div className="cell-sub">{r.provider}{r.domain ? ` · ${r.domain}` : ''}</div></td>
|
||||||
|
<td className="hide-sm right num">{formatCents(r.invoiceAmountCents)}<div className="cell-sub">für {r.billingMonths} Mon.</div></td>
|
||||||
|
<td className="hide-md">{fmtDate(r.paidUntil)}</td>
|
||||||
|
<td>
|
||||||
<Badge tone={st.tone}>{st.label}</Badge>
|
<Badge tone={st.tone}>{st.label}</Badge>
|
||||||
{r.status === 'genehmigt' && <><br /><span className="area-muted">{formatCents(r.amountCents)}{r.appliedAt ? '' : ' · nicht gebucht'}</span></>}
|
{note && <div className="cell-sub">{note}</div>}
|
||||||
</span>
|
</td>
|
||||||
<span>
|
<td>
|
||||||
<Button size="sm" variant="primary" onClick={() => onSelect(r.id)}>
|
<button type="button" className="link" onClick={() => onSelect(r.id)}>{creditActionLabel(r.status)}</button>
|
||||||
{['offen', 'rueckfrage'].includes(r.status) ? 'Prüfen' : 'Ansehen'}
|
</td>
|
||||||
</Button>
|
</tr>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</tbody>
|
||||||
)}
|
</table>
|
||||||
</Card>
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -23,11 +23,11 @@ export default function CreditsView({ customerId }) {
|
|||||||
return (
|
return (
|
||||||
<div className="credits-view">
|
<div className="credits-view">
|
||||||
{selected && (
|
{selected && (
|
||||||
<div style={{ marginBottom: 16 }}>
|
<CreditDetail key={selected} id={selected} onClose={() => select('')} onOpen={select} onChanged={() => setReloadKey((k) => k + 1)} />
|
||||||
<CreditDetail key={selected} id={selected} onClose={() => select('')} onOpen={select} onChanged={() => setReloadKey((k) => k + 1)} />
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
<CreditList customerId={customerId} selectedId={selected} onSelect={select} reloadKey={reloadKey} />
|
<div className={selected ? 'mt-3' : undefined}>
|
||||||
|
<CreditList customerId={customerId} selectedId={selected} onSelect={select} reloadKey={reloadKey} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ export default function CreditsPage() {
|
|||||||
<div className="page">
|
<div className="page">
|
||||||
<PageHeader title="Gutschriften" subtitle="Übernahmegutschriften: bereits beim alten Anbieter bezahlte Leistungen verrechnen" />
|
<PageHeader title="Gutschriften" subtitle="Übernahmegutschriften: bereits beim alten Anbieter bezahlte Leistungen verrechnen" />
|
||||||
<CreditsView />
|
<CreditsView />
|
||||||
<p className="area-muted" style={{ marginTop: 12 }}>
|
<p className="table-foot">
|
||||||
Übernahmegutschrift: Der Kunde hat Domain, Hosting oder E-Mail beim bisherigen Anbieter schon bezahlt. Nach der Prüfung wird der doppelt bezahlte Anteil als Guthaben in Stripe gebucht und mit den nächsten Abo-Rechnungen verrechnet.
|
Übernahmegutschrift: Der Kunde hat Domain, Hosting oder E-Mail beim bisherigen Anbieter schon bezahlt. Nach der Prüfung wird der doppelt bezahlte Anteil als Guthaben in Stripe gebucht und mit den nächsten Abo-Rechnungen verrechnet.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in new issue
Block a user