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:
@@ -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."
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user