Polish up clients ui

This commit is contained in:
RobbStarkAustria
2025-10-12 05:23:10 +00:00
parent 4d807be6f8
commit 7ab4ea14c4
7 changed files with 131 additions and 144 deletions

View File

@@ -387,7 +387,7 @@ const Appointments: React.FC = () => {
return (
<div>
<h1 className="text-2xl font-bold mb-4">Terminmanagement</h1>
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 16 }}>Terminmanagement</h1>
<div
style={{
marginBottom: 16,

View File

@@ -15,107 +15,73 @@ import {
Edit,
} from '@syncfusion/ej2-react-grids';
import { DialogComponent } from '@syncfusion/ej2-react-popups';
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
// 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;
// Details dialog renders via Syncfusion Dialog for consistent look & feel
function DetailsContent({ client, groupIdToName }: { client: Client; groupIdToName: Record<string | number, string> }) {
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 === 'description'
? 'Beschreibung'
: key === 'last_alive'
? 'Letzter Kontakt'
: key === 'model'
? 'Modell'
: key === 'uuid'
? 'Client-Code'
: key === "os_version"
? 'Betriebssystem'
: key === 'software_version'
? 'Clientsoftware'
: key === 'macs'
? 'MAC-Adressen'
: 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 className="e-card-content">
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
<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: 600, padding: '6px 8px', width: '40%' }}>
{key === 'group_id'
? 'Raumgruppe'
: key === 'ip'
? 'IP-Adresse'
: key === 'registration_time'
? 'Registriert am'
: key === 'description'
? 'Beschreibung'
: key === 'last_alive'
? 'Letzter Kontakt'
: key === 'model'
? 'Modell'
: key === 'uuid'
? 'Client-Code'
: key === 'os_version'
? 'Betriebssystem'
: key === 'software_version'
? 'Clientsoftware'
: key === 'macs'
? 'MAC-Adressen'
: key.charAt(0).toUpperCase() + key.slice(1)}
</td>
<td style={{ padding: '6px 8px' }}>
{key === 'group_id'
? value !== undefined
? groupIdToName[value as string | number] || String(value)
: ''
: key === 'registration_time' && value
? new Date(
(value as string).endsWith('Z') ? (value as string) : String(value) + 'Z'
).toLocaleString()
: key === 'last_alive' && value
? String(value)
: key === 'macs' && typeof value === 'string'
? value.replace(/,\s*/g, ', ')
: String(value)}
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
@@ -154,31 +120,38 @@ const Clients: React.FC = () => {
// 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 }}
>
<div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
<ButtonComponent cssClass="e-primary" onClick={() => setDetailsClient(props)}>
Details
</button>
<button
className="e-btn e-danger"
</ButtonComponent>
<ButtonComponent
cssClass="e-danger"
onClick={e => {
e.preventDefault();
e.stopPropagation();
handleDelete(props.uuid);
}}
style={{ minWidth: 80 }}
>
Entfernen
</button>
</ButtonComponent>
</div>
);
return (
<div>
<div className="flex justify-between items-center mb-4">
<h2 className="text-xl font-bold">Client-Übersicht</h2>
<div id="dialog-target">
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 16,
gap: 12,
flexWrap: 'wrap',
}}
>
<h2 style={{ margin: 0, fontSize: '1.25rem', fontWeight: 700 }}>
Client-Übersicht
</h2>
<SetupModeButton />
</div>
{groups.length > 0 ? (
@@ -190,7 +163,7 @@ const Clients: React.FC = () => {
toolbar={['Search', 'Edit', 'Update', 'Cancel']}
allowSorting={true}
allowFiltering={true}
height={400}
height={420}
editSettings={{
allowEditing: true,
allowAdding: false,
@@ -228,17 +201,17 @@ const Clients: React.FC = () => {
width="140"
/>
<ColumnDirective field="uuid" headerText="UUID" allowEditing={false} width="160" />
<ColumnDirective field="ip" headerText="IP-Adresse" allowEditing={false} width="80" />
<ColumnDirective field="ip" headerText="IP-Adresse" allowEditing={false} width="100" />
<ColumnDirective
field="last_alive"
headerText="Last Alive"
allowEditing={false}
width="120"
width="150"
/>
<ColumnDirective field="model" headerText="Model" allowEditing={true} width="120" />
<ColumnDirective field="model" headerText="Model" allowEditing={true} width="140" />
<ColumnDirective
headerText="Aktion"
width="190"
width="210"
template={detailsButtonTemplate}
textAlign="Center"
allowEditing={false}
@@ -246,17 +219,31 @@ const Clients: React.FC = () => {
</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>
<span style={{ color: '#6b7280' }}>Raumgruppen werden geladen ...</span>
)}
{/* DialogComponent für Bestätigung */}
{/* Details-Dialog */}
{detailsClient && (
<DialogComponent
visible={!!detailsClient}
header="Client-Details"
showCloseIcon={true}
target="#dialog-target"
width="560px"
close={() => setDetailsClient(null)}
footerTemplate={() => (
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8 }}>
<ButtonComponent onClick={() => setDetailsClient(null)}>{'Schließen'}</ButtonComponent>
</div>
)}
>
<DetailsContent client={detailsClient} groupIdToName={groupIdToName} />
</DialogComponent>
)}
{/* Bestätigungs-Dialog für Löschen */}
{showDialog && deleteClientId && (
<DialogComponent
visible={showDialog}
@@ -264,6 +251,7 @@ const Clients: React.FC = () => {
content="Möchten Sie diesen Client wirklich entfernen?"
showCloseIcon={true}
width="400px"
target="#dialog-target"
buttons={[
{ click: confirmDelete, buttonModel: { content: 'Ja', isPrimary: true } },
{ click: cancelDelete, buttonModel: { content: 'Abbrechen' } },

View File

@@ -1,18 +1,21 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Wrench } from 'lucide-react';
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
const SetupModeButton: React.FC = () => {
const navigate = useNavigate();
return (
<button
className="setupmode-btn flex items-center gap-2 px-4 py-2 bg-yellow-200 hover:bg-yellow-300 rounded"
<ButtonComponent
cssClass="e-warning"
onClick={() => navigate('/setup')}
title="Erweiterungsmodus starten"
>
<Wrench size={18} />
Erweiterungsmodus
</button>
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
<Wrench size={14.4} />
Erweiterungsmodus
</span>
</ButtonComponent>
);
};

View File

@@ -171,10 +171,12 @@ const Dashboard: React.FC = () => {
return (
<div>
<header className="mb-8 pb-4 border-b-2 border-[#d6c3a6]">
<h2 className="text-3xl font-extrabold mb-2">Dashboard</h2>
<header style={{ marginBottom: 32, paddingBottom: 16, borderBottom: '2px solid #d6c3a6' }}>
<h2 style={{ fontSize: '1.75rem', fontWeight: 800, margin: 0, marginBottom: 8 }}>Dashboard</h2>
</header>
<h3 className="text-lg font-semibold mt-6 mb-4">Raumgruppen Übersicht</h3>
<h3 style={{ fontSize: '1.125rem', fontWeight: 600, marginTop: 24, marginBottom: 16 }}>
Raumgruppen Übersicht
</h3>
<GridComponent
dataSource={groups}
allowPaging={true}

View File

@@ -335,7 +335,7 @@ const Infoscreen_groups: React.FC = () => {
return (
<div id="dialog-target">
<h2 className="text-xl font-bold mb-4">{de.title}</h2>
<h2 style={{ fontSize: '1.25rem', fontWeight: 700, marginBottom: 16 }}>{de.title}</h2>
<div
style={{
display: 'flex',

View File

@@ -1,10 +0,0 @@
module.exports = {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
corePlugins: {
preflight: false,
},
theme: {
extend: {},
},
plugins: [],
};