feat: Admin-Toggle für mobile Zeiterfassung
Backend:
- Company.mobile_stamping_enabled (BOOLEAN DEFAULT TRUE)
- CompanyOut + CompanyUpdate: neues Feld
- Migration 0027: companies.mobile_stamping_enabled
Frontend Desktop (CompanySettingsPage):
- Abschnitt 'Mobile-Ansicht' mit Toggle-Switch
- Speichert via PATCH /companies/me
Frontend Mobile (MobileStampScreen):
- Lädt mobile_stamping_enabled aus GET /companies/me
- Deaktiviert: Hinweis-Banner statt Buttons
('Einstempeln nicht verfügbar – bitte Kiosk/Desktop nutzen')
- Aktiviert: normales Verhalten
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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<TodayStatus | null>(null)
|
||||
const [balance, setBalance] = useState<BalanceResponse | null>(null)
|
||||
const [stampingAllowed, setStampingAllowed] = useState(true)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [stamping, setStamping] = useState(false)
|
||||
const [error, setError] = useState<string | null>(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<TodayStatus>('/dashboard/me'),
|
||||
api.get<BalanceResponse>(`/time/balance/me?period_start=${monday}`),
|
||||
api.get<CompanySettings>('/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() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Stempeln deaktiviert */}
|
||||
{!stampingAllowed && (
|
||||
<div className='bg-amber-50 border border-amber-200 rounded-xl px-4 py-4 flex gap-3 items-start'>
|
||||
<span className='text-xl mt-0.5'>🔒</span>
|
||||
<div>
|
||||
<p className='text-sm font-semibold text-amber-800'>Einstempeln nicht verfügbar</p>
|
||||
<p className='text-xs text-amber-700 mt-0.5'>
|
||||
Dein Unternehmen hat die Zeiterfassung über die mobile Ansicht deaktiviert.
|
||||
Bitte nutze das Kiosk-Terminal oder die Desktop-Version.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Status-Karte */}
|
||||
<div className='bg-white rounded-2xl border border-gray-200 shadow-sm px-5 py-5 flex flex-col items-center gap-3'>
|
||||
{/* Status-Label */}
|
||||
@@ -232,8 +253,8 @@ export function MobileStampScreen() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Haupt-Button */}
|
||||
{!isOpen ? (
|
||||
{/* Haupt-Button – nur wenn Stempeln erlaubt */}
|
||||
{stampingAllowed && !isOpen ? (
|
||||
<button
|
||||
onClick={stampIn}
|
||||
disabled={stamping}
|
||||
@@ -243,7 +264,7 @@ export function MobileStampScreen() {
|
||||
<span className='animate-spin rounded-full h-7 w-7 border-4 border-white border-t-transparent' />
|
||||
) : 'EINSTEMPELN'}
|
||||
</button>
|
||||
) : isOnBreak ? (
|
||||
) : stampingAllowed && isOnBreak ? (
|
||||
<button
|
||||
onClick={breakEnd}
|
||||
disabled={stamping}
|
||||
@@ -253,7 +274,7 @@ export function MobileStampScreen() {
|
||||
<span className='animate-spin rounded-full h-7 w-7 border-4 border-white border-t-transparent' />
|
||||
) : 'PAUSE BEENDEN'}
|
||||
</button>
|
||||
) : (
|
||||
) : stampingAllowed ? (
|
||||
<button
|
||||
onClick={stampOut}
|
||||
disabled={stamping}
|
||||
@@ -265,8 +286,10 @@ export function MobileStampScreen() {
|
||||
</button>
|
||||
)}
|
||||
|
||||
) : null}
|
||||
|
||||
{/* Sekundärer Button: Pause starten */}
|
||||
{isOpen && !isOnBreak && (
|
||||
{stampingAllowed && isOpen && !isOnBreak && (
|
||||
<button
|
||||
onClick={breakStart}
|
||||
disabled={stamping}
|
||||
|
||||
Reference in New Issue
Block a user