new custom modalbox for event input
This commit is contained in:
26
dashboard/package-lock.json
generated
26
dashboard/package-lock.json
generated
@@ -12,6 +12,7 @@
|
|||||||
"@syncfusion/ej2-react-calendars": "^30.1.37",
|
"@syncfusion/ej2-react-calendars": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
|
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-grids": "^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-kanban": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-notifications": "^30.1.37",
|
"@syncfusion/ej2-react-notifications": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-popups": "^30.1.37",
|
"@syncfusion/ej2-react-popups": "^30.1.37",
|
||||||
@@ -981,9 +982,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@syncfusion/ej2-base": {
|
"node_modules/@syncfusion/ej2-base": {
|
||||||
"version": "30.1.37",
|
"version": "30.1.38",
|
||||||
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-base/-/ej2-base-30.1.37.tgz",
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-base/-/ej2-base-30.1.38.tgz",
|
||||||
"integrity": "sha512-/RXLTid6XaKEyqBttyhO6FsGHhGitOzxPv64qo4AtE+33uU2usCRuBY4QZtGq5SBMQs7tIvjdtwff989jWHgzQ==",
|
"integrity": "sha512-cj01hiMSAt7QB2bnA0hI1IGolYnqXpmv4I4cWyBB+D8V2LIiWIfqZA4I3LuffPeo60BV96XOeI6NnseK2RTIuQ==",
|
||||||
"license": "SEE LICENSE IN license",
|
"license": "SEE LICENSE IN license",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@syncfusion/ej2-icons": "~30.1.37"
|
"@syncfusion/ej2-icons": "~30.1.37"
|
||||||
@@ -1093,12 +1094,12 @@
|
|||||||
"license": "SEE LICENSE IN license"
|
"license": "SEE LICENSE IN license"
|
||||||
},
|
},
|
||||||
"node_modules/@syncfusion/ej2-inputs": {
|
"node_modules/@syncfusion/ej2-inputs": {
|
||||||
"version": "30.1.37",
|
"version": "30.1.38",
|
||||||
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-inputs/-/ej2-inputs-30.1.37.tgz",
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-inputs/-/ej2-inputs-30.1.38.tgz",
|
||||||
"integrity": "sha512-6gOvtZO7ygdPnec8M7cr0TNBkENMmiW4YLnJTgGl5UgogQb+f8D8giE3SKqfWdmlJMBD1lzW96Ig4gW2n4JwMQ==",
|
"integrity": "sha512-slZHBwCbCCus4L41FvUBejOciHHfjx2XuVXx+dxmWh4IStSRn0T40WKMC8Nz8BY3X4/Wc70AbDnYcOq99erqvg==",
|
||||||
"license": "SEE LICENSE IN license",
|
"license": "SEE LICENSE IN license",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@syncfusion/ej2-base": "~30.1.37",
|
"@syncfusion/ej2-base": "~30.1.38",
|
||||||
"@syncfusion/ej2-buttons": "~30.1.37",
|
"@syncfusion/ej2-buttons": "~30.1.37",
|
||||||
"@syncfusion/ej2-popups": "~30.1.37",
|
"@syncfusion/ej2-popups": "~30.1.37",
|
||||||
"@syncfusion/ej2-splitbuttons": "~30.1.37"
|
"@syncfusion/ej2-splitbuttons": "~30.1.37"
|
||||||
@@ -1239,6 +1240,17 @@
|
|||||||
"@syncfusion/ej2-react-base": "~30.1.37"
|
"@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": {
|
"node_modules/@syncfusion/ej2-react-kanban": {
|
||||||
"version": "30.1.37",
|
"version": "30.1.37",
|
||||||
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-kanban/-/ej2-react-kanban-30.1.37.tgz",
|
"resolved": "https://registry.npmjs.org/@syncfusion/ej2-react-kanban/-/ej2-react-kanban-30.1.37.tgz",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"@syncfusion/ej2-react-calendars": "^30.1.37",
|
"@syncfusion/ej2-react-calendars": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
|
"@syncfusion/ej2-react-dropdowns": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-grids": "^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-kanban": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-notifications": "^30.1.37",
|
"@syncfusion/ej2-react-notifications": "^30.1.37",
|
||||||
"@syncfusion/ej2-react-popups": "^30.1.37",
|
"@syncfusion/ej2-react-popups": "^30.1.37",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { fetchEvents } from './apiEvents';
|
|||||||
import { fetchGroups } from './apiGroups';
|
import { fetchGroups } from './apiGroups';
|
||||||
import { getGroupColor } from './groupColors';
|
import { getGroupColor } from './groupColors';
|
||||||
import { deleteEvent } from './apiEvents';
|
import { deleteEvent } from './apiEvents';
|
||||||
|
import CustomEventModal from './components/CustomEventModal';
|
||||||
|
|
||||||
// Typ für Gruppe ergänzen
|
// Typ für Gruppe ergänzen
|
||||||
type Group = {
|
type Group = {
|
||||||
@@ -94,6 +95,8 @@ 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);
|
||||||
const [events, setEvents] = useState<Event[]>([]);
|
const [events, setEvents] = useState<Event[]>([]);
|
||||||
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
|
const [modalInitialData, setModalInitialData] = useState({});
|
||||||
|
|
||||||
// Gruppen laden
|
// Gruppen laden
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -129,8 +132,6 @@ const Appointments: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}, [selectedGroupId]);
|
}, [selectedGroupId]);
|
||||||
|
|
||||||
console.log('Aktuelle Events:', events);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold mb-4">Terminmanagement</h1>
|
<h1 className="text-2xl font-bold mb-4">Terminmanagement</h1>
|
||||||
@@ -153,6 +154,26 @@ const Appointments: React.FC = () => {
|
|||||||
style={{ flex: 1 }}
|
style={{ flex: 1 }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
className="e-btn e-success mb-4"
|
||||||
|
onClick={() => {
|
||||||
|
setModalInitialData({});
|
||||||
|
setModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Neuen Termin anlegen
|
||||||
|
</button>
|
||||||
|
<CustomEventModal
|
||||||
|
open={modalOpen}
|
||||||
|
onClose={() => 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 ?? ''}
|
||||||
|
/>
|
||||||
<ScheduleComponent
|
<ScheduleComponent
|
||||||
key={selectedGroupId}
|
key={selectedGroupId}
|
||||||
height="750px"
|
height="750px"
|
||||||
@@ -161,6 +182,16 @@ const Appointments: React.FC = () => {
|
|||||||
eventSettings={{
|
eventSettings={{
|
||||||
dataSource: events,
|
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) => {
|
eventRendered={(args: EventRenderedArgs) => {
|
||||||
if (selectedGroupId && args.data && args.data.Id) {
|
if (selectedGroupId && args.data && args.data.Id) {
|
||||||
const groupColor = getGroupColor(selectedGroupId, groups);
|
const groupColor = getGroupColor(selectedGroupId, groups);
|
||||||
|
|||||||
247
dashboard/src/components/CustomEventModal.tsx
Normal file
247
dashboard/src/components/CustomEventModal.tsx
Normal file
@@ -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<CustomEventData>;
|
||||||
|
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<CustomEventModalProps> = ({
|
||||||
|
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<number[]>(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 (
|
||||||
|
<DialogComponent
|
||||||
|
visible={open}
|
||||||
|
width="800px"
|
||||||
|
header={() => (
|
||||||
|
<div>
|
||||||
|
Neuen Termin anlegen
|
||||||
|
{groupName && (
|
||||||
|
<span style={{ fontWeight: 400, fontSize: 16, marginLeft: 16, color: '#888' }}>
|
||||||
|
für Raumgruppe: <b>{groupName}</b>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
showCloseIcon={true}
|
||||||
|
close={onClose}
|
||||||
|
isModal={true}
|
||||||
|
footerTemplate={() => (
|
||||||
|
<div className="flex gap-2 justify-end">
|
||||||
|
<button className="e-btn e-danger" onClick={onClose}>
|
||||||
|
Schließen
|
||||||
|
</button>
|
||||||
|
<button className="e-btn e-success" onClick={handleSave}>
|
||||||
|
Termin(e) speichern
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div style={{ padding: '24px' }}>
|
||||||
|
<div style={{ display: 'flex', gap: 24, flexWrap: 'wrap' }}>
|
||||||
|
<div style={{ flex: 1, minWidth: 260 }}>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<TextBoxComponent
|
||||||
|
placeholder="Titel"
|
||||||
|
floatLabelType="Auto"
|
||||||
|
value={title}
|
||||||
|
change={e => setTitle(e.value)}
|
||||||
|
/>
|
||||||
|
{errors.title && <div style={{ color: 'red', fontSize: 12 }}>{errors.title}</div>}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<DatePickerComponent
|
||||||
|
placeholder="Startdatum"
|
||||||
|
floatLabelType="Auto"
|
||||||
|
value={startDate ?? undefined}
|
||||||
|
change={e => setStartDate(e.value)}
|
||||||
|
/>
|
||||||
|
{errors.startDate && (
|
||||||
|
<div style={{ color: 'red', fontSize: 12 }}>{errors.startDate}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<TimePickerComponent
|
||||||
|
placeholder="Startzeit"
|
||||||
|
floatLabelType="Auto"
|
||||||
|
value={startTime}
|
||||||
|
step={30}
|
||||||
|
change={e => setStartTime(e.value)}
|
||||||
|
/>
|
||||||
|
{errors.startTime && (
|
||||||
|
<div style={{ color: 'red', fontSize: 12 }}>{errors.startTime}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<TimePickerComponent
|
||||||
|
placeholder="Endzeit"
|
||||||
|
floatLabelType="Auto"
|
||||||
|
value={endTime}
|
||||||
|
step={30}
|
||||||
|
change={e => setEndTime(e.value)}
|
||||||
|
/>
|
||||||
|
{errors.endTime && (
|
||||||
|
<div style={{ color: 'red', fontSize: 12 }}>{errors.endTime}</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 style={{ flex: 1, minWidth: 260 }}>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<CheckBoxComponent
|
||||||
|
label="Wiederholender Termin"
|
||||||
|
checked={repeat}
|
||||||
|
change={e => setRepeat(e.checked)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<MultiSelectComponent
|
||||||
|
key={repeat ? 'enabled' : 'disabled'}
|
||||||
|
dataSource={weekdayOptions}
|
||||||
|
fields={{ text: 'label', value: 'value' }}
|
||||||
|
placeholder="Wochentage"
|
||||||
|
value={weekdays}
|
||||||
|
change={e => setWeekdays(e.value as number[])}
|
||||||
|
disabled={!repeat}
|
||||||
|
showDropDownIcon={true}
|
||||||
|
closePopupOnSelect={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<DatePickerComponent
|
||||||
|
key={repeat ? 'enabled' : 'disabled'}
|
||||||
|
placeholder="Wiederholung bis"
|
||||||
|
floatLabelType="Auto"
|
||||||
|
value={repeatUntil ?? undefined}
|
||||||
|
change={e => setRepeatUntil(e.value)}
|
||||||
|
disabled={!repeat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginBottom: 12 }}>
|
||||||
|
<CheckBoxComponent
|
||||||
|
label="Ferientage berücksichtigen"
|
||||||
|
checked={skipHolidays}
|
||||||
|
change={e => setSkipHolidays(e.checked)}
|
||||||
|
disabled={!repeat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogComponent>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CustomEventModal;
|
||||||
Reference in New Issue
Block a user