introduce icons in events
This commit is contained in:
@@ -23,6 +23,12 @@ import { getGroupColor } from './groupColors';
|
|||||||
import { deleteEvent } from './apiEvents';
|
import { deleteEvent } from './apiEvents';
|
||||||
import CustomEventModal from './components/CustomEventModal';
|
import CustomEventModal from './components/CustomEventModal';
|
||||||
import { fetchMediaById } from './apiClients';
|
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
|
// Typ für Gruppe ergänzen
|
||||||
type Group = {
|
type Group = {
|
||||||
@@ -37,9 +43,11 @@ type Event = {
|
|||||||
StartTime: Date;
|
StartTime: Date;
|
||||||
EndTime: Date;
|
EndTime: Date;
|
||||||
IsAllDay: boolean;
|
IsAllDay: boolean;
|
||||||
MediaId?: string | number; // Nur die MediaId wird gespeichert!
|
MediaId?: string | number;
|
||||||
SlideshowInterval?: number;
|
SlideshowInterval?: number;
|
||||||
WebsiteUrl?: string;
|
WebsiteUrl?: string;
|
||||||
|
Icon?: string; // <--- Icon ergänzen!
|
||||||
|
Type?: string; // <--- Typ ergänzen, falls benötigt
|
||||||
};
|
};
|
||||||
|
|
||||||
type RawEvent = {
|
type RawEvent = {
|
||||||
@@ -48,21 +56,13 @@ type RawEvent = {
|
|||||||
StartTime: string;
|
StartTime: string;
|
||||||
EndTime: string;
|
EndTime: string;
|
||||||
IsAllDay: boolean;
|
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';
|
// CLDR-Daten laden (direkt die JSON-Objekte übergeben)
|
||||||
import * as numbers from 'cldr-data/main/de/numbers.json';
|
loadCldr(caGregorian, numbers, timeZoneNames, numberingSystems);
|
||||||
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
|
// Deutsche Lokalisierung für den Scheduler
|
||||||
L10n.load({
|
L10n.load({
|
||||||
@@ -100,6 +100,37 @@ L10n.load({
|
|||||||
// Kultur setzen
|
// Kultur setzen
|
||||||
setCulture('de');
|
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 Appointments: React.FC = () => {
|
||||||
const [groups, setGroups] = useState<Group[]>([]);
|
const [groups, setGroups] = useState<Group[]>([]);
|
||||||
const [selectedGroupId, setSelectedGroupId] = useState<string | null>(null);
|
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'),
|
EndTime: new Date(e.EndTime.endsWith('Z') ? e.EndTime : e.EndTime + 'Z'),
|
||||||
IsAllDay: e.IsAllDay,
|
IsAllDay: e.IsAllDay,
|
||||||
MediaId: e.MediaId,
|
MediaId: e.MediaId,
|
||||||
|
Icon: e.Icon, // <--- Icon übernehmen!
|
||||||
|
Type: e.Type, // <--- Typ übernehmen!
|
||||||
}));
|
}));
|
||||||
setEvents(mapped);
|
setEvents(mapped);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -247,6 +280,7 @@ const Appointments: React.FC = () => {
|
|||||||
currentView="Week"
|
currentView="Week"
|
||||||
eventSettings={{
|
eventSettings={{
|
||||||
dataSource: events,
|
dataSource: events,
|
||||||
|
template: eventTemplate, // <--- Hier das Template setzen!
|
||||||
}}
|
}}
|
||||||
cellClick={args => {
|
cellClick={args => {
|
||||||
// args.startTime und args.endTime sind Date-Objekte
|
// args.startTime und args.endTime sind Date-Objekte
|
||||||
@@ -317,14 +351,44 @@ const Appointments: React.FC = () => {
|
|||||||
if (selectedGroupId && args.data && args.data.Id) {
|
if (selectedGroupId && args.data && args.data.Id) {
|
||||||
const groupColor = getGroupColor(selectedGroupId, groups);
|
const groupColor = getGroupColor(selectedGroupId, groups);
|
||||||
const now = new Date();
|
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) {
|
if (args.data.EndTime && args.data.EndTime < now) {
|
||||||
// Vergangene Termine: Raumgruppenfarbe mit Transparenz und grauer Schrift
|
args.element.style.backgroundColor = groupColor ? `${groupColor}` : '#f3f3f3';
|
||||||
args.element.style.backgroundColor = groupColor
|
|
||||||
? `${groupColor}` // 100 = ~100% Transparenz für hex-Farben
|
|
||||||
: '#f3f3f3';
|
|
||||||
args.element.style.color = '';
|
args.element.style.color = '';
|
||||||
} else if (groupColor) {
|
} else if (groupColor) {
|
||||||
// Aktuelle/future Termine: normale Raumgruppenfarbe
|
|
||||||
args.element.style.backgroundColor = groupColor;
|
args.element.style.backgroundColor = groupColor;
|
||||||
args.element.style.color = '';
|
args.element.style.color = '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,6 +93,8 @@ class MediaType(enum.Enum):
|
|||||||
svg = "svg"
|
svg = "svg"
|
||||||
# HTML-Mitteilung
|
# HTML-Mitteilung
|
||||||
html = "html"
|
html = "html"
|
||||||
|
# Webseiten
|
||||||
|
website = "website"
|
||||||
|
|
||||||
|
|
||||||
class Event(Base):
|
class Event(Base):
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""Add website to MediaType enum
|
||||||
|
|
||||||
|
Revision ID: e6eaede720aa
|
||||||
|
Revises: 0c47280d3e2d
|
||||||
|
Create Date: 2025-07-24 13:40:50.553863
|
||||||
|
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = 'e6eaede720aa'
|
||||||
|
down_revision: Union[str, None] = '0c47280d3e2d'
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.execute(
|
||||||
|
"ALTER TABLE event_media MODIFY COLUMN media_type ENUM('pdf','ppt','pptx','odp','mp4','avi','mkv','mov','wmv','flv','webm','mpg','mpeg','ogv','jpg','jpeg','png','gif','bmp','tiff','svg','html','website') NOT NULL;"
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
pass
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -9,6 +9,17 @@ sys.path.append('/workspace')
|
|||||||
events_bp = Blueprint("events", __name__, url_prefix="/api/events")
|
events_bp = Blueprint("events", __name__, url_prefix="/api/events")
|
||||||
|
|
||||||
|
|
||||||
|
def get_icon_for_type(event_type):
|
||||||
|
# Lucide-Icon-Namen als String
|
||||||
|
return {
|
||||||
|
"presentation": "Presentation", # <--- geändert!
|
||||||
|
"website": "Globe",
|
||||||
|
"video": "Video",
|
||||||
|
"message": "MessageSquare",
|
||||||
|
"webuntis": "School",
|
||||||
|
}.get(event_type, "")
|
||||||
|
|
||||||
|
|
||||||
@events_bp.route("", methods=["GET"])
|
@events_bp.route("", methods=["GET"])
|
||||||
def get_events():
|
def get_events():
|
||||||
session = Session()
|
session = Session()
|
||||||
@@ -45,6 +56,8 @@ def get_events():
|
|||||||
"EndTime": e.end.isoformat() if e.end else None,
|
"EndTime": e.end.isoformat() if e.end else None,
|
||||||
"IsAllDay": False,
|
"IsAllDay": False,
|
||||||
"MediaId": e.event_media_id,
|
"MediaId": e.event_media_id,
|
||||||
|
"Type": e.event_type.value if e.event_type else None, # <-- Enum zu String!
|
||||||
|
"Icon": get_icon_for_type(e.event_type.value if e.event_type else None),
|
||||||
})
|
})
|
||||||
session.close()
|
session.close()
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|||||||
Reference in New Issue
Block a user