Create and save custom events in database
This commit is contained in:
@@ -97,6 +97,7 @@ const Appointments: React.FC = () => {
|
|||||||
const [events, setEvents] = useState<Event[]>([]);
|
const [events, setEvents] = useState<Event[]>([]);
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [modalInitialData, setModalInitialData] = useState({});
|
const [modalInitialData, setModalInitialData] = useState({});
|
||||||
|
const [schedulerKey, setSchedulerKey] = useState(0);
|
||||||
|
|
||||||
// Gruppen laden
|
// Gruppen laden
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -120,8 +121,8 @@ const Appointments: React.FC = () => {
|
|||||||
const mapped: Event[] = data.map(e => ({
|
const mapped: Event[] = data.map(e => ({
|
||||||
Id: e.Id,
|
Id: e.Id,
|
||||||
Subject: e.Subject,
|
Subject: e.Subject,
|
||||||
StartTime: new Date(e.StartTime),
|
StartTime: new Date(e.StartTime.endsWith('Z') ? e.StartTime : e.StartTime + 'Z'),
|
||||||
EndTime: new Date(e.EndTime),
|
EndTime: new Date(e.EndTime.endsWith('Z') ? e.EndTime : e.EndTime + 'Z'),
|
||||||
IsAllDay: e.IsAllDay,
|
IsAllDay: e.IsAllDay,
|
||||||
}));
|
}));
|
||||||
setEvents(mapped);
|
setEvents(mapped);
|
||||||
@@ -166,16 +167,28 @@ const Appointments: React.FC = () => {
|
|||||||
<CustomEventModal
|
<CustomEventModal
|
||||||
open={modalOpen}
|
open={modalOpen}
|
||||||
onClose={() => setModalOpen(false)}
|
onClose={() => setModalOpen(false)}
|
||||||
onSave={eventData => {
|
onSave={async eventData => {
|
||||||
console.log('Event-Daten zum Speichern:', eventData);
|
|
||||||
// Event speichern (API-Aufruf oder State-Update)
|
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
|
console.log('onSave wird aufgerufen');
|
||||||
|
if (selectedGroupId) {
|
||||||
|
const data = await fetchEvents(selectedGroupId);
|
||||||
|
console.log('Events nach Save:', data);
|
||||||
|
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,
|
||||||
|
}));
|
||||||
|
setEvents(mapped);
|
||||||
|
setSchedulerKey(prev => prev + 1); // <-- Key erhöhen
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
initialData={modalInitialData}
|
initialData={modalInitialData}
|
||||||
groupName={groups.find(g => g.id === selectedGroupId)?.name ?? ''}
|
groupName={groups.find(g => g.id === selectedGroupId) ?? { id: selectedGroupId, name: '' }}
|
||||||
/>
|
/>
|
||||||
<ScheduleComponent
|
<ScheduleComponent
|
||||||
key={selectedGroupId}
|
key={schedulerKey} // <-- dynamischer Key
|
||||||
height="750px"
|
height="750px"
|
||||||
locale="de"
|
locale="de"
|
||||||
currentView="Week"
|
currentView="Week"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { TextBoxComponent } from '@syncfusion/ej2-react-inputs';
|
|||||||
import { DatePickerComponent, TimePickerComponent } from '@syncfusion/ej2-react-calendars';
|
import { DatePickerComponent, TimePickerComponent } from '@syncfusion/ej2-react-calendars';
|
||||||
import { DropDownListComponent, MultiSelectComponent } from '@syncfusion/ej2-react-dropdowns';
|
import { DropDownListComponent, MultiSelectComponent } from '@syncfusion/ej2-react-dropdowns';
|
||||||
import { CheckBoxComponent } from '@syncfusion/ej2-react-buttons';
|
import { CheckBoxComponent } from '@syncfusion/ej2-react-buttons';
|
||||||
|
import CustomSelectUploadEventModal from './CustomSelectUploadEventModal';
|
||||||
|
|
||||||
type CustomEventData = {
|
type CustomEventData = {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -21,9 +22,9 @@ type CustomEventData = {
|
|||||||
type CustomEventModalProps = {
|
type CustomEventModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSave: (event: CustomEventData) => void;
|
onSave: (eventData: any) => void;
|
||||||
initialData?: Partial<CustomEventData>;
|
initialData?: any;
|
||||||
groupName?: string; // <--- NEU
|
groupName: string | { id: string | null; name: string }; // <- angepasst
|
||||||
};
|
};
|
||||||
|
|
||||||
const weekdayOptions = [
|
const weekdayOptions = [
|
||||||
@@ -42,7 +43,6 @@ const typeOptions = [
|
|||||||
{ value: 'video', label: 'Video' },
|
{ value: 'video', label: 'Video' },
|
||||||
{ value: 'message', label: 'Nachricht' },
|
{ value: 'message', label: 'Nachricht' },
|
||||||
{ value: 'webuntis', label: 'WebUntis' },
|
{ value: 'webuntis', label: 'WebUntis' },
|
||||||
{ value: 'other', label: 'Sonstiges' },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const CustomEventModal: React.FC<CustomEventModalProps> = ({
|
const CustomEventModal: React.FC<CustomEventModalProps> = ({
|
||||||
@@ -65,30 +65,57 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
|
|||||||
const [repeatUntil, setRepeatUntil] = React.useState(initialData.repeatUntil || null);
|
const [repeatUntil, setRepeatUntil] = React.useState(initialData.repeatUntil || null);
|
||||||
const [skipHolidays, setSkipHolidays] = React.useState(initialData.skipHolidays || false);
|
const [skipHolidays, setSkipHolidays] = React.useState(initialData.skipHolidays || false);
|
||||||
const [errors, setErrors] = React.useState<{ [key: string]: string }>({});
|
const [errors, setErrors] = React.useState<{ [key: string]: string }>({});
|
||||||
|
const [media, setMedia] = React.useState<{ id: string; path: string; name: string } | null>(null);
|
||||||
|
const [pendingMedia, setPendingMedia] = React.useState<{
|
||||||
|
id: string;
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
} | null>(null);
|
||||||
|
const [slideshowInterval, setSlideshowInterval] = React.useState<number>(10);
|
||||||
|
const [websiteUrl, setWebsiteUrl] = React.useState<string>('');
|
||||||
|
const [mediaModalOpen, setMediaModalOpen] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (open && initialData) {
|
if (open) {
|
||||||
setTitle(initialData.title || '');
|
setTitle(initialData.title || '');
|
||||||
setStartDate(initialData.startDate || null);
|
setStartDate(initialData.startDate || null);
|
||||||
setStartTime(initialData.startTime || new Date(0, 0, 0, 9, 0));
|
setStartTime(initialData.startTime || new Date(0, 0, 0, 9, 0));
|
||||||
setEndTime(initialData.endTime || new Date(0, 0, 0, 9, 30));
|
setEndTime(initialData.endTime || new Date(0, 0, 0, 9, 30));
|
||||||
setType(initialData.type || '');
|
setType(initialData.type ?? 'presentation'); // Immer 'presentation' als Default
|
||||||
setDescription(initialData.description || '');
|
setDescription(initialData.description || '');
|
||||||
setRepeat(initialData.repeat || false);
|
setRepeat(initialData.repeat || false);
|
||||||
setWeekdays(initialData.weekdays || []);
|
setWeekdays(initialData.weekdays || []);
|
||||||
setRepeatUntil(initialData.repeatUntil || null);
|
setRepeatUntil(initialData.repeatUntil || null);
|
||||||
setSkipHolidays(initialData.skipHolidays || false);
|
setSkipHolidays(initialData.skipHolidays || false);
|
||||||
|
setMedia(null);
|
||||||
|
setSlideshowInterval(10);
|
||||||
|
setWebsiteUrl('');
|
||||||
}
|
}
|
||||||
}, [open, initialData]);
|
}, [open, initialData]);
|
||||||
|
|
||||||
const handleSave = () => {
|
React.useEffect(() => {
|
||||||
|
if (!mediaModalOpen && pendingMedia) {
|
||||||
|
setMedia(pendingMedia);
|
||||||
|
setPendingMedia(null);
|
||||||
|
}
|
||||||
|
}, [mediaModalOpen, pendingMedia]);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
const newErrors: { [key: string]: string } = {};
|
const newErrors: { [key: string]: string } = {};
|
||||||
if (!title.trim()) newErrors.title = 'Titel ist erforderlich';
|
if (!title.trim()) newErrors.title = 'Titel ist erforderlich';
|
||||||
if (!startDate) newErrors.startDate = 'Startdatum ist erforderlich';
|
if (!startDate) newErrors.startDate = 'Startdatum ist erforderlich';
|
||||||
if (!startTime) newErrors.startTime = 'Startzeit ist erforderlich';
|
if (!startTime) newErrors.startTime = 'Startzeit ist erforderlich';
|
||||||
if (!endTime) newErrors.endTime = 'Endzeit ist erforderlich';
|
if (!endTime) newErrors.endTime = 'Endzeit ist erforderlich';
|
||||||
if (!type) newErrors.type = 'Termintyp ist erforderlich';
|
if (!type) newErrors.type = 'Termintyp ist erforderlich';
|
||||||
// ggf. weitere Felder prüfen
|
|
||||||
|
if (type === 'presentation') {
|
||||||
|
if (!media) newErrors.media = 'Bitte eine Präsentation auswählen';
|
||||||
|
if (!slideshowInterval || slideshowInterval < 1)
|
||||||
|
newErrors.slideshowInterval = 'Intervall angeben';
|
||||||
|
}
|
||||||
|
if (type === 'website') {
|
||||||
|
if (!websiteUrl.trim()) newErrors.websiteUrl = 'Webseiten-URL ist erforderlich';
|
||||||
|
}
|
||||||
|
|
||||||
if (Object.keys(newErrors).length > 0) {
|
if (Object.keys(newErrors).length > 0) {
|
||||||
setErrors(newErrors);
|
setErrors(newErrors);
|
||||||
@@ -96,22 +123,71 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setErrors({});
|
setErrors({});
|
||||||
onSave({
|
|
||||||
|
// group_id ist jetzt wirklich die ID (z.B. als prop übergeben)
|
||||||
|
const group_id = typeof groupName === 'object' && groupName !== null ? groupName.id : groupName;
|
||||||
|
|
||||||
|
// Daten für das Backend zusammenstellen
|
||||||
|
const payload: any = {
|
||||||
|
group_id,
|
||||||
title,
|
title,
|
||||||
startDate,
|
|
||||||
startTime,
|
|
||||||
endTime,
|
|
||||||
type,
|
|
||||||
description,
|
description,
|
||||||
repeat,
|
start:
|
||||||
weekdays,
|
startDate && startTime
|
||||||
repeatUntil,
|
? new Date(
|
||||||
skipHolidays,
|
startDate.getFullYear(),
|
||||||
|
startDate.getMonth(),
|
||||||
|
startDate.getDate(),
|
||||||
|
startTime.getHours(),
|
||||||
|
startTime.getMinutes()
|
||||||
|
).toISOString()
|
||||||
|
: null,
|
||||||
|
end:
|
||||||
|
startDate && endTime
|
||||||
|
? new Date(
|
||||||
|
startDate.getFullYear(),
|
||||||
|
startDate.getMonth(),
|
||||||
|
startDate.getDate(),
|
||||||
|
endTime.getHours(),
|
||||||
|
endTime.getMinutes()
|
||||||
|
).toISOString()
|
||||||
|
: null,
|
||||||
|
event_type: type,
|
||||||
|
is_active: 1,
|
||||||
|
created_by: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type === 'presentation') {
|
||||||
|
payload.event_media_id = media?.id;
|
||||||
|
payload.slideshow_interval = slideshowInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'website') {
|
||||||
|
payload.website_url = websiteUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/events', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
});
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
onSave(payload); // <--- HIER ergänzen!
|
||||||
|
onClose();
|
||||||
|
} else {
|
||||||
|
const err = await res.json();
|
||||||
|
setErrors({ api: err.error || 'Fehler beim Speichern' });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setErrors({ api: 'Netzwerkfehler beim Speichern' });
|
||||||
|
}
|
||||||
|
console.log('handleSave called');
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DialogComponent
|
<DialogComponent
|
||||||
|
target="#root"
|
||||||
visible={open}
|
visible={open}
|
||||||
width="800px"
|
width="800px"
|
||||||
header={() => (
|
header={() => (
|
||||||
@@ -119,7 +195,7 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
|
|||||||
Neuen Termin anlegen
|
Neuen Termin anlegen
|
||||||
{groupName && (
|
{groupName && (
|
||||||
<span style={{ fontWeight: 400, fontSize: 16, marginLeft: 16, color: '#888' }}>
|
<span style={{ fontWeight: 400, fontSize: 16, marginLeft: 16, color: '#888' }}>
|
||||||
für Raumgruppe: <b>{groupName}</b>
|
für Raumgruppe: <b>{typeof groupName === 'object' ? groupName.name : groupName}</b>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -141,6 +217,7 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
|
|||||||
<div style={{ padding: '24px' }}>
|
<div style={{ padding: '24px' }}>
|
||||||
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap' }}>
|
||||||
<div style={{ flex: 1, minWidth: 260 }}>
|
<div style={{ flex: 1, minWidth: 260 }}>
|
||||||
|
{/* ...Titel, Beschreibung, Datum, Zeit... */}
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<TextBoxComponent
|
<TextBoxComponent
|
||||||
placeholder="Titel"
|
placeholder="Titel"
|
||||||
@@ -196,18 +273,9 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginBottom: 12 }}>
|
|
||||||
<DropDownListComponent
|
|
||||||
dataSource={typeOptions}
|
|
||||||
fields={{ text: 'label', value: 'value' }}
|
|
||||||
placeholder="Termintyp"
|
|
||||||
value={type}
|
|
||||||
change={e => setType(e.value as string)}
|
|
||||||
/>
|
|
||||||
{errors.type && <div style={{ color: 'red', fontSize: 12 }}>{errors.type}</div>}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1, minWidth: 260 }}>
|
<div style={{ flex: 1, minWidth: 260 }}>
|
||||||
|
{/* ...Wiederholung, MultiSelect, Wiederholung bis, Ferien... */}
|
||||||
<div style={{ marginBottom: 12 }}>
|
<div style={{ marginBottom: 12 }}>
|
||||||
<CheckBoxComponent
|
<CheckBoxComponent
|
||||||
label="Wiederholender Termin"
|
label="Wiederholender Termin"
|
||||||
@@ -248,7 +316,77 @@ const CustomEventModal: React.FC<CustomEventModalProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* NEUER ZWEISPALTIGER BEREICH für Termintyp und Zusatzfelder */}
|
||||||
|
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', marginTop: 8 }}>
|
||||||
|
<div style={{ flex: 1, minWidth: 260 }}>
|
||||||
|
<div style={{ marginBottom: 12, marginTop: 16 }}>
|
||||||
|
<DropDownListComponent
|
||||||
|
dataSource={typeOptions}
|
||||||
|
fields={{ text: 'label', value: 'value' }}
|
||||||
|
placeholder="Termintyp"
|
||||||
|
value={type}
|
||||||
|
change={e => setType(e.value as string)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
{errors.type && <div style={{ color: 'red', fontSize: 12 }}>{errors.type}</div>}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1, minWidth: 260 }}>
|
||||||
|
<div style={{ marginBottom: 12, minHeight: 60 }}>
|
||||||
|
{type === 'presentation' && (
|
||||||
|
<div>
|
||||||
|
<div style={{ marginBottom: 8, marginTop: 16 }}>
|
||||||
|
<button
|
||||||
|
className="e-btn"
|
||||||
|
onClick={() => setMediaModalOpen(true)}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
Medium auswählen/hochladen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<b>Ausgewähltes Medium:</b>{' '}
|
||||||
|
{media ? (
|
||||||
|
media.path
|
||||||
|
) : (
|
||||||
|
<span style={{ color: '#888' }}>Kein Medium ausgewählt</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<TextBoxComponent
|
||||||
|
placeholder="Slideshow-Intervall (Sekunden)"
|
||||||
|
floatLabelType="Auto"
|
||||||
|
type="number"
|
||||||
|
value={String(slideshowInterval)}
|
||||||
|
change={e => setSlideshowInterval(Number(e.value))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{type === 'website' && (
|
||||||
|
<div>
|
||||||
|
<TextBoxComponent
|
||||||
|
placeholder="Webseiten-URL"
|
||||||
|
floatLabelType="Always"
|
||||||
|
value={websiteUrl}
|
||||||
|
change={e => setWebsiteUrl(e.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{mediaModalOpen && (
|
||||||
|
<CustomSelectUploadEventModal
|
||||||
|
open={mediaModalOpen}
|
||||||
|
onClose={() => setMediaModalOpen(false)}
|
||||||
|
onSelect={({ id, path, name }) => {
|
||||||
|
setPendingMedia({ id, path, name });
|
||||||
|
setMediaModalOpen(false);
|
||||||
|
}}
|
||||||
|
selectedFileId={null}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</DialogComponent>
|
</DialogComponent>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
119
dashboard/src/components/CustomSelectUploadEventModal.tsx
Normal file
119
dashboard/src/components/CustomSelectUploadEventModal.tsx
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { DialogComponent } from '@syncfusion/ej2-react-popups';
|
||||||
|
import {
|
||||||
|
FileManagerComponent,
|
||||||
|
Inject,
|
||||||
|
NavigationPane,
|
||||||
|
DetailsView,
|
||||||
|
Toolbar,
|
||||||
|
} from '@syncfusion/ej2-react-filemanager';
|
||||||
|
|
||||||
|
const hostUrl = '/api/eventmedia/filemanager/';
|
||||||
|
|
||||||
|
type CustomSelectUploadEventModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSelect: (file: { id: string; path: string; name: string }) => void; // name ergänzt
|
||||||
|
selectedFileId?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CustomSelectUploadEventModal: React.FC<CustomSelectUploadEventModalProps> = props => {
|
||||||
|
const { open, onClose, onSelect } = props;
|
||||||
|
|
||||||
|
const [selectedFile, setSelectedFile] = useState<{
|
||||||
|
id: string;
|
||||||
|
path: string;
|
||||||
|
name: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
// Callback für Dateiauswahl
|
||||||
|
interface FileSelectEventArgs {
|
||||||
|
fileDetails: {
|
||||||
|
name: string;
|
||||||
|
isFile: boolean;
|
||||||
|
size: number;
|
||||||
|
// weitere Felder falls benötigt
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFileSelect = async (args: FileSelectEventArgs) => {
|
||||||
|
if (args.fileDetails.isFile && args.fileDetails.size > 0) {
|
||||||
|
const filename = args.fileDetails.name;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/eventmedia/find_by_filename?filename=${encodeURIComponent(filename)}`
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
setSelectedFile({ id: data.id, path: data.file_path, name: filename });
|
||||||
|
} else {
|
||||||
|
setSelectedFile({ id: filename, path: filename, name: filename });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching file details:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Button-Handler
|
||||||
|
const handleSelectClick = () => {
|
||||||
|
if (selectedFile) {
|
||||||
|
onSelect(selectedFile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DialogComponent
|
||||||
|
target="#root"
|
||||||
|
visible={open}
|
||||||
|
width="700px"
|
||||||
|
header="Medium auswählen/hochladen"
|
||||||
|
showCloseIcon={true}
|
||||||
|
close={onClose}
|
||||||
|
isModal={true}
|
||||||
|
footerTemplate={() => (
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<button className="e-btn" onClick={onClose}>
|
||||||
|
Abbrechen
|
||||||
|
</button>
|
||||||
|
<button className="e-btn e-primary" disabled={!selectedFile} onClick={handleSelectClick}>
|
||||||
|
Auswählen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FileManagerComponent
|
||||||
|
ajaxSettings={{
|
||||||
|
url: hostUrl + 'operations',
|
||||||
|
getImageUrl: hostUrl + 'get-image',
|
||||||
|
uploadUrl: hostUrl + 'upload',
|
||||||
|
downloadUrl: hostUrl + 'download',
|
||||||
|
}}
|
||||||
|
toolbarSettings={{
|
||||||
|
items: [
|
||||||
|
'NewFolder',
|
||||||
|
'Upload',
|
||||||
|
'Download',
|
||||||
|
'Rename',
|
||||||
|
'Delete',
|
||||||
|
'SortBy',
|
||||||
|
'Refresh',
|
||||||
|
'Details',
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
contextMenuSettings={{
|
||||||
|
file: ['Open', '|', 'Download', '|', 'Rename', 'Delete', '|', 'Details'],
|
||||||
|
folder: ['Open', '|', 'Rename', 'Delete', '|', 'Details'],
|
||||||
|
layout: ['SortBy', 'Refresh', '|', 'View', 'Details'],
|
||||||
|
}}
|
||||||
|
allowMultiSelection={false}
|
||||||
|
fileSelect={handleFileSelect}
|
||||||
|
>
|
||||||
|
<Inject services={[NavigationPane, DetailsView, Toolbar]} />
|
||||||
|
</FileManagerComponent>
|
||||||
|
</DialogComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomSelectUploadEventModal;
|
||||||
@@ -143,3 +143,23 @@ def update_media(media_id):
|
|||||||
# Event-Zuordnung ggf. ergänzen
|
# Event-Zuordnung ggf. ergänzen
|
||||||
session.commit()
|
session.commit()
|
||||||
return jsonify(media.to_dict())
|
return jsonify(media.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@eventmedia_bp.route('/find_by_filename', methods=['GET'])
|
||||||
|
def find_by_filename():
|
||||||
|
filename = request.args.get('filename')
|
||||||
|
if not filename:
|
||||||
|
return jsonify({'error': 'Missing filename'}), 400
|
||||||
|
session = Session()
|
||||||
|
# Suche nach exaktem Dateinamen in url oder file_path
|
||||||
|
media = session.query(EventMedia).filter(
|
||||||
|
(EventMedia.url == filename) | (
|
||||||
|
EventMedia.file_path.like(f"%{filename}"))
|
||||||
|
).first()
|
||||||
|
if not media:
|
||||||
|
return jsonify({'error': 'Not found'}), 404
|
||||||
|
return jsonify({
|
||||||
|
'id': media.id,
|
||||||
|
'file_path': media.file_path,
|
||||||
|
'url': media.url
|
||||||
|
})
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
from database import Session
|
|
||||||
from sqlalchemy import and_
|
|
||||||
from models import Event
|
|
||||||
from flask import Blueprint, request, jsonify
|
from flask import Blueprint, request, jsonify
|
||||||
|
from database import Session
|
||||||
|
from models import Event, EventMedia, MediaType
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from sqlalchemy import and_
|
||||||
import sys
|
import sys
|
||||||
sys.path.append('/workspace')
|
sys.path.append('/workspace')
|
||||||
|
|
||||||
@@ -47,3 +48,70 @@ def delete_event(event_id):
|
|||||||
session.commit()
|
session.commit()
|
||||||
session.close()
|
session.close()
|
||||||
return jsonify({"success": True})
|
return jsonify({"success": True})
|
||||||
|
|
||||||
|
|
||||||
|
@events_bp.route("", methods=["POST"])
|
||||||
|
def create_event():
|
||||||
|
data = request.json
|
||||||
|
session = Session()
|
||||||
|
|
||||||
|
# Pflichtfelder prüfen
|
||||||
|
required = ["group_id", "title", "description",
|
||||||
|
"start", "end", "event_type", "created_by"]
|
||||||
|
for field in required:
|
||||||
|
if field not in data:
|
||||||
|
return jsonify({"error": f"Missing field: {field}"}), 400
|
||||||
|
|
||||||
|
event_type = data["event_type"]
|
||||||
|
event_media_id = None
|
||||||
|
slideshow_interval = None
|
||||||
|
|
||||||
|
# Präsentation: event_media_id und slideshow_interval übernehmen
|
||||||
|
if event_type == "presentation":
|
||||||
|
event_media_id = data.get("event_media_id")
|
||||||
|
slideshow_interval = data.get("slideshow_interval")
|
||||||
|
if not event_media_id:
|
||||||
|
return jsonify({"error": "event_media_id required for presentation"}), 400
|
||||||
|
|
||||||
|
# Website: Webseite als EventMedia anlegen und ID übernehmen
|
||||||
|
if event_type == "website":
|
||||||
|
website_url = data.get("website_url")
|
||||||
|
if not website_url:
|
||||||
|
return jsonify({"error": "website_url required for website"}), 400
|
||||||
|
# EventMedia für Webseite anlegen
|
||||||
|
media = EventMedia(
|
||||||
|
media_type=MediaType.website,
|
||||||
|
url=website_url,
|
||||||
|
file_path=website_url
|
||||||
|
)
|
||||||
|
session.add(media)
|
||||||
|
session.commit()
|
||||||
|
event_media_id = media.id
|
||||||
|
|
||||||
|
# created_by aus den Daten holen, Default: None
|
||||||
|
created_by = data.get("created_by")
|
||||||
|
|
||||||
|
# Start- und Endzeit in UTC umwandeln, falls kein Zulu-Zeitstempel
|
||||||
|
start = datetime.fromisoformat(data["start"])
|
||||||
|
end = datetime.fromisoformat(data["end"])
|
||||||
|
if start.tzinfo is None:
|
||||||
|
start = start.astimezone(timezone.utc)
|
||||||
|
if end.tzinfo is None:
|
||||||
|
end = end.astimezone(timezone.utc)
|
||||||
|
|
||||||
|
# Event anlegen
|
||||||
|
event = Event(
|
||||||
|
group_id=data["group_id"],
|
||||||
|
title=data["title"],
|
||||||
|
description=data["description"],
|
||||||
|
start=start,
|
||||||
|
end=end,
|
||||||
|
event_type=event_type,
|
||||||
|
is_active=True,
|
||||||
|
event_media_id=event_media_id,
|
||||||
|
slideshow_interval=slideshow_interval,
|
||||||
|
created_by=created_by # <--- HIER hinzugefügt
|
||||||
|
)
|
||||||
|
session.add(event)
|
||||||
|
session.commit()
|
||||||
|
return jsonify({"success": True, "event_id": event.id})
|
||||||
|
|||||||
Reference in New Issue
Block a user