feat: extend SettingsPage with Agrarmonitor polling UI
Benutzer & Betriebe tab: - Add Betriebe table with inline-editable AgrarmonitorBetriebId column Agrarmonitor tab: - Add Polling-Konfiguration card (tag-IDs, auto-loaded, save button) - Add Polling ausführen card (run button, result display with error list) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import {
|
||||
INBOX_ACTION_LABELS,
|
||||
type InboxAction, type InboxActionType,
|
||||
agrarmonitorApi, type AgrarmonitorStatusData,
|
||||
type SettingClient, type AgrarmonitorPollingConfig, type AgrarmonitorPollingResult,
|
||||
} from '../api/settings';
|
||||
import { clientsApi, type Client } from '../api/inbox';
|
||||
import { apiKeysApi, type ApiKey } from '../api/api-keys';
|
||||
@@ -213,7 +214,9 @@ function FilterBuilder({ value, onChange, tags, docTypes, correspondents, custom
|
||||
function UserClientsTab() {
|
||||
const [data, setData] = useState<SettingUserClient[]>([]);
|
||||
const [clients, setClients] = useState<Client[]>([]);
|
||||
const [allClients, setAllClients] = useState<SettingClient[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [clientsLoading, setClientsLoading] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -229,7 +232,15 @@ function UserClientsTab() {
|
||||
} finally { setLoading(false); }
|
||||
}, []);
|
||||
|
||||
useEffect(() => { load(); }, [load]);
|
||||
const loadAllClients = useCallback(async () => {
|
||||
setClientsLoading(true);
|
||||
try {
|
||||
const cls = await settingsApi.getClients();
|
||||
setAllClients(cls);
|
||||
} finally { setClientsLoading(false); }
|
||||
}, []);
|
||||
|
||||
useEffect(() => { load(); loadAllClients(); }, [load, loadAllClients]);
|
||||
|
||||
const handleAdd = async () => {
|
||||
const values = await form.validateFields();
|
||||
@@ -246,6 +257,37 @@ function UserClientsTab() {
|
||||
load();
|
||||
};
|
||||
|
||||
const handleUpdateBetriebId = async (id: number, val: number | null) => {
|
||||
try {
|
||||
const updated = await settingsApi.updateClient(id, val);
|
||||
setAllClients(prev => prev.map(c => c.Id === id ? updated : c));
|
||||
} catch {
|
||||
message.error('Speichern fehlgeschlagen');
|
||||
}
|
||||
};
|
||||
|
||||
const allClientColumns: ColumnsType<SettingClient> = [
|
||||
{ title: 'Name', dataIndex: 'Name', key: 'name' },
|
||||
{
|
||||
title: 'Agrarmonitor-BetriebId',
|
||||
dataIndex: 'AgrarmonitorBetriebId',
|
||||
key: 'betriebId',
|
||||
render: (val: number | null, record) => (
|
||||
<InputNumber
|
||||
value={val ?? undefined}
|
||||
placeholder="–"
|
||||
min={1}
|
||||
style={{ width: 120 }}
|
||||
onBlur={(e) => {
|
||||
const parsed = e.target.value ? parseInt(e.target.value, 10) : null;
|
||||
const current = val ?? null;
|
||||
if (parsed !== current) handleUpdateBetriebId(record.Id, isNaN(parsed as number) ? null : parsed);
|
||||
}}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const columns: ColumnsType<SettingUserClient> = [
|
||||
{ title: 'User ID', dataIndex: 'UserId', key: 'userId' },
|
||||
{
|
||||
@@ -277,6 +319,21 @@ function UserClientsTab() {
|
||||
Zuordnung hinzufügen
|
||||
</Button>
|
||||
<Table dataSource={data} columns={columns} loading={loading} rowKey="Id" size="small" pagination={false} />
|
||||
|
||||
<Divider />
|
||||
<Typography.Title level={5} style={{ marginBottom: 8 }}>Betriebe — Agrarmonitor-Zuordnung</Typography.Title>
|
||||
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 12 }}>
|
||||
Ordne jedem Betrieb die zugehörige Agrarmonitor-BetriebId zu. Wird beim Polling verwendet.
|
||||
</Typography.Text>
|
||||
<Table
|
||||
dataSource={allClients}
|
||||
columns={allClientColumns}
|
||||
loading={clientsLoading}
|
||||
rowKey="Id"
|
||||
size="small"
|
||||
pagination={false}
|
||||
/>
|
||||
|
||||
<Modal title="Neue Zuordnung" open={modalOpen} onOk={handleAdd} onCancel={() => setModalOpen(false)}>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item name="UserId" label="User ID (Authentik)" rules={[{ required: true }]}>
|
||||
@@ -2228,10 +2285,15 @@ function BarcodeTemplatesTab() {
|
||||
|
||||
function AgrarmonitorTab() {
|
||||
const [form] = Form.useForm();
|
||||
const [pollingForm] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [registering, setRegistering] = useState(false);
|
||||
const [pollingConfigLoading, setPollingConfigLoading] = useState(false);
|
||||
const [pollingSaving, setPollingSaving] = useState(false);
|
||||
const [pollingRunning, setPollingRunning] = useState(false);
|
||||
const [status, setStatus] = useState<AgrarmonitorStatusData | null>(null);
|
||||
const [registerResult, setRegisterResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||
const [pollingResult, setPollingResult] = useState<AgrarmonitorPollingResult | null>(null);
|
||||
|
||||
const handleLoadStatus = async () => {
|
||||
setLoading(true);
|
||||
@@ -2267,6 +2329,46 @@ function AgrarmonitorTab() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleLoadPollingConfig = useCallback(async () => {
|
||||
setPollingConfigLoading(true);
|
||||
try {
|
||||
const cfg = await agrarmonitorApi.getPollingConfig();
|
||||
pollingForm.setFieldsValue(cfg);
|
||||
} catch {
|
||||
message.error('Polling-Konfiguration konnte nicht geladen werden');
|
||||
} finally {
|
||||
setPollingConfigLoading(false);
|
||||
}
|
||||
}, [pollingForm]);
|
||||
|
||||
useEffect(() => { handleLoadPollingConfig(); }, [handleLoadPollingConfig]);
|
||||
|
||||
const handleSavePollingConfig = async () => {
|
||||
const values = await pollingForm.validateFields() as AgrarmonitorPollingConfig;
|
||||
setPollingSaving(true);
|
||||
try {
|
||||
await agrarmonitorApi.updatePollingConfig(values);
|
||||
message.success('Konfiguration gespeichert');
|
||||
} catch {
|
||||
message.error('Speichern fehlgeschlagen');
|
||||
} finally {
|
||||
setPollingSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRunPolling = async () => {
|
||||
setPollingRunning(true);
|
||||
setPollingResult(null);
|
||||
try {
|
||||
const result = await agrarmonitorApi.runPolling();
|
||||
setPollingResult(result);
|
||||
} catch {
|
||||
message.error('Polling fehlgeschlagen');
|
||||
} finally {
|
||||
setPollingRunning(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderStatusTag = (value: boolean | null, labelTrue: string, labelFalse: string) => {
|
||||
if (value === null) return <Tag>–</Tag>;
|
||||
return value
|
||||
@@ -2343,6 +2445,53 @@ function AgrarmonitorTab() {
|
||||
)}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card size="small" title="Polling-Konfiguration" loading={pollingConfigLoading}>
|
||||
<Form form={pollingForm} layout="vertical">
|
||||
<Form.Item
|
||||
name="tagFertig"
|
||||
label="Tag-ID: Fertig in Agrarmonitor"
|
||||
rules={[{ required: true, message: 'Pflichtfeld' }]}
|
||||
>
|
||||
<Input placeholder="4" style={{ width: 120 }} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="tagVerbucht"
|
||||
label="Tag-ID: Verbucht"
|
||||
rules={[{ required: true, message: 'Pflichtfeld' }]}
|
||||
>
|
||||
<Input placeholder="9" style={{ width: 120 }} />
|
||||
</Form.Item>
|
||||
<Button type="primary" loading={pollingSaving} onClick={handleSavePollingConfig}>
|
||||
Speichern
|
||||
</Button>
|
||||
</Form>
|
||||
</Card>
|
||||
|
||||
<Card size="small" title="Polling ausführen">
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<Button loading={pollingRunning} onClick={handleRunPolling}>
|
||||
Jetzt ausführen
|
||||
</Button>
|
||||
{pollingResult && (
|
||||
<div>
|
||||
<Tag color="blue">{pollingResult.processed} verarbeitet</Tag>
|
||||
<Tag color="success">{pollingResult.updated} aktualisiert</Tag>
|
||||
<Tag>{pollingResult.skipped} übersprungen</Tag>
|
||||
{pollingResult.errors.length > 0 && (
|
||||
<Tag color="error">{pollingResult.errors.length} Fehler</Tag>
|
||||
)}
|
||||
{pollingResult.errors.length > 0 && (
|
||||
<ul style={{ marginTop: 8, paddingLeft: 20 }}>
|
||||
{pollingResult.errors.map((e, i) => (
|
||||
<li key={i} style={{ color: '#ff4d4f' }}>{e}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</Card>
|
||||
</Space>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user