Files
Webklar-Kundenbereich/public/neu/vertraege-rechnungen.js
T

66 lines
3.8 KiB
JavaScript

/* Kundenansicht neu (P2): Reiter Rechnungen in Verträge. Oben der Abschnitt "Aufträge" (Alt-Modul auftrag-portal.js, blendet sich selbst ein,
wenn Aufträge aktiv und vorhanden sind), darunter die Rechnungen als Tabelle. Fehler der Aufträge stören die Rechnungen nie. */
import { api } from './api.js'
import { h, leeren } from './dom.js'
import { K, legacyRequest, sichereAdresse, fmtEuro, fmtDatum } from './ansicht-hilfen.js'
import { initAuftraege } from '../auftrag-portal.js'
const TEXT_LEER = 'Es liegen noch keine Rechnungen vor.'
const TEXT_LUECKE = 'Deine Rechnungen konnten gerade nicht vollständig geladen werden. Bitte versuche es später noch einmal.'
const laeufe = new WeakMap() // Reiter-Inhalt → Nummer des jüngsten Aufrufs
const pille = (r) => (r.overdue ? K.pille.rot : r.paid ? K.pille.gruen : r.cancelled ? K.pille.grau : K.pille.orange)
function rechnungsZeile(r) {
const offen = Number(r.balance) > 0
const url = sichereAdresse(r.portalUrl)
const links = [
url ? h('a', { class: 'kn-zeilenlink', href: url, target: '_blank', rel: 'noopener noreferrer' }, offen ? 'Zahlen' : 'Ansehen') : null,
r.hasPdf !== false ? h('a', { class: 'kn-zeilenlink', href: `/api/invoices/${encodeURIComponent(r.id)}/pdf` }, 'PDF') : null,
].filter(Boolean)
return h('tr', { class: offen ? 'is-offen' : 'is-bezahlt' },
h('td', { 'data-label': 'Nr.', class: 'kn-rechnung-nr' }, r.number, r.projekt ? h('span', { class: 'kn-rechnung-projekt' }, r.projekt) : null),
h('td', { 'data-label': 'Datum', class: 'kn-num' }, fmtDatum(r.date)),
h('td', { 'data-label': 'Betrag', class: 'kn-num' }, fmtEuro(r.amount)),
h('td', { 'data-label': 'Offen', class: 'kn-num' }, fmtEuro(r.balance)),
h('td', { 'data-label': 'Status' }, h('span', { class: pille(r) }, r.status)),
h('td', { class: 'kn-rechnung-aktionen' }, links))
}
function tabelle(rechnungen) {
return h('div', { class: 'kn-tabelle kn-rechnungen' }, h('table', null,
h('thead', null, h('tr', null, ['Nr.', 'Datum', 'Betrag', 'Offen', 'Status'].map((t) => h('th', null, t)), h('th', { 'aria-label': 'Aktionen' }))),
h('tbody', null, rechnungen.map(rechnungsZeile))))
}
/** Reiter-Inhalt. antwort ist optional die schon geladene Antwort von GET /api/invoices; ohne sie lädt die Funktion selbst. */
export async function zeigeRechnungen(box, { me, antwort } = {}) {
const nr = (laeufe.get(box) ?? 0) + 1
laeufe.set(box, nr)
const aktuell = () => laeufe.get(box) === nr
const auftraegeAbschnitt = h('section', { class: 'ab-portal hidden', id: 'kn-auftraege' }, h('h2', { class: K.abschnitt }, 'Aufträge'), h('div', { class: 'kn-auftraege-rumpf' }))
const rechnungenTitel = h('h2', { class: `${K.abschnitt} hidden` }, 'Rechnungen')
const liste = h('div', { class: 'kn-rechnungen-liste' }, h('p', { class: K.leer }, 'Wird geladen …'))
leeren(box).append(auftraegeAbschnitt, rechnungenTitel, liste)
let daten = antwort
if (!daten) {
try { daten = await api('/api/invoices') } catch (f) {
if (!aktuell()) return
leeren(liste).append(h('p', { class: K.fehler }, `Rechnungen konnten nicht geladen werden: ${f.message}`))
}
}
if (!aktuell()) return
if (daten) {
const rechnungen = Array.isArray(daten.invoices) ? daten.invoices : []
const luecke = daten.incomplete ? h('p', { class: K.meldung }, TEXT_LUECKE) : null
leeren(liste).append(...(rechnungen.length ? [tabelle(rechnungen), luecke] : [luecke ?? h('p', { class: 'kn-hinweis' }, TEXT_LEER)]).filter(Boolean))
}
// Aufträge: ein Fehler lässt den Abschnitt weg und lässt die Rechnungen stehen
try {
await initAuftraege({ section: auftraegeAbschnitt, body: auftraegeAbschnitt.querySelector('.kn-auftraege-rumpf'), invoicesTitle: rechnungenTitel, request: legacyRequest(api), viewAs: me?.viewAs === true })
} catch { /* ohne Aufträge bleiben die Rechnungen */ }
}