Add delete selected button
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
82315dd4ab
commit
b8d02c43f8
@ -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,
|
||||||
|
|||||||
@ -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
|
||||||
# ============================================
|
# ============================================
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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; }
|
||||||
|
|||||||
@ -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,13 +373,21 @@ 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-whatsapp"
|
<button
|
||||||
onClick={() => setShowWhatsAppModal(true)}
|
className="btn-delete-selected"
|
||||||
title={he.selectGuestsFirst}
|
onClick={handleDeleteSelected}
|
||||||
>
|
>
|
||||||
{he.sendWhatsApp} ({selectedGuestIds.size})
|
{he.deleteSelected} ({selectedGuestIds.size})
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className="btn-whatsapp"
|
||||||
|
onClick={() => setShowWhatsAppModal(true)}
|
||||||
|
title={he.selectGuestsFirst}
|
||||||
|
>
|
||||||
|
{he.sendWhatsApp} ({selectedGuestIds.size})
|
||||||
|
</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
BIN
guest-list-2026-02-28.xlsx
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user