@@ -61,6 +61,8 @@ export const SprintBoardView: React.FC<SprintBoardViewProps> = ({
6161 const [ tasks , setTasks ] = useState < Task [ ] > ( [ ] )
6262 const [ epics , setEpics ] = useState < Epic [ ] > ( [ ] )
6363 const [ sprints , setSprints ] = useState < Sprint [ ] > ( [ ] )
64+ const [ colleagues , setColleagues ] = useState < Colleague [ ] > ( [ ] )
65+ const [ users , setUsers ] = useState < User [ ] > ( [ ] )
6466 const [ selectedSprintIds , setSelectedSprintIds ] = useLocalStorage < string [ ] > (
6567 'sprintBoardView_selectedSprints' ,
6668 [ ] ,
@@ -70,10 +72,10 @@ export const SprintBoardView: React.FC<SprintBoardViewProps> = ({
7072 const [ isAddSprintModalOpen , setIsAddSprintModalOpen ] = useState ( false )
7173 const [ selectedTask , setSelectedTask ] = useState < Task | null > ( null )
7274 const [ draggedTask , setDraggedTask ] = useState < Task | null > ( null )
73- const [ users , setUsers ] = useState < User [ ] > ( [ ] )
74- const [ colleagues , setColleagues ] = useState < Colleague [ ] > ( [ ] )
75- const [ heroHeight , setHeroHeight ] = useState ( 0 )
75+ const [ dragOverSprintId , setDragOverSprintId ] = useState < number | null > ( null )
76+ const [ dragOverBacklog , setDragOverBacklog ] = useState ( false )
7677 const [ isSprintSelectorOpen , setIsSprintSelectorOpen ] = useState ( false )
78+ const [ heroHeight , setHeroHeight ] = useState ( 0 )
7779
7880 const heroRef = useRef < HTMLDivElement > ( null )
7981 const containerRef = useRef < HTMLDivElement > ( null )
@@ -197,6 +199,50 @@ export const SprintBoardView: React.FC<SprintBoardViewProps> = ({
197199 setDraggedTask ( task )
198200 }
199201
202+ const handleDragOverSprint = ( e : React . DragEvent ) => {
203+ e . preventDefault ( )
204+ e . dataTransfer . dropEffect = 'move'
205+ }
206+
207+ const handleDragOverBacklog = ( e : React . DragEvent ) => {
208+ e . preventDefault ( )
209+ e . dataTransfer . dropEffect = 'move'
210+ }
211+
212+ const handleDropOnSprint = ( sprintId : number ) => {
213+ if ( ! draggedTask ) return
214+
215+ const currentSprint = extractId ( draggedTask . sprint )
216+ if ( currentSprint !== sprintId ) {
217+ setTasks ( ( prev ) =>
218+ prev . map ( ( task ) =>
219+ task . id === draggedTask . id ? { ...task , sprint : sprintId } : task ,
220+ ) ,
221+ )
222+ onUpdateTask ?.( draggedTask . id . toString ( ) , { sprint : sprintId } )
223+ }
224+
225+ setDraggedTask ( null )
226+ setDragOverSprintId ( null )
227+ }
228+
229+ const handleDropOnBacklog = ( ) => {
230+ if ( ! draggedTask ) return
231+
232+ const currentSprint = extractId ( draggedTask . sprint )
233+ if ( currentSprint ) {
234+ setTasks ( ( prev ) =>
235+ prev . map ( ( task ) =>
236+ task . id === draggedTask . id ? { ...task , sprint : null } : task ,
237+ ) ,
238+ )
239+ onUpdateTask ?.( draggedTask . id . toString ( ) , { sprint : null } )
240+ }
241+
242+ setDraggedTask ( null )
243+ setDragOverBacklog ( false )
244+ }
245+
200246 const handleAddTask = async ( newTask : Omit < Task , 'id' | 'createdAt' | 'updatedAt' > ) => {
201247 const temporaryTask : Task = {
202248 ...newTask ,
@@ -283,7 +329,7 @@ export const SprintBoardView: React.FC<SprintBoardViewProps> = ({
283329 const calculatedHeight =
284330 heroHeight > 0 ? `calc(100vh - ${ heroHeight + 120 } px)` : 'calc(100vh - 12rem)'
285331
286- const selectedEpicIds = React . useMemo (
332+ const selectedEpicIds = React . useMemo < number [ ] > (
287333 ( ) => [ ...epics . map ( ( e ) => e . id ) , 0 ] , // include "no-epic" sentinel id 0
288334 [ epics ] ,
289335 )
@@ -482,8 +528,14 @@ export const SprintBoardView: React.FC<SprintBoardViewProps> = ({
482528 < div className = "grid gap-6 h-full overflow-x-auto" style = { { gridTemplateColumns : `repeat(${ visibleSprints . length + 1 } , minmax(350px, 1fr))` } } >
483529 { /* Backlog Column */ }
484530 < Card
485- className = "p-4 bg-card border-l-4 border-l-amber-500 shadow-sm hover:shadow-md transition-shadow duration-200 flex flex-col"
531+ className = { `p-4 bg-card border-l-4 border-l-amber-500 shadow-sm hover:shadow-md transition-all duration-200 flex flex-col ${
532+ dragOverBacklog ? 'ring-2 ring-amber-500 bg-amber-50/50 dark:bg-amber-950/20' : ''
533+ } `}
486534 style = { { height : calculatedHeight } }
535+ onDragOver = { handleDragOverBacklog }
536+ onDragEnter = { ( ) => setDragOverBacklog ( true ) }
537+ onDragLeave = { ( ) => setDragOverBacklog ( false ) }
538+ onDrop = { handleDropOnBacklog }
487539 >
488540 { /* Column Header */ }
489541 < div className = "flex items-center justify-between mb-4 flex-shrink-0" >
@@ -584,8 +636,14 @@ export const SprintBoardView: React.FC<SprintBoardViewProps> = ({
584636 return (
585637 < Card
586638 key = { sprint . id }
587- className = "p-4 bg-card border-l-4 border-l-blue-500 shadow-sm hover:shadow-md transition-shadow duration-200 flex flex-col"
639+ className = { `p-4 bg-card border-l-4 border-l-blue-500 shadow-sm hover:shadow-md transition-all duration-200 flex flex-col ${
640+ dragOverSprintId === sprint . id ? 'ring-2 ring-blue-500 bg-blue-50/50 dark:bg-blue-950/20' : ''
641+ } `}
588642 style = { { height : calculatedHeight } }
643+ onDragOver = { handleDragOverSprint }
644+ onDragEnter = { ( ) => setDragOverSprintId ( sprint . id ) }
645+ onDragLeave = { ( ) => setDragOverSprintId ( null ) }
646+ onDrop = { ( ) => handleDropOnSprint ( sprint . id ) }
589647 >
590648 { /* Column Header */ }
591649 < div className = "flex items-center justify-between mb-4 flex-shrink-0" >
0 commit comments