docs: clarify event deletion flows and dialog handling for all event types
- Documented unified deletion process for single, single-in-series, and recurring series events - Explained custom dialog interception of Syncfusion RecurrenceAlert and DeleteAlert - Updated both README.md and .github/copilot-instructions.md to match current frontend logic
This commit is contained in:
@@ -32,8 +32,12 @@ export async function fetchEventById(eventId: string) {
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function deleteEvent(eventId: string) {
|
||||
const res = await fetch(`/api/events/${encodeURIComponent(eventId)}`, {
|
||||
export async function deleteEvent(eventId: string, force: boolean = false) {
|
||||
const url = force
|
||||
? `/api/events/${encodeURIComponent(eventId)}?force=1`
|
||||
: `/api/events/${encodeURIComponent(eventId)}`;
|
||||
|
||||
const res = await fetch(url, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
const data = await res.json();
|
||||
|
||||
@@ -75,22 +75,6 @@ type Event = {
|
||||
RecurrenceException?: string;
|
||||
};
|
||||
|
||||
type RawEvent = {
|
||||
Id: string;
|
||||
Subject: string;
|
||||
StartTime: string;
|
||||
EndTime: string;
|
||||
IsAllDay: boolean;
|
||||
MediaId?: string | number;
|
||||
Icon?: string; // <--- Icon ergänzen!
|
||||
Type?: string;
|
||||
OccurrenceOfId?: string;
|
||||
RecurrenceRule?: string | null;
|
||||
RecurrenceEnd?: string | null;
|
||||
SkipHolidays?: boolean;
|
||||
RecurrenceException?: string;
|
||||
};
|
||||
|
||||
// CLDR-Daten laden (direkt die JSON-Objekte übergeben)
|
||||
loadCldr(
|
||||
caGregorian as object,
|
||||
@@ -208,6 +192,7 @@ const Appointments: React.FC = () => {
|
||||
const [periods, setPeriods] = React.useState<{ id: number; label: string }[]>([]);
|
||||
const [activePeriodId, setActivePeriodId] = React.useState<number | null>(null);
|
||||
|
||||
|
||||
// Confirmation dialog state
|
||||
const [confirmDialogOpen, setConfirmDialogOpen] = React.useState(false);
|
||||
const [confirmDialogData, setConfirmDialogData] = React.useState<{
|
||||
@@ -217,6 +202,44 @@ const Appointments: React.FC = () => {
|
||||
onCancel: () => void;
|
||||
} | null>(null);
|
||||
|
||||
// Recurring deletion dialog state
|
||||
const [recurringDeleteDialogOpen, setRecurringDeleteDialogOpen] = React.useState(false);
|
||||
const [recurringDeleteData, setRecurringDeleteData] = React.useState<{
|
||||
event: Event;
|
||||
onChoice: (choice: 'series' | 'occurrence' | 'cancel') => void;
|
||||
} | null>(null);
|
||||
|
||||
// Series deletion final confirmation dialog (after choosing 'series')
|
||||
const [seriesConfirmDialogOpen, setSeriesConfirmDialogOpen] = React.useState(false);
|
||||
const [seriesConfirmData, setSeriesConfirmData] = React.useState<{
|
||||
event: Event;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
} | null>(null);
|
||||
|
||||
const showSeriesConfirmDialog = (event: Event): Promise<boolean> => {
|
||||
return new Promise(resolve => {
|
||||
console.log('[Delete] showSeriesConfirmDialog invoked for event', event.Id);
|
||||
// Defer open to next tick to avoid race with closing previous dialog
|
||||
setSeriesConfirmData({
|
||||
event,
|
||||
onConfirm: () => {
|
||||
console.log('[Delete] Series confirm dialog: confirmed');
|
||||
setSeriesConfirmDialogOpen(false);
|
||||
resolve(true);
|
||||
},
|
||||
onCancel: () => {
|
||||
console.log('[Delete] Series confirm dialog: cancelled');
|
||||
setSeriesConfirmDialogOpen(false);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
setTimeout(() => {
|
||||
setSeriesConfirmDialogOpen(true);
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to show confirmation dialog
|
||||
const showConfirmDialog = (title: string, message: string): Promise<boolean> => {
|
||||
return new Promise((resolve) => {
|
||||
@@ -236,6 +259,20 @@ const Appointments: React.FC = () => {
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function to show recurring event deletion dialog
|
||||
const showRecurringDeleteDialog = (event: Event): Promise<'series' | 'occurrence' | 'cancel'> => {
|
||||
return new Promise((resolve) => {
|
||||
setRecurringDeleteData({
|
||||
event,
|
||||
onChoice: (choice: 'series' | 'occurrence' | 'cancel') => {
|
||||
setRecurringDeleteDialogOpen(false);
|
||||
resolve(choice);
|
||||
}
|
||||
});
|
||||
setRecurringDeleteDialogOpen(true);
|
||||
});
|
||||
};
|
||||
|
||||
// Gruppen laden
|
||||
useEffect(() => {
|
||||
fetchGroups()
|
||||
@@ -563,6 +600,22 @@ const Appointments: React.FC = () => {
|
||||
updateHolidaysInView();
|
||||
}, [holidays, updateHolidaysInView]);
|
||||
|
||||
// Inject global z-index fixes for dialogs (only once)
|
||||
React.useEffect(() => {
|
||||
if (typeof document !== 'undefined' && !document.getElementById('series-dialog-zfix')) {
|
||||
const style = document.createElement('style');
|
||||
style.id = 'series-dialog-zfix';
|
||||
style.textContent = `\n .final-series-dialog.e-dialog { z-index: 25000 !important; }\n .final-series-dialog + .e-dlg-overlay { z-index: 24990 !important; }\n .recurring-delete-dialog.e-dialog { z-index: 24000 !important; }\n .recurring-delete-dialog + .e-dlg-overlay { z-index: 23990 !important; }\n `;
|
||||
document.head.appendChild(style);
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (seriesConfirmDialogOpen) {
|
||||
console.log('[Delete] Series confirm dialog now visible');
|
||||
}
|
||||
}, [seriesConfirmDialogOpen]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, marginBottom: 16 }}>Terminmanagement</h1>
|
||||
@@ -733,17 +786,25 @@ const Appointments: React.FC = () => {
|
||||
setModalOpen(false);
|
||||
setEditMode(false); // Editiermodus zurücksetzen
|
||||
}}
|
||||
onSave={async () => {
|
||||
onSave={async (eventData) => {
|
||||
console.log('Modal saved event data:', eventData);
|
||||
|
||||
// The CustomEventModal already handled the API calls internally
|
||||
// For now, just refresh the data (the recurring event logic is handled in the modal itself)
|
||||
console.log('Modal operation completed, refreshing data');
|
||||
|
||||
setModalOpen(false);
|
||||
setEditMode(false);
|
||||
|
||||
// Force immediate data refresh
|
||||
// Refresh the data and scheduler
|
||||
await fetchAndSetEvents();
|
||||
|
||||
// Defer refresh to avoid interfering with current React commit
|
||||
setTimeout(() => {
|
||||
scheduleRef.current?.refreshEvents?.();
|
||||
}, 0);
|
||||
|
||||
console.log('Modal save cycle completed - data refreshed');
|
||||
}}
|
||||
initialData={modalInitialData}
|
||||
groupName={groups.find(g => g.id === selectedGroupId) ?? { id: selectedGroupId, name: '' }}
|
||||
@@ -781,6 +842,7 @@ const Appointments: React.FC = () => {
|
||||
|
||||
// Persist UI-driven changes (drag/resize/editor fallbacks)
|
||||
if (args && args.requestType === 'eventChanged') {
|
||||
console.log('actionComplete: Processing eventChanged from direct UI interaction (drag/resize)');
|
||||
try {
|
||||
type SchedulerEvent = Partial<Event> & {
|
||||
Id?: string | number;
|
||||
@@ -810,18 +872,64 @@ const Appointments: React.FC = () => {
|
||||
payload.end = e.toISOString();
|
||||
}
|
||||
|
||||
// Single occurrence change from a recurring master (our manual expansion marks OccurrenceOfId)
|
||||
if (changed.OccurrenceOfId) {
|
||||
if (!changed.StartTime) return; // cannot determine occurrence date
|
||||
// Check if this is a single occurrence edit by looking at the original master event
|
||||
const eventId = String(changed.Id);
|
||||
|
||||
// Debug logging to understand what Syncfusion sends
|
||||
console.log('actionComplete eventChanged - Debug info:', {
|
||||
eventId,
|
||||
changedRecurrenceRule: changed.RecurrenceRule,
|
||||
changedRecurrenceID: changed.RecurrenceID,
|
||||
changedStartTime: changed.StartTime,
|
||||
changedSubject: changed.Subject,
|
||||
payload,
|
||||
fullChangedObject: JSON.stringify(changed, null, 2)
|
||||
});
|
||||
|
||||
// First, fetch the master event to check if it has a RecurrenceRule
|
||||
let masterEvent = null;
|
||||
let isMasterRecurring = false;
|
||||
try {
|
||||
masterEvent = await fetchEventById(eventId);
|
||||
isMasterRecurring = !!masterEvent.RecurrenceRule;
|
||||
console.log('Master event info:', {
|
||||
masterRecurrenceRule: masterEvent.RecurrenceRule,
|
||||
masterStartTime: masterEvent.StartTime,
|
||||
isMasterRecurring
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch master event:', err);
|
||||
}
|
||||
|
||||
// KEY DETECTION: Syncfusion sets RecurrenceID when editing a single occurrence
|
||||
const hasRecurrenceID = 'RecurrenceID' in changed && !!(changed as Record<string, unknown>).RecurrenceID;
|
||||
|
||||
// When dragging a single occurrence, Syncfusion may not provide RecurrenceID
|
||||
// but it won't provide RecurrenceRule on the changed object
|
||||
const isRecurrenceRuleStripped = isMasterRecurring && !changed.RecurrenceRule;
|
||||
|
||||
console.log('FINAL Edit detection:', {
|
||||
isMasterRecurring,
|
||||
hasRecurrenceID,
|
||||
isRecurrenceRuleStripped,
|
||||
masterHasRule: masterEvent?.RecurrenceRule ? 'YES' : 'NO',
|
||||
changedHasRule: changed.RecurrenceRule ? 'YES' : 'NO',
|
||||
decision: (hasRecurrenceID || isRecurrenceRuleStripped) ? 'DETACH' : 'UPDATE'
|
||||
});
|
||||
|
||||
// SINGLE OCCURRENCE EDIT detection:
|
||||
// 1. RecurrenceID is set (explicit single occurrence marker)
|
||||
// 2. OR master has RecurrenceRule but changed object doesn't (stripped during single edit)
|
||||
if (isMasterRecurring && (hasRecurrenceID || isRecurrenceRuleStripped) && changed.StartTime) {
|
||||
// This is a single occurrence edit - detach it
|
||||
console.log('Detaching single occurrence...');
|
||||
const occStart = changed.StartTime instanceof Date ? changed.StartTime : new Date(changed.StartTime as string);
|
||||
const occDate = occStart.toISOString().split('T')[0];
|
||||
await detachEventOccurrence(Number(changed.OccurrenceOfId), occDate, payload);
|
||||
} else if (changed.RecurrenceRule) {
|
||||
// Change to master series (non-manually expanded recurrences)
|
||||
await updateEvent(String(changed.Id), payload);
|
||||
} else if (changed.Id) {
|
||||
// Regular single event
|
||||
await updateEvent(String(changed.Id), payload);
|
||||
await detachEventOccurrence(Number(eventId), occDate, payload);
|
||||
} else {
|
||||
// This is a series edit or regular single event
|
||||
console.log('Updating event directly...');
|
||||
await updateEvent(eventId, payload);
|
||||
}
|
||||
|
||||
// Refresh events and scheduler cache after persisting
|
||||
@@ -860,6 +968,96 @@ const Appointments: React.FC = () => {
|
||||
setModalOpen(true);
|
||||
}}
|
||||
popupOpen={async args => {
|
||||
// Intercept Syncfusion's recurrence choice dialog (RecurrenceAlert) and replace with custom
|
||||
if (args.type === 'RecurrenceAlert') {
|
||||
// Prevent default Syncfusion dialog
|
||||
args.cancel = true;
|
||||
const event = args.data;
|
||||
console.log('[RecurrenceAlert] Intercepted for event', event?.Id);
|
||||
if (!event) return;
|
||||
|
||||
// Show our custom recurring delete dialog
|
||||
const choice = await showRecurringDeleteDialog(event);
|
||||
let didDelete = false;
|
||||
try {
|
||||
if (choice === 'series') {
|
||||
const confirmed = await showSeriesConfirmDialog(event);
|
||||
if (confirmed) {
|
||||
await deleteEvent(event.Id, true);
|
||||
didDelete = true;
|
||||
}
|
||||
} else if (choice === 'occurrence') {
|
||||
const occurrenceDate = event.StartTime instanceof Date
|
||||
? event.StartTime.toISOString().split('T')[0]
|
||||
: new Date(event.StartTime).toISOString().split('T')[0];
|
||||
// If this is the master being edited for a single occurrence, treat as occurrence delete
|
||||
if (event.OccurrenceOfId) {
|
||||
await deleteEventOccurrence(event.OccurrenceOfId, occurrenceDate);
|
||||
} else {
|
||||
await deleteEventOccurrence(event.Id, occurrenceDate);
|
||||
}
|
||||
didDelete = true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Fehler bei RecurrenceAlert Löschung:', e);
|
||||
}
|
||||
if (didDelete) {
|
||||
await fetchAndSetEvents();
|
||||
setTimeout(() => scheduleRef.current?.refreshEvents?.(), 0);
|
||||
}
|
||||
return; // handled
|
||||
}
|
||||
if (args.type === 'DeleteAlert') {
|
||||
// Handle delete confirmation directly here to avoid multiple dialogs
|
||||
args.cancel = true;
|
||||
const event = args.data;
|
||||
let didDelete = false;
|
||||
|
||||
try {
|
||||
// 1) Single occurrence of a recurring event → delete occurrence only
|
||||
if (event.OccurrenceOfId && event.StartTime) {
|
||||
console.log('[Delete] Deleting single occurrence via OccurrenceOfId path', {
|
||||
eventId: event.Id,
|
||||
masterId: event.OccurrenceOfId,
|
||||
start: event.StartTime
|
||||
});
|
||||
const occurrenceDate = event.StartTime instanceof Date
|
||||
? event.StartTime.toISOString().split('T')[0]
|
||||
: new Date(event.StartTime).toISOString().split('T')[0];
|
||||
await deleteEventOccurrence(event.OccurrenceOfId, occurrenceDate);
|
||||
didDelete = true;
|
||||
}
|
||||
// 2) Recurring master event deletion → show deletion choice dialog
|
||||
else if (event.RecurrenceRule) {
|
||||
// For recurring events the RecurrenceAlert should have been intercepted.
|
||||
console.log('[DeleteAlert] Recurring event delete without RecurrenceAlert (fallback)');
|
||||
const confirmed = await showSeriesConfirmDialog(event);
|
||||
if (confirmed) {
|
||||
await deleteEvent(event.Id, true);
|
||||
didDelete = true;
|
||||
}
|
||||
}
|
||||
// 3) Single non-recurring event → delete normally with simple confirmation
|
||||
else {
|
||||
console.log('Deleting single non-recurring event:', event.Id);
|
||||
await deleteEvent(event.Id, false);
|
||||
didDelete = true;
|
||||
}
|
||||
|
||||
// Refresh events only if a deletion actually occurred
|
||||
if (didDelete) {
|
||||
await fetchAndSetEvents();
|
||||
setTimeout(() => {
|
||||
scheduleRef.current?.refreshEvents?.();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Löschen:', err);
|
||||
}
|
||||
return; // Exit early for delete operations
|
||||
}
|
||||
|
||||
if (args.type === 'Editor') {
|
||||
args.cancel = true;
|
||||
const event = args.data;
|
||||
@@ -943,9 +1141,11 @@ const Appointments: React.FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Fixed: Ensure OccurrenceOfId is set for recurring events in native recurrence mode
|
||||
|
||||
const modalData = {
|
||||
Id: (event.OccurrenceOfId && !isSingleOccurrence) ? event.OccurrenceOfId : event.Id, // Use master ID for series edit, occurrence ID for single edit
|
||||
OccurrenceOfId: event.OccurrenceOfId, // Master event ID if this is an occurrence
|
||||
OccurrenceOfId: event.OccurrenceOfId || (event.RecurrenceRule ? event.Id : undefined), // Master event ID - use current ID if it's a recurring master
|
||||
occurrenceDate: isSingleOccurrence ? event.StartTime : null, // Store occurrence date for single occurrence editing
|
||||
isSingleOccurrence,
|
||||
title: eventDataToUse.Subject,
|
||||
@@ -1029,54 +1229,14 @@ const Appointments: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
actionBegin={async (args: ActionEventArgs) => {
|
||||
// Delete operations are now handled in popupOpen to avoid multiple dialogs
|
||||
if (args.requestType === 'eventRemove') {
|
||||
// args.data ist ein Array von zu löschenden Events
|
||||
const toDelete = Array.isArray(args.data) ? args.data : [args.data];
|
||||
for (const ev of toDelete) {
|
||||
try {
|
||||
// 1) Single occurrence of a recurring event → delete occurrence only
|
||||
if (ev.OccurrenceOfId && ev.StartTime) {
|
||||
const occurrenceDate = ev.StartTime instanceof Date
|
||||
? ev.StartTime.toISOString().split('T')[0]
|
||||
: new Date(ev.StartTime).toISOString().split('T')[0];
|
||||
await deleteEventOccurrence(ev.OccurrenceOfId, occurrenceDate);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 2) Recurring master being removed unexpectedly → block deletion (safety)
|
||||
// Syncfusion can sometimes raise eventRemove during edits; do NOT delete the series here.
|
||||
if (ev.RecurrenceRule) {
|
||||
console.warn('Blocked deletion of recurring master event via eventRemove.');
|
||||
// If the user truly wants to delete the series, provide an explicit UI path.
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3) Single non-recurring event → delete normally
|
||||
await deleteEvent(ev.Id);
|
||||
} catch (err) {
|
||||
console.error('Fehler beim Löschen:', err);
|
||||
}
|
||||
}
|
||||
// Events nach Löschen neu laden
|
||||
if (selectedGroupId) {
|
||||
fetchEvents(selectedGroupId, showInactive)
|
||||
.then((data: RawEvent[]) => {
|
||||
const mapped: Event[] = data.map((e: RawEvent) => ({
|
||||
Id: e.Id,
|
||||
Subject: e.Subject,
|
||||
StartTime: parseEventDate(e.StartTime),
|
||||
EndTime: parseEventDate(e.EndTime),
|
||||
IsAllDay: e.IsAllDay,
|
||||
MediaId: e.MediaId,
|
||||
SkipHolidays: e.SkipHolidays ?? false,
|
||||
}));
|
||||
setEvents(mapped);
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
// Syncfusion soll das Event nicht selbst löschen
|
||||
// Cancel all delete operations here - they're handled in popupOpen
|
||||
args.cancel = true;
|
||||
} else if (
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
(args.requestType === 'eventCreate' || args.requestType === 'eventChange') &&
|
||||
!allowScheduleOnHolidays
|
||||
) {
|
||||
@@ -1156,6 +1316,167 @@ const Appointments: React.FC = () => {
|
||||
</div>
|
||||
</DialogComponent>
|
||||
)}
|
||||
|
||||
{/* Recurring Event Deletion Dialog */}
|
||||
{recurringDeleteDialogOpen && recurringDeleteData && (
|
||||
<DialogComponent
|
||||
target="#root"
|
||||
visible={recurringDeleteDialogOpen}
|
||||
width="500px"
|
||||
zIndex={18000}
|
||||
cssClass="recurring-delete-dialog"
|
||||
header={() => (
|
||||
<div style={{
|
||||
padding: '12px 20px',
|
||||
background: '#dc3545',
|
||||
color: 'white',
|
||||
fontWeight: 600,
|
||||
borderRadius: '6px 6px 0 0'
|
||||
}}>
|
||||
🗑️ Wiederkehrenden Termin löschen
|
||||
</div>
|
||||
)}
|
||||
showCloseIcon={true}
|
||||
close={() => recurringDeleteData.onChoice('cancel')}
|
||||
isModal={true}
|
||||
footerTemplate={() => (
|
||||
<div style={{ padding: '12px 20px', display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
|
||||
<button
|
||||
className="e-btn e-outline"
|
||||
onClick={() => recurringDeleteData.onChoice('cancel')}
|
||||
style={{ minWidth: '100px' }}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
className="e-btn e-warning"
|
||||
onClick={() => recurringDeleteData.onChoice('occurrence')}
|
||||
style={{ minWidth: '140px' }}
|
||||
>
|
||||
Nur diesen Termin
|
||||
</button>
|
||||
<button
|
||||
className="e-btn e-danger"
|
||||
onClick={() => recurringDeleteData.onChoice('series')}
|
||||
style={{ minWidth: '140px' }}
|
||||
>
|
||||
Gesamte Serie
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div style={{ padding: '24px', fontSize: '14px', lineHeight: 1.5 }}>
|
||||
<div style={{ marginBottom: '16px', fontSize: '16px', fontWeight: 500 }}>
|
||||
Sie möchten einen wiederkehrenden Termin löschen:
|
||||
</div>
|
||||
<div style={{
|
||||
background: '#f8f9fa',
|
||||
border: '1px solid #e9ecef',
|
||||
borderRadius: '6px',
|
||||
padding: '12px',
|
||||
marginBottom: '20px',
|
||||
fontWeight: 500
|
||||
}}>
|
||||
📅 {recurringDeleteData.event.Subject}
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<strong>Was möchten Sie löschen?</strong>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '12px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
|
||||
<span style={{ color: '#fd7e14', fontSize: '16px' }}>📝</span>
|
||||
<div>
|
||||
<strong>Nur diesen Termin:</strong> Löscht nur den ausgewählten Termin. Die anderen Termine der Serie bleiben bestehen.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ marginBottom: '20px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'flex-start', gap: '8px' }}>
|
||||
<span style={{ color: '#dc3545', fontSize: '16px' }}>⚠️</span>
|
||||
<div>
|
||||
<strong>Gesamte Serie:</strong> Löscht <u>alle Termine</u> dieser Wiederholungsserie. Diese Aktion kann nicht rückgängig gemacht werden!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</DialogComponent>
|
||||
)}
|
||||
|
||||
{/* Final Series Deletion Confirmation Dialog */}
|
||||
{seriesConfirmDialogOpen && seriesConfirmData && (
|
||||
<DialogComponent
|
||||
target="#root"
|
||||
visible={seriesConfirmDialogOpen}
|
||||
width="520px"
|
||||
zIndex={19000}
|
||||
cssClass="final-series-dialog"
|
||||
header={() => (
|
||||
<div style={{
|
||||
padding: '12px 20px',
|
||||
background: '#b91c1c',
|
||||
color: 'white',
|
||||
fontWeight: 600,
|
||||
borderRadius: '6px 6px 0 0'
|
||||
}}>
|
||||
⚠️ Serie endgültig löschen
|
||||
</div>
|
||||
)}
|
||||
showCloseIcon={true}
|
||||
close={() => seriesConfirmData.onCancel()}
|
||||
isModal={true}
|
||||
footerTemplate={() => (
|
||||
<div style={{ padding: '12px 20px', display: 'flex', gap: '12px', justifyContent: 'flex-end' }}>
|
||||
<button
|
||||
className="e-btn e-outline"
|
||||
onClick={seriesConfirmData.onCancel}
|
||||
style={{ minWidth: '110px' }}
|
||||
>
|
||||
Abbrechen
|
||||
</button>
|
||||
<button
|
||||
className="e-btn e-danger"
|
||||
onClick={seriesConfirmData.onConfirm}
|
||||
style={{ minWidth: '180px' }}
|
||||
>
|
||||
Serie löschen
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<div style={{ padding: '24px', fontSize: '14px', lineHeight: 1.55 }}>
|
||||
<div style={{ marginBottom: '14px' }}>
|
||||
Sie sind dabei die <strong>gesamte Terminserie</strong> zu löschen:
|
||||
</div>
|
||||
<div style={{
|
||||
background: '#fef2f2',
|
||||
border: '1px solid #fecaca',
|
||||
borderRadius: 6,
|
||||
padding: '10px 14px',
|
||||
marginBottom: 18,
|
||||
fontWeight: 500
|
||||
}}>
|
||||
📅 {seriesConfirmData.event.Subject}
|
||||
</div>
|
||||
<ul style={{ margin: '0 0 18px 18px', padding: 0 }}>
|
||||
<li>Alle zukünftigen und vergangenen Vorkommen werden entfernt.</li>
|
||||
<li>Dieser Vorgang kann nicht rückgängig gemacht werden.</li>
|
||||
<li>Einzelne bereits abgetrennte Einzeltermine bleiben bestehen.</li>
|
||||
</ul>
|
||||
<div style={{
|
||||
background: '#fff7ed',
|
||||
border: '1px solid #ffedd5',
|
||||
borderRadius: 6,
|
||||
padding: '10px 14px',
|
||||
fontSize: 13
|
||||
}}>
|
||||
Wenn Sie nur einen einzelnen Termin entfernen möchten, schließen Sie diesen Dialog und wählen Sie im vorherigen Dialog "Nur diesen Termin".
|
||||
</div>
|
||||
</div>
|
||||
</DialogComponent>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user