@@ -30,7 +30,8 @@ export function Sidebar(): JSX.Element {
3030 connections, groups, sessions, activeSessionId,
3131 sidebarWidth, setSidebarWidth,
3232 setConnectionDialogOpen, setQuickConnectOpen,
33- openSession, openSftpSession, exportConnections, importConnections
33+ openSession, openSftpSession, exportConnections, importConnections,
34+ saveConnection,
3435 } = useAppStore ( )
3536
3637 const [ importMsg , setImportMsg ] = useState < string | null > ( null )
@@ -39,6 +40,21 @@ export function Sidebar(): JSX.Element {
3940 const [ exportDialogOpen , setExportDialogOpen ] = useState ( false )
4041 const [ importDialogOpen , setImportDialogOpen ] = useState ( false )
4142
43+ // DnD state for connections → groups
44+ const dragConnId = useRef < string | null > ( null )
45+ const [ dropTargetId , setDropTargetId ] = useState < string | null > ( null ) // group id or 'ungrouped'
46+
47+ const handleConnDrop = useCallback ( async ( targetGroupId : string | undefined ) => {
48+ const connId = dragConnId . current
49+ if ( ! connId ) return
50+ const conn = connections . find ( ( c ) => c . id === connId )
51+ if ( conn && conn . groupId !== targetGroupId ) {
52+ await saveConnection ( { ...conn , groupId : targetGroupId } )
53+ }
54+ dragConnId . current = null
55+ setDropTargetId ( null )
56+ } , [ connections , saveConnection ] )
57+
4258 const [ search , setSearch ] = useState ( '' )
4359 const [ collapsedGroups , setCollapsedGroups ] = useState < Set < string > > ( new Set ( ) )
4460 const [ groupDialog , setGroupDialog ] = useState < { open : boolean ; group ?: ConnectionGroup } > ( { open : false } )
@@ -150,8 +166,15 @@ export function Sidebar(): JSX.Element {
150166 if ( search && groupConns . length === 0 ) return null
151167 const isCollapsed = collapsedGroups . has ( group . id )
152168 const groupColor = group . color || GROUP_COLORS [ 0 ]
169+ const isDropTarget = dropTargetId === group . id
153170 return (
154- < div key = { group . id } >
171+ < div
172+ key = { group . id }
173+ onDragOver = { ( e ) => { e . preventDefault ( ) ; setDropTargetId ( group . id ) } }
174+ onDragLeave = { ( ) => setDropTargetId ( null ) }
175+ onDrop = { ( e ) => { e . preventDefault ( ) ; handleConnDrop ( group . id ) } }
176+ className = { cn ( 'rounded-lg transition-colors' , isDropTarget && 'ring-1 ring-primary/50 bg-primary/5' ) }
177+ >
155178 < div className = "flex items-center group/grp hover:bg-sidebar-accent/50 mx-1 rounded-lg transition-colors" >
156179 < button
157180 onClick = { ( ) => toggleGroup ( group . id ) }
@@ -197,24 +220,33 @@ export function Sidebar(): JSX.Element {
197220 onConnect = { ( ) => openSession ( conn ) }
198221 onOpenSftp = { ( ) => openSftpSession ( conn ) }
199222 onEdit = { ( ) => setConnectionDialogOpen ( true , conn ) }
223+ onDragStart = { ( ) => { dragConnId . current = conn . id } }
200224 />
201225 ) ) }
202226 </ div >
203227 )
204228 } ) }
205229
206- { /* Ungrouped */ }
207- { ungrouped . map ( ( conn ) => (
208- < ConnectionItem
209- key = { conn . id }
210- connection = { conn }
211- sessions = { sessions }
212- activeSessionId = { activeSessionId }
213- onConnect = { ( ) => openSession ( conn ) }
214- onOpenSftp = { ( ) => openSftpSession ( conn ) }
215- onEdit = { ( ) => setConnectionDialogOpen ( true , conn ) }
216- />
217- ) ) }
230+ { /* Ungrouped — also a drop target */ }
231+ < div
232+ onDragOver = { ( e ) => { e . preventDefault ( ) ; setDropTargetId ( 'ungrouped' ) } }
233+ onDragLeave = { ( ) => setDropTargetId ( null ) }
234+ onDrop = { ( e ) => { e . preventDefault ( ) ; handleConnDrop ( undefined ) } }
235+ className = { cn ( 'rounded-lg transition-colors' , dropTargetId === 'ungrouped' && 'ring-1 ring-primary/50 bg-primary/5' ) }
236+ >
237+ { ungrouped . map ( ( conn ) => (
238+ < ConnectionItem
239+ key = { conn . id }
240+ connection = { conn }
241+ sessions = { sessions }
242+ activeSessionId = { activeSessionId }
243+ onConnect = { ( ) => openSession ( conn ) }
244+ onOpenSftp = { ( ) => openSftpSession ( conn ) }
245+ onEdit = { ( ) => setConnectionDialogOpen ( true , conn ) }
246+ onDragStart = { ( ) => { dragConnId . current = conn . id } }
247+ />
248+ ) ) }
249+ </ div >
218250 </ div >
219251
220252 { /* Footer actions */ }
@@ -309,10 +341,11 @@ interface ConnectionItemProps {
309341 onConnect : ( ) => void
310342 onOpenSftp : ( ) => void
311343 onEdit : ( ) => void
344+ onDragStart ?: ( ) => void
312345}
313346
314347function ConnectionItem ( {
315- connection, indent = false , sessions, activeSessionId, onConnect, onOpenSftp, onEdit
348+ connection, indent = false , sessions, activeSessionId, onConnect, onOpenSftp, onEdit, onDragStart
316349} : ConnectionItemProps ) : JSX . Element {
317350 const { deleteConnection, saveConnection } = useAppStore ( )
318351
@@ -338,6 +371,8 @@ function ConnectionItem({
338371 return (
339372 < >
340373 < button
374+ draggable
375+ onDragStart = { ( e ) => { e . dataTransfer . effectAllowed = 'move' ; onDragStart ?.( ) } }
341376 onDoubleClick = { onConnect }
342377 onContextMenu = { handleContextMenu }
343378 className = { cn (
0 commit comments