Polish up clients ui
This commit is contained in:
4
.github/copilot-instructions.md
vendored
4
.github/copilot-instructions.md
vendored
@@ -1,5 +1,9 @@
|
|||||||
# Copilot instructions for infoscreen_2025
|
# Copilot instructions for infoscreen_2025
|
||||||
|
|
||||||
|
# Purpose
|
||||||
|
These instructions tell Copilot Chat how to reason about this codebase.
|
||||||
|
Prefer explanations and refactors that align with these structures.
|
||||||
|
|
||||||
Use this as your shared context when proposing changes. Keep edits minimal and match existing patterns referenced below.
|
Use this as your shared context when proposing changes. Keep edits minimal and match existing patterns referenced below.
|
||||||
|
|
||||||
## Big picture
|
## Big picture
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ const Appointments: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold mb-4">Terminmanagement</h1>
|
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 16 }}>Terminmanagement</h1>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
marginBottom: 16,
|
marginBottom: 16,
|
||||||
|
|||||||
@@ -15,107 +15,73 @@ import {
|
|||||||
Edit,
|
Edit,
|
||||||
} from '@syncfusion/ej2-react-grids';
|
} from '@syncfusion/ej2-react-grids';
|
||||||
import { DialogComponent } from '@syncfusion/ej2-react-popups';
|
import { DialogComponent } from '@syncfusion/ej2-react-popups';
|
||||||
|
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
|
||||||
|
|
||||||
// Raumgruppen werden dynamisch aus der API geladen
|
// Raumgruppen werden dynamisch aus der API geladen
|
||||||
|
|
||||||
interface DetailsModalProps {
|
// Details dialog renders via Syncfusion Dialog for consistent look & feel
|
||||||
open: boolean;
|
function DetailsContent({ client, groupIdToName }: { client: Client; groupIdToName: Record<string | number, string> }) {
|
||||||
client: Client | null;
|
|
||||||
groupIdToName: Record<string | number, string>;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function DetailsModal({ open, client, groupIdToName, onClose }: DetailsModalProps) {
|
|
||||||
if (!open || !client) return null;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="e-card-content">
|
||||||
style={{
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
position: 'fixed',
|
<tbody>
|
||||||
top: 0,
|
{Object.entries(client)
|
||||||
left: 0,
|
.filter(
|
||||||
right: 0,
|
([key]) =>
|
||||||
bottom: 0,
|
![
|
||||||
background: 'rgba(0,0,0,0.3)',
|
'index',
|
||||||
zIndex: 1000,
|
'is_active',
|
||||||
}}
|
'type',
|
||||||
>
|
'column',
|
||||||
<div
|
'group_name',
|
||||||
style={{
|
'foreignKeyData',
|
||||||
background: 'white',
|
'hardware_token',
|
||||||
padding: 0,
|
].includes(key)
|
||||||
margin: '100px auto',
|
)
|
||||||
maxWidth: 500,
|
.map(([key, value]) => (
|
||||||
borderRadius: 12,
|
<tr key={key}>
|
||||||
boxShadow: '0 4px 24px rgba(0,0,0,0.12)',
|
<td style={{ fontWeight: 600, padding: '6px 8px', width: '40%' }}>
|
||||||
}}
|
{key === 'group_id'
|
||||||
>
|
? 'Raumgruppe'
|
||||||
<div style={{ padding: 32 }}>
|
: key === 'ip'
|
||||||
<h3 style={{ fontSize: '1.25rem', fontWeight: 700, marginBottom: 18 }}>Client-Details</h3>
|
? 'IP-Adresse'
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', marginBottom: 24 }}>
|
: key === 'registration_time'
|
||||||
<tbody>
|
? 'Registriert am'
|
||||||
{Object.entries(client)
|
: key === 'description'
|
||||||
.filter(
|
? 'Beschreibung'
|
||||||
([key]) =>
|
: key === 'last_alive'
|
||||||
![
|
? 'Letzter Kontakt'
|
||||||
'index',
|
: key === 'model'
|
||||||
'is_active',
|
? 'Modell'
|
||||||
'type',
|
: key === 'uuid'
|
||||||
'column',
|
? 'Client-Code'
|
||||||
'group_name',
|
: key === 'os_version'
|
||||||
'foreignKeyData',
|
? 'Betriebssystem'
|
||||||
'hardware_token',
|
: key === 'software_version'
|
||||||
].includes(key)
|
? 'Clientsoftware'
|
||||||
)
|
: key === 'macs'
|
||||||
.map(([key, value]) => (
|
? 'MAC-Adressen'
|
||||||
<tr key={key}>
|
: key.charAt(0).toUpperCase() + key.slice(1)}
|
||||||
<td style={{ fontWeight: 'bold', padding: '6px 8px' }}>
|
</td>
|
||||||
{key === 'group_id'
|
<td style={{ padding: '6px 8px' }}>
|
||||||
? 'Raumgruppe'
|
{key === 'group_id'
|
||||||
: key === 'ip'
|
? value !== undefined
|
||||||
? 'IP-Adresse'
|
? groupIdToName[value as string | number] || String(value)
|
||||||
: key === 'registration_time'
|
: ''
|
||||||
? 'Registriert am'
|
: key === 'registration_time' && value
|
||||||
: key === 'description'
|
? new Date(
|
||||||
? 'Beschreibung'
|
(value as string).endsWith('Z') ? (value as string) : String(value) + 'Z'
|
||||||
: key === 'last_alive'
|
).toLocaleString()
|
||||||
? 'Letzter Kontakt'
|
: key === 'last_alive' && value
|
||||||
: key === 'model'
|
? String(value)
|
||||||
? 'Modell'
|
: key === 'macs' && typeof value === 'string'
|
||||||
: key === 'uuid'
|
? value.replace(/,\s*/g, ', ')
|
||||||
? 'Client-Code'
|
: String(value)}
|
||||||
: key === "os_version"
|
</td>
|
||||||
? 'Betriebssystem'
|
</tr>
|
||||||
: key === 'software_version'
|
))}
|
||||||
? 'Clientsoftware'
|
</tbody>
|
||||||
: key === 'macs'
|
</table>
|
||||||
? '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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -154,31 +120,38 @@ const Clients: React.FC = () => {
|
|||||||
|
|
||||||
// DataGrid row template für Details- und Entfernen-Button
|
// DataGrid row template für Details- und Entfernen-Button
|
||||||
const detailsButtonTemplate = (props: Client) => (
|
const detailsButtonTemplate = (props: Client) => (
|
||||||
<div style={{ display: 'flex', gap: '8px' }}>
|
<div style={{ display: 'flex', gap: 8, justifyContent: 'center' }}>
|
||||||
<button
|
<ButtonComponent cssClass="e-primary" onClick={() => setDetailsClient(props)}>
|
||||||
className="e-btn e-primary"
|
|
||||||
onClick={() => setDetailsClient(props)}
|
|
||||||
style={{ minWidth: 80 }}
|
|
||||||
>
|
|
||||||
Details
|
Details
|
||||||
</button>
|
</ButtonComponent>
|
||||||
<button
|
<ButtonComponent
|
||||||
className="e-btn e-danger"
|
cssClass="e-danger"
|
||||||
onClick={e => {
|
onClick={e => {
|
||||||
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
handleDelete(props.uuid);
|
handleDelete(props.uuid);
|
||||||
}}
|
}}
|
||||||
style={{ minWidth: 80 }}
|
|
||||||
>
|
>
|
||||||
Entfernen
|
Entfernen
|
||||||
</button>
|
</ButtonComponent>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div id="dialog-target">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div
|
||||||
<h2 className="text-xl font-bold">Client-Übersicht</h2>
|
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 />
|
<SetupModeButton />
|
||||||
</div>
|
</div>
|
||||||
{groups.length > 0 ? (
|
{groups.length > 0 ? (
|
||||||
@@ -190,7 +163,7 @@ const Clients: React.FC = () => {
|
|||||||
toolbar={['Search', 'Edit', 'Update', 'Cancel']}
|
toolbar={['Search', 'Edit', 'Update', 'Cancel']}
|
||||||
allowSorting={true}
|
allowSorting={true}
|
||||||
allowFiltering={true}
|
allowFiltering={true}
|
||||||
height={400}
|
height={420}
|
||||||
editSettings={{
|
editSettings={{
|
||||||
allowEditing: true,
|
allowEditing: true,
|
||||||
allowAdding: false,
|
allowAdding: false,
|
||||||
@@ -228,17 +201,17 @@ const Clients: React.FC = () => {
|
|||||||
width="140"
|
width="140"
|
||||||
/>
|
/>
|
||||||
<ColumnDirective field="uuid" headerText="UUID" allowEditing={false} width="160" />
|
<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
|
<ColumnDirective
|
||||||
field="last_alive"
|
field="last_alive"
|
||||||
headerText="Last Alive"
|
headerText="Last Alive"
|
||||||
allowEditing={false}
|
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
|
<ColumnDirective
|
||||||
headerText="Aktion"
|
headerText="Aktion"
|
||||||
width="190"
|
width="210"
|
||||||
template={detailsButtonTemplate}
|
template={detailsButtonTemplate}
|
||||||
textAlign="Center"
|
textAlign="Center"
|
||||||
allowEditing={false}
|
allowEditing={false}
|
||||||
@@ -246,17 +219,31 @@ const Clients: React.FC = () => {
|
|||||||
</ColumnsDirective>
|
</ColumnsDirective>
|
||||||
<Inject services={[Page, Toolbar, Search, Sort, Edit]} />
|
<Inject services={[Page, Toolbar, Search, Sort, Edit]} />
|
||||||
</GridComponent>
|
</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 && (
|
{showDialog && deleteClientId && (
|
||||||
<DialogComponent
|
<DialogComponent
|
||||||
visible={showDialog}
|
visible={showDialog}
|
||||||
@@ -264,6 +251,7 @@ const Clients: React.FC = () => {
|
|||||||
content="Möchten Sie diesen Client wirklich entfernen?"
|
content="Möchten Sie diesen Client wirklich entfernen?"
|
||||||
showCloseIcon={true}
|
showCloseIcon={true}
|
||||||
width="400px"
|
width="400px"
|
||||||
|
target="#dialog-target"
|
||||||
buttons={[
|
buttons={[
|
||||||
{ click: confirmDelete, buttonModel: { content: 'Ja', isPrimary: true } },
|
{ click: confirmDelete, buttonModel: { content: 'Ja', isPrimary: true } },
|
||||||
{ click: cancelDelete, buttonModel: { content: 'Abbrechen' } },
|
{ click: cancelDelete, buttonModel: { content: 'Abbrechen' } },
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Wrench } from 'lucide-react';
|
import { Wrench } from 'lucide-react';
|
||||||
|
import { ButtonComponent } from '@syncfusion/ej2-react-buttons';
|
||||||
|
|
||||||
const SetupModeButton: React.FC = () => {
|
const SetupModeButton: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
return (
|
return (
|
||||||
<button
|
<ButtonComponent
|
||||||
className="setupmode-btn flex items-center gap-2 px-4 py-2 bg-yellow-200 hover:bg-yellow-300 rounded"
|
cssClass="e-warning"
|
||||||
onClick={() => navigate('/setup')}
|
onClick={() => navigate('/setup')}
|
||||||
title="Erweiterungsmodus starten"
|
title="Erweiterungsmodus starten"
|
||||||
>
|
>
|
||||||
<Wrench size={18} />
|
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 8 }}>
|
||||||
Erweiterungsmodus
|
<Wrench size={14.4} />
|
||||||
</button>
|
Erweiterungsmodus
|
||||||
|
</span>
|
||||||
|
</ButtonComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -171,10 +171,12 @@ const Dashboard: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<header className="mb-8 pb-4 border-b-2 border-[#d6c3a6]">
|
<header style={{ marginBottom: 32, paddingBottom: 16, borderBottom: '2px solid #d6c3a6' }}>
|
||||||
<h2 className="text-3xl font-extrabold mb-2">Dashboard</h2>
|
<h2 style={{ fontSize: '1.75rem', fontWeight: 800, margin: 0, marginBottom: 8 }}>Dashboard</h2>
|
||||||
</header>
|
</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
|
<GridComponent
|
||||||
dataSource={groups}
|
dataSource={groups}
|
||||||
allowPaging={true}
|
allowPaging={true}
|
||||||
|
|||||||
@@ -335,7 +335,7 @@ const Infoscreen_groups: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div id="dialog-target">
|
<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
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
|
||||||
corePlugins: {
|
|
||||||
preflight: false,
|
|
||||||
},
|
|
||||||
theme: {
|
|
||||||
extend: {},
|
|
||||||
},
|
|
||||||
plugins: [],
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user