@@ -2,6 +2,7 @@ import { useDroppable } from '@dnd-kit/core';
22import { useSortable } from '@dnd-kit/sortable' ;
33import { CSS } from '@dnd-kit/utilities' ;
44import { useMutation } from '@tanstack/react-query' ;
5+ import { Ellipsis } from 'lucide-react' ;
56import { useState } from 'react' ;
67import {
78 BoardCard ,
@@ -10,13 +11,22 @@ import {
1011 DRAG_TYPE as DRAG_TYPE_CARD ,
1112} from '@/components/board/board-card' ;
1213import { Badge } from '@/components/ui/badge' ;
14+ import { Button } from '@/components/ui/button' ;
15+ import {
16+ DropdownMenu ,
17+ DropdownMenuContent ,
18+ DropdownMenuItem ,
19+ DropdownMenuTrigger ,
20+ } from '@/components/ui/dropdown-menu' ;
1321import { Input } from '@/components/ui/input' ;
1422import { useOptimisticMutation } from '@/hooks/use-optimistic-mutation' ;
23+ import { useOptimisticMutationWithUndo } from '@/hooks/use-optimistic-mutation-with-undo' ;
1524import { useRouteContext } from '@/hooks/use-route-context' ;
1625import {
1726 type Board ,
1827 type BoardColumn as BoardColumnType ,
1928 type BoardMember ,
29+ removeBoardColumnData ,
2030 renameBoardColumnData ,
2131} from '@/query-helpers/board' ;
2232import type { BoardCard as BoardCardType } from '@/query-helpers/board-cards' ;
@@ -27,12 +37,14 @@ export const DRAG_TYPE = 'board-column';
2737export const BoardColumn = ( {
2838 board,
2939 boardColumn,
40+ boardCards,
3041 unreadBoardCardCount,
3142 children,
3243 isDraggingColumn,
3344} : {
3445 board : Board ;
3546 boardColumn : BoardColumnType ;
47+ boardCards : BoardCardType [ ] ;
3648 unreadBoardCardCount : number ;
3749 children : React . ReactNode ;
3850 isDraggingColumn : boolean ;
@@ -50,15 +62,31 @@ export const BoardColumn = ({
5062 const [ isEditing , setIsEditing ] = useState ( false ) ;
5163 const [ editedName , setEditedName ] = useState ( boardColumn . name ) ;
5264
65+ const boardQueryKey = trpc . board . get . queryKey ( { boardId : board . id } ) ;
66+
5367 const optimisticallySetName = useOptimisticMutation ( {
5468 queryClient,
55- queryKey : trpc . board . get . queryKey ( { boardId : board . id } ) ,
69+ queryKey : boardQueryKey ,
5670 onExecute : ( { name } ) =>
5771 renameBoardColumnData ( { trpc, queryClient, params : { boardId : board . id , boardColumnId : boardColumn . id , name } } ) ,
5872 errorToast : 'Failed to rename column. Please try again.' ,
5973 mutation : useMutation ( trpc . boardColumn . setName . mutationOptions ( ) ) ,
6074 } ) ;
6175
76+ const createMutation = useMutation ( trpc . boardColumn . create . mutationOptions ( ) ) ;
77+ const optimisticallyDeleteColumn = useOptimisticMutationWithUndo ( {
78+ queryClient,
79+ queryKey : boardQueryKey ,
80+ onExecute : ( params ) => removeBoardColumnData ( { trpc, queryClient, params } ) ,
81+ successToast : 'Column deleted' ,
82+ errorToast : 'Failed to delete column. Please try again.' ,
83+ mutation : useMutation ( trpc . boardColumn . delete . mutationOptions ( ) ) ,
84+ undoMutationConfig : ( ) => ( {
85+ mutation : createMutation ,
86+ params : { boardId : board . id , name : boardColumn . name } ,
87+ } ) ,
88+ } ) ;
89+
6290 const handleNameClick = ( ) => {
6391 setIsEditing ( true ) ;
6492 setEditedName ( boardColumn . name ) ;
@@ -81,6 +109,14 @@ export const BoardColumn = ({
81109 }
82110 } ;
83111
112+ const hasBoardCards = boardCards . length > 0 ;
113+
114+ const handleDelete = ( ) => {
115+ if ( ! hasBoardCards ) {
116+ optimisticallyDeleteColumn ( { boardId : board . id , boardColumnId : boardColumn . id } ) ;
117+ }
118+ } ;
119+
84120 const style = {
85121 transform : CSS . Transform . toString ( transform ) ,
86122 transition,
@@ -113,14 +149,30 @@ export const BoardColumn = ({
113149 autoFocus
114150 />
115151 ) : (
116- < h2 className = "text-sm font-semibold cursor-pointer hover:text-primary w-full" onClick = { handleNameClick } >
117- { editedName }
118- </ h2 >
119- ) }
120- { unreadBoardCardCount > 0 && (
121- < Badge variant = "default" size = "sm" >
122- { unreadBoardCardCount }
123- </ Badge >
152+ < >
153+ < div className = "flex items-center gap-2 flex-1" >
154+ < h2 className = "text-sm font-semibold cursor-pointer hover:text-primary" onClick = { handleNameClick } >
155+ { editedName }
156+ </ h2 >
157+ { unreadBoardCardCount > 0 && (
158+ < Badge variant = "default" size = "sm" >
159+ { unreadBoardCardCount }
160+ </ Badge >
161+ ) }
162+ </ div >
163+ < DropdownMenu >
164+ < DropdownMenuTrigger asChild onClick = { ( e ) => e . stopPropagation ( ) } >
165+ < Button variant = "ghost" size = "icon-sm" className = "size-5 p-0 focus-visible:ring-0" >
166+ < Ellipsis className = "size-4 text-muted-foreground" />
167+ </ Button >
168+ </ DropdownMenuTrigger >
169+ < DropdownMenuContent align = "end" >
170+ < DropdownMenuItem onClick = { handleDelete } disabled = { hasBoardCards } className = "text-sm" >
171+ Delete column
172+ </ DropdownMenuItem >
173+ </ DropdownMenuContent >
174+ </ DropdownMenu >
175+ </ >
124176 ) }
125177 </ div >
126178 < div
0 commit comments