Files
infoscreen/dashboard/src/clients.tsx
olaf 7f4800496a implement functionality to delete clients in
clients and SetupMode components
2025-07-22 16:04:26 +00:00

265 lines
8.5 KiB
TypeScript

import SetupModeButton from './components/SetupModeButton';
import React, { useEffect, useState } from 'react';
import { useClientDelete } from './hooks/useClientDelete';
import { fetchClients, updateClient } from './apiClients';
import type { Client } from './apiClients';
import {
GridComponent,
ColumnsDirective,
ColumnDirective,
Page,
Inject,
Toolbar,
Search,
Sort,
Edit,
} from '@syncfusion/ej2-react-grids';
import { DialogComponent } from '@syncfusion/ej2-react-popups';
// Raumgruppen werden dynamisch aus der API geladen
interface DetailsModalProps {
open: boolean;
client: Client | null;
groupIdToName: Record<string | number, string>;
onClose: () => void;
}
function DetailsModal({ open, client, groupIdToName, onClose }: DetailsModalProps) {
if (!open || !client) return null;
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'rgba(0,0,0,0.3)',
zIndex: 1000,
}}
>
<div
style={{
background: 'white',
padding: 0,
margin: '100px auto',
maxWidth: 500,
borderRadius: 12,
boxShadow: '0 4px 24px rgba(0,0,0,0.12)',
}}
>
<div style={{ padding: 32 }}>
<h3 style={{ fontSize: '1.25rem', fontWeight: 700, marginBottom: 18 }}>Client-Details</h3>
<table style={{ width: '100%', borderCollapse: 'collapse', marginBottom: 24 }}>
<tbody>
{Object.entries(client)
.filter(
([key]) =>
![
'index',
'is_active',
'type',
'column',
'group_name',
'foreignKeyData',
'hardware_token',
].includes(key)
)
.map(([key, value]) => (
<tr key={key}>
<td style={{ fontWeight: 'bold', padding: '6px 8px' }}>
{key === 'group_id'
? 'Raumgruppe'
: key === 'ip'
? 'IP-Adresse'
: key === 'registration_time'
? 'Registriert am'
: key.charAt(0).toUpperCase() + key.slice(1)}
:
</td>
<td style={{ padding: '6px 8px' }}>
{key === 'group_id'
? value !== undefined
? groupIdToName[value as string | number] || value
: ''
: key === 'registration_time' && value
? new Date(
(value as string).endsWith('Z') ? (value as string) : value + 'Z'
).toLocaleString()
: key === 'last_alive' && value
? String(value) // Wert direkt anzeigen, nicht erneut parsen
: String(value)}
</td>
</tr>
))}
</tbody>
</table>
<div style={{ textAlign: 'right' }}>
<button className="e-btn e-outline" onClick={onClose}>
Schließen
</button>
</div>
</div>
</div>
</div>
);
}
const Clients: React.FC = () => {
const [clients, setClients] = useState<Client[]>([]);
const [groups, setGroups] = useState<{ id: number; name: string }[]>([]);
const [detailsClient, setDetailsClient] = useState<Client | null>(null);
const { showDialog, deleteClientId, handleDelete, confirmDelete, cancelDelete } = useClientDelete(
uuid => setClients(prev => prev.filter(c => c.uuid !== uuid))
);
useEffect(() => {
fetchClients().then(setClients);
// Gruppen auslesen
import('./apiGroups').then(mod => mod.fetchGroups()).then(setGroups);
}, []);
// Map group_id zu group_name
const groupIdToName: Record<string | number, string> = {};
groups.forEach(g => {
groupIdToName[g.id] = g.name;
});
// DataGrid data mit korrektem Gruppennamen und formatierten Zeitangaben
const gridData = clients.map(c => ({
...c,
group_name: c.group_id !== undefined ? groupIdToName[c.group_id] || String(c.group_id) : '',
last_alive: c.last_alive
? new Date(
(c.last_alive as string).endsWith('Z') ? (c.last_alive as string) : c.last_alive + 'Z'
).toLocaleString()
: '',
}));
// DataGrid row template für Details- und Entfernen-Button
const detailsButtonTemplate = (props: Client) => (
<div style={{ display: 'flex', gap: '8px' }}>
<button
className="e-btn e-primary"
onClick={() => setDetailsClient(props)}
style={{ minWidth: 80 }}
>
Details
</button>
<button
className="e-btn e-danger"
onClick={e => {
e.stopPropagation();
handleDelete(props.uuid);
}}
style={{ minWidth: 80 }}
>
Entfernen
</button>
</div>
);
return (
<div>
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Client-Übersicht</h2>
<SetupModeButton />
</div>
{groups.length > 0 ? (
<>
<GridComponent
dataSource={gridData}
allowPaging={true}
pageSettings={{ pageSize: 10 }}
toolbar={['Search', 'Edit', 'Update', 'Cancel']}
allowSorting={true}
allowFiltering={true}
height={400}
editSettings={{
allowEditing: true,
allowAdding: false,
allowDeleting: false,
mode: 'Normal',
}}
actionComplete={async (args: {
requestType: string;
data: Record<string, unknown>;
}) => {
if (args.requestType === 'save') {
const { uuid, description, model } = args.data as {
uuid: string;
description: string;
model: string;
};
// API-Aufruf zum Speichern
await updateClient(uuid, { description, model });
// Nach dem Speichern neu laden
fetchClients().then(setClients);
}
}}
>
<ColumnsDirective>
<ColumnDirective
field="description"
headerText="Beschreibung"
allowEditing={true}
width="180"
/>
<ColumnDirective
field="group_name"
headerText="Raumgruppe"
allowEditing={false}
width="140"
/>
<ColumnDirective field="uuid" headerText="UUID" allowEditing={false} width="160" />
<ColumnDirective field="ip" headerText="IP-Adresse" allowEditing={false} width="80" />
<ColumnDirective
field="last_alive"
headerText="Last Alive"
allowEditing={false}
width="120"
/>
<ColumnDirective field="model" headerText="Model" allowEditing={true} width="120" />
<ColumnDirective
headerText="Aktion"
width="190"
template={detailsButtonTemplate}
textAlign="Center"
allowEditing={false}
/>
</ColumnsDirective>
<Inject services={[Page, Toolbar, Search, Sort, Edit]} />
</GridComponent>
<DetailsModal
open={!!detailsClient}
client={detailsClient}
groupIdToName={groupIdToName}
onClose={() => setDetailsClient(null)}
/>
</>
) : (
<div className="text-gray-500">Raumgruppen werden geladen ...</div>
)}
{/* DialogComponent für Bestätigung */}
{showDialog && deleteClientId && (
<DialogComponent
visible={showDialog}
header="Bestätigung"
content="Möchten Sie diesen Client wirklich entfernen?"
showCloseIcon={true}
width="400px"
buttons={[
{ click: confirmDelete, buttonModel: { content: 'Ja', isPrimary: true } },
{ click: cancelDelete, buttonModel: { content: 'Abbrechen' } },
]}
close={cancelDelete}
/>
)}
</div>
);
};
export default Clients;