revert(frontend): Frontend auf letzten funktionierenden Stand 5680179 zurückgesetzt — i18n-Massen-Batch brach Dashboard-Shell in Produktion; Render-Loop-Fix und DSGVO-Antrags-UI liegen sicher in Historie für kontrollierten Wiedereinspiel

This commit is contained in:
Agent Zero
2026-08-27 09:14:21 +02:00
parent bea479bfad
commit 66c11d3d64
119 changed files with 925 additions and 1477 deletions
@@ -3,7 +3,6 @@ import { Select } from '@/components/ui/Select';
import { Input } from '@/components/ui/Input';
import { Code2, FormInput } from 'lucide-react';
import type { WorkflowStep, WorkflowStepType } from '@/api/workflows';
import { useTranslation } from 'react-i18next';
const stepTypeOptions: { value: WorkflowStepType; label: string }[] = [
{ value: 'action', label: 'Action' },
@@ -54,7 +53,6 @@ export interface StepConfigPanelProps {
}
export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
const { t } = useTranslation();
const [mode, setMode] = useState<ConfigMode>('form');
const [configText, setConfigText] = useState('');
const [configError, setConfigError] = useState<string | undefined>(undefined);
@@ -113,7 +111,6 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
};
const renderTypeForm = () => {
const { t } = useTranslation();
switch (step.type) {
case 'wait':
return (
@@ -129,13 +126,13 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
e.target.value === '' ? undefined : Number(e.target.value)
)
}
placeholder={t('stepConfigPanel.zb3600')}
placeholder="z.B. 3600"
/>
<Input
label="Resume-Zeitpunkt (ISO)"
value={strVal('resume_at')}
onChange={(e) => setConfig('resume_at', e.target.value || undefined)}
placeholder={t('stepConfigPanel.20260818t090000z')}
placeholder="2026-08-18T09:00:00Z"
/>
</div>
);
@@ -154,7 +151,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
required
value={strVal('url')}
onChange={(e) => setConfig('url', e.target.value)}
placeholder={t('stepConfigPanel.httpsapiexamplecomwebhook')}
placeholder="https://api.example.com/webhook"
/>
</div>
<JsonField
@@ -171,7 +168,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
onChange={(e) => setConfig('body', e.target.value)}
rows={3}
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder={t('stepConfigPanel.keyvalue')}
placeholder='{"key": "value"}'
/>
</div>
<Input
@@ -197,14 +194,14 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
required
value={strVal('to')}
onChange={(e) => setConfig('to', e.target.value)}
placeholder={t('stepConfigPanel.empfaengerexamplecom')}
placeholder="empfaenger@example.com"
/>
<Input
label="Betreff"
required
value={strVal('subject')}
onChange={(e) => setConfig('subject', e.target.value)}
placeholder={t('stepConfigPanel.betreff')}
placeholder="Betreff"
/>
</div>
<div>
@@ -216,14 +213,14 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
onChange={(e) => setConfig('body', e.target.value)}
rows={4}
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder={t('stepConfigPanel.nachrichtentext')}
placeholder="Nachrichtentext"
/>
</div>
<Input
label="Account-ID (optional)"
value={strVal('account_id')}
onChange={(e) => setConfig('account_id', e.target.value || undefined)}
placeholder={t('stepConfigPanel.standardkontowennleer')}
placeholder="Standard-Konto wenn leer"
/>
</div>
);
@@ -243,19 +240,19 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
label="Titel"
value={strVal('title')}
onChange={(e) => setConfig('title', e.target.value)}
placeholder={t('stepConfigPanel.eventtitel')}
placeholder="Event-Titel"
/>
<Input
label="Start (ISO)"
value={strVal('start')}
onChange={(e) => setConfig('start', e.target.value)}
placeholder={t('stepConfigPanel.20260818t090000z')}
placeholder="2026-08-18T09:00:00Z"
/>
<Input
label="Ende (ISO)"
value={strVal('end')}
onChange={(e) => setConfig('end', e.target.value)}
placeholder={t('stepConfigPanel.20260818t100000z')}
placeholder="2026-08-18T10:00:00Z"
/>
</div>
)}
@@ -264,7 +261,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
label="Event-ID"
value={strVal('event_id')}
onChange={(e) => setConfig('event_id', e.target.value)}
placeholder={t('stepConfigPanel.eventuuid')}
placeholder="Event-UUID"
/>
)}
</div>
@@ -285,7 +282,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
label="Suchbegriff"
value={strVal('query')}
onChange={(e) => setConfig('query', e.target.value)}
placeholder={t('stepConfigPanel.suchbegriff')}
placeholder="Suchbegriff"
/>
)}
{action === 'download' && (
@@ -293,7 +290,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
label="Datei-ID"
value={strVal('file_id')}
onChange={(e) => setConfig('file_id', e.target.value)}
placeholder={t('stepConfigPanel.dateiuuid')}
placeholder="Datei-UUID"
/>
)}
{action === 'upload' && (
@@ -302,13 +299,13 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
label="Dateiname"
value={strVal('file_name')}
onChange={(e) => setConfig('file_name', e.target.value)}
placeholder={t('stepConfigPanel.dateipdf')}
placeholder="datei.pdf"
/>
<Input
label="Inhalt"
value={strVal('content')}
onChange={(e) => setConfig('content', e.target.value)}
placeholder={t('stepConfigPanel.dateiinhalt')}
placeholder="Dateiinhalt"
/>
</div>
)}
@@ -323,13 +320,13 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
required
value={strVal('query')}
onChange={(e) => setConfig('query', e.target.value)}
placeholder={t('stepConfigPanel.suchbegriff')}
placeholder="Suchbegriff"
/>
<Input
label="Entity-Typ (optional)"
value={strVal('entity_type')}
onChange={(e) => setConfig('entity_type', e.target.value || undefined)}
placeholder={t('stepConfigPanel.contactcompanyfile')}
placeholder="contact, company, file, ..."
/>
<Input
label="Limit"
@@ -350,7 +347,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
required
value={strVal('agent_id')}
onChange={(e) => setConfig('agent_id', e.target.value)}
placeholder={t('stepConfigPanel.agentuuid')}
placeholder="Agent-UUID"
/>
<JsonField
label="Input (JSON)"
@@ -364,7 +361,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
onChange={(e) => setConfig('wait_for_completion', e.target.checked)}
className="rounded border-secondary-300 text-primary-600 focus:ring-primary-500"
/>
{t('stepConfigPanel.aufabschlusswarten')}
Auf Abschluss warten
</label>
</div>
);
@@ -383,7 +380,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
label="Entity-ID"
value={strVal('entity_id')}
onChange={(e) => setConfig('entity_id', e.target.value)}
placeholder={t('stepConfigPanel.entityuuid')}
placeholder="Entity-UUID"
/>
)}
{(action.includes('create') || action.includes('update')) && (
@@ -400,14 +397,14 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
return (
<div>
<label className="block text-sm font-medium text-secondary-700 mb-1">
{t('stepConfigPanel.konfigurationjson')}
Konfiguration (JSON)
</label>
<textarea
value={configText}
onChange={(e) => handleConfigChange(e.target.value)}
rows={5}
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder={t('stepConfigPanel.keyvalue')}
placeholder='{"key": "value"}'
/>
{configError && (
<p className="mt-1 text-sm text-danger-600" role="alert">
@@ -427,7 +424,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
required
value={step.name}
onChange={(e) => onChange({ ...step, name: e.target.value })}
placeholder={t('stepConfigPanel.zbgenehmigungeinholen')}
placeholder="z.B. Genehmigung einholen"
/>
<Select
label="Typ"
@@ -448,7 +445,7 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
onChange={(e) => onChange({ ...step, description: e.target.value || null })}
rows={2}
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder={t('stepConfigPanel.optionalebeschreibung')}
placeholder="Optionale Beschreibung"
/>
</div>
@@ -487,14 +484,14 @@ export function StepConfigPanel({ step, onChange }: StepConfigPanelProps) {
) : (
<div>
<label className="block text-sm font-medium text-secondary-700 mb-1">
{t('stepConfigPanel.konfigurationjson')}
Konfiguration (JSON)
</label>
<textarea
value={configText}
onChange={(e) => handleConfigChange(e.target.value)}
rows={5}
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder={t('stepConfigPanel.keyvalue')}
placeholder='{"key": "value"}'
/>
{configError && (
<p className="mt-1 text-sm text-danger-600" role="alert">
@@ -514,7 +511,6 @@ interface JsonFieldProps {
}
function JsonField({ label, value, onChange }: JsonFieldProps) {
const { t } = useTranslation();
const [text, setText] = useState(value);
const [error, setError] = useState<string | undefined>(undefined);
@@ -547,7 +543,7 @@ function JsonField({ label, value, onChange }: JsonFieldProps) {
onChange={(e) => handleChange(e.target.value)}
rows={3}
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder={t('stepConfigPanel.keyvalue')}
placeholder='{"key": "value"}'
/>
{error && (
<p className="mt-1 text-sm text-danger-600" role="alert">
@@ -391,7 +391,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
}`}
aria-pressed={mode === 'json'}
>
<Code2 className="h-3.5 w-3.5" /> {t('workflowEditor.jsonexpert')}
<Code2 className="h-3.5 w-3.5" /> JSON Expert
</button>
</div>
</div>
@@ -428,7 +428,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
required
value={form.name}
onChange={(e) => updateField('name', e.target.value)}
placeholder={t('workflowEditor.zbdealgenehmigungsprozess')}
placeholder="z.B. Deal-Genehmigungsprozess"
/>
<div>
<label className="block text-sm font-medium text-secondary-700 mb-1">
@@ -439,7 +439,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
onChange={(e) => updateField('description', e.target.value)}
rows={2}
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder={t('workflowEditor.optionalebeschreibung')}
placeholder="Optionale Beschreibung"
/>
</div>
<Select
@@ -472,12 +472,12 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
type="button"
icon={<Plus className="h-4 w-4" />}
>
{t('workflowEditor.schritthinzufuegen')}
Schritt hinzufuegen
</Button>
</div>
{form.steps.length === 0 && (
<p className="text-sm text-secondary-400 italic">
{t('workflowEditor.keineschrittedefiniertklickeaufschritt')}
Keine Schritte definiert. Klicke auf Schritt hinzufuegen.
</p>
)}
<div className="space-y-4">
@@ -493,7 +493,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
onClick={() => moveStep(i, 'up')}
disabled={i === 0}
className="text-secondary-400 hover:text-secondary-700 disabled:opacity-30 p-1"
aria-label={t('workflowEditor.nachoben')}
aria-label="Nach oben"
>
<ArrowUp className="h-4 w-4" />
</button>
@@ -502,7 +502,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
onClick={() => moveStep(i, 'down')}
disabled={i === form.steps.length - 1}
className="text-secondary-400 hover:text-secondary-700 disabled:opacity-30 p-1"
aria-label={t('workflowEditor.nachunten')}
aria-label="Nach unten"
>
<ArrowDown className="h-4 w-4" />
</button>
@@ -510,7 +510,7 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
type="button"
onClick={() => removeStep(i)}
className="text-danger-500 hover:text-danger-700 p-1"
aria-label={t('workflowEditor.schrittentfernen')}
aria-label="Schritt entfernen"
>
<Trash2 className="h-4 w-4" />
</button>
@@ -528,14 +528,14 @@ export function WorkflowEditor({ open, workflow, onClose }: WorkflowEditorProps)
) : (
<div>
<label className="block text-sm font-medium text-secondary-700 mb-1">
{t('workflowEditor.workflowjson')}
Workflow (JSON)
</label>
<textarea
value={jsonText}
onChange={(e) => handleJsonChange(e.target.value)}
rows={18}
className="w-full rounded-lg border border-secondary-300 px-3 py-2 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary-500"
placeholder={t('workflowEditor.namesteps')}
placeholder='{"name": "...", "steps": [...]}'
/>
{jsonError && (
<p className="mt-1 text-sm text-danger-600" role="alert">
@@ -7,7 +7,6 @@ import { Skeleton } from '@/components/ui/Skeleton';
import { EmptyState } from '@/components/ui/EmptyState';
import { Card } from '@/components/ui/Card';
import { AlertCircle, ChevronRight } from 'lucide-react';
import { useTranslation } from 'react-i18next';
const statusFilterOptions = [
{ value: '', label: 'Alle Status' },
@@ -55,7 +54,6 @@ export interface WorkflowInstanceListProps {
export function WorkflowInstanceList({
onSelectInstance,
}: WorkflowInstanceListProps) {
const { t } = useTranslation();
const [statusFilter, setStatusFilter] = useState<string>('');
const [page, setPage] = useState(1);
const pageSize = 20;
@@ -105,12 +103,12 @@ export function WorkflowInstanceList({
<Card className="p-6">
<div className="flex items-center gap-3 text-danger-600">
<AlertCircle className="h-5 w-5" />
<span>{t('workflowInstanceList.fehlerbeimladenderinstanzen')}</span>
<span>Fehler beim Laden der Instanzen</span>
<button
onClick={() => refetch()}
className="text-sm text-primary-600 hover:underline"
>
{t('workflowInstanceList.erneutversuchen')}
Erneut versuchen
</button>
</div>
</Card>
@@ -119,7 +117,7 @@ export function WorkflowInstanceList({
{/* Empty state */}
{!isLoading && !isError && instances.length === 0 && (
<EmptyState
title={t('workflowInstanceList.keineworkflowinstanzen')}
title="Keine Workflow-Instanzen"
description="Es wurden keine Instanzen gefunden, die dem Filter entsprechen."
/>
)}