22
33import * as React from "react"
44import { Button } from "@/components/ui/button"
5+ import { Checkbox } from "@/components/ui/checkbox"
56import { ScrollArea } from "@/components/ui/scroll-area"
67import { Collection , CollectionFolder , CollectionRequest , HistoryRequest } from "../types"
78import { CollectionItem } from "./collection-item"
@@ -42,6 +43,7 @@ interface CollectionsSidebarProps {
4243 history ?: HistoryRequest [ ]
4344 onClearHistory ?: ( ) => void
4445 onDeleteHistoryItem ?: ( id : string ) => void
46+ onDeleteMultiple ?: ( ids : string [ ] ) => void
4547}
4648
4749export function CollectionsSidebar ( {
@@ -57,6 +59,7 @@ export function CollectionsSidebar({
5759 history,
5860 onClearHistory,
5961 onDeleteHistoryItem,
62+ onDeleteMultiple,
6063} : CollectionsSidebarProps ) {
6164 const t = useTranslations ( "ApiClient.collectionsSidebar" )
6265 const tRoot = useTranslations ( "ApiClient" )
@@ -89,6 +92,9 @@ export function CollectionsSidebar({
8992 const [ renameCollectionName , setRenameCollectionName ] = React . useState ( "" )
9093 const [ targetParentId , setTargetParentId ] = React . useState < string | null > ( null )
9194 const [ targetCollectionId , setTargetCollectionId ] = React . useState < string | null > ( null )
95+ const [ selectedCollections , setSelectedCollections ] = React . useState < Set < string > > ( new Set ( ) )
96+ const [ deleteBulkDialogOpen , setDeleteBulkDialogOpen ] = React . useState ( false )
97+ const [ isDeleting , setIsDeleting ] = React . useState ( false )
9298
9399 const handleAddFolder = ( ) => {
94100 if ( newFolderName && targetParentId ) {
@@ -127,21 +133,50 @@ export function CollectionsSidebar({
127133 setRenameCollectionDialogOpen ( true )
128134 }
129135
136+ const toggleCollectionSelection = ( collectionId : string ) => {
137+ setSelectedCollections ( prev => {
138+ const next = new Set ( prev )
139+ if ( next . has ( collectionId ) ) {
140+ next . delete ( collectionId )
141+ } else {
142+ next . add ( collectionId )
143+ }
144+ return next
145+ } )
146+ }
147+
148+ const clearSelection = ( ) => {
149+ setSelectedCollections ( new Set ( ) )
150+ }
151+
130152 return (
131153 < div className = "flex flex-col w-full h-full bg-background/50" >
132154 < Tabs defaultValue = "collections" className = "flex-1 flex flex-col h-full min-h-0" >
133155 < div className = "px-4 py-3 border-b flex flex-col gap-3 shrink-0 bg-card/40 backdrop-blur-sm" >
134156 < div className = "flex items-center justify-between" >
135157 < h3 className = "font-semibold text-sm tracking-tight" > { t ( "title" ) } </ h3 >
136- < Button
137- variant = "ghost"
138- size = "icon"
139- className = "h-7 w-7 rounded-lg hover:bg-primary/10 hover:text-primary transition-colors"
140- onClick = { ( ) => setNewCollectionDialogOpen ( true ) }
141- title = { t ( "newCollection" ) }
142- >
143- < FolderPlus className = "h-4 w-4" />
144- </ Button >
158+ < div className = "flex items-center gap-2" >
159+ { selectedCollections . size > 0 && (
160+ < Button
161+ variant = "destructive"
162+ size = "sm"
163+ className = "h-7 px-3 rounded-lg text-xs font-medium gap-2"
164+ onClick = { ( ) => setDeleteBulkDialogOpen ( true ) }
165+ >
166+ < Trash2 className = "h-3.5 w-3.5" />
167+ Delete ({ selectedCollections . size } )
168+ </ Button >
169+ ) }
170+ < Button
171+ variant = "ghost"
172+ size = "icon"
173+ className = "h-7 w-7 rounded-lg hover:bg-primary/10 hover:text-primary transition-colors"
174+ onClick = { ( ) => setNewCollectionDialogOpen ( true ) }
175+ title = { t ( "newCollection" ) }
176+ >
177+ < FolderPlus className = "h-4 w-4" />
178+ </ Button >
179+ </ div >
145180 </ div >
146181 < TabsList className = "w-full grid grid-cols-2 p-1 bg-muted/50 rounded-lg" >
147182 < TabsTrigger value = "collections" className = "rounded-md text-xs font-medium" > { t ( "tabCollections" ) } </ TabsTrigger >
@@ -173,9 +208,18 @@ export function CollectionsSidebar({
173208 collections . map ( ( collection ) => (
174209 < div key = { collection . id } className = "mb-4" >
175210 < div className = "flex items-center justify-between px-2 py-1.5 mb-1 group rounded-md hover:bg-muted/50 transition-colors" >
176- < span className = "text-[11px] font-bold text-muted-foreground uppercase tracking-wider truncate flex-1 mr-2 px-1" >
177- { collection . name }
178- </ span >
211+ < div className = "flex items-center gap-2 flex-1 min-w-0" >
212+ < div className = "opacity-0 group-hover:opacity-100 transition-opacity flex items-center" >
213+ < Checkbox
214+ checked = { selectedCollections . has ( collection . id ) }
215+ onCheckedChange = { ( ) => toggleCollectionSelection ( collection . id ) }
216+ className = "h-4 w-4"
217+ />
218+ </ div >
219+ < span className = "text-[11px] font-bold text-muted-foreground uppercase tracking-wider truncate flex-1 px-1" >
220+ { collection . name }
221+ </ span >
222+ </ div >
179223 < div className = "flex items-center opacity-0 group-hover:opacity-100 transition-opacity" >
180224 < Button
181225 variant = "ghost"
@@ -200,14 +244,14 @@ export function CollectionsSidebar({
200244 < DropdownMenuItem onClick = { ( ) => openRenameCollectionDialog ( collection ) } >
201245 < Pencil className = "h-4 w-4 mr-2" />
202246 { t ( "rename" ) }
203- </ DropdownMenuItem >
247+ </ DropdownMenuItem >
204248 < DropdownMenuItem
205249 className = "text-destructive focus:text-destructive"
206250 onClick = { ( ) => onDelete ( collection . id ) }
207251 >
208252 < Trash2 className = "h-4 w-4 mr-2" />
209253 { t ( "delete" ) }
210- </ DropdownMenuItem >
254+ </ DropdownMenuItem >
211255 </ DropdownMenuContent >
212256 </ DropdownMenu >
213257 </ div >
@@ -432,6 +476,55 @@ export function CollectionsSidebar({
432476 </ DialogFooter >
433477 </ DialogContent >
434478 </ Dialog >
479+
480+ < Dialog open = { deleteBulkDialogOpen } onOpenChange = { setDeleteBulkDialogOpen } >
481+ < DialogContent className = "sm:max-w-[425px]" >
482+ < DialogHeader >
483+ < DialogTitle > { t ( "dialogDeleteBulkTitle" ) || "Delete Collections" } </ DialogTitle >
484+ < DialogDescription >
485+ { t ( "dialogDeleteBulkDescription" ) || "This action cannot be undone. The following collections will be permanently deleted:" }
486+ </ DialogDescription >
487+ </ DialogHeader >
488+ < div className = "py-4" >
489+ < div className = "space-y-2 max-h-[200px] overflow-y-auto" >
490+ { Array . from ( selectedCollections ) . map ( collectionId => {
491+ const collection = collections . find ( c => c . id === collectionId )
492+ return (
493+ < div key = { collectionId } className = "flex items-center gap-2 px-3 py-2 bg-muted/50 rounded-md border border-border/50" >
494+ < Trash2 className = "h-3.5 w-3.5 text-muted-foreground shrink-0" />
495+ < span className = "text-sm font-medium truncate" > { collection ?. name || "Unknown" } </ span >
496+ </ div >
497+ )
498+ } ) }
499+ </ div >
500+ </ div >
501+ < DialogFooter >
502+ < Button variant = "outline" onClick = { ( ) => setDeleteBulkDialogOpen ( false ) } >
503+ { t ( "cancel" ) || "Cancel" }
504+ </ Button >
505+ < Button
506+ variant = "destructive"
507+ disabled = { isDeleting }
508+ onClick = { async ( ) => {
509+ setIsDeleting ( true )
510+ try {
511+ await onDeleteMultiple ?.( Array . from ( selectedCollections ) )
512+ // Only close dialog and clear selection on successful deletion
513+ setDeleteBulkDialogOpen ( false )
514+ clearSelection ( )
515+ } catch ( error ) {
516+ // Keep dialog open if deletion failed so user can retry
517+ console . error ( "Error deleting collections:" , error )
518+ } finally {
519+ setIsDeleting ( false )
520+ }
521+ } }
522+ >
523+ { isDeleting ? "Deleting..." : ( t ( "delete" ) || "Delete" ) }
524+ </ Button >
525+ </ DialogFooter >
526+ </ DialogContent >
527+ </ Dialog >
435528 </ div >
436529 )
437530}
0 commit comments