diff --git a/frontend/src/components/agents/AgentMonitor.tsx b/frontend/src/components/agents/AgentMonitor.tsx
index 7ccf97d..7539934 100644
--- a/frontend/src/components/agents/AgentMonitor.tsx
+++ b/frontend/src/components/agents/AgentMonitor.tsx
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
-import { ChartBarIcon, ExclamationTriangleIcon, CurrencyDollarIcon, ClockIcon } from '@heroicons/react/24/outline';
+import { BarChart3, AlertTriangle, DollarSign, Clock } from 'lucide-react';
export function AgentMonitor() {
const { t } = useTranslation();
@@ -32,28 +32,28 @@ export function AgentMonitor() {
-
+
{t('agents.activeRuns')}
{stats?.active_runs ?? 0}
-
+
{t('agents.totalBudget')}
${(stats?.total_budget_usd ?? 0).toFixed(4)}
-
+
{t('agents.runsPerHour')}
{stats?.runs_per_hour ?? 0}
-
+
{t('agents.errorRate')}
{(stats?.error_rate ?? 0).toFixed(1)}%
diff --git a/frontend/src/components/agents/AgentRunLog.tsx b/frontend/src/components/agents/AgentRunLog.tsx
index c83c694..b77dca3 100644
--- a/frontend/src/components/agents/AgentRunLog.tsx
+++ b/frontend/src/components/agents/AgentRunLog.tsx
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
-import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
+import { Download } from 'lucide-react';
interface RunStep {
id: string;
@@ -52,10 +52,10 @@ export function AgentRunLog({ agentId, runId }: AgentRunLogProps) {
{t('agents.runLog')}
diff --git a/frontend/src/components/common/ABACRuleEditor.tsx b/frontend/src/components/common/ABACRuleEditor.tsx
index 37bf30c..b5078f4 100644
--- a/frontend/src/components/common/ABACRuleEditor.tsx
+++ b/frontend/src/components/common/ABACRuleEditor.tsx
@@ -9,6 +9,7 @@
* - Text-basierte Vorschau der Policy
*/
+import { asError } from '@/utils/errorTypes';
import React, { useState, useCallback, useMemo } from 'react';
import clsx from 'clsx';
import {
@@ -439,8 +440,8 @@ function PolicyForm({ initial, entityType, onSave, onCancel }: PolicyFormProps)
await createPolicy.mutateAsync(payload);
}
onSave();
- } catch (err: any) {
- setError(err?.message || t('abac.saveError', 'Failed to save policy'));
+ } catch (err: unknown) { const errObj = asError(err);
+ setError(errObj?.message || t('abac.saveError', 'Failed to save policy'));
}
}, [
initial,
diff --git a/frontend/src/components/common/SaveFilterDialog.tsx b/frontend/src/components/common/SaveFilterDialog.tsx
index 832792c..0d0f762 100644
--- a/frontend/src/components/common/SaveFilterDialog.tsx
+++ b/frontend/src/components/common/SaveFilterDialog.tsx
@@ -7,6 +7,7 @@
* shows a success toast, and closes.
*/
+import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Modal } from '@/components/ui/Modal';
@@ -98,9 +99,9 @@ export function SaveFilterDialog({
);
setName('');
onClose();
- } catch (err: any) {
+ } catch (err: unknown) { const errObj = asError(err);
toast.error(
- err?.message || t('savedFilters.saveError', 'Filter konnte nicht gespeichert werden')
+ errObj?.message || t('savedFilters.saveError', 'Filter konnte nicht gespeichert werden')
);
} finally {
setSubmitting(false);
diff --git a/frontend/src/components/common/SavedFilterBar.tsx b/frontend/src/components/common/SavedFilterBar.tsx
index 76dc3d9..7458aa6 100644
--- a/frontend/src/components/common/SavedFilterBar.tsx
+++ b/frontend/src/components/common/SavedFilterBar.tsx
@@ -10,6 +10,7 @@
* • Click-outside-to-close dropdown behaviour
*/
+import { asError } from '@/utils/errorTypes';
import React, { useState, useRef, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button';
@@ -86,8 +87,8 @@ export function SavedFilterBar({
const handleDelete = useCallback(
async (e: React.MouseEvent, id: string) => {
e.stopPropagation(); try { await deleteMutation.mutateAsync(id); if (activeFilterId === id) setActiveFilterId(null); toast.success(t('savedFilters.deleted', 'Filter gelöscht'));
- } catch (err: any) {
- toast.error(err?.message || t('common.error', 'Fehler'));
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj?.message || t('common.error', 'Fehler'));
} }, [deleteMutation, activeFilterId, toast, t]
);
diff --git a/frontend/src/components/contacts/ContactDetail.tsx b/frontend/src/components/contacts/ContactDetail.tsx
index 96cd63b..20db1c7 100644
--- a/frontend/src/components/contacts/ContactDetail.tsx
+++ b/frontend/src/components/contacts/ContactDetail.tsx
@@ -1,3 +1,4 @@
+import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button';
@@ -245,8 +246,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
await deleteMutation.mutateAsync({ id: contact.id });
toast.success(t('contacts.deleted'));
onDeleted();
- } catch (err: any) {
- toast.error(err.message || t('common.error'));
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj.message || t('common.error'));
}
};
@@ -261,8 +262,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
}
setPersonModalOpen(false);
setEditingPerson(null);
- } catch (err: any) {
- toast.error(err.message || t('common.error'));
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj.message || t('common.error'));
}
};
@@ -271,8 +272,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
try {
await deletePersonMutation.mutateAsync({ contactId: contact.id, personId: person.id });
toast.success(t('contacts.personDeleted'));
- } catch (err: any) {
- toast.error(err.message || t('common.error'));
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj.message || t('common.error'));
}
};
diff --git a/frontend/src/components/contacts/ContactEditForm.tsx b/frontend/src/components/contacts/ContactEditForm.tsx
index 4847a35..b799b02 100644
--- a/frontend/src/components/contacts/ContactEditForm.tsx
+++ b/frontend/src/components/contacts/ContactEditForm.tsx
@@ -1,3 +1,4 @@
+import { asError } from '@/utils/errorTypes';
import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form';
@@ -205,13 +206,13 @@ export function ContactEditForm({ onClose, contact, onSaved }: ContactEditFormPr
if (savedId && customFieldDefs.length > 0 && Object.keys(customValues).length > 0) {
try {
await updateCustomFields.mutateAsync({ contactId: savedId, values: customValues });
- } catch (cfErr: any) {
- console.error('Custom fields save failed:', cfErr);
+ } catch (cfErr: unknown) { const errObj = asError(cfErr);
+ console.error('Custom fields save failed:', errObj);
}
}
onClose();
- } catch (err: any) {
- toast.error(err.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
}
};
diff --git a/frontend/src/components/contacts/ContactEditModal.tsx b/frontend/src/components/contacts/ContactEditModal.tsx
index ba9bd84..0389fe9 100644
--- a/frontend/src/components/contacts/ContactEditModal.tsx
+++ b/frontend/src/components/contacts/ContactEditModal.tsx
@@ -1,3 +1,4 @@
+import { asError } from '@/utils/errorTypes';
import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form';
@@ -209,14 +210,14 @@ export function ContactEditModal({ open, onClose, contact, onSaved }: ContactEdi
if (savedId && customFieldDefs.length > 0 && Object.keys(customValues).length > 0) {
try {
await updateCustomFields.mutateAsync({ contactId: savedId, values: customValues });
- } catch (cfErr: any) {
+ } catch (cfErr: unknown) { const errObj = asError(cfErr);
// Don't fail the whole save if custom fields fail
- console.error('Custom fields save failed:', cfErr);
+ console.error('Custom fields save failed:', errObj);
}
}
onClose();
- } catch (err: any) {
- toast.error(err.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
}
};
diff --git a/frontend/src/components/contacts/ContactFolderTree.tsx b/frontend/src/components/contacts/ContactFolderTree.tsx
index b85dbbb..759c0cf 100644
--- a/frontend/src/components/contacts/ContactFolderTree.tsx
+++ b/frontend/src/components/contacts/ContactFolderTree.tsx
@@ -1,3 +1,4 @@
+import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom';
import clsx from 'clsx';
@@ -306,8 +307,8 @@ export function ContactFolderTree({
const name = prompt('Ordnername:');
if (!name) return;
createFolderMut.mutate({ name }, {
- onError: (err: any) => {
- const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim Anlegen';
+ onError: (err: unknown) => { const errObj = asError(err);
+ const msg = errObj?.response?.data?.detail?.detail || errObj?.response?.data?.detail || errObj?.message || 'Fehler beim Anlegen';
toast.error(typeof msg === 'string' ? msg : 'Fehler beim Anlegen des Ordners');
},
});
@@ -318,8 +319,8 @@ export function ContactFolderTree({
const newName = prompt('Neuer Name:', folder?.name || '');
if (!newName) return;
updateFolderMut.mutate({ id, data: { name: newName } }, {
- onError: (err: any) => {
- const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim Umbenennen';
+ onError: (err: unknown) => { const errObj = asError(err);
+ const msg = errObj?.response?.data?.detail?.detail || errObj?.response?.data?.detail || errObj?.message || 'Fehler beim Umbenennen';
toast.error(typeof msg === 'string' ? msg : 'Fehler beim Umbenennen');
},
});
@@ -328,8 +329,8 @@ export function ContactFolderTree({
const handleDelete = (id: string) => {
if (!confirm('Ordner l\u00f6schen? Kontakte bleiben erhalten, werden aber keinem Ordner mehr zugeordnet.')) return;
deleteFolderMut.mutate(id, {
- onError: (err: any) => {
- const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim L\u00f6schen';
+ onError: (err: unknown) => { const errObj = asError(err);
+ const msg = errObj?.response?.data?.detail?.detail || errObj?.response?.data?.detail || errObj?.message || 'Fehler beim L\u00f6schen';
toast.error(typeof msg === 'string' ? msg : 'Fehler beim L\u00f6schen');
},
});
diff --git a/frontend/src/components/import-export/ExportPanel.tsx b/frontend/src/components/import-export/ExportPanel.tsx
index c1ecb03..5fbffee 100644
--- a/frontend/src/components/import-export/ExportPanel.tsx
+++ b/frontend/src/components/import-export/ExportPanel.tsx
@@ -1,3 +1,4 @@
+import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Download, Loader2, FileText, CheckCircle } from 'lucide-react';
@@ -41,9 +42,9 @@ export function ExportPanel() {
toast.success(
t('importExport.exportSuccess', 'Export erfolgreich heruntergeladen')
);
- } catch (err: any) {
+ } catch (err: unknown) { const errObj = asError(err);
toast.error(
- err?.message || t('importExport.exportFailed', 'Export fehlgeschlagen')
+ errObj?.message || t('importExport.exportFailed', 'Export fehlgeschlagen')
);
} finally {
setIsExporting(false);
diff --git a/frontend/src/components/mail/SignatureManager.tsx b/frontend/src/components/mail/SignatureManager.tsx
index 8b25a57..019373f 100644
--- a/frontend/src/components/mail/SignatureManager.tsx
+++ b/frontend/src/components/mail/SignatureManager.tsx
@@ -3,6 +3,7 @@
* Supports placeholder variables for user/tenant data.
*/
+import { asError } from '@/utils/errorTypes';
import DOMPurify from 'dompurify';
import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -70,8 +71,8 @@ export function SignatureManager() {
setSignatures(data);
setError(null);
})
- .catch((err: any) => {
- setError(err?.message || err?.detail || (typeof err === 'string' ? err : 'Failed to load signatures'));
+ .catch((err: unknown) => { const errObj = asError(err);
+ setError(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Failed to load signatures'));
})
.finally(() => setLoading(false));
}, []);
@@ -103,8 +104,8 @@ export function SignatureManager() {
toast.success(t('mail.signatureCreated'));
}
setShowForm(false);
- } catch (err: any) {
- toast.error(err?.message || err?.detail || (typeof err === 'string' ? err : 'Save failed'));
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Save failed'));
} finally {
setSaving(false);
}
@@ -117,8 +118,8 @@ export function SignatureManager() {
setSignatures((prev) => prev.filter((s) => s.id !== deleteTarget.id));
toast.success(t('mail.signatureDeleted'));
setDeleteTarget(null);
- } catch (err: any) {
- toast.error(err?.message || err?.detail || (typeof err === 'string' ? err : 'Delete failed'));
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Delete failed'));
}
}, [deleteTarget, toast, t]);
diff --git a/frontend/src/components/shared/CsvImportDialog.tsx b/frontend/src/components/shared/CsvImportDialog.tsx
index ab6daa9..e190e1e 100644
--- a/frontend/src/components/shared/CsvImportDialog.tsx
+++ b/frontend/src/components/shared/CsvImportDialog.tsx
@@ -1,3 +1,4 @@
+import { asError } from '@/utils/errorTypes';
import React, { useState, useRef, useCallback } from 'react';
import { Modal } from '@/components/ui/Modal';
import { Button } from '@/components/ui/Button';
@@ -74,8 +75,8 @@ export function CsvImportDialog({ open, onClose, onSuccess }: CsvImportDialogPro
setError(null);
onSuccess?.();
onClose();
- } catch (err: any) {
- toast.error(err.message || 'Import fehlgeschlagen.');
+ } catch (err: unknown) { const errObj = asError(err);
+ toast.error(errObj.message || 'Import fehlgeschlagen.');
} finally {
setImporting(false);
}
diff --git a/frontend/src/components/tasks/TaskBoard.tsx b/frontend/src/components/tasks/TaskBoard.tsx
index 8836bb3..62a7495 100644
--- a/frontend/src/components/tasks/TaskBoard.tsx
+++ b/frontend/src/components/tasks/TaskBoard.tsx
@@ -6,7 +6,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next';
import { Badge } from '@/components/ui/Badge';
import { Card } from '@/components/ui/Card';
-import { useTasks, type Task, type TaskStatus } from '@/api/tasks';
+import { useTasks, type Task, type TaskStatus, type TaskFilter } from '@/api/tasks';
import { Clock, AlertCircle, CheckCircle2, Loader2 } from 'lucide-react';
const STATUS_COLUMNS: TaskStatus[] = ['open', 'in_progress', 'review', 'blocked', 'done', 'cancelled'];
@@ -92,14 +92,7 @@ function TaskCard({ task, onSelect }: TaskCardProps) {
}
interface TaskBoardProps {
- filter?: {
- entity_type?: string;
- entity_id?: string;
- assignee_type?: string;
- assignee_id?: string;
- parent_task_id?: string;
- task_type?: string;
- };
+ filter?: TaskFilter;
onSelectTask?: (task: Task) => void;
}
diff --git a/frontend/src/components/tasks/TaskDetail.tsx b/frontend/src/components/tasks/TaskDetail.tsx
index 8eebec1..ca91f76 100644
--- a/frontend/src/components/tasks/TaskDetail.tsx
+++ b/frontend/src/components/tasks/TaskDetail.tsx
@@ -169,13 +169,8 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
value={task.status}
onChange={(e) => handleStatusChange(e.target.value as TaskStatus)}
className="w-48"
- >
- {STATUS_OPTIONS.map((s) => (
-
- ))}
-
+ options={STATUS_OPTIONS.map((s) => ({ value: s, label: statusLabel(t, s) }))}
+ />
{/* Assignee */}
@@ -189,13 +184,8 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
value={assigneeType}
onChange={(e) => setAssigneeType(e.target.value as AssigneeType)}
className="w-32"
- >
- {ASSIGNEE_TYPES.map((at) => (
-
- ))}
-
+ options={ASSIGNEE_TYPES.map((at) => ({ value: at, label: t(`tasks.assigneeType${at.charAt(0).toUpperCase() + at.slice(1)}`) }))}
+ />