diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json index ccf4804..3373fd3 100644 --- a/dashboard/package-lock.json +++ b/dashboard/package-lock.json @@ -12,6 +12,7 @@ "@syncfusion/ej2-react-calendars": "^30.1.37", "@syncfusion/ej2-react-dropdowns": "^30.1.37", "@syncfusion/ej2-react-grids": "^30.1.37", + "@syncfusion/ej2-react-inputs": "^30.1.38", "@syncfusion/ej2-react-kanban": "^30.1.37", "@syncfusion/ej2-react-notifications": "^30.1.37", "@syncfusion/ej2-react-popups": "^30.1.37", @@ -981,9 +982,9 @@ ] }, "node_modules/@syncfusion/ej2-base": { - "version": "30.1.37", - "resolved": "https://registry.npmjs.org/@syncfusion/ej2-base/-/ej2-base-30.1.37.tgz", - "integrity": "sha512-/RXLTid6XaKEyqBttyhO6FsGHhGitOzxPv64qo4AtE+33uU2usCRuBY4QZtGq5SBMQs7tIvjdtwff989jWHgzQ==", + "version": "30.1.38", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-base/-/ej2-base-30.1.38.tgz", + "integrity": "sha512-cj01hiMSAt7QB2bnA0hI1IGolYnqXpmv4I4cWyBB+D8V2LIiWIfqZA4I3LuffPeo60BV96XOeI6NnseK2RTIuQ==", "license": "SEE LICENSE IN license", "dependencies": { "@syncfusion/ej2-icons": "~30.1.37" @@ -1093,12 +1094,12 @@ "license": "SEE LICENSE IN license" }, "node_modules/@syncfusion/ej2-inputs": { - "version": "30.1.37", - "resolved": "https://registry.npmjs.org/@syncfusion/ej2-inputs/-/ej2-inputs-30.1.37.tgz", - "integrity": "sha512-6gOvtZO7ygdPnec8M7cr0TNBkENMmiW4YLnJTgGl5UgogQb+f8D8giE3SKqfWdmlJMBD1lzW96Ig4gW2n4JwMQ==", + "version": "30.1.38", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-inputs/-/ej2-inputs-30.1.38.tgz", + "integrity": "sha512-slZHBwCbCCus4L41FvUBejOciHHfjx2XuVXx+dxmWh4IStSRn0T40WKMC8Nz8BY3X4/Wc70AbDnYcOq99erqvg==", "license": "SEE LICENSE IN license", "dependencies": { - "@syncfusion/ej2-base": "~30.1.37", + "@syncfusion/ej2-base": "~30.1.38", "@syncfusion/ej2-buttons": "~30.1.37", "@syncfusion/ej2-popups": "~30.1.37", "@syncfusion/ej2-splitbuttons": "~30.1.37" @@ -1239,6 +1240,17 @@ "@syncfusion/ej2-react-base": "~30.1.37" } }, + "node_modules/@syncfusion/ej2-react-inputs": { + "version": "30.1.38", + "resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-inputs/-/ej2-react-inputs-30.1.38.tgz", + "integrity": "sha512-vJb0ZcPeGj9kSzybIde30msp8guCiWrAXtOu/LdTP/4oQwelmWpZHMy9zcsdhnTq5faW2cWZltNBZMnyQNT14Q==", + "license": "SEE LICENSE IN license", + "dependencies": { + "@syncfusion/ej2-base": "~30.1.38", + "@syncfusion/ej2-inputs": "30.1.38", + "@syncfusion/ej2-react-base": "~30.1.37" + } + }, "node_modules/@syncfusion/ej2-react-kanban": { "version": "30.1.37", "resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-kanban/-/ej2-react-kanban-30.1.37.tgz", diff --git a/dashboard/package.json b/dashboard/package.json index 8b1ad18..bf2eff1 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -14,6 +14,7 @@ "@syncfusion/ej2-react-calendars": "^30.1.37", "@syncfusion/ej2-react-dropdowns": "^30.1.37", "@syncfusion/ej2-react-grids": "^30.1.37", + "@syncfusion/ej2-react-inputs": "^30.1.38", "@syncfusion/ej2-react-kanban": "^30.1.37", "@syncfusion/ej2-react-notifications": "^30.1.37", "@syncfusion/ej2-react-popups": "^30.1.37", diff --git a/dashboard/src/appointments.tsx b/dashboard/src/appointments.tsx index 4f7d2a7..bf752f6 100644 --- a/dashboard/src/appointments.tsx +++ b/dashboard/src/appointments.tsx @@ -17,6 +17,7 @@ import { fetchEvents } from './apiEvents'; import { fetchGroups } from './apiGroups'; import { getGroupColor } from './groupColors'; import { deleteEvent } from './apiEvents'; +import CustomEventModal from './components/CustomEventModal'; // Typ für Gruppe ergänzen type Group = { @@ -94,6 +95,8 @@ const Appointments: React.FC = () => { const [groups, setGroups] = useState([]); const [selectedGroupId, setSelectedGroupId] = useState(null); const [events, setEvents] = useState([]); + const [modalOpen, setModalOpen] = useState(false); + const [modalInitialData, setModalInitialData] = useState({}); // Gruppen laden useEffect(() => { @@ -129,8 +132,6 @@ const Appointments: React.FC = () => { } }, [selectedGroupId]); - console.log('Aktuelle Events:', events); - return (

Terminmanagement

@@ -153,6 +154,26 @@ const Appointments: React.FC = () => { style={{ flex: 1 }} />
+ + setModalOpen(false)} + onSave={eventData => { + console.log('Event-Daten zum Speichern:', eventData); + // Event speichern (API-Aufruf oder State-Update) + setModalOpen(false); + }} + initialData={modalInitialData} + groupName={groups.find(g => g.id === selectedGroupId)?.name ?? ''} + /> { eventSettings={{ dataSource: events, }} + cellClick={args => { + // args.startTime und args.endTime sind Date-Objekte + args.cancel = true; // Verhindert die Standardaktion + setModalInitialData({ + startDate: args.startTime, + startTime: args.startTime, + endTime: args.endTime, + }); + setModalOpen(true); + }} eventRendered={(args: EventRenderedArgs) => { if (selectedGroupId && args.data && args.data.Id) { const groupColor = getGroupColor(selectedGroupId, groups); diff --git a/dashboard/src/components/CustomEventModal.tsx b/dashboard/src/components/CustomEventModal.tsx new file mode 100644 index 0000000..6e457cb --- /dev/null +++ b/dashboard/src/components/CustomEventModal.tsx @@ -0,0 +1,247 @@ +import React from 'react'; +import { DialogComponent } from '@syncfusion/ej2-react-popups'; +import { TextBoxComponent } from '@syncfusion/ej2-react-inputs'; +import { DatePickerComponent, TimePickerComponent } from '@syncfusion/ej2-react-calendars'; +import { DropDownListComponent, MultiSelectComponent } from '@syncfusion/ej2-react-dropdowns'; +import { CheckBoxComponent } from '@syncfusion/ej2-react-buttons'; + +type CustomEventData = { + title: string; + startDate: Date | null; + startTime: Date | null; + endTime: Date | null; + type: string; + description: string; + repeat: boolean; + weekdays: number[]; + repeatUntil: Date | null; + skipHolidays: boolean; +}; + +type CustomEventModalProps = { + open: boolean; + onClose: () => void; + onSave: (event: CustomEventData) => void; + initialData?: Partial; + groupName?: string; // <--- NEU +}; + +const weekdayOptions = [ + { value: 0, label: 'Montag' }, + { value: 1, label: 'Dienstag' }, + { value: 2, label: 'Mittwoch' }, + { value: 3, label: 'Donnerstag' }, + { value: 4, label: 'Freitag' }, + { value: 5, label: 'Samstag' }, + { value: 6, label: 'Sonntag' }, +]; + +const typeOptions = [ + { value: 'presentation', label: 'Präsentation' }, + { value: 'website', label: 'Website' }, + { value: 'video', label: 'Video' }, + { value: 'message', label: 'Nachricht' }, + { value: 'webuntis', label: 'WebUntis' }, + { value: 'other', label: 'Sonstiges' }, +]; + +const CustomEventModal: React.FC = ({ + open, + onClose, + onSave, + initialData = {}, + groupName, // <--- NEU +}) => { + const [title, setTitle] = React.useState(initialData.title || ''); + const [startDate, setStartDate] = React.useState(initialData.startDate || null); + const [startTime, setStartTime] = React.useState( + initialData.startTime || new Date(0, 0, 0, 9, 0) + ); + const [endTime, setEndTime] = React.useState(initialData.endTime || new Date(0, 0, 0, 9, 30)); + const [type, setType] = React.useState(initialData.type ?? 'presentation'); + const [description, setDescription] = React.useState(initialData.description || ''); + const [repeat, setRepeat] = React.useState(initialData.repeat || false); + const [weekdays, setWeekdays] = React.useState(initialData.weekdays || []); + const [repeatUntil, setRepeatUntil] = React.useState(initialData.repeatUntil || null); + const [skipHolidays, setSkipHolidays] = React.useState(initialData.skipHolidays || false); + const [errors, setErrors] = React.useState<{ [key: string]: string }>({}); + + React.useEffect(() => { + if (open && initialData) { + setTitle(initialData.title || ''); + setStartDate(initialData.startDate || null); + setStartTime(initialData.startTime || new Date(0, 0, 0, 9, 0)); + setEndTime(initialData.endTime || new Date(0, 0, 0, 9, 30)); + setType(initialData.type || ''); + setDescription(initialData.description || ''); + setRepeat(initialData.repeat || false); + setWeekdays(initialData.weekdays || []); + setRepeatUntil(initialData.repeatUntil || null); + setSkipHolidays(initialData.skipHolidays || false); + } + }, [open, initialData]); + + const handleSave = () => { + const newErrors: { [key: string]: string } = {}; + if (!title.trim()) newErrors.title = 'Titel ist erforderlich'; + if (!startDate) newErrors.startDate = 'Startdatum ist erforderlich'; + if (!startTime) newErrors.startTime = 'Startzeit ist erforderlich'; + if (!endTime) newErrors.endTime = 'Endzeit ist erforderlich'; + if (!type) newErrors.type = 'Termintyp ist erforderlich'; + // ggf. weitere Felder prüfen + + if (Object.keys(newErrors).length > 0) { + setErrors(newErrors); + return; + } + + setErrors({}); + onSave({ + title, + startDate, + startTime, + endTime, + type, + description, + repeat, + weekdays, + repeatUntil, + skipHolidays, + }); + }; + + return ( + ( +
+ Neuen Termin anlegen + {groupName && ( + + für Raumgruppe: {groupName} + + )} +
+ )} + showCloseIcon={true} + close={onClose} + isModal={true} + footerTemplate={() => ( +
+ + +
+ )} + > +
+
+
+
+ setTitle(e.value)} + /> + {errors.title &&
{errors.title}
} +
+
+ setStartDate(e.value)} + /> + {errors.startDate && ( +
{errors.startDate}
+ )} +
+
+
+ setStartTime(e.value)} + /> + {errors.startTime && ( +
{errors.startTime}
+ )} +
+
+ setEndTime(e.value)} + /> + {errors.endTime && ( +
{errors.endTime}
+ )} +
+
+
+ setType(e.value as string)} + /> + {errors.type &&
{errors.type}
} +
+
+
+
+ setRepeat(e.checked)} + /> +
+
+ setWeekdays(e.value as number[])} + disabled={!repeat} + showDropDownIcon={true} + closePopupOnSelect={false} + /> +
+
+ setRepeatUntil(e.value)} + disabled={!repeat} + /> +
+
+ setSkipHolidays(e.checked)} + disabled={!repeat} + /> +
+
+
+
+
+ ); +}; + +export default CustomEventModal;