feat: Mantine design system with dark mode

This commit is contained in:
Jonas Miederer 2026-09-12 08:39:00 +02:00
parent 6e12720ad6
commit 38d4899b07
18 changed files with 1170 additions and 322 deletions

View file

@ -1,9 +1,22 @@
import { useCallback, useEffect, useState } from "react";
import Form from "@rjsf/core";
import Form from "@rjsf/mantine";
import validator from "@rjsf/validator-ajv8";
import type { RJSFSchema, UiSchema } from "@rjsf/utils";
import {
Alert,
Button,
Card,
Code,
Select,
Table,
Text,
Textarea,
Title,
} from "@mantine/core";
import { IconAlertCircle, IconPlayerPlay } from "@tabler/icons-react";
import { ApiError, api, type FlowDefinition, type InputSchema, type Run } from "../api";
import { useAuth } from "../auth";
import StatusBadge from "../components/StatusBadge";
export default function WorkflowsPage() {
const { me } = useAuth();
@ -70,68 +83,107 @@ export default function WorkflowsPage() {
return (
<div>
<h2>Workflows</h2>
{!me && <p>Sie sind nicht angemeldet — Läufe nutzen den Standard-Mandanten.</p>}
{error && <p style={{ color: "crimson" }}>{error}</p>}
<div style={{ display: "flex", gap: 8, marginBottom: 12 }}>
<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>
</div>
<Title order={2} mb="md">
Workflows
</Title>
{!me && (
<Alert color="blue" mb="md">
Sie sind nicht angemeldet — Läufe nutzen den Standard-Mandanten.
</Alert>
)}
{error && (
<Alert color="red" icon={<IconAlertCircle size={18} />} mb="md">
{error}
</Alert>
)}
<Select
label="Flow"
placeholder="— Flow wählen —"
value={flowId}
onChange={(v) => setFlowId(v ?? "")}
data={flows.map((f) => ({ value: f.id, label: f.name }))}
mb="md"
maw={480}
/>
{schema && schema.fallback && (
<div>
<textarea
<Card withBorder padding="lg" mb="md">
<Textarea
label="Freitext-Eingabe"
rows={6}
style={{ width: "100%", fontFamily: "monospace" }}
value={freeText}
onChange={(e) => setFreeText(e.target.value)}
placeholder="Freitext-Eingabe"
mb="sm"
/>
<button
<Button
leftSection={<IconPlayerPlay size={16} />}
loading={busy}
disabled={!flowId}
onClick={() => void onRun({ text: freeText })}
disabled={!flowId || busy}
>
{busy ? "Läuft …" : "Starten"}
</button>
</div>
Starten
</Button>
</Card>
)}
{schema && !schema.fallback && (
<Form
schema={schema.json_schema as RJSFSchema}
uiSchema={schema.ui_schema as UiSchema}
validator={validator}
formData={formData}
onChange={(e) =>
setFormData((e.formData as Record<string, unknown>) ?? {})
}
onSubmit={(e) => void onRun(e.formData)}
>
<button type="submit" disabled={busy}>
{busy ? "Läuft …" : "Starten"}
</button>
</Form>
<Card withBorder padding="lg" mb="md">
<Form
schema={schema.json_schema as RJSFSchema}
uiSchema={schema.ui_schema as UiSchema}
validator={validator}
formData={formData}
onChange={(e) =>
setFormData((e.formData as Record<string, unknown>) ?? {})
}
onSubmit={(e) => void onRun(e.formData)}
>
<Button
type="submit"
leftSection={<IconPlayerPlay size={16} />}
loading={busy}
mt="sm"
>
Starten
</Button>
</Form>
</Card>
)}
{result && (
<div>
<h3>Ergebnis</h3>
<pre style={{ whiteSpace: "pre-wrap" }}>{result}</pre>
</div>
<Card withBorder padding="lg" mb="md">
<Title order={3} mb="xs">
Ergebnis
</Title>
<Code block>{result}</Code>
</Card>
)}
<Title order={3} mb="xs">
Letzte Läufe
</Title>
{runs.length === 0 ? (
<Text c="dimmed">Noch keine Läufe.</Text>
) : (
<Table striped highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Status</Table.Th>
<Table.Th>Mandant</Table.Th>
<Table.Th>Ergebnis</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{runs.map((r) => (
<Table.Tr key={r.id}>
<Table.Td>
<StatusBadge status={r.status} />
</Table.Td>
<Table.Td>{r.tenant_id}</Table.Td>
<Table.Td>
<Code>{r.output?.result ?? r.output?.error ?? "–"}</Code>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
)}
<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>
);
}