introduce icons in events

This commit is contained in:
2025-07-24 14:11:27 +00:00
parent 49e9f9eade
commit 270bad5980
4 changed files with 132 additions and 19 deletions

View File

@@ -23,6 +23,12 @@ import { getGroupColor } from './groupColors';
import { deleteEvent } from './apiEvents';
import CustomEventModal from './components/CustomEventModal';
import { fetchMediaById } from './apiClients';
import { Presentation, Globe, Video, MessageSquare, School } from 'lucide-react';
import { renderToStaticMarkup } from 'react-dom/server';
import caGregorian from 'cldr-data/main/de/ca-gregorian.json';
import numbers from 'cldr-data/main/de/numbers.json';
import timeZoneNames from 'cldr-data/main/de/timeZoneNames.json';
import numberingSystems from 'cldr-data/supplemental/numberingSystems.json';
// Typ für Gruppe ergänzen
type Group = {
@@ -37,9 +43,11 @@ type Event = {
StartTime: Date;
EndTime: Date;
IsAllDay: boolean;
MediaId?: string | number; // Nur die MediaId wird gespeichert!
MediaId?: string | number;
SlideshowInterval?: number;
WebsiteUrl?: string;
Icon?: string; // <--- Icon ergänzen!
Type?: string; // <--- Typ ergänzen, falls benötigt
};
type RawEvent = {
@@ -48,21 +56,13 @@ type RawEvent = {
StartTime: string;
EndTime: string;
IsAllDay: boolean;
MediaId?: string | number; // Nur die MediaId wird gespeichert!
MediaId?: string | number;
Icon?: string; // <--- Icon ergänzen!
Type?: string;
};
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
);
// CLDR-Daten laden (direkt die JSON-Objekte übergeben)
loadCldr(caGregorian, numbers, timeZoneNames, numberingSystems);
// Deutsche Lokalisierung für den Scheduler
L10n.load({
@@ -100,6 +100,37 @@ L10n.load({
// Kultur setzen
setCulture('de');
// Mapping für Lucide-Icons
const iconMap: Record<string, React.ElementType> = {
Presentation,
Globe,
Video,
MessageSquare,
School,
};
const eventTemplate = (event: Event) => {
const IconComponent = iconMap[event.Icon ?? ''] || null;
// Zeitangabe formatieren
const start = event.StartTime instanceof Date ? event.StartTime : new Date(event.StartTime);
const end = event.EndTime instanceof Date ? event.EndTime : new Date(event.EndTime);
const timeString = `${start.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} - ${end.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}`;
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-start' }}>
<div style={{ display: 'flex', alignItems: 'center', color: '#fff', marginBottom: 2 }}>
{IconComponent && (
<span style={{ verticalAlign: 'middle', display: 'inline-block', marginRight: 6 }}>
<IconComponent size={18} color="#fff" />
</span>
)}
<span style={{ marginTop: 3 }}>{event.Subject}</span>
</div>
<div style={{ fontSize: '0.95em', color: '#fff', marginTop: -2 }}>{timeString}</div>
</div>
);
};
const Appointments: React.FC = () => {
const [groups, setGroups] = useState<Group[]>([]);
const [selectedGroupId, setSelectedGroupId] = useState<string | null>(null);
@@ -137,6 +168,8 @@ const Appointments: React.FC = () => {
EndTime: new Date(e.EndTime.endsWith('Z') ? e.EndTime : e.EndTime + 'Z'),
IsAllDay: e.IsAllDay,
MediaId: e.MediaId,
Icon: e.Icon, // <--- Icon übernehmen!
Type: e.Type, // <--- Typ übernehmen!
}));
setEvents(mapped);
} catch (err) {
@@ -247,6 +280,7 @@ const Appointments: React.FC = () => {
currentView="Week"
eventSettings={{
dataSource: events,
template: eventTemplate, // <--- Hier das Template setzen!
}}
cellClick={args => {
// args.startTime und args.endTime sind Date-Objekte
@@ -317,14 +351,44 @@ const Appointments: React.FC = () => {
if (selectedGroupId && args.data && args.data.Id) {
const groupColor = getGroupColor(selectedGroupId, groups);
const now = new Date();
let IconComponent: React.ElementType | null = null;
switch (args.data.Type) {
case 'presentation':
IconComponent = Presentation;
break;
case 'website':
IconComponent = Globe;
break;
case 'video':
IconComponent = Video;
break;
case 'message':
IconComponent = MessageSquare;
break;
case 'webuntis':
IconComponent = School;
break;
default:
IconComponent = null;
}
// Nur .e-subject verwenden!
const titleElement = args.element.querySelector('.e-subject');
if (titleElement && IconComponent) {
const svgString = renderToStaticMarkup(<IconComponent size={18} color="#78591c" />);
// Immer nur den reinen Text nehmen, kein vorhandenes Icon!
const subjectText = (titleElement as HTMLElement).textContent ?? '';
(titleElement as HTMLElement).innerHTML =
`<span style="vertical-align:middle;display:inline-block;margin-right:6px;">${svgString}</span>` +
subjectText;
}
// Vergangene Termine: Raumgruppenfarbe mit Transparenz
if (args.data.EndTime && args.data.EndTime < now) {
// Vergangene Termine: Raumgruppenfarbe mit Transparenz und grauer Schrift
args.element.style.backgroundColor = groupColor
? `${groupColor}` // 100 = ~100% Transparenz für hex-Farben
: '#f3f3f3';
args.element.style.backgroundColor = groupColor ? `${groupColor}` : '#f3f3f3';
args.element.style.color = '';
} else if (groupColor) {
// Aktuelle/future Termine: normale Raumgruppenfarbe
args.element.style.backgroundColor = groupColor;
args.element.style.color = '';
}