@htlkg/components 0.0.2 → 0.0.3
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 +52 -0
- package/dist/composables/index.js +196 -4
- package/dist/composables/index.js.map +1 -1
- package/package.json +7 -4
- package/src/composables/composables.md +109 -0
- package/src/composables/index.ts +17 -0
- package/src/composables/usePageContext.ts +171 -0
- package/src/composables/useTable.ts +26 -5
- package/src/data/DataTable.vue +553 -0
- package/src/data/Table/Table.vue +295 -0
- package/src/data/columnHelpers.ts +334 -0
- package/src/data/data.md +106 -0
- package/src/data/index.ts +20 -0
- package/src/domain/domain.md +102 -0
- package/src/forms/forms.md +89 -0
- package/src/index.ts +4 -3
- package/src/navigation/navigation.md +80 -0
- package/src/overlays/overlays.md +86 -0
- package/src/stores/stores.md +82 -0
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
<script setup lang="ts" generic="T extends Record<string, any>">
|
|
2
|
+
/**
|
|
3
|
+
* DataTable Component
|
|
4
|
+
*
|
|
5
|
+
* A simplified table component that encapsulates useTable composable and Table component,
|
|
6
|
+
* providing a cleaner API with automatic event wiring and column transformations.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { computed, toRef, watch } from "vue";
|
|
10
|
+
import Table from "./Table.vue";
|
|
11
|
+
import { useTable, type SmartFilter } from "../composables/useTable";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Column definition for DataTable
|
|
15
|
+
*/
|
|
16
|
+
export interface DataTableColumn<TItem = any> {
|
|
17
|
+
/** Property key to access from item */
|
|
18
|
+
key: string;
|
|
19
|
+
/** Column header label */
|
|
20
|
+
label: string;
|
|
21
|
+
/** Whether column is sortable (default: true) */
|
|
22
|
+
sortable?: boolean;
|
|
23
|
+
/** Column width */
|
|
24
|
+
width?: string;
|
|
25
|
+
/** Custom render function - returns value for table cell */
|
|
26
|
+
render?: (item: TItem, index: number) => any;
|
|
27
|
+
/** Cell type hint for default formatting */
|
|
28
|
+
type?: "text" | "tag" | "badge" | "date" | "number" | "actions";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Filter definition for DataTable
|
|
33
|
+
*/
|
|
34
|
+
export interface DataTableFilter {
|
|
35
|
+
/** Filter key (matches column key) */
|
|
36
|
+
key: string;
|
|
37
|
+
/** Filter label */
|
|
38
|
+
label: string;
|
|
39
|
+
/** Filter input type */
|
|
40
|
+
type: "text" | "select" | "number" | "date" | "dateRange";
|
|
41
|
+
/** Options for select type */
|
|
42
|
+
options?: Array<{ label: string; value: any }>;
|
|
43
|
+
/** Placeholder text */
|
|
44
|
+
placeholder?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Bulk action definition
|
|
49
|
+
*/
|
|
50
|
+
export interface BulkAction {
|
|
51
|
+
/** Action identifier */
|
|
52
|
+
id: string;
|
|
53
|
+
/** Display label */
|
|
54
|
+
label: string;
|
|
55
|
+
/** Icon name */
|
|
56
|
+
icon?: string;
|
|
57
|
+
/** Visual variant */
|
|
58
|
+
variant?: "default" | "danger" | "warning";
|
|
59
|
+
/** Require confirmation */
|
|
60
|
+
confirm?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Row action definition
|
|
65
|
+
*/
|
|
66
|
+
export interface RowAction {
|
|
67
|
+
/** Action identifier */
|
|
68
|
+
id: string;
|
|
69
|
+
/** Display label */
|
|
70
|
+
label: string;
|
|
71
|
+
/** Icon name */
|
|
72
|
+
icon?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* No results configuration
|
|
77
|
+
*/
|
|
78
|
+
export interface NoResultsConfig {
|
|
79
|
+
/** Title text */
|
|
80
|
+
title: string;
|
|
81
|
+
/** Message text */
|
|
82
|
+
message: string;
|
|
83
|
+
/** Action buttons */
|
|
84
|
+
actions?: Array<{ action: string; text: string }>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
interface Props {
|
|
88
|
+
/** Data items to display */
|
|
89
|
+
items: T[];
|
|
90
|
+
/** Column definitions */
|
|
91
|
+
columns: DataTableColumn<T>[];
|
|
92
|
+
/** Filter definitions */
|
|
93
|
+
filters?: DataTableFilter[];
|
|
94
|
+
/** Initial filter values (from URL params) */
|
|
95
|
+
initialFilters?: Record<string, any>;
|
|
96
|
+
/** Loading state */
|
|
97
|
+
loading?: boolean;
|
|
98
|
+
/** Items per page (default: 10) */
|
|
99
|
+
pageSize?: number;
|
|
100
|
+
/** Current page (for syncUrl mode) */
|
|
101
|
+
currentPage?: number;
|
|
102
|
+
/** Total pages (for syncUrl mode) */
|
|
103
|
+
totalPages?: number;
|
|
104
|
+
/** Total items count (for syncUrl mode) */
|
|
105
|
+
totalItems?: number;
|
|
106
|
+
/** Property key for item ID (default: 'id') */
|
|
107
|
+
idKey?: keyof T;
|
|
108
|
+
/** Row actions (shown in actions column) */
|
|
109
|
+
actions?: RowAction[] | string[];
|
|
110
|
+
/** Enable row selection */
|
|
111
|
+
selectable?: boolean;
|
|
112
|
+
/** Bulk actions (shown when items selected) */
|
|
113
|
+
bulkActions?: BulkAction[];
|
|
114
|
+
/** No results configuration */
|
|
115
|
+
noResults?: NoResultsConfig;
|
|
116
|
+
/** Default sort key */
|
|
117
|
+
defaultSortKey?: string;
|
|
118
|
+
/** Default sort order */
|
|
119
|
+
defaultSortOrder?: "asc" | "desc";
|
|
120
|
+
/** Sync table state with URL (for SSR pages) */
|
|
121
|
+
syncUrl?: boolean;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
125
|
+
loading: false,
|
|
126
|
+
pageSize: 10,
|
|
127
|
+
currentPage: 1,
|
|
128
|
+
totalPages: 1,
|
|
129
|
+
totalItems: 0,
|
|
130
|
+
idKey: "id" as keyof T,
|
|
131
|
+
selectable: false,
|
|
132
|
+
defaultSortOrder: "asc",
|
|
133
|
+
syncUrl: false,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const emit = defineEmits<{
|
|
137
|
+
/** Row action clicked */
|
|
138
|
+
action: [action: string, item: T];
|
|
139
|
+
/** Bulk action clicked */
|
|
140
|
+
bulkAction: [action: string, items: T[]];
|
|
141
|
+
/** Selection changed */
|
|
142
|
+
selection: [items: T[]];
|
|
143
|
+
/** Row clicked */
|
|
144
|
+
rowClick: [item: T];
|
|
145
|
+
/** Custom cell emit (e.g., link clicks with emits config) */
|
|
146
|
+
customEmit: [data: any];
|
|
147
|
+
/** Page changed (for URL sync) */
|
|
148
|
+
pageChange: [page: number];
|
|
149
|
+
/** Sort changed (for URL sync) */
|
|
150
|
+
sortChange: [key: string, order: "asc" | "desc"];
|
|
151
|
+
/** Filters changed (for URL sync) */
|
|
152
|
+
filterChange: [filters: Record<string, any>];
|
|
153
|
+
}>();
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Update URL with new params (for syncUrl mode)
|
|
157
|
+
*/
|
|
158
|
+
function updateUrl(params: Record<string, string | undefined>) {
|
|
159
|
+
if (typeof window === "undefined") return;
|
|
160
|
+
|
|
161
|
+
const url = new URL(window.location.href);
|
|
162
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
163
|
+
if (value !== undefined && value !== "") {
|
|
164
|
+
url.searchParams.set(key, value);
|
|
165
|
+
} else {
|
|
166
|
+
url.searchParams.delete(key);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
// Navigate to trigger server-side data fetch
|
|
170
|
+
window.location.href = url.toString();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Handle page change with optional URL sync
|
|
175
|
+
*/
|
|
176
|
+
function handlePageChange(page: number) {
|
|
177
|
+
if (props.syncUrl) {
|
|
178
|
+
emit("pageChange", page);
|
|
179
|
+
updateUrl({ page: String(page) });
|
|
180
|
+
} else {
|
|
181
|
+
table.handlePageChange(page);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Handle page size change with optional URL sync
|
|
187
|
+
*/
|
|
188
|
+
function handlePageSizeChange(size: number | string) {
|
|
189
|
+
if (props.syncUrl) {
|
|
190
|
+
updateUrl({ pageSize: String(size), page: "1" });
|
|
191
|
+
} else {
|
|
192
|
+
table.handlePageSizeChange(size);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Handle sort change with optional URL sync
|
|
198
|
+
*/
|
|
199
|
+
function handleOrderBy(event: { value: string; orderDirection: "asc" | "desc" }) {
|
|
200
|
+
if (props.syncUrl) {
|
|
201
|
+
emit("sortChange", event.value, event.orderDirection);
|
|
202
|
+
updateUrl({ sortKey: event.value, sortOrder: event.orderDirection, page: "1" });
|
|
203
|
+
} else {
|
|
204
|
+
table.handleOrderBy(event);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Extract actual value from filter value (handles uiSelect objects with { id, name })
|
|
210
|
+
*/
|
|
211
|
+
function extractFilterValue(value: any): string | undefined {
|
|
212
|
+
if (value === undefined || value === null || value === "") {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
// If value is an object with 'id' property (uiSelect format), extract the id
|
|
216
|
+
if (typeof value === "object" && value !== null && "id" in value) {
|
|
217
|
+
return String(value.id);
|
|
218
|
+
}
|
|
219
|
+
return String(value);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Handle filters applied with optional URL sync
|
|
224
|
+
*/
|
|
225
|
+
function handleFiltersApplied(filters: any) {
|
|
226
|
+
if (props.syncUrl) {
|
|
227
|
+
// Convert filter data to URL params
|
|
228
|
+
const filterParams: Record<string, string | undefined> = { page: "1" };
|
|
229
|
+
|
|
230
|
+
// Handle uiTable format: { logicOperator: 'and', filters: [...] }
|
|
231
|
+
if (filters && typeof filters === "object" && "filters" in filters && Array.isArray(filters.filters)) {
|
|
232
|
+
filters.filters.forEach((f: { name: string; value: any }) => {
|
|
233
|
+
const extractedValue = extractFilterValue(f.value);
|
|
234
|
+
if (extractedValue !== undefined) {
|
|
235
|
+
filterParams[f.name] = extractedValue;
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
// Handle SmartFilter array format: [{ category, operator, value }, ...]
|
|
240
|
+
else if (Array.isArray(filters)) {
|
|
241
|
+
filters.forEach((f: { category?: string; name?: string; value: any }) => {
|
|
242
|
+
const key = f.category || f.name;
|
|
243
|
+
const extractedValue = extractFilterValue(f.value);
|
|
244
|
+
if (key && extractedValue !== undefined) {
|
|
245
|
+
filterParams[key] = extractedValue;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
// Handle simple object format: { email: 'john', status: 'active' }
|
|
250
|
+
else if (filters && typeof filters === "object") {
|
|
251
|
+
Object.entries(filters).forEach(([key, value]) => {
|
|
252
|
+
// Skip non-filter keys
|
|
253
|
+
if (key === "logicOperator" || key === "filters") return;
|
|
254
|
+
const extractedValue = extractFilterValue(value);
|
|
255
|
+
if (extractedValue !== undefined) {
|
|
256
|
+
filterParams[key] = extractedValue;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
emit("filterChange", filterParams);
|
|
262
|
+
updateUrl(filterParams);
|
|
263
|
+
} else {
|
|
264
|
+
table.handleSmartFiltersApplied(filters);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Handle filters cleared with optional URL sync
|
|
270
|
+
*/
|
|
271
|
+
function handleFiltersCleared() {
|
|
272
|
+
if (props.syncUrl) {
|
|
273
|
+
// Clear all filter params from URL
|
|
274
|
+
if (typeof window === "undefined") return;
|
|
275
|
+
const url = new URL(window.location.href);
|
|
276
|
+
// Keep only non-filter params
|
|
277
|
+
const keysToRemove: string[] = [];
|
|
278
|
+
url.searchParams.forEach((_, key) => {
|
|
279
|
+
if (!["page", "pageSize", "sortKey", "sortOrder"].includes(key)) {
|
|
280
|
+
keysToRemove.push(key);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
keysToRemove.forEach((key) => url.searchParams.delete(key));
|
|
284
|
+
url.searchParams.set("page", "1");
|
|
285
|
+
window.location.href = url.toString();
|
|
286
|
+
} else {
|
|
287
|
+
table.handleSmartFiltersCleared();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Handle single filter deleted with optional URL sync
|
|
293
|
+
*/
|
|
294
|
+
function handleFilterDeleted(index: number) {
|
|
295
|
+
if (props.syncUrl) {
|
|
296
|
+
// For URL sync, we need to rebuild filters without the deleted one
|
|
297
|
+
// This is handled by the UI component, just trigger a page reload
|
|
298
|
+
handleFiltersCleared();
|
|
299
|
+
} else {
|
|
300
|
+
table.handleSmartFilterDeleted(index);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Initialize useTable with reactive items
|
|
305
|
+
const table = useTable<T>({
|
|
306
|
+
items: toRef(() => props.items),
|
|
307
|
+
pageSize: props.pageSize,
|
|
308
|
+
sortKey: props.defaultSortKey ?? "",
|
|
309
|
+
sortOrder: props.defaultSortOrder,
|
|
310
|
+
idKey: props.idKey as string,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Transform columns to table header format
|
|
314
|
+
const tableHeader = computed(() =>
|
|
315
|
+
props.columns.map((col) => ({
|
|
316
|
+
name: col.key,
|
|
317
|
+
label: col.label,
|
|
318
|
+
sortable: col.sortable !== false,
|
|
319
|
+
width: col.width,
|
|
320
|
+
}))
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
// Transform items to table row format
|
|
324
|
+
// When syncUrl is true, data is already paginated server-side, use props.items directly
|
|
325
|
+
const tableItems = computed(() => {
|
|
326
|
+
const itemsToRender = props.syncUrl ? props.items : table.paginatedItems.value;
|
|
327
|
+
return itemsToRender.map((item, itemIndex) => ({
|
|
328
|
+
id: item[props.idKey as keyof T],
|
|
329
|
+
row: props.columns.map((col) => {
|
|
330
|
+
// Use custom render if provided
|
|
331
|
+
if (col.render) {
|
|
332
|
+
return col.render(item, itemIndex);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Get raw value
|
|
336
|
+
const value = item[col.key as keyof T];
|
|
337
|
+
|
|
338
|
+
// Apply type-based formatting
|
|
339
|
+
return formatCellValue(value, col.type);
|
|
340
|
+
}),
|
|
341
|
+
// Store original item for action handlers
|
|
342
|
+
_originalData: item,
|
|
343
|
+
}));
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// Transform filters to smart filter categories
|
|
347
|
+
const filterCategories = computed(() =>
|
|
348
|
+
props.filters?.map((f) => ({
|
|
349
|
+
id: f.key,
|
|
350
|
+
name: f.label,
|
|
351
|
+
componentType: mapFilterType(f.type),
|
|
352
|
+
defaultProps: buildFilterProps(f),
|
|
353
|
+
})) ?? []
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
// Convert initial filters (from URL params) to Table's filter format
|
|
357
|
+
const initialTableFilters = computed(() => {
|
|
358
|
+
if (!props.initialFilters || !props.filters) return undefined;
|
|
359
|
+
|
|
360
|
+
const filterEntries = Object.entries(props.initialFilters).filter(
|
|
361
|
+
([, value]) => value !== undefined && value !== null && value !== ""
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
if (filterEntries.length === 0) return undefined;
|
|
365
|
+
|
|
366
|
+
return {
|
|
367
|
+
logicOperator: "and",
|
|
368
|
+
filters: filterEntries.map(([key, value]) => {
|
|
369
|
+
const filterDef = props.filters?.find((f) => f.key === key);
|
|
370
|
+
return {
|
|
371
|
+
name: key,
|
|
372
|
+
label: filterDef?.label ?? key,
|
|
373
|
+
type: filterDef ? mapFilterType(filterDef.type) : "uiInput",
|
|
374
|
+
value: String(value),
|
|
375
|
+
};
|
|
376
|
+
}),
|
|
377
|
+
};
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Build row actions for table
|
|
381
|
+
const rowActions = computed(() => {
|
|
382
|
+
if (!props.actions || props.actions.length === 0) return undefined;
|
|
383
|
+
|
|
384
|
+
return props.actions.map((action) => {
|
|
385
|
+
if (typeof action === "string") {
|
|
386
|
+
return { name: action, id: action };
|
|
387
|
+
}
|
|
388
|
+
return { name: action.label, id: action.id };
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Build bulk action buttons
|
|
393
|
+
const bulkActionButtons = computed(() => {
|
|
394
|
+
if (!props.bulkActions || !props.selectable) return undefined;
|
|
395
|
+
|
|
396
|
+
return props.bulkActions.map((action) => ({
|
|
397
|
+
text: action.label,
|
|
398
|
+
id: action.id,
|
|
399
|
+
color: action.variant === "danger" ? "red" : action.variant === "warning" ? "yellow" : "blue",
|
|
400
|
+
icon: action.icon,
|
|
401
|
+
}));
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
// Select all modal config
|
|
405
|
+
const selectAllModal = computed(() => {
|
|
406
|
+
if (!props.selectable) return undefined;
|
|
407
|
+
return {
|
|
408
|
+
title: "Select All Items",
|
|
409
|
+
message: `Select all ${table.totalItems.value} items?`,
|
|
410
|
+
cancelText: "Select page only",
|
|
411
|
+
confirmText: "Select all",
|
|
412
|
+
};
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Format cell value based on type
|
|
417
|
+
*/
|
|
418
|
+
function formatCellValue(value: any, type?: string): any {
|
|
419
|
+
if (value === null || value === undefined) return "";
|
|
420
|
+
|
|
421
|
+
switch (type) {
|
|
422
|
+
case "date":
|
|
423
|
+
if (value instanceof Date) {
|
|
424
|
+
return value.toLocaleDateString();
|
|
425
|
+
}
|
|
426
|
+
if (typeof value === "string") {
|
|
427
|
+
return new Date(value).toLocaleDateString();
|
|
428
|
+
}
|
|
429
|
+
return value;
|
|
430
|
+
|
|
431
|
+
case "number":
|
|
432
|
+
return typeof value === "number" ? value.toLocaleString() : value;
|
|
433
|
+
|
|
434
|
+
case "badge":
|
|
435
|
+
return { content: value, type: "badge" };
|
|
436
|
+
|
|
437
|
+
case "tag":
|
|
438
|
+
return { content: value, type: "tag" };
|
|
439
|
+
|
|
440
|
+
default:
|
|
441
|
+
return value;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Map filter type to component type
|
|
447
|
+
*/
|
|
448
|
+
function mapFilterType(type: string): "uiInput" | "uiSelect" {
|
|
449
|
+
switch (type) {
|
|
450
|
+
case "select":
|
|
451
|
+
return "uiSelect";
|
|
452
|
+
default:
|
|
453
|
+
return "uiInput";
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Build filter component props
|
|
459
|
+
*/
|
|
460
|
+
function buildFilterProps(filter: DataTableFilter): Record<string, any> {
|
|
461
|
+
const defaultProps: Record<string, any> = {};
|
|
462
|
+
|
|
463
|
+
if (filter.placeholder) {
|
|
464
|
+
defaultProps.placeholder = filter.placeholder;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (filter.type === "select" && filter.options) {
|
|
468
|
+
// uiSelect smart filter uses 'name' as the filter value and 'label' for display
|
|
469
|
+
// Format: { id: unique, name: actualValue, label: displayText }
|
|
470
|
+
defaultProps.items = filter.options.map((opt, index) => ({
|
|
471
|
+
id: String(index),
|
|
472
|
+
name: String(opt.value), // This is what gets used as filter value
|
|
473
|
+
label: opt.label, // This is what gets displayed
|
|
474
|
+
}));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (filter.type === "number") {
|
|
478
|
+
defaultProps.type = "number";
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return defaultProps;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Handle table action (row action)
|
|
486
|
+
*/
|
|
487
|
+
function handleTableAction(data: { action: string; items: Array<string | number> }) {
|
|
488
|
+
// Find the item that was acted upon
|
|
489
|
+
const itemId = data.items[0];
|
|
490
|
+
const tableItem = tableItems.value.find((ti) => ti.id === itemId);
|
|
491
|
+
if (tableItem && tableItem._originalData) {
|
|
492
|
+
emit("action", data.action, tableItem._originalData);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Handle bulk action button click
|
|
498
|
+
*/
|
|
499
|
+
function handleBulkActionClick(data: { id: string; text: string }) {
|
|
500
|
+
emit("bulkAction", data.id, table.selectedItems.value);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Handle custom cell emit (e.g., link clicks with emits config)
|
|
505
|
+
*/
|
|
506
|
+
function handleCustomEmit(data: any) {
|
|
507
|
+
emit("customEmit", data);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
/**
|
|
511
|
+
* Watch for selection changes
|
|
512
|
+
*/
|
|
513
|
+
watch(
|
|
514
|
+
() => table.selectedItems.value,
|
|
515
|
+
(items) => {
|
|
516
|
+
emit("selection", items);
|
|
517
|
+
},
|
|
518
|
+
{ deep: true }
|
|
519
|
+
);
|
|
520
|
+
</script>
|
|
521
|
+
|
|
522
|
+
<template>
|
|
523
|
+
<Table
|
|
524
|
+
:header="tableHeader"
|
|
525
|
+
:items="tableItems"
|
|
526
|
+
:loading="loading"
|
|
527
|
+
:current-page="syncUrl ? currentPage : table.currentPage.value"
|
|
528
|
+
:total-pages="syncUrl ? totalPages : table.totalPages.value"
|
|
529
|
+
:total-items="syncUrl ? totalItems : table.totalItems.value"
|
|
530
|
+
:page-size="syncUrl ? pageSize : table.pageSize.value"
|
|
531
|
+
:ordered-by="defaultSortKey ?? table.sortKey.value"
|
|
532
|
+
:order-direction="defaultSortOrder ?? table.sortOrder.value"
|
|
533
|
+
:smart-filter-categories="filterCategories"
|
|
534
|
+
:filters="initialTableFilters"
|
|
535
|
+
:hidden-columns="table.hiddenColumns.value"
|
|
536
|
+
:reset-selected="table.resetSelected.value"
|
|
537
|
+
:actions="rowActions"
|
|
538
|
+
:select-all-items-modal="selectAllModal"
|
|
539
|
+
:table-action-buttons="bulkActionButtons"
|
|
540
|
+
:no-results="noResults"
|
|
541
|
+
@change-page="handlePageChange"
|
|
542
|
+
@change-page-size="handlePageSizeChange"
|
|
543
|
+
@order-by="handleOrderBy"
|
|
544
|
+
@smart-filters-sent="handleFiltersApplied"
|
|
545
|
+
@smart-filters-cleared="handleFiltersCleared"
|
|
546
|
+
@smart-filter-deleted="handleFilterDeleted"
|
|
547
|
+
@columns-visibility-changed="table.handleColumnsVisibilityChanged"
|
|
548
|
+
@modal-action="table.handleModalAction"
|
|
549
|
+
@table-action="handleTableAction"
|
|
550
|
+
@table-action-button-clicked="handleBulkActionClick"
|
|
551
|
+
@custom-emit="handleCustomEmit"
|
|
552
|
+
/>
|
|
553
|
+
</template>
|