prevent saving past events

add function to show inactive events
This commit is contained in:
2025-07-24 09:35:57 +00:00
parent 4e6451ce80
commit 8bbda836b3
4 changed files with 135 additions and 43 deletions

View File

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

View File

@@ -45,8 +45,6 @@ type RawEvent = {
EndTime: string;
IsAllDay: boolean;
MediaId?: string | number; // Nur die MediaId wird gespeichert!
SlideshowInterval?: number;
WebsiteUrl?: string;
};
import * as de from 'cldr-data/main/de/ca-gregorian.json';
@@ -106,6 +104,7 @@ const Appointments: React.FC = () => {
const [modalInitialData, setModalInitialData] = useState({});
const [schedulerKey, setSchedulerKey] = useState(0);
const [editMode, setEditMode] = useState(false); // NEU: Editiermodus
const [showInactive, setShowInactive] = React.useState(false);
// Gruppen laden
useEffect(() => {
@@ -119,26 +118,36 @@ const Appointments: React.FC = () => {
.catch(console.error);
}, []);
// Termine für die ausgewählte Gruppe laden
useEffect(() => {
// fetchAndSetEvents als useCallback definieren, damit die Dependency korrekt ist:
const fetchAndSetEvents = React.useCallback(async () => {
if (!selectedGroupId) {
setEvents([]);
return;
}
try {
const data = await fetchEvents(selectedGroupId, showInactive); // selectedGroupId ist jetzt garantiert string
const mapped: Event[] = data.map((e: RawEvent) => ({
Id: e.Id,
Subject: e.Subject,
StartTime: new Date(e.StartTime.endsWith('Z') ? e.StartTime : e.StartTime + 'Z'),
EndTime: new Date(e.EndTime.endsWith('Z') ? e.EndTime : e.EndTime + 'Z'),
IsAllDay: e.IsAllDay,
MediaId: e.MediaId,
}));
setEvents(mapped);
} catch (err) {
console.error(err);
}
}, [selectedGroupId, showInactive]);
React.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.endsWith('Z') ? e.StartTime : e.StartTime + 'Z'),
EndTime: new Date(e.EndTime.endsWith('Z') ? e.EndTime : e.EndTime + 'Z'),
IsAllDay: e.IsAllDay,
MediaId: e.MediaId,
}));
setEvents(mapped);
})
.catch(console.error);
// selectedGroupId kann null sein, fetchEvents erwartet aber string
fetchAndSetEvents();
} else {
setEvents([]);
}
}, [selectedGroupId]);
}, [selectedGroupId, showInactive, fetchAndSetEvents]);
return (
<div>
@@ -155,9 +164,10 @@ const Appointments: React.FC = () => {
fields={{ text: 'name', value: 'id' }}
placeholder="Gruppe auswählen"
value={selectedGroupId}
change={e => {
change={(e: { value: string }) => {
// <--- Typ für e ergänzt
setEvents([]); // Events sofort leeren
setSelectedGroupId(e.value as string);
setSelectedGroupId(e.value);
}}
style={{ flex: 1 }}
/>
@@ -187,6 +197,17 @@ const Appointments: React.FC = () => {
>
Neuen Termin anlegen
</button>
<div style={{ marginBottom: 16 }}>
<label>
<input
type="checkbox"
checked={showInactive}
onChange={e => setShowInactive(e.target.checked)}
style={{ marginRight: 8 }}
/>
Vergangene Termine anzeigen
</label>
</div>
<CustomEventModal
open={modalOpen}
onClose={() => {
@@ -197,7 +218,7 @@ const Appointments: React.FC = () => {
setModalOpen(false);
setEditMode(false);
if (selectedGroupId) {
const data = await fetchEvents(selectedGroupId);
const data = await fetchEvents(selectedGroupId, showInactive);
const mapped: Event[] = data.map((e: RawEvent) => ({
Id: e.Id,
Subject: e.Subject,
@@ -291,8 +312,17 @@ const Appointments: React.FC = () => {
eventRendered={(args: EventRenderedArgs) => {
if (selectedGroupId && args.data && args.data.Id) {
const groupColor = getGroupColor(selectedGroupId, groups);
if (groupColor) {
const now = new Date();
if (args.data.EndTime && args.data.EndTime < now) {
// Vergangene Termine: Raumgruppenfarbe mit Transparenz und grauer Schrift
args.element.style.backgroundColor = groupColor
? `${groupColor}80` // 80 = ~50% Transparenz für hex-Farben
: '#f3f3f3';
args.element.style.color = '';
} else if (groupColor) {
// Aktuelle/future Termine: normale Raumgruppenfarbe
args.element.style.backgroundColor = groupColor;
args.element.style.color = '';
}
}
}}

View File

@@ -123,6 +123,23 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
if (!endTime) newErrors.endTime = 'Endzeit ist erforderlich';
if (!type) newErrors.type = 'Termintyp ist erforderlich';
// Vergangenheitsprüfung
const startDateTime =
startDate && startTime
? new Date(
startDate.getFullYear(),
startDate.getMonth(),
startDate.getDate(),
startTime.getHours(),
startTime.getMinutes()
)
: null;
const isPast = startDateTime && startDateTime < new Date();
if (isPast) {
newErrors.startDate = 'Termine in der Vergangenheit sind nicht erlaubt!';
}
if (type === 'presentation') {
if (!media) newErrors.media = 'Bitte eine Präsentation auswählen';
if (!slideshowInterval || slideshowInterval < 1)
@@ -213,6 +230,19 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
console.log('handleSave called');
};
// Vergangenheitsprüfung (außerhalb von handleSave, damit im Render verfügbar)
const startDateTime =
startDate && startTime
? new Date(
startDate.getFullYear(),
startDate.getMonth(),
startDate.getDate(),
startTime.getHours(),
startTime.getMinutes()
)
: null;
const isPast = !!(startDateTime && startDateTime < new Date());
return (
<DialogComponent
target="#root"
@@ -247,7 +277,11 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
<button className="e-btn e-danger" onClick={onClose}>
Schließen
</button>
<button className="e-btn e-success" onClick={handleSave}>
<button
className="e-btn e-success"
onClick={handleSave}
disabled={isPast} // <--- Button deaktivieren, wenn Termin in Vergangenheit
>
Termin(e) speichern
</button>
</div>
@@ -285,6 +319,22 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
{errors.startDate && (
<div style={{ color: 'red', fontSize: 12 }}>{errors.startDate}</div>
)}
{isPast && (
<span
style={{
color: 'orange',
fontWeight: 600,
marginLeft: 8,
display: 'inline-block',
background: '#fff3cd',
borderRadius: 4,
padding: '2px 8px',
border: '1px solid #ffeeba',
}}
>
Termin liegt in der Vergangenheit!
</span>
)}
</div>
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
<div style={{ flex: 1 }}>

View File

@@ -14,28 +14,38 @@ def get_events():
session = Session()
start = request.args.get("start")
end = request.args.get("end")
# geändert: jetzt group_id statt client_uuid
group_id = request.args.get("group_id")
query = session.query(Event).filter(Event.is_active == True)
if start and end:
query = query.filter(and_(Event.start < end, Event.end > start))
show_inactive = request.args.get(
"show_inactive", "0") == "1" # Checkbox-Logik
now = datetime.now(timezone.utc)
events_query = session.query(Event)
if group_id:
# geändert: filter auf group_id
query = query.filter(Event.group_id == int(group_id))
events = query.all()
events_query = events_query.filter(Event.group_id == int(group_id))
events = events_query.all()
result = []
for e in events:
result.append({
"Id": str(e.id),
"GroupId": e.group_id,
"Subject": e.title,
"StartTime": e.start.isoformat() if e.start else None,
"EndTime": e.end.isoformat() if e.end else None,
"IsAllDay": False,
"MediaId": e.event_media_id, # <--- Media-ID zurückgeben
"SlideshowInterval": e.slideshow_interval,
"WebsiteUrl": None, # Optional: für Website-Typ
})
# Zeitzonen-Korrektur für e.end
if e.end and e.end.tzinfo is None:
end_dt = e.end.replace(tzinfo=timezone.utc)
else:
end_dt = e.end
# Setze is_active auf False, wenn Termin vorbei ist
if end_dt and end_dt < now and e.is_active:
e.is_active = False
session.commit()
if show_inactive or e.is_active:
result.append({
"Id": str(e.id),
"GroupId": e.group_id,
"Subject": e.title,
"StartTime": e.start.isoformat() if e.start else None,
"EndTime": e.end.isoformat() if e.end else None,
"IsAllDay": False,
"MediaId": e.event_media_id,
})
session.close()
return jsonify(result)