fix(audit): P2 frontend any→concrete types (181→61), heroicons→lucide-react, missing type exports, toast API, Select options, TaskStatus types; P2-9 hooks.py type annotations

This commit is contained in:
Agent Zero
2026-08-17 22:24:24 +02:00
parent 40fd633917
commit 45ebbee26f
52 changed files with 404 additions and 302 deletions
+2 -2
View File
@@ -50,8 +50,8 @@ class HookRegistry:
def __new__(cls) -> HookRegistry: def __new__(cls) -> HookRegistry:
if cls._instance is None: if cls._instance is None:
cls._instance = super().__new__(cls) cls._instance = super().__new__(cls)
cls._instance._actions: dict[str, list[tuple[int, Callable]]] = defaultdict(list) cls._instance._actions: dict[str, list[tuple[int, Callable, str]]] = defaultdict(list)
cls._instance._filters: dict[str, list[tuple[int, Callable]]] = defaultdict(list) cls._instance._filters: dict[str, list[tuple[int, Callable, str]]] = defaultdict(list)
return cls._instance return cls._instance
# ─── Registration ─── # ─── Registration ───
+4 -3
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React from 'react'; import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AppRouter } from '@/routes'; import { AppRouter } from '@/routes';
@@ -21,10 +22,10 @@ function QueryClientWrapper({ children }: { children: React.ReactNode }) {
}, },
mutations: { mutations: {
retry: 0, retry: 0,
onError: (error: any) => { onError: (error: unknown) => { const errObj = asError(error);
// Don't show toast for 401 (handled by auth) // Don't show toast for 401 (handled by auth)
if (error?.status === 401) return; if (errObj?.status === 401) return;
const msg = error?.detail || error?.message || 'Ein Fehler ist aufgetreten'; const msg = errObj?.detail || errObj?.message || 'Ein Fehler ist aufgetreten';
toast.error(msg); toast.error(msg);
}, },
}, },
+3 -2
View File
@@ -2,6 +2,7 @@
* Authentication hooks: login, logout, current user, password reset, tenant switching. * Authentication hooks: login, logout, current user, password reset, tenant switching.
*/ */
import { asError } from '@/utils/errorTypes';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apiPost, apiGet, setCsrfToken } from './client'; import { apiPost, apiGet, setCsrfToken } from './client';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
@@ -44,8 +45,8 @@ export function useLogin() {
setCsrfToken(data.csrf_token); setCsrfToken(data.csrf_token);
} }
}, },
onError: (error: any) => { onError: (error: unknown) => { const errObj = asError(error);
setError(error.message || 'Login failed'); setError(errObj.message || 'Login failed');
}, },
}); });
} }
+9 -8
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { apiGet, apiPost, apiPatch, apiDelete } from '@/api/client'; import { apiGet, apiPost, apiPatch, apiDelete } from '@/api/client';
@@ -49,8 +50,8 @@ export function AddressList({ entityType, entityId }: AddressListProps) {
`/addresses?entity_type=${entityType}&entity_id=${entityId}` `/addresses?entity_type=${entityType}&entity_id=${entityId}`
); );
setAddresses(data.items); setAddresses(data.items);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -65,8 +66,8 @@ export function AddressList({ entityType, entityId }: AddressListProps) {
try { try {
await apiDelete(`/addresses/${id}`); await apiDelete(`/addresses/${id}`);
await fetchAddresses(); await fetchAddresses();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
@@ -74,8 +75,8 @@ export function AddressList({ entityType, entityId }: AddressListProps) {
try { try {
await apiPatch(`/addresses/${id}`, { is_default: true }); await apiPatch(`/addresses/${id}`, { is_default: true });
await fetchAddresses(); await fetchAddresses();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
@@ -89,8 +90,8 @@ export function AddressList({ entityType, entityId }: AddressListProps) {
setShowForm(false); setShowForm(false);
setEditingAddress(null); setEditingAddress(null);
await fetchAddresses(); await fetchAddresses();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
+5 -4
View File
@@ -3,6 +3,7 @@
* Integrates into list views (Contacts, Mail, Calendar, DMS). * Integrates into list views (Contacts, Mail, Calendar, DMS).
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -44,8 +45,8 @@ export function SavedFilters({ entityType, currentCriteria, onLoadFilter }: Save
toast.success(t('savedFilters.saved')); toast.success(t('savedFilters.saved'));
setFilterName(''); setFilterName('');
setSaveModalOpen(false); setSaveModalOpen(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -53,8 +54,8 @@ export function SavedFilters({ entityType, currentCriteria, onLoadFilter }: Save
try { try {
await deleteMutation.mutateAsync(id); await deleteMutation.mutateAsync(id);
toast.success(t('savedFilters.deleted')); toast.success(t('savedFilters.deleted'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+8 -8
View File
@@ -1,6 +1,6 @@
import { useState, useRef, useCallback } from 'react'; import { useState, useRef, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PaperAirplaneIcon, StopCircleIcon, CurrencyDollarIcon } from '@heroicons/react/24/outline'; import { Send, CircleStop, DollarSign } from 'lucide-react';
interface AgentStep { interface AgentStep {
step_number: number; step_number: number;
@@ -40,27 +40,27 @@ export function AgentChat({ agentId, agentName }: AgentChatProps) {
const eventSource = new EventSource(url); const eventSource = new EventSource(url);
eventSourceRef.current = eventSource; eventSourceRef.current = eventSource;
eventSource.addEventListener('step', (e) => { eventSource.addEventListener('step', (e: MessageEvent) => {
const step = JSON.parse(e.data) as AgentStep; const step = JSON.parse(e.data) as AgentStep;
setSteps((prev) => [...prev, step]); setSteps((prev) => [...prev, step]);
setTotalCost((prev) => prev + (step.cost_usd || 0)); setTotalCost((prev) => prev + (step.cost_usd || 0));
}); });
eventSource.addEventListener('status', (e) => { eventSource.addEventListener('status', (e: MessageEvent) => {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
if (data.status === 'running') { if (data.status === 'running') {
setMessages((prev) => [...prev, `Step ${data.step}: ${data.action || 'Thinking...'}`]); setMessages((prev) => [...prev, `Step ${data.step}: ${data.action || 'Thinking...'}`]);
} }
}); });
eventSource.addEventListener('done', (e) => { eventSource.addEventListener('done', (e: MessageEvent) => {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
setMessages((prev) => [...prev, `Agent: ${data.final_content}`]); setMessages((prev) => [...prev, `Agent: ${data.final_content}`]);
setIsRunning(false); setIsRunning(false);
eventSource.close(); eventSource.close();
}); });
eventSource.addEventListener('error', (e) => { eventSource.addEventListener('error', (e: MessageEvent) => {
try { try {
const data = JSON.parse(e.data); const data = JSON.parse(e.data);
setMessages((prev) => [...prev, `Error: ${data.error}`]); setMessages((prev) => [...prev, `Error: ${data.error}`]);
@@ -93,7 +93,7 @@ export function AgentChat({ agentId, agentName }: AgentChatProps) {
</label> </label>
{totalCost > 0 && ( {totalCost > 0 && (
<span className="flex items-center gap-1 text-sm text-gray-600 dark:text-gray-400"> <span className="flex items-center gap-1 text-sm text-gray-600 dark:text-gray-400">
<CurrencyDollarIcon className="w-4 h-4" /> <DollarSign className="w-4 h-4" />
{totalCost.toFixed(6)} {totalCost.toFixed(6)}
</span> </span>
)} )}
@@ -133,11 +133,11 @@ export function AgentChat({ agentId, agentName }: AgentChatProps) {
/> />
{isRunning ? ( {isRunning ? (
<button onClick={handleStop} className="p-2 rounded-lg bg-red-500 text-white hover:bg-red-600 min-h-[44px] min-w-[44px]" aria-label={t('agents.stop')}> <button onClick={handleStop} className="p-2 rounded-lg bg-red-500 text-white hover:bg-red-600 min-h-[44px] min-w-[44px]" aria-label={t('agents.stop')}>
<StopCircleIcon className="w-5 h-5" /> <CircleStop className="w-5 h-5" />
</button> </button>
) : ( ) : (
<button onClick={handleSend} disabled={!input.trim()} className="p-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 disabled:opacity-50 min-h-[44px] min-w-[44px]" aria-label={t('agents.send')}> <button onClick={handleSend} disabled={!input.trim()} className="p-2 rounded-lg bg-primary-600 text-white hover:bg-primary-700 disabled:opacity-50 min-h-[44px] min-w-[44px]" aria-label={t('agents.send')}>
<PaperAirplaneIcon className="w-5 h-5" /> <Send className="w-5 h-5" />
</button> </button>
)} )}
</div> </div>
+18 -18
View File
@@ -70,7 +70,7 @@ const commonModels = [
export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) { export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const { toast } = useToast(); const toast = useToast();
const { data: tools = [] } = useAgentToolsFull(); const { data: tools = [] } = useAgentToolsFull();
const { data: skills = [] } = useAgentSkills(); const { data: skills = [] } = useAgentSkills();
const createAgent = useCreateAgent(); const createAgent = useCreateAgent();
@@ -83,17 +83,17 @@ export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) {
name: agent.name, name: agent.name,
description: agent.description || '', description: agent.description || '',
system_prompt: agent.system_prompt || '', system_prompt: agent.system_prompt || '',
llm_model: agent.llm_model, llm_model: agent.llm_model || agent.model || '',
tool_ids: agent.tool_ids || [], tool_ids: agent.tool_ids || [],
skill_ids: agent.skill_ids || [], skill_ids: agent.skill_ids || [],
max_steps: agent.max_steps, max_steps: agent.max_steps ?? 20,
max_duration_seconds: agent.max_duration_seconds, max_duration_seconds: agent.max_duration_seconds ?? 300,
budget_limit_usd: agent.budget_limit_usd, budget_limit_usd: agent.budget_limit_usd ?? agent.budget_limit ?? 1.0,
temperature: agent.temperature, temperature: agent.temperature ?? 0.3,
max_tokens: agent.max_tokens, max_tokens: agent.max_tokens ?? 1000,
trace_mode: agent.trace_mode, trace_mode: (agent.trace_mode === 'extended' ? 'extended' : 'standard') as 'standard' | 'extended',
mode: agent.mode, mode: agent.mode,
is_active: agent.is_active, is_active: agent.is_active ?? agent.active ?? true,
trigger_config: agent.trigger_config || {}, trigger_config: agent.trigger_config || {},
ai_use_case_metadata: agent.ai_use_case_metadata || {}, ai_use_case_metadata: agent.ai_use_case_metadata || {},
}; };
@@ -142,28 +142,28 @@ export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) {
try { try {
if (agent) { if (agent) {
const updated = await updateAgent.mutateAsync({ id: agent.id, data }); const updated = await updateAgent.mutateAsync({ id: agent.id, data });
toast({ title: t('agent.saved'), variant: 'success' }); toast.success(t('agent.saved'));
onSaved?.(updated); onSaved?.(updated);
} else { } else {
const created = await createAgent.mutateAsync(data); const created = await createAgent.mutateAsync(data);
toast({ title: t('agent.created'), variant: 'success' }); toast.success(t('agent.created'));
onSaved?.(created); onSaved?.(created);
} }
} catch { } catch {
toast({ title: t('agent.saveFailed'), variant: 'error' }); toast.error(t('agent.saveFailed'));
} }
}; };
const handleTestRun = async () => { const handleTestRun = async () => {
if (!agent) { if (!agent) {
toast({ title: t('agent.saveBeforeTest'), variant: 'warning' }); toast.warning(t('agent.saveBeforeTest'));
return; return;
} }
try { try {
await testRunAgent.mutateAsync(agent.id); await testRunAgent.mutateAsync(agent.id);
toast({ title: t('agent.testRunOk'), variant: 'success' }); toast.success(t('agent.testRunOk'));
} catch { } catch {
toast({ title: t('agent.testRunFailed'), variant: 'error' }); toast.error(t('agent.testRunFailed'));
} }
}; };
@@ -268,11 +268,11 @@ export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) {
) : ( ) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-2 max-h-40 overflow-y-auto" role="group" aria-label={t('agent.tools')}> <div className="grid grid-cols-1 md:grid-cols-2 gap-2 max-h-40 overflow-y-auto" role="group" aria-label={t('agent.tools')}>
{tools.map((tool) => ( {tools.map((tool) => (
<label key={tool.id} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer text-sm min-h-touch"> <label key={tool.id || tool.name} className="flex items-center gap-2 p-2 rounded-md hover:bg-secondary-50 cursor-pointer text-sm min-h-touch">
<input <input
type="checkbox" type="checkbox"
checked={selectedToolIds.includes(tool.id)} checked={selectedToolIds.includes(tool.id || tool.name)}
onChange={() => toggleArrayValue('tool_ids', tool.id)} onChange={() => toggleArrayValue('tool_ids', tool.id || tool.name)}
className="rounded border-secondary-300 text-primary-600 focus:ring-primary-500" className="rounded border-secondary-300 text-primary-600 focus:ring-primary-500"
/> />
<div> <div>
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query'; 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() { export function AgentMonitor() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -32,28 +32,28 @@ export function AgentMonitor() {
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="p-4 rounded-lg bg-primary-50 dark:bg-primary-900/20"> <div className="p-4 rounded-lg bg-primary-50 dark:bg-primary-900/20">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<ClockIcon className="w-5 h-5 text-primary-600" /> <Clock className="w-5 h-5 text-primary-600" />
<span className="text-sm text-gray-600 dark:text-gray-400">{t('agents.activeRuns')}</span> <span className="text-sm text-gray-600 dark:text-gray-400">{t('agents.activeRuns')}</span>
</div> </div>
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats?.active_runs ?? 0}</p> <p className="text-2xl font-bold text-gray-900 dark:text-white">{stats?.active_runs ?? 0}</p>
</div> </div>
<div className="p-4 rounded-lg bg-green-50 dark:bg-green-900/20"> <div className="p-4 rounded-lg bg-green-50 dark:bg-green-900/20">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<CurrencyDollarIcon className="w-5 h-5 text-green-600" /> <DollarSign className="w-5 h-5 text-green-600" />
<span className="text-sm text-gray-600 dark:text-gray-400">{t('agents.totalBudget')}</span> <span className="text-sm text-gray-600 dark:text-gray-400">{t('agents.totalBudget')}</span>
</div> </div>
<p className="text-2xl font-bold text-gray-900 dark:text-white">${(stats?.total_budget_usd ?? 0).toFixed(4)}</p> <p className="text-2xl font-bold text-gray-900 dark:text-white">${(stats?.total_budget_usd ?? 0).toFixed(4)}</p>
</div> </div>
<div className="p-4 rounded-lg bg-blue-50 dark:bg-blue-900/20"> <div className="p-4 rounded-lg bg-blue-50 dark:bg-blue-900/20">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<ChartBarIcon className="w-5 h-5 text-blue-600" /> <BarChart3 className="w-5 h-5 text-blue-600" />
<span className="text-sm text-gray-600 dark:text-gray-400">{t('agents.runsPerHour')}</span> <span className="text-sm text-gray-600 dark:text-gray-400">{t('agents.runsPerHour')}</span>
</div> </div>
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats?.runs_per_hour ?? 0}</p> <p className="text-2xl font-bold text-gray-900 dark:text-white">{stats?.runs_per_hour ?? 0}</p>
</div> </div>
<div className="p-4 rounded-lg bg-red-50 dark:bg-red-900/20"> <div className="p-4 rounded-lg bg-red-50 dark:bg-red-900/20">
<div className="flex items-center gap-2 mb-2"> <div className="flex items-center gap-2 mb-2">
<ExclamationTriangleIcon className="w-5 h-5 text-red-600" /> <AlertTriangle className="w-5 h-5 text-red-600" />
<span className="text-sm text-gray-600 dark:text-gray-400">{t('agents.errorRate')}</span> <span className="text-sm text-gray-600 dark:text-gray-400">{t('agents.errorRate')}</span>
</div> </div>
<p className="text-2xl font-bold text-gray-900 dark:text-white">{(stats?.error_rate ?? 0).toFixed(1)}%</p> <p className="text-2xl font-bold text-gray-900 dark:text-white">{(stats?.error_rate ?? 0).toFixed(1)}%</p>
@@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'; import { Download } from 'lucide-react';
interface RunStep { interface RunStep {
id: string; id: string;
@@ -52,10 +52,10 @@ export function AgentRunLog({ agentId, runId }: AgentRunLogProps) {
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">{t('agents.runLog')}</h2> <h2 className="text-lg font-semibold text-gray-900 dark:text-white">{t('agents.runLog')}</h2>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<button onClick={() => handleExport('json')} className="p-2 rounded-lg text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800 min-h-[44px] min-w-[44px]" aria-label={t('agents.exportJson')}> <button onClick={() => handleExport('json')} className="p-2 rounded-lg text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800 min-h-[44px] min-w-[44px]" aria-label={t('agents.exportJson')}>
<ArrowDownTrayIcon className="w-5 h-5" /> <Download className="w-5 h-5" />
</button> </button>
<button onClick={() => handleExport('csv')} className="p-2 rounded-lg text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800 min-h-[44px] min-w-[44px]" aria-label={t('agents.exportCsv')}> <button onClick={() => handleExport('csv')} className="p-2 rounded-lg text-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800 min-h-[44px] min-w-[44px]" aria-label={t('agents.exportCsv')}>
<ArrowDownTrayIcon className="w-5 h-5" /> <Download className="w-5 h-5" />
</button> </button>
</div> </div>
</div> </div>
@@ -9,6 +9,7 @@
* - Text-basierte Vorschau der Policy * - Text-basierte Vorschau der Policy
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState, useCallback, useMemo } from 'react'; import React, { useState, useCallback, useMemo } from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import { import {
@@ -439,8 +440,8 @@ function PolicyForm({ initial, entityType, onSave, onCancel }: PolicyFormProps)
await createPolicy.mutateAsync(payload); await createPolicy.mutateAsync(payload);
} }
onSave(); onSave();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err?.message || t('abac.saveError', 'Failed to save policy')); setError(errObj?.message || t('abac.saveError', 'Failed to save policy'));
} }
}, [ }, [
initial, initial,
@@ -7,6 +7,7 @@
* shows a success toast, and closes. * shows a success toast, and closes.
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Modal } from '@/components/ui/Modal'; import { Modal } from '@/components/ui/Modal';
@@ -98,9 +99,9 @@ export function SaveFilterDialog({
); );
setName(''); setName('');
onClose(); onClose();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error( toast.error(
err?.message || t('savedFilters.saveError', 'Filter konnte nicht gespeichert werden') errObj?.message || t('savedFilters.saveError', 'Filter konnte nicht gespeichert werden')
); );
} finally { } finally {
setSubmitting(false); setSubmitting(false);
@@ -10,6 +10,7 @@
* • Click-outside-to-close dropdown behaviour * • Click-outside-to-close dropdown behaviour
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState, useRef, useEffect, useCallback } from 'react'; import React, { useState, useRef, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -86,8 +87,8 @@ export function SavedFilterBar({
const handleDelete = useCallback( const handleDelete = useCallback(
async (e: React.MouseEvent, id: string) => { 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')); e.stopPropagation(); try { await deleteMutation.mutateAsync(id); if (activeFilterId === id) setActiveFilterId(null); toast.success(t('savedFilters.deleted', 'Filter gelöscht'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || t('common.error', 'Fehler')); toast.error(errObj?.message || t('common.error', 'Fehler'));
} }, [deleteMutation, activeFilterId, toast, t] } }, [deleteMutation, activeFilterId, toast, t]
); );
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect, useMemo } from 'react'; import React, { useState, useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -245,8 +246,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
await deleteMutation.mutateAsync({ id: contact.id }); await deleteMutation.mutateAsync({ id: contact.id });
toast.success(t('contacts.deleted')); toast.success(t('contacts.deleted'));
onDeleted(); onDeleted();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -261,8 +262,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
} }
setPersonModalOpen(false); setPersonModalOpen(false);
setEditingPerson(null); setEditingPerson(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -271,8 +272,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
try { try {
await deletePersonMutation.mutateAsync({ contactId: contact.id, personId: person.id }); await deletePersonMutation.mutateAsync({ contactId: contact.id, personId: person.id });
toast.success(t('contacts.personDeleted')); toast.success(t('contacts.personDeleted'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; 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) { if (savedId && customFieldDefs.length > 0 && Object.keys(customValues).length > 0) {
try { try {
await updateCustomFields.mutateAsync({ contactId: savedId, values: customValues }); await updateCustomFields.mutateAsync({ contactId: savedId, values: customValues });
} catch (cfErr: any) { } catch (cfErr: unknown) { const errObj = asError(cfErr);
console.error('Custom fields save failed:', cfErr); console.error('Custom fields save failed:', errObj);
} }
} }
onClose(); onClose();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed'))); toast.error(errObj.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
} }
}; };
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useEffect, useMemo } from 'react'; import React, { useEffect, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; 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) { if (savedId && customFieldDefs.length > 0 && Object.keys(customValues).length > 0) {
try { try {
await updateCustomFields.mutateAsync({ contactId: savedId, values: customValues }); 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 // 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(); onClose();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed'))); toast.error(errObj.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
} }
}; };
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect, useRef, useCallback } from 'react'; import React, { useState, useEffect, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import clsx from 'clsx'; import clsx from 'clsx';
@@ -306,8 +307,8 @@ export function ContactFolderTree({
const name = prompt('Ordnername:'); const name = prompt('Ordnername:');
if (!name) return; if (!name) return;
createFolderMut.mutate({ name }, { createFolderMut.mutate({ name }, {
onError: (err: any) => { onError: (err: unknown) => { const errObj = asError(err);
const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim Anlegen'; 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'); 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 || ''); const newName = prompt('Neuer Name:', folder?.name || '');
if (!newName) return; if (!newName) return;
updateFolderMut.mutate({ id, data: { name: newName } }, { updateFolderMut.mutate({ id, data: { name: newName } }, {
onError: (err: any) => { onError: (err: unknown) => { const errObj = asError(err);
const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim Umbenennen'; 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'); toast.error(typeof msg === 'string' ? msg : 'Fehler beim Umbenennen');
}, },
}); });
@@ -328,8 +329,8 @@ export function ContactFolderTree({
const handleDelete = (id: string) => { const handleDelete = (id: string) => {
if (!confirm('Ordner l\u00f6schen? Kontakte bleiben erhalten, werden aber keinem Ordner mehr zugeordnet.')) return; if (!confirm('Ordner l\u00f6schen? Kontakte bleiben erhalten, werden aber keinem Ordner mehr zugeordnet.')) return;
deleteFolderMut.mutate(id, { deleteFolderMut.mutate(id, {
onError: (err: any) => { onError: (err: unknown) => { const errObj = asError(err);
const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim L\u00f6schen'; 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'); toast.error(typeof msg === 'string' ? msg : 'Fehler beim L\u00f6schen');
}, },
}); });
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Download, Loader2, FileText, CheckCircle } from 'lucide-react'; import { Download, Loader2, FileText, CheckCircle } from 'lucide-react';
@@ -41,9 +42,9 @@ export function ExportPanel() {
toast.success( toast.success(
t('importExport.exportSuccess', 'Export erfolgreich heruntergeladen') t('importExport.exportSuccess', 'Export erfolgreich heruntergeladen')
); );
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error( toast.error(
err?.message || t('importExport.exportFailed', 'Export fehlgeschlagen') errObj?.message || t('importExport.exportFailed', 'Export fehlgeschlagen')
); );
} finally { } finally {
setIsExporting(false); setIsExporting(false);
@@ -3,6 +3,7 @@
* Supports placeholder variables for user/tenant data. * Supports placeholder variables for user/tenant data.
*/ */
import { asError } from '@/utils/errorTypes';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -70,8 +71,8 @@ export function SignatureManager() {
setSignatures(data); setSignatures(data);
setError(null); setError(null);
}) })
.catch((err: any) => { .catch((err: unknown) => { const errObj = asError(err);
setError(err?.message || err?.detail || (typeof err === 'string' ? err : 'Failed to load signatures')); setError(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Failed to load signatures'));
}) })
.finally(() => setLoading(false)); .finally(() => setLoading(false));
}, []); }, []);
@@ -103,8 +104,8 @@ export function SignatureManager() {
toast.success(t('mail.signatureCreated')); toast.success(t('mail.signatureCreated'));
} }
setShowForm(false); setShowForm(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || (typeof err === 'string' ? err : 'Save failed')); toast.error(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Save failed'));
} finally { } finally {
setSaving(false); setSaving(false);
} }
@@ -117,8 +118,8 @@ export function SignatureManager() {
setSignatures((prev) => prev.filter((s) => s.id !== deleteTarget.id)); setSignatures((prev) => prev.filter((s) => s.id !== deleteTarget.id));
toast.success(t('mail.signatureDeleted')); toast.success(t('mail.signatureDeleted'));
setDeleteTarget(null); setDeleteTarget(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || (typeof err === 'string' ? err : 'Delete failed')); toast.error(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Delete failed'));
} }
}, [deleteTarget, toast, t]); }, [deleteTarget, toast, t]);
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useRef, useCallback } from 'react'; import React, { useState, useRef, useCallback } from 'react';
import { Modal } from '@/components/ui/Modal'; import { Modal } from '@/components/ui/Modal';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -74,8 +75,8 @@ export function CsvImportDialog({ open, onClose, onSuccess }: CsvImportDialogPro
setError(null); setError(null);
onSuccess?.(); onSuccess?.();
onClose(); onClose();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Import fehlgeschlagen.'); toast.error(errObj.message || 'Import fehlgeschlagen.');
} finally { } finally {
setImporting(false); setImporting(false);
} }
+2 -9
View File
@@ -6,7 +6,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Badge } from '@/components/ui/Badge'; import { Badge } from '@/components/ui/Badge';
import { Card } from '@/components/ui/Card'; 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'; import { Clock, AlertCircle, CheckCircle2, Loader2 } from 'lucide-react';
const STATUS_COLUMNS: TaskStatus[] = ['open', 'in_progress', 'review', 'blocked', 'done', 'cancelled']; const STATUS_COLUMNS: TaskStatus[] = ['open', 'in_progress', 'review', 'blocked', 'done', 'cancelled'];
@@ -92,14 +92,7 @@ function TaskCard({ task, onSelect }: TaskCardProps) {
} }
interface TaskBoardProps { interface TaskBoardProps {
filter?: { filter?: TaskFilter;
entity_type?: string;
entity_id?: string;
assignee_type?: string;
assignee_id?: string;
parent_task_id?: string;
task_type?: string;
};
onSelectTask?: (task: Task) => void; onSelectTask?: (task: Task) => void;
} }
+4 -14
View File
@@ -169,13 +169,8 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
value={task.status} value={task.status}
onChange={(e) => handleStatusChange(e.target.value as TaskStatus)} onChange={(e) => handleStatusChange(e.target.value as TaskStatus)}
className="w-48" className="w-48"
> options={STATUS_OPTIONS.map((s) => ({ value: s, label: statusLabel(t, s) }))}
{STATUS_OPTIONS.map((s) => ( />
<option key={s} value={s}>
{statusLabel(t, s)}
</option>
))}
</Select>
</div> </div>
{/* Assignee */} {/* Assignee */}
@@ -189,13 +184,8 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
value={assigneeType} value={assigneeType}
onChange={(e) => setAssigneeType(e.target.value as AssigneeType)} onChange={(e) => setAssigneeType(e.target.value as AssigneeType)}
className="w-32" className="w-32"
> options={ASSIGNEE_TYPES.map((at) => ({ value: at, label: t(`tasks.assigneeType${at.charAt(0).toUpperCase() + at.slice(1)}`) }))}
{ASSIGNEE_TYPES.map((at) => ( />
<option key={at} value={at}>
{t(`tasks.assigneeType${at.charAt(0).toUpperCase() + at.slice(1)}`)}
</option>
))}
</Select>
</div> </div>
<div> <div>
<label htmlFor="assignee-id" className="block text-sm font-medium text-gray-700"> <label htmlFor="assignee-id" className="block text-sm font-medium text-gray-700">
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useRef, useEffect, useCallback } from 'react'; import React, { useState, useRef, useEffect, useCallback } from 'react';
import { Sparkles, Send } from 'lucide-react'; import { Sparkles, Send } from 'lucide-react';
import { import {
@@ -96,8 +97,8 @@ export function AiChatPanel({ windowTitle, windowType }: AiChatPanelProps) {
setError(event.content || 'Ein Fehler ist aufgetreten'); setError(event.content || 'Ein Fehler ist aufgetreten');
} }
} }
} catch (e: any) { } catch (e: unknown) { const errObj = asError(e);
setError(e?.message || 'KI Chat nicht verfügbar'); setError(errObj?.message || 'KI Chat nicht verfügbar');
} finally { } finally {
setIsStreaming(false); setIsStreaming(false);
} }
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
// TODO: P2-F21 — Replace hardcoded triggerEventOptions with backend config // TODO: P2-F21 — Replace hardcoded triggerEventOptions with backend config
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -166,17 +167,17 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
toast.success('Workflow erstellt'); toast.success('Workflow erstellt');
} }
onClose(); onClose();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
// Show detailed validation errors from backend (422) // Show detailed validation errors from backend (422)
if (err.validationErrors) { if (errObj.validationErrors) {
const details = Object.entries(err.validationErrors) const details = Object.entries(errObj.validationErrors)
.map(([field, msgs]) => `${field}: ${(msgs as string[]).join(', ')}`) .map(([field, msgs]) => `${field}: ${(msgs as string[]).join(', ')}`)
.join('; '); .join('; ');
toast.error(`Validierungsfehler: ${details}`); toast.error(`Validierungsfehler: ${details}`);
} else if (err.detail) { } else if (errObj.detail) {
toast.error(err.detail); toast.error(errObj.detail);
} else { } else {
toast.error(err.message || 'Fehler beim Speichern'); toast.error(errObj.message || 'Fehler beim Speichern');
} }
} }
}; };
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { import {
useWorkflowInstance, useWorkflowInstance,
@@ -95,8 +96,8 @@ export function WorkflowInstanceDetail({
); );
setComment(''); setComment('');
setShowCommentField(null); setShowCommentField(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler bei der Aktion'); toast.error(errObj.message || 'Fehler bei der Aktion');
} }
}; };
@@ -105,8 +106,8 @@ export function WorkflowInstanceDetail({
try { try {
await cancelMutation.mutateAsync(instance.id); await cancelMutation.mutateAsync(instance.id);
toast.success('Instanz abgebrochen'); toast.success('Instanz abgebrochen');
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Abbrechen'); toast.error(errObj.message || 'Fehler beim Abbrechen');
} }
}; };
+10 -9
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Tabs } from '@/components/shared/Tabs'; import { Tabs } from '@/components/shared/Tabs';
import { import {
@@ -18,7 +19,7 @@ function ProviderTab() {
const [form, setForm] = useState({ name: '', provider_type: 'openai', api_key: '', base_url: '', is_default: false }); const [form, setForm] = useState({ name: '', provider_type: 'openai', api_key: '', base_url: '', is_default: false });
const load = async () => { const load = async () => {
try { setProviders(await fetchProviders()); } catch (e: any) { setError(e?.message); } finally { setLoading(false); } try { setProviders(await fetchProviders()); } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); } finally { setLoading(false); }
}; };
useEffect(() => { load(); }, []); useEffect(() => { load(); }, []);
@@ -27,7 +28,7 @@ function ProviderTab() {
if (editId) { await updateProvider(editId, form); } else { await createProvider(form); } if (editId) { await updateProvider(editId, form); } else { await createProvider(form); }
setShowForm(false); setEditId(null); setForm({ name: '', provider_type: 'openai', api_key: '', base_url: '', is_default: false }); setShowForm(false); setEditId(null); setForm({ name: '', provider_type: 'openai', api_key: '', base_url: '', is_default: false });
await load(); await load();
} catch (e: any) { setError(e?.message); } } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); }
}; };
const handleEdit = (p: AIProvider) => { const handleEdit = (p: AIProvider) => {
@@ -37,7 +38,7 @@ function ProviderTab() {
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
if (!confirm('Anbieter wirklich löschen?')) return; if (!confirm('Anbieter wirklich löschen?')) return;
try { await deleteProvider(id); await load(); } catch (e: any) { setError(e?.message); } try { await deleteProvider(id); await load(); } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); }
}; };
if (loading) return <div className="p-4">Laden...</div>; if (loading) return <div className="p-4">Laden...</div>;
@@ -102,7 +103,7 @@ function PresetTab() {
const [form, setForm] = useState({ name: '', model_id: '', provider_id: '', temperature: 0.7, max_tokens: 2048, top_p: 1.0, system_prompt: '' }); const [form, setForm] = useState({ name: '', model_id: '', provider_id: '', temperature: 0.7, max_tokens: 2048, top_p: 1.0, system_prompt: '' });
const load = async () => { const load = async () => {
try { setPresets(await fetchPresets()); setProviders(await fetchProviders()); } catch (e: any) { setError(e?.message); } finally { setLoading(false); } try { setPresets(await fetchPresets()); setProviders(await fetchProviders()); } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); } finally { setLoading(false); }
}; };
useEffect(() => { load(); }, []); useEffect(() => { load(); }, []);
@@ -112,7 +113,7 @@ function PresetTab() {
if (editId) { await updatePreset(editId, data); } else { await createPreset(data); } if (editId) { await updatePreset(editId, data); } else { await createPreset(data); }
setShowForm(false); setEditId(null); setForm({ name: '', model_id: '', provider_id: '', temperature: 0.7, max_tokens: 2048, top_p: 1.0, system_prompt: '' }); setShowForm(false); setEditId(null); setForm({ name: '', model_id: '', provider_id: '', temperature: 0.7, max_tokens: 2048, top_p: 1.0, system_prompt: '' });
await load(); await load();
} catch (e: any) { setError(e?.message); } } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); }
}; };
const handleEdit = (p: AIPreset) => { const handleEdit = (p: AIPreset) => {
@@ -122,7 +123,7 @@ function PresetTab() {
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
if (!confirm('Preset löschen?')) return; if (!confirm('Preset löschen?')) return;
try { await deletePreset(id); await load(); } catch (e: any) { setError(e?.message); } try { await deletePreset(id); await load(); } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); }
}; };
if (loading) return <div className="p-4">Laden...</div>; if (loading) return <div className="p-4">Laden...</div>;
@@ -184,7 +185,7 @@ function AgentTab() {
const [form, setForm] = useState({ name: '', description: '', system_prompt: '', preset_id: '', tool_ids: [] as string[] }); const [form, setForm] = useState({ name: '', description: '', system_prompt: '', preset_id: '', tool_ids: [] as string[] });
const load = async () => { const load = async () => {
try { setAgents(await fetchAgents()); setPresets(await fetchPresets()); setTools(await fetchTools()); } catch (e: any) { setError(e?.message); } finally { setLoading(false); } try { setAgents(await fetchAgents()); setPresets(await fetchPresets()); setTools(await fetchTools()); } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); } finally { setLoading(false); }
}; };
useEffect(() => { load(); }, []); useEffect(() => { load(); }, []);
@@ -194,7 +195,7 @@ function AgentTab() {
if (editId) { await updateAgent(editId, data); } else { await createAgent(data); } if (editId) { await updateAgent(editId, data); } else { await createAgent(data); }
setShowForm(false); setEditId(null); setForm({ name: '', description: '', system_prompt: '', preset_id: '', tool_ids: [] }); setShowForm(false); setEditId(null); setForm({ name: '', description: '', system_prompt: '', preset_id: '', tool_ids: [] });
await load(); await load();
} catch (e: any) { setError(e?.message); } } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); }
}; };
const handleEdit = (a: AIAgent) => { const handleEdit = (a: AIAgent) => {
@@ -204,7 +205,7 @@ function AgentTab() {
const handleDelete = async (id: string) => { const handleDelete = async (id: string) => {
if (!confirm('Agent löschen?')) return; if (!confirm('Agent löschen?')) return;
try { await deleteAgent(id); await load(); } catch (e: any) { setError(e?.message); } try { await deleteAgent(id); await load(); } catch (e: unknown) { const errObj = asError(e); setError(errObj?.message || null); }
}; };
const toggleTool = (toolName: string) => { const toggleTool = (toolName: string) => {
+13 -12
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
// TODO: P2-F25 — Replace hardcoded commonModels with /ai/providers API // TODO: P2-F25 — Replace hardcoded commonModels with /ai/providers API
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -432,8 +433,8 @@ function VersionHistoryModal({
try { try {
await restoreMutation.mutateAsync({ id: agentId, versionId }); await restoreMutation.mutateAsync({ id: agentId, versionId });
toast.success(t('agent.versionRestored')); toast.success(t('agent.versionRestored'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -510,8 +511,8 @@ export function AgentDashboardPage() {
} }
setShowForm(false); setShowForm(false);
setEditingAgent(undefined); setEditingAgent(undefined);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -521,8 +522,8 @@ export function AgentDashboardPage() {
await deleteMutation.mutateAsync(confirmDelete.id); await deleteMutation.mutateAsync(confirmDelete.id);
toast.success(t('agent.deleted')); toast.success(t('agent.deleted'));
setConfirmDelete(null); setConfirmDelete(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -530,8 +531,8 @@ export function AgentDashboardPage() {
try { try {
await executeMutation.mutateAsync(id); await executeMutation.mutateAsync(id);
toast.success(t('agent.executed')); toast.success(t('agent.executed'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -539,8 +540,8 @@ export function AgentDashboardPage() {
try { try {
await testRunMutation.mutateAsync(id); await testRunMutation.mutateAsync(id);
toast.success(t('agent.testRunSuccess')); toast.success(t('agent.testRunSuccess'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -554,8 +555,8 @@ export function AgentDashboardPage() {
}); });
toast.success(t('agent.messageSent')); toast.success(t('agent.messageSent'));
setChatMessage(''); setChatMessage('');
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+11 -10
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -474,8 +475,8 @@ function VersionHistoryModal({
try { try {
await restoreMutation.mutateAsync({ id: automationId, versionId }); await restoreMutation.mutateAsync({ id: automationId, versionId });
toast.success(t('automation.versionRestored')); toast.success(t('automation.versionRestored'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -548,8 +549,8 @@ export function AutomationDashboardPage() {
} }
setShowForm(false); setShowForm(false);
setEditingAutomation(undefined); setEditingAutomation(undefined);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -559,8 +560,8 @@ export function AutomationDashboardPage() {
await deleteMutation.mutateAsync(confirmDelete.id); await deleteMutation.mutateAsync(confirmDelete.id);
toast.success(t('automation.deleted')); toast.success(t('automation.deleted'));
setConfirmDelete(null); setConfirmDelete(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -568,8 +569,8 @@ export function AutomationDashboardPage() {
try { try {
await executeMutation.mutateAsync(id); await executeMutation.mutateAsync(id);
toast.success(t('automation.executed')); toast.success(t('automation.executed'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -577,8 +578,8 @@ export function AutomationDashboardPage() {
try { try {
await dryRunMutation.mutateAsync(id); await dryRunMutation.mutateAsync(id);
toast.success(t('automation.dryRunSuccess')); toast.success(t('automation.dryRunSuccess'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+7 -6
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -55,8 +56,8 @@ export function AutomationSettingsPage() {
try { try {
await updateMutation.mutateAsync(form); await updateMutation.mutateAsync(form);
toast.success(t('automation.settingsSaved')); toast.success(t('automation.settingsSaved'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -92,8 +93,8 @@ export function AutomationSettingsPage() {
setShowMiniAppForm(false); setShowMiniAppForm(false);
setMiniAppForm({ app_id: '', name: '', icon: 'AppWindow', description: '', render_schema: '{}' }); setMiniAppForm({ app_id: '', name: '', icon: 'AppWindow', description: '', render_schema: '{}' });
refetchMiniapps(); refetchMiniapps();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -102,8 +103,8 @@ export function AutomationSettingsPage() {
await deleteMiniAppMutation.mutateAsync(appId); await deleteMiniAppMutation.mutateAsync(appId);
toast.success(t('automation.miniAppDeleted')); toast.success(t('automation.miniAppDeleted'));
refetchMiniapps(); refetchMiniapps();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+4 -3
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react'; import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import clsx from 'clsx'; import clsx from 'clsx';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
@@ -406,8 +407,8 @@ function ChatWindow({ convId, category, onBack }: ChatWindowProps) {
const msg = await sendMessage(convId, content); const msg = await sendMessage(convId, content);
setMessages(prev => [...prev, msg]); setMessages(prev => [...prev, msg]);
} }
} catch (e: any) { } catch (e: unknown) { const errObj = asError(e);
setError(e?.message || 'Senden fehlgeschlagen'); setError(errObj?.message || 'Senden fehlgeschlagen');
} finally { } finally {
setSending(false); setSending(false);
} }
@@ -428,7 +429,7 @@ function ChatWindow({ convId, category, onBack }: ChatWindowProps) {
is_sidebar: false, is_sidebar: false,
}); });
setAiSessionId(r.data.id); setAiSessionId(r.data.id);
} catch (e: any) { } catch (e: unknown) { const errObj = asError(e);
setError('KI Session konnte nicht gestartet werden'); setError('KI Session konnte nicht gestartet werden');
} }
}; };
+7 -6
View File
@@ -4,6 +4,7 @@
* Manage custom field definitions per entity (contact/company). * Manage custom field definitions per entity (contact/company).
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState, useMemo, useEffect } from 'react'; import React, { useState, useMemo, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
@@ -223,8 +224,8 @@ export function CustomFieldsPage() {
await updateMutation.mutateAsync({ id: editingId!, data: updateData }); await updateMutation.mutateAsync({ id: editingId!, data: updateData });
toast.success(t('customFields.updated', 'Feld aktualisiert')); toast.success(t('customFields.updated', 'Feld aktualisiert'));
closeFormModal(); closeFormModal();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setFormError(err.message || 'Fehler beim Aktualisieren'); setFormError(errObj.message || 'Fehler beim Aktualisieren');
} }
} else { } else {
const createData: CustomFieldDefinitionCreate = { const createData: CustomFieldDefinitionCreate = {
@@ -241,8 +242,8 @@ export function CustomFieldsPage() {
await createMutation.mutateAsync(createData); await createMutation.mutateAsync(createData);
toast.success(t('customFields.created', 'Feld erstellt')); toast.success(t('customFields.created', 'Feld erstellt'));
closeFormModal(); closeFormModal();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setFormError(err.message || 'Fehler beim Erstellen'); setFormError(errObj.message || 'Fehler beim Erstellen');
} }
} }
}; };
@@ -253,8 +254,8 @@ export function CustomFieldsPage() {
await deleteMutation.mutateAsync(deleteTarget.id); await deleteMutation.mutateAsync(deleteTarget.id);
toast.success(t('customFields.deleted', 'Feld gelöscht')); toast.success(t('customFields.deleted', 'Feld gelöscht'));
setDeleteTarget(null); setDeleteTarget(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Löschen'); toast.error(errObj.message || 'Fehler beim Löschen');
setDeleteTarget(null); setDeleteTarget(null);
} }
}; };
+3 -2
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom'; import { useNavigate, Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -38,8 +39,8 @@ export function LoginPage() {
await loginMutation.mutateAsync(data); await loginMutation.mutateAsync(data);
addToast({ type: 'success', message: t('auth.login') + ' erfolgreich' }); addToast({ type: 'success', message: t('auth.login') + ' erfolgreich' });
navigate('/start'); navigate('/start');
} catch (error: any) { } catch (error: unknown) { const errObj = asError(error);
const msg = error.message || t('auth.loginFailed'); const msg = errObj.message || t('auth.loginFailed');
setSubmitError(msg); setSubmitError(msg);
addToast({ type: 'error', message: msg }); addToast({ type: 'error', message: msg });
} }
+3 -2
View File
@@ -4,6 +4,7 @@
* Account selection is integrated into the folder tree (left pane). * Account selection is integrated into the folder tree (left pane).
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@@ -778,8 +779,8 @@ export function MailPage() {
await loadMails(); await loadMails();
toast.success(t('mail.syncSuccess')); toast.success(t('mail.syncSuccess'));
} }
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
const msg = err?.message || err?.detail || (typeof err === 'string' ? err : 'Sync failed'); const msg = errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Sync failed');
toast.error(msg); toast.error(msg);
} finally { } finally {
setIsSyncing(false); setIsSyncing(false);
+15 -14
View File
@@ -2,6 +2,7 @@
* Mail settings page — signatures, rules, labels, PGP, vacation responder. * Mail settings page — signatures, rules, labels, PGP, vacation responder.
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState, useCallback, useEffect } from 'react'; import React, { useState, useCallback, useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
@@ -77,8 +78,8 @@ export function MailSettingsPage() {
if (accs.length > 0) { if (accs.length > 0) {
setSelectedAccountId((prev) => prev || accs[0].id); setSelectedAccountId((prev) => prev || accs[0].id);
} }
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || 'Failed to load accounts'); toast.error(errObj?.message || errObj?.detail || 'Failed to load accounts');
} }
setLoading(false); setLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -93,8 +94,8 @@ export function MailSettingsPage() {
toast.success(t('mail.accountCreated')); toast.success(t('mail.accountCreated'));
setShowAddAccount(false); setShowAddAccount(false);
resetAccount({ email: '', display_name: '', username: '', imap_host: '', imap_port: 993, smtp_host: '', smtp_port: 587, password: '', is_shared: false }); resetAccount({ email: '', display_name: '', username: '', imap_host: '', imap_port: 993, smtp_host: '', smtp_port: 587, password: '', is_shared: false });
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || 'Save failed'); toast.error(errObj?.message || errObj?.detail || 'Save failed');
} }
}, [toast, t, resetAccount]); }, [toast, t, resetAccount]);
@@ -107,8 +108,8 @@ export function MailSettingsPage() {
} else { } else {
toast.error(result.message); toast.error(result.message);
} }
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || 'Connection test failed'); toast.error(errObj?.message || errObj?.detail || 'Connection test failed');
} finally { } finally {
setTesting(null); setTesting(null);
} }
@@ -123,8 +124,8 @@ export function MailSettingsPage() {
} else { } else {
toast.success(t('mail.syncSuccess')); toast.success(t('mail.syncSuccess'));
} }
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || 'Sync failed'); toast.error(errObj?.message || errObj?.detail || 'Sync failed');
} finally { } finally {
setSyncing(null); setSyncing(null);
} }
@@ -136,8 +137,8 @@ export function MailSettingsPage() {
await deleteAccount(accountId); await deleteAccount(accountId);
setAccounts((prev) => prev.filter((a) => a.id !== accountId)); setAccounts((prev) => prev.filter((a) => a.id !== accountId));
toast.success(t('mail.accountDeleted')); toast.success(t('mail.accountDeleted'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || 'Delete failed'); toast.error(errObj?.message || errObj?.detail || 'Delete failed');
} }
}, [toast, t]); }, [toast, t]);
@@ -147,8 +148,8 @@ export function MailSettingsPage() {
setAccounts((prev) => prev.map((a) => (a.id === accountId ? updated : a))); setAccounts((prev) => prev.map((a) => (a.id === accountId ? updated : a)));
setEditingDisplayName(null); setEditingDisplayName(null);
toast.success(t('common.saved')); toast.success(t('common.saved'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || 'Save failed'); toast.error(errObj?.message || errObj?.detail || 'Save failed');
} }
}, [displayNameValue, toast, t]); }, [displayNameValue, toast, t]);
@@ -191,8 +192,8 @@ export function MailSettingsPage() {
setAccounts((prev) => prev.map((a) => (a.id === accountId ? updated : a))); setAccounts((prev) => prev.map((a) => (a.id === accountId ? updated : a)));
setFolderMappingEdit(null); setFolderMappingEdit(null);
toast.success(t('common.saved')); toast.success(t('common.saved'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || err?.detail || 'Save failed'); toast.error(errObj?.message || errObj?.detail || 'Save failed');
} finally { } finally {
setSavingMapping(false); setSavingMapping(false);
} }
+9 -8
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -126,8 +127,8 @@ export function ReportsPage() {
}); });
toast.success(t('reports.templateUpdated', 'Template updated successfully')); toast.success(t('reports.templateUpdated', 'Template updated successfully'));
} }
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || t('reports.saveFailed', 'Failed to save template')); toast.error(errObj?.message || t('reports.saveFailed', 'Failed to save template'));
} }
}; };
@@ -141,8 +142,8 @@ export function ReportsPage() {
setEditorContent(''); setEditorContent('');
} }
toast.success(t('reports.templateDeleted', 'Template deleted')); toast.success(t('reports.templateDeleted', 'Template deleted'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || t('reports.deleteFailed', 'Failed to delete template')); toast.error(errObj?.message || t('reports.deleteFailed', 'Failed to delete template'));
} }
}; };
@@ -174,8 +175,8 @@ export function ReportsPage() {
...prev, ...prev,
]); ]);
toast.success(t('reports.generated', 'Report generated successfully')); toast.success(t('reports.generated', 'Report generated successfully'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || t('reports.generateFailed', 'Failed to generate report')); toast.error(errObj?.message || t('reports.generateFailed', 'Failed to generate report'));
} }
}; };
@@ -207,8 +208,8 @@ export function ReportsPage() {
...prev, ...prev,
]); ]);
toast.success(t('reports.generated', 'Report generated successfully')); toast.success(t('reports.generated', 'Report generated successfully'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err?.message || t('reports.generateFailed', 'Failed to generate report')); toast.error(errObj?.message || t('reports.generateFailed', 'Failed to generate report'));
} }
}; };
+7 -6
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -47,8 +48,8 @@ export function SettingsCurrenciesPage() {
try { try {
const data = await apiGet<{ items: Currency[]; total: number }>('/currencies'); const data = await apiGet<{ items: Currency[]; total: number }>('/currencies');
setCurrencies(data.items); setCurrencies(data.items);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -67,8 +68,8 @@ export function SettingsCurrenciesPage() {
setEditing(null); setEditing(null);
reset({ code: '', name: '', symbol: '', is_default: false }); reset({ code: '', name: '', symbol: '', is_default: false });
await fetchCurrencies(); await fetchCurrencies();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
@@ -77,8 +78,8 @@ export function SettingsCurrenciesPage() {
try { try {
await apiDelete(`/currencies/${id}`); await apiDelete(`/currencies/${id}`);
await fetchCurrencies(); await fetchCurrencies();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
+3 -2
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -111,8 +112,8 @@ export function SettingsFirmendatenPage() {
if (!payload.default_tax_id) payload.default_tax_id = null as any; if (!payload.default_tax_id) payload.default_tax_id = null as any;
await updateMutation.mutateAsync(payload); await updateMutation.mutateAsync(payload);
toast.success(t('systemSettings.saved')); toast.success(t('systemSettings.saved'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('systemSettings.saveFailed')); toast.error(errObj.message || t('systemSettings.saveFailed'));
} }
}; };
+11 -10
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -144,8 +145,8 @@ export function SettingsGroupsPage() {
setNewGroupPermissions({}); setNewGroupPermissions({});
setNewGroupDenied([]); setNewGroupDenied([]);
setCreateOpen(false); setCreateOpen(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -190,8 +191,8 @@ export function SettingsGroupsPage() {
}); });
toast.success(t('settings.groupUpdated', 'Gruppe aktualisiert')); toast.success(t('settings.groupUpdated', 'Gruppe aktualisiert'));
setEditingGroup(null); setEditingGroup(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -201,8 +202,8 @@ export function SettingsGroupsPage() {
await deleteGroupMutation.mutateAsync(confirmDelete.id); await deleteGroupMutation.mutateAsync(confirmDelete.id);
toast.success(t('settings.groupDeleted', 'Gruppe gelöscht')); toast.success(t('settings.groupDeleted', 'Gruppe gelöscht'));
setConfirmDelete(null); setConfirmDelete(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -501,8 +502,8 @@ function EditGroupContent({
await addMemberMutation.mutateAsync({ groupId, userId: memberSearchUserId }); await addMemberMutation.mutateAsync({ groupId, userId: memberSearchUserId });
toast.success(t('settings.memberAdded', 'Mitglied hinzugefügt')); toast.success(t('settings.memberAdded', 'Mitglied hinzugefügt'));
setMemberSearchUserId(''); setMemberSearchUserId('');
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -511,8 +512,8 @@ function EditGroupContent({
try { try {
await removeMemberMutation.mutateAsync({ groupId, userId }); await removeMemberMutation.mutateAsync({ groupId, userId });
toast.success(t('settings.memberRemoved', 'Mitglied entfernt')); toast.success(t('settings.memberRemoved', 'Mitglied entfernt'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+3 -2
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect, useMemo, useCallback } from 'react'; import React, { useState, useEffect, useMemo, useCallback } from 'react';
// TODO: P2-F23 — Replace hardcoded DEFAULT_ORDER with backend config // TODO: P2-F23 — Replace hardcoded DEFAULT_ORDER with backend config
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -162,8 +163,8 @@ export function SettingsMenuOrderPage() {
const order = items.map((item) => item.id); const order = items.map((item) => item.id);
await updateMutation.mutateAsync(order); await updateMutation.mutateAsync(order);
toast.success(t('settings.saved', 'Gespeichert')); toast.success(t('settings.saved', 'Gespeichert'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('settings.saveFailed', 'Speichern fehlgeschlagen')); toast.error(errObj.message || t('settings.saveFailed', 'Speichern fehlgeschlagen'));
} }
}; };
+3 -2
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useToast } from '@/components/ui/Toast'; import { useToast } from '@/components/ui/Toast';
@@ -42,8 +43,8 @@ export function SettingsNotificationsPage() {
onSuccess: () => { onSuccess: () => {
toast.success(t('notifications.saved')); toast.success(t('notifications.saved'));
}, },
onError: (err: any) => { onError: (err: unknown) => { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
}, },
} }
); );
+13 -12
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -79,8 +80,8 @@ function InstallPluginSection() {
if (fileInputRef.current) { if (fileInputRef.current) {
fileInputRef.current.value = ''; fileInputRef.current.value = '';
} }
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -93,8 +94,8 @@ function InstallPluginSection() {
await installUrlMutation.mutateAsync(url.trim()); await installUrlMutation.mutateAsync(url.trim());
toast.success(t('settings.pluginUrlInstalledSuccess')); toast.success(t('settings.pluginUrlInstalledSuccess'));
setUrl(''); setUrl('');
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -188,8 +189,8 @@ export function SettingsPluginsPage() {
try { try {
await installMutation.mutateAsync(plugin.name); await installMutation.mutateAsync(plugin.name);
toast.success(t('settings.pluginInstalledSuccess')); toast.success(t('settings.pluginInstalledSuccess'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -197,8 +198,8 @@ export function SettingsPluginsPage() {
try { try {
await activateMutation.mutateAsync(plugin.name); await activateMutation.mutateAsync(plugin.name);
toast.success(t('settings.pluginActivatedSuccess')); toast.success(t('settings.pluginActivatedSuccess'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -206,8 +207,8 @@ export function SettingsPluginsPage() {
try { try {
await deactivateMutation.mutateAsync(plugin.name); await deactivateMutation.mutateAsync(plugin.name);
toast.success(t('settings.pluginDeactivatedSuccess')); toast.success(t('settings.pluginDeactivatedSuccess'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -218,8 +219,8 @@ export function SettingsPluginsPage() {
toast.success(t('settings.pluginUninstalledSuccess')); toast.success(t('settings.pluginUninstalledSuccess'));
setConfirmUninstall(null); setConfirmUninstall(null);
setConfirmRemoveData(false); setConfirmRemoveData(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+5 -4
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAuthStore } from '@/store/authStore'; import { useAuthStore } from '@/store/authStore';
@@ -33,8 +34,8 @@ export function SettingsProfilePage() {
}); });
toast.success(t('settings.profileSaved')); toast.success(t('settings.profileSaved'));
setProfileDirty(false); setProfileDirty(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -58,8 +59,8 @@ export function SettingsProfilePage() {
setNewPassword(''); setNewPassword('');
setConfirmPassword(''); setConfirmPassword('');
setPasswordDirty(false); setPasswordDirty(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+3 -2
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
// TODO: P2-T19 — Replace hardcoded PermissionLevelBadge/PrincipalTypeBadge with i18n // TODO: P2-T19 — Replace hardcoded PermissionLevelBadge/PrincipalTypeBadge with i18n
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -140,8 +141,8 @@ function FreigabenTab() {
toast.success('Berechtigung gelöscht'); toast.success('Berechtigung gelöscht');
setConfirmDelete(null); setConfirmDelete(null);
refetchPerms(); refetchPerms();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Löschen'); toast.error(errObj.message || 'Fehler beim Löschen');
} finally { } finally {
setDeleting(false); setDeleting(false);
} }
+7 -6
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -143,8 +144,8 @@ export function SettingsRolesPage() {
setNewRolePermissions({}); setNewRolePermissions({});
setNewRoleDenied([]); setNewRoleDenied([]);
setCreateOpen(false); setCreateOpen(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -201,8 +202,8 @@ export function SettingsRolesPage() {
}); });
toast.success(t('settings.roleUpdated')); toast.success(t('settings.roleUpdated'));
setEditingRole(null); setEditingRole(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -212,8 +213,8 @@ export function SettingsRolesPage() {
await deleteRoleMutation.mutateAsync(confirmDelete.id); await deleteRoleMutation.mutateAsync(confirmDelete.id);
toast.success(t('settings.roleDeleted')); toast.success(t('settings.roleDeleted'));
setConfirmDelete(null); setConfirmDelete(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+7 -6
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -46,8 +47,8 @@ export function SettingsSequencesPage() {
try { try {
const data = await apiGet<{ items: Sequence[]; total: number }>('/sequences'); const data = await apiGet<{ items: Sequence[]; total: number }>('/sequences');
setSequences(data.items); setSequences(data.items);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -66,8 +67,8 @@ export function SettingsSequencesPage() {
setEditing(null); setEditing(null);
reset({ name: '', prefix: '', padding: 4 }); reset({ name: '', prefix: '', padding: 4 });
await fetchSequences(); await fetchSequences();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
@@ -76,8 +77,8 @@ export function SettingsSequencesPage() {
try { try {
await apiDelete(`/sequences/${id}`); await apiDelete(`/sequences/${id}`);
await fetchSequences(); await fetchSequences();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
+13 -12
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { SettingsFirmendatenPage } from './SettingsFirmendaten'; import { SettingsFirmendatenPage } from './SettingsFirmendaten';
@@ -87,8 +88,8 @@ function AdressenTab() {
label: a.label || '', label: a.label || '',
})) }))
); );
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Laden der Adressen'); toast.error(errObj.message || 'Fehler beim Laden der Adressen');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -113,8 +114,8 @@ function AdressenTab() {
await apiDelete(`/addresses/${id}`); await apiDelete(`/addresses/${id}`);
setAddresses(addresses.filter(a => a.id !== id)); setAddresses(addresses.filter(a => a.id !== id));
toast.success('Adresse gelöscht'); toast.success('Adresse gelöscht');
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Löschen'); toast.error(errObj.message || 'Fehler beim Löschen');
} }
}; };
@@ -151,8 +152,8 @@ function AdressenTab() {
setShowForm(false); setShowForm(false);
setEditing(null); setEditing(null);
await fetchAddresses(); await fetchAddresses();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Speichern'); toast.error(errObj.message || 'Fehler beim Speichern');
} }
}; };
@@ -284,8 +285,8 @@ function KontenTab() {
defaultTax: a.default_tax || '', defaultTax: a.default_tax || '',
})) }))
); );
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Laden der Konten'); toast.error(errObj.message || 'Fehler beim Laden der Konten');
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -310,8 +311,8 @@ function KontenTab() {
await apiDelete(`/bank-accounts/${id}`); await apiDelete(`/bank-accounts/${id}`);
setAccounts(accounts.filter(a => a.id !== id)); setAccounts(accounts.filter(a => a.id !== id));
toast.success('Konto gelöscht'); toast.success('Konto gelöscht');
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Löschen'); toast.error(errObj.message || 'Fehler beim Löschen');
} }
}; };
@@ -337,8 +338,8 @@ function KontenTab() {
setShowForm(false); setShowForm(false);
setEditing(null); setEditing(null);
await fetchAccounts(); await fetchAccounts();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Speichern'); toast.error(errObj.message || 'Fehler beim Speichern');
} }
}; };
+7 -6
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
@@ -47,8 +48,8 @@ export function SettingsTaxesPage() {
try { try {
const data = await apiGet<{ items: TaxRate[]; total: number }>('/taxes'); const data = await apiGet<{ items: TaxRate[]; total: number }>('/taxes');
setTaxes(data.items); setTaxes(data.items);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -68,8 +69,8 @@ export function SettingsTaxesPage() {
setEditing(null); setEditing(null);
reset({ name: '', rate: 0, is_default: false, country: '' }); reset({ name: '', rate: 0, is_default: false, country: '' });
await fetchTaxes(); await fetchTaxes();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
@@ -78,8 +79,8 @@ export function SettingsTaxesPage() {
try { try {
await apiDelete(`/taxes/${id}`); await apiDelete(`/taxes/${id}`);
await fetchTaxes(); await fetchTaxes();
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
setError(err.message || t('common.error')); setError(errObj.message || t('common.error'));
} }
}; };
+9 -8
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
// TODO: P2-F22 — Replace hardcoded LEGACY_ROLES with /roles API // TODO: P2-F22 — Replace hardcoded LEGACY_ROLES with /roles API
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -107,8 +108,8 @@ export function SettingsUsersPage() {
toast.success(t('settings.userInvited')); toast.success(t('settings.userInvited'));
reset({ name: '', email: '', password: '', role_id: '' }); reset({ name: '', email: '', password: '', role_id: '' });
setInviteOpen(false); setInviteOpen(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -124,8 +125,8 @@ export function SettingsUsersPage() {
} }
await updateUserMutation.mutateAsync({ id: userId, data }); await updateUserMutation.mutateAsync({ id: userId, data });
toast.success(t('settings.assignRole') + ' — OK'); toast.success(t('settings.assignRole') + ' — OK');
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -140,8 +141,8 @@ export function SettingsUsersPage() {
toast.success(t('settings.userDeactivated')); toast.success(t('settings.userDeactivated'));
} }
setConfirmDeactivate(null); setConfirmDeactivate(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -151,8 +152,8 @@ export function SettingsUsersPage() {
await deleteUserMutation.mutateAsync(confirmDelete.id); await deleteUserMutation.mutateAsync(confirmDelete.id);
toast.success(t('settings.userDeleted')); toast.success(t('settings.userDeleted'));
setConfirmDelete(null); setConfirmDelete(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
+7 -6
View File
@@ -8,6 +8,7 @@
* - Test button sends test payload, shows result * - Test button sends test payload, shows result
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
@@ -377,8 +378,8 @@ export function SettingsWebhooksPage() {
setFormModalOpen(false); setFormModalOpen(false);
setEditingWebhook(null); setEditingWebhook(null);
}, },
onError: (err: any) => { onError: (err: unknown) => { const errObj = asError(err);
setFormError(err?.message || t('webhooks.updateError', 'Fehler beim Aktualisieren')); setFormError(errObj?.message || t('webhooks.updateError', 'Fehler beim Aktualisieren'));
}, },
} }
); );
@@ -387,8 +388,8 @@ export function SettingsWebhooksPage() {
onSuccess: () => { onSuccess: () => {
setFormModalOpen(false); setFormModalOpen(false);
}, },
onError: (err: any) => { onError: (err: unknown) => { const errObj = asError(err);
setFormError(err?.message || t('webhooks.createError', 'Fehler beim Erstellen')); setFormError(errObj?.message || t('webhooks.createError', 'Fehler beim Erstellen'));
}, },
}); });
} }
@@ -418,8 +419,8 @@ export function SettingsWebhooksPage() {
setTestResult(result); setTestResult(result);
setTestResultModalOpen(true); setTestResultModalOpen(true);
}, },
onError: (err: any) => { onError: (err: unknown) => { const errObj = asError(err);
setTestResult({ success: false, status_code: null, error: err?.message || 'Unknown error' }); setTestResult({ success: false, status_code: null, error: errObj?.message || 'Unknown error' });
setTestResultModalOpen(true); setTestResultModalOpen(true);
}, },
}); });
+12 -10
View File
@@ -2,6 +2,7 @@
* Tasks page — list with filter, create modal, detail. * Tasks page — list with filter, create modal, detail.
*/ */
import { asError } from '@/utils/errorTypes';
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
@@ -20,6 +21,7 @@ import {
useUpdateTaskStatus, useUpdateTaskStatus,
type Task, type Task,
type TaskFilter, type TaskFilter,
type TaskStatus,
} from '@/api/tasks'; } from '@/api/tasks';
const STATUS_COLORS: Record<string, 'secondary' | 'info' | 'success'> = { const STATUS_COLORS: Record<string, 'secondary' | 'info' | 'success'> = {
@@ -96,8 +98,8 @@ export function TasksPage() {
}); });
toast.success(t('tasks.created')); toast.success(t('tasks.created'));
setCreateModalOpen(false); setCreateModalOpen(false);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -107,8 +109,8 @@ export function TasksPage() {
await updateMutation.mutateAsync({ id: editingTask.id, data: formData }); await updateMutation.mutateAsync({ id: editingTask.id, data: formData });
toast.success(t('tasks.updated')); toast.success(t('tasks.updated'));
setEditingTask(null); setEditingTask(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -118,17 +120,17 @@ export function TasksPage() {
await deleteMutation.mutateAsync(task.id); await deleteMutation.mutateAsync(task.id);
toast.success(t('tasks.deleted')); toast.success(t('tasks.deleted'));
setSelectedTask(null); setSelectedTask(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
const handleStatusChange = async (task: Task, newStatus: string) => { const handleStatusChange = async (task: Task, newStatus: TaskStatus) => {
try { try {
await statusMutation.mutateAsync({ id: task.id, status: newStatus }); await statusMutation.mutateAsync({ id: task.id, status: newStatus });
toast.success(t('tasks.statusUpdated')); toast.success(t('tasks.statusUpdated'));
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || t('common.error')); toast.error(errObj.message || t('common.error'));
} }
}; };
@@ -163,7 +165,7 @@ export function TasksPage() {
{ value: 'done', label: t('tasks.statusDone') }, { value: 'done', label: t('tasks.statusDone') },
]} ]}
value={filter.status || ''} value={filter.status || ''}
onChange={(e) => { setFilter(f => ({ ...f, status: e.target.value || undefined })); setPage(1); }} onChange={(e) => { setFilter(f => ({ ...f, status: (e.target.value || undefined) as TaskStatus | undefined })); setPage(1); }}
className="w-40" className="w-40"
/> />
<Select <Select
+5 -4
View File
@@ -1,3 +1,4 @@
import { asError } from '@/utils/errorTypes';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@@ -56,8 +57,8 @@ export function WorkflowsPage() {
toast.success( toast.success(
workflow.is_active ? 'Workflow deaktiviert' : 'Workflow aktiviert' workflow.is_active ? 'Workflow deaktiviert' : 'Workflow aktiviert'
); );
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Umschalten'); toast.error(errObj.message || 'Fehler beim Umschalten');
} }
}; };
@@ -67,8 +68,8 @@ export function WorkflowsPage() {
await deleteMutation.mutateAsync(confirmDelete.id); await deleteMutation.mutateAsync(confirmDelete.id);
toast.success('Workflow geloescht'); toast.success('Workflow geloescht');
setConfirmDelete(null); setConfirmDelete(null);
} catch (err: any) { } catch (err: unknown) { const errObj = asError(err);
toast.error(err.message || 'Fehler beim Loeschen'); toast.error(errObj.message || 'Fehler beim Loeschen');
} }
}; };
+38
View File
@@ -86,6 +86,44 @@ export interface AgentTool {
description: string; description: string;
} }
export interface AgentToolInfo extends AgentTool {
id?: string;
category?: string;
required_permissions?: string[];
}
export interface AgentSkillInfo {
name: string;
description: string;
tools?: string[];
required_permissions?: string[];
}
export interface AgentRunFull extends AgentRun {
agent_name?: string;
cost?: number;
steps?: number;
error_category?: string;
}
export interface AgentDefinitionFull extends AgentDefinition {
llm_model?: string;
tool_ids?: string[];
skill_ids?: string[];
max_steps?: number;
max_duration_seconds?: number;
budget_limit_usd?: number;
temperature?: number;
max_tokens?: number;
trace_mode?: 'standard' | 'extended' | 'off';
is_active?: boolean;
trigger_config?: Record<string, unknown>;
ai_use_case_metadata?: Record<string, unknown>;
tools_list?: AgentToolInfo[];
skills_list?: AgentSkillInfo[];
version?: number;
}
export interface AutomationSettings { export interface AutomationSettings {
default_llm_model: string; default_llm_model: string;
heartbeat_default_interval: number; heartbeat_default_interval: number;
+45 -8
View File
@@ -9,12 +9,49 @@ export interface CategorizedError {
field?: string; field?: string;
} }
export function categorizeError(error: any): CategorizedError { export interface ErrorLike {
const status = error?.status || 0; status?: number;
if (status === 0) return { category: 'network', status: 0, message: 'Netzwerkfehler', detail: error?.message }; message?: string;
if (status === 401) return { category: 'auth', status, message: 'Nicht authentifiziert', detail: error?.detail }; detail?: string;
if (status === 403) return { category: 'permission', status, message: 'Keine Berechtigung', detail: error?.detail }; validationErrors?: Record<string, string[]>;
if (status === 422) return { category: 'validation', status, message: 'Validierungsfehler', detail: error?.detail, field: error?.field }; code?: string;
if (status >= 500) return { category: 'server', status, message: 'Serverfehler', detail: error?.detail }; field?: string;
return { category: 'unknown', status, message: error?.message || 'Unbekannter Fehler', detail: error?.detail }; response?: { data?: { detail?: { detail?: unknown } } };
}
/**
* Narrow an unknown error value to a typed error-like object.
* Handles strings, Error instances, and API error payloads.
*/
export function asError(error: unknown): ErrorLike {
if (typeof error === 'string') {
return { message: error };
}
if (error instanceof Error) {
return { message: error.message };
}
if (error && typeof error === 'object') {
const e = error as Record<string, unknown>;
return {
status: typeof e.status === 'number' ? e.status : undefined,
message: typeof e.message === 'string' ? e.message : undefined,
detail: typeof e.detail === 'string' ? e.detail : undefined,
validationErrors: e.validationErrors as Record<string, string[]> | undefined,
code: typeof e.code === 'string' ? e.code : undefined,
field: typeof e.field === 'string' ? e.field : undefined,
response: e.response as { data?: { detail?: { detail?: unknown } } } | undefined,
};
}
return {};
}
export function categorizeError(error: unknown): CategorizedError {
const e = asError(error);
const status = e.status || 0;
if (status === 0) return { category: 'network', status: 0, message: 'Netzwerkfehler', detail: e.message };
if (status === 401) return { category: 'auth', status, message: 'Nicht authentifiziert', detail: e.detail };
if (status === 403) return { category: 'permission', status, message: 'Keine Berechtigung', detail: e.detail };
if (status === 422) return { category: 'validation', status, message: 'Validierungsfehler', detail: e.detail, field: e.field };
if (status >= 500) return { category: 'server', status, message: 'Serverfehler', detail: e.detail };
return { category: 'unknown', status, message: e.message || 'Unbekannter Fehler', detail: e.detail };
} }