@ram_28/kf-ai-sdk 1.0.16 → 1.0.19
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/dist/api/client.d.ts.map +1 -1
- package/dist/api.cjs +1 -1
- package/dist/api.mjs +2 -2
- package/dist/auth.cjs +1 -1
- package/dist/auth.mjs +1 -1
- package/dist/{client-C15j4O5B.cjs → client-DgtkT50N.cjs} +1 -1
- package/dist/{client-CfvLiGfP.js → client-V-WzUb8H.js} +9 -5
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -1
- package/dist/components/hooks/useKanban/context.d.ts +1 -1
- package/dist/components/hooks/useKanban/context.d.ts.map +1 -1
- package/dist/form.cjs +1 -1
- package/dist/form.mjs +740 -740
- package/dist/kanban.cjs +1 -1
- package/dist/kanban.mjs +4 -2
- package/dist/{metadata-2FLBsFcf.cjs → metadata-0lZAfuTP.cjs} +1 -1
- package/dist/{metadata-DBcoDth-.js → metadata-B88D_pVS.js} +1 -1
- package/dist/table.cjs +1 -1
- package/dist/table.mjs +1 -1
- package/docs/QUICK_REFERENCE.md +528 -0
- package/docs/useAuth.md +402 -0
- package/docs/useFilter.md +273 -0
- package/docs/useForm.md +629 -0
- package/docs/useKanban.md +421 -0
- package/docs/useTable.md +372 -0
- package/package.json +4 -2
- package/sdk/api/client.ts +7 -1
- package/sdk/components/hooks/useForm/useForm.ts +12 -2
- package/sdk/components/hooks/useKanban/context.ts +5 -3
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
# useKanban
|
|
2
|
+
|
|
3
|
+
## Brief Description
|
|
4
|
+
|
|
5
|
+
- Provides complete kanban board state management including column configuration, card CRUD operations, and drag-and-drop functionality
|
|
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
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { useKanban } from "@ram_28/kf-ai-sdk/kanban";
|
|
14
|
+
import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
|
|
15
|
+
import type {
|
|
16
|
+
UseKanbanOptionsType,
|
|
17
|
+
UseKanbanReturnType,
|
|
18
|
+
KanbanCardType,
|
|
19
|
+
KanbanColumnType,
|
|
20
|
+
ColumnConfigType,
|
|
21
|
+
} from "@ram_28/kf-ai-sdk/kanban/types";
|
|
22
|
+
import type {
|
|
23
|
+
ConditionType,
|
|
24
|
+
ConditionGroupType,
|
|
25
|
+
ConditionGroupOperatorType,
|
|
26
|
+
FilterType,
|
|
27
|
+
UseFilterReturnType,
|
|
28
|
+
} from "@ram_28/kf-ai-sdk/filter/types";
|
|
29
|
+
|
|
30
|
+
// Static column configuration
|
|
31
|
+
interface ColumnConfigType {
|
|
32
|
+
id: string;
|
|
33
|
+
title: string;
|
|
34
|
+
position: number;
|
|
35
|
+
color?: string;
|
|
36
|
+
limit?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Kanban card with custom fields
|
|
40
|
+
type KanbanCardType<T = Record<string, any>> = {
|
|
41
|
+
_id: string;
|
|
42
|
+
title: string;
|
|
43
|
+
columnId: string;
|
|
44
|
+
position: number;
|
|
45
|
+
_created_at?: Date;
|
|
46
|
+
_modified_at?: Date;
|
|
47
|
+
} & T;
|
|
48
|
+
|
|
49
|
+
// Kanban column with cards
|
|
50
|
+
interface KanbanColumnType<T = Record<string, any>> {
|
|
51
|
+
_id: string;
|
|
52
|
+
title: string;
|
|
53
|
+
position: number;
|
|
54
|
+
cards: KanbanCardType<T>[];
|
|
55
|
+
color?: string;
|
|
56
|
+
limit?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Hook options
|
|
60
|
+
interface UseKanbanOptionsType<T> {
|
|
61
|
+
source: string;
|
|
62
|
+
columns: ColumnConfigType[];
|
|
63
|
+
enableDragDrop?: boolean;
|
|
64
|
+
enableFiltering?: boolean;
|
|
65
|
+
enableSearch?: boolean;
|
|
66
|
+
initialState?: {
|
|
67
|
+
filters?: Array<ConditionType | ConditionGroupType>;
|
|
68
|
+
filterOperator?: ConditionGroupOperatorType;
|
|
69
|
+
search?: string;
|
|
70
|
+
};
|
|
71
|
+
onCardMove?: (card: KanbanCardType<T>, fromColumnId: string, toColumnId: string) => void;
|
|
72
|
+
onCardCreate?: (card: KanbanCardType<T>) => void;
|
|
73
|
+
onCardUpdate?: (card: KanbanCardType<T>) => void;
|
|
74
|
+
onCardDelete?: (cardId: string) => void;
|
|
75
|
+
onError?: (error: Error) => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Hook return type
|
|
79
|
+
interface UseKanbanReturnType<T> {
|
|
80
|
+
// Data
|
|
81
|
+
columns: KanbanColumnType<T>[];
|
|
82
|
+
totalCards: number;
|
|
83
|
+
|
|
84
|
+
// Loading states
|
|
85
|
+
isLoading: boolean;
|
|
86
|
+
isFetching: boolean;
|
|
87
|
+
isUpdating: boolean;
|
|
88
|
+
error: Error | null;
|
|
89
|
+
|
|
90
|
+
// Card operations
|
|
91
|
+
createCard: (card: Partial<KanbanCardType<T>> & { columnId: string }) => Promise<string>;
|
|
92
|
+
updateCard: (id: string, updates: Partial<KanbanCardType<T>>) => Promise<void>;
|
|
93
|
+
deleteCard: (id: string) => Promise<void>;
|
|
94
|
+
moveCard: (cardId: string, toColumnId: string, position?: number) => Promise<void>;
|
|
95
|
+
reorderCards: (cardIds: string[], columnId: string) => Promise<void>;
|
|
96
|
+
|
|
97
|
+
// Search
|
|
98
|
+
searchQuery: string;
|
|
99
|
+
setSearchQuery: (value: string) => void;
|
|
100
|
+
clearSearch: () => void;
|
|
101
|
+
|
|
102
|
+
// Filter (uses useFilter internally)
|
|
103
|
+
filter: UseFilterReturnType;
|
|
104
|
+
|
|
105
|
+
// Drag drop state
|
|
106
|
+
isDragging: boolean;
|
|
107
|
+
draggedCard: KanbanCardType<T> | null;
|
|
108
|
+
dragOverColumn: string | null;
|
|
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
|
+
};
|
|
126
|
+
|
|
127
|
+
// Utilities
|
|
128
|
+
refetch: () => Promise<void>;
|
|
129
|
+
loadMore: (columnId: string) => void;
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Usage Example
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
import { useKanban } from "@ram_28/kf-ai-sdk/kanban";
|
|
137
|
+
import { isCondition, isConditionGroup } from "@ram_28/kf-ai-sdk/filter";
|
|
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
|
+
}
|
|
167
|
+
|
|
168
|
+
// Full card type with base fields + custom data
|
|
169
|
+
type RestockingCard = KanbanCardType<RestockingCardData>;
|
|
170
|
+
|
|
171
|
+
function InventoryRestockingBoard() {
|
|
172
|
+
// Instantiate the ProductRestocking source with role
|
|
173
|
+
const restocking = new ProductRestocking(Roles.InventoryManager);
|
|
174
|
+
|
|
175
|
+
// Static column configurations
|
|
176
|
+
const columnConfigs: ColumnConfigType[] = [
|
|
177
|
+
{ id: "LowStockAlert", title: "Low Stock Alert", position: 0, color: "red" },
|
|
178
|
+
{ id: "OrderPlaced", title: "Order Placed", position: 1, color: "yellow" },
|
|
179
|
+
{ id: "InTransit", title: "In Transit", position: 2, color: "blue" },
|
|
180
|
+
{ id: "Received", title: "Received & Restocked", position: 3, color: "green", limit: 20 },
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
// Hook configuration with full options
|
|
184
|
+
const options: UseKanbanOptionsType<RestockingCardData> = {
|
|
185
|
+
source: restocking._id, // Use the Business Object ID from the source class
|
|
186
|
+
columns: columnConfigs,
|
|
187
|
+
enableDragDrop: true,
|
|
188
|
+
enableFiltering: true,
|
|
189
|
+
enableSearch: true,
|
|
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
|
+
};
|
|
201
|
+
|
|
202
|
+
const kanban: UseKanbanReturnType<RestockingCardData> = useKanban<RestockingCardData>(options);
|
|
203
|
+
|
|
204
|
+
// Access filter state (UseFilterReturnType)
|
|
205
|
+
const filterState: UseFilterReturnType = kanban.filter;
|
|
206
|
+
|
|
207
|
+
// Create a new card
|
|
208
|
+
const handleCreateCard = async (columnId: string) => {
|
|
209
|
+
const cardId: string = await kanban.createCard({
|
|
210
|
+
columnId,
|
|
211
|
+
title: "New Restock Task",
|
|
212
|
+
productTitle: "Sample Product",
|
|
213
|
+
productSKU: "SKU-001",
|
|
214
|
+
currentStock: 5,
|
|
215
|
+
quantityOrdered: 50,
|
|
216
|
+
warehouse: "Warehouse_A",
|
|
217
|
+
priority: "High",
|
|
218
|
+
});
|
|
219
|
+
console.log("Created card with id:", cardId);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// Update card priority
|
|
223
|
+
const handleUpdatePriority = async (card: RestockingCard, priority: RestockingCardData["priority"]) => {
|
|
224
|
+
await kanban.updateCard(card._id, { priority });
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Delete a card
|
|
228
|
+
const handleDeleteCard = async (cardId: string) => {
|
|
229
|
+
await kanban.deleteCard(cardId);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Move card between columns
|
|
233
|
+
const handleMoveCard = async (cardId: string, toColumnId: string) => {
|
|
234
|
+
await kanban.moveCard(cardId, toColumnId);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// Filter by priority
|
|
238
|
+
const filterByPriority = (priority: string) => {
|
|
239
|
+
kanban.filter.clearAllConditions();
|
|
240
|
+
if (priority !== "all") {
|
|
241
|
+
kanban.filter.addCondition({
|
|
242
|
+
Operator: "EQ",
|
|
243
|
+
LHSField: "priority",
|
|
244
|
+
RHSValue: priority,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// Add complex filter (Critical OR High priority)
|
|
250
|
+
const addUrgentFilter = () => {
|
|
251
|
+
const groupId = kanban.filter.addConditionGroup("Or");
|
|
252
|
+
kanban.filter.addCondition({
|
|
253
|
+
Operator: "EQ",
|
|
254
|
+
LHSField: "priority",
|
|
255
|
+
RHSValue: "Critical",
|
|
256
|
+
}, groupId);
|
|
257
|
+
kanban.filter.addCondition({
|
|
258
|
+
Operator: "EQ",
|
|
259
|
+
LHSField: "priority",
|
|
260
|
+
RHSValue: "High",
|
|
261
|
+
}, groupId);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Render active filters using type guards
|
|
265
|
+
const renderActiveFilters = () => (
|
|
266
|
+
<div className="active-filters">
|
|
267
|
+
{kanban.filter.items.map((item) => {
|
|
268
|
+
if (isCondition(item)) {
|
|
269
|
+
return (
|
|
270
|
+
<span key={item.id} className="filter-tag">
|
|
271
|
+
{item.LHSField} {item.Operator} {String(item.RHSValue)}
|
|
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
|
+
})}
|
|
286
|
+
</div>
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Get filter payload for debugging
|
|
290
|
+
const getFilterPayload = (): FilterType | undefined => {
|
|
291
|
+
return kanban.filter.payload;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
if (kanban.isLoading) {
|
|
295
|
+
return <div>Loading board...</div>;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (kanban.error) {
|
|
299
|
+
return (
|
|
300
|
+
<div>
|
|
301
|
+
<p>Error: {kanban.error.message}</p>
|
|
302
|
+
<button onClick={() => kanban.refetch()}>Retry</button>
|
|
303
|
+
</div>
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div className="kanban-board">
|
|
309
|
+
{/* Search */}
|
|
310
|
+
<div className="search-bar">
|
|
311
|
+
<input
|
|
312
|
+
type="text"
|
|
313
|
+
placeholder="Search products..."
|
|
314
|
+
value={kanban.searchQuery}
|
|
315
|
+
onChange={(e) => kanban.setSearchQuery(e.target.value)}
|
|
316
|
+
/>
|
|
317
|
+
<button onClick={kanban.clearSearch}>Clear</button>
|
|
318
|
+
</div>
|
|
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>
|
|
328
|
+
)}
|
|
329
|
+
<span>Logic: {kanban.filter.operator}</span>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
{/* Active Filters */}
|
|
333
|
+
{renderActiveFilters()}
|
|
334
|
+
|
|
335
|
+
{/* Board Stats */}
|
|
336
|
+
<div className="board-stats">
|
|
337
|
+
<span>Total cards: {kanban.totalCards}</span>
|
|
338
|
+
{kanban.isDragging && (
|
|
339
|
+
<span>Dragging: {kanban.draggedCard?.productTitle}</span>
|
|
340
|
+
)}
|
|
341
|
+
{kanban.isUpdating && <span>Updating...</span>}
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
{/* Kanban Columns */}
|
|
345
|
+
<div className="columns-container">
|
|
346
|
+
{kanban.columns.map((column: KanbanColumnType<RestockingCardData>) => (
|
|
347
|
+
<div
|
|
348
|
+
key={column._id}
|
|
349
|
+
className={`column ${kanban.dragOverColumn === column._id ? "drag-over" : ""}`}
|
|
350
|
+
style={{ backgroundColor: column.color }}
|
|
351
|
+
{...kanban.getColumnProps(column._id)}
|
|
352
|
+
>
|
|
353
|
+
{/* Column Header */}
|
|
354
|
+
<div className="column-header">
|
|
355
|
+
<h3>{column.title}</h3>
|
|
356
|
+
<span>
|
|
357
|
+
{column.cards.length}
|
|
358
|
+
{column.limit && ` / ${column.limit}`}
|
|
359
|
+
</span>
|
|
360
|
+
<button onClick={() => handleCreateCard(column._id)}>+ Add</button>
|
|
361
|
+
</div>
|
|
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
|
+
)}
|
|
402
|
+
</div>
|
|
403
|
+
))}
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
{/* Board Actions */}
|
|
407
|
+
<div className="board-actions">
|
|
408
|
+
<button onClick={() => kanban.refetch()} disabled={kanban.isFetching}>
|
|
409
|
+
{kanban.isFetching ? "Refreshing..." : "Refresh Board"}
|
|
410
|
+
</button>
|
|
411
|
+
</div>
|
|
412
|
+
|
|
413
|
+
{/* Debug: Filter payload */}
|
|
414
|
+
<details>
|
|
415
|
+
<summary>Filter Payload (Debug)</summary>
|
|
416
|
+
<pre>{JSON.stringify(getFilterPayload(), null, 2)}</pre>
|
|
417
|
+
</details>
|
|
418
|
+
</div>
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
```
|