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:
@@ -1,3 +1,4 @@
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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}`
|
||||
);
|
||||
setAddresses(data.items);
|
||||
} catch (err: any) {
|
||||
setError(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
setError(errObj.message || t('common.error'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -65,8 +66,8 @@ export function AddressList({ entityType, entityId }: AddressListProps) {
|
||||
try {
|
||||
await apiDelete(`/addresses/${id}`);
|
||||
await fetchAddresses();
|
||||
} catch (err: any) {
|
||||
setError(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
setError(errObj.message || t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,8 +75,8 @@ export function AddressList({ entityType, entityId }: AddressListProps) {
|
||||
try {
|
||||
await apiPatch(`/addresses/${id}`, { is_default: true });
|
||||
await fetchAddresses();
|
||||
} catch (err: any) {
|
||||
setError(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
setError(errObj.message || t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -89,8 +90,8 @@ export function AddressList({ entityType, entityId }: AddressListProps) {
|
||||
setShowForm(false);
|
||||
setEditingAddress(null);
|
||||
await fetchAddresses();
|
||||
} catch (err: any) {
|
||||
setError(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
setError(errObj.message || t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Integrates into list views (Contacts, Mail, Calendar, DMS).
|
||||
*/
|
||||
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
@@ -44,8 +45,8 @@ export function SavedFilters({ entityType, currentCriteria, onLoadFilter }: Save
|
||||
toast.success(t('savedFilters.saved'));
|
||||
setFilterName('');
|
||||
setSaveModalOpen(false);
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -53,8 +54,8 @@ export function SavedFilters({ entityType, currentCriteria, onLoadFilter }: Save
|
||||
try {
|
||||
await deleteMutation.mutateAsync(id);
|
||||
toast.success(t('savedFilters.deleted'));
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PaperAirplaneIcon, StopCircleIcon, CurrencyDollarIcon } from '@heroicons/react/24/outline';
|
||||
import { Send, CircleStop, DollarSign } from 'lucide-react';
|
||||
|
||||
interface AgentStep {
|
||||
step_number: number;
|
||||
@@ -40,27 +40,27 @@ export function AgentChat({ agentId, agentName }: AgentChatProps) {
|
||||
const eventSource = new EventSource(url);
|
||||
eventSourceRef.current = eventSource;
|
||||
|
||||
eventSource.addEventListener('step', (e) => {
|
||||
eventSource.addEventListener('step', (e: MessageEvent) => {
|
||||
const step = JSON.parse(e.data) as AgentStep;
|
||||
setSteps((prev) => [...prev, step]);
|
||||
setTotalCost((prev) => prev + (step.cost_usd || 0));
|
||||
});
|
||||
|
||||
eventSource.addEventListener('status', (e) => {
|
||||
eventSource.addEventListener('status', (e: MessageEvent) => {
|
||||
const data = JSON.parse(e.data);
|
||||
if (data.status === 'running') {
|
||||
setMessages((prev) => [...prev, `Step ${data.step}: ${data.action || 'Thinking...'}`]);
|
||||
}
|
||||
});
|
||||
|
||||
eventSource.addEventListener('done', (e) => {
|
||||
eventSource.addEventListener('done', (e: MessageEvent) => {
|
||||
const data = JSON.parse(e.data);
|
||||
setMessages((prev) => [...prev, `Agent: ${data.final_content}`]);
|
||||
setIsRunning(false);
|
||||
eventSource.close();
|
||||
});
|
||||
|
||||
eventSource.addEventListener('error', (e) => {
|
||||
eventSource.addEventListener('error', (e: MessageEvent) => {
|
||||
try {
|
||||
const data = JSON.parse(e.data);
|
||||
setMessages((prev) => [...prev, `Error: ${data.error}`]);
|
||||
@@ -93,7 +93,7 @@ export function AgentChat({ agentId, agentName }: AgentChatProps) {
|
||||
</label>
|
||||
{totalCost > 0 && (
|
||||
<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)}
|
||||
</span>
|
||||
)}
|
||||
@@ -133,11 +133,11 @@ export function AgentChat({ agentId, agentName }: AgentChatProps) {
|
||||
/>
|
||||
{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')}>
|
||||
<StopCircleIcon className="w-5 h-5" />
|
||||
<CircleStop className="w-5 h-5" />
|
||||
</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')}>
|
||||
<PaperAirplaneIcon className="w-5 h-5" />
|
||||
<Send className="w-5 h-5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -70,7 +70,7 @@ const commonModels = [
|
||||
|
||||
export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) {
|
||||
const { t } = useTranslation();
|
||||
const { toast } = useToast();
|
||||
const toast = useToast();
|
||||
const { data: tools = [] } = useAgentToolsFull();
|
||||
const { data: skills = [] } = useAgentSkills();
|
||||
const createAgent = useCreateAgent();
|
||||
@@ -83,17 +83,17 @@ export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) {
|
||||
name: agent.name,
|
||||
description: agent.description || '',
|
||||
system_prompt: agent.system_prompt || '',
|
||||
llm_model: agent.llm_model,
|
||||
llm_model: agent.llm_model || agent.model || '',
|
||||
tool_ids: agent.tool_ids || [],
|
||||
skill_ids: agent.skill_ids || [],
|
||||
max_steps: agent.max_steps,
|
||||
max_duration_seconds: agent.max_duration_seconds,
|
||||
budget_limit_usd: agent.budget_limit_usd,
|
||||
temperature: agent.temperature,
|
||||
max_tokens: agent.max_tokens,
|
||||
trace_mode: agent.trace_mode,
|
||||
max_steps: agent.max_steps ?? 20,
|
||||
max_duration_seconds: agent.max_duration_seconds ?? 300,
|
||||
budget_limit_usd: agent.budget_limit_usd ?? agent.budget_limit ?? 1.0,
|
||||
temperature: agent.temperature ?? 0.3,
|
||||
max_tokens: agent.max_tokens ?? 1000,
|
||||
trace_mode: (agent.trace_mode === 'extended' ? 'extended' : 'standard') as 'standard' | 'extended',
|
||||
mode: agent.mode,
|
||||
is_active: agent.is_active,
|
||||
is_active: agent.is_active ?? agent.active ?? true,
|
||||
trigger_config: agent.trigger_config || {},
|
||||
ai_use_case_metadata: agent.ai_use_case_metadata || {},
|
||||
};
|
||||
@@ -142,28 +142,28 @@ export function AgentEditor({ agent, onSaved, onCancel }: AgentEditorProps) {
|
||||
try {
|
||||
if (agent) {
|
||||
const updated = await updateAgent.mutateAsync({ id: agent.id, data });
|
||||
toast({ title: t('agent.saved'), variant: 'success' });
|
||||
toast.success(t('agent.saved'));
|
||||
onSaved?.(updated);
|
||||
} else {
|
||||
const created = await createAgent.mutateAsync(data);
|
||||
toast({ title: t('agent.created'), variant: 'success' });
|
||||
toast.success(t('agent.created'));
|
||||
onSaved?.(created);
|
||||
}
|
||||
} catch {
|
||||
toast({ title: t('agent.saveFailed'), variant: 'error' });
|
||||
toast.error(t('agent.saveFailed'));
|
||||
}
|
||||
};
|
||||
|
||||
const handleTestRun = async () => {
|
||||
if (!agent) {
|
||||
toast({ title: t('agent.saveBeforeTest'), variant: 'warning' });
|
||||
toast.warning(t('agent.saveBeforeTest'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await testRunAgent.mutateAsync(agent.id);
|
||||
toast({ title: t('agent.testRunOk'), variant: 'success' });
|
||||
toast.success(t('agent.testRunOk'));
|
||||
} 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')}>
|
||||
{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
|
||||
type="checkbox"
|
||||
checked={selectedToolIds.includes(tool.id)}
|
||||
onChange={() => toggleArrayValue('tool_ids', tool.id)}
|
||||
checked={selectedToolIds.includes(tool.id || tool.name)}
|
||||
onChange={() => toggleArrayValue('tool_ids', tool.id || tool.name)}
|
||||
className="rounded border-secondary-300 text-primary-600 focus:ring-primary-500"
|
||||
/>
|
||||
<div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ChartBarIcon, ExclamationTriangleIcon, CurrencyDollarIcon, ClockIcon } from '@heroicons/react/24/outline';
|
||||
import { BarChart3, AlertTriangle, DollarSign, Clock } from 'lucide-react';
|
||||
|
||||
export function AgentMonitor() {
|
||||
const { t } = useTranslation();
|
||||
@@ -32,28 +32,28 @@ export function AgentMonitor() {
|
||||
<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="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>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats?.active_runs ?? 0}</p>
|
||||
</div>
|
||||
<div className="p-4 rounded-lg bg-green-50 dark:bg-green-900/20">
|
||||
<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>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">${(stats?.total_budget_usd ?? 0).toFixed(4)}</p>
|
||||
</div>
|
||||
<div className="p-4 rounded-lg bg-blue-50 dark:bg-blue-900/20">
|
||||
<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>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-gray-900 dark:text-white">{stats?.runs_per_hour ?? 0}</p>
|
||||
</div>
|
||||
<div className="p-4 rounded-lg bg-red-50 dark:bg-red-900/20">
|
||||
<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>
|
||||
</div>
|
||||
<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 { useTranslation } from 'react-i18next';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ArrowDownTrayIcon } from '@heroicons/react/24/outline';
|
||||
import { Download } from 'lucide-react';
|
||||
|
||||
interface RunStep {
|
||||
id: string;
|
||||
@@ -52,10 +52,10 @@ export function AgentRunLog({ agentId, runId }: AgentRunLogProps) {
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">{t('agents.runLog')}</h2>
|
||||
<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')}>
|
||||
<ArrowDownTrayIcon className="w-5 h-5" />
|
||||
<Download className="w-5 h-5" />
|
||||
</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')}>
|
||||
<ArrowDownTrayIcon className="w-5 h-5" />
|
||||
<Download className="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
* - Text-basierte Vorschau der Policy
|
||||
*/
|
||||
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
@@ -439,8 +440,8 @@ function PolicyForm({ initial, entityType, onSave, onCancel }: PolicyFormProps)
|
||||
await createPolicy.mutateAsync(payload);
|
||||
}
|
||||
onSave();
|
||||
} catch (err: any) {
|
||||
setError(err?.message || t('abac.saveError', 'Failed to save policy'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
setError(errObj?.message || t('abac.saveError', 'Failed to save policy'));
|
||||
}
|
||||
}, [
|
||||
initial,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* shows a success toast, and closes.
|
||||
*/
|
||||
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Modal } from '@/components/ui/Modal';
|
||||
@@ -98,9 +99,9 @@ export function SaveFilterDialog({
|
||||
);
|
||||
setName('');
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(
|
||||
err?.message || t('savedFilters.saveError', 'Filter konnte nicht gespeichert werden')
|
||||
errObj?.message || t('savedFilters.saveError', 'Filter konnte nicht gespeichert werden')
|
||||
);
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* • Click-outside-to-close dropdown behaviour
|
||||
*/
|
||||
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
@@ -86,8 +87,8 @@ export function SavedFilterBar({
|
||||
const handleDelete = useCallback(
|
||||
async (e: React.MouseEvent, id: string) => {
|
||||
e.stopPropagation(); try { await deleteMutation.mutateAsync(id); if (activeFilterId === id) setActiveFilterId(null); toast.success(t('savedFilters.deleted', 'Filter gelöscht'));
|
||||
} catch (err: any) {
|
||||
toast.error(err?.message || t('common.error', 'Fehler'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj?.message || t('common.error', 'Fehler'));
|
||||
} }, [deleteMutation, activeFilterId, toast, t]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
@@ -245,8 +246,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
|
||||
await deleteMutation.mutateAsync({ id: contact.id });
|
||||
toast.success(t('contacts.deleted'));
|
||||
onDeleted();
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -261,8 +262,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
|
||||
}
|
||||
setPersonModalOpen(false);
|
||||
setEditingPerson(null);
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -271,8 +272,8 @@ export function ContactDetail({ contact, loading, onEdit, onDeleted, dataTestId
|
||||
try {
|
||||
await deletePersonMutation.mutateAsync({ contactId: contact.id, personId: person.id });
|
||||
toast.success(t('contacts.personDeleted'));
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || t('common.error'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || t('common.error'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -205,13 +206,13 @@ export function ContactEditForm({ onClose, contact, onSaved }: ContactEditFormPr
|
||||
if (savedId && customFieldDefs.length > 0 && Object.keys(customValues).length > 0) {
|
||||
try {
|
||||
await updateCustomFields.mutateAsync({ contactId: savedId, values: customValues });
|
||||
} catch (cfErr: any) {
|
||||
console.error('Custom fields save failed:', cfErr);
|
||||
} catch (cfErr: unknown) { const errObj = asError(cfErr);
|
||||
console.error('Custom fields save failed:', errObj);
|
||||
}
|
||||
}
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useForm } from 'react-hook-form';
|
||||
@@ -209,14 +210,14 @@ export function ContactEditModal({ open, onClose, contact, onSaved }: ContactEdi
|
||||
if (savedId && customFieldDefs.length > 0 && Object.keys(customValues).length > 0) {
|
||||
try {
|
||||
await updateCustomFields.mutateAsync({ contactId: savedId, values: customValues });
|
||||
} catch (cfErr: any) {
|
||||
} catch (cfErr: unknown) { const errObj = asError(cfErr);
|
||||
// Don't fail the whole save if custom fields fail
|
||||
console.error('Custom fields save failed:', cfErr);
|
||||
console.error('Custom fields save failed:', errObj);
|
||||
}
|
||||
}
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || (isEdit ? t('contacts.updateFailed') : t('contacts.createFailed')));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import clsx from 'clsx';
|
||||
@@ -306,8 +307,8 @@ export function ContactFolderTree({
|
||||
const name = prompt('Ordnername:');
|
||||
if (!name) return;
|
||||
createFolderMut.mutate({ name }, {
|
||||
onError: (err: any) => {
|
||||
const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim Anlegen';
|
||||
onError: (err: unknown) => { const errObj = asError(err);
|
||||
const msg = errObj?.response?.data?.detail?.detail || errObj?.response?.data?.detail || errObj?.message || 'Fehler beim Anlegen';
|
||||
toast.error(typeof msg === 'string' ? msg : 'Fehler beim Anlegen des Ordners');
|
||||
},
|
||||
});
|
||||
@@ -318,8 +319,8 @@ export function ContactFolderTree({
|
||||
const newName = prompt('Neuer Name:', folder?.name || '');
|
||||
if (!newName) return;
|
||||
updateFolderMut.mutate({ id, data: { name: newName } }, {
|
||||
onError: (err: any) => {
|
||||
const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim Umbenennen';
|
||||
onError: (err: unknown) => { const errObj = asError(err);
|
||||
const msg = errObj?.response?.data?.detail?.detail || errObj?.response?.data?.detail || errObj?.message || 'Fehler beim Umbenennen';
|
||||
toast.error(typeof msg === 'string' ? msg : 'Fehler beim Umbenennen');
|
||||
},
|
||||
});
|
||||
@@ -328,8 +329,8 @@ export function ContactFolderTree({
|
||||
const handleDelete = (id: string) => {
|
||||
if (!confirm('Ordner l\u00f6schen? Kontakte bleiben erhalten, werden aber keinem Ordner mehr zugeordnet.')) return;
|
||||
deleteFolderMut.mutate(id, {
|
||||
onError: (err: any) => {
|
||||
const msg = err?.response?.data?.detail?.detail || err?.response?.data?.detail || err?.message || 'Fehler beim L\u00f6schen';
|
||||
onError: (err: unknown) => { const errObj = asError(err);
|
||||
const msg = errObj?.response?.data?.detail?.detail || errObj?.response?.data?.detail || errObj?.message || 'Fehler beim L\u00f6schen';
|
||||
toast.error(typeof msg === 'string' ? msg : 'Fehler beim L\u00f6schen');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Download, Loader2, FileText, CheckCircle } from 'lucide-react';
|
||||
@@ -41,9 +42,9 @@ export function ExportPanel() {
|
||||
toast.success(
|
||||
t('importExport.exportSuccess', 'Export erfolgreich heruntergeladen')
|
||||
);
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(
|
||||
err?.message || t('importExport.exportFailed', 'Export fehlgeschlagen')
|
||||
errObj?.message || t('importExport.exportFailed', 'Export fehlgeschlagen')
|
||||
);
|
||||
} finally {
|
||||
setIsExporting(false);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* Supports placeholder variables for user/tenant data.
|
||||
*/
|
||||
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import DOMPurify from 'dompurify';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -70,8 +71,8 @@ export function SignatureManager() {
|
||||
setSignatures(data);
|
||||
setError(null);
|
||||
})
|
||||
.catch((err: any) => {
|
||||
setError(err?.message || err?.detail || (typeof err === 'string' ? err : 'Failed to load signatures'));
|
||||
.catch((err: unknown) => { const errObj = asError(err);
|
||||
setError(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Failed to load signatures'));
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
}, []);
|
||||
@@ -103,8 +104,8 @@ export function SignatureManager() {
|
||||
toast.success(t('mail.signatureCreated'));
|
||||
}
|
||||
setShowForm(false);
|
||||
} catch (err: any) {
|
||||
toast.error(err?.message || err?.detail || (typeof err === 'string' ? err : 'Save failed'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Save failed'));
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
@@ -117,8 +118,8 @@ export function SignatureManager() {
|
||||
setSignatures((prev) => prev.filter((s) => s.id !== deleteTarget.id));
|
||||
toast.success(t('mail.signatureDeleted'));
|
||||
setDeleteTarget(null);
|
||||
} catch (err: any) {
|
||||
toast.error(err?.message || err?.detail || (typeof err === 'string' ? err : 'Delete failed'));
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj?.message || errObj?.detail || (typeof errObj === 'string' ? errObj : 'Delete failed'));
|
||||
}
|
||||
}, [deleteTarget, toast, t]);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState, useRef, useCallback } from 'react';
|
||||
import { Modal } from '@/components/ui/Modal';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
@@ -74,8 +75,8 @@ export function CsvImportDialog({ open, onClose, onSuccess }: CsvImportDialogPro
|
||||
setError(null);
|
||||
onSuccess?.();
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || 'Import fehlgeschlagen.');
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || 'Import fehlgeschlagen.');
|
||||
} finally {
|
||||
setImporting(false);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { useTasks, type Task, type TaskStatus } from '@/api/tasks';
|
||||
import { useTasks, type Task, type TaskStatus, type TaskFilter } from '@/api/tasks';
|
||||
import { Clock, AlertCircle, CheckCircle2, Loader2 } from 'lucide-react';
|
||||
|
||||
const STATUS_COLUMNS: TaskStatus[] = ['open', 'in_progress', 'review', 'blocked', 'done', 'cancelled'];
|
||||
@@ -92,14 +92,7 @@ function TaskCard({ task, onSelect }: TaskCardProps) {
|
||||
}
|
||||
|
||||
interface TaskBoardProps {
|
||||
filter?: {
|
||||
entity_type?: string;
|
||||
entity_id?: string;
|
||||
assignee_type?: string;
|
||||
assignee_id?: string;
|
||||
parent_task_id?: string;
|
||||
task_type?: string;
|
||||
};
|
||||
filter?: TaskFilter;
|
||||
onSelectTask?: (task: Task) => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -169,13 +169,8 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
|
||||
value={task.status}
|
||||
onChange={(e) => handleStatusChange(e.target.value as TaskStatus)}
|
||||
className="w-48"
|
||||
>
|
||||
{STATUS_OPTIONS.map((s) => (
|
||||
<option key={s} value={s}>
|
||||
{statusLabel(t, s)}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
options={STATUS_OPTIONS.map((s) => ({ value: s, label: statusLabel(t, s) }))}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Assignee */}
|
||||
@@ -189,13 +184,8 @@ export function TaskDetail({ taskId, onClose }: TaskDetailProps) {
|
||||
value={assigneeType}
|
||||
onChange={(e) => setAssigneeType(e.target.value as AssigneeType)}
|
||||
className="w-32"
|
||||
>
|
||||
{ASSIGNEE_TYPES.map((at) => (
|
||||
<option key={at} value={at}>
|
||||
{t(`tasks.assigneeType${at.charAt(0).toUpperCase() + at.slice(1)}`)}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
options={ASSIGNEE_TYPES.map((at) => ({ value: at, label: t(`tasks.assigneeType${at.charAt(0).toUpperCase() + at.slice(1)}`) }))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<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 { Sparkles, Send } from 'lucide-react';
|
||||
import {
|
||||
@@ -96,8 +97,8 @@ export function AiChatPanel({ windowTitle, windowType }: AiChatPanelProps) {
|
||||
setError(event.content || 'Ein Fehler ist aufgetreten');
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
setError(e?.message || 'KI Chat nicht verfügbar');
|
||||
} catch (e: unknown) { const errObj = asError(e);
|
||||
setError(errObj?.message || 'KI Chat nicht verfügbar');
|
||||
} finally {
|
||||
setIsStreaming(false);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { asError } from '@/utils/errorTypes';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
// TODO: P2-F21 — Replace hardcoded triggerEventOptions with backend config
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -166,17 +167,17 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
|
||||
toast.success('Workflow erstellt');
|
||||
}
|
||||
onClose();
|
||||
} catch (err: any) {
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
// Show detailed validation errors from backend (422)
|
||||
if (err.validationErrors) {
|
||||
const details = Object.entries(err.validationErrors)
|
||||
if (errObj.validationErrors) {
|
||||
const details = Object.entries(errObj.validationErrors)
|
||||
.map(([field, msgs]) => `${field}: ${(msgs as string[]).join(', ')}`)
|
||||
.join('; ');
|
||||
toast.error(`Validierungsfehler: ${details}`);
|
||||
} else if (err.detail) {
|
||||
toast.error(err.detail);
|
||||
} else if (errObj.detail) {
|
||||
toast.error(errObj.detail);
|
||||
} 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 {
|
||||
useWorkflowInstance,
|
||||
@@ -95,8 +96,8 @@ export function WorkflowInstanceDetail({
|
||||
);
|
||||
setComment('');
|
||||
setShowCommentField(null);
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || 'Fehler bei der Aktion');
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || 'Fehler bei der Aktion');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,8 +106,8 @@ export function WorkflowInstanceDetail({
|
||||
try {
|
||||
await cancelMutation.mutateAsync(instance.id);
|
||||
toast.success('Instanz abgebrochen');
|
||||
} catch (err: any) {
|
||||
toast.error(err.message || 'Fehler beim Abbrechen');
|
||||
} catch (err: unknown) { const errObj = asError(err);
|
||||
toast.error(errObj.message || 'Fehler beim Abbrechen');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user