Kundenportal Aufträge: Abschnitt „Aufträge“ im Portal (Karten, Bestätigen, Ansichtsmodus gesperrt)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
cad9ba7e40
commit
b53f9f67ed
3 files changed
+272
No files matched your search
@@ -0,0 +1,84 @@
|
||||
/* Abschnitt „Aufträge“ im Reiter Rechnungen des Kundenportals (geladen von app.js, loadCustomerOrders; request ist api aus app.js).
|
||||
Kundentext kommt nur über textContent in die Seite. Alle Texte stehen in auftrag-ansicht.js; das Portal spricht in der Du-Form. */
|
||||
import {
|
||||
ANREDE_DU, agbPfad, darfBestaetigen, kartenMeta, sortiereAuftraege, statusAnzeige, texte,
|
||||
} from './auftrag-ansicht.js'
|
||||
import {
|
||||
h, renderBestaetigtBlock, renderBestaetigung, renderDokumente, renderZusammenfassung,
|
||||
} from './auftrag-dom.js'
|
||||
|
||||
export async function initAuftraege({ section, body, invoicesTitle, request, viewAs = false } = {}) {
|
||||
if (!section || !body || typeof request !== 'function') return { sichtbar: false }
|
||||
const t = texte(ANREDE_DU)
|
||||
let eintraege = []
|
||||
|
||||
/** Bestätigen darf, wer der Server dafür freigibt; in der Kundenansicht zeigt die Oberfläche die offene AB mit gesperrtem Formular. */
|
||||
const wirksam = (e) => darfBestaetigen(e) && (viewAs || e.kannBestaetigen === true)
|
||||
|
||||
async function ladeListe() {
|
||||
const daten = await request('/api/auftraege')
|
||||
if (daten?.aktiv !== true || !Array.isArray(daten.auftraege)) return []
|
||||
return daten.auftraege.filter((e) => e && typeof e.id === 'string' && e.id !== '')
|
||||
}
|
||||
|
||||
function karte(auftrag, offeneId) {
|
||||
const kann = wirksam(auftrag)
|
||||
const status = statusAnzeige({ ...auftrag, kannBestaetigen: kann })
|
||||
const pfad = `/api/auftraege/${encodeURIComponent(auftrag.id)}`
|
||||
|
||||
async function senden(koerper) {
|
||||
try {
|
||||
const antwort = await request(`${pfad}/bestaetigen`, { method: 'POST', body: JSON.stringify(koerper) })
|
||||
return antwort?.auftrag ? { ok: true, auftrag: antwort.auftrag } : { ok: false, status: 0, body: {} }
|
||||
} catch (err) {
|
||||
return { ok: false, status: Number(err?.status) || 0, body: { error: String(err?.message ?? '') } }
|
||||
}
|
||||
}
|
||||
function nachErfolg(neu) {
|
||||
eintraege = eintraege.map((alt) => (alt.id === auftrag.id ? { ...alt, ...neu, id: alt.id, kannBestaetigen: false } : alt))
|
||||
zeichne(auftrag.id)
|
||||
body.querySelector(`[data-auftrag="${CSS.escape(auftrag.id)}"] [data-bestaetigt]`)?.focus()
|
||||
}
|
||||
async function nachEnde() {
|
||||
try {
|
||||
eintraege = await ladeListe()
|
||||
zeichne('')
|
||||
} catch {
|
||||
/* die Anzeige bleibt stehen */
|
||||
}
|
||||
}
|
||||
|
||||
return h('article', { class: 'ab-karte', 'data-auftrag': auftrag.id, 'data-status': auftrag.status || '' },
|
||||
h('div', { class: 'ab-karte-kopf' },
|
||||
h('div', null,
|
||||
h('p', { class: 'ab-karte-nummer' }, h('strong', null, auftrag.nummer || '')),
|
||||
auftrag.titel ? h('p', { class: 'ab-karte-titel' }, auftrag.titel) : null,
|
||||
h('p', { class: 'ab-karte-meta' }, kartenMeta(auftrag))),
|
||||
h('span', { class: `ab-pille ab-pille-${status.ton}` }, status.text)),
|
||||
h('div', { class: 'ab-karte-aktionen' },
|
||||
h('a', { class: 'ab-link', 'data-link': 'pdf', href: `${pfad}/pdf`, target: '_blank', rel: 'noopener noreferrer' }, t.kartePdf)),
|
||||
h('details', { class: 'ab-details', open: kann || auftrag.id === offeneId },
|
||||
h('summary', null, kann ? t.karteDetailsBestaetigen : t.karteDetails),
|
||||
renderZusammenfassung(auftrag, { ebene: 4 }),
|
||||
renderDokumente({ agbHref: agbPfad(auftrag.agbUrl) }),
|
||||
renderBestaetigtBlock(auftrag, { anrede: ANREDE_DU }),
|
||||
kann
|
||||
? renderBestaetigung(auftrag, { senden, nachErfolg, nachEnde, gesperrt: viewAs ? t.ansichtsmodus : '', anrede: ANREDE_DU })
|
||||
: null))
|
||||
}
|
||||
|
||||
function zeichne(offeneId) {
|
||||
body.replaceChildren(...sortiereAuftraege(eintraege.map((e) => ({ ...e, kannBestaetigen: wirksam(e) }))).map((e) => karte(eintraege.find((x) => x.id === e.id), offeneId)))
|
||||
}
|
||||
|
||||
try {
|
||||
eintraege = await ladeListe()
|
||||
} catch {
|
||||
return { sichtbar: false }
|
||||
}
|
||||
if (!eintraege.length) return { sichtbar: false }
|
||||
zeichne('')
|
||||
section.classList.remove('hidden')
|
||||
invoicesTitle?.classList.remove('hidden')
|
||||
return { sichtbar: true }
|
||||
}
|
||||
@@ -122,6 +122,35 @@ body.ab-seite {
|
||||
.ab-seite .ab-knopf, .ab-portal .ab-knopf { width: 100%; }
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ Portal (Reiter Rechnungen, dunkel) --- */
|
||||
.ab-portal {
|
||||
--ab-karte: var(--kb-surface); --ab-text: #ffffff; --ab-weich: var(--kb-muted); --ab-linie: var(--kb-border); --ab-box: rgba(255, 255, 255, 0.04);
|
||||
--ab-knopf: var(--kb-purple); --ab-knopf-text: #ffffff; --ab-fokus: #d4b4ff;
|
||||
--ab-warn-bg: rgba(157, 61, 254, 0.16); --ab-warn-text: #d8b4fe; --ab-ok-bg: rgba(74, 222, 128, 0.1); --ab-ok-text: #86efac;
|
||||
--ab-rot-bg: rgba(248, 113, 113, 0.12); --ab-rot-text: #fca5a5; --ab-muted-bg: rgba(255, 255, 255, 0.06); --ab-muted-text: var(--kb-muted);
|
||||
--ab-fehler: #fca5a5;
|
||||
margin: 0 0 28px; color: var(--ab-text);
|
||||
}
|
||||
.ab-portal h2 { font-size: 20px; margin: 0 0 6px; }
|
||||
.ab-portal .ab-karte {
|
||||
margin: 12px 0 0; padding: 16px 18px; border-radius: 16px; background: var(--ab-karte); border: 1px solid var(--ab-linie); overflow-wrap: anywhere;
|
||||
}
|
||||
.ab-portal .ab-karte-kopf { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; }
|
||||
.ab-portal .ab-karte-kopf .ab-pille { margin: 0; flex: none; }
|
||||
.ab-portal .ab-karte-kopf p { margin: 0; }
|
||||
.ab-portal .ab-karte-titel { margin-top: 2px; }
|
||||
.ab-portal .ab-karte-meta { margin-top: 2px; font-size: 13px; color: var(--ab-weich); }
|
||||
.ab-portal .ab-karte-aktionen { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }
|
||||
.ab-portal .ab-karte-aktionen a {
|
||||
display: inline-flex; align-items: center; min-height: 44px; padding: 0 16px; border-radius: 999px; border: 1px solid var(--kb-border-strong);
|
||||
color: var(--ab-text); text-decoration: none; font-weight: 600; font-size: 14px;
|
||||
}
|
||||
.ab-portal .ab-details > summary { cursor: pointer; display: flex; align-items: center; min-height: 44px; color: #d4b4ff; font-weight: 700; }
|
||||
.ab-portal .ab-details[open] > summary { margin-bottom: 4px; }
|
||||
.ab-portal .ab-knopf { border-radius: 999px; }
|
||||
.ab-rechnungen-titel { font-size: 20px; margin: 0 0 10px; }
|
||||
.inv-project { display: block; margin-top: 2px; font-size: 12px; font-weight: 400; color: var(--kb-muted, #6b6b70); overflow-wrap: anywhere; }
|
||||
|
||||
/* ------------------------------------------------------------------ Installierte App --- */
|
||||
.ab-app-back { display: none; }
|
||||
@media (display-mode: standalone) {
|
||||
|
||||
Reference in new issue
Block a user