@htlkg/components 0.0.3 → 0.0.4
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/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js +367 -0
- package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js.map +1 -0
- package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js +263 -0
- package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js.map +1 -0
- package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js +580 -0
- package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js.map +1 -0
- package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js +187 -0
- package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js.map +1 -0
- package/dist/_plugin-vue_export-helper-1tPrXgE0.js +11 -0
- package/dist/_plugin-vue_export-helper-1tPrXgE0.js.map +1 -0
- package/dist/components.css +15 -0
- package/dist/composables/index.js +32 -765
- package/dist/composables/index.js.map +1 -1
- package/dist/data/index.js +18 -0
- package/dist/data/index.js.map +1 -0
- package/dist/domain/index.js +8 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/filterHelpers-DgRyoYSa.js +1386 -0
- package/dist/filterHelpers-DgRyoYSa.js.map +1 -0
- package/dist/forms/index.js +6 -0
- package/dist/forms/index.js.map +1 -0
- package/dist/index-DGO_pNgG.js +79 -0
- package/dist/index-DGO_pNgG.js.map +1 -0
- package/dist/index-QK97OdqQ.js +25 -0
- package/dist/index-QK97OdqQ.js.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation/index.js +8 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/overlays/index.js +8 -0
- package/dist/overlays/index.js.map +1 -0
- package/dist/stores/index.js +14 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/useAdminPage-GhgXp0x8.js +1070 -0
- package/dist/useAdminPage-GhgXp0x8.js.map +1 -0
- package/dist/useTable-DutR1gkg.js +293 -0
- package/dist/useTable-DutR1gkg.js.map +1 -0
- package/package.json +37 -11
- package/src/composables/index.ts +52 -0
- package/src/composables/useAdminPage.ts +462 -0
- package/src/composables/useConfirmation.ts +358 -0
- package/src/composables/useStats.ts +361 -0
- package/src/composables/useWizard.ts +448 -0
- package/src/data/columnHelpers.ts +169 -0
- package/src/data/filterHelpers.ts +358 -0
- package/src/data/index.ts +11 -0
- package/src/forms/JsonSchemaForm.vue +4 -1
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filter Definition Helpers
|
|
3
|
+
*
|
|
4
|
+
* Pre-built filter definitions for common patterns, reducing boilerplate
|
|
5
|
+
* when defining DataTable filters.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { DataTableFilter } from "./DataTable.vue";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Filter option for select filters
|
|
12
|
+
*/
|
|
13
|
+
export interface FilterOption {
|
|
14
|
+
label: string;
|
|
15
|
+
value: string | number | boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Common status options
|
|
20
|
+
*/
|
|
21
|
+
export const statusOptions: FilterOption[] = [
|
|
22
|
+
{ label: "Active", value: "active" },
|
|
23
|
+
{ label: "Inactive", value: "inactive" },
|
|
24
|
+
{ label: "Pending", value: "pending" },
|
|
25
|
+
{ label: "Archived", value: "archived" },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Boolean options
|
|
30
|
+
*/
|
|
31
|
+
export const booleanOptions: FilterOption[] = [
|
|
32
|
+
{ label: "Yes", value: "true" },
|
|
33
|
+
{ label: "No", value: "false" },
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Pre-built filter helpers
|
|
38
|
+
*/
|
|
39
|
+
export const filters = {
|
|
40
|
+
/**
|
|
41
|
+
* Text search filter
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* filters.text('name', 'Name')
|
|
45
|
+
* filters.text('email', 'Email', 'Search by email address...')
|
|
46
|
+
*/
|
|
47
|
+
text: (key: string, label: string, placeholder?: string): DataTableFilter => ({
|
|
48
|
+
key,
|
|
49
|
+
label,
|
|
50
|
+
type: "text",
|
|
51
|
+
placeholder: placeholder ?? `Search by ${label.toLowerCase()}...`,
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Select dropdown filter
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* filters.select('role', 'Role', [
|
|
59
|
+
* { label: 'Admin', value: 'admin' },
|
|
60
|
+
* { label: 'User', value: 'user' },
|
|
61
|
+
* ])
|
|
62
|
+
*/
|
|
63
|
+
select: (key: string, label: string, options: FilterOption[]): DataTableFilter => ({
|
|
64
|
+
key,
|
|
65
|
+
label,
|
|
66
|
+
type: "select",
|
|
67
|
+
options,
|
|
68
|
+
}),
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Status filter with common status options
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* filters.status()
|
|
75
|
+
* filters.status('state', 'State')
|
|
76
|
+
* filters.status('status', 'Status', [
|
|
77
|
+
* { label: 'Active', value: 'active' },
|
|
78
|
+
* { label: 'Draft', value: 'draft' },
|
|
79
|
+
* ])
|
|
80
|
+
*/
|
|
81
|
+
status: (key: string = "status", label: string = "Status", options?: FilterOption[]): DataTableFilter => ({
|
|
82
|
+
key,
|
|
83
|
+
label,
|
|
84
|
+
type: "select",
|
|
85
|
+
options: options ?? statusOptions,
|
|
86
|
+
}),
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Boolean (Yes/No) filter
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* filters.boolean('verified', 'Verified')
|
|
93
|
+
* filters.boolean('active', 'Active', 'Enabled', 'Disabled')
|
|
94
|
+
*/
|
|
95
|
+
boolean: (key: string, label: string, trueLabel: string = "Yes", falseLabel: string = "No"): DataTableFilter => ({
|
|
96
|
+
key,
|
|
97
|
+
label,
|
|
98
|
+
type: "select",
|
|
99
|
+
options: [
|
|
100
|
+
{ label: trueLabel, value: "true" },
|
|
101
|
+
{ label: falseLabel, value: "false" },
|
|
102
|
+
],
|
|
103
|
+
}),
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Number filter
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* filters.number('age', 'Age')
|
|
110
|
+
* filters.number('amount', 'Amount', 'Enter amount...')
|
|
111
|
+
*/
|
|
112
|
+
number: (key: string, label: string, placeholder?: string): DataTableFilter => ({
|
|
113
|
+
key,
|
|
114
|
+
label,
|
|
115
|
+
type: "number",
|
|
116
|
+
placeholder: placeholder ?? `Enter ${label.toLowerCase()}...`,
|
|
117
|
+
}),
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Date filter
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* filters.date('createdAt', 'Created Date')
|
|
124
|
+
*/
|
|
125
|
+
date: (key: string, label: string): DataTableFilter => ({
|
|
126
|
+
key,
|
|
127
|
+
label,
|
|
128
|
+
type: "date",
|
|
129
|
+
}),
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Date range filter
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* filters.dateRange('createdAt', 'Created')
|
|
136
|
+
*/
|
|
137
|
+
dateRange: (key: string, label: string): DataTableFilter => ({
|
|
138
|
+
key,
|
|
139
|
+
label,
|
|
140
|
+
type: "dateRange",
|
|
141
|
+
}),
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Role filter with common role options
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* filters.role()
|
|
148
|
+
* filters.role('userRole', 'User Role')
|
|
149
|
+
*/
|
|
150
|
+
role: (key: string = "role", label: string = "Role", options?: FilterOption[]): DataTableFilter => ({
|
|
151
|
+
key,
|
|
152
|
+
label,
|
|
153
|
+
type: "select",
|
|
154
|
+
options: options ?? [
|
|
155
|
+
{ label: "Admin", value: "admin" },
|
|
156
|
+
{ label: "Manager", value: "manager" },
|
|
157
|
+
{ label: "User", value: "user" },
|
|
158
|
+
{ label: "Guest", value: "guest" },
|
|
159
|
+
],
|
|
160
|
+
}),
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Type filter (generic entity type)
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* filters.type('campaignType', 'Type', [
|
|
167
|
+
* { label: 'Email', value: 'email' },
|
|
168
|
+
* { label: 'SMS', value: 'sms' },
|
|
169
|
+
* ])
|
|
170
|
+
*/
|
|
171
|
+
type: (key: string, label: string, options: FilterOption[]): DataTableFilter => ({
|
|
172
|
+
key,
|
|
173
|
+
label,
|
|
174
|
+
type: "select",
|
|
175
|
+
options,
|
|
176
|
+
}),
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Priority filter
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* filters.priority()
|
|
183
|
+
* filters.priority('urgency', 'Urgency')
|
|
184
|
+
*/
|
|
185
|
+
priority: (key: string = "priority", label: string = "Priority"): DataTableFilter => ({
|
|
186
|
+
key,
|
|
187
|
+
label,
|
|
188
|
+
type: "select",
|
|
189
|
+
options: [
|
|
190
|
+
{ label: "Low", value: "low" },
|
|
191
|
+
{ label: "Medium", value: "medium" },
|
|
192
|
+
{ label: "High", value: "high" },
|
|
193
|
+
{ label: "Critical", value: "critical" },
|
|
194
|
+
],
|
|
195
|
+
}),
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Category filter from dynamic options
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* // From array of objects
|
|
202
|
+
* filters.fromData('categoryId', 'Category', categories, 'name', 'id')
|
|
203
|
+
*/
|
|
204
|
+
fromData: <T extends Record<string, any>>(
|
|
205
|
+
key: string,
|
|
206
|
+
label: string,
|
|
207
|
+
data: T[],
|
|
208
|
+
labelKey: keyof T,
|
|
209
|
+
valueKey: keyof T
|
|
210
|
+
): DataTableFilter => ({
|
|
211
|
+
key,
|
|
212
|
+
label,
|
|
213
|
+
type: "select",
|
|
214
|
+
options: data.map((item) => ({
|
|
215
|
+
label: String(item[labelKey]),
|
|
216
|
+
value: item[valueKey] as string | number,
|
|
217
|
+
})),
|
|
218
|
+
}),
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Enum filter from TypeScript enum
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* enum Status { Active = 'active', Inactive = 'inactive' }
|
|
225
|
+
* filters.fromEnum('status', 'Status', Status)
|
|
226
|
+
*/
|
|
227
|
+
fromEnum: (key: string, label: string, enumObj: Record<string, string | number>): DataTableFilter => ({
|
|
228
|
+
key,
|
|
229
|
+
label,
|
|
230
|
+
type: "select",
|
|
231
|
+
options: Object.entries(enumObj)
|
|
232
|
+
.filter(([k]) => Number.isNaN(Number(k))) // Filter out numeric keys from numeric enums
|
|
233
|
+
.map(([enumKey, enumValue]) => ({
|
|
234
|
+
label: enumKey.replace(/([A-Z])/g, " $1").trim(), // Convert camelCase to Title Case
|
|
235
|
+
value: enumValue,
|
|
236
|
+
})),
|
|
237
|
+
}),
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Create a filter group with common filters
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* const tableFilters = createFilterGroup({
|
|
245
|
+
* search: ['name', 'email'],
|
|
246
|
+
* status: true,
|
|
247
|
+
* dateRange: 'createdAt',
|
|
248
|
+
* custom: [
|
|
249
|
+
* filters.role('userRole', 'Role'),
|
|
250
|
+
* ],
|
|
251
|
+
* });
|
|
252
|
+
*/
|
|
253
|
+
export interface FilterGroupConfig {
|
|
254
|
+
/** Fields to include as text search filters */
|
|
255
|
+
search?: string[];
|
|
256
|
+
/** Include status filter */
|
|
257
|
+
status?: boolean | { key?: string; label?: string; options?: FilterOption[] };
|
|
258
|
+
/** Field for date range filter */
|
|
259
|
+
dateRange?: string;
|
|
260
|
+
/** Additional custom filters */
|
|
261
|
+
custom?: DataTableFilter[];
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function createFilterGroup(config: FilterGroupConfig): DataTableFilter[] {
|
|
265
|
+
const result: DataTableFilter[] = [];
|
|
266
|
+
|
|
267
|
+
// Add search filters
|
|
268
|
+
if (config.search) {
|
|
269
|
+
config.search.forEach((field) => {
|
|
270
|
+
result.push(filters.text(field, field.charAt(0).toUpperCase() + field.slice(1)));
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Add status filter
|
|
275
|
+
if (config.status) {
|
|
276
|
+
if (typeof config.status === "boolean") {
|
|
277
|
+
result.push(filters.status());
|
|
278
|
+
} else {
|
|
279
|
+
result.push(filters.status(config.status.key, config.status.label, config.status.options));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Add date range filter
|
|
284
|
+
if (config.dateRange) {
|
|
285
|
+
result.push(filters.dateRange(config.dateRange, "Date Range"));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Add custom filters
|
|
289
|
+
if (config.custom) {
|
|
290
|
+
result.push(...config.custom);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Common filter presets for specific entity types
|
|
298
|
+
*/
|
|
299
|
+
export const filterPresets = {
|
|
300
|
+
/**
|
|
301
|
+
* Filters for user/account tables
|
|
302
|
+
*/
|
|
303
|
+
users: (): DataTableFilter[] => [
|
|
304
|
+
filters.text("name", "Name"),
|
|
305
|
+
filters.text("email", "Email"),
|
|
306
|
+
filters.role(),
|
|
307
|
+
filters.status(),
|
|
308
|
+
],
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Filters for campaign tables
|
|
312
|
+
*/
|
|
313
|
+
campaigns: (): DataTableFilter[] => [
|
|
314
|
+
filters.text("name", "Campaign Name"),
|
|
315
|
+
filters.status("status", "Status", [
|
|
316
|
+
{ label: "Active", value: "active" },
|
|
317
|
+
{ label: "Draft", value: "draft" },
|
|
318
|
+
{ label: "Completed", value: "completed" },
|
|
319
|
+
{ label: "Archived", value: "archived" },
|
|
320
|
+
{ label: "Failed", value: "failed" },
|
|
321
|
+
]),
|
|
322
|
+
filters.dateRange("createdAt", "Created Date"),
|
|
323
|
+
],
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Filters for contact tables
|
|
327
|
+
*/
|
|
328
|
+
contacts: (): DataTableFilter[] => [
|
|
329
|
+
filters.text("name", "Name"),
|
|
330
|
+
filters.text("email", "Email"),
|
|
331
|
+
filters.text("phone", "Phone"),
|
|
332
|
+
filters.status(),
|
|
333
|
+
],
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Filters for booking/reservation tables
|
|
337
|
+
*/
|
|
338
|
+
bookings: (): DataTableFilter[] => [
|
|
339
|
+
filters.text("guestName", "Guest Name"),
|
|
340
|
+
filters.text("confirmationNumber", "Confirmation #"),
|
|
341
|
+
filters.status("status", "Status", [
|
|
342
|
+
{ label: "Confirmed", value: "confirmed" },
|
|
343
|
+
{ label: "Pending", value: "pending" },
|
|
344
|
+
{ label: "Cancelled", value: "cancelled" },
|
|
345
|
+
{ label: "Completed", value: "completed" },
|
|
346
|
+
]),
|
|
347
|
+
filters.dateRange("checkIn", "Check-in Date"),
|
|
348
|
+
],
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Filters for audit/log tables
|
|
352
|
+
*/
|
|
353
|
+
auditLogs: (): DataTableFilter[] => [
|
|
354
|
+
filters.text("action", "Action"),
|
|
355
|
+
filters.text("user", "User"),
|
|
356
|
+
filters.dateRange("timestamp", "Date"),
|
|
357
|
+
],
|
|
358
|
+
};
|
package/src/data/index.ts
CHANGED
|
@@ -23,3 +23,14 @@ export {
|
|
|
23
23
|
type DateFormatOptions,
|
|
24
24
|
type TagColorFn,
|
|
25
25
|
} from './columnHelpers';
|
|
26
|
+
|
|
27
|
+
// Filter definition helpers
|
|
28
|
+
export {
|
|
29
|
+
filters,
|
|
30
|
+
filterPresets,
|
|
31
|
+
createFilterGroup,
|
|
32
|
+
statusOptions,
|
|
33
|
+
booleanOptions,
|
|
34
|
+
type FilterOption,
|
|
35
|
+
type FilterGroupConfig,
|
|
36
|
+
} from './filterHelpers';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, computed } from 'vue';
|
|
3
|
-
import
|
|
3
|
+
import _Ajv from 'ajv';
|
|
4
4
|
import addFormats from 'ajv-formats';
|
|
5
5
|
import {
|
|
6
6
|
uiInput,
|
|
@@ -11,6 +11,9 @@ import {
|
|
|
11
11
|
uiButton
|
|
12
12
|
} from '@hotelinking/ui';
|
|
13
13
|
|
|
14
|
+
// Handle ESM/CJS interop for ajv
|
|
15
|
+
const Ajv = (_Ajv as unknown as { default: typeof _Ajv }).default ?? _Ajv;
|
|
16
|
+
|
|
14
17
|
// Initialize AJV for JSON Schema validation with format support
|
|
15
18
|
const ajv = new Ajv({ allErrors: true });
|
|
16
19
|
addFormats(ajv); // Add support for format keywords like "email", "uri", "date", etc.
|