diff --git a/server/src/index.js b/server/src/index.js index c999abe..e85db40 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -211,6 +211,11 @@ const mapBeleg = (b) => ({ const deutschesDatum = () => new Date().toLocaleDateString('de-DE') const deutschesDatumZeit = () => new Date().toLocaleString('de-DE') +/** ISO-Eingaben (YYYY-MM-DD, z. B. aus date-Feldern) in das deutsche Format wandeln. */ +const datumDeutsch = (s) => { + const iso = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(s || '').trim()) + return iso ? `${iso[3]}.${iso[2]}.${iso[1]}` : String(s || '').trim() +} // ── Health ─────────────────────────────────────────────────── app.get('/api/healthz', async (_req, res) => { @@ -566,7 +571,7 @@ app.post('/api/meters', auth, adminOnly, async (req, res, next) => { if (!ZAEHLER_ARTEN.includes(art)) return res.status(400).json({ fehler: 'Ungültige Zählerart.' }) const standZahl = Number(stand) if (!Number.isFinite(standZahl) || standZahl < 0) return res.status(400).json({ fehler: 'Ungültiger Zählerstand.' }) - const datum = String(standAm || '').trim() || deutschesDatum() + const datum = datumDeutsch(standAm) || deutschesDatum() if (!zaehlerVerlauf.datumGueltig(datum)) return res.status(400).json({ fehler: 'Ungültiges Ablesedatum.' }) // Wohnung angegeben → Haus aus der Wohnung ableiten; sonst muss das Haus im Body stehen let einheitId = unitId ? String(unitId) : null @@ -665,7 +670,7 @@ app.post('/api/meters/:id/readings', auth, async (req, res, next) => { const { stand, standAm } = req.body || {} const standZahl = Number(stand) if (!Number.isFinite(standZahl) || standZahl < 0) return res.status(400).json({ fehler: 'Ungültiger Zählerstand.' }) - const datum = String(standAm || '').trim() || deutschesDatum() + const datum = datumDeutsch(standAm) || deutschesDatum() if (!zaehlerVerlauf.datumGueltig(datum)) return res.status(400).json({ fehler: 'Ungültiges Ablesedatum.' }) const { eintrag, anomalie, grund } = await zaehlerVerlauf.standEintragen({ meter: m, stand: standZahl, standAm: datum, diff --git a/src/components/PdfLightbox.tsx b/src/components/PdfLightbox.tsx index ae51cac..f1340c9 100644 --- a/src/components/PdfLightbox.tsx +++ b/src/components/PdfLightbox.tsx @@ -11,6 +11,7 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { useStore } from '../store/store' +import { fmtDatum } from '../lib/format' import type { DocumentItem } from '../types' /** PDF-Lightbox (v1.31): Inline-Vorschau erzeugter/gespeicherter PDFs mit @@ -68,7 +69,7 @@ export default function PdfLightbox({ dokument, trigger }: {
{dokument.title}

- {dokument.category} · {dokument.date} + {dokument.category} · {fmtDatum(dokument.date)} {dokument.fileName && ` · ${dokument.fileName}`}

diff --git a/src/lib/format.ts b/src/lib/format.ts new file mode 100644 index 0000000..7db0b6d --- /dev/null +++ b/src/lib/format.ts @@ -0,0 +1,25 @@ +/** Zentrale Datumsformate (v1.32): Anzeige immer deutsch (TT.MM.JJJJ). + * Ältere Einträge können ISO-Formate (YYYY-MM-DD) enthalten – diese werden + * robust umgerechnet, deutsche Werte unverändert durchgereicht. */ + +const ISO_DATUM = /^(\d{4})-(\d{2})-(\d{2})$/ +const ISO_DATUMZEIT = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2})(?::(\d{2}))?/ + +/** „2026-10-04" → „04.10.2026"; „04.10.2026" bleibt; unbekannt → unverändert. */ +export function fmtDatum(wert: string | null | undefined): string { + if (!wert) return '–' + const iso = ISO_DATUM.exec(wert.trim()) + if (iso) return `${iso[3]}.${iso[2]}.${iso[1]}` + return wert +} + +/** Wie fmtDatum, aber mit Uhrzeit: ISO-Datetime → „04.10.2026, 15:23". */ +export function fmtDatumZeit(wert: string | null | undefined): string { + if (!wert) return '–' + const iso = ISO_DATUMZEIT.exec(wert.trim()) + if (iso) { + const sek = iso[6] ? `:${iso[6]}` : '' + return `${iso[3]}.${iso[2]}.${iso[1]}, ${iso[4]}:${iso[5]}${sek}` + } + return fmtDatum(wert) +} diff --git a/src/pages/BenutzerVerwaltung.tsx b/src/pages/BenutzerVerwaltung.tsx index 92cdf20..a4443a9 100644 --- a/src/pages/BenutzerVerwaltung.tsx +++ b/src/pages/BenutzerVerwaltung.tsx @@ -14,6 +14,7 @@ import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Textarea } from '@/components/ui/textarea' import { useStore } from '../store/store' +import { fmtDatumZeit } from '../lib/format' import type { Benutzer, BenutzerRolle } from '../types' function Chip({ label, value, tone = 'slate' }: { label: string; value: number | string; tone?: 'slate' | 'amber' | 'teal' | 'blue' }) { @@ -419,7 +420,7 @@ export default function BenutzerVerwaltung() {
@{b.benutzername} {einheit && ` · ${einheit.building}, Whg. ${einheit.number}`} - {b.letzterLogin && ` · zuletzt: ${b.letzterLogin}`} + {b.letzterLogin && ` · zuletzt: ${fmtDatumZeit(b.letzterLogin)}`}
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 1d0f47c..57c79fe 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -23,6 +23,7 @@ import { } from 'lucide-react' import { Link } from 'react-router' import { useStore, fmtEUR } from '../store/store' +import { fmtDatum } from '../lib/format' const monthShort = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'] @@ -146,7 +147,7 @@ function TenantDashboard() { {a.tag} - {a.date} + {fmtDatum(a.date)}
{a.title}
{a.text}
@@ -174,7 +175,7 @@ function TenantDashboard() {
  • {t.title}
    -
    {t.category} · {t.createdAt}
    +
    {t.category} · {fmtDatum(t.createdAt)}
    - {d.date} + {fmtDatum(d.date)} {d.size}
    diff --git a/src/pages/Kosten.tsx b/src/pages/Kosten.tsx index 5bad441..ff7a299 100644 --- a/src/pages/Kosten.tsx +++ b/src/pages/Kosten.tsx @@ -28,6 +28,7 @@ import { TableRow, } from '@/components/ui/table' import { useStore, fmtEUR } from '../store/store' +import { fmtDatum } from '../lib/format' import type { Beleg, DocumentItem, KostenItem } from '../types' import PdfLightbox from '../components/PdfLightbox' @@ -408,7 +409,7 @@ function BelegeListe() { {b.notiz &&
    {b.notiz}
    } - {b.datum || '–'} + {b.datum ? fmtDatum(b.datum) : '–'} {b.jahr && · {b.jahr}} {b.kategorie || '–'} diff --git a/src/pages/Messages.tsx b/src/pages/Messages.tsx index 876cea8..d1230bc 100644 --- a/src/pages/Messages.tsx +++ b/src/pages/Messages.tsx @@ -14,6 +14,7 @@ import { } from '@/components/ui/dialog' import { Label } from '@/components/ui/label' import { useStore } from '../store/store' +import { fmtDatumZeit } from '../lib/format' function NewMessageDialog({ onCreated }: { onCreated: (id: string) => void }) { const { newThread, role, meineEinheit } = useStore() @@ -185,7 +186,7 @@ export default function Messages() { )}
    {m.text}
    - {m.at} + {fmtDatumZeit(m.at)}
    diff --git a/src/pages/Profil.tsx b/src/pages/Profil.tsx index 4266220..ae820b8 100644 --- a/src/pages/Profil.tsx +++ b/src/pages/Profil.tsx @@ -4,6 +4,7 @@ import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { useStore } from '../store/store' +import { fmtDatum } from '../lib/format' export default function Profil() { const { me, role, meineEinheit, units, aktualisiereProfil } = useStore() @@ -88,7 +89,7 @@ export default function Profil() {
    Mitglied seit
    -
    {me.erstelltAm}
    +
    {fmtDatum(me.erstelltAm)}
    diff --git a/src/pages/Tickets.tsx b/src/pages/Tickets.tsx index 893cd6c..8d2f74e 100644 --- a/src/pages/Tickets.tsx +++ b/src/pages/Tickets.tsx @@ -22,6 +22,7 @@ import { SelectValue, } from '@/components/ui/select' import { useStore } from '../store/store' +import { fmtDatum, fmtDatumZeit } from '../lib/format' import type { TicketPriority, TicketStatus } from '../types' const statusStyle: Record = { @@ -214,7 +215,7 @@ export default function Tickets() { {role === 'admin' && ` · ${t.tenant}`}
    - {t.createdAt} + {fmtDatum(t.createdAt)} Kategorie: {t.category} @@ -265,7 +266,7 @@ export default function Tickets() {
    {letzte.author}:{' '} {letzte.text} - {letzte.at} + {fmtDatumZeit(letzte.at)}
    )} diff --git a/src/pages/Zaehler.tsx b/src/pages/Zaehler.tsx index 2c7f72b..a1ed7f0 100644 --- a/src/pages/Zaehler.tsx +++ b/src/pages/Zaehler.tsx @@ -20,6 +20,7 @@ import { SelectValue, } from '@/components/ui/select' import { useStore } from '../store/store' +import { fmtDatum } from '../lib/format' import type { Meter, MeterReading, ZaehlerArt } from '../types' const ARTEN: ZaehlerArt[] = ['Strom', 'Wasser', 'Gas', 'Wärme', 'Sonstiges'] @@ -282,7 +283,7 @@ function VerlaufDialog({ meter }: { meter: Meter }) {
    - Ablesedatum: {r.standAm} + Ablesedatum: {fmtDatum(r.standAm)} {r.erfasstVon && · erfasst von {r.erfasstVon}}
    {r.anomalie && r.anomalieGrund && ( @@ -317,7 +318,7 @@ function StandEintragenDialog({ meter }: { meter: Meter }) { setSende(true) setFehler(null) setWarnung(null) - const r = await zaehlerstandEintragen(meter.id, standZahl, datum) + const r = await zaehlerstandEintragen(meter.id, standZahl, datum.split('-').reverse().join('.')) setSende(false) if (r.fehler) return setFehler(r.fehler) if (r.anomalie) { @@ -528,7 +529,7 @@ export default function Zaehler() {
    - Stand: {m.standAm} + Stand: {fmtDatum(m.standAm)} (gepflegt: {m.geaendertAm})
    @@ -572,7 +573,7 @@ export default function Zaehler() { {fmtStand(m.stand)} - {m.standAm} + {fmtDatum(m.standAm)} {m.letzteAnomalie && (