add delete and edit list

This commit is contained in:
david 2026-02-27 22:38:38 +03:00
parent 8a33e1c644
commit 78ce18f4d3
5 changed files with 276 additions and 7 deletions

View file

@ -0,0 +1,81 @@
import { ModalContentProps } from '../../types';
import Trash2Icon from '../icons/Trash2Icon';
interface DeleteListModalProps extends ModalContentProps {
listName: string;
onDelete: () => Promise<void>;
}
export function DeleteListModal({ onClose, onDelete, listName }: DeleteListModalProps) {
const handleDelete = async () => {
try {
await onDelete();
onClose();
} catch (err) {
console.error('Failed to delete list:', err);
}
};
return (
<div className="bg-gray-800 rounded-lg shadow-xl w-full max-w-md border border-gray-700">
<div className="flex justify-between items-center p-6 border-b border-gray-700">
<h2 id="modal-title" className="text-xl font-bold text-white">
Delete List
</h2>
<button onClick={onClose} className="text-gray-400 hover:text-white transition-colors">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<div className="p-6">
<div className="flex items-start gap-4 mb-6">
<div className="flex-shrink-0">
<div className="w-12 h-12 bg-red-600/20 rounded-full flex items-center justify-center">
<span className="w-6 h-6">
<Trash2Icon />
</span>
</div>
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-2">
Delete &quot;{listName}&quot;?
</h3>
<p className="text-gray-400 text-sm">
This will permanently delete the list and all cards in it. This action cannot be
undone.
</p>
</div>
</div>
<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="px-6 py-3 text-gray-300 hover:text-white transition-colors"
>
Cancel
</button>
<button
onClick={handleDelete}
className="bg-red-600 hover:bg-red-700 text-white font-medium py-3 px-6 rounded-lg transition-colors"
>
Delete List
</button>
</div>
</div>
</div>
);
}

View file

@ -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<typeof listSchema>;
interface EditListModalProps extends ModalContentProps {
listName: string;
onSave: (name: string) => Promise<void>;
}
export function EditListModal({ onClose, onSave, listName }: EditListModalProps) {
const {
register,
handleSubmit,
formState: { errors },
} = useForm<ListFormData>({
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 (
<div className="bg-gray-800 rounded-lg shadow-xl w-full max-w-md border border-gray-700">
<div className="flex justify-between items-center p-6 border-b border-gray-700">
<h2 id="modal-title" className="text-xl font-bold text-white">
Edit List
</h2>
<button onClick={onClose} className="text-gray-400 hover:text-white transition-colors">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
<form onSubmit={handleSubmit(onSubmit)} className="p-6">
<div className="mb-6">
<label htmlFor="name" className="block text-sm font-medium text-gray-300 mb-2">
List Name <span className="text-red-400">*</span>
</label>
<input
id="name"
type="text"
{...register('name')}
className="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-lg text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="e.g., To Do, In Progress, Done"
autoFocus
/>
{errors.name && <p className="mt-1 text-sm text-red-400">{errors.name.message}</p>}
</div>
<div className="flex justify-end gap-3">
<button
type="button"
onClick={onClose}
className="px-6 py-3 text-gray-300 hover:text-white transition-colors"
>
Cancel
</button>
<button
type="submit"
className="bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition-colors"
>
Save Changes
</button>
</div>
</form>
</div>
);
}

View file

@ -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<void>;
onListEdit?: (name: string) => Promise<void>;
onListDelete?: () => Promise<void>;
}
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) => <CreateCardModal {...props} onCreate={onCardCreate} />);
};
const handleEditList = () => {
if (!onListEdit) return;
openModal((props) => (
<EditListModal
{...props}
listName={list.name}
onSave={async (name) => {
await onListEdit(name);
}}
/>
));
};
const handleDeleteList = () => {
if (!onListDelete) return;
openModal((props) => (
<DeleteListModal
{...props}
listName={list.name}
onDelete={async () => {
await onListDelete();
}}
/>
));
};
return (
<div className="bg-gray-800 rounded-lg p-4 min-w-[300px] max-w-[300px] border border-gray-700 flex flex-col">
<h2 className="text-white font-bold text-lg mb-4 flex items-center justify-between">
{list.name}
<span className="bg-gray-600 text-gray-300 text-xs px-2 py-1 rounded-full">
{cards.length}
<div className="mb-4">
<div className="flex items-center justify-between mb-2">
<h2 className="text-white font-bold text-lg">{list.name}</h2>
<div className="flex items-center gap-2">
{onListEdit && (
<button
onClick={handleEditList}
className="text-gray-400 hover:text-blue-400 transition-colors"
title="Edit list"
>
<span className="w-4 h-4">
<Edit2Icon />
</span>
</h2>
</button>
)}
{onListDelete && (
<button
onClick={handleDeleteList}
className="text-gray-400 hover:text-red-400 transition-colors"
title="Delete list"
>
<span className="w-4 h-4">
<Trash2Icon />
</span>
</button>
)}
</div>
</div>
<span className="bg-gray-600 text-gray-300 text-xs px-2 py-1 rounded-full inline-block">
{cards.length} cards
</span>
</div>
<SortableContext
id={list.id.toString()}

View file

@ -36,6 +36,7 @@ export function useListMutations(boardId: number, onUpdate: () => 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({

View file

@ -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<CardType | null>(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)}
/>
))}
</div>