@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.
Files changed (60) hide show
  1. package/README.md +52 -0
  2. package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js +367 -0
  3. package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js.map +1 -0
  4. package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js +263 -0
  5. package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js.map +1 -0
  6. package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js +580 -0
  7. package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js.map +1 -0
  8. package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js +187 -0
  9. package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js.map +1 -0
  10. package/dist/_plugin-vue_export-helper-1tPrXgE0.js +11 -0
  11. package/dist/_plugin-vue_export-helper-1tPrXgE0.js.map +1 -0
  12. package/dist/components.css +15 -0
  13. package/dist/composables/index.js +32 -573
  14. package/dist/composables/index.js.map +1 -1
  15. package/dist/data/index.js +18 -0
  16. package/dist/data/index.js.map +1 -0
  17. package/dist/domain/index.js +8 -0
  18. package/dist/domain/index.js.map +1 -0
  19. package/dist/filterHelpers-DgRyoYSa.js +1386 -0
  20. package/dist/filterHelpers-DgRyoYSa.js.map +1 -0
  21. package/dist/forms/index.js +6 -0
  22. package/dist/forms/index.js.map +1 -0
  23. package/dist/index-DGO_pNgG.js +79 -0
  24. package/dist/index-DGO_pNgG.js.map +1 -0
  25. package/dist/index-QK97OdqQ.js +25 -0
  26. package/dist/index-QK97OdqQ.js.map +1 -0
  27. package/dist/index.js +67 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/navigation/index.js +8 -0
  30. package/dist/navigation/index.js.map +1 -0
  31. package/dist/overlays/index.js +8 -0
  32. package/dist/overlays/index.js.map +1 -0
  33. package/dist/stores/index.js +14 -0
  34. package/dist/stores/index.js.map +1 -0
  35. package/dist/useAdminPage-GhgXp0x8.js +1070 -0
  36. package/dist/useAdminPage-GhgXp0x8.js.map +1 -0
  37. package/dist/useTable-DutR1gkg.js +293 -0
  38. package/dist/useTable-DutR1gkg.js.map +1 -0
  39. package/package.json +43 -14
  40. package/src/composables/composables.md +109 -0
  41. package/src/composables/index.ts +69 -0
  42. package/src/composables/useAdminPage.ts +462 -0
  43. package/src/composables/useConfirmation.ts +358 -0
  44. package/src/composables/usePageContext.ts +171 -0
  45. package/src/composables/useStats.ts +361 -0
  46. package/src/composables/useTable.ts +26 -5
  47. package/src/composables/useWizard.ts +448 -0
  48. package/src/data/DataTable.vue +553 -0
  49. package/src/data/Table/Table.vue +295 -0
  50. package/src/data/columnHelpers.ts +503 -0
  51. package/src/data/data.md +106 -0
  52. package/src/data/filterHelpers.ts +358 -0
  53. package/src/data/index.ts +31 -0
  54. package/src/domain/domain.md +102 -0
  55. package/src/forms/JsonSchemaForm.vue +4 -1
  56. package/src/forms/forms.md +89 -0
  57. package/src/index.ts +4 -3
  58. package/src/navigation/navigation.md +80 -0
  59. package/src/overlays/overlays.md +86 -0
  60. package/src/stores/stores.md +82 -0
@@ -0,0 +1,358 @@
1
+ /**
2
+ * Confirmation Composable
3
+ *
4
+ * Manages confirmation dialog state for destructive actions.
5
+ * Works with uiModal from @hotelinking/ui.
6
+ */
7
+
8
+ import { ref, computed, type Ref, type ComputedRef, type Component } from 'vue';
9
+
10
+ /**
11
+ * Confirmation dialog variant
12
+ */
13
+ export type ConfirmationVariant = 'danger' | 'warning' | 'info' | 'default';
14
+
15
+ /**
16
+ * Configuration for a confirmation dialog
17
+ */
18
+ export interface ConfirmationConfig {
19
+ /** Dialog title */
20
+ title: string;
21
+ /** Dialog message/description */
22
+ message: string;
23
+ /** Text for confirm button */
24
+ confirmText?: string;
25
+ /** Text for cancel button */
26
+ cancelText?: string;
27
+ /** Visual variant */
28
+ variant?: ConfirmationVariant;
29
+ /** Icon component to display */
30
+ icon?: Component;
31
+ /** Whether action is in progress */
32
+ loading?: boolean;
33
+ }
34
+
35
+ /**
36
+ * Internal state for pending confirmation
37
+ */
38
+ interface PendingConfirmation {
39
+ resolve: (value: boolean) => void;
40
+ reject: (reason?: unknown) => void;
41
+ }
42
+
43
+ /**
44
+ * Options for useConfirmation
45
+ */
46
+ export interface UseConfirmationOptions {
47
+ /** Default confirm button text */
48
+ defaultConfirmText?: string;
49
+ /** Default cancel button text */
50
+ defaultCancelText?: string;
51
+ /** Default variant */
52
+ defaultVariant?: ConfirmationVariant;
53
+ }
54
+
55
+ /**
56
+ * Return type for useConfirmation
57
+ */
58
+ export interface UseConfirmationReturn {
59
+ /** Whether dialog is open */
60
+ isOpen: Ref<boolean>;
61
+ /** Current dialog configuration */
62
+ config: Ref<ConfirmationConfig>;
63
+ /** Whether action is in progress */
64
+ isLoading: Ref<boolean>;
65
+
66
+ /** Open confirmation dialog and wait for response */
67
+ confirm: (options: ConfirmationConfig) => Promise<boolean>;
68
+ /** Handle confirm button click */
69
+ handleConfirm: () => void;
70
+ /** Handle cancel button click */
71
+ handleCancel: () => void;
72
+ /** Close dialog without triggering callbacks */
73
+ close: () => void;
74
+
75
+ /** Convenience: Confirm delete action */
76
+ confirmDelete: (itemName: string, options?: Partial<ConfirmationConfig>) => Promise<boolean>;
77
+ /** Convenience: Confirm bulk delete action */
78
+ confirmBulkDelete: (count: number, itemType?: string, options?: Partial<ConfirmationConfig>) => Promise<boolean>;
79
+ /** Convenience: Confirm destructive action */
80
+ confirmDestructive: (actionName: string, options?: Partial<ConfirmationConfig>) => Promise<boolean>;
81
+ /** Convenience: Confirm with custom async action */
82
+ confirmWithAction: <T>(
83
+ options: ConfirmationConfig,
84
+ action: () => Promise<T>
85
+ ) => Promise<{ confirmed: boolean; result?: T; error?: Error }>;
86
+
87
+ /** Props for uiModal component */
88
+ modalProps: ComputedRef<{
89
+ title: string;
90
+ open: boolean;
91
+ modalName: string;
92
+ actions: Array<{ name: string; value: string }>;
93
+ }>;
94
+ }
95
+
96
+ /**
97
+ * Creates a confirmation dialog composable
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const confirmation = useConfirmation();
102
+ *
103
+ * async function handleDelete(item: Item) {
104
+ * const confirmed = await confirmation.confirmDelete(item.name);
105
+ * if (confirmed) {
106
+ * await deleteItem(item.id);
107
+ * }
108
+ * }
109
+ *
110
+ * // Or with async action
111
+ * async function handleDeleteWithAction(item: Item) {
112
+ * const { confirmed, error } = await confirmation.confirmWithAction(
113
+ * { title: 'Delete Item', message: `Delete "${item.name}"?` },
114
+ * () => deleteItem(item.id)
115
+ * );
116
+ * if (confirmed && !error) {
117
+ * showSuccess('Item deleted');
118
+ * }
119
+ * }
120
+ *
121
+ * // In template
122
+ * <Modal
123
+ * v-bind="confirmation.modalProps"
124
+ * @modal-action="(e) => e.action === 'confirm' ? confirmation.handleConfirm() : confirmation.handleCancel()"
125
+ * >
126
+ * <p>{{ confirmation.config.message }}</p>
127
+ * </Modal>
128
+ * ```
129
+ */
130
+ export function useConfirmation(options: UseConfirmationOptions = {}): UseConfirmationReturn {
131
+ const {
132
+ defaultConfirmText = 'Confirm',
133
+ defaultCancelText = 'Cancel',
134
+ defaultVariant = 'default',
135
+ } = options;
136
+
137
+ // State
138
+ const isOpen = ref(false);
139
+ const isLoading = ref(false);
140
+ const config = ref<ConfirmationConfig>({
141
+ title: '',
142
+ message: '',
143
+ confirmText: defaultConfirmText,
144
+ cancelText: defaultCancelText,
145
+ variant: defaultVariant,
146
+ });
147
+
148
+ // Pending promise resolution
149
+ let pending: PendingConfirmation | null = null;
150
+
151
+ /**
152
+ * Open confirmation dialog and wait for user response
153
+ */
154
+ function confirm(confirmConfig: ConfirmationConfig): Promise<boolean> {
155
+ // Set configuration with defaults
156
+ config.value = {
157
+ ...confirmConfig,
158
+ confirmText: confirmConfig.confirmText ?? defaultConfirmText,
159
+ cancelText: confirmConfig.cancelText ?? defaultCancelText,
160
+ variant: confirmConfig.variant ?? defaultVariant,
161
+ };
162
+
163
+ isOpen.value = true;
164
+ isLoading.value = false;
165
+
166
+ return new Promise((resolve, reject) => {
167
+ pending = { resolve, reject };
168
+ });
169
+ }
170
+
171
+ /**
172
+ * Handle confirm button click
173
+ */
174
+ function handleConfirm(): void {
175
+ if (pending) {
176
+ pending.resolve(true);
177
+ pending = null;
178
+ }
179
+ isOpen.value = false;
180
+ isLoading.value = false;
181
+ }
182
+
183
+ /**
184
+ * Handle cancel button click
185
+ */
186
+ function handleCancel(): void {
187
+ if (pending) {
188
+ pending.resolve(false);
189
+ pending = null;
190
+ }
191
+ isOpen.value = false;
192
+ isLoading.value = false;
193
+ }
194
+
195
+ /**
196
+ * Close dialog without resolving
197
+ */
198
+ function close(): void {
199
+ if (pending) {
200
+ pending.resolve(false);
201
+ pending = null;
202
+ }
203
+ isOpen.value = false;
204
+ isLoading.value = false;
205
+ }
206
+
207
+ /**
208
+ * Convenience: Confirm delete action
209
+ */
210
+ function confirmDelete(
211
+ itemName: string,
212
+ confirmConfig?: Partial<ConfirmationConfig>
213
+ ): Promise<boolean> {
214
+ return confirm({
215
+ title: 'Delete Item',
216
+ message: `Are you sure you want to delete "${itemName}"? This action cannot be undone.`,
217
+ confirmText: 'Delete',
218
+ cancelText: 'Cancel',
219
+ variant: 'danger',
220
+ ...confirmConfig,
221
+ });
222
+ }
223
+
224
+ /**
225
+ * Convenience: Confirm bulk delete action
226
+ */
227
+ function confirmBulkDelete(
228
+ count: number,
229
+ itemType: string = 'items',
230
+ confirmConfig?: Partial<ConfirmationConfig>
231
+ ): Promise<boolean> {
232
+ return confirm({
233
+ title: `Delete ${count} ${itemType}`,
234
+ message: `Are you sure you want to delete ${count} ${itemType}? This action cannot be undone.`,
235
+ confirmText: 'Delete All',
236
+ cancelText: 'Cancel',
237
+ variant: 'danger',
238
+ ...confirmConfig,
239
+ });
240
+ }
241
+
242
+ /**
243
+ * Convenience: Confirm destructive action
244
+ */
245
+ function confirmDestructive(
246
+ actionName: string,
247
+ confirmConfig?: Partial<ConfirmationConfig>
248
+ ): Promise<boolean> {
249
+ return confirm({
250
+ title: `Confirm ${actionName}`,
251
+ message: `Are you sure you want to ${actionName.toLowerCase()}? This action cannot be undone.`,
252
+ confirmText: actionName,
253
+ cancelText: 'Cancel',
254
+ variant: 'danger',
255
+ ...confirmConfig,
256
+ });
257
+ }
258
+
259
+ /**
260
+ * Convenience: Confirm with async action execution
261
+ */
262
+ async function confirmWithAction<T>(
263
+ confirmConfig: ConfirmationConfig,
264
+ action: () => Promise<T>
265
+ ): Promise<{ confirmed: boolean; result?: T; error?: Error }> {
266
+ const confirmed = await confirm(confirmConfig);
267
+
268
+ if (!confirmed) {
269
+ return { confirmed: false };
270
+ }
271
+
272
+ // Show loading state
273
+ isLoading.value = true;
274
+
275
+ try {
276
+ const result = await action();
277
+ isOpen.value = false;
278
+ isLoading.value = false;
279
+ return { confirmed: true, result };
280
+ } catch (error) {
281
+ isLoading.value = false;
282
+ return {
283
+ confirmed: true,
284
+ error: error instanceof Error ? error : new Error(String(error)),
285
+ };
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Props for uiModal component
291
+ */
292
+ const modalProps = computed(() => ({
293
+ title: config.value.title,
294
+ open: isOpen.value,
295
+ modalName: 'confirmation-dialog',
296
+ actions: [
297
+ {
298
+ name: config.value.cancelText ?? defaultCancelText,
299
+ value: 'cancel',
300
+ },
301
+ {
302
+ name: config.value.confirmText ?? defaultConfirmText,
303
+ value: 'confirm',
304
+ },
305
+ ],
306
+ }));
307
+
308
+ return {
309
+ // State
310
+ isOpen,
311
+ config,
312
+ isLoading,
313
+
314
+ // Core methods
315
+ confirm,
316
+ handleConfirm,
317
+ handleCancel,
318
+ close,
319
+
320
+ // Convenience methods
321
+ confirmDelete,
322
+ confirmBulkDelete,
323
+ confirmDestructive,
324
+ confirmWithAction,
325
+
326
+ // Modal props
327
+ modalProps,
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Global confirmation instance for use across components
333
+ */
334
+ let globalConfirmation: UseConfirmationReturn | null = null;
335
+
336
+ /**
337
+ * Get or create global confirmation instance
338
+ *
339
+ * @example
340
+ * ```typescript
341
+ * // In any component
342
+ * const confirmation = useGlobalConfirmation();
343
+ * await confirmation.confirmDelete('Item Name');
344
+ * ```
345
+ */
346
+ export function useGlobalConfirmation(): UseConfirmationReturn {
347
+ if (!globalConfirmation) {
348
+ globalConfirmation = useConfirmation();
349
+ }
350
+ return globalConfirmation;
351
+ }
352
+
353
+ /**
354
+ * Reset global confirmation instance (useful for testing)
355
+ */
356
+ export function resetGlobalConfirmation(): void {
357
+ globalConfirmation = null;
358
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Page Context Composable
3
+ *
4
+ * Provides access to common page context data (user, brand, routes)
5
+ * within Vue components, without prop drilling.
6
+ */
7
+
8
+ import { computed, inject, type InjectionKey, type ComputedRef } from "vue";
9
+ import { useStore } from "@nanostores/vue";
10
+ import { atom } from "nanostores";
11
+ import { routes, type Routes } from "@htlkg/core";
12
+
13
+ /**
14
+ * User information from authentication
15
+ */
16
+ export interface PageUser {
17
+ username: string;
18
+ email?: string;
19
+ isAdmin: boolean;
20
+ isSuperAdmin: boolean;
21
+ brandIds?: number[];
22
+ accountIds?: number[];
23
+ roles?: string[];
24
+ }
25
+
26
+ /**
27
+ * Brand context for brand-scoped pages
28
+ */
29
+ export interface PageBrand {
30
+ id: number;
31
+ name: string;
32
+ logo?: string;
33
+ }
34
+
35
+ /**
36
+ * Full page context
37
+ */
38
+ export interface PageContext {
39
+ user: PageUser | null;
40
+ brand?: PageBrand;
41
+ brandId?: number;
42
+ isAdmin: boolean;
43
+ isSuperAdmin: boolean;
44
+ routes: Routes;
45
+ }
46
+
47
+ /**
48
+ * Injection key for providing page context
49
+ */
50
+ export const PAGE_CONTEXT_KEY: InjectionKey<PageContext> = Symbol("pageContext");
51
+
52
+ /**
53
+ * Nanostore for user state (set by AdminLayout)
54
+ */
55
+ export const $user = atom<PageUser | null>(null);
56
+
57
+ /**
58
+ * Nanostore for current brand (set by BrandLayout)
59
+ */
60
+ export const $currentBrand = atom<PageBrand | null>(null);
61
+
62
+ /**
63
+ * Set user in the store (called from layout)
64
+ */
65
+ export function setUser(user: PageUser | null): void {
66
+ $user.set(user);
67
+ }
68
+
69
+ /**
70
+ * Set current brand in the store (called from layout)
71
+ */
72
+ export function setCurrentBrand(brand: PageBrand | null): void {
73
+ $currentBrand.set(brand);
74
+ }
75
+
76
+ /**
77
+ * Get page context within a Vue component
78
+ *
79
+ * This composable provides access to:
80
+ * - Current user information
81
+ * - Current brand (if in brand context)
82
+ * - Admin/superadmin status
83
+ * - Type-safe routes
84
+ *
85
+ * @example
86
+ * ```vue
87
+ * <script setup>
88
+ * import { usePageContext } from '@htlkg/components';
89
+ *
90
+ * const { user, isAdmin, routes } = usePageContext();
91
+ *
92
+ * function goToAccounts() {
93
+ * window.location.href = routes.admin.accounts();
94
+ * }
95
+ * </script>
96
+ * ```
97
+ *
98
+ * @example With provider pattern
99
+ * ```vue
100
+ * // In parent component
101
+ * import { provide, PAGE_CONTEXT_KEY } from '@htlkg/components';
102
+ * provide(PAGE_CONTEXT_KEY, { user, brand, isAdmin: true, routes });
103
+ *
104
+ * // In child component
105
+ * const context = usePageContext();
106
+ * ```
107
+ */
108
+ export function usePageContext(): ComputedRef<PageContext> {
109
+ // Try injection first (from provider)
110
+ const injected = inject(PAGE_CONTEXT_KEY, null);
111
+ if (injected) {
112
+ return computed(() => injected);
113
+ }
114
+
115
+ // Fallback to nanostores
116
+ const user = useStore($user);
117
+ const brand = useStore($currentBrand);
118
+
119
+ return computed(() => ({
120
+ user: user.value,
121
+ brand: brand.value ?? undefined,
122
+ brandId: brand.value?.id,
123
+ isAdmin: user.value?.isAdmin ?? false,
124
+ isSuperAdmin: user.value?.isSuperAdmin ?? false,
125
+ routes,
126
+ }));
127
+ }
128
+
129
+ /**
130
+ * Check if user has access to a specific brand
131
+ */
132
+ export function useHasAccessToBrand(brandId: number): ComputedRef<boolean> {
133
+ const context = usePageContext();
134
+
135
+ return computed(() => {
136
+ const user = context.value.user;
137
+ if (!user) return false;
138
+ if (user.isAdmin || user.isSuperAdmin) return true;
139
+ return user.brandIds?.includes(brandId) ?? false;
140
+ });
141
+ }
142
+
143
+ /**
144
+ * Check if user has access to a specific account
145
+ */
146
+ export function useHasAccessToAccount(accountId: number): ComputedRef<boolean> {
147
+ const context = usePageContext();
148
+
149
+ return computed(() => {
150
+ const user = context.value.user;
151
+ if (!user) return false;
152
+ if (user.isAdmin || user.isSuperAdmin) return true;
153
+ return user.accountIds?.includes(accountId) ?? false;
154
+ });
155
+ }
156
+
157
+ /**
158
+ * Get user roles
159
+ */
160
+ export function useUserRoles(): ComputedRef<string[]> {
161
+ const context = usePageContext();
162
+ return computed(() => context.value.user?.roles ?? []);
163
+ }
164
+
165
+ /**
166
+ * Check if user has a specific role
167
+ */
168
+ export function useHasRole(role: string): ComputedRef<boolean> {
169
+ const roles = useUserRoles();
170
+ return computed(() => roles.value.includes(role));
171
+ }