import { useEffect, useState } from 'react' import { api } from '../api/client' import { Layout } from '../components/Layout' interface UserOut { id: string first_name: string last_name: string email: string role: string } interface PreviewEntry { kind: 'time' | 'absence' date_from: string date_to: string start: string | null end: string | null break_minutes: number worked_hours: number | null absence_type: string | null note: string | null skipped: boolean skip_reason: string | null } interface PreviewResponse { preview: PreviewEntry[] time_count: number absence_count: number skip_count: number errors: string[] } interface RunResponse { time_imported: number absence_imported: number skipped: number errors: string[] } export default function ImportPage() { const [me, setMe] = useState(null) const [users, setUsers] = useState([]) const [targetUserId, setTargetUserId] = useState('') const [file, setFile] = useState(null) const [preview, setPreview] = useState(null) const [result, setResult] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { api.get('/auth/me').then(setMe).catch(() => {}) api.get<{ total: number; items: UserOut[] }>('/users/?limit=500') .then(r => setUsers(r.items)).catch(() => {}) }, []) async function loadPreview() { if (!targetUserId || !file) return setLoading(true) setError(null) setPreview(null) setResult(null) try { const form = new FormData() form.append('user_id', targetUserId) form.append('file', file) const r = await api.postForm('/import/kimai/preview', form) setPreview(r) } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Fehler beim Laden der Vorschau') } finally { setLoading(false) } } async function runImport() { if (!targetUserId || !file) return setLoading(true) setError(null) setResult(null) try { const form = new FormData() form.append('user_id', targetUserId) form.append('file', file) const r = await api.postForm('/import/kimai/run', form) setResult(r) setPreview(null) } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Fehler beim Import') } finally { setLoading(false) } } const selectedUser = users.find(u => u.id === targetUserId) return (

Kimai-Import

{/* Konfiguration */}

Einstellungen

{ setFile(e.target.files?.[0] ?? null); setPreview(null); setResult(null) }} className="w-full text-sm text-gray-700 border border-gray-300 rounded-lg px-3 py-2 file:mr-3 file:py-1 file:px-3 file:rounded file:border-0 file:text-sm file:bg-blue-50 file:text-blue-700" />
{preview && ( )}
{error && (
{error}
)} {/* Ergebnis nach Import */} {result && (

Import abgeschlossen

{result.time_imported} Zeiteinträge importiert {result.absence_imported} Abwesenheiten importiert {result.skipped > 0 && {result.skipped} übersprungen}
{result.errors.length > 0 && (
    {result.errors.map((e, i) =>
  • ⚠ {e}
  • )}
)}
)} {/* Vorschau-Tabelle */} {preview && (

Vorschau für {selectedUser ? `${selectedUser.first_name} ${selectedUser.last_name}` : ''}

{preview.time_count} Zeiteinträge {preview.absence_count} Abwesenheiten {preview.skip_count > 0 && ( {preview.skip_count} übersprungen )}
{preview.errors.length > 0 && (
{preview.errors.map((e, i) => (

⚠ {e}

))}
)}
{preview.preview.map((row, i) => ( ))}
Typ Datum Beginn Ende Stunden Notiz Status
{row.kind === 'time' ? ( Zeit ) : ( {row.absence_type ?? 'Abwesenheit'} )} {row.date_from} {row.date_to !== row.date_from && ( – {row.date_to} )} {row.start ?? '–'} {row.end ?? '–'} {row.break_minutes > 0 && ( ({row.break_minutes}m Pause) )} {row.worked_hours != null ? `${row.worked_hours.toFixed(2)} h` : '–'} {row.note ?? '–'} {row.skipped ? ( Übersprungen ) : ( Import )}
)}
) }