feat: Allow event owners to delete their own events and admins to delete any event with UI action buttons and confirmation dialog

This commit is contained in:
Richard
2026-07-31 11:19:49 +02:00
parent e2b311b634
commit 782d4d72a3
12 changed files with 382 additions and 300 deletions
+31 -4
View File
@@ -264,6 +264,29 @@ export default function App() {
}
};
const handleDeleteEvent = async (eventObj) => {
if (!eventObj || !eventObj.id) return;
if (!window.confirm(`Möchtest du die Veranstaltung "${eventObj.title}" und alle zugehörigen Schichten wirklich unwiderruflich löschen?`)) {
return;
}
try {
await apiFetch(`/events/${eventObj.id}/`, {
method: 'DELETE'
});
showNotification(`Veranstaltung "${eventObj.title}" wurde erfolgreich gelöscht.`);
if (selectedEventId === eventObj.id) {
setSelectedEventId(null);
}
if (activeTab === 'event-editor') {
setActiveTab('home');
}
refreshEvents();
} catch (err) {
showNotification(`Fehler beim Löschen: ${err.message}`);
}
};
const [eventToEdit, setEventToEdit] = useState(null);
const handleSaveEventSubmit = async (eventPayload) => {
@@ -444,6 +467,7 @@ export default function App() {
setEventToEdit(evt);
setActiveTab('event-editor');
}}
onDeleteEvent={handleDeleteEvent}
branding={branding}
/>
) : activeTab === 'calendar' ? (
@@ -467,8 +491,10 @@ export default function App() {
<EventEditorPage
eventToEdit={eventToEdit}
skills={skills}
user={user}
onBack={() => setActiveTab('schedule')}
onSubmit={handleSaveEventSubmit}
onDeleteEvent={handleDeleteEvent}
/>
) : activeTab === 'templates' ? (
<TemplatesPage
@@ -522,6 +548,11 @@ export default function App() {
user={user}
onSignupClick={handleShiftSignupClick}
onCancelClick={handleCancelSignup}
onEditEvent={(evt) => {
setEventToEdit(evt);
setActiveTab('event-editor');
}}
onDeleteEvent={handleDeleteEvent}
onRemoveUserFromShift={async (shift, signupId) => {
try {
const res = await apiFetch(`/shifts/${shift.id}/signup/${signupId}/`, {
@@ -539,10 +570,6 @@ export default function App() {
onGenerateClaimLink={handleGenerateClaimLink}
onExportPdf={handleExportPdf}
onPrintView={handlePrintView}
onEditEvent={(evt) => {
setEventToEdit(evt);
setActiveTab('event-editor');
}}
/>
</div>
)}
+12 -2
View File
@@ -1,7 +1,7 @@
/* Hallmark · pre-emit critique: P5 H5 E5 S5 R5 V5 */
/* Hallmark · component: shift-matrix-table · genre: editorial · theme: Atelier (Dark & Light) · studied-DNA: https://inihaus.de */
import React from 'react';
import { Clock, UserCheck, Shield, Printer, Download, Award, CheckCircle2, UserX, UserPlus, MapPin, Edit3 } from 'lucide-react';
import { Clock, UserCheck, Shield, Printer, Download, Award, CheckCircle2, UserX, UserPlus, MapPin, Edit3, Trash2 } from 'lucide-react';
export default function ShiftMatrixTable({
event,
@@ -12,7 +12,8 @@ export default function ShiftMatrixTable({
onGenerateClaimLink,
onExportPdf,
onPrintView,
onEditEvent
onEditEvent,
onDeleteEvent
}) {
if (!event || !event.task_areas || event.task_areas.length === 0) {
return (
@@ -71,6 +72,15 @@ export default function ShiftMatrixTable({
<Edit3 className="w-3.5 h-3.5" /> SCHICHTEN BEARBEITEN
</button>
)}
{user && (user.is_admin_user || user.is_staff || user.is_superuser || event?.created_by === user.id || event?.created_by?.id === user.id) && onDeleteEvent && (
<button
onClick={() => onDeleteEvent(event)}
className="px-3.5 py-1.5 rounded-sm bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/30 transition flex items-center gap-1.5 font-bold"
title="Diese Veranstaltung unwiderruflich löschen"
>
<Trash2 className="w-3.5 h-3.5" /> LÖSCHEN
</button>
)}
<button
onClick={onPrintView}
className="px-3.5 py-1.5 rounded-sm bg-surface hover:bg-surface-hover text-main border border-grid transition flex items-center gap-1.5 font-bold"
+16
View File
@@ -670,6 +670,22 @@ export default function AdminPage({ branding, onRefreshBranding, onRefreshEvents
}`}>
{evt.is_active !== false ? 'AKTIV' : 'DEAKTIVIERT'}
</span>
<button
onClick={async () => {
if (window.confirm(`Möchtest du "${evt.title}" und alle Schichten wirklich unwiderruflich löschen?`)) {
try {
await apiFetch(`/events/${evt.id}/`, { method: 'DELETE' });
onRefreshEvents();
} catch (e) {
alert(e.message);
}
}
}}
className="px-2.5 py-1 rounded-sm text-[10px] font-bold bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 transition flex items-center gap-1"
title="Veranstaltung löschen"
>
<Trash2 className="w-3 h-3" /> LÖSCHEN
</button>
</div>
</div>
))}
+30 -18
View File
@@ -4,7 +4,7 @@ import React, { useState, useEffect } from 'react';
import { ArrowLeft, Calendar, Layers, Plus, Trash2, Edit3, CheckCircle2, AlertCircle, Users, UserCheck } from 'lucide-react';
import { apiFetch } from '../api/client';
export default function EventEditorPage({ eventToEdit, skills, user, onBack, onSubmit }) {
export default function EventEditorPage({ eventToEdit, skills, user, onBack, onSubmit, onDeleteEvent }) {
const [title, setTitle] = useState(eventToEdit?.title || '');
const [description, setDescription] = useState(eventToEdit?.description || '');
const [location, setLocation] = useState(eventToEdit?.location || '');
@@ -434,23 +434,35 @@ export default function EventEditorPage({ eventToEdit, skills, user, onBack, onS
</div>
{/* Submit Actions */}
<div className="flex items-center justify-end gap-3 pt-2 font-mono">
<button
type="button"
onClick={onBack}
className="px-5 py-2.5 rounded-sm text-xs font-semibold text-muted hover:bg-surface-hover transition border border-grid"
>
Abbrechen
</button>
<button
type="submit"
disabled={submitting}
style={{ backgroundColor: 'var(--brand-primary)' }}
className="px-6 py-2.5 rounded-sm text-xs font-bold text-white transition hover:brightness-110 shadow-sm flex items-center gap-2"
>
<CheckCircle2 className="w-4 h-4" />
{submitting ? 'Speichern...' : 'Veranstaltung Speichern'}
</button>
<div className="flex items-center justify-between gap-3 pt-2 font-mono">
{eventToEdit && onDeleteEvent && user && (user.is_admin_user || user.is_staff || user.is_superuser || eventToEdit.created_by === user.id || eventToEdit.created_by?.id === user.id) ? (
<button
type="button"
onClick={() => onDeleteEvent(eventToEdit)}
className="px-4 py-2.5 rounded-sm text-xs font-bold bg-red-500/10 hover:bg-red-500/20 text-red-400 border border-red-500/20 transition flex items-center gap-1.5"
>
<Trash2 className="w-4 h-4" /> VERANSTALTUNG LÖSCHEN
</button>
) : <div />}
<div className="flex items-center gap-3">
<button
type="button"
onClick={onBack}
className="px-5 py-2.5 rounded-sm text-xs font-semibold text-muted hover:bg-surface-hover transition border border-grid"
>
Abbrechen
</button>
<button
type="submit"
disabled={submitting}
style={{ backgroundColor: 'var(--brand-primary)' }}
className="px-6 py-2.5 rounded-sm text-xs font-bold text-white transition hover:brightness-110 shadow-sm flex items-center gap-2"
>
<CheckCircle2 className="w-4 h-4" />
{submitting ? 'Speichern...' : 'Veranstaltung Speichern'}
</button>
</div>
</div>
</form>
</div>
+12 -1
View File
@@ -1,7 +1,7 @@
/* Hallmark · pre-emit critique: P5 H5 E5 S5 R5 V5 */
/* Hallmark · redesign: HomePage · studied-DNA: https://inihaus.de · macrostructure: Newsstand Feed */
import React, { useState } from 'react';
import { Search, Calendar, MapPin, Clock, ArrowRight, Plus, EyeOff, CheckCircle2, Edit3, ShieldAlert } from 'lucide-react';
import { Search, Calendar, MapPin, Clock, ArrowRight, Plus, EyeOff, CheckCircle2, Edit3, ShieldAlert, Trash2 } from 'lucide-react';
export default function HomePage({
events,
@@ -10,6 +10,7 @@ export default function HomePage({
onOpenCreateEvent,
onToggleEventActive,
onEditEvent,
onDeleteEvent,
branding
}) {
const [searchQuery, setSearchQuery] = useState('');
@@ -212,6 +213,16 @@ export default function HomePage({
{evt.is_active !== false ? <EyeOff className="w-3.5 h-3.5" /> : <CheckCircle2 className="w-3.5 h-3.5" />}
</button>
)}
{(isUserAdmin || (user && (evt.created_by === user.id || evt.created_by?.id === user.id))) && onDeleteEvent && (
<button
onClick={() => onDeleteEvent(evt)}
className="py-1.5 px-2 rounded-sm text-[10px] font-mono font-bold bg-red-500/10 text-red-400 border border-red-500/20 hover:bg-red-500/20 transition flex items-center gap-1 shrink-0"
title="Veranstaltung unwiderruflich löschen"
>
<Trash2 className="w-3.5 h-3.5" />
</button>
)}
</div>
</div>
</div>