1- import { useCallback , useEffect , useState } from 'react'
1+ import { useCallback , useEffect , useMemo , useState } from 'react'
22import { toast } from '@sim/emcn'
33import { getErrorMessage } from '@sim/utils/errors'
44import { useRouter } from 'next/navigation'
55import { organizationRoutes } from '@/lib/navigation/paths'
66import type { OrganizationChat } from '@/app/o/[organizationId]/components/organization-sidebar/hooks/use-organization-chats'
7+ import { useChatSelection } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-chat-selection'
78import { useFlyoutInlineRename } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-flyout-inline-rename'
89import { useHoverMenu } from '@/app/workspace/[workspaceId]/w/components/sidebar/hooks/use-hover-menu'
910import {
11+ MothershipChatDeleteError ,
1012 useDeleteMothershipChat ,
13+ useDeleteMothershipChats ,
1114 useMarkMothershipChatRead ,
1215 useMarkMothershipChatUnread ,
1316 useRenameMothershipChat ,
1417 useSetMothershipChatPinned ,
1518} from '@/hooks/queries/mothership-chats'
1619import { useContextMenu } from '@/hooks/use-context-menu'
20+ import { useFolderStore } from '@/stores/folders/store'
1721
1822interface UseOrganizationChatActionsProps {
1923 organizationId : string
@@ -26,16 +30,28 @@ export function useOrganizationChatActions({
2630} : UseOrganizationChatActionsProps ) {
2731 const router = useRouter ( )
2832 const owner = { organizationId }
29- const { mutate : deleteChat , isPending : isDeleting } = useDeleteMothershipChat ( owner )
33+ const deleteChat = useDeleteMothershipChat ( owner )
34+ const deleteChats = useDeleteMothershipChats ( owner )
35+ const isDeleting = deleteChat . isPending || deleteChats . isPending
3036 const { mutateAsync : renameChat } = useRenameMothershipChat ( owner )
3137 const { mutate : pinChat } = useSetMothershipChatPinned ( owner )
3238 const { mutate : readChat } = useMarkMothershipChatRead ( owner )
3339 const { mutate : unreadChat } = useMarkMothershipChatUnread ( owner )
3440 const menu = useContextMenu ( )
3541 const hover = useHoverMenu ( )
36- const [ selectedChatId , setSelectedChatId ] = useState < string | null > ( null )
37- const [ chatToDelete , setChatToDelete ] = useState < OrganizationChat | null > ( null )
38- const selectedChat = chats . find ( ( chat ) => chat . id === selectedChatId )
42+ const chatIds = useMemo ( ( ) => chats . map ( ( chat ) => chat . id ) , [ chats ] )
43+ const { selectedChats, handleChatClick } = useChatSelection ( { chatIds } )
44+ const [ menuSelection , setMenuSelection ] = useState < {
45+ chatId : string
46+ chatIds : string [ ]
47+ } | null > ( null )
48+ const [ chatIdsToDelete , setChatIdsToDelete ] = useState < string [ ] > ( [ ] )
49+ const deleteTargetIds = new Set ( chatIdsToDelete )
50+ const chatsToDelete = chats . filter ( ( chat ) => deleteTargetIds . has ( chat . id ) )
51+ const selectedChat =
52+ menuSelection ?. chatIds . length === 1
53+ ? chats . find ( ( chat ) => chat . id === menuSelection . chatId )
54+ : undefined
3955 const rename = useFlyoutInlineRename ( {
4056 itemType : 'chat' ,
4157 onSave : async ( chatId , title ) => {
@@ -53,14 +69,27 @@ export function useOrganizationChatActions({
5369 hover . setLocked ( menu . isOpen || rename . editingId !== null )
5470 } , [ menu . isOpen , rename . editingId , hover . setLocked ] )
5571
72+ const captureSelection = useCallback (
73+ ( chatId : string ) => {
74+ const { selectedChats, selectChatOnly } = useFolderStore . getState ( )
75+ if ( selectedChats . has ( chatId ) ) {
76+ setMenuSelection ( { chatId, chatIds : chatIds . filter ( ( id ) => selectedChats . has ( id ) ) } )
77+ } else {
78+ selectChatOnly ( chatId )
79+ setMenuSelection ( { chatId, chatIds : [ chatId ] } )
80+ }
81+ } ,
82+ [ chatIds ]
83+ )
84+
5685 const onContextMenu = useCallback (
5786 ( event : React . MouseEvent , chatId : string ) => {
58- setSelectedChatId ( chatId )
87+ captureSelection ( chatId )
5988 hover . setLocked ( true )
6089 menu . preventDismiss ( )
6190 menu . handleContextMenu ( event )
6291 } ,
63- [ hover . setLocked , menu . preventDismiss , menu . handleContextMenu ]
92+ [ captureSelection , hover . setLocked , menu . preventDismiss , menu . handleContextMenu ]
6493 )
6594
6695 const onMorePointerDown = useCallback ( ( ) => {
@@ -69,16 +98,16 @@ export function useOrganizationChatActions({
6998
7099 const onMoreClick = useCallback (
71100 ( event : React . MouseEvent < HTMLButtonElement > , chatId : string ) => {
72- if ( menu . isOpen && selectedChatId === chatId ) {
101+ if ( menu . isOpen && menuSelection ?. chatId === chatId ) {
73102 menu . closeMenu ( )
74103 return
75104 }
76105 const rect = event . currentTarget . getBoundingClientRect ( )
77- setSelectedChatId ( chatId )
106+ captureSelection ( chatId )
78107 hover . setLocked ( true )
79108 menu . openMenuAt ( { x : rect . right , y : rect . top } )
80109 } ,
81- [ menu . isOpen , menu . closeMenu , menu . openMenuAt , hover . setLocked , selectedChatId ]
110+ [ menu . isOpen , menu . closeMenu , menu . openMenuAt , hover . setLocked , menuSelection , captureSelection ]
82111 )
83112
84113 const chatId = selectedChat ?. id
@@ -87,25 +116,54 @@ export function useOrganizationChatActions({
87116 const chatPinned = selectedChat ?. isPinned
88117
89118 const startDelete = useCallback ( ( ) => {
90- if ( selectedChat ) setChatToDelete ( selectedChat )
91- } , [ selectedChat ] )
119+ setChatIdsToDelete ( menuSelection ?. chatIds ?? [ ] )
120+ } , [ menuSelection ] )
92121
93122 const cancelDelete = useCallback ( ( ) => {
94- if ( ! isDeleting ) setChatToDelete ( null )
123+ if ( ! isDeleting ) setChatIdsToDelete ( [ ] )
95124 } , [ isDeleting ] )
96125
97126 const confirmDelete = useCallback ( ( ) => {
98- if ( ! chatToDelete || isDeleting ) return
99- deleteChat ( chatToDelete . id , {
127+ if ( chatsToDelete . length === 0 || isDeleting ) return
128+ const redirectIfDeleted = ( ids : string [ ] ) => {
129+ if (
130+ ids . some ( ( id ) => window . location . pathname === organizationRoutes ( organizationId ) . chat ( id ) )
131+ ) {
132+ router . push ( organizationRoutes ( organizationId ) . home )
133+ }
134+ }
135+ const options = {
100136 onSuccess : ( ) => {
101- setChatToDelete ( null )
102- if ( window . location . pathname === chatToDelete . href ) {
103- router . push ( organizationRoutes ( organizationId ) . home )
137+ setChatIdsToDelete ( [ ] )
138+ useFolderStore . getState ( ) . clearChatSelection ( )
139+ redirectIfDeleted ( chatIdsToDelete )
140+ } ,
141+ onError : ( error : Error ) => {
142+ if ( error instanceof MothershipChatDeleteError ) {
143+ const deletedIds = new Set ( error . deletedChatIds )
144+ setChatIdsToDelete ( ( ids ) => ids . filter ( ( id ) => ! deletedIds . has ( id ) ) )
145+ redirectIfDeleted ( error . deletedChatIds )
104146 }
147+ toast . error ( error . message )
105148 } ,
106- onError : ( error ) => toast . error ( error . message ) ,
107- } )
108- } , [ chatToDelete , deleteChat , isDeleting , organizationId , router ] )
149+ }
150+ if ( chatsToDelete . length === 1 ) {
151+ deleteChat . mutate ( chatsToDelete [ 0 ] . id , options )
152+ } else {
153+ deleteChats . mutate (
154+ chatsToDelete . map ( ( chat ) => chat . id ) ,
155+ options
156+ )
157+ }
158+ } , [
159+ chatIdsToDelete ,
160+ chatsToDelete ,
161+ deleteChat . mutate ,
162+ deleteChats . mutate ,
163+ isDeleting ,
164+ organizationId ,
165+ router ,
166+ ] )
109167
110168 const startRename = useCallback ( ( ) => {
111169 if ( chatId && chatName !== undefined ) rename . startRename ( { id : chatId , name : chatName } )
@@ -142,11 +200,15 @@ export function useOrganizationChatActions({
142200 hover,
143201 rename,
144202 selectedChat,
203+ selectedChats,
204+ handleChatClick,
205+ menuOpenChatId : menu . isOpen ? menuSelection ?. chatId : null ,
206+ selectedCount : menuSelection ?. chatIds . length ?? 0 ,
145207 onContextMenu,
146208 onMorePointerDown,
147209 onMoreClick,
148210 startRename,
149- chatToDelete ,
211+ chatsToDelete ,
150212 isDeleting,
151213 startDelete,
152214 cancelDelete,
0 commit comments