@ram_28/kf-ai-sdk 1.0.0
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/LICENSE +21 -0
- package/README.md +840 -0
- package/dist/api/client.d.ts +78 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/datetime.d.ts +21 -0
- package/dist/api/datetime.d.ts.map +1 -0
- package/dist/api/index.d.ts +7 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/metadata.d.ts +75 -0
- package/dist/api/metadata.d.ts.map +1 -0
- package/dist/components/hooks/index.d.ts +8 -0
- package/dist/components/hooks/index.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/index.d.ts +5 -0
- package/dist/components/hooks/useFilter/index.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts +33 -0
- package/dist/components/hooks/useFilter/payloadBuilder.utils.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/types.d.ts +137 -0
- package/dist/components/hooks/useFilter/types.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/useFilter.d.ts +3 -0
- package/dist/components/hooks/useFilter/useFilter.d.ts.map +1 -0
- package/dist/components/hooks/useFilter/validation.utils.d.ts +38 -0
- package/dist/components/hooks/useFilter/validation.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/apiClient.d.ts +71 -0
- package/dist/components/hooks/useForm/apiClient.d.ts.map +1 -0
- package/dist/components/hooks/useForm/expressionValidator.utils.d.ts +28 -0
- package/dist/components/hooks/useForm/expressionValidator.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/index.d.ts +6 -0
- package/dist/components/hooks/useForm/index.d.ts.map +1 -0
- package/dist/components/hooks/useForm/optimizedExpressionValidator.utils.d.ts +88 -0
- package/dist/components/hooks/useForm/optimizedExpressionValidator.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/ruleClassifier.utils.d.ts +28 -0
- package/dist/components/hooks/useForm/ruleClassifier.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/schemaParser.utils.d.ts +29 -0
- package/dist/components/hooks/useForm/schemaParser.utils.d.ts.map +1 -0
- package/dist/components/hooks/useForm/types.d.ts +412 -0
- package/dist/components/hooks/useForm/types.d.ts.map +1 -0
- package/dist/components/hooks/useForm/useForm.d.ts +3 -0
- package/dist/components/hooks/useForm/useForm.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/apiClient.d.ts +99 -0
- package/dist/components/hooks/useKanban/apiClient.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/context.d.ts +4 -0
- package/dist/components/hooks/useKanban/context.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/dragDropManager.d.ts +27 -0
- package/dist/components/hooks/useKanban/dragDropManager.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/index.d.ts +6 -0
- package/dist/components/hooks/useKanban/index.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/types.d.ts +438 -0
- package/dist/components/hooks/useKanban/types.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/useKanban.d.ts +3 -0
- package/dist/components/hooks/useKanban/useKanban.d.ts.map +1 -0
- package/dist/components/hooks/useKanban/useKanbanSimple.d.ts +62 -0
- package/dist/components/hooks/useKanban/useKanbanSimple.d.ts.map +1 -0
- package/dist/components/hooks/useTable/index.d.ts +3 -0
- package/dist/components/hooks/useTable/index.d.ts.map +1 -0
- package/dist/components/hooks/useTable/types.d.ts +107 -0
- package/dist/components/hooks/useTable/types.d.ts.map +1 -0
- package/dist/components/hooks/useTable/useTable.d.ts +8 -0
- package/dist/components/hooks/useTable/useTable.d.ts.map +1 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/ui/index.d.ts +2 -0
- package/dist/components/ui/index.d.ts.map +1 -0
- package/dist/components/ui/kanban/Kanban.d.ts +12 -0
- package/dist/components/ui/kanban/Kanban.d.ts.map +1 -0
- package/dist/components/ui/kanban/index.d.ts +2 -0
- package/dist/components/ui/kanban/index.d.ts.map +1 -0
- package/dist/index.cjs +45 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +6522 -0
- package/dist/types/base-fields.d.ts +182 -0
- package/dist/types/base-fields.d.ts.map +1 -0
- package/dist/types/common.d.ts +238 -0
- package/dist/types/common.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/cn.d.ts +7 -0
- package/dist/utils/cn.d.ts.map +1 -0
- package/dist/utils/formatting.d.ts +52 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/package.json +98 -0
- package/sdk/api/client.ts +447 -0
- package/sdk/api/datetime.ts +33 -0
- package/sdk/api/index.ts +61 -0
- package/sdk/api/metadata.ts +148 -0
- package/sdk/components/hooks/index.ts +34 -0
- package/sdk/components/hooks/useFilter/index.ts +37 -0
- package/sdk/components/hooks/useFilter/payloadBuilder.utils.ts +298 -0
- package/sdk/components/hooks/useFilter/types.ts +158 -0
- package/sdk/components/hooks/useFilter/useFilter.llm.txt +497 -0
- package/sdk/components/hooks/useFilter/useFilter.ts +494 -0
- package/sdk/components/hooks/useFilter/validation.utils.ts +401 -0
- package/sdk/components/hooks/useForm/apiClient.ts +441 -0
- package/sdk/components/hooks/useForm/expressionValidator.utils.ts +444 -0
- package/sdk/components/hooks/useForm/index.ts +64 -0
- package/sdk/components/hooks/useForm/optimizedExpressionValidator.utils.ts +482 -0
- package/sdk/components/hooks/useForm/ruleClassifier.utils.ts +424 -0
- package/sdk/components/hooks/useForm/schemaParser.utils.ts +519 -0
- package/sdk/components/hooks/useForm/types.ts +630 -0
- package/sdk/components/hooks/useForm/useForm.llm.txt +340 -0
- package/sdk/components/hooks/useForm/useForm.ts +821 -0
- package/sdk/components/hooks/useKanban/apiClient.ts +494 -0
- package/sdk/components/hooks/useKanban/context.ts +14 -0
- package/sdk/components/hooks/useKanban/dragDropManager.ts +529 -0
- package/sdk/components/hooks/useKanban/index.ts +63 -0
- package/sdk/components/hooks/useKanban/types.ts +606 -0
- package/sdk/components/hooks/useKanban/useKanban.llm.txt +482 -0
- package/sdk/components/hooks/useKanban/useKanban.ts +725 -0
- package/sdk/components/hooks/useKanban/useKanbanSimple.ts +389 -0
- package/sdk/components/hooks/useTable/index.ts +5 -0
- package/sdk/components/hooks/useTable/types.ts +154 -0
- package/sdk/components/hooks/useTable/useTable.llm.txt +344 -0
- package/sdk/components/hooks/useTable/useTable.ts +413 -0
- package/sdk/components/index.ts +15 -0
- package/sdk/components/ui/index.ts +2 -0
- package/sdk/components/ui/kanban/Kanban.tsx +134 -0
- package/sdk/components/ui/kanban/index.ts +11 -0
- package/sdk/index.ts +13 -0
- package/sdk/types/base-fields.ts +221 -0
- package/sdk/types/common.ts +306 -0
- package/sdk/types/index.ts +5 -0
- package/sdk/utils/cn.ts +10 -0
- package/sdk/utils/formatting.ts +212 -0
- package/sdk/utils/index.ts +5 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// KANBAN API CLIENT
|
|
3
|
+
// ============================================================
|
|
4
|
+
// Backend integration for kanban operations using existing API patterns
|
|
5
|
+
// Follows the same structure as useForm and useTable API clients
|
|
6
|
+
|
|
7
|
+
import { api } from "../../../api";
|
|
8
|
+
import type {
|
|
9
|
+
ListOptions,
|
|
10
|
+
ListResponse,
|
|
11
|
+
CreateUpdateResponse,
|
|
12
|
+
DeleteResponse,
|
|
13
|
+
CountResponse
|
|
14
|
+
} from "../../../types/common";
|
|
15
|
+
import type {
|
|
16
|
+
KanbanCard,
|
|
17
|
+
KanbanColumn,
|
|
18
|
+
BulkCardUpdateRequest,
|
|
19
|
+
BulkColumnUpdateRequest
|
|
20
|
+
} from "./types";
|
|
21
|
+
|
|
22
|
+
// ============================================================
|
|
23
|
+
// COLUMN API OPERATIONS
|
|
24
|
+
// ============================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fetch columns from backend with optional filtering and sorting
|
|
28
|
+
*/
|
|
29
|
+
export async function fetchColumns<T>(
|
|
30
|
+
source: string,
|
|
31
|
+
options?: ListOptions
|
|
32
|
+
): Promise<KanbanColumn<T>[]> {
|
|
33
|
+
const client = api<KanbanColumn<T>>(source);
|
|
34
|
+
|
|
35
|
+
// Build API options for columns query
|
|
36
|
+
const apiOptions: ListOptions = {
|
|
37
|
+
...options,
|
|
38
|
+
// Default sort by position if no sort specified - using correct API format
|
|
39
|
+
Sort: options?.Sort || [{ position: "ASC" }]
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const response: ListResponse<KanbanColumn<T>> = await client.list(apiOptions);
|
|
43
|
+
|
|
44
|
+
// Initialize empty cards array for each column
|
|
45
|
+
// Cards will be fetched separately and merged
|
|
46
|
+
return response.Data.map(column => ({
|
|
47
|
+
...column,
|
|
48
|
+
cards: []
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a new column
|
|
54
|
+
*/
|
|
55
|
+
export async function createColumn<T>(
|
|
56
|
+
source: string,
|
|
57
|
+
column: Partial<KanbanColumn<T>>
|
|
58
|
+
): Promise<string> {
|
|
59
|
+
const client = api<KanbanColumn<T>>(source);
|
|
60
|
+
|
|
61
|
+
// Exclude cards array from creation payload
|
|
62
|
+
const { cards, ...columnData } = column;
|
|
63
|
+
|
|
64
|
+
const response: CreateUpdateResponse = await client.create(columnData);
|
|
65
|
+
return response._id;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Update an existing column
|
|
70
|
+
*/
|
|
71
|
+
export async function updateColumn<T>(
|
|
72
|
+
source: string,
|
|
73
|
+
id: string,
|
|
74
|
+
updates: Partial<KanbanColumn<T>>
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
const client = api<KanbanColumn<T>>(source);
|
|
77
|
+
|
|
78
|
+
// Exclude cards array from update payload
|
|
79
|
+
const { cards, ...columnUpdates } = updates;
|
|
80
|
+
|
|
81
|
+
await client.update(id, columnUpdates);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Delete a column
|
|
86
|
+
*/
|
|
87
|
+
export async function deleteColumn(
|
|
88
|
+
source: string,
|
|
89
|
+
id: string
|
|
90
|
+
): Promise<void> {
|
|
91
|
+
const client = api(source);
|
|
92
|
+
await client.delete(id);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Reorder multiple columns
|
|
97
|
+
*/
|
|
98
|
+
export async function reorderColumns<T>(
|
|
99
|
+
source: string,
|
|
100
|
+
columnIds: string[]
|
|
101
|
+
): Promise<void> {
|
|
102
|
+
const client = api<KanbanColumn<T>>(source);
|
|
103
|
+
|
|
104
|
+
// Update position for each column
|
|
105
|
+
const updates = columnIds.map((id, index) => ({
|
|
106
|
+
columnId: id,
|
|
107
|
+
data: { position: index }
|
|
108
|
+
}));
|
|
109
|
+
|
|
110
|
+
// Perform bulk update
|
|
111
|
+
await Promise.all(
|
|
112
|
+
updates.map(update =>
|
|
113
|
+
client.update(update.columnId, update.data)
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================
|
|
119
|
+
// CARD API OPERATIONS
|
|
120
|
+
// ============================================================
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Fetch cards from backend with optional filtering and sorting
|
|
124
|
+
*/
|
|
125
|
+
export async function fetchCards<T>(
|
|
126
|
+
source: string,
|
|
127
|
+
options?: ListOptions
|
|
128
|
+
): Promise<KanbanCard<T>[]> {
|
|
129
|
+
const client = api<KanbanCard<T>>(source);
|
|
130
|
+
|
|
131
|
+
// Build API options for cards query
|
|
132
|
+
const apiOptions: ListOptions = {
|
|
133
|
+
...options,
|
|
134
|
+
// Default sort by column and position if no sort specified - using correct API format
|
|
135
|
+
Sort: options?.Sort || [
|
|
136
|
+
{ columnId: "ASC" },
|
|
137
|
+
{ position: "ASC" }
|
|
138
|
+
]
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const response: ListResponse<KanbanCard<T>> = await client.list(apiOptions);
|
|
142
|
+
return response.Data;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Create a new card
|
|
147
|
+
*/
|
|
148
|
+
export async function createCard<T>(
|
|
149
|
+
source: string,
|
|
150
|
+
card: Partial<KanbanCard<T>> & { columnId: string }
|
|
151
|
+
): Promise<string> {
|
|
152
|
+
const client = api<KanbanCard<T>>(source);
|
|
153
|
+
|
|
154
|
+
const response: CreateUpdateResponse = await client.create(card);
|
|
155
|
+
return response._id;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Update an existing card
|
|
160
|
+
*/
|
|
161
|
+
export async function updateCard<T>(
|
|
162
|
+
source: string,
|
|
163
|
+
id: string,
|
|
164
|
+
updates: Partial<KanbanCard<T>>
|
|
165
|
+
): Promise<void> {
|
|
166
|
+
const client = api<KanbanCard<T>>(source);
|
|
167
|
+
await client.update(id, updates);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Delete a card
|
|
172
|
+
*/
|
|
173
|
+
export async function deleteCard(
|
|
174
|
+
source: string,
|
|
175
|
+
id: string
|
|
176
|
+
): Promise<void> {
|
|
177
|
+
const client = api(source);
|
|
178
|
+
await client.delete(id);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Move a card to a different column or position
|
|
183
|
+
*/
|
|
184
|
+
export async function moveCard<T>(
|
|
185
|
+
source: string,
|
|
186
|
+
cardId: string,
|
|
187
|
+
toColumnId: string,
|
|
188
|
+
position?: number
|
|
189
|
+
): Promise<void> {
|
|
190
|
+
const client = api<KanbanCard<T>>(source);
|
|
191
|
+
|
|
192
|
+
const updates: any = {
|
|
193
|
+
columnId: toColumnId,
|
|
194
|
+
...(position !== undefined && { position })
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
await client.update(cardId, updates);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Reorder cards within a column
|
|
202
|
+
*/
|
|
203
|
+
export async function reorderCards<T>(
|
|
204
|
+
source: string,
|
|
205
|
+
cardIds: string[],
|
|
206
|
+
columnId: string
|
|
207
|
+
): Promise<void> {
|
|
208
|
+
const client = api<KanbanCard<T>>(source);
|
|
209
|
+
|
|
210
|
+
// Update position for each card
|
|
211
|
+
const updates = cardIds.map((id, index) => ({
|
|
212
|
+
cardId: id,
|
|
213
|
+
data: { position: index, columnId } as any
|
|
214
|
+
}));
|
|
215
|
+
|
|
216
|
+
// Perform bulk update
|
|
217
|
+
await Promise.all(
|
|
218
|
+
updates.map(update =>
|
|
219
|
+
client.update(update.cardId, update.data)
|
|
220
|
+
)
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ============================================================
|
|
225
|
+
// BULK OPERATIONS
|
|
226
|
+
// ============================================================
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Bulk update multiple cards
|
|
230
|
+
*/
|
|
231
|
+
export async function bulkUpdateCards<T>(
|
|
232
|
+
source: string,
|
|
233
|
+
request: BulkCardUpdateRequest<T>
|
|
234
|
+
): Promise<void> {
|
|
235
|
+
const client = api<KanbanCard<T>>(source);
|
|
236
|
+
|
|
237
|
+
await Promise.all(
|
|
238
|
+
request.updates.map(update =>
|
|
239
|
+
client.update(update.cardId, update.data)
|
|
240
|
+
)
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Bulk update multiple columns
|
|
246
|
+
*/
|
|
247
|
+
export async function bulkUpdateColumns<T>(
|
|
248
|
+
source: string,
|
|
249
|
+
request: BulkColumnUpdateRequest<T>
|
|
250
|
+
): Promise<void> {
|
|
251
|
+
const client = api<KanbanColumn<T>>(source);
|
|
252
|
+
|
|
253
|
+
await Promise.all(
|
|
254
|
+
request.updates.map(update =>
|
|
255
|
+
client.update(update.columnId, update.data)
|
|
256
|
+
)
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Move multiple cards to a new column
|
|
262
|
+
*/
|
|
263
|
+
export async function bulkMoveCards<T>(
|
|
264
|
+
cardSource: string,
|
|
265
|
+
cardIds: string[],
|
|
266
|
+
toColumnId: string,
|
|
267
|
+
startPosition = 0
|
|
268
|
+
): Promise<void> {
|
|
269
|
+
const client = api<KanbanCard<T>>(cardSource);
|
|
270
|
+
|
|
271
|
+
const updates = cardIds.map((cardId, index) => ({
|
|
272
|
+
cardId,
|
|
273
|
+
data: {
|
|
274
|
+
columnId: toColumnId,
|
|
275
|
+
position: startPosition + index
|
|
276
|
+
} as any
|
|
277
|
+
}));
|
|
278
|
+
|
|
279
|
+
await Promise.all(
|
|
280
|
+
updates.map(update =>
|
|
281
|
+
client.update(update.cardId, update.data)
|
|
282
|
+
)
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ============================================================
|
|
287
|
+
// QUERY HELPERS
|
|
288
|
+
// ============================================================
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get count of cards in a specific column
|
|
292
|
+
*/
|
|
293
|
+
export async function getCardCount(
|
|
294
|
+
source: string,
|
|
295
|
+
columnId: string
|
|
296
|
+
): Promise<number> {
|
|
297
|
+
const client = api(source);
|
|
298
|
+
|
|
299
|
+
const options: ListOptions = {
|
|
300
|
+
Filter: {
|
|
301
|
+
Operator: "And",
|
|
302
|
+
Condition: [
|
|
303
|
+
{
|
|
304
|
+
Operator: "EQ",
|
|
305
|
+
LHSField: "columnId",
|
|
306
|
+
RHSValue: columnId,
|
|
307
|
+
RHSType: "Constant"
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const response: CountResponse = await client.count(options);
|
|
314
|
+
return response.Count;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get total count of all cards across columns
|
|
319
|
+
*/
|
|
320
|
+
export async function getTotalCardCount(
|
|
321
|
+
source: string,
|
|
322
|
+
options?: ListOptions
|
|
323
|
+
): Promise<number> {
|
|
324
|
+
const client = api(source);
|
|
325
|
+
const response: CountResponse = await client.count(options);
|
|
326
|
+
return response.Count;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Search cards across all columns
|
|
331
|
+
*/
|
|
332
|
+
export async function searchCards<T>(
|
|
333
|
+
source: string,
|
|
334
|
+
searchQuery: string,
|
|
335
|
+
additionalOptions?: ListOptions
|
|
336
|
+
): Promise<KanbanCard<T>[]> {
|
|
337
|
+
const client = api<KanbanCard<T>>(source);
|
|
338
|
+
|
|
339
|
+
const options: ListOptions = {
|
|
340
|
+
...additionalOptions,
|
|
341
|
+
Search: searchQuery,
|
|
342
|
+
Sort: additionalOptions?.Sort || [
|
|
343
|
+
{ columnId: "ASC" },
|
|
344
|
+
{ position: "ASC" }
|
|
345
|
+
]
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const response: ListResponse<KanbanCard<T>> = await client.list(options);
|
|
349
|
+
return response.Data;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ============================================================
|
|
353
|
+
// DATA PROCESSING HELPERS
|
|
354
|
+
// ============================================================
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Merge cards into their respective columns
|
|
358
|
+
*/
|
|
359
|
+
export function mergeCardsIntoColumns<T>(
|
|
360
|
+
columns: KanbanColumn<T>[],
|
|
361
|
+
cards: KanbanCard<T>[]
|
|
362
|
+
): KanbanColumn<T>[] {
|
|
363
|
+
// Group cards by columnId
|
|
364
|
+
const cardsByColumn = cards.reduce((acc, card) => {
|
|
365
|
+
if (!acc[card.columnId]) {
|
|
366
|
+
acc[card.columnId] = [];
|
|
367
|
+
}
|
|
368
|
+
acc[card.columnId].push(card);
|
|
369
|
+
return acc;
|
|
370
|
+
}, {} as Record<string, KanbanCard<T>[]>);
|
|
371
|
+
|
|
372
|
+
// Assign cards to their respective columns
|
|
373
|
+
return columns.map(column => ({
|
|
374
|
+
...column,
|
|
375
|
+
cards: (cardsByColumn[column._id] || [])
|
|
376
|
+
.sort((a, b) => a.position - b.position)
|
|
377
|
+
}));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Calculate optimal position for inserting a new card
|
|
382
|
+
*/
|
|
383
|
+
export function calculateCardPosition<T>(
|
|
384
|
+
column: KanbanColumn<T>,
|
|
385
|
+
insertIndex?: number
|
|
386
|
+
): number {
|
|
387
|
+
if (column.cards.length === 0) {
|
|
388
|
+
return 0;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (insertIndex === undefined || insertIndex >= column.cards.length) {
|
|
392
|
+
// Insert at end
|
|
393
|
+
const lastCard = column.cards[column.cards.length - 1];
|
|
394
|
+
return lastCard.position + 1;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (insertIndex === 0) {
|
|
398
|
+
// Insert at beginning
|
|
399
|
+
const firstCard = column.cards[0];
|
|
400
|
+
return Math.max(0, firstCard.position - 1);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Insert between cards
|
|
404
|
+
const prevCard = column.cards[insertIndex - 1];
|
|
405
|
+
const nextCard = column.cards[insertIndex];
|
|
406
|
+
return Math.floor((prevCard.position + nextCard.position) / 2);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Calculate optimal position for inserting a new column
|
|
411
|
+
*/
|
|
412
|
+
export function calculateColumnPosition<T>(
|
|
413
|
+
columns: KanbanColumn<T>[],
|
|
414
|
+
insertIndex?: number
|
|
415
|
+
): number {
|
|
416
|
+
if (columns.length === 0) {
|
|
417
|
+
return 0;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (insertIndex === undefined || insertIndex >= columns.length) {
|
|
421
|
+
// Insert at end
|
|
422
|
+
const lastColumn = columns[columns.length - 1];
|
|
423
|
+
return lastColumn.position + 1;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
if (insertIndex === 0) {
|
|
427
|
+
// Insert at beginning
|
|
428
|
+
const firstColumn = columns[0];
|
|
429
|
+
return Math.max(0, firstColumn.position - 1);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Insert between columns
|
|
433
|
+
const prevColumn = columns[insertIndex - 1];
|
|
434
|
+
const nextColumn = columns[insertIndex];
|
|
435
|
+
return Math.floor((prevColumn.position + nextColumn.position) / 2);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Recalculate positions to ensure proper ordering
|
|
440
|
+
*/
|
|
441
|
+
export function normalizePositions<T extends { position: number }>(
|
|
442
|
+
items: T[]
|
|
443
|
+
): T[] {
|
|
444
|
+
return items
|
|
445
|
+
.sort((a, b) => a.position - b.position)
|
|
446
|
+
.map((item, index) => ({
|
|
447
|
+
...item,
|
|
448
|
+
position: index
|
|
449
|
+
}));
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// ============================================================
|
|
453
|
+
// ERROR HANDLING
|
|
454
|
+
// ============================================================
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Handle API errors with context
|
|
458
|
+
*/
|
|
459
|
+
export function handleKanbanApiError(
|
|
460
|
+
error: unknown,
|
|
461
|
+
operation: string,
|
|
462
|
+
entityType: 'card' | 'column',
|
|
463
|
+
entityId?: string
|
|
464
|
+
): Error {
|
|
465
|
+
const baseMessage = `Failed to ${operation} ${entityType}`;
|
|
466
|
+
const context = entityId ? ` (ID: ${entityId})` : '';
|
|
467
|
+
|
|
468
|
+
if (error instanceof Error) {
|
|
469
|
+
return new Error(`${baseMessage}${context}: ${error.message}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return new Error(`${baseMessage}${context}: Unknown error`);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/**
|
|
476
|
+
* Validate API response structure
|
|
477
|
+
*/
|
|
478
|
+
export function validateApiResponse<T>(
|
|
479
|
+
response: any,
|
|
480
|
+
expectedType: 'list' | 'single' | 'create' | 'delete'
|
|
481
|
+
): response is ListResponse<T> | T | CreateUpdateResponse | DeleteResponse {
|
|
482
|
+
switch (expectedType) {
|
|
483
|
+
case 'list':
|
|
484
|
+
return response && Array.isArray(response.Data);
|
|
485
|
+
case 'single':
|
|
486
|
+
return response && typeof response === 'object';
|
|
487
|
+
case 'create':
|
|
488
|
+
return response && typeof response._id === 'string';
|
|
489
|
+
case 'delete':
|
|
490
|
+
return response && response.status === 'success';
|
|
491
|
+
default:
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import { UseKanbanReturn } from "./types";
|
|
3
|
+
|
|
4
|
+
export const KanbanContext = createContext<UseKanbanReturn<any> | null>(null);
|
|
5
|
+
|
|
6
|
+
export function useKanbanContext<T extends Record<string, any> = any>() {
|
|
7
|
+
const context = useContext(KanbanContext);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
"Kanban components must be used within a KanbanBoard component"
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
return context as UseKanbanReturn<T>;
|
|
14
|
+
}
|