feat: Implement bulk delete functionality for photos in API and frontend

This commit introduces a new feature for bulk deleting photos, allowing admins to permanently remove multiple photos at once. The backend has been updated with a new API endpoint for handling bulk delete requests, including response handling for missing photo IDs. The frontend has been enhanced with a confirmation dialog and a button to trigger the bulk delete action, improving the user experience. Documentation has been updated to reflect these changes.
This commit is contained in:
tanyar09
2025-11-25 13:21:16 -05:00
parent f9e8c476bc
commit a0cc3a985a
4 changed files with 132 additions and 0 deletions
+14
View File
@@ -31,6 +31,12 @@ export interface UploadResponse {
errors: string[]
}
export interface BulkDeletePhotosResponse {
message: string
deleted_count: number
missing_photo_ids: number[]
}
export const photosApi = {
importPhotos: async (
request: PhotoImportRequest
@@ -119,6 +125,14 @@ export const photosApi = {
return data
},
bulkDeletePhotos: async (photoIds: number[]): Promise<BulkDeletePhotosResponse> => {
const { data } = await apiClient.post<BulkDeletePhotosResponse>(
'/api/v1/photos/bulk-delete',
{ photo_ids: photoIds }
)
return data
},
openFolder: async (photoId: number): Promise<{ message: string; folder: string }> => {
const { data } = await apiClient.post<{ message: string; folder: string }>(
`/api/v1/photos/${photoId}/open-folder`
+44
View File
@@ -59,6 +59,7 @@ export default function Search() {
const [loadingTags, setLoadingTags] = useState(false)
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
const [loadingFavorites, setLoadingFavorites] = useState(false)
const [deletingPhotos, setDeletingPhotos] = useState(false)
// Photo viewer
const [showPhotoViewer, setShowPhotoViewer] = useState(false)
@@ -454,6 +455,41 @@ export default function Search() {
}
}
const handleDeleteSelectedPhotos = async () => {
if (selectedPhotos.size === 0) {
alert('Please select photos to delete.')
return
}
const warningLines = [
`⚠️ WARNING: Permanently delete ${selectedPhotos.size} photo(s)?`,
'',
]
warningLines.push('This action CANNOT be undone. Continue?')
if (!confirm(warningLines.join('\n'))) {
return
}
setDeletingPhotos(true)
try {
const response = await photosApi.bulkDeletePhotos(Array.from(selectedPhotos))
const missingNote = response.missing_photo_ids.length > 0
? `\nMissing photo IDs: ${response.missing_photo_ids.join(', ')}`
: ''
alert(`${response.message}${missingNote}`)
await performSearch(page)
setSelectedPhotos(new Set())
} catch (error: any) {
console.error('Error deleting photos:', error)
const errorMessage = error?.response?.data?.detail || error?.message || 'Failed to delete photos.'
alert(`Error deleting photos: ${errorMessage}`)
} finally {
setDeletingPhotos(false)
}
}
const handleToggleFavorite = async (photoId: number) => {
try {
await photosApi.toggleFavorite(photoId)
@@ -747,6 +783,14 @@ export default function Search() {
>
Tag selected photos
</button>
<button
onClick={handleDeleteSelectedPhotos}
disabled={selectedPhotos.size === 0 || deletingPhotos}
className="px-3 py-1 text-sm border rounded text-red-700 border-red-200 hover:bg-red-50 disabled:bg-gray-100 disabled:text-gray-400 disabled:border-gray-200"
title="Permanently delete selected photos"
>
{deletingPhotos ? 'Deleting...' : '🗑 Delete selected'}
</button>
<button
onClick={selectAll}
disabled={results.length === 0}