166 lines
6.4 KiB
JavaScript
166 lines
6.4 KiB
JavaScript
/* Kundenansicht neu (P2): gemeinsame Hilfen der Ansichten. Formate, Bausteinklassen, Navigation, Ereignisse und kleine Merker.
|
||
Kein DOM-Zugriff beim Import; document und window werden erst beim Aufruf gelesen. */
|
||
|
||
import { api } from './api.js'
|
||
import { geheZu } from './router.js'
|
||
import { hashFuerAnsicht } from './routen.js'
|
||
import { safeUrl } from '../fn-util.js'
|
||
|
||
/** Klassen der Bausteine aus basis.css. */
|
||
export const K = Object.freeze({
|
||
knopf: 'kn-knopf',
|
||
primaer: 'kn-knopf-primaer',
|
||
zweit: 'kn-knopf-zweit',
|
||
textknopf: 'kn-textknopf',
|
||
feld: 'kn-feld',
|
||
input: 'kn-input',
|
||
select: 'kn-select',
|
||
textarea: 'kn-textarea',
|
||
check: 'kn-check',
|
||
liste: 'kn-liste',
|
||
zeile: 'kn-zeile',
|
||
abschnitt: 'kn-abschnitt',
|
||
meldung: 'kn-meldung',
|
||
fehler: 'kn-fehler',
|
||
leer: 'kn-leer',
|
||
versteckt: 'hidden',
|
||
pille: Object.freeze({
|
||
grau: 'kn-pille kn-pille-grau',
|
||
blau: 'kn-pille kn-pille-blau',
|
||
orange: 'kn-pille kn-pille-orange',
|
||
violett: 'kn-pille kn-pille-violett',
|
||
gruen: 'kn-pille kn-pille-gruen',
|
||
rot: 'kn-pille kn-pille-rot',
|
||
}),
|
||
})
|
||
|
||
export const ABO_NICHT_GELADEN = 'Deine Verträge konnten gerade nicht geladen werden. Bitte versuche es später noch einmal.'
|
||
|
||
/* ---------- Formate ---------- */
|
||
|
||
const ZAHL_2 = new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||
const ZAHL_1 = new Intl.NumberFormat('de-DE', { minimumFractionDigits: 1, maximumFractionDigits: 1 })
|
||
const ZAHL_STD = new Intl.NumberFormat('de-DE', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
|
||
|
||
/** Teile eines Zeitpunkts in Berliner Zeit; ungültige Werte ergeben null. */
|
||
function berlinTeile(wert, extra = {}) {
|
||
const d = wert instanceof Date ? wert : new Date(wert)
|
||
if (!wert || Number.isNaN(d.getTime())) return null
|
||
const teile = new Intl.DateTimeFormat('de-DE', { timeZone: 'Europe/Berlin', hourCycle: 'h23', ...extra }).formatToParts(d)
|
||
return Object.fromEntries(teile.map((t) => [t.type, t.value]))
|
||
}
|
||
|
||
/** 50 → '50,00 €' (geschütztes Leerzeichen vor dem Euro-Zeichen). */
|
||
export const fmtEuro = (euro) => `${ZAHL_2.format(Number(euro) || 0)} €`
|
||
|
||
/** 'TT.MM.JJJJ' in Berliner Zeit; ein Wert, der schon so aussieht, bleibt; leer oder ungültig '–'. */
|
||
export function fmtDatum(wert) {
|
||
if (typeof wert === 'string' && /^\d{2}\.\d{2}\.\d{4}$/.test(wert)) return wert
|
||
const t = berlinTeile(wert, { day: '2-digit', month: '2-digit', year: 'numeric' })
|
||
return t ? `${t.day}.${t.month}.${t.year}` : '–'
|
||
}
|
||
|
||
/** 'TT.MM.JJ, HH:MM' in Berliner Zeit. */
|
||
export function fmtDatumZeit(wert) {
|
||
const t = berlinTeile(wert, { day: '2-digit', month: '2-digit', year: '2-digit', hour: '2-digit', minute: '2-digit' })
|
||
return t ? `${t.day}.${t.month}.${t.year}, ${t.hour}:${t.minute}` : '–'
|
||
}
|
||
|
||
/** 'TT.MM.' in Berliner Zeit; ungültig ''. */
|
||
export function fmtTagMonat(wert) {
|
||
const t = berlinTeile(wert, { day: '2-digit', month: '2-digit' })
|
||
return t ? `${t.day}.${t.month}.` : ''
|
||
}
|
||
|
||
/** Minuten → '2,5 Std.' | '2 Std.' | '0,75 Std.' (Komma, höchstens zwei Stellen). */
|
||
export const fmtStd = (minuten) => `${ZAHL_STD.format(Math.round(((Number(minuten) || 0) / 60) * 100) / 100)} Std.`
|
||
|
||
/** Minuten → '' | '45 min' | '2 h' | '2 h 30 min'. */
|
||
export function fmtMinuten(minuten) {
|
||
const m = Math.round(Number(minuten) || 0)
|
||
if (m <= 0) return ''
|
||
const h = Math.floor(m / 60)
|
||
const rest = m % 60
|
||
if (!h) return `${rest} min`
|
||
return rest ? `${h} h ${rest} min` : `${h} h`
|
||
}
|
||
|
||
/** Bytes → '12 B' | '1,5 KB' | '5,0 MB'. */
|
||
export function fmtBytes(bytes) {
|
||
const b = Number(bytes) || 0
|
||
if (b < 1024) return `${Math.round(b)} B`
|
||
if (b < 1024 * 1024) return `${ZAHL_1.format(b / 1024)} KB`
|
||
return `${ZAHL_1.format(b / (1024 * 1024))} MB`
|
||
}
|
||
|
||
/* ---------- Alt-Module, Navigation, Ereignisse ---------- */
|
||
|
||
/** Adapter für Alt-Module (auftrag-portal.js), die den Body als JSON-String schicken: das neue api() kodiert Objekte selbst. */
|
||
export function legacyRequest(apiFn) {
|
||
return (pfad, opts = {}) => {
|
||
const { body } = opts
|
||
if (typeof body !== 'string') return apiFn(pfad, { ...opts })
|
||
let objekt = body
|
||
try { objekt = JSON.parse(body) } catch { /* kein JSON: unverändert weitergeben */ }
|
||
return apiFn(pfad, { ...opts, body: objekt })
|
||
}
|
||
}
|
||
|
||
/** Programmatisch navigieren (nur über den Router); wirft der Router, setzt der Hash die Adresse. */
|
||
export function navigiere(hash, { ersetzen = false } = {}) {
|
||
try {
|
||
geheZu(hash, { ersetzen })
|
||
} catch {
|
||
window.location.hash = hash
|
||
}
|
||
}
|
||
|
||
/** Verknüpfung zu einer Ansicht für die Brücke der Hülle: href plus data-view (und data-project). */
|
||
export function zuAnsicht(name, projektId = '') {
|
||
const basis = hashFuerAnsicht(name) ?? '#'
|
||
if (!projektId) return { href: basis, 'data-view': name }
|
||
return { href: `${basis}${basis.includes('?') ? '&' : '?'}projekt=${encodeURIComponent(projektId)}`, 'data-view': name, 'data-project': projektId }
|
||
}
|
||
|
||
/** Titel der Etage melden (die Hülle zeigt ihn im Kopf). */
|
||
export function meldeTitel(el, text) {
|
||
el.dispatchEvent(new CustomEvent('kn:titel', { bubbles: true, detail: { text } }))
|
||
}
|
||
|
||
/** Zähler neu laden lassen: was = 'chat' | 'vertraege' | 'alle'. Ohne Dokument tut es nichts. */
|
||
export function meldeZaehlerNeu(was) {
|
||
if (typeof document === 'undefined' || typeof document.dispatchEvent !== 'function' || typeof CustomEvent === 'undefined') return
|
||
document.dispatchEvent(new CustomEvent('kn:zaehler-neu', { bubbles: true, detail: { was } }))
|
||
}
|
||
|
||
/* ---------- Merker zwischen Ansichten (einmalig) ---------- */
|
||
|
||
let hinweis = ''
|
||
export function merkeHinweis(text) { hinweis = String(text ?? '') }
|
||
export function nimmHinweis() { const t = hinweis; hinweis = ''; return t }
|
||
|
||
let chatVorgabe = ''
|
||
export function setzeChatVorgabe(text) { chatVorgabe = String(text ?? '') }
|
||
export function nimmChatVorgabe() { const t = chatVorgabe; chatVorgabe = ''; return t }
|
||
|
||
/* ---------- Abo ---------- */
|
||
|
||
/** GET /api/abo → Objekt oder null (jeder Fehler). */
|
||
export async function ladeAbo() {
|
||
try {
|
||
const antwort = await api('/api/abo')
|
||
return antwort && typeof antwort === 'object' ? antwort : null
|
||
} catch {
|
||
return null
|
||
}
|
||
}
|
||
|
||
/** Gemerkte Funktion eines Wechsels (V11): nur wenn key und projectId nichtleere Texte sind. */
|
||
export function wechselFunktionAus(sub) {
|
||
const f = sub?.wechselFunktion
|
||
if (!f || typeof f.key !== 'string' || !f.key || typeof f.projectId !== 'string' || !f.projectId) return null
|
||
return { key: f.key, projectId: f.projectId }
|
||
}
|
||
|
||
export { safeUrl }
|