@ram_28/kf-ai-sdk 1.0.19 → 1.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -12
- package/dist/components/hooks/useFilter/types.d.ts +14 -11
- package/dist/components/hooks/useFilter/types.d.ts.map +1 -1
- package/dist/components/hooks/useFilter/useFilter.d.ts +1 -1
- package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -1
- package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -1
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/types.d.ts +5 -22
- package/dist/components/hooks/useKanban/types.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -1
- package/dist/components/hooks/useTable/types.d.ts +19 -31
- package/dist/components/hooks/useTable/types.d.ts.map +1 -1
- package/dist/components/hooks/useTable/useTable.d.ts.map +1 -1
- package/dist/error-handling-CAoD0Kwb.cjs +1 -0
- package/dist/error-handling-CrhTtD88.js +14 -0
- package/dist/filter.cjs +1 -1
- package/dist/filter.mjs +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +338 -327
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/kanban.cjs +2 -2
- package/dist/kanban.mjs +332 -322
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +113 -96
- package/dist/table.types.d.ts +1 -1
- package/dist/table.types.d.ts.map +1 -1
- package/dist/types/common.d.ts +26 -6
- package/dist/types/common.d.ts.map +1 -1
- package/dist/useFilter-DzpP_ag0.cjs +1 -0
- package/dist/useFilter-H5bgAZQF.js +120 -0
- package/dist/utils/api/buildListOptions.d.ts +43 -0
- package/dist/utils/api/buildListOptions.d.ts.map +1 -0
- package/dist/utils/api/index.d.ts +2 -0
- package/dist/utils/api/index.d.ts.map +1 -0
- package/dist/utils/error-handling.d.ts +41 -0
- package/dist/utils/error-handling.d.ts.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/docs/QUICK_REFERENCE.md +142 -420
- package/docs/useAuth.md +52 -340
- package/docs/useFilter.md +858 -162
- package/docs/useForm.md +712 -501
- package/docs/useKanban.md +534 -279
- package/docs/useTable.md +725 -214
- package/package.json +1 -1
- package/sdk/components/hooks/useFilter/types.ts +14 -11
- package/sdk/components/hooks/useFilter/useFilter.ts +20 -18
- package/sdk/components/hooks/useForm/apiClient.ts +2 -1
- package/sdk/components/hooks/useForm/useForm.ts +35 -11
- package/sdk/components/hooks/useKanban/types.ts +7 -23
- package/sdk/components/hooks/useKanban/useKanban.ts +54 -18
- package/sdk/components/hooks/useTable/types.ts +26 -32
- package/sdk/components/hooks/useTable/useTable.llm.txt +8 -22
- package/sdk/components/hooks/useTable/useTable.ts +70 -25
- package/sdk/index.ts +154 -10
- package/sdk/table.types.ts +3 -0
- package/sdk/types/common.ts +31 -6
- package/sdk/utils/api/buildListOptions.ts +120 -0
- package/sdk/utils/api/index.ts +2 -0
- package/sdk/utils/error-handling.ts +150 -0
- package/sdk/utils/index.ts +6 -0
- package/dist/useFilter-Dofowpr_.cjs +0 -1
- package/dist/useFilter-Dv-mr9QW.js +0 -117
package/docs/useKanban.md
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
1
|
# useKanban
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Kanban board state management with drag-and-drop, card CRUD operations, and filtering.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
- Integrates with `useFilter` for filtering cards across the board with support for nested condition groups
|
|
7
|
-
- Includes built-in drag-and-drop handlers and prop getters (`getCardProps`, `getColumnProps`) for easy integration with any UI library
|
|
8
|
-
- Handles optimistic updates for card operations with automatic rollback on failure
|
|
9
|
-
|
|
10
|
-
## Type Reference
|
|
5
|
+
## Imports
|
|
11
6
|
|
|
12
7
|
```typescript
|
|
13
8
|
import { useKanban } from "@ram_28/kf-ai-sdk/kanban";
|
|
14
|
-
import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
|
|
15
9
|
import type {
|
|
16
10
|
UseKanbanOptionsType,
|
|
17
11
|
UseKanbanReturnType,
|
|
@@ -19,15 +13,12 @@ import type {
|
|
|
19
13
|
KanbanColumnType,
|
|
20
14
|
ColumnConfigType,
|
|
21
15
|
} from "@ram_28/kf-ai-sdk/kanban/types";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
} from "@ram_28/kf-ai-sdk/filter/types";
|
|
29
|
-
|
|
30
|
-
// Static column configuration
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Type Definitions
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
// Column configuration
|
|
31
22
|
interface ColumnConfigType {
|
|
32
23
|
id: string;
|
|
33
24
|
title: string;
|
|
@@ -36,18 +27,16 @@ interface ColumnConfigType {
|
|
|
36
27
|
limit?: number;
|
|
37
28
|
}
|
|
38
29
|
|
|
39
|
-
//
|
|
40
|
-
type KanbanCardType<T =
|
|
30
|
+
// Card type with custom fields
|
|
31
|
+
type KanbanCardType<T> = {
|
|
41
32
|
_id: string;
|
|
42
33
|
title: string;
|
|
43
34
|
columnId: string;
|
|
44
35
|
position: number;
|
|
45
|
-
_created_at?: Date;
|
|
46
|
-
_modified_at?: Date;
|
|
47
36
|
} & T;
|
|
48
37
|
|
|
49
|
-
//
|
|
50
|
-
interface KanbanColumnType<T
|
|
38
|
+
// Column with cards
|
|
39
|
+
interface KanbanColumnType<T> {
|
|
51
40
|
_id: string;
|
|
52
41
|
title: string;
|
|
53
42
|
position: number;
|
|
@@ -64,177 +53,350 @@ interface UseKanbanOptionsType<T> {
|
|
|
64
53
|
enableFiltering?: boolean;
|
|
65
54
|
enableSearch?: boolean;
|
|
66
55
|
initialState?: {
|
|
67
|
-
|
|
68
|
-
filterOperator?: ConditionGroupOperatorType;
|
|
56
|
+
filter?: UseFilterOptionsType; // { conditions?, operator? }
|
|
69
57
|
search?: string;
|
|
58
|
+
columnOrder?: string[];
|
|
59
|
+
sorting?: { field: keyof T; direction: "asc" | "desc" };
|
|
70
60
|
};
|
|
71
|
-
onCardMove?: (card
|
|
72
|
-
onCardCreate?: (card
|
|
73
|
-
onCardUpdate?: (card
|
|
74
|
-
onCardDelete?: (cardId
|
|
75
|
-
onError?: (error
|
|
61
|
+
onCardMove?: (card, fromColumnId, toColumnId) => void;
|
|
62
|
+
onCardCreate?: (card) => void;
|
|
63
|
+
onCardUpdate?: (card) => void;
|
|
64
|
+
onCardDelete?: (cardId) => void;
|
|
65
|
+
onError?: (error) => void;
|
|
76
66
|
}
|
|
77
67
|
|
|
78
68
|
// Hook return type
|
|
79
69
|
interface UseKanbanReturnType<T> {
|
|
80
|
-
// Data
|
|
81
70
|
columns: KanbanColumnType<T>[];
|
|
82
71
|
totalCards: number;
|
|
83
|
-
|
|
84
|
-
// Loading states
|
|
85
72
|
isLoading: boolean;
|
|
86
73
|
isFetching: boolean;
|
|
87
74
|
isUpdating: boolean;
|
|
88
75
|
error: Error | null;
|
|
89
76
|
|
|
90
77
|
// Card operations
|
|
91
|
-
createCard: (card
|
|
92
|
-
updateCard: (id
|
|
93
|
-
deleteCard: (id
|
|
94
|
-
moveCard: (cardId
|
|
95
|
-
reorderCards: (cardIds: string[], columnId: string) => Promise<void>;
|
|
78
|
+
createCard: (card) => Promise<string>;
|
|
79
|
+
updateCard: (id, updates) => Promise<void>;
|
|
80
|
+
deleteCard: (id) => Promise<void>;
|
|
81
|
+
moveCard: (cardId, toColumnId, position?) => Promise<void>;
|
|
96
82
|
|
|
97
|
-
// Search
|
|
83
|
+
// Search & Filter
|
|
98
84
|
searchQuery: string;
|
|
99
|
-
setSearchQuery: (value
|
|
100
|
-
clearSearch: () => void;
|
|
101
|
-
|
|
102
|
-
// Filter (uses useFilter internally)
|
|
85
|
+
setSearchQuery: (value) => void;
|
|
103
86
|
filter: UseFilterReturnType;
|
|
104
87
|
|
|
105
|
-
// Drag
|
|
88
|
+
// Drag & Drop
|
|
106
89
|
isDragging: boolean;
|
|
107
90
|
draggedCard: KanbanCardType<T> | null;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Prop getters
|
|
111
|
-
getCardProps: (card: KanbanCardType<T>) => {
|
|
112
|
-
draggable: boolean;
|
|
113
|
-
role: string;
|
|
114
|
-
"aria-selected": boolean;
|
|
115
|
-
"aria-grabbed": boolean;
|
|
116
|
-
onDragStart: (e: any) => void;
|
|
117
|
-
onDragEnd: () => void;
|
|
118
|
-
onKeyDown: (e: any) => void;
|
|
119
|
-
};
|
|
120
|
-
getColumnProps: (columnId: string) => {
|
|
121
|
-
"data-column-id": string;
|
|
122
|
-
role: string;
|
|
123
|
-
onDragOver: (e: any) => void;
|
|
124
|
-
onDrop: (e: any) => void;
|
|
125
|
-
};
|
|
91
|
+
getCardProps: (card) => object;
|
|
92
|
+
getColumnProps: (columnId) => object;
|
|
126
93
|
|
|
127
|
-
// Utilities
|
|
128
94
|
refetch: () => Promise<void>;
|
|
129
|
-
loadMore: (columnId: string) => void;
|
|
130
95
|
}
|
|
131
96
|
```
|
|
132
97
|
|
|
133
|
-
##
|
|
98
|
+
## Basic Example
|
|
99
|
+
|
|
100
|
+
A minimal kanban board displaying columns and cards.
|
|
134
101
|
|
|
135
102
|
```tsx
|
|
136
103
|
import { useKanban } from "@ram_28/kf-ai-sdk/kanban";
|
|
137
|
-
import {
|
|
138
|
-
import type {
|
|
139
|
-
UseKanbanOptionsType,
|
|
140
|
-
UseKanbanReturnType,
|
|
141
|
-
KanbanCardType,
|
|
142
|
-
KanbanColumnType,
|
|
143
|
-
ColumnConfigType,
|
|
144
|
-
} from "@ram_28/kf-ai-sdk/kanban/types";
|
|
145
|
-
import type {
|
|
146
|
-
ConditionType,
|
|
147
|
-
ConditionGroupType,
|
|
148
|
-
ConditionGroupOperatorType,
|
|
149
|
-
FilterType,
|
|
150
|
-
UseFilterReturnType,
|
|
151
|
-
} from "@ram_28/kf-ai-sdk/filter/types";
|
|
152
|
-
import { ProductRestocking, ProductRestockingType } from "../sources";
|
|
153
|
-
import { Roles } from "../sources/roles";
|
|
154
|
-
|
|
155
|
-
// Get the typed restocking record for the InventoryManager role
|
|
156
|
-
type RestockingRecord = ProductRestockingType<typeof Roles.InventoryManager>;
|
|
157
|
-
|
|
158
|
-
// Define custom card fields that extend the base KanbanCardType
|
|
159
|
-
interface RestockingCardData {
|
|
160
|
-
productTitle: string;
|
|
161
|
-
productSKU: string;
|
|
162
|
-
currentStock: number;
|
|
163
|
-
quantityOrdered: number;
|
|
164
|
-
warehouse: "Warehouse_A" | "Warehouse_B" | "Warehouse_C";
|
|
165
|
-
priority: "Low" | "Medium" | "High" | "Critical";
|
|
166
|
-
}
|
|
104
|
+
import type { ColumnConfigType, KanbanCardType } from "@ram_28/kf-ai-sdk/kanban/types";
|
|
167
105
|
|
|
168
|
-
|
|
169
|
-
|
|
106
|
+
interface TaskData {
|
|
107
|
+
priority: "Low" | "Medium" | "High";
|
|
108
|
+
assignee: string;
|
|
109
|
+
}
|
|
170
110
|
|
|
171
|
-
|
|
172
|
-
// Instantiate the ProductRestocking source with role
|
|
173
|
-
const restocking = new ProductRestocking(Roles.InventoryManager);
|
|
111
|
+
type Task = KanbanCardType<TaskData>;
|
|
174
112
|
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
{ id: "
|
|
178
|
-
{ id: "
|
|
179
|
-
{ id: "
|
|
180
|
-
{ id: "Received", title: "Received & Restocked", position: 3, color: "green", limit: 20 },
|
|
113
|
+
function TaskBoard() {
|
|
114
|
+
const columns: ColumnConfigType[] = [
|
|
115
|
+
{ id: "todo", title: "To Do", position: 0 },
|
|
116
|
+
{ id: "in-progress", title: "In Progress", position: 1 },
|
|
117
|
+
{ id: "done", title: "Done", position: 2 },
|
|
181
118
|
];
|
|
182
119
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
initialState: {
|
|
191
|
-
filterOperator: "And",
|
|
192
|
-
},
|
|
193
|
-
onCardMove: (card: RestockingCard, fromColumnId: string, toColumnId: string) => {
|
|
194
|
-
console.log(`Moved "${card.productTitle}" from ${fromColumnId} to ${toColumnId}`);
|
|
195
|
-
},
|
|
196
|
-
onCardCreate: (card: RestockingCard) => console.log("Created:", card.productTitle),
|
|
197
|
-
onCardUpdate: (card: RestockingCard) => console.log("Updated:", card._id),
|
|
198
|
-
onCardDelete: (cardId: string) => console.log("Deleted:", cardId),
|
|
199
|
-
onError: (error: Error) => console.error("Kanban error:", error.message),
|
|
200
|
-
};
|
|
120
|
+
const kanban = useKanban<TaskData>({
|
|
121
|
+
source: "BDO_Tasks",
|
|
122
|
+
columns,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
if (kanban.isLoading) return <div>Loading board...</div>;
|
|
126
|
+
if (kanban.error) return <div>Error: {kanban.error.message}</div>;
|
|
201
127
|
|
|
202
|
-
|
|
128
|
+
return (
|
|
129
|
+
<div className="kanban-board">
|
|
130
|
+
{kanban.columns.map((column) => (
|
|
131
|
+
<div key={column._id} className="column">
|
|
132
|
+
<h3>{column.title} ({column.cards.length})</h3>
|
|
133
|
+
<div className="cards">
|
|
134
|
+
{column.cards.map((card) => (
|
|
135
|
+
<div key={card._id} className="card">
|
|
136
|
+
<h4>{card.title}</h4>
|
|
137
|
+
<span className="priority">{card.priority}</span>
|
|
138
|
+
<span className="assignee">{card.assignee}</span>
|
|
139
|
+
</div>
|
|
140
|
+
))}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
))}
|
|
144
|
+
</div>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
203
150
|
|
|
204
|
-
|
|
205
|
-
|
|
151
|
+
## Card Operations
|
|
152
|
+
|
|
153
|
+
### Create Card
|
|
154
|
+
|
|
155
|
+
Add a new card to a column.
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
function BoardWithCreate() {
|
|
159
|
+
const kanban = useKanban<TaskData>({
|
|
160
|
+
source: "BDO_Tasks",
|
|
161
|
+
columns,
|
|
162
|
+
});
|
|
206
163
|
|
|
207
|
-
// Create a new card
|
|
208
164
|
const handleCreateCard = async (columnId: string) => {
|
|
209
|
-
const cardId
|
|
165
|
+
const cardId = await kanban.createCard({
|
|
210
166
|
columnId,
|
|
211
|
-
title: "New
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
currentStock: 5,
|
|
215
|
-
quantityOrdered: 50,
|
|
216
|
-
warehouse: "Warehouse_A",
|
|
217
|
-
priority: "High",
|
|
167
|
+
title: "New Task",
|
|
168
|
+
priority: "Medium",
|
|
169
|
+
assignee: "Unassigned",
|
|
218
170
|
});
|
|
219
|
-
console.log("Created card
|
|
171
|
+
console.log("Created card:", cardId);
|
|
220
172
|
};
|
|
221
173
|
|
|
222
|
-
|
|
223
|
-
|
|
174
|
+
return (
|
|
175
|
+
<div className="kanban-board">
|
|
176
|
+
{kanban.columns.map((column) => (
|
|
177
|
+
<div key={column._id} className="column">
|
|
178
|
+
<div className="column-header">
|
|
179
|
+
<h3>{column.title}</h3>
|
|
180
|
+
<button onClick={() => handleCreateCard(column._id)}>
|
|
181
|
+
+ Add Card
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
{/* cards */}
|
|
185
|
+
</div>
|
|
186
|
+
))}
|
|
187
|
+
</div>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Update Card
|
|
193
|
+
|
|
194
|
+
Modify an existing card's properties.
|
|
195
|
+
|
|
196
|
+
```tsx
|
|
197
|
+
function CardWithEdit({ card }: { card: Task }) {
|
|
198
|
+
const kanban = useKanban<TaskData>({
|
|
199
|
+
source: "BDO_Tasks",
|
|
200
|
+
columns,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const handleUpdateTitle = async (newTitle: string) => {
|
|
204
|
+
await kanban.updateCard(card._id, { title: newTitle });
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const handleChangePriority = async (priority: TaskData["priority"]) => {
|
|
224
208
|
await kanban.updateCard(card._id, { priority });
|
|
225
209
|
};
|
|
226
210
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
211
|
+
return (
|
|
212
|
+
<div className="card">
|
|
213
|
+
<input
|
|
214
|
+
defaultValue={card.title}
|
|
215
|
+
onBlur={(e) => handleUpdateTitle(e.target.value)}
|
|
216
|
+
/>
|
|
217
|
+
<select
|
|
218
|
+
value={card.priority}
|
|
219
|
+
onChange={(e) => handleChangePriority(e.target.value as TaskData["priority"])}
|
|
220
|
+
>
|
|
221
|
+
<option value="Low">Low</option>
|
|
222
|
+
<option value="Medium">Medium</option>
|
|
223
|
+
<option value="High">High</option>
|
|
224
|
+
</select>
|
|
225
|
+
</div>
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Delete Card
|
|
231
|
+
|
|
232
|
+
Remove a card from the board.
|
|
233
|
+
|
|
234
|
+
```tsx
|
|
235
|
+
function CardWithDelete({ card }: { card: Task }) {
|
|
236
|
+
const kanban = useKanban<TaskData>({
|
|
237
|
+
source: "BDO_Tasks",
|
|
238
|
+
columns,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
const handleDelete = async () => {
|
|
242
|
+
if (confirm(`Delete "${card.title}"?`)) {
|
|
243
|
+
await kanban.deleteCard(card._id);
|
|
244
|
+
}
|
|
230
245
|
};
|
|
231
246
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
247
|
+
return (
|
|
248
|
+
<div className="card">
|
|
249
|
+
<h4>{card.title}</h4>
|
|
250
|
+
<button onClick={handleDelete} className="delete-btn">
|
|
251
|
+
Delete
|
|
252
|
+
</button>
|
|
253
|
+
</div>
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Move Card Between Columns
|
|
259
|
+
|
|
260
|
+
Programmatically move a card to a different column.
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
function CardWithMoveActions({ card }: { card: Task }) {
|
|
264
|
+
const kanban = useKanban<TaskData>({
|
|
265
|
+
source: "BDO_Tasks",
|
|
266
|
+
columns,
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
const moveTo = async (columnId: string) => {
|
|
270
|
+
await kanban.moveCard(card._id, columnId);
|
|
235
271
|
};
|
|
236
272
|
|
|
237
|
-
|
|
273
|
+
return (
|
|
274
|
+
<div className="card">
|
|
275
|
+
<h4>{card.title}</h4>
|
|
276
|
+
<div className="move-actions">
|
|
277
|
+
<button onClick={() => moveTo("todo")}>To Do</button>
|
|
278
|
+
<button onClick={() => moveTo("in-progress")}>In Progress</button>
|
|
279
|
+
<button onClick={() => moveTo("done")}>Done</button>
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Drag and Drop
|
|
289
|
+
|
|
290
|
+
### Using Prop Getters
|
|
291
|
+
|
|
292
|
+
Apply drag-and-drop handlers using built-in prop getters.
|
|
293
|
+
|
|
294
|
+
```tsx
|
|
295
|
+
function DraggableBoard() {
|
|
296
|
+
const kanban = useKanban<TaskData>({
|
|
297
|
+
source: "BDO_Tasks",
|
|
298
|
+
columns,
|
|
299
|
+
enableDragDrop: true,
|
|
300
|
+
onCardMove: (card, from, to) => {
|
|
301
|
+
console.log(`Moved "${card.title}" from ${from} to ${to}`);
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<div className="kanban-board">
|
|
307
|
+
{kanban.columns.map((column) => (
|
|
308
|
+
<div
|
|
309
|
+
key={column._id}
|
|
310
|
+
className={`column ${kanban.dragOverColumn === column._id ? "drag-over" : ""}`}
|
|
311
|
+
{...kanban.getColumnProps(column._id)}
|
|
312
|
+
>
|
|
313
|
+
<h3>{column.title}</h3>
|
|
314
|
+
<div className="cards">
|
|
315
|
+
{column.cards.map((card) => (
|
|
316
|
+
<div
|
|
317
|
+
key={card._id}
|
|
318
|
+
className={`card ${kanban.draggedCard?._id === card._id ? "dragging" : ""}`}
|
|
319
|
+
{...kanban.getCardProps(card)}
|
|
320
|
+
>
|
|
321
|
+
<h4>{card.title}</h4>
|
|
322
|
+
</div>
|
|
323
|
+
))}
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
))}
|
|
327
|
+
</div>
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Drag State Indicators
|
|
333
|
+
|
|
334
|
+
Show visual feedback during drag operations.
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
function BoardWithDragIndicators() {
|
|
338
|
+
const kanban = useKanban<TaskData>({
|
|
339
|
+
source: "BDO_Tasks",
|
|
340
|
+
columns,
|
|
341
|
+
enableDragDrop: true,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<div className="kanban-board">
|
|
346
|
+
{/* Drag indicator */}
|
|
347
|
+
{kanban.isDragging && (
|
|
348
|
+
<div className="drag-indicator">
|
|
349
|
+
Moving: {kanban.draggedCard?.title}
|
|
350
|
+
</div>
|
|
351
|
+
)}
|
|
352
|
+
|
|
353
|
+
{kanban.columns.map((column) => (
|
|
354
|
+
<div
|
|
355
|
+
key={column._id}
|
|
356
|
+
className="column"
|
|
357
|
+
style={{
|
|
358
|
+
backgroundColor: kanban.dragOverColumn === column._id ? "#e3f2fd" : "white",
|
|
359
|
+
}}
|
|
360
|
+
{...kanban.getColumnProps(column._id)}
|
|
361
|
+
>
|
|
362
|
+
<h3>{column.title}</h3>
|
|
363
|
+
<div className="cards">
|
|
364
|
+
{column.cards.map((card) => (
|
|
365
|
+
<div
|
|
366
|
+
key={card._id}
|
|
367
|
+
className="card"
|
|
368
|
+
style={{
|
|
369
|
+
opacity: kanban.draggedCard?._id === card._id ? 0.5 : 1,
|
|
370
|
+
}}
|
|
371
|
+
{...kanban.getCardProps(card)}
|
|
372
|
+
>
|
|
373
|
+
{card.title}
|
|
374
|
+
</div>
|
|
375
|
+
))}
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
))}
|
|
379
|
+
</div>
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Filtering Cards
|
|
387
|
+
|
|
388
|
+
### Filter by Priority
|
|
389
|
+
|
|
390
|
+
Show only cards matching a priority level.
|
|
391
|
+
|
|
392
|
+
```tsx
|
|
393
|
+
function BoardWithPriorityFilter() {
|
|
394
|
+
const kanban = useKanban<TaskData>({
|
|
395
|
+
source: "BDO_Tasks",
|
|
396
|
+
columns,
|
|
397
|
+
enableFiltering: true,
|
|
398
|
+
});
|
|
399
|
+
|
|
238
400
|
const filterByPriority = (priority: string) => {
|
|
239
401
|
kanban.filter.clearAllConditions();
|
|
240
402
|
if (priority !== "all") {
|
|
@@ -246,175 +408,268 @@ function InventoryRestockingBoard() {
|
|
|
246
408
|
}
|
|
247
409
|
};
|
|
248
410
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
411
|
+
return (
|
|
412
|
+
<div>
|
|
413
|
+
<div className="filter-bar">
|
|
414
|
+
<button onClick={() => filterByPriority("all")}>All</button>
|
|
415
|
+
<button onClick={() => filterByPriority("High")}>High Priority</button>
|
|
416
|
+
<button onClick={() => filterByPriority("Medium")}>Medium</button>
|
|
417
|
+
<button onClick={() => filterByPriority("Low")}>Low</button>
|
|
418
|
+
</div>
|
|
419
|
+
|
|
420
|
+
{/* board rendering */}
|
|
421
|
+
</div>
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Filter by Assignee (My Tasks)
|
|
427
|
+
|
|
428
|
+
Show only cards assigned to the current user.
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
function BoardWithMyTasks({ currentUserId }: { currentUserId: string }) {
|
|
432
|
+
const kanban = useKanban<TaskData>({
|
|
433
|
+
source: "BDO_Tasks",
|
|
434
|
+
columns,
|
|
435
|
+
enableFiltering: true,
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
const showMyTasks = () => {
|
|
439
|
+
kanban.filter.clearAllConditions();
|
|
252
440
|
kanban.filter.addCondition({
|
|
253
441
|
Operator: "EQ",
|
|
254
|
-
LHSField: "
|
|
255
|
-
RHSValue:
|
|
256
|
-
}
|
|
442
|
+
LHSField: "assignee",
|
|
443
|
+
RHSValue: currentUserId,
|
|
444
|
+
});
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
const showAllTasks = () => {
|
|
448
|
+
kanban.filter.clearAllConditions();
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
return (
|
|
452
|
+
<div>
|
|
453
|
+
<div className="filter-bar">
|
|
454
|
+
<button onClick={showMyTasks}>My Tasks</button>
|
|
455
|
+
<button onClick={showAllTasks}>All Tasks</button>
|
|
456
|
+
{kanban.filter.hasConditions && (
|
|
457
|
+
<span>Filtered: {kanban.totalCards} cards</span>
|
|
458
|
+
)}
|
|
459
|
+
</div>
|
|
460
|
+
|
|
461
|
+
{/* board rendering */}
|
|
462
|
+
</div>
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Combined Filters
|
|
468
|
+
|
|
469
|
+
Apply multiple filter conditions.
|
|
470
|
+
|
|
471
|
+
```tsx
|
|
472
|
+
function BoardWithCombinedFilters() {
|
|
473
|
+
const kanban = useKanban<TaskData>({
|
|
474
|
+
source: "BDO_Tasks",
|
|
475
|
+
columns,
|
|
476
|
+
enableFiltering: true,
|
|
477
|
+
initialState: {
|
|
478
|
+
filter: {
|
|
479
|
+
operator: "And",
|
|
480
|
+
},
|
|
481
|
+
},
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
const applyUrgentFilter = () => {
|
|
485
|
+
kanban.filter.clearAllConditions();
|
|
486
|
+
|
|
487
|
+
// High priority OR overdue
|
|
488
|
+
const groupId = kanban.filter.addConditionGroup("Or");
|
|
257
489
|
kanban.filter.addCondition({
|
|
258
490
|
Operator: "EQ",
|
|
259
491
|
LHSField: "priority",
|
|
260
492
|
RHSValue: "High",
|
|
261
493
|
}, groupId);
|
|
494
|
+
kanban.filter.addCondition({
|
|
495
|
+
Operator: "LT",
|
|
496
|
+
LHSField: "dueDate",
|
|
497
|
+
RHSValue: new Date().toISOString(),
|
|
498
|
+
}, groupId);
|
|
262
499
|
};
|
|
263
500
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
<button onClick={() => kanban.filter.removeCondition(item.id!)}>×</button>
|
|
273
|
-
</span>
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
if (isConditionGroup(item)) {
|
|
277
|
-
return (
|
|
278
|
-
<span key={item.id} className="filter-group-tag">
|
|
279
|
-
{item.Operator} Group ({item.Condition.length})
|
|
280
|
-
<button onClick={() => kanban.filter.removeCondition(item.id!)}>×</button>
|
|
281
|
-
</span>
|
|
282
|
-
);
|
|
283
|
-
}
|
|
284
|
-
return null;
|
|
285
|
-
})}
|
|
501
|
+
return (
|
|
502
|
+
<div>
|
|
503
|
+
<div className="filter-bar">
|
|
504
|
+
<button onClick={applyUrgentFilter}>Urgent Tasks</button>
|
|
505
|
+
<button onClick={() => kanban.filter.clearAllConditions()}>Clear</button>
|
|
506
|
+
</div>
|
|
507
|
+
|
|
508
|
+
{/* board rendering */}
|
|
286
509
|
</div>
|
|
287
510
|
);
|
|
511
|
+
}
|
|
512
|
+
```
|
|
288
513
|
|
|
289
|
-
|
|
290
|
-
const getFilterPayload = (): FilterType | undefined => {
|
|
291
|
-
return kanban.filter.payload;
|
|
292
|
-
};
|
|
514
|
+
---
|
|
293
515
|
|
|
294
|
-
|
|
295
|
-
return <div>Loading board...</div>;
|
|
296
|
-
}
|
|
516
|
+
## Search
|
|
297
517
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
518
|
+
### Basic Search
|
|
519
|
+
|
|
520
|
+
Filter cards by text search.
|
|
521
|
+
|
|
522
|
+
```tsx
|
|
523
|
+
function BoardWithSearch() {
|
|
524
|
+
const kanban = useKanban<TaskData>({
|
|
525
|
+
source: "BDO_Tasks",
|
|
526
|
+
columns,
|
|
527
|
+
enableSearch: true,
|
|
528
|
+
});
|
|
306
529
|
|
|
307
530
|
return (
|
|
308
|
-
<div
|
|
309
|
-
{/* Search */}
|
|
531
|
+
<div>
|
|
310
532
|
<div className="search-bar">
|
|
311
533
|
<input
|
|
312
534
|
type="text"
|
|
313
|
-
placeholder="Search
|
|
535
|
+
placeholder="Search tasks..."
|
|
314
536
|
value={kanban.searchQuery}
|
|
315
537
|
onChange={(e) => kanban.setSearchQuery(e.target.value)}
|
|
316
538
|
/>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
{/* Filter Controls */}
|
|
321
|
-
<div className="filter-controls">
|
|
322
|
-
<button onClick={() => filterByPriority("Critical")}>Critical</button>
|
|
323
|
-
<button onClick={() => filterByPriority("High")}>High</button>
|
|
324
|
-
<button onClick={addUrgentFilter}>Urgent (Critical OR High)</button>
|
|
325
|
-
<button onClick={() => filterByPriority("all")}>All</button>
|
|
326
|
-
{kanban.filter.hasConditions && (
|
|
327
|
-
<button onClick={() => kanban.filter.clearAllConditions()}>Clear Filters</button>
|
|
539
|
+
{kanban.searchQuery && (
|
|
540
|
+
<button onClick={kanban.clearSearch}>Clear</button>
|
|
328
541
|
)}
|
|
329
|
-
<span>Logic: {kanban.filter.operator}</span>
|
|
330
542
|
</div>
|
|
331
543
|
|
|
332
|
-
{/*
|
|
333
|
-
|
|
544
|
+
{/* board rendering */}
|
|
545
|
+
</div>
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
```
|
|
334
549
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
)}
|
|
341
|
-
{kanban.isUpdating && <span>Updating...</span>}
|
|
342
|
-
</div>
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## Column Configuration
|
|
553
|
+
|
|
554
|
+
### Columns with Limits
|
|
343
555
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
556
|
+
Set maximum card limits per column.
|
|
557
|
+
|
|
558
|
+
```tsx
|
|
559
|
+
function BoardWithLimits() {
|
|
560
|
+
const columns: ColumnConfigType[] = [
|
|
561
|
+
{ id: "todo", title: "To Do", position: 0 },
|
|
562
|
+
{ id: "in-progress", title: "In Progress", position: 1, limit: 5 },
|
|
563
|
+
{ id: "review", title: "Review", position: 2, limit: 3 },
|
|
564
|
+
{ id: "done", title: "Done", position: 3 },
|
|
565
|
+
];
|
|
566
|
+
|
|
567
|
+
const kanban = useKanban<TaskData>({
|
|
568
|
+
source: "BDO_Tasks",
|
|
569
|
+
columns,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
return (
|
|
573
|
+
<div className="kanban-board">
|
|
574
|
+
{kanban.columns.map((column) => {
|
|
575
|
+
const isOverLimit = column.limit && column.cards.length >= column.limit;
|
|
576
|
+
|
|
577
|
+
return (
|
|
347
578
|
<div
|
|
348
579
|
key={column._id}
|
|
349
|
-
className={`column ${
|
|
350
|
-
style={{ backgroundColor: column.color }}
|
|
351
|
-
{...kanban.getColumnProps(column._id)}
|
|
580
|
+
className={`column ${isOverLimit ? "over-limit" : ""}`}
|
|
352
581
|
>
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
<
|
|
356
|
-
<span>
|
|
582
|
+
<h3>
|
|
583
|
+
{column.title}
|
|
584
|
+
<span className="count">
|
|
357
585
|
{column.cards.length}
|
|
358
586
|
{column.limit && ` / ${column.limit}`}
|
|
359
587
|
</span>
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
{/* Cards */}
|
|
364
|
-
<div className="cards-container">
|
|
365
|
-
{column.cards.map((card: RestockingCard) => (
|
|
366
|
-
<div
|
|
367
|
-
key={card._id}
|
|
368
|
-
className={`card ${kanban.draggedCard?._id === card._id ? "dragging" : ""}`}
|
|
369
|
-
{...kanban.getCardProps(card)}
|
|
370
|
-
>
|
|
371
|
-
<div className="card-header">
|
|
372
|
-
<span className={`priority-badge ${card.priority.toLowerCase()}`}>
|
|
373
|
-
{card.priority}
|
|
374
|
-
</span>
|
|
375
|
-
<button onClick={() => handleDeleteCard(card._id)}>×</button>
|
|
376
|
-
</div>
|
|
377
|
-
<h4>{card.productTitle}</h4>
|
|
378
|
-
<p>SKU: {card.productSKU}</p>
|
|
379
|
-
<p>Stock: {card.currentStock} / Ordered: {card.quantityOrdered}</p>
|
|
380
|
-
<p>Warehouse: {card.warehouse}</p>
|
|
381
|
-
<div className="card-actions">
|
|
382
|
-
<button onClick={() => handleUpdatePriority(card, "Critical")}>
|
|
383
|
-
Set Critical
|
|
384
|
-
</button>
|
|
385
|
-
<button onClick={() => handleMoveCard(card._id, "Received")}>
|
|
386
|
-
Mark Received
|
|
387
|
-
</button>
|
|
388
|
-
</div>
|
|
389
|
-
</div>
|
|
390
|
-
))}
|
|
391
|
-
</div>
|
|
392
|
-
|
|
393
|
-
{/* Load More */}
|
|
394
|
-
{column.cards.length >= 10 && (
|
|
395
|
-
<button
|
|
396
|
-
className="load-more"
|
|
397
|
-
onClick={() => kanban.loadMore(column._id)}
|
|
398
|
-
>
|
|
399
|
-
Load More
|
|
400
|
-
</button>
|
|
401
|
-
)}
|
|
588
|
+
</h3>
|
|
589
|
+
{/* cards */}
|
|
402
590
|
</div>
|
|
403
|
-
)
|
|
404
|
-
|
|
591
|
+
);
|
|
592
|
+
})}
|
|
593
|
+
</div>
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
```
|
|
405
597
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
598
|
+
### Colored Columns
|
|
599
|
+
|
|
600
|
+
Apply colors to columns for visual distinction.
|
|
601
|
+
|
|
602
|
+
```tsx
|
|
603
|
+
function ColoredBoard() {
|
|
604
|
+
const columns: ColumnConfigType[] = [
|
|
605
|
+
{ id: "backlog", title: "Backlog", position: 0, color: "#f5f5f5" },
|
|
606
|
+
{ id: "todo", title: "To Do", position: 1, color: "#fff3e0" },
|
|
607
|
+
{ id: "in-progress", title: "In Progress", position: 2, color: "#e3f2fd" },
|
|
608
|
+
{ id: "done", title: "Done", position: 3, color: "#e8f5e9" },
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
const kanban = useKanban<TaskData>({
|
|
612
|
+
source: "BDO_Tasks",
|
|
613
|
+
columns,
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
return (
|
|
617
|
+
<div className="kanban-board">
|
|
618
|
+
{kanban.columns.map((column) => (
|
|
619
|
+
<div
|
|
620
|
+
key={column._id}
|
|
621
|
+
className="column"
|
|
622
|
+
style={{ backgroundColor: column.color }}
|
|
623
|
+
>
|
|
624
|
+
<h3>{column.title}</h3>
|
|
625
|
+
{/* cards */}
|
|
626
|
+
</div>
|
|
627
|
+
))}
|
|
628
|
+
</div>
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
```
|
|
412
632
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## Event Callbacks
|
|
636
|
+
|
|
637
|
+
### Track Card Operations
|
|
638
|
+
|
|
639
|
+
Handle card lifecycle events.
|
|
640
|
+
|
|
641
|
+
```tsx
|
|
642
|
+
function BoardWithCallbacks() {
|
|
643
|
+
const kanban = useKanban<TaskData>({
|
|
644
|
+
source: "BDO_Tasks",
|
|
645
|
+
columns,
|
|
646
|
+
onCardMove: (card, fromColumn, toColumn) => {
|
|
647
|
+
console.log(`Card "${card.title}" moved from ${fromColumn} to ${toColumn}`);
|
|
648
|
+
|
|
649
|
+
// Track analytics
|
|
650
|
+
analytics.track("card_moved", {
|
|
651
|
+
cardId: card._id,
|
|
652
|
+
from: fromColumn,
|
|
653
|
+
to: toColumn,
|
|
654
|
+
});
|
|
655
|
+
},
|
|
656
|
+
onCardCreate: (card) => {
|
|
657
|
+
console.log(`Card created: ${card.title}`);
|
|
658
|
+
},
|
|
659
|
+
onCardUpdate: (card) => {
|
|
660
|
+
console.log(`Card updated: ${card._id}`);
|
|
661
|
+
},
|
|
662
|
+
onCardDelete: (cardId) => {
|
|
663
|
+
console.log(`Card deleted: ${cardId}`);
|
|
664
|
+
},
|
|
665
|
+
onError: (error) => {
|
|
666
|
+
alert(`Error: ${error.message}`);
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
return (
|
|
671
|
+
<div className="kanban-board">
|
|
672
|
+
{/* board rendering */}
|
|
418
673
|
</div>
|
|
419
674
|
);
|
|
420
675
|
}
|