diff --git a/DEVLOG.md b/DEVLOG.md index 6c5b04f..620b46b 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1134,3 +1134,31 @@ Keine Commits in dieser Session. - frontend/src/pages/LoginPage.tsx | 11 +++++++++-- --- +## 2026-05-24 23:28 – 23:31 (2m) +**Beschreibung:** Claude Code Session +**Projekt:** timemaster + +### Commits +- 22be68e feat: Abwesenheiten-Screen in Mobile-App + +### Geänderte Dateien +- DEVLOG.md | 40 +++ +- frontend/src/pages/mobile/MobileAbsencesScreen.tsx | 370 +++++++++++++++++++++ +- frontend/src/pages/mobile/MobileBottomNav.tsx | 16 +- +- frontend/src/pages/mobile/MobilePage.tsx | 17 +- + +--- +## 2026-05-24 23:33 – 23:33 (0m) +**Beschreibung:** Claude Code Session +**Projekt:** timemaster + +### Commits +Keine Commits in dieser Session. + +### Geänderte Dateien +- DEVLOG.md | 40 +++ +- frontend/src/pages/mobile/MobileAbsencesScreen.tsx | 370 +++++++++++++++++++++ +- frontend/src/pages/mobile/MobileBottomNav.tsx | 16 +- +- frontend/src/pages/mobile/MobilePage.tsx | 17 +- + +--- diff --git a/backend/app/models/company.py b/backend/app/models/company.py index e51947a..7107744 100644 --- a/backend/app/models/company.py +++ b/backend/app/models/company.py @@ -49,6 +49,9 @@ class Company(Base): kiosk_track_current_user: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) kiosk_heartbeat_interval_sec: Mapped[int] = mapped_column(Integer, nullable=False, default=30) + # Mobile-Konfiguration + mobile_stamping_enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) + # Relationships users: Mapped[list["User"]] = relationship("User", back_populates="company", lazy="noload") departments: Mapped[list["Department"]] = relationship("Department", back_populates="company", lazy="noload") diff --git a/backend/app/schemas/company.py b/backend/app/schemas/company.py index cb774a2..b820083 100644 --- a/backend/app/schemas/company.py +++ b/backend/app/schemas/company.py @@ -21,6 +21,7 @@ class CompanyOut(BaseModel): personnel_number_required: bool = False personnel_number_mode: PersonnelNumberModeT = "manual" personnel_number_next: int = 1 + mobile_stamping_enabled: bool = True class CompanyUpdate(BaseModel): @@ -30,6 +31,7 @@ class CompanyUpdate(BaseModel): personnel_number_required: bool | None = None personnel_number_mode: PersonnelNumberModeT | None = None personnel_number_next: int | None = Field(None, ge=1) + mobile_stamping_enabled: bool | None = None class DepartmentOut(BaseModel): diff --git a/backend/migrations/versions/0027_mobile_stamping.py b/backend/migrations/versions/0027_mobile_stamping.py new file mode 100644 index 0000000..a0b471e --- /dev/null +++ b/backend/migrations/versions/0027_mobile_stamping.py @@ -0,0 +1,29 @@ +"""Mobile-Konfiguration: mobile_stamping_enabled + +Revision ID: 0027 +Revises: 0026 +Create Date: 2026-05-24 + +Neues Feld in companies: + mobile_stamping_enabled BOOLEAN DEFAULT TRUE + Steuert ob der Einstempel-Button in der mobilen Ansicht sichtbar ist. + Default TRUE: alle bestehenden Firmen bleiben unverändert. +""" +from alembic import op +import sqlalchemy as sa + +revision = "0027" +down_revision = "0026" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "companies", + sa.Column("mobile_stamping_enabled", sa.Boolean(), nullable=False, server_default=sa.text("true")), + ) + + +def downgrade() -> None: + op.drop_column("companies", "mobile_stamping_enabled") diff --git a/frontend/src/pages/CompanySettingsPage.tsx b/frontend/src/pages/CompanySettingsPage.tsx index 47c36a3..39cba66 100644 --- a/frontend/src/pages/CompanySettingsPage.tsx +++ b/frontend/src/pages/CompanySettingsPage.tsx @@ -53,6 +53,8 @@ export function CompanySettingsPage() { const [pnRequired, setPnRequired] = useState(false) const [pnMode, setPnMode] = useState<'manual' | 'auto'>('manual') const [pnNext, setPnNext] = useState(1) + // Mobile + const [mobileStamping, setMobileStamping] = useState(true) // Busylight const [blStatus, setBlStatus] = useState<{ configured: boolean; created_at: string | null } | null>(null) const [blPlaintext, setBlPlaintext] = useState(null) @@ -76,6 +78,7 @@ export function CompanySettingsPage() { setPnRequired(c.personnel_number_required ?? false) setPnMode(c.personnel_number_mode ?? 'manual') setPnNext(c.personnel_number_next ?? 1) + setMobileStamping((c as CompanyOut & { mobile_stamping_enabled?: boolean }).mobile_stamping_enabled ?? true) }).catch(() => {}) api.get<{ configured: boolean; created_at: string | null }>('/companies/me/busylight-token') .then(setBlStatus) @@ -143,6 +146,7 @@ export function CompanySettingsPage() { personnel_number_required: pnRequired, personnel_number_mode: pnMode, personnel_number_next: pnNext, + mobile_stamping_enabled: mobileStamping, }) setCompany(updated) setSaved(true) @@ -519,6 +523,37 @@ export function CompanySettingsPage() { )} + {/* Mobile-Einstellungen */} +
+
+ 📱 +

Mobile-Ansicht

+
+
+
+

Einstempeln über Mobile erlauben

+

+ Wenn deaktiviert, sehen Mitarbeiter unter /mobile keinen Stempel-Button. + Nützlich wenn Zeiterfassung nur über Kiosk-Terminals erfolgen soll. +

+
+ +
+
+ {/* Firmen-Info (readonly) */} {company && (
diff --git a/frontend/src/pages/mobile/MobileStampScreen.tsx b/frontend/src/pages/mobile/MobileStampScreen.tsx index b5f5426..53d39c5 100644 --- a/frontend/src/pages/mobile/MobileStampScreen.tsx +++ b/frontend/src/pages/mobile/MobileStampScreen.tsx @@ -9,6 +9,10 @@ interface TodayStatus { break_minutes?: number } +interface CompanySettings { + mobile_stamping_enabled: boolean +} + interface TimeEntryWithWarnings { entry: { id: string } warnings: string[] @@ -64,6 +68,7 @@ function fmtTime(iso: string | null): string { export function MobileStampScreen() { const [dashboard, setDashboard] = useState(null) const [balance, setBalance] = useState(null) + const [stampingAllowed, setStampingAllowed] = useState(true) const [loading, setLoading] = useState(true) const [stamping, setStamping] = useState(false) const [error, setError] = useState(null) @@ -78,12 +83,14 @@ export function MobileStampScreen() { setError(null) try { const monday = getMondayOfCurrentWeek() - const [dash, bal] = await Promise.all([ + const [dash, bal, company] = await Promise.all([ api.get('/dashboard/me'), api.get(`/time/balance/me?period_start=${monday}`), + api.get('/companies/me'), ]) setDashboard(dash) setBalance(bal) + setStampingAllowed(company.mobile_stamping_enabled ?? true) } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Fehler beim Laden') } finally { @@ -190,6 +197,20 @@ export function MobileStampScreen() {
)} + {/* Stempeln deaktiviert */} + {!stampingAllowed && ( +
+ 🔒 +
+

Einstempeln nicht verfügbar

+

+ Dein Unternehmen hat die Zeiterfassung über die mobile Ansicht deaktiviert. + Bitte nutze das Kiosk-Terminal oder die Desktop-Version. +

+
+
+ )} + {/* Status-Karte */}
{/* Status-Label */} @@ -232,8 +253,8 @@ export function MobileStampScreen() {
)} - {/* Haupt-Button */} - {!isOpen ? ( + {/* Haupt-Button – nur wenn Stempeln erlaubt */} + {stampingAllowed && !isOpen ? ( - ) : isOnBreak ? ( + ) : stampingAllowed && isOnBreak ? ( - ) : ( + ) : stampingAllowed ? (