diff --git a/DEVLOG.md b/DEVLOG.md index b1aa9bc..6c5b04f 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1094,3 +1094,43 @@ Keine Commits in dieser Session. - frontend/src/api/client.ts | 56 ++++++++++++++++++++++++++++++++++++++++++++++ --- +## 2026-05-24 21:15 – 21:17 (2m) +**Beschreibung:** Claude Code Session +**Projekt:** timemaster + +### Commits +- edb1568 feat: mobile Login-Seite /mobile/login + +### Geänderte Dateien +- DEVLOG.md | 77 ++++++ +- frontend/src/App.tsx | 4 + +- frontend/src/pages/mobile/MobileBottomNav.tsx | 69 +++++ +- frontend/src/pages/mobile/MobileLoginPage.tsx | 195 ++++++++++++++ +- frontend/src/pages/mobile/MobilePage.tsx | 75 ++++++ +- frontend/src/pages/mobile/MobileProfileScreen.tsx | 124 +++++++++ +- frontend/src/pages/mobile/MobileStampScreen.tsx | 310 ++++++++++++++++++++++ +- frontend/src/pages/mobile/MobileTodayScreen.tsx | 146 ++++++++++ + +--- +## 2026-05-24 23:21 – 23:22 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** timemaster + +### Commits +- 4a1dec7 fix: mobile/tablet Geräteerkennung in LoginPage → Redirect zu /mobile/login + +### Geänderte Dateien +- frontend/src/pages/LoginPage.tsx | 11 +++++++++-- + +--- +## 2026-05-24 23:23 – 23:23 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** timemaster + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- frontend/src/pages/LoginPage.tsx | 11 +++++++++-- + +--- diff --git a/frontend/src/pages/mobile/MobileAbsencesScreen.tsx b/frontend/src/pages/mobile/MobileAbsencesScreen.tsx new file mode 100644 index 0000000..4295db5 --- /dev/null +++ b/frontend/src/pages/mobile/MobileAbsencesScreen.tsx @@ -0,0 +1,370 @@ +import { useCallback, useEffect, useState } from 'react' +import { api } from '../../api/client' + +// ── Typen ──────────────────────────────────────────────────────────────────── + +interface AbsenceType { + id: string + name: string + category: string + color: string | null + requires_approval: boolean +} + +interface Absence { + id: string + type_id: string + start_date: string + end_date: string + half_day_start: boolean + half_day_end: boolean + working_days: number + status: 'pending' | 'approved' | 'rejected' | 'cancelled' + note: string | null + rejection_reason: string | null +} + +interface AbsenceList { + total: number + items: Absence[] +} + +interface VacationBalance { + entitled_days: number + special_days: number + carried_over: number + used_days: number + total_days: number + remaining_days: number + pending_days: number +} + +// ── Helpers ────────────────────────────────────────────────────────────────── + +function fmtDate(d: string) { + return new Date(d + 'T00:00:00').toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) +} + +function statusLabel(s: Absence['status']) { + return { pending: 'Ausstehend', approved: 'Genehmigt', rejected: 'Abgelehnt', cancelled: 'Storniert' }[s] +} + +function statusColor(s: Absence['status']) { + return { + pending: 'bg-yellow-100 text-yellow-700', + approved: 'bg-green-100 text-green-700', + rejected: 'bg-red-100 text-red-700', + cancelled: 'bg-gray-100 text-gray-500', + }[s] +} + +const today = new Date() +const todayStr = today.toISOString().slice(0, 10) + +// ── Neuer Antrag Modal ─────────────────────────────────────────────────────── + +interface NewAbsenceModalProps { + types: AbsenceType[] + onClose: () => void + onCreated: () => void +} + +function NewAbsenceModal({ types, onClose, onCreated }: NewAbsenceModalProps) { + const [typeId, setTypeId] = useState(types[0]?.id ?? '') + const [startDate, setStartDate] = useState(todayStr) + const [endDate, setEndDate] = useState(todayStr) + const [note, setNote] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + + // Kategorie SICK → quick-sick, sonst normaler Antrag + const selectedType = types.find(t => t.id === typeId) + const isSick = selectedType?.category === 'SICK' + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true); setError(null) + try { + if (isSick) { + await api.post('/absences/quick-sick', { start_date: startDate, end_date: endDate }) + } else { + await api.post('/absences/', { type_id: typeId, start_date: startDate, end_date: endDate, note: note || null }) + } + onCreated() + } catch (e: unknown) { + setError(e instanceof Error ? e.message : 'Fehler beim Erstellen') + } finally { + setLoading(false) } + } + + return ( +
+
e.stopPropagation()} + > +
+

Neuer Antrag

+ +
+ + {error && ( +
{error}
+ )} + +
+ {/* Typ */} +
+ + +
+ + {/* Datum */} +
+
+ + { setStartDate(e.target.value); if (e.target.value > endDate) setEndDate(e.target.value) }} + required + className='min-h-[48px] px-3 rounded-xl border border-gray-300 text-gray-900 text-base focus:outline-none focus:ring-2 focus:ring-blue-500' + /> +
+
+ + setEndDate(e.target.value)} + required + className='min-h-[48px] px-3 rounded-xl border border-gray-300 text-gray-900 text-base focus:outline-none focus:ring-2 focus:ring-blue-500' + /> +
+
+ + {/* Notiz (nicht bei Krankmeldung) */} + {!isSick && ( +
+ +