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,
|
INBOX_ACTION_LABELS,
|
||||||
type InboxAction, type InboxActionType,
|
type InboxAction, type InboxActionType,
|
||||||
agrarmonitorApi, type AgrarmonitorStatusData,
|
agrarmonitorApi, type AgrarmonitorStatusData,
|
||||||
|
type SettingClient, type AgrarmonitorPollingConfig, type AgrarmonitorPollingResult,
|
||||||
} from '../api/settings';
|
} from '../api/settings';
|
||||||
import { clientsApi, type Client } from '../api/inbox';
|
import { clientsApi, type Client } from '../api/inbox';
|
||||||
import { apiKeysApi, type ApiKey } from '../api/api-keys';
|
import { apiKeysApi, type ApiKey } from '../api/api-keys';
|
||||||
@@ -213,7 +214,9 @@ function FilterBuilder({ value, onChange, tags, docTypes, correspondents, custom
|
|||||||
function UserClientsTab() {
|
function UserClientsTab() {
|
||||||
const [data, setData] = useState<SettingUserClient[]>([]);
|
const [data, setData] = useState<SettingUserClient[]>([]);
|
||||||
const [clients, setClients] = useState<Client[]>([]);
|
const [clients, setClients] = useState<Client[]>([]);
|
||||||
|
const [allClients, setAllClients] = useState<SettingClient[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [clientsLoading, setClientsLoading] = useState(false);
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
@@ -229,7 +232,15 @@ function UserClientsTab() {
|
|||||||
} finally { setLoading(false); }
|
} 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 handleAdd = async () => {
|
||||||
const values = await form.validateFields();
|
const values = await form.validateFields();
|
||||||
@@ -246,6 +257,37 @@ function UserClientsTab() {
|
|||||||
load();
|
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> = [
|
const columns: ColumnsType<SettingUserClient> = [
|
||||||
{ title: 'User ID', dataIndex: 'UserId', key: 'userId' },
|
{ title: 'User ID', dataIndex: 'UserId', key: 'userId' },
|
||||||
{
|
{
|
||||||
@@ -277,6 +319,21 @@ function UserClientsTab() {
|
|||||||
Zuordnung hinzufügen
|
Zuordnung hinzufügen
|
||||||
</Button>
|
</Button>
|
||||||
<Table dataSource={data} columns={columns} loading={loading} rowKey="Id" size="small" pagination={false} />
|
<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)}>
|
<Modal title="Neue Zuordnung" open={modalOpen} onOk={handleAdd} onCancel={() => setModalOpen(false)}>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
<Form.Item name="UserId" label="User ID (Authentik)" rules={[{ required: true }]}>
|
<Form.Item name="UserId" label="User ID (Authentik)" rules={[{ required: true }]}>
|
||||||
@@ -2228,10 +2285,15 @@ function BarcodeTemplatesTab() {
|
|||||||
|
|
||||||
function AgrarmonitorTab() {
|
function AgrarmonitorTab() {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
const [pollingForm] = Form.useForm();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [registering, setRegistering] = 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 [status, setStatus] = useState<AgrarmonitorStatusData | null>(null);
|
||||||
const [registerResult, setRegisterResult] = useState<{ success: boolean; message: string } | null>(null);
|
const [registerResult, setRegisterResult] = useState<{ success: boolean; message: string } | null>(null);
|
||||||
|
const [pollingResult, setPollingResult] = useState<AgrarmonitorPollingResult | null>(null);
|
||||||
|
|
||||||
const handleLoadStatus = async () => {
|
const handleLoadStatus = async () => {
|
||||||
setLoading(true);
|
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) => {
|
const renderStatusTag = (value: boolean | null, labelTrue: string, labelFalse: string) => {
|
||||||
if (value === null) return <Tag>–</Tag>;
|
if (value === null) return <Tag>–</Tag>;
|
||||||
return value
|
return value
|
||||||
@@ -2343,6 +2445,53 @@ function AgrarmonitorTab() {
|
|||||||
)}
|
)}
|
||||||
</Card>
|
</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>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user