infoscreen-overview in cards
This commit is contained in:
@@ -1,113 +1,156 @@
|
||||
// import 'react-app-polyfill/ie11'; // optional, falls benötigt
|
||||
import React, { useState } from 'react';
|
||||
import { BrowserRouter as Router, Routes, Route, Link, Outlet } from 'react-router-dom';
|
||||
import logo from './assets/logo.png';
|
||||
import './App.css';
|
||||
import React from 'react';
|
||||
|
||||
// Lucide Icons importieren
|
||||
import {
|
||||
ScheduleComponent,
|
||||
Day,
|
||||
Week,
|
||||
WorkWeek,
|
||||
Month,
|
||||
Agenda,
|
||||
TimelineViews,
|
||||
TimelineMonth,
|
||||
Inject,
|
||||
ViewsDirective,
|
||||
ViewDirective,
|
||||
ResourcesDirective,
|
||||
ResourceDirective,
|
||||
} from '@syncfusion/ej2-react-schedule';
|
||||
import { L10n, loadCldr, setCulture } from '@syncfusion/ej2-base';
|
||||
import * as de from 'cldr-data/main/de/ca-gregorian.json';
|
||||
import * as numbers from 'cldr-data/main/de/numbers.json';
|
||||
import * as timeZoneNames from 'cldr-data/main/de/timeZoneNames.json';
|
||||
import * as numberingSystems from 'cldr-data/supplemental/numberingSystems.json';
|
||||
LayoutDashboard,
|
||||
Calendar,
|
||||
Boxes,
|
||||
Users,
|
||||
UserSquare,
|
||||
Image,
|
||||
User,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
|
||||
// CLDR-Daten laden
|
||||
loadCldr(
|
||||
(de as unknown as { default: object }).default,
|
||||
(numbers as unknown as { default: object }).default,
|
||||
(timeZoneNames as unknown as { default: object }).default,
|
||||
(numberingSystems as unknown as { default: object }).default
|
||||
);
|
||||
|
||||
// Deutsche Lokalisierung für den Scheduler
|
||||
L10n.load({
|
||||
de: {
|
||||
schedule: {
|
||||
day: 'Tag',
|
||||
week: 'Woche',
|
||||
workWeek: 'Arbeitswoche',
|
||||
month: 'Monat',
|
||||
agenda: 'Agenda',
|
||||
today: 'Heute',
|
||||
noEvents: 'Keine Termine',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Kultur setzen
|
||||
setCulture('de');
|
||||
|
||||
// Ressourcen-Daten
|
||||
const resources = [
|
||||
{ text: 'Raum A', id: 1, color: '#1aaa55' },
|
||||
{ text: 'Raum B', id: 2, color: '#357cd2' },
|
||||
{ text: 'Raum C', id: 3, color: '#7fa900' },
|
||||
const sidebarItems = [
|
||||
{ name: 'Dashboard', path: '/', icon: LayoutDashboard },
|
||||
{ name: 'Termine', path: '/termine', icon: Calendar },
|
||||
{ name: 'Ressourcen', path: '/ressourcen', icon: Boxes },
|
||||
{ name: 'Infoscreens', path: '/Infoscreens', icon: Users },
|
||||
{ name: 'Gruppen', path: '/gruppen', icon: UserSquare },
|
||||
{ name: 'Medien', path: '/medien', icon: Image },
|
||||
{ name: 'Benutzer', path: '/benutzer', icon: User },
|
||||
{ name: 'Einstellungen', path: '/einstellungen', icon: Settings },
|
||||
];
|
||||
|
||||
// Dummy-Termine generieren
|
||||
const now = new Date();
|
||||
const appointments = Array.from({ length: 10 }).map((_, i) => {
|
||||
const dayOffset = Math.floor(i * 1.4); // verteilt auf 14 Tage
|
||||
const start = new Date(now);
|
||||
start.setDate(now.getDate() + dayOffset);
|
||||
start.setHours(9 + (i % 4), 0, 0, 0);
|
||||
const end = new Date(start);
|
||||
end.setHours(start.getHours() + 1);
|
||||
const Layout: React.FC = () => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
return {
|
||||
Id: i + 1,
|
||||
Subject: `Termin ${i + 1}`,
|
||||
StartTime: start,
|
||||
EndTime: end,
|
||||
ResourceId: (i % 3) + 1,
|
||||
Location: resources[i % 3].text,
|
||||
};
|
||||
});
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<div className="p-8 bg-gray-100 min-h-screen">
|
||||
<h1 className="text-2xl font-bold mb-4">Infoscreen Kalendersteuerung</h1>
|
||||
<ScheduleComponent
|
||||
height="650px"
|
||||
locale="de"
|
||||
currentView="TimelineWeek"
|
||||
eventSettings={{ dataSource: appointments }}
|
||||
group={{ resources: ['Räume'] }}
|
||||
<div className="flex min-h-screen">
|
||||
{/* Sidebar */}
|
||||
<aside
|
||||
className={`flex flex-col transition-all duration-300 ${collapsed ? 'w-20' : 'w-30'}`}
|
||||
style={{
|
||||
backgroundColor: '#e5d8c7',
|
||||
color: '#78591c',
|
||||
fontSize: '1.15rem',
|
||||
fontFamily:
|
||||
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif',
|
||||
}}
|
||||
>
|
||||
<ViewsDirective>
|
||||
<ViewDirective option="Week" />
|
||||
<ViewDirective option="TimelineWeek" />
|
||||
<ViewDirective option="Month" />
|
||||
<ViewDirective option="TimelineMonth" />
|
||||
</ViewsDirective>
|
||||
<ResourcesDirective>
|
||||
<ResourceDirective
|
||||
field="ResourceId"
|
||||
title="Räume"
|
||||
name="Räume"
|
||||
allowMultiple={false}
|
||||
dataSource={resources}
|
||||
textField="text"
|
||||
idField="id"
|
||||
colorField="color"
|
||||
<div
|
||||
className="h-20 flex items-center justify-center border-b"
|
||||
style={{ borderColor: '#d6c3a6' }}
|
||||
>
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
className="h-12"
|
||||
style={{ display: collapsed ? 'none' : 'block' }}
|
||||
/>
|
||||
</ResourcesDirective>
|
||||
<Inject services={[Day, Week, WorkWeek, Month, Agenda, TimelineViews, TimelineMonth]} />
|
||||
</ScheduleComponent>
|
||||
</div>
|
||||
<button
|
||||
className="p-2 focus:outline-none hover:bg-[#d6c3a6] transition-colors"
|
||||
onClick={() => setCollapsed(!collapsed)}
|
||||
aria-label={collapsed ? 'Sidebar ausklappen' : 'Sidebar einklappen'}
|
||||
>
|
||||
<span style={{ fontSize: 20 }}>{collapsed ? '▶' : '◀'}</span>
|
||||
</button>
|
||||
<nav className="flex-1 mt-4">
|
||||
{sidebarItems.map(item => {
|
||||
const Icon = item.icon;
|
||||
return (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
className="flex items-center gap-3 px-6 py-3 transition-colors no-underline"
|
||||
style={{
|
||||
color: '#78591c',
|
||||
textDecoration: 'none',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
title={collapsed ? item.name : undefined}
|
||||
onMouseEnter={e => {
|
||||
e.currentTarget.style.backgroundColor = '#78591c';
|
||||
e.currentTarget.style.color = '#e5d8c7';
|
||||
}}
|
||||
onMouseLeave={e => {
|
||||
e.currentTarget.style.backgroundColor = '';
|
||||
e.currentTarget.style.color = '#78591c';
|
||||
}}
|
||||
>
|
||||
<Icon size={22} />
|
||||
{!collapsed && item.name}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
{/* Main Content */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* Header */}
|
||||
<header
|
||||
className="flex items-center px-8 shadow"
|
||||
style={{
|
||||
backgroundColor: '#e5d8c7',
|
||||
color: '#78591c',
|
||||
height: 'calc(48px + 20px)',
|
||||
fontSize: '1.15rem',
|
||||
fontFamily:
|
||||
'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src={logo}
|
||||
alt="Logo"
|
||||
className="h-12 mr-4"
|
||||
style={{ marginTop: 10, marginBottom: 10 }}
|
||||
/>
|
||||
<span className="text-2xl font-bold mr-8">Infoscreen-Management</span>
|
||||
<span className="ml-auto" style={{ color: '#78591c' }}>
|
||||
[Organisationsname]
|
||||
</span>
|
||||
</header>
|
||||
<main className="flex-1 p-8 bg-gray-100">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FC = () => (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Dashboard />} />
|
||||
<Route path="termine" element={<Appointments />} />
|
||||
<Route path="ressourcen" element={<Ressourcen />} />
|
||||
<Route path="Infoscreens" element={<Infoscreens />} />
|
||||
<Route path="gruppen" element={<Gruppen />} />
|
||||
<Route path="medien" element={<Medien />} />
|
||||
<Route path="benutzer" element={<Benutzer />} />
|
||||
<Route path="einstellungen" element={<Einstellungen />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
|
||||
export default App;
|
||||
|
||||
// Dummy Components (können in eigene Dateien ausgelagert werden)
|
||||
import Dashboard from './dashboard';
|
||||
import Appointments from './appointments';
|
||||
import Ressourcen from './ressourcen';
|
||||
import Infoscreens from './clients';
|
||||
import Gruppen from './gruppen';
|
||||
import Medien from './medien';
|
||||
import Benutzer from './benutzer';
|
||||
import Einstellungen from './einstellungen';
|
||||
|
||||
17
dashboard/src/apiClients.ts
Normal file
17
dashboard/src/apiClients.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// Funktion zum Laden der Clients von der API
|
||||
|
||||
export interface Client {
|
||||
uuid: string;
|
||||
location: string;
|
||||
hardware_hash: string;
|
||||
ip_address: string;
|
||||
last_alive: string | null;
|
||||
}
|
||||
|
||||
export async function fetchClients(): Promise<Client[]> {
|
||||
const response = await fetch('/api/clients');
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Laden der Clients');
|
||||
}
|
||||
return await response.json();
|
||||
}
|
||||
106
dashboard/src/appointments.tsx
Normal file
106
dashboard/src/appointments.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
ScheduleComponent,
|
||||
Day,
|
||||
Week,
|
||||
WorkWeek,
|
||||
Month,
|
||||
Agenda,
|
||||
TimelineViews,
|
||||
TimelineMonth,
|
||||
Inject,
|
||||
ViewsDirective,
|
||||
ViewDirective,
|
||||
ResourcesDirective,
|
||||
ResourceDirective,
|
||||
} from '@syncfusion/ej2-react-schedule';
|
||||
import { L10n, loadCldr, setCulture } from '@syncfusion/ej2-base';
|
||||
import * as de from 'cldr-data/main/de/ca-gregorian.json';
|
||||
import * as numbers from 'cldr-data/main/de/numbers.json';
|
||||
import * as timeZoneNames from 'cldr-data/main/de/timeZoneNames.json';
|
||||
import * as numberingSystems from 'cldr-data/supplemental/numberingSystems.json';
|
||||
|
||||
// CLDR-Daten laden
|
||||
loadCldr(
|
||||
(de as unknown as { default: object }).default,
|
||||
(numbers as unknown as { default: object }).default,
|
||||
(timeZoneNames as unknown as { default: object }).default,
|
||||
(numberingSystems as unknown as { default: object }).default
|
||||
);
|
||||
|
||||
// Deutsche Lokalisierung für den Scheduler
|
||||
L10n.load({
|
||||
de: {
|
||||
schedule: {
|
||||
day: 'Tag',
|
||||
week: 'Woche',
|
||||
workWeek: 'Arbeitswoche',
|
||||
month: 'Monat',
|
||||
agenda: 'Agenda',
|
||||
today: 'Heute',
|
||||
noEvents: 'Keine Termine',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Kultur setzen
|
||||
setCulture('de');
|
||||
|
||||
// Ressourcen-Daten
|
||||
const resources = [
|
||||
{ text: 'Raum A', id: 1, color: '#1aaa55' },
|
||||
{ text: 'Raum B', id: 2, color: '#357cd2' },
|
||||
{ text: 'Raum C', id: 3, color: '#7fa900' },
|
||||
];
|
||||
|
||||
// Dummy-Termine generieren
|
||||
const now = new Date();
|
||||
const appointments = Array.from({ length: 10 }).map((_, i) => {
|
||||
const dayOffset = Math.floor(i * 1.4); // verteilt auf 14 Tage
|
||||
const start = new Date(now);
|
||||
start.setDate(now.getDate() + dayOffset);
|
||||
start.setHours(9 + (i % 4), 0, 0, 0);
|
||||
const end = new Date(start);
|
||||
end.setHours(start.getHours() + 1);
|
||||
|
||||
return {
|
||||
Id: i + 1,
|
||||
Subject: `Termin ${i + 1}`,
|
||||
StartTime: start,
|
||||
EndTime: end,
|
||||
ResourceId: (i % 3) + 1,
|
||||
Location: resources[i % 3].text,
|
||||
};
|
||||
});
|
||||
|
||||
const Appointments: React.FC = () => (
|
||||
<ScheduleComponent
|
||||
height="650px"
|
||||
locale="de"
|
||||
currentView="TimelineWeek"
|
||||
eventSettings={{ dataSource: appointments }}
|
||||
group={{ resources: ['Räume'] }}
|
||||
>
|
||||
<ViewsDirective>
|
||||
<ViewDirective option="Week" />
|
||||
<ViewDirective option="TimelineWeek" />
|
||||
<ViewDirective option="Month" />
|
||||
<ViewDirective option="TimelineMonth" />
|
||||
</ViewsDirective>
|
||||
<ResourcesDirective>
|
||||
<ResourceDirective
|
||||
field="ResourceId"
|
||||
title="Räume"
|
||||
name="Räume"
|
||||
allowMultiple={false}
|
||||
dataSource={resources}
|
||||
textField="text"
|
||||
idField="id"
|
||||
colorField="color"
|
||||
/>
|
||||
</ResourcesDirective>
|
||||
<Inject services={[Day, Week, WorkWeek, Month, Agenda, TimelineViews, TimelineMonth]} />
|
||||
</ScheduleComponent>
|
||||
);
|
||||
|
||||
export default Appointments;
|
||||
BIN
dashboard/src/assets/TAA_Logo.png
Normal file
BIN
dashboard/src/assets/TAA_Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
BIN
dashboard/src/assets/logo.png
Normal file
BIN
dashboard/src/assets/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
8
dashboard/src/benutzer.tsx
Normal file
8
dashboard/src/benutzer.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Benutzer: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Benutzer</h2>
|
||||
<p>Willkommen im Infoscreen-Management Benutzer.</p>
|
||||
</div>
|
||||
);
|
||||
export default Benutzer;
|
||||
8
dashboard/src/clients.tsx
Normal file
8
dashboard/src/clients.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Infoscreens: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Infoscreens</h2>
|
||||
<p>Willkommen im Infoscreen-Management Infoscreens.</p>
|
||||
</div>
|
||||
);
|
||||
export default Infoscreens;
|
||||
46
dashboard/src/dashboard.tsx
Normal file
46
dashboard/src/dashboard.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { fetchClients } from './apiClients';
|
||||
import type { Client } from './apiClients';
|
||||
|
||||
const Dashboard: React.FC = () => {
|
||||
const [clients, setClients] = useState<Client[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchClients().then(setClients).catch(console.error);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<header className="mb-8 pb-4 border-b-2 border-[#d6c3a6]">
|
||||
<h2 className="text-3xl font-extrabold mb-2">Dashboard</h2>
|
||||
<p className="text-lg">Willkommen im Infoscreen-Management Dashboard.</p>
|
||||
</header>
|
||||
<h3 className="text-lg font-semibold mt-6 mb-4">Clients</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{clients.map(client => (
|
||||
<div key={client.uuid} className="bg-white rounded shadow p-4 flex flex-col items-center">
|
||||
<h4 className="text-lg font-bold mb-2">{client.location || 'Unbekannter Standort'}</h4>
|
||||
<img
|
||||
src={`/screenshots/${client.uuid}`}
|
||||
alt={`Screenshot ${client.location}`}
|
||||
className="w-full h-48 object-contain bg-gray-100 mb-2"
|
||||
onError={e => (e.currentTarget.style.display = 'none')}
|
||||
/>
|
||||
<div className="text-sm text-gray-700 mb-1">
|
||||
<span className="font-semibold">IP:</span> {client.ip_address}
|
||||
</div>
|
||||
<div className="text-sm text-gray-700">
|
||||
<span className="font-semibold">Letztes Lebenszeichen:</span>{' '}
|
||||
{client.last_alive ? new Date(client.last_alive).toLocaleString() : '-'}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{clients.length === 0 && (
|
||||
<div className="col-span-full text-center text-gray-400">Keine Clients gefunden.</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
8
dashboard/src/einstellungen.tsx
Normal file
8
dashboard/src/einstellungen.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Einstellungen: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Einstellungen</h2>
|
||||
<p>Willkommen im Infoscreen-Management Einstellungen.</p>
|
||||
</div>
|
||||
);
|
||||
export default Einstellungen;
|
||||
8
dashboard/src/gruppen.tsx
Normal file
8
dashboard/src/gruppen.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Gruppen: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Gruppen</h2>
|
||||
<p>Willkommen im Infoscreen-Management Gruppen.</p>
|
||||
</div>
|
||||
);
|
||||
export default Gruppen;
|
||||
8
dashboard/src/medien.tsx
Normal file
8
dashboard/src/medien.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Medien: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Medien</h2>
|
||||
<p>Willkommen im Infoscreen-Management Medien.</p>
|
||||
</div>
|
||||
);
|
||||
export default Medien;
|
||||
8
dashboard/src/ressourcen.tsx
Normal file
8
dashboard/src/ressourcen.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
const Ressourcen: React.FC = () => (
|
||||
<div>
|
||||
<h2 className="text-xl font-bold mb-4">Ressourcen</h2>
|
||||
<p>Willkommen im Infoscreen-Management Ressourcen.</p>
|
||||
</div>
|
||||
);
|
||||
export default Ressourcen;
|
||||
0
dashboard/src/termine.tsx
Normal file
0
dashboard/src/termine.tsx
Normal file
Reference in New Issue
Block a user