Files
Webklar-Kundenbereich/public/neu/ansicht-hilfen.js
T

166 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 }