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 && (
|