From 78ce18f4d321dd2643e6222fdb79a9b1a92e06f3 Mon Sep 17 00:00:00 2001 From: david Date: Fri, 27 Feb 2026 22:38:38 +0300 Subject: [PATCH] add delete and edit list --- .../src/components/kanban/DeleteListModal.tsx | 81 +++++++++++++ .../src/components/kanban/EditListModal.tsx | 108 ++++++++++++++++++ .../src/components/kanban/KanbanColumn.tsx | 77 ++++++++++++- frontend/src/hooks/useListMutations.ts | 2 + frontend/src/pages/BoardDetail.tsx | 15 ++- 5 files changed, 276 insertions(+), 7 deletions(-) create mode 100644 frontend/src/components/kanban/DeleteListModal.tsx create mode 100644 frontend/src/components/kanban/EditListModal.tsx diff --git a/frontend/src/components/kanban/DeleteListModal.tsx b/frontend/src/components/kanban/DeleteListModal.tsx new file mode 100644 index 0000000..a10889e --- /dev/null +++ b/frontend/src/components/kanban/DeleteListModal.tsx @@ -0,0 +1,81 @@ +import { ModalContentProps } from '../../types'; +import Trash2Icon from '../icons/Trash2Icon'; + +interface DeleteListModalProps extends ModalContentProps { + listName: string; + onDelete: () => Promise; +} + +export function DeleteListModal({ onClose, onDelete, listName }: DeleteListModalProps) { + const handleDelete = async () => { + try { + await onDelete(); + onClose(); + } catch (err) { + console.error('Failed to delete list:', err); + } + }; + + return ( +
+
+ + +
+ +
+
+
+
+ + + +
+
+
+

+ Delete "{listName}"? +

+

+ This will permanently delete the list and all cards in it. This action cannot be + undone. +

+
+
+ +
+ + +
+
+
+ ); +} diff --git a/frontend/src/components/kanban/EditListModal.tsx b/frontend/src/components/kanban/EditListModal.tsx new file mode 100644 index 0000000..a6d3452 --- /dev/null +++ b/frontend/src/components/kanban/EditListModal.tsx @@ -0,0 +1,108 @@ +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { ModalContentProps } from '../../types'; +import { useToast } from '../../context/toasts/useToast'; + +const listSchema = z.object({ + name: z + .string() + .min(1, 'List name is required') + .max(100, 'List name must be less than 100 characters'), +}); + +type ListFormData = z.infer; + +interface EditListModalProps extends ModalContentProps { + listName: string; + onSave: (name: string) => Promise; +} + +export function EditListModal({ onClose, onSave, listName }: EditListModalProps) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(listSchema), + defaultValues: { + name: listName, + }, + }); + + const { addNotification } = useToast(); + + const onSubmit = async (data: ListFormData) => { + try { + await onSave(data.name); + onClose(); + } catch (err) { + const errorMessage = err instanceof Error ? err.message : 'Failed to update list'; + addNotification({ + type: 'error', + title: 'Error', + message: errorMessage, + duration: 5000, + }); + } + }; + + return ( +
+
+ + +
+ +
+
+ + + {errors.name &&

{errors.name.message}

} +
+ +
+ + +
+
+
+ ); +} diff --git a/frontend/src/components/kanban/KanbanColumn.tsx b/frontend/src/components/kanban/KanbanColumn.tsx index 617dd40..5f624a9 100644 --- a/frontend/src/components/kanban/KanbanColumn.tsx +++ b/frontend/src/components/kanban/KanbanColumn.tsx @@ -3,6 +3,10 @@ import { SortableContext, verticalListSortingStrategy } from '@dnd-kit/sortable' import { ListWithCards, Card as CardType } from '../../types/kanban'; import { KanbanCard } from './KanbanCard'; import { CreateCardModal } from './CreateCardModal'; +import { EditListModal } from './EditListModal'; +import { DeleteListModal } from './DeleteListModal'; +import Edit2Icon from '../icons/Edit2Icon'; +import Trash2Icon from '../icons/Trash2Icon'; import { useModal } from '../../context/modals/useModal'; interface KanbanColumnProps { @@ -10,9 +14,18 @@ interface KanbanColumnProps { cards: CardType[]; onOpenCardModal: (card: CardType) => void; onCardCreate: (data: { name: string; description?: string }) => Promise; + onListEdit?: (name: string) => Promise; + onListDelete?: () => Promise; } -export function KanbanColumn({ list, cards, onOpenCardModal, onCardCreate }: KanbanColumnProps) { +export function KanbanColumn({ + list, + cards, + onOpenCardModal, + onCardCreate, + onListEdit, + onListDelete, +}: KanbanColumnProps) { const { setNodeRef, isOver } = useDroppable({ id: `LIST_${list.id}`, }); @@ -23,14 +36,66 @@ export function KanbanColumn({ list, cards, onOpenCardModal, onCardCreate }: Kan openModal((props) => ); }; + const handleEditList = () => { + if (!onListEdit) return; + openModal((props) => ( + { + await onListEdit(name); + }} + /> + )); + }; + + const handleDeleteList = () => { + if (!onListDelete) return; + openModal((props) => ( + { + await onListDelete(); + }} + /> + )); + }; + return (
-

- {list.name} - - {cards.length} +
+
+

{list.name}

+
+ {onListEdit && ( + + )} + {onListDelete && ( + + )} +
+
+ + {cards.length} cards -

+
void) { () => updateList(listId, { ...data, closed: false }), 'Updating list...' ); + onUpdate(); // Refresh board data after update addNotification({ type: 'success', title: 'List Updated', @@ -64,6 +65,7 @@ export function useListMutations(boardId: number, onUpdate: () => void) { message: 'List deleted successfully.', duration: 3000, }); + onUpdate(); // Refresh board data after delete } catch (err) { const errorMessage = err instanceof Error ? err.message : 'Failed to delete list'; addNotification({ diff --git a/frontend/src/pages/BoardDetail.tsx b/frontend/src/pages/BoardDetail.tsx index ae173e8..563c0dc 100644 --- a/frontend/src/pages/BoardDetail.tsx +++ b/frontend/src/pages/BoardDetail.tsx @@ -24,7 +24,7 @@ export function BoardDetail() { const { id } = useParams<{ id: string }>(); const { board, fetchBoard } = useBoard(parseInt(id || '0')); const { createCard, moveCard } = useCardMutations(parseInt(id || '0'), fetchBoard); - const { createList } = useListMutations(parseInt(id || '0'), fetchBoard); + const { createList, updateList, deleteList } = useListMutations(parseInt(id || '0'), fetchBoard); const { openModal } = useModal(); const [activeCard, setActiveCard] = useState(null); @@ -176,6 +176,17 @@ export function BoardDetail() { )); }; + const handleEditList = async (listId: number, name: string) => { + const list = board?.lists.find((l) => l.id === listId); + if (list) { + await updateList(listId, { name, pos: list.pos }); + } + }; + + const handleDeleteList = async (listId: number) => { + await deleteList(listId); + }; + const handleAddCard = (listId: number) => async (data: { name: string; description?: string }) => { await createCard(listId, { @@ -231,6 +242,8 @@ export function BoardDetail() { cards={list.cards} onOpenCardModal={handleOpenCardModal} onCardCreate={handleAddCard(list.id)} + onListEdit={(name) => handleEditList(list.id, name)} + onListDelete={() => handleDeleteList(list.id)} /> ))}