'use client'; import { useCallback, useEffect, useState } from 'react'; import { useTranslations } from 'next-intl'; import { fetchSources, addSource, updateSource, deleteSource, testSource, } from '@/lib/calendar-api'; import type { CalendarSource, CreateSourcePayload } from '@/lib/calendar-api'; import { CalendarSourceForm } from './calendar-source-form'; /** * Type badge label map. */ const TYPE_LABELS: Record = { caldav: 'CalDAV', ics: 'ICS', exchange: 'Exchange', }; /** * Calendar Settings Panel (D-08, D-09, D-11). * * Manages calendar sources: list with visibility toggle (CAL-02), * add/edit forms, connection test, delete with confirmation dialog. */ export function CalendarSettingsPanel() { const t = useTranslations('widgets'); const ts = useTranslations('settings'); const [sources, setSources] = useState([]); const [isLoading, setIsLoading] = useState(true); const [showAddForm, setShowAddForm] = useState(false); const [editingId, setEditingId] = useState(null); const [deletingId, setDeletingId] = useState(null); const [isSaving, setIsSaving] = useState(false); const [saveError, setSaveError] = useState(null); const [editSaveError, setEditSaveError] = useState(null); const [editSaveSuccess, setEditSaveSuccess] = useState(false); const [testResults, setTestResults] = useState>({}); // Load sources on mount useEffect(() => { fetchSources() .then(setSources) .catch(() => { // Silent fail — show empty state }) .finally(() => setIsLoading(false)); }, []); // Toggle visibility (CAL-02) const handleVisibilityToggle = useCallback( async (source: CalendarSource) => { const newIsVisible = !source.isVisible; // Optimistic update setSources((prev) => prev.map((s) => s.id === source.id ? { ...s, isVisible: newIsVisible } : s, ), ); try { await updateSource(source.id, { isVisible: newIsVisible }); } catch { // Revert on error setSources((prev) => prev.map((s) => s.id === source.id ? { ...s, isVisible: source.isVisible } : s, ), ); } }, [], ); // Add new source const handleAddSource = useCallback(async (payload: CreateSourcePayload) => { setIsSaving(true); setSaveError(null); try { const created = await addSource(payload); setSources((prev) => [...prev, created]); setShowAddForm(false); // Auto-test connection after save setTestResults((prev) => ({ ...prev, [created.id]: 'testing' })); try { const result = await testSource(created.id); setTestResults((prev) => ({ ...prev, [created.id]: result.success ? 'success' : 'error', })); } catch { setTestResults((prev) => ({ ...prev, [created.id]: 'error' })); } } catch { setSaveError(t('calendar.saveError') || 'Fehler beim Speichern'); } finally { setIsSaving(false); } }, [t]); // Delete source const handleDeleteSource = useCallback(async (id: string) => { try { await deleteSource(id); setSources((prev) => prev.filter((s) => s.id !== id)); setDeletingId(null); } catch { // Error handled silently } }, []); // Loading state if (isLoading) { return (
Loading...
); } return (
{/* Source list */} {sources.length === 0 && !showAddForm && (

{t('calendar.sourceEmpty')}

)} {sources.length > 0 && (
{sources.map((source) => (
{/* Color dot */}
))}
)} {/* Edit form (below the source being edited) */} {editingId && (
{editSaveError && (

{editSaveError}

)} {editSaveSuccess && (

{t('calendar.saveSuccess') || 'Gespeichert'}

)}

Edit Source

{ const s = sources.find((src) => src.id === editingId); return s ? { id: s.id, name: s.name, type: s.type, url: s.url, username: s.username ?? '', exchangeMode: s.exchangeMode ?? undefined, domain: s.domain ?? undefined, color: s.color ?? undefined, } : undefined; })()} onSave={async (payload) => { setIsSaving(true); setEditSaveError(null); setEditSaveSuccess(false); try { const updated = await updateSource(editingId, payload); setSources((prev) => prev.map((s) => (s.id === editingId ? updated : s)), ); setEditSaveSuccess(true); } catch { setEditSaveError(t('calendar.saveError') || 'Fehler beim Speichern'); } finally { setIsSaving(false); } }} onCancel={() => setEditingId(null)} isSaving={isSaving} />
)} {/* Add source button */} {!showAddForm && ( )} {/* Add source form */} {showAddForm && (

{t('calendar.addSource')}

{saveError && (

{saveError}

)} setShowAddForm(false)} isSaving={isSaving} />
)} {/* Delete confirmation dialog (UI-SPEC destructive pattern) */} {deletingId && (
{ if (e.target === e.currentTarget) setDeletingId(null); }} >

{ts('sourceDeleteCta')}

{ts('sourceDeleteConfirm')}

)}
); }