Add delete selected button
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
dvirlabs 2026-04-03 02:03:44 +03:00
parent 82315dd4ab
commit b8d02c43f8
6 changed files with 66 additions and 10 deletions

View File

@ -451,8 +451,19 @@ async def delete_guest(
# ============================================ # ============================================
# Bulk Guest Import # Bulk Guest Delete
# ============================================ # ============================================
@app.post("/events/{event_id}/guests/bulk-delete")
async def bulk_delete_guests(
event_id: UUID,
delete_data: schemas.GuestBulkDelete,
db: Session = Depends(get_db),
current_user_id: UUID = Depends(get_current_user_id)
):
"""Bulk delete guests (admin only)"""
await authz.verify_event_admin(event_id, db, current_user_id)
deleted_count = crud.delete_guests_bulk(db, event_id, delete_data.guest_ids)
return {"message": f"{deleted_count} guests deleted successfully", "deleted_count": deleted_count}
@app.post("/events/{event_id}/guests/import", response_model=dict) @app.post("/events/{event_id}/guests/import", response_model=dict)
async def bulk_import_guests( async def bulk_import_guests(
event_id: UUID, event_id: UUID,

View File

@ -154,6 +154,10 @@ class GuestBulkImport(BaseModel):
guests: List[GuestImportItem] guests: List[GuestImportItem]
class GuestBulkDelete(BaseModel):
guest_ids: List[UUID]
# ============================================ # ============================================
# Filter/Search Schemas # Filter/Search Schemas
# ============================================ # ============================================

View File

@ -119,6 +119,11 @@ export const deleteGuest = async (eventId, guestId) => {
return response.data return response.data
} }
export const bulkDeleteGuests = async (eventId, guestIds) => {
const response = await api.post(`/events/${eventId}/guests/bulk-delete`, { guest_ids: guestIds })
return response.data
}
export const bulkImportGuests = async (eventId, guests) => { export const bulkImportGuests = async (eventId, guests) => {
const response = await api.post(`/events/${eventId}/guests/import`, { guests }) const response = await api.post(`/events/${eventId}/guests/import`, { guests })
return response.data return response.data

View File

@ -95,6 +95,7 @@
.btn-tool, .btn-tool,
.btn-add-guest, .btn-add-guest,
.btn-whatsapp, .btn-whatsapp,
.btn-delete-selected,
.btn-export, .btn-export,
.btn-duplicate { .btn-duplicate {
display: inline-flex; display: inline-flex;
@ -160,6 +161,16 @@
cursor: not-allowed; cursor: not-allowed;
} }
/* delete selected */
.btn-delete-selected {
background: var(--color-danger, #e53e3e);
color: #fff;
}
.btn-delete-selected:hover {
background: #c53030;
box-shadow: 0 2px 8px rgba(229, 62, 62, 0.35);
}
/* ── legacy class aliases kept for any remaining refs ── */ /* ── legacy class aliases kept for any remaining refs ── */
.header-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; } .header-actions { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.btn-members { display: none; } .btn-members { display: none; }

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { getGuests, getGuestOwners, createGuest, updateGuest, deleteGuest, sendWhatsAppInvitationToGuests, getEvent } from '../api/api' import { getGuests, getGuestOwners, createGuest, updateGuest, deleteGuest, bulkDeleteGuests, sendWhatsAppInvitationToGuests, getEvent } from '../api/api'
import GuestForm from './GuestForm' import GuestForm from './GuestForm'
import GoogleImport from './GoogleImport' import GoogleImport from './GoogleImport'
import ImportContacts from './ImportContacts' import ImportContacts from './ImportContacts'
@ -46,7 +46,10 @@ const he = {
failedToDelete: 'נכשל במחיקת אורח', failedToDelete: 'נכשל במחיקת אורח',
sendWhatsApp: '💬 שלח בוואטסאפ', sendWhatsApp: '💬 שלח בוואטסאפ',
noGuestsSelected: 'בחר לפחות אורח אחד', noGuestsSelected: 'בחר לפחות אורח אחד',
selectGuestsFirst: 'בחר אורחים לשליחת הזמנה' selectGuestsFirst: 'בחר אורחים לשליחת הזמנה',
deleteSelected: '🗑️ מחק נבחרים',
confirmDeleteSelected: 'האם אתה בטוח שברצונך למחוק {count} אורחים?',
failedToDeleteSelected: 'נכשל במחיקת האורחים הנבחרים'
} }
function GuestList({ eventId, onBack, onShowMembers }) { function GuestList({ eventId, onBack, onShowMembers }) {
@ -168,6 +171,20 @@ function GuestList({ eventId, onBack, onShowMembers }) {
} }
} }
const handleDeleteSelected = async () => {
if (selectedGuestIds.size === 0) return
if (!window.confirm(he.confirmDeleteSelected.replace('{count}', selectedGuestIds.size))) return
try {
await bulkDeleteGuests(eventId, Array.from(selectedGuestIds))
setGuests(guests.filter(g => !selectedGuestIds.has(g.id)))
setSelectedGuestIds(new Set())
} catch (err) {
setError(he.failedToDeleteSelected)
console.error(err)
}
}
const handleEdit = (guest) => { const handleEdit = (guest) => {
setEditingGuest(guest) setEditingGuest(guest)
setShowGuestForm(true) setShowGuestForm(true)
@ -356,6 +373,13 @@ function GuestList({ eventId, onBack, onShowMembers }) {
</div> </div>
<div className="btn-group btn-group-primary"> <div className="btn-group btn-group-primary">
{selectedGuestIds.size > 0 && ( {selectedGuestIds.size > 0 && (
<>
<button
className="btn-delete-selected"
onClick={handleDeleteSelected}
>
{he.deleteSelected} ({selectedGuestIds.size})
</button>
<button <button
className="btn-whatsapp" className="btn-whatsapp"
onClick={() => setShowWhatsAppModal(true)} onClick={() => setShowWhatsAppModal(true)}
@ -363,6 +387,7 @@ function GuestList({ eventId, onBack, onShowMembers }) {
> >
{he.sendWhatsApp} ({selectedGuestIds.size}) {he.sendWhatsApp} ({selectedGuestIds.size})
</button> </button>
</>
)} )}
<button className="btn-add-guest" onClick={() => { <button className="btn-add-guest" onClick={() => {
setEditingGuest(null) setEditingGuest(null)

BIN
guest-list-2026-02-28.xlsx Normal file

Binary file not shown.