@htlkg/components 0.0.2 → 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/README.md +52 -0
- 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 -573
- 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 +43 -14
- package/src/composables/composables.md +109 -0
- package/src/composables/index.ts +69 -0
- package/src/composables/useAdminPage.ts +462 -0
- package/src/composables/useConfirmation.ts +358 -0
- package/src/composables/usePageContext.ts +171 -0
- package/src/composables/useStats.ts +361 -0
- package/src/composables/useTable.ts +26 -5
- package/src/composables/useWizard.ts +448 -0
- package/src/data/DataTable.vue +553 -0
- package/src/data/Table/Table.vue +295 -0
- package/src/data/columnHelpers.ts +503 -0
- package/src/data/data.md +106 -0
- package/src/data/filterHelpers.ts +358 -0
- package/src/data/index.ts +31 -0
- package/src/domain/domain.md +102 -0
- package/src/forms/JsonSchemaForm.vue +4 -1
- 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,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
|
@@ -3,3 +3,34 @@ export { default as DataList } from './DataList.vue';
|
|
|
3
3
|
export { default as SearchableSelect } from './SearchableSelect.vue';
|
|
4
4
|
export { default as Chart } from './Chart.vue';
|
|
5
5
|
export type { ChartSeries, ChartOptions, ChartDateRange, ChartAnnotation, NpsLiterals } from './Chart.vue';
|
|
6
|
+
|
|
7
|
+
// DataTable - Simplified table with automatic useTable integration
|
|
8
|
+
export { default as DataTable } from './DataTable.vue';
|
|
9
|
+
export type {
|
|
10
|
+
DataTableColumn,
|
|
11
|
+
DataTableFilter,
|
|
12
|
+
BulkAction,
|
|
13
|
+
RowAction,
|
|
14
|
+
NoResultsConfig,
|
|
15
|
+
} from './DataTable.vue';
|
|
16
|
+
|
|
17
|
+
// Column definition helpers
|
|
18
|
+
export {
|
|
19
|
+
columns,
|
|
20
|
+
statusColors,
|
|
21
|
+
getStatusColor,
|
|
22
|
+
createStatusColorFn,
|
|
23
|
+
type DateFormatOptions,
|
|
24
|
+
type TagColorFn,
|
|
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';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Domain Module
|
|
2
|
+
|
|
3
|
+
Domain-specific components for the Hotelinking platform.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### BrandCard
|
|
8
|
+
|
|
9
|
+
Card component for displaying brand information.
|
|
10
|
+
|
|
11
|
+
```vue
|
|
12
|
+
<script setup>
|
|
13
|
+
import { BrandCard } from '@htlkg/components/domain';
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<BrandCard
|
|
18
|
+
:brand="brand"
|
|
19
|
+
:show-status="true"
|
|
20
|
+
@click="handleClick"
|
|
21
|
+
@edit="handleEdit"
|
|
22
|
+
/>
|
|
23
|
+
</template>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### BrandSelector
|
|
27
|
+
|
|
28
|
+
Grid-based brand selection component.
|
|
29
|
+
|
|
30
|
+
```vue
|
|
31
|
+
<script setup>
|
|
32
|
+
import { ref } from 'vue';
|
|
33
|
+
import { BrandSelector } from '@htlkg/components/domain';
|
|
34
|
+
|
|
35
|
+
const selectedBrand = ref(null);
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<BrandSelector
|
|
40
|
+
v-model="selectedBrand"
|
|
41
|
+
:brands="brands"
|
|
42
|
+
:loading="loading"
|
|
43
|
+
searchable
|
|
44
|
+
/>
|
|
45
|
+
</template>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### ProductBadge
|
|
49
|
+
|
|
50
|
+
Badge component for product status indicators.
|
|
51
|
+
|
|
52
|
+
```vue
|
|
53
|
+
<script setup>
|
|
54
|
+
import { ProductBadge } from '@htlkg/components/domain';
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<ProductBadge
|
|
59
|
+
:product="product"
|
|
60
|
+
:enabled="isEnabled"
|
|
61
|
+
size="sm"
|
|
62
|
+
/>
|
|
63
|
+
</template>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### UserAvatar
|
|
67
|
+
|
|
68
|
+
User avatar with profile pictures or initials.
|
|
69
|
+
|
|
70
|
+
```vue
|
|
71
|
+
<script setup>
|
|
72
|
+
import { UserAvatar } from '@htlkg/components/domain';
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<template>
|
|
76
|
+
<UserAvatar
|
|
77
|
+
:user="user"
|
|
78
|
+
size="md"
|
|
79
|
+
:show-name="true"
|
|
80
|
+
:show-role="true"
|
|
81
|
+
/>
|
|
82
|
+
</template>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Props
|
|
86
|
+
|
|
87
|
+
### BrandCard
|
|
88
|
+
|
|
89
|
+
| Prop | Type | Description |
|
|
90
|
+
|------|------|-------------|
|
|
91
|
+
| brand | Brand | Brand object |
|
|
92
|
+
| showStatus | boolean | Show status badge |
|
|
93
|
+
| clickable | boolean | Enable click handler |
|
|
94
|
+
|
|
95
|
+
### BrandSelector
|
|
96
|
+
|
|
97
|
+
| Prop | Type | Description |
|
|
98
|
+
|------|------|-------------|
|
|
99
|
+
| brands | Brand[] | Available brands |
|
|
100
|
+
| modelValue | Brand | Selected brand (v-model) |
|
|
101
|
+
| searchable | boolean | Enable search |
|
|
102
|
+
| loading | boolean | Loading state |
|
|
@@ -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.
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Forms Module
|
|
2
|
+
|
|
3
|
+
Components for form creation and validation.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### JsonSchemaForm
|
|
8
|
+
|
|
9
|
+
Dynamic form generator from JSON Schema with AJV validation.
|
|
10
|
+
|
|
11
|
+
```vue
|
|
12
|
+
<script setup>
|
|
13
|
+
import { ref } from 'vue';
|
|
14
|
+
import { JsonSchemaForm } from '@htlkg/components/forms';
|
|
15
|
+
|
|
16
|
+
const formData = ref({});
|
|
17
|
+
const schema = {
|
|
18
|
+
type: 'object',
|
|
19
|
+
properties: {
|
|
20
|
+
name: { type: 'string', title: 'Name' },
|
|
21
|
+
email: { type: 'string', format: 'email', title: 'Email' },
|
|
22
|
+
age: { type: 'number', minimum: 0, title: 'Age' },
|
|
23
|
+
},
|
|
24
|
+
required: ['name', 'email'],
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const uiSchema = {
|
|
28
|
+
name: { 'ui:placeholder': 'Enter name' },
|
|
29
|
+
email: { 'ui:widget': 'email' },
|
|
30
|
+
};
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<JsonSchemaForm
|
|
35
|
+
v-model="formData"
|
|
36
|
+
:schema="schema"
|
|
37
|
+
:ui-schema="uiSchema"
|
|
38
|
+
@submit="handleSubmit"
|
|
39
|
+
@validation-error="handleError"
|
|
40
|
+
/>
|
|
41
|
+
</template>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### DateRange
|
|
45
|
+
|
|
46
|
+
Date range selector with start/end dates.
|
|
47
|
+
|
|
48
|
+
```vue
|
|
49
|
+
<script setup>
|
|
50
|
+
import { ref } from 'vue';
|
|
51
|
+
import { DateRange } from '@htlkg/components/forms';
|
|
52
|
+
|
|
53
|
+
const dateRange = ref({
|
|
54
|
+
start: null,
|
|
55
|
+
end: null,
|
|
56
|
+
});
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<template>
|
|
60
|
+
<DateRange
|
|
61
|
+
v-model="dateRange"
|
|
62
|
+
:min-date="minDate"
|
|
63
|
+
:max-date="maxDate"
|
|
64
|
+
@search="handleSearch"
|
|
65
|
+
/>
|
|
66
|
+
</template>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Validation
|
|
70
|
+
|
|
71
|
+
JsonSchemaForm uses AJV for validation:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
const schema = {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
password: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
minLength: 8,
|
|
80
|
+
pattern: '^(?=.*[A-Z])(?=.*[0-9])',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
errorMessage: {
|
|
84
|
+
properties: {
|
|
85
|
+
password: 'Password must be 8+ chars with uppercase and number',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
```
|
package/src/index.ts
CHANGED
|
@@ -13,8 +13,9 @@ export * from './overlays';
|
|
|
13
13
|
// Domain components
|
|
14
14
|
export * from './domain';
|
|
15
15
|
|
|
16
|
-
// Composables
|
|
16
|
+
// Composables (includes page context with setUser, $user, etc.)
|
|
17
17
|
export * from './composables';
|
|
18
18
|
|
|
19
|
-
// Stores
|
|
20
|
-
|
|
19
|
+
// Note: Stores are not re-exported here to avoid naming conflicts
|
|
20
|
+
// Use composables/usePageContext for page-level user state
|
|
21
|
+
// Import from '@htlkg/components/stores' for other store utilities
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Navigation Module
|
|
2
|
+
|
|
3
|
+
Components for navigation and page structure.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
### Breadcrumbs
|
|
8
|
+
|
|
9
|
+
Hierarchical navigation showing current page location.
|
|
10
|
+
|
|
11
|
+
```vue
|
|
12
|
+
<script setup>
|
|
13
|
+
import { Breadcrumbs } from '@htlkg/components/navigation';
|
|
14
|
+
|
|
15
|
+
const items = [
|
|
16
|
+
{ label: 'Home', href: '/' },
|
|
17
|
+
{ label: 'Brands', href: '/brands' },
|
|
18
|
+
{ label: 'Brand Name' },
|
|
19
|
+
];
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<template>
|
|
23
|
+
<Breadcrumbs :items="items" />
|
|
24
|
+
</template>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Stepper
|
|
28
|
+
|
|
29
|
+
Multi-step progress indicator for wizards and forms.
|
|
30
|
+
|
|
31
|
+
```vue
|
|
32
|
+
<script setup>
|
|
33
|
+
import { ref } from 'vue';
|
|
34
|
+
import { Stepper } from '@htlkg/components/navigation';
|
|
35
|
+
|
|
36
|
+
const currentStep = ref(0);
|
|
37
|
+
const steps = [
|
|
38
|
+
{ label: 'Account', valid: true },
|
|
39
|
+
{ label: 'Profile', valid: false },
|
|
40
|
+
{ label: 'Review', valid: false },
|
|
41
|
+
];
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<template>
|
|
45
|
+
<Stepper
|
|
46
|
+
v-model="currentStep"
|
|
47
|
+
:steps="steps"
|
|
48
|
+
@step-change="handleStepChange"
|
|
49
|
+
/>
|
|
50
|
+
</template>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Tabs
|
|
54
|
+
|
|
55
|
+
Tabbed interface for organizing content.
|
|
56
|
+
|
|
57
|
+
```vue
|
|
58
|
+
<script setup>
|
|
59
|
+
import { ref } from 'vue';
|
|
60
|
+
import { Tabs } from '@htlkg/components/navigation';
|
|
61
|
+
|
|
62
|
+
const activeTab = ref('overview');
|
|
63
|
+
const tabs = [
|
|
64
|
+
{ id: 'overview', label: 'Overview' },
|
|
65
|
+
{ id: 'settings', label: 'Settings', count: 3 },
|
|
66
|
+
{ id: 'users', label: 'Users', disabled: true },
|
|
67
|
+
];
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<template>
|
|
71
|
+
<Tabs v-model="activeTab" :tabs="tabs">
|
|
72
|
+
<template #overview>
|
|
73
|
+
<OverviewContent />
|
|
74
|
+
</template>
|
|
75
|
+
<template #settings>
|
|
76
|
+
<SettingsContent />
|
|
77
|
+
</template>
|
|
78
|
+
</Tabs>
|
|
79
|
+
</template>
|
|
80
|
+
```
|