@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,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
|
+
}
|
|
@@ -122,11 +122,13 @@ export function useTable<T extends Record<string, any>>(
|
|
|
122
122
|
|
|
123
123
|
// Computed - Filtering
|
|
124
124
|
const filteredItems = computed(() => {
|
|
125
|
-
|
|
125
|
+
// Ensure activeFilters is always an array
|
|
126
|
+
const filters = Array.isArray(activeFilters.value) ? activeFilters.value : [];
|
|
127
|
+
if (filters.length === 0) return items.value;
|
|
126
128
|
|
|
127
129
|
return items.value.filter(item => {
|
|
128
130
|
// All filters must pass (AND logic by default)
|
|
129
|
-
return
|
|
131
|
+
return filters.every(filter => {
|
|
130
132
|
const { category, operator, value } = filter;
|
|
131
133
|
|
|
132
134
|
if (value === undefined || value === null || value === '') return true;
|
|
@@ -297,8 +299,27 @@ export function useTable<T extends Record<string, any>>(
|
|
|
297
299
|
}
|
|
298
300
|
|
|
299
301
|
// Methods - Filtering
|
|
300
|
-
function applyFilters(filters: SmartFilter[]) {
|
|
301
|
-
|
|
302
|
+
function applyFilters(filters: SmartFilter[] | Record<string, any>) {
|
|
303
|
+
// Handle various filter formats from UI components
|
|
304
|
+
if (Array.isArray(filters)) {
|
|
305
|
+
activeFilters.value = filters;
|
|
306
|
+
} else if (filters && typeof filters === 'object') {
|
|
307
|
+
// Handle object format like { filters: [...] } or extract values
|
|
308
|
+
if ('filters' in filters && Array.isArray(filters.filters)) {
|
|
309
|
+
activeFilters.value = filters.filters;
|
|
310
|
+
} else {
|
|
311
|
+
// Convert object to SmartFilter array
|
|
312
|
+
activeFilters.value = Object.entries(filters)
|
|
313
|
+
.filter(([_, value]) => value !== undefined && value !== null && value !== '')
|
|
314
|
+
.map(([key, value]) => ({
|
|
315
|
+
category: key,
|
|
316
|
+
operator: 'contains',
|
|
317
|
+
value,
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
activeFilters.value = [];
|
|
322
|
+
}
|
|
302
323
|
currentPage.value = 1; // Reset to first page when filters change
|
|
303
324
|
}
|
|
304
325
|
|
|
@@ -314,7 +335,7 @@ export function useTable<T extends Record<string, any>>(
|
|
|
314
335
|
}
|
|
315
336
|
}
|
|
316
337
|
|
|
317
|
-
function handleSmartFiltersApplied(filters: SmartFilter[]) {
|
|
338
|
+
function handleSmartFiltersApplied(filters: SmartFilter[] | Record<string, any>) {
|
|
318
339
|
applyFilters(filters);
|
|
319
340
|
}
|
|
320
341
|
|