kirche-einfach/frontend/src/components/FlowsTab.tsx
Jonas Miederer 0a2b73f3b4 feat: minimal frontend (run view, approvals, admin) with E2E smoke
- Tabs: Vorgaenge (run flows + history), Freigaben (decide tasks),
  Administration (Langflow link, import, deploy)
- Typed API client over vite /api proxy; tsc + vite build clean
- scripts/smoke_e2e.py: real LFX + API end-to-end, all green
- 34 backend tests green
2026-09-06 23:13:05 +00:00

105 lines
3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useCallback, useEffect, useState } from "react";
import { ApiError, api, type FlowDefinition, type Run } from "../api";
const inputStyle: React.CSSProperties = {
width: "100%",
fontFamily: "monospace",
fontSize: 13,
};
export default function FlowsTab() {
const [flows, setFlows] = useState<FlowDefinition[]>([]);
const [flowId, setFlowId] = useState("");
const [inputJson, setInputJson] = useState(
'{\n "betrag": 120000,\n "schwelle": 200000\n}',
);
const [tenant, setTenant] = useState("default");
const [runs, setRuns] = useState<Run[]>([]);
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const loadFlows = useCallback(async () => {
try {
const list = await api.listFlows();
setFlows(list);
const first = list[0];
if (first && !flowId) setFlowId(first.id);
} catch (e) {
setError(e instanceof ApiError ? `${e.status}: ${e.message}` : String(e));
}
}, [flowId]);
const loadRuns = useCallback(async (id: string) => {
if (!id) return;
try {
setRuns(await api.listRuns(id));
} catch (e) {
setError(e instanceof ApiError ? `${e.status}: ${e.message}` : String(e));
}
}, []);
useEffect(() => {
void loadFlows();
}, [loadFlows]);
useEffect(() => {
void loadRuns(flowId);
}, [flowId, loadRuns]);
async function onRun() {
setBusy(true);
setError("");
try {
const payload: unknown = JSON.parse(inputJson);
const run = await api.runFlow(flowId, payload, tenant);
setRuns((prev) => [run, ...prev]);
} catch (e) {
if (e instanceof SyntaxError) setError("Ungültiges JSON in der Eingabe.");
else setError(e instanceof ApiError ? `${e.status}: ${e.message}` : String(e));
} finally {
setBusy(false);
}
}
return (
<div>
<h2>Vorgang starten</h2>
{error && <p style={{ color: "crimson" }}>{error}</p>}
<div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
<select value={flowId} onChange={(e) => setFlowId(e.target.value)}>
<option value="">— Flow wählen —</option>
{flows.map((f) => (
<option key={f.id} value={f.id}>
{f.name}
</option>
))}
</select>
<input
value={tenant}
onChange={(e) => setTenant(e.target.value)}
placeholder="Mandant"
style={{ width: 160 }}
/>
<button onClick={() => void onRun()} disabled={!flowId || busy}>
{busy ? "Läuft …" : "Starten"}
</button>
</div>
<textarea
rows={6}
style={inputStyle}
value={inputJson}
onChange={(e) => setInputJson(e.target.value)}
/>
<h3>Letzte Läufe</h3>
{runs.length === 0 && <p>Noch keine Läufe.</p>}
<ul>
{runs.map((r) => (
<li key={r.id}>
<strong>{r.status}</strong> · {r.tenant_id} ·{" "}
<code>{r.output?.result ?? r.output?.error ?? "–"}</code>
</li>
))}
</ul>
</div>
);
}