import { useEffect, useState, useCallback } from 'react' import { api } from '../api/client' import { Spinner } from '../components/Spinner' import { Layout } from '../components/Layout' interface UserOut { id: string; first_name: string; last_name: string; email: string; role: string; company_id: string } interface WorkScheduleOut { id: string company_id: string name: string mon_h: number; tue_h: number; wed_h: number; thu_h: number fri_h: number; sat_h: number; sun_h: number valid_from: string } interface UserListResponse { total: number items: UserOut[] } const DAYS = [ { key: 'mon_h', label: 'Mo' }, { key: 'tue_h', label: 'Di' }, { key: 'wed_h', label: 'Mi' }, { key: 'thu_h', label: 'Do' }, { key: 'fri_h', label: 'Fr' }, { key: 'sat_h', label: 'Sa' }, { key: 'sun_h', label: 'So' }, ] as const const EMPTY_FORM = { name: '', mon_h: 8, tue_h: 8, wed_h: 8, thu_h: 8, fri_h: 8, sat_h: 0, sun_h: 0, valid_from: new Date().toISOString().slice(0, 10) } export function WorkSchedulePage() { const [user, setUser] = useState(null) const [schedules, setSchedules] = useState([]) const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) const [showForm, setShowForm] = useState(false) const [editId, setEditId] = useState(null) const [form, setForm] = useState(EMPTY_FORM) const [saving, setSaving] = useState(false) const [error, setError] = useState('') const [success, setSuccess] = useState('') const [assignModal, setAssignModal] = useState(null) // scheduleId const [assigning, setAssigning] = useState(false) const load = useCallback(async () => { setLoading(true) try { const [me, sched, ul] = await Promise.all([ api.get('/auth/me'), api.get('/time/schedules'), api.get('/users/?limit=200&active_only=true'), ]) setUser(me) setSchedules(sched) setUsers(ul.items) } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Fehler') } finally { setLoading(false) } }, []) useEffect(() => { load() }, [load]) const openCreate = () => { setEditId(null) setForm(EMPTY_FORM) setShowForm(true) setError('') } const openEdit = (s: WorkScheduleOut) => { setEditId(s.id) setForm({ name: s.name, mon_h: s.mon_h, tue_h: s.tue_h, wed_h: s.wed_h, thu_h: s.thu_h, fri_h: s.fri_h, sat_h: s.sat_h, sun_h: s.sun_h, valid_from: s.valid_from }) setShowForm(true) setError('') } const save = async () => { setSaving(true) setError('') try { if (editId) { await api.patch(`/time/schedules/${editId}`, form) } else { await api.post('/time/schedules', form) } setShowForm(false) setSuccess(editId ? 'Arbeitsplan aktualisiert.' : 'Arbeitsplan erstellt.') await load() } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Fehler') } finally { setSaving(false) } } const del = async (id: string) => { if (!confirm('Arbeitsplan wirklich löschen?')) return setError('') try { await api.del(`/time/schedules/${id}`) setSuccess('Arbeitsplan gelöscht.') await load() } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Fehler') } } const assignSchedule = async (userId: string, scheduleId: string | null) => { setAssigning(true) try { await api.patch(`/users/${userId}`, { work_schedule_id: scheduleId }) await load() } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Fehler bei Zuweisung') } finally { setAssigning(false) } } const totalH = (s: typeof form) => [s.mon_h, s.tue_h, s.wed_h, s.thu_h, s.fri_h, s.sat_h, s.sun_h].reduce((a, b) => a + Number(b), 0) const inputCls = 'border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500' if (loading) return
if (!user) return null return (

Arbeitspläne

{error &&
{error}
} {success &&
{success}
} {/* Schedule list */}
{schedules.length === 0 && (
Noch keine Arbeitspläne. Klicke auf „+ Neuer Arbeitsplan".
)} {schedules.map(s => { const weekly = [s.mon_h, s.tue_h, s.wed_h, s.thu_h, s.fri_h, s.sat_h, s.sun_h].reduce((a, b) => a + Number(b), 0) const assigned = users.filter(u => (u as UserOut & { work_schedule_id?: string }).work_schedule_id === s.id) return (

{s.name}

Gültig ab {new Date(s.valid_from).toLocaleDateString('de-DE')} · {weekly}h/Woche

{/* Day grid */}
{DAYS.map(d => (
0 ? 'bg-blue-50' : 'bg-gray-50'}`}>

{d.label}

0 ? 'text-blue-700' : 'text-gray-300'}`}>{Number(s[d.key])}h

))}
{/* Assigned users */} {assigned.length > 0 && (

Zugewiesen: {assigned.map(u => `${u.first_name} ${u.last_name}`).join(', ')}

)}
) })}
{/* Users without schedule */} {(() => { const unassigned = users.filter(u => !(u as UserOut & { work_schedule_id?: string }).work_schedule_id) if (unassigned.length === 0) return null return (

Mitarbeiter ohne Arbeitsplan ({unassigned.length})

{unassigned.map(u => ( {u.first_name} {u.last_name} ))}
) })()}
{/* Create/Edit Modal */} {showForm && (

{editId ? 'Arbeitsplan bearbeiten' : 'Neuer Arbeitsplan'}

{error &&

{error}

}
setForm(f => ({ ...f, name: e.target.value }))} placeholder='z. B. Vollzeit (40h)' className={`w-full ${inputCls}`} />
{DAYS.map(d => (

{d.label}

setForm(f => ({ ...f, [d.key]: Number(e.target.value) }))} className='w-full border border-gray-300 rounded text-sm text-center py-1.5 focus:outline-none focus:ring-2 focus:ring-blue-500' />
))}

Gesamt: {totalH(form)}h/Woche

setForm(f => ({ ...f, valid_from: e.target.value }))} className={`w-full ${inputCls}`} />
)} {/* Assign Modal */} {assignModal && (

Mitarbeiter zuweisen

{users.map(u => { const userWithSchedule = u as UserOut & { work_schedule_id?: string } const isAssigned = userWithSchedule.work_schedule_id === assignModal return (

{u.first_name} {u.last_name}

{u.email}

) })}
)}
) }