feat: Phase 2 card interactions - priority picker, context menu, WIP limits, column collapse, checklist reorder
- PriorityPicker component with 5 colored chips in card detail modal - Card context menu: Move to column, Set priority, Duplicate, Delete - duplicateCard store action (clones card, inserts after original) - Column WIP limits with amber/red indicators when at/over limit - Column collapse/expand to 40px vertical strip - Checklist item drag reordering with grip handle - Comment store actions (addComment, deleteComment) for Phase 3
This commit is contained in:
@@ -28,10 +28,13 @@ interface BoardActions {
|
||||
moveColumn: (fromIndex: number, toIndex: number) => void;
|
||||
setColumnWidth: (columnId: string, width: ColumnWidth) => void;
|
||||
setColumnColor: (columnId: string, color: string | null) => void;
|
||||
setColumnWipLimit: (columnId: string, limit: number | null) => void;
|
||||
toggleColumnCollapse: (columnId: string) => void;
|
||||
|
||||
addCard: (columnId: string, title: string) => string;
|
||||
updateCard: (cardId: string, updates: Partial<Card>) => void;
|
||||
deleteCard: (cardId: string) => void;
|
||||
duplicateCard: (cardId: string) => string | null;
|
||||
moveCard: (
|
||||
cardId: string,
|
||||
fromColumnId: string,
|
||||
@@ -48,10 +51,14 @@ interface BoardActions {
|
||||
toggleChecklistItem: (cardId: string, itemId: string) => void;
|
||||
updateChecklistItem: (cardId: string, itemId: string, text: string) => void;
|
||||
deleteChecklistItem: (cardId: string, itemId: string) => void;
|
||||
reorderChecklistItems: (cardId: string, fromIndex: number, toIndex: number) => void;
|
||||
|
||||
addAttachment: (cardId: string, attachment: Omit<Attachment, "id">) => void;
|
||||
removeAttachment: (cardId: string, attachmentId: string) => void;
|
||||
|
||||
addComment: (cardId: string, text: string) => void;
|
||||
deleteComment: (cardId: string, commentId: string) => void;
|
||||
|
||||
updateBoardTitle: (title: string) => void;
|
||||
updateBoardColor: (color: string) => void;
|
||||
updateBoardSettings: (settings: Board["settings"]) => void;
|
||||
@@ -195,6 +202,26 @@ export const useBoardStore = create<BoardState & BoardActions>()(
|
||||
}));
|
||||
},
|
||||
|
||||
setColumnWipLimit: (columnId, limit) => {
|
||||
mutate(get, set, (b) => ({
|
||||
...b,
|
||||
updatedAt: now(),
|
||||
columns: b.columns.map((c) =>
|
||||
c.id === columnId ? { ...c, wipLimit: limit } : c
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
||||
toggleColumnCollapse: (columnId) => {
|
||||
mutate(get, set, (b) => ({
|
||||
...b,
|
||||
updatedAt: now(),
|
||||
columns: b.columns.map((c) =>
|
||||
c.id === columnId ? { ...c, collapsed: !c.collapsed } : c
|
||||
),
|
||||
}));
|
||||
},
|
||||
|
||||
// -- Card actions --
|
||||
|
||||
addCard: (columnId, title) => {
|
||||
@@ -260,6 +287,48 @@ export const useBoardStore = create<BoardState & BoardActions>()(
|
||||
});
|
||||
},
|
||||
|
||||
duplicateCard: (cardId) => {
|
||||
const { board } = get();
|
||||
if (!board) return null;
|
||||
const original = board.cards[cardId];
|
||||
if (!original) return null;
|
||||
const column = board.columns.find((c) => c.cardIds.includes(cardId));
|
||||
if (!column) return null;
|
||||
|
||||
const newId = ulid();
|
||||
const ts = now();
|
||||
const clone: Card = {
|
||||
...original,
|
||||
id: newId,
|
||||
title: `${original.title} (copy)`,
|
||||
comments: [],
|
||||
createdAt: ts,
|
||||
updatedAt: ts,
|
||||
};
|
||||
|
||||
const insertIndex = column.cardIds.indexOf(cardId) + 1;
|
||||
|
||||
mutate(get, set, (b) => ({
|
||||
...b,
|
||||
updatedAt: ts,
|
||||
cards: { ...b.cards, [newId]: clone },
|
||||
columns: b.columns.map((c) =>
|
||||
c.id === column.id
|
||||
? {
|
||||
...c,
|
||||
cardIds: [
|
||||
...c.cardIds.slice(0, insertIndex),
|
||||
newId,
|
||||
...c.cardIds.slice(insertIndex),
|
||||
],
|
||||
}
|
||||
: c
|
||||
),
|
||||
}));
|
||||
|
||||
return newId;
|
||||
},
|
||||
|
||||
moveCard: (cardId, fromColumnId, toColumnId, toIndex) => {
|
||||
mutate(get, set, (b) => ({
|
||||
...b,
|
||||
@@ -428,6 +497,24 @@ export const useBoardStore = create<BoardState & BoardActions>()(
|
||||
});
|
||||
},
|
||||
|
||||
reorderChecklistItems: (cardId, fromIndex, toIndex) => {
|
||||
mutate(get, set, (b) => {
|
||||
const card = b.cards[cardId];
|
||||
if (!card) return b;
|
||||
const items = [...card.checklist];
|
||||
const [moved] = items.splice(fromIndex, 1);
|
||||
items.splice(toIndex, 0, moved);
|
||||
return {
|
||||
...b,
|
||||
updatedAt: now(),
|
||||
cards: {
|
||||
...b.cards,
|
||||
[cardId]: { ...card, checklist: items, updatedAt: now() },
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// -- Attachment actions --
|
||||
|
||||
addAttachment: (cardId, attachment) => {
|
||||
@@ -473,6 +560,47 @@ export const useBoardStore = create<BoardState & BoardActions>()(
|
||||
});
|
||||
},
|
||||
|
||||
// -- Comment actions --
|
||||
|
||||
addComment: (cardId, text) => {
|
||||
mutate(get, set, (b) => {
|
||||
const card = b.cards[cardId];
|
||||
if (!card) return b;
|
||||
const comment = { id: ulid(), text, createdAt: now() };
|
||||
return {
|
||||
...b,
|
||||
updatedAt: now(),
|
||||
cards: {
|
||||
...b.cards,
|
||||
[cardId]: {
|
||||
...card,
|
||||
comments: [comment, ...card.comments],
|
||||
updatedAt: now(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
deleteComment: (cardId, commentId) => {
|
||||
mutate(get, set, (b) => {
|
||||
const card = b.cards[cardId];
|
||||
if (!card) return b;
|
||||
return {
|
||||
...b,
|
||||
updatedAt: now(),
|
||||
cards: {
|
||||
...b.cards,
|
||||
[cardId]: {
|
||||
...card,
|
||||
comments: card.comments.filter((c) => c.id !== commentId),
|
||||
updatedAt: now(),
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
// -- Board metadata --
|
||||
|
||||
updateBoardTitle: (title) => {
|
||||
|
||||
Reference in New Issue
Block a user