fix: redesign recurring delete as overlay with loading indicator
Replace inline delete buttons (Dieses/Künftige/Alle) with a proper overlay dialog matching the recurring edit overlay style. Shows animated dots on the active delete option while processing. Other options dim during deletion. v1.75.7 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
de3b4a103e
commit
51301bc497
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "my-weekly-todo-list",
|
"name": "my-weekly-todo-list",
|
||||||
"version": "1.75.6",
|
"version": "1.75.7",
|
||||||
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -161,6 +161,7 @@ export default function CalendarEventModal({
|
|||||||
const [allDay, setAllDay] = useState(!!event?.allDay);
|
const [allDay, setAllDay] = useState(!!event?.allDay);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [isDeleting, setIsDeleting] = useState(false);
|
const [isDeleting, setIsDeleting] = useState(false);
|
||||||
|
const [deletingMode, setDeletingMode] = useState<string | null>(null);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [isCalendarSelectorOpen, setIsCalendarSelectorOpen] = useState(false);
|
const [isCalendarSelectorOpen, setIsCalendarSelectorOpen] = useState(false);
|
||||||
const calendarSelectorRef = useRef<HTMLDivElement>(null);
|
const calendarSelectorRef = useRef<HTMLDivElement>(null);
|
||||||
@ -269,12 +270,14 @@ export default function CalendarEventModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
setIsDeleting(true);
|
setIsDeleting(true);
|
||||||
|
setDeletingMode(mode || 'all');
|
||||||
try {
|
try {
|
||||||
await onDelete(event.id, event.calendarId, mode || 'all');
|
await onDelete(event.id, event.calendarId, mode || 'all');
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.message || 'Failed to delete event');
|
setError(err.message || 'Failed to delete event');
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
|
setDeletingMode(null);
|
||||||
setIsDeleteConfirming(false);
|
setIsDeleteConfirming(false);
|
||||||
setShowRecurringDeleteOptions(false);
|
setShowRecurringDeleteOptions(false);
|
||||||
}
|
}
|
||||||
@ -874,34 +877,21 @@ export default function CalendarEventModal({
|
|||||||
{isDeleting ? '...' : isDeleteConfirming ? (language === 'de' ? 'Bestätigen?' : 'Confirm?') : (language === 'de' ? 'Löschen' : 'Delete')}
|
{isDeleting ? '...' : isDeleteConfirming ? (language === 'de' ? 'Bestätigen?' : 'Confirm?') : (language === 'de' ? 'Löschen' : 'Delete')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{event && onDelete && showRecurringDeleteOptions && (
|
{event && onDelete && showRecurringDeleteOptions && !isDeleting && (
|
||||||
<div style={{ display: 'flex', gap: '3px', flexWrap: 'wrap', alignItems: 'center' }}>
|
<button
|
||||||
{[
|
onClick={() => setShowRecurringDeleteOptions(false)}
|
||||||
{ mode: 'this', label: language === 'de' ? 'Dieses' : 'This' },
|
style={{
|
||||||
{ mode: 'future', label: language === 'de' ? 'Künftige' : 'Future' },
|
padding: '5px 10px', background: 'none', color: 'var(--weekly-text-light)',
|
||||||
{ mode: 'all', label: language === 'de' ? 'Alle' : 'All' },
|
border: 'none', fontSize: '0.8rem', cursor: 'pointer',
|
||||||
].map(({ mode, label }) => (
|
}}
|
||||||
<button
|
>
|
||||||
key={mode}
|
{language === 'de' ? 'Abbrechen' : 'Cancel'}
|
||||||
onClick={() => handleDelete(mode)}
|
</button>
|
||||||
disabled={isDeleting}
|
)}
|
||||||
style={{
|
{event && onDelete && isDeleting && (
|
||||||
padding: '3px 7px', fontSize: '0.7rem', fontWeight: 500,
|
<span style={{ fontSize: '0.8rem', color: '#ef4444', fontWeight: 500, padding: '5px 10px' }}>
|
||||||
color: '#ef4444', background: 'rgba(239,68,68,0.08)',
|
{language === 'de' ? 'Lösche' : 'Deleting'}<AnimatedDots />
|
||||||
border: '1px solid rgba(239,68,68,0.2)', borderRadius: '5px',
|
</span>
|
||||||
cursor: 'pointer', opacity: isDeleting ? 0.5 : 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
<button
|
|
||||||
onClick={() => setShowRecurringDeleteOptions(false)}
|
|
||||||
style={{ padding: '2px', color: 'var(--weekly-text-light)', background: 'none', border: 'none', cursor: 'pointer' }}
|
|
||||||
>
|
|
||||||
<X size={12} />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{!event && (
|
{!event && (
|
||||||
<button onClick={onClose} style={{ padding: '5px 10px', fontSize: '0.8rem', color: 'var(--weekly-text-light)', border: 'none', background: 'none', cursor: 'pointer' }}>
|
<button onClick={onClose} style={{ padding: '5px 10px', fontSize: '0.8rem', color: 'var(--weekly-text-light)', border: 'none', background: 'none', cursor: 'pointer' }}>
|
||||||
@ -1000,6 +990,63 @@ export default function CalendarEventModal({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{/* Recurring delete mode overlay */}
|
||||||
|
{showRecurringDeleteOptions && (
|
||||||
|
<div style={{
|
||||||
|
position: 'fixed', inset: 0, zIndex: 3000,
|
||||||
|
display: 'flex', justifyContent: 'center', alignItems: 'center',
|
||||||
|
background: 'rgba(0,0,0,0.35)', backdropFilter: 'blur(2px)',
|
||||||
|
}} onClick={() => !isDeleting && setShowRecurringDeleteOptions(false)}>
|
||||||
|
<div style={{
|
||||||
|
background: 'var(--weekly-bg, white)', color: 'var(--weekly-text, #333)',
|
||||||
|
borderRadius: 12, width: '90%', maxWidth: 340,
|
||||||
|
boxShadow: '0 12px 40px rgba(0,0,0,0.25)',
|
||||||
|
overflow: 'hidden',
|
||||||
|
}} onClick={e => e.stopPropagation()}>
|
||||||
|
<div style={{
|
||||||
|
padding: '14px 16px 10px', fontWeight: 700, fontSize: '0.9rem',
|
||||||
|
borderBottom: '2px solid #ef4444',
|
||||||
|
}}>
|
||||||
|
{language === 'de' ? 'Wiederkehrendes Ereignis löschen' : 'Delete recurring event'}
|
||||||
|
{!isDeleting && <button onClick={() => setShowRecurringDeleteOptions(false)} style={{
|
||||||
|
float: 'right', background: 'none', border: 'none', cursor: 'pointer',
|
||||||
|
color: 'var(--weekly-text-light)', padding: 0,
|
||||||
|
}}><X size={16} /></button>}
|
||||||
|
</div>
|
||||||
|
<div style={{ padding: '4px 0' }}>
|
||||||
|
{([
|
||||||
|
{ value: 'this' as const, title: language === 'de' ? 'Nur dieses Ereignis' : 'This event only', desc: language === 'de' ? 'Alle anderen bleiben erhalten.' : 'All other events stay the same.' },
|
||||||
|
{ value: 'future' as const, title: language === 'de' ? 'Dieses und folgende' : 'This and following', desc: language === 'de' ? 'Dieses und alle zukünftigen werden gelöscht.' : 'This and all future events will be deleted.' },
|
||||||
|
{ value: 'all' as const, title: language === 'de' ? 'Alle Ereignisse' : 'All events', desc: language === 'de' ? 'Die gesamte Serie wird gelöscht.' : 'The entire series will be deleted.' },
|
||||||
|
]).map(opt => (
|
||||||
|
<button
|
||||||
|
key={opt.value}
|
||||||
|
onClick={() => handleDelete(opt.value)}
|
||||||
|
disabled={isDeleting}
|
||||||
|
style={{
|
||||||
|
display: 'flex', gap: 10, padding: '10px 16px', cursor: 'pointer',
|
||||||
|
width: '100%', textAlign: 'left', background: 'none',
|
||||||
|
border: 'none', borderBottom: '1px solid var(--weekly-border, #eee)',
|
||||||
|
color: 'var(--weekly-text, #333)',
|
||||||
|
opacity: isDeleting && deletingMode !== opt.value ? 0.4 : 1,
|
||||||
|
alignItems: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<div style={{ fontWeight: 600, fontSize: '0.82rem', color: '#ef4444' }}>{opt.title}</div>
|
||||||
|
<div style={{ fontSize: '0.72rem', opacity: 0.5, marginTop: 1 }}>{opt.desc}</div>
|
||||||
|
</div>
|
||||||
|
{isDeleting && deletingMode === opt.value && (
|
||||||
|
<span style={{ fontSize: '0.8rem', color: '#ef4444', fontWeight: 600 }}>
|
||||||
|
<AnimatedDots />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user