different colors for different groups

This commit is contained in:
2025-07-01 18:17:05 +00:00
parent 95b823ae3d
commit 37332249aa
6 changed files with 184 additions and 25 deletions

View File

@@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@syncfusion/ej2-react-buttons": "^30.1.37", "@syncfusion/ej2-react-buttons": "^30.1.37",
"@syncfusion/ej2-react-calendars": "^30.1.37", "@syncfusion/ej2-react-calendars": "^30.1.37",
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
"@syncfusion/ej2-react-grids": "^30.1.37", "@syncfusion/ej2-react-grids": "^30.1.37",
"@syncfusion/ej2-react-kanban": "^30.1.37", "@syncfusion/ej2-react-kanban": "^30.1.37",
"@syncfusion/ej2-react-notifications": "^30.1.37", "@syncfusion/ej2-react-notifications": "^30.1.37",
@@ -1216,6 +1217,17 @@
"@syncfusion/ej2-react-base": "~30.1.37" "@syncfusion/ej2-react-base": "~30.1.37"
} }
}, },
"node_modules/@syncfusion/ej2-react-dropdowns": {
"version": "30.1.37",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-dropdowns/-/ej2-react-dropdowns-30.1.37.tgz",
"integrity": "sha512-4mgqlsC8T/9YVMCo1LZIZVUfVY3llv4hndm5MlYIOSaKeVqpfbu9iUvTOZdg4NIEruaK04BGnEemO0TZkMi4Rg==",
"license": "SEE LICENSE IN license",
"dependencies": {
"@syncfusion/ej2-base": "~30.1.37",
"@syncfusion/ej2-dropdowns": "30.1.37",
"@syncfusion/ej2-react-base": "~30.1.37"
}
},
"node_modules/@syncfusion/ej2-react-grids": { "node_modules/@syncfusion/ej2-react-grids": {
"version": "30.1.37", "version": "30.1.37",
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-grids/-/ej2-react-grids-30.1.37.tgz", "resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-grids/-/ej2-react-grids-30.1.37.tgz",

View File

@@ -12,6 +12,7 @@
"dependencies": { "dependencies": {
"@syncfusion/ej2-react-buttons": "^30.1.37", "@syncfusion/ej2-react-buttons": "^30.1.37",
"@syncfusion/ej2-react-calendars": "^30.1.37", "@syncfusion/ej2-react-calendars": "^30.1.37",
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
"@syncfusion/ej2-react-grids": "^30.1.37", "@syncfusion/ej2-react-grids": "^30.1.37",
"@syncfusion/ej2-react-kanban": "^30.1.37", "@syncfusion/ej2-react-kanban": "^30.1.37",
"@syncfusion/ej2-react-notifications": "^30.1.37", "@syncfusion/ej2-react-notifications": "^30.1.37",

View File

@@ -21,8 +21,8 @@ const sidebarItems = [
{ name: 'Dashboard', path: '/', icon: LayoutDashboard }, { name: 'Dashboard', path: '/', icon: LayoutDashboard },
{ name: 'Termine', path: '/termine', icon: Calendar }, { name: 'Termine', path: '/termine', icon: Calendar },
{ name: 'Ressourcen', path: '/ressourcen', icon: Boxes }, { name: 'Ressourcen', path: '/ressourcen', icon: Boxes },
{ name: 'Raumgruppen', path: '/infoscr_groups', icon: MonitorDotIcon },
{ name: 'Infoscreens', path: '/Infoscreens', icon: Monitor }, { name: 'Infoscreens', path: '/Infoscreens', icon: Monitor },
{ name: 'Gruppen', path: '/infoscr_groups', icon: MonitorDotIcon },
{ name: 'Medien', path: '/medien', icon: Image }, { name: 'Medien', path: '/medien', icon: Image },
{ name: 'Benutzer', path: '/benutzer', icon: User }, { name: 'Benutzer', path: '/benutzer', icon: User },
{ name: 'Einstellungen', path: '/einstellungen', icon: Settings }, { name: 'Einstellungen', path: '/einstellungen', icon: Settings },

View File

@@ -8,10 +8,9 @@ export interface Event {
extendedProps: Record<string, unknown>; extendedProps: Record<string, unknown>;
} }
export async function fetchEvents(): Promise<Event[]> { export async function fetchEvents(groupId: string) {
const response = await fetch('/api/events'); const res = await fetch(`/api/events?group_id=${encodeURIComponent(groupId)}`);
if (!response.ok) { const data = await res.json();
throw new Error('Fehler beim Laden der Events'); if (!res.ok || data.error) throw new Error(data.error || 'Fehler beim Laden der Termine');
} return data;
return await response.json();
} }

View File

@@ -10,9 +10,36 @@ import {
ViewsDirective, ViewsDirective,
ViewDirective, ViewDirective,
} from '@syncfusion/ej2-react-schedule'; } from '@syncfusion/ej2-react-schedule';
import { DropDownListComponent } from '@syncfusion/ej2-react-dropdowns';
import { L10n, loadCldr, setCulture } from '@syncfusion/ej2-base'; import { L10n, loadCldr, setCulture } from '@syncfusion/ej2-base';
import type { EventRenderedArgs } from '@syncfusion/ej2-react-schedule';
import { fetchEvents } from './apiEvents'; import { fetchEvents } from './apiEvents';
import type { Event } from './apiEvents'; import { fetchGroups } from './apiGroups';
import { getGroupColor } from './groupColors';
// Typ für Gruppe ergänzen
type Group = {
id: string;
name: string;
};
// Typ für Event hinzufügen
type Event = {
Id: string;
Subject: string;
StartTime: Date;
EndTime: Date;
IsAllDay: boolean;
};
type RawEvent = {
Id: string;
Subject: string;
StartTime: string;
EndTime: string;
IsAllDay: boolean;
};
import * as de from 'cldr-data/main/de/ca-gregorian.json'; import * as de from 'cldr-data/main/de/ca-gregorian.json';
import * as numbers from 'cldr-data/main/de/numbers.json'; import * as numbers from 'cldr-data/main/de/numbers.json';
import * as timeZoneNames from 'cldr-data/main/de/timeZoneNames.json'; import * as timeZoneNames from 'cldr-data/main/de/timeZoneNames.json';
@@ -45,29 +72,97 @@ L10n.load({
setCulture('de'); setCulture('de');
const Appointments: React.FC = () => { const Appointments: React.FC = () => {
const [groups, setGroups] = useState<Group[]>([]);
const [selectedGroupId, setSelectedGroupId] = useState<string | null>(null);
const [events, setEvents] = useState<Event[]>([]); const [events, setEvents] = useState<Event[]>([]);
// Gruppen laden
useEffect(() => { useEffect(() => {
fetchEvents().then(setEvents).catch(console.error); fetchGroups()
.then(data => {
// Nur Gruppen mit id berücksichtigen (nicht zugeordnet ignorieren)
const filtered = Array.isArray(data)
? data.filter(g => g.id && g.name && g.name !== 'nicht zugeordnet')
: [];
setGroups(filtered);
if (filtered.length > 0) setSelectedGroupId(filtered[0].id);
})
.catch(console.error);
}, []); }, []);
// Termine für die ausgewählte Gruppe laden
useEffect(() => {
if (selectedGroupId) {
fetchEvents(selectedGroupId)
.then((data: RawEvent[]) => {
const mapped: Event[] = data.map(e => ({
Id: e.Id,
Subject: e.Subject,
StartTime: new Date(e.StartTime),
EndTime: new Date(e.EndTime),
IsAllDay: e.IsAllDay,
}));
setEvents(mapped);
})
.catch(console.error);
} else {
setEvents([]);
}
}, [selectedGroupId]);
console.log('Aktuelle Events:', events);
return ( return (
<ScheduleComponent <div>
height="650px" <h1 className="text-2xl font-bold mb-4">Terminmanagement</h1>
locale="de" <div
currentView="Week" style={{ marginBottom: 16, maxWidth: 500, display: 'flex', alignItems: 'center', gap: 12 }}
eventSettings={{ dataSource: events }} >
firstDayOfWeek={1} // Montag als ersten Wochentag setzen <label htmlFor="groupDropdown" className="mb-0 mr-2" style={{ whiteSpace: 'nowrap' }}>
> Raumgruppe auswählen:
<ViewsDirective> </label>
<ViewDirective option="Day" /> <DropDownListComponent
<ViewDirective option="Week" /> id="groupDropdown"
<ViewDirective option="WorkWeek" /> dataSource={groups}
<ViewDirective option="Month" /> fields={{ text: 'name', value: 'id' }}
<ViewDirective option="Agenda" /> placeholder="Gruppe auswählen"
</ViewsDirective> value={selectedGroupId}
<Inject services={[Day, Week, WorkWeek, Month, Agenda]} /> change={e => {
</ScheduleComponent> setEvents([]); // Events sofort leeren
setSelectedGroupId(e.value as string);
}}
style={{ flex: 1 }}
/>
</div>
<ScheduleComponent
key={selectedGroupId} // <- erzwingt komplettes Neurendern bei Gruppenwechsel
height="750px"
locale="de"
currentView="Week"
eventSettings={{
dataSource: events,
}}
eventRendered={(args: EventRenderedArgs) => {
// Farbe basierend auf der Gruppe setzen
if (selectedGroupId && args.data && args.data.Id) {
const groupColor = getGroupColor(selectedGroupId, groups);
if (groupColor) {
args.element.style.backgroundColor = groupColor;
}
}
}}
firstDayOfWeek={1}
>
<ViewsDirective>
<ViewDirective option="Day" />
<ViewDirective option="Week" />
<ViewDirective option="WorkWeek" />
<ViewDirective option="Month" />
<ViewDirective option="Agenda" />
</ViewsDirective>
<Inject services={[Day, Week, WorkWeek, Month, Agenda]} />
</ScheduleComponent>
</div>
); );
}; };

View File

@@ -0,0 +1,52 @@
// 20 gut unterscheidbare Farben für Gruppen
export const groupColorPalette: string[] = [
'#1E90FF', // Blau
'#28A745', // Grün
'#FFC107', // Gelb
'#DC3545', // Rot
'#6F42C1', // Lila
'#20C997', // Türkis
'#FD7E14', // Orange
'#6610F2', // Violett
'#17A2B8', // Cyan
'#E83E8C', // Pink
'#FF5733', // Koralle
'#2ECC40', // Hellgrün
'#FFB300', // Dunkelgelb
'#00796B', // Petrol
'#C70039', // Dunkelrot
'#8D6E63', // Braun
'#607D8B', // Grau-Blau
'#00B8D4', // Türkisblau
'#FF6F00', // Dunkelorange
'#9C27B0', // Dunkellila
];
// Gibt für eine Gruppen-ID immer dieselbe Farbe zurück (Index basiert auf Gruppenliste)
export function getGroupColor(groupId: string, groups: { id: string }[]): string {
const colorPalette = [
'#1E90FF',
'#28A745',
'#FFC107',
'#DC3545',
'#6F42C1',
'#20C997',
'#FD7E14',
'#6610F2',
'#17A2B8',
'#E83E8C',
'#FF5733',
'#2ECC40',
'#FFB300',
'#00796B',
'#C70039',
'#8D6E63',
'#607D8B',
'#00B8D4',
'#FF6F00',
'#9C27B0',
];
const idx = groups.findIndex(g => g.id === groupId);
return colorPalette[idx % colorPalette.length];
}