Files
betreuungs-cockpit/src/lib/utils.ts
T
Florian Wilde cc72f5efd7 chore: Projektgeruest mit Next.js, TypeScript und Tailwind
Grundgeruest der Anwendung: Next.js 15 (App Router), TypeScript im
strict-Modus, Tailwind mit shadcn/ui-Farbtokens.

Bewusste Festlegungen:
- Nur Systemschriften, keine externen Fonts, kein CDN.
- Grundschrift eine Stufe groesser, Fokusring immer sichtbar,
  Touchziele mindestens 44px.
- Ampelfarben fuer Fristen als eigene Design-Tokens.
- Betraege werden als Cent-Ganzzahl gefuehrt, damit die Rechnungslegung
  keine Rundungsfehler erbt.
- .env.example dokumentiert alle Geheimnisse; npm run schluessel erzeugt sie.

Etappe: 1
Phase: Fundament
2026-08-22 01:31:17 +02:00

82 lines
2.5 KiB
TypeScript
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.
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
/** Tailwind-Klassen zusammenfuehren, spaetere gewinnen. */
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
const DATUM_FORMAT = new Intl.DateTimeFormat('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
const DATUM_ZEIT_FORMAT = new Intl.DateTimeFormat('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
const EURO_FORMAT = new Intl.NumberFormat('de-DE', {
style: 'currency',
currency: 'EUR',
})
/** 22.08.2026 - oder ein Gedankenstrich, wenn nichts da ist. */
export function formatDatum(datum: Date | string | null | undefined): string {
if (!datum) return '–'
const d = typeof datum === 'string' ? new Date(datum) : datum
if (Number.isNaN(d.getTime())) return '–'
return DATUM_FORMAT.format(d)
}
/** 22.08.2026, 14:30 */
export function formatDatumZeit(datum: Date | string | null | undefined): string {
if (!datum) return '–'
const d = typeof datum === 'string' ? new Date(datum) : datum
if (Number.isNaN(d.getTime())) return '–'
return DATUM_ZEIT_FORMAT.format(d)
}
/**
* Betraege liegen in der Datenbank als Cent-Ganzzahl (BigInt), damit
* keine Rundungsfehler in die Rechnungslegung wandern.
*/
export function formatCent(cent: bigint | number | null | undefined): string {
if (cent === null || cent === undefined) return '–'
const zahl = typeof cent === 'bigint' ? Number(cent) : cent
return EURO_FORMAT.format(zahl / 100)
}
/** Tage zwischen heute (Mitternacht) und einem Datum. Negativ = ueberfaellig. */
export function tageBis(datum: Date | string): number {
const ziel = typeof datum === 'string' ? new Date(datum) : datum
const heute = new Date()
heute.setHours(0, 0, 0, 0)
const zielTag = new Date(ziel)
zielTag.setHours(0, 0, 0, 0)
return Math.round((zielTag.getTime() - heute.getTime()) / 86_400_000)
}
/** "vor 3 Tagen", "in 2 Wochen" - fuer die Dashboard-Kacheln. */
export function relativeTage(tage: number): string {
if (tage === 0) return 'heute'
if (tage === 1) return 'morgen'
if (tage === -1) return 'gestern'
if (tage < 0) return `vor ${Math.abs(tage)} Tagen`
return `in ${tage} Tagen`
}
/** Aus "Erika Mustermann" wird "EM" - fuer Avatare in der Navigation. */
export function initialen(name: string): string {
return name
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((teil) => teil[0]?.toUpperCase() ?? '')
.join('')
}