@htlkg/data 0.0.21 → 0.0.23
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/hooks/index.d.ts +702 -94
- package/dist/hooks/index.js +793 -56
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +802 -65
- package/dist/index.js.map +1 -1
- package/dist/mutations/index.js +4 -4
- package/dist/mutations/index.js.map +1 -1
- package/dist/queries/index.js +5 -5
- package/dist/queries/index.js.map +1 -1
- package/package.json +11 -12
- package/src/hooks/accounts/index.ts +2 -0
- package/src/hooks/{useAccounts.ts → accounts/useAccounts.ts} +48 -5
- package/src/hooks/accounts/usePaginatedAccounts.ts +166 -0
- package/src/hooks/brands/index.ts +2 -0
- package/src/hooks/{useBrands.ts → brands/useBrands.ts} +1 -1
- package/src/hooks/brands/usePaginatedBrands.ts +206 -0
- package/src/hooks/contacts/index.ts +2 -0
- package/src/hooks/contacts/useContacts.ts +176 -0
- package/src/hooks/contacts/usePaginatedContacts.ts +268 -0
- package/src/hooks/createPaginatedDataHook.ts +359 -0
- package/src/hooks/data-hook-errors.property.test.ts +4 -4
- package/src/hooks/data-hook-filters.property.test.ts +4 -4
- package/src/hooks/data-hooks.property.test.ts +4 -4
- package/src/hooks/index.ts +101 -8
- package/src/hooks/productInstances/index.ts +1 -0
- package/src/hooks/{useProductInstances.ts → productInstances/useProductInstances.ts} +9 -6
- package/src/hooks/products/index.ts +1 -0
- package/src/hooks/{useProducts.ts → products/useProducts.ts} +4 -5
- package/src/hooks/reservations/index.ts +2 -0
- package/src/hooks/reservations/usePaginatedReservations.ts +258 -0
- package/src/hooks/{useReservations.ts → reservations/useReservations.ts} +65 -10
- package/src/hooks/users/index.ts +2 -0
- package/src/hooks/users/usePaginatedUsers.ts +213 -0
- package/src/hooks/{useUsers.ts → users/useUsers.ts} +1 -1
- package/src/mutations/accounts/accounts.test.ts +287 -0
- package/src/mutations/{accounts.ts → accounts/accounts.ts} +2 -2
- package/src/mutations/accounts/index.ts +1 -0
- package/src/mutations/brands/brands.test.ts +292 -0
- package/src/mutations/{brands.ts → brands/brands.ts} +2 -2
- package/src/mutations/brands/index.ts +1 -0
- package/src/mutations/reservations/index.ts +1 -0
- package/src/mutations/{reservations.test.ts → reservations/reservations.test.ts} +1 -1
- package/src/mutations/{reservations.ts → reservations/reservations.ts} +2 -2
- package/src/mutations/users/index.ts +1 -0
- package/src/mutations/users/users.test.ts +289 -0
- package/src/mutations/{users.ts → users/users.ts} +2 -2
- package/src/queries/accounts/accounts.test.ts +228 -0
- package/src/queries/accounts/index.ts +1 -0
- package/src/queries/brands/brands.test.ts +288 -0
- package/src/queries/brands/index.ts +1 -0
- package/src/queries/products/index.ts +1 -0
- package/src/queries/products/products.test.ts +347 -0
- package/src/queries/reservations/index.ts +1 -0
- package/src/queries/users/index.ts +1 -0
- package/src/queries/users/users.test.ts +301 -0
- /package/src/queries/{accounts.ts → accounts/accounts.ts} +0 -0
- /package/src/queries/{brands.ts → brands/brands.ts} +0 -0
- /package/src/queries/{products.ts → products/products.ts} +0 -0
- /package/src/queries/{reservations.test.ts → reservations/reservations.test.ts} +0 -0
- /package/src/queries/{reservations.ts → reservations/reservations.ts} +0 -0
- /package/src/queries/{users.ts → users/users.ts} +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paginated Reservations Hooks
|
|
3
|
+
*
|
|
4
|
+
* Vue composables for fetching reservations with server-side pagination.
|
|
5
|
+
* Provides separate hooks for active and deleted reservations to enable
|
|
6
|
+
* efficient tab-based filtering in large datasets.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Ref, ComputedRef } from "vue";
|
|
10
|
+
import type { Reservation } from "../../queries/reservations";
|
|
11
|
+
import {
|
|
12
|
+
createPaginatedDataHook,
|
|
13
|
+
ACTIVE_FILTER,
|
|
14
|
+
DELETED_FILTER,
|
|
15
|
+
type PaginatedHookOptions,
|
|
16
|
+
type PaginationState,
|
|
17
|
+
} from "../createPaginatedDataHook";
|
|
18
|
+
import type { ReservationWithRelations } from "./useReservations";
|
|
19
|
+
|
|
20
|
+
export interface UsePaginatedReservationsOptions extends PaginatedHookOptions {
|
|
21
|
+
/** Filter by brand ID */
|
|
22
|
+
brandId?: string;
|
|
23
|
+
/** Filter by start date (check-in date >= startDate) */
|
|
24
|
+
startDate?: string;
|
|
25
|
+
/** Filter by end date (check-in date <= endDate) */
|
|
26
|
+
endDate?: string;
|
|
27
|
+
/** Filter by reservation status */
|
|
28
|
+
status?: Reservation["status"];
|
|
29
|
+
/** Filter by contact/visit ID */
|
|
30
|
+
contactId?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface UsePaginatedReservationsReturn {
|
|
34
|
+
/** Reactive array of reservations with brand and guest info */
|
|
35
|
+
reservations: Ref<ReservationWithRelations[]>;
|
|
36
|
+
/** Loading state (true during any fetch) */
|
|
37
|
+
loading: Ref<boolean>;
|
|
38
|
+
/** Loading state for initial fetch */
|
|
39
|
+
initialLoading: Ref<boolean>;
|
|
40
|
+
/** Loading state for loadMore */
|
|
41
|
+
loadingMore: Ref<boolean>;
|
|
42
|
+
/** Error state */
|
|
43
|
+
error: Ref<Error | null>;
|
|
44
|
+
/** Pagination state */
|
|
45
|
+
pagination: Ref<PaginationState>;
|
|
46
|
+
/** Whether there are more items to load */
|
|
47
|
+
hasMore: ComputedRef<boolean>;
|
|
48
|
+
/** Load next page of data */
|
|
49
|
+
loadMore: () => Promise<void>;
|
|
50
|
+
/** Refetch data (resets to first page) */
|
|
51
|
+
refetch: () => Promise<void>;
|
|
52
|
+
/** Reset data and pagination state */
|
|
53
|
+
reset: () => void;
|
|
54
|
+
/** Update search filter and refetch from server (searches ALL data) */
|
|
55
|
+
setSearchFilter: (filter: any) => Promise<void>;
|
|
56
|
+
/** Current search filter */
|
|
57
|
+
searchFilter: Ref<any>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Build additional filter from hook options
|
|
62
|
+
*/
|
|
63
|
+
function buildFilter(options: UsePaginatedReservationsOptions): any {
|
|
64
|
+
const conditions: any[] = [];
|
|
65
|
+
|
|
66
|
+
if (options.brandId) {
|
|
67
|
+
conditions.push({ brandId: { eq: options.brandId } });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (options.status) {
|
|
71
|
+
conditions.push({ status: { eq: options.status } });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (options.contactId) {
|
|
75
|
+
conditions.push({ visitId: { eq: options.contactId } });
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (options.startDate) {
|
|
79
|
+
conditions.push({ checkIn: { ge: options.startDate } });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (options.endDate) {
|
|
83
|
+
conditions.push({ checkIn: { le: options.endDate } });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (options.filter && Object.keys(options.filter).length > 0) {
|
|
87
|
+
conditions.push(options.filter);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (conditions.length === 0) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
if (conditions.length === 1) {
|
|
94
|
+
return conditions[0];
|
|
95
|
+
}
|
|
96
|
+
return { and: conditions };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Transform reservation data to include brandName and guest info from visit.snapshot
|
|
101
|
+
*/
|
|
102
|
+
function transformReservation(r: any): ReservationWithRelations {
|
|
103
|
+
// Parse snapshot if it's a string (handles double-stringification from import)
|
|
104
|
+
let snapshot = r.visit?.snapshot;
|
|
105
|
+
if (typeof snapshot === "string") {
|
|
106
|
+
try {
|
|
107
|
+
snapshot = JSON.parse(snapshot);
|
|
108
|
+
} catch {
|
|
109
|
+
snapshot = null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
...r,
|
|
115
|
+
brandName: r.brand?.name || "Unknown Brand",
|
|
116
|
+
guestName: snapshot
|
|
117
|
+
? `${snapshot.firstName || ""} ${snapshot.lastName || ""}`.trim() || "Unknown Guest"
|
|
118
|
+
: "Unknown Guest",
|
|
119
|
+
guestEmail: snapshot?.email || "",
|
|
120
|
+
guestPhone: snapshot?.phone || "",
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Selection set for reservation queries
|
|
126
|
+
*/
|
|
127
|
+
const RESERVATION_SELECTION_SET = [
|
|
128
|
+
"id",
|
|
129
|
+
"brandId",
|
|
130
|
+
"visitId",
|
|
131
|
+
"checkIn",
|
|
132
|
+
"checkOut",
|
|
133
|
+
"room",
|
|
134
|
+
"roomType",
|
|
135
|
+
"status",
|
|
136
|
+
"source",
|
|
137
|
+
"channel",
|
|
138
|
+
"confirmationCode",
|
|
139
|
+
"totalAmount",
|
|
140
|
+
"currency",
|
|
141
|
+
"nights",
|
|
142
|
+
"deletedAt",
|
|
143
|
+
"deletedBy",
|
|
144
|
+
"brand.name",
|
|
145
|
+
"visit.snapshot",
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Internal hook for active reservations
|
|
150
|
+
*/
|
|
151
|
+
const useActiveReservationsInternal = createPaginatedDataHook<
|
|
152
|
+
ReservationWithRelations,
|
|
153
|
+
UsePaginatedReservationsOptions
|
|
154
|
+
>({
|
|
155
|
+
model: "Reservation",
|
|
156
|
+
dataPropertyName: "reservations",
|
|
157
|
+
defaultPageSize: 25,
|
|
158
|
+
selectionSet: RESERVATION_SELECTION_SET,
|
|
159
|
+
transform: transformReservation,
|
|
160
|
+
buildFilter,
|
|
161
|
+
baseFilter: ACTIVE_FILTER,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Internal hook for deleted reservations
|
|
166
|
+
*/
|
|
167
|
+
const useDeletedReservationsInternal = createPaginatedDataHook<
|
|
168
|
+
ReservationWithRelations,
|
|
169
|
+
UsePaginatedReservationsOptions
|
|
170
|
+
>({
|
|
171
|
+
model: "Reservation",
|
|
172
|
+
dataPropertyName: "reservations",
|
|
173
|
+
defaultPageSize: 25,
|
|
174
|
+
selectionSet: RESERVATION_SELECTION_SET,
|
|
175
|
+
transform: transformReservation,
|
|
176
|
+
buildFilter,
|
|
177
|
+
baseFilter: DELETED_FILTER,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Composable for fetching active (non-deleted) reservations with pagination
|
|
182
|
+
*
|
|
183
|
+
* @example
|
|
184
|
+
* ```typescript
|
|
185
|
+
* import { useActiveReservations } from '@htlkg/data/hooks';
|
|
186
|
+
*
|
|
187
|
+
* const { reservations, loading, hasMore, loadMore, refetch } = useActiveReservations({
|
|
188
|
+
* pageSize: 25,
|
|
189
|
+
* brandId: 'brand-123',
|
|
190
|
+
* });
|
|
191
|
+
*
|
|
192
|
+
* // Load more when user scrolls or clicks "Load More"
|
|
193
|
+
* async function onLoadMore() {
|
|
194
|
+
* await loadMore();
|
|
195
|
+
* }
|
|
196
|
+
* ```
|
|
197
|
+
*
|
|
198
|
+
* @example With date filters
|
|
199
|
+
* ```typescript
|
|
200
|
+
* const { reservations, loading } = useActiveReservations({
|
|
201
|
+
* brandId: 'brand-123',
|
|
202
|
+
* startDate: '2026-01-01',
|
|
203
|
+
* endDate: '2026-01-31',
|
|
204
|
+
* status: 'confirmed'
|
|
205
|
+
* });
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
export function useActiveReservations(
|
|
209
|
+
options: UsePaginatedReservationsOptions = {},
|
|
210
|
+
): UsePaginatedReservationsReturn {
|
|
211
|
+
const result = useActiveReservationsInternal(options);
|
|
212
|
+
return {
|
|
213
|
+
reservations: result.reservations as Ref<ReservationWithRelations[]>,
|
|
214
|
+
loading: result.loading,
|
|
215
|
+
initialLoading: result.initialLoading,
|
|
216
|
+
loadingMore: result.loadingMore,
|
|
217
|
+
error: result.error,
|
|
218
|
+
pagination: result.pagination,
|
|
219
|
+
hasMore: result.hasMore,
|
|
220
|
+
loadMore: result.loadMore,
|
|
221
|
+
refetch: result.refetch,
|
|
222
|
+
reset: result.reset,
|
|
223
|
+
setSearchFilter: result.setSearchFilter,
|
|
224
|
+
searchFilter: result.searchFilter,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Composable for fetching deleted reservations with pagination
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```typescript
|
|
233
|
+
* import { useDeletedReservations } from '@htlkg/data/hooks';
|
|
234
|
+
*
|
|
235
|
+
* const { reservations, loading, hasMore, loadMore, refetch } = useDeletedReservations({
|
|
236
|
+
* pageSize: 25,
|
|
237
|
+
* });
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export function useDeletedReservations(
|
|
241
|
+
options: UsePaginatedReservationsOptions = {},
|
|
242
|
+
): UsePaginatedReservationsReturn {
|
|
243
|
+
const result = useDeletedReservationsInternal(options);
|
|
244
|
+
return {
|
|
245
|
+
reservations: result.reservations as Ref<ReservationWithRelations[]>,
|
|
246
|
+
loading: result.loading,
|
|
247
|
+
initialLoading: result.initialLoading,
|
|
248
|
+
loadingMore: result.loadingMore,
|
|
249
|
+
error: result.error,
|
|
250
|
+
pagination: result.pagination,
|
|
251
|
+
hasMore: result.hasMore,
|
|
252
|
+
loadMore: result.loadMore,
|
|
253
|
+
refetch: result.refetch,
|
|
254
|
+
reset: result.reset,
|
|
255
|
+
setSearchFilter: result.setSearchFilter,
|
|
256
|
+
searchFilter: result.searchFilter,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import type { Ref, ComputedRef } from "vue";
|
|
9
|
-
import type { Reservation } from "
|
|
10
|
-
import { createDataHook, type BaseHookOptions } from "
|
|
9
|
+
import type { Reservation } from "../../queries/reservations";
|
|
10
|
+
import { createDataHook, type BaseHookOptions } from "../createDataHook";
|
|
11
11
|
|
|
12
12
|
export interface UseReservationsOptions extends BaseHookOptions {
|
|
13
13
|
/** Filter by brand ID */
|
|
@@ -24,13 +24,21 @@ export interface UseReservationsOptions extends BaseHookOptions {
|
|
|
24
24
|
nextToken?: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Extended reservation type with related data */
|
|
28
|
+
export type ReservationWithRelations = Reservation & {
|
|
29
|
+
brandName: string;
|
|
30
|
+
guestName: string;
|
|
31
|
+
guestEmail: string;
|
|
32
|
+
guestPhone: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
27
35
|
export interface UseReservationsReturn {
|
|
28
|
-
/** Reactive array of reservations */
|
|
29
|
-
reservations: Ref<
|
|
36
|
+
/** Reactive array of reservations with brand and guest info */
|
|
37
|
+
reservations: Ref<ReservationWithRelations[]>;
|
|
30
38
|
/** Computed array of confirmed reservations */
|
|
31
|
-
confirmedReservations: ComputedRef<
|
|
39
|
+
confirmedReservations: ComputedRef<ReservationWithRelations[]>;
|
|
32
40
|
/** Computed array of active reservations (confirmed or checked_in) */
|
|
33
|
-
activeReservations: ComputedRef<
|
|
41
|
+
activeReservations: ComputedRef<ReservationWithRelations[]>;
|
|
34
42
|
/** Loading state */
|
|
35
43
|
loading: Ref<boolean>;
|
|
36
44
|
/** Error state */
|
|
@@ -80,17 +88,64 @@ function buildFilter(options: UseReservationsOptions): any {
|
|
|
80
88
|
return { and: conditions };
|
|
81
89
|
}
|
|
82
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Transform reservation data to include brandName and guest info from visit.snapshot
|
|
93
|
+
*/
|
|
94
|
+
function transformReservation(r: any): Reservation & { brandName: string; guestName: string; guestEmail: string; guestPhone: string } {
|
|
95
|
+
// Parse snapshot if it's a string (handles double-stringification from import)
|
|
96
|
+
let snapshot = r.visit?.snapshot;
|
|
97
|
+
if (typeof snapshot === "string") {
|
|
98
|
+
try {
|
|
99
|
+
snapshot = JSON.parse(snapshot);
|
|
100
|
+
} catch {
|
|
101
|
+
snapshot = null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
...r,
|
|
107
|
+
brandName: r.brand?.name || "Unknown Brand",
|
|
108
|
+
guestName: snapshot
|
|
109
|
+
? `${snapshot.firstName || ""} ${snapshot.lastName || ""}`.trim() || "Unknown Guest"
|
|
110
|
+
: "Unknown Guest",
|
|
111
|
+
guestEmail: snapshot?.email || "",
|
|
112
|
+
guestPhone: snapshot?.phone || "",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
83
116
|
/**
|
|
84
117
|
* Internal hook created by factory
|
|
85
118
|
*/
|
|
86
119
|
const useReservationsInternal = createDataHook<
|
|
87
|
-
Reservation,
|
|
120
|
+
Reservation & { brandName: string; guestName: string; guestEmail: string; guestPhone: string },
|
|
88
121
|
UseReservationsOptions,
|
|
89
122
|
{ confirmedReservations: Reservation[]; activeReservations: Reservation[] }
|
|
90
123
|
>({
|
|
91
124
|
model: "Reservation",
|
|
92
125
|
dataPropertyName: "reservations",
|
|
93
126
|
buildFilter,
|
|
127
|
+
// Include related data for brand name and guest info
|
|
128
|
+
selectionSet: [
|
|
129
|
+
"id",
|
|
130
|
+
"brandId",
|
|
131
|
+
"visitId",
|
|
132
|
+
"checkIn",
|
|
133
|
+
"checkOut",
|
|
134
|
+
"room",
|
|
135
|
+
"roomType",
|
|
136
|
+
"status",
|
|
137
|
+
"source",
|
|
138
|
+
"channel",
|
|
139
|
+
"confirmationCode",
|
|
140
|
+
"totalAmount",
|
|
141
|
+
"currency",
|
|
142
|
+
"nights",
|
|
143
|
+
"deletedAt",
|
|
144
|
+
"deletedBy",
|
|
145
|
+
"brand.name",
|
|
146
|
+
"visit.snapshot",
|
|
147
|
+
],
|
|
148
|
+
transform: transformReservation,
|
|
94
149
|
computedProperties: {
|
|
95
150
|
confirmedReservations: (reservations) =>
|
|
96
151
|
reservations.filter((r) => r.status === "confirmed"),
|
|
@@ -135,9 +190,9 @@ const useReservationsInternal = createDataHook<
|
|
|
135
190
|
export function useReservations(options: UseReservationsOptions = {}): UseReservationsReturn {
|
|
136
191
|
const result = useReservationsInternal(options);
|
|
137
192
|
return {
|
|
138
|
-
reservations: result.reservations as Ref<
|
|
139
|
-
confirmedReservations: result.confirmedReservations as ComputedRef<
|
|
140
|
-
activeReservations: result.activeReservations as ComputedRef<
|
|
193
|
+
reservations: result.reservations as Ref<ReservationWithRelations[]>,
|
|
194
|
+
confirmedReservations: result.confirmedReservations as ComputedRef<ReservationWithRelations[]>,
|
|
195
|
+
activeReservations: result.activeReservations as ComputedRef<ReservationWithRelations[]>,
|
|
141
196
|
loading: result.loading,
|
|
142
197
|
error: result.error,
|
|
143
198
|
refetch: result.refetch,
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paginated Users Hooks
|
|
3
|
+
*
|
|
4
|
+
* Vue composables for fetching users with server-side pagination.
|
|
5
|
+
* Provides separate hooks for active and deleted users to enable
|
|
6
|
+
* efficient tab-based filtering in large datasets.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Ref, ComputedRef } from "vue";
|
|
10
|
+
import type { User } from "@htlkg/core/types";
|
|
11
|
+
import {
|
|
12
|
+
createPaginatedDataHook,
|
|
13
|
+
ACTIVE_FILTER,
|
|
14
|
+
DELETED_FILTER,
|
|
15
|
+
type PaginatedHookOptions,
|
|
16
|
+
type PaginationState,
|
|
17
|
+
} from "../createPaginatedDataHook";
|
|
18
|
+
|
|
19
|
+
/** Extended User type with computed fields */
|
|
20
|
+
export type UserWithRelations = User & {
|
|
21
|
+
accountName?: string;
|
|
22
|
+
brandNames?: string[];
|
|
23
|
+
deletedAt?: string;
|
|
24
|
+
deletedBy?: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface UsePaginatedUsersOptions extends PaginatedHookOptions {
|
|
28
|
+
/** Filter by brand ID */
|
|
29
|
+
brandId?: string;
|
|
30
|
+
/** Filter by account ID */
|
|
31
|
+
accountId?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UsePaginatedUsersReturn {
|
|
35
|
+
/** Reactive array of users */
|
|
36
|
+
users: Ref<UserWithRelations[]>;
|
|
37
|
+
/** Loading state (true during any fetch) */
|
|
38
|
+
loading: Ref<boolean>;
|
|
39
|
+
/** Loading state for initial fetch */
|
|
40
|
+
initialLoading: Ref<boolean>;
|
|
41
|
+
/** Loading state for loadMore */
|
|
42
|
+
loadingMore: Ref<boolean>;
|
|
43
|
+
/** Error state */
|
|
44
|
+
error: Ref<Error | null>;
|
|
45
|
+
/** Pagination state */
|
|
46
|
+
pagination: Ref<PaginationState>;
|
|
47
|
+
/** Whether there are more items to load */
|
|
48
|
+
hasMore: ComputedRef<boolean>;
|
|
49
|
+
/** Load next page of data */
|
|
50
|
+
loadMore: () => Promise<void>;
|
|
51
|
+
/** Refetch data (resets to first page) */
|
|
52
|
+
refetch: () => Promise<void>;
|
|
53
|
+
/** Reset data and pagination state */
|
|
54
|
+
reset: () => void;
|
|
55
|
+
/** Update search filter and refetch from server (searches ALL data) */
|
|
56
|
+
setSearchFilter: (filter: any) => Promise<void>;
|
|
57
|
+
/** Current search filter */
|
|
58
|
+
searchFilter: Ref<any>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build additional filter from hook options
|
|
63
|
+
*/
|
|
64
|
+
function buildFilter(options: UsePaginatedUsersOptions): any {
|
|
65
|
+
const conditions: any[] = [];
|
|
66
|
+
|
|
67
|
+
if (options.brandId) {
|
|
68
|
+
conditions.push({ brandIds: { contains: options.brandId } });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (options.accountId) {
|
|
72
|
+
conditions.push({ accountId: { eq: options.accountId } });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (options.filter && Object.keys(options.filter).length > 0) {
|
|
76
|
+
conditions.push(options.filter);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (conditions.length === 0) {
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
if (conditions.length === 1) {
|
|
83
|
+
return conditions[0];
|
|
84
|
+
}
|
|
85
|
+
return { and: conditions };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Transform user data
|
|
90
|
+
*/
|
|
91
|
+
function transformUser(user: any): UserWithRelations {
|
|
92
|
+
return {
|
|
93
|
+
...user,
|
|
94
|
+
accountName: user.account?.name || "",
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Selection set for user queries
|
|
100
|
+
*/
|
|
101
|
+
const USER_SELECTION_SET = [
|
|
102
|
+
"id",
|
|
103
|
+
"email",
|
|
104
|
+
"accountId",
|
|
105
|
+
"brandIds",
|
|
106
|
+
"roles",
|
|
107
|
+
"status",
|
|
108
|
+
"lastLogin",
|
|
109
|
+
"createdAt",
|
|
110
|
+
"deletedAt",
|
|
111
|
+
"deletedBy",
|
|
112
|
+
"account.name",
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Internal hook for active users
|
|
117
|
+
*/
|
|
118
|
+
const useActiveUsersInternal = createPaginatedDataHook<UserWithRelations, UsePaginatedUsersOptions>({
|
|
119
|
+
model: "User",
|
|
120
|
+
dataPropertyName: "users",
|
|
121
|
+
defaultPageSize: 25,
|
|
122
|
+
selectionSet: USER_SELECTION_SET,
|
|
123
|
+
transform: transformUser,
|
|
124
|
+
buildFilter,
|
|
125
|
+
baseFilter: ACTIVE_FILTER,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Internal hook for deleted users
|
|
130
|
+
*/
|
|
131
|
+
const useDeletedUsersInternal = createPaginatedDataHook<UserWithRelations, UsePaginatedUsersOptions>({
|
|
132
|
+
model: "User",
|
|
133
|
+
dataPropertyName: "users",
|
|
134
|
+
defaultPageSize: 25,
|
|
135
|
+
selectionSet: USER_SELECTION_SET,
|
|
136
|
+
transform: transformUser,
|
|
137
|
+
buildFilter,
|
|
138
|
+
baseFilter: DELETED_FILTER,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Composable for fetching active (non-deleted) users with pagination
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* import { useActiveUsers } from '@htlkg/data/hooks';
|
|
147
|
+
*
|
|
148
|
+
* const { users, loading, hasMore, loadMore, refetch } = useActiveUsers({
|
|
149
|
+
* pageSize: 25,
|
|
150
|
+
* });
|
|
151
|
+
*
|
|
152
|
+
* // Load more when user scrolls or clicks "Load More"
|
|
153
|
+
* async function onLoadMore() {
|
|
154
|
+
* await loadMore();
|
|
155
|
+
* }
|
|
156
|
+
* ```
|
|
157
|
+
*
|
|
158
|
+
* @example With filters
|
|
159
|
+
* ```typescript
|
|
160
|
+
* const { users, loading } = useActiveUsers({
|
|
161
|
+
* accountId: 'account-123',
|
|
162
|
+
* brandId: 'brand-456',
|
|
163
|
+
* pageSize: 50
|
|
164
|
+
* });
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
export function useActiveUsers(options: UsePaginatedUsersOptions = {}): UsePaginatedUsersReturn {
|
|
168
|
+
const result = useActiveUsersInternal(options);
|
|
169
|
+
return {
|
|
170
|
+
users: result.users as Ref<UserWithRelations[]>,
|
|
171
|
+
loading: result.loading,
|
|
172
|
+
initialLoading: result.initialLoading,
|
|
173
|
+
loadingMore: result.loadingMore,
|
|
174
|
+
error: result.error,
|
|
175
|
+
pagination: result.pagination,
|
|
176
|
+
hasMore: result.hasMore,
|
|
177
|
+
loadMore: result.loadMore,
|
|
178
|
+
refetch: result.refetch,
|
|
179
|
+
reset: result.reset,
|
|
180
|
+
setSearchFilter: result.setSearchFilter,
|
|
181
|
+
searchFilter: result.searchFilter,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Composable for fetching deleted users with pagination
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```typescript
|
|
190
|
+
* import { useDeletedUsers } from '@htlkg/data/hooks';
|
|
191
|
+
*
|
|
192
|
+
* const { users, loading, hasMore, loadMore, refetch } = useDeletedUsers({
|
|
193
|
+
* pageSize: 25,
|
|
194
|
+
* });
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export function useDeletedUsers(options: UsePaginatedUsersOptions = {}): UsePaginatedUsersReturn {
|
|
198
|
+
const result = useDeletedUsersInternal(options);
|
|
199
|
+
return {
|
|
200
|
+
users: result.users as Ref<UserWithRelations[]>,
|
|
201
|
+
loading: result.loading,
|
|
202
|
+
initialLoading: result.initialLoading,
|
|
203
|
+
loadingMore: result.loadingMore,
|
|
204
|
+
error: result.error,
|
|
205
|
+
pagination: result.pagination,
|
|
206
|
+
hasMore: result.hasMore,
|
|
207
|
+
loadMore: result.loadMore,
|
|
208
|
+
refetch: result.refetch,
|
|
209
|
+
reset: result.reset,
|
|
210
|
+
setSearchFilter: result.setSearchFilter,
|
|
211
|
+
searchFilter: result.searchFilter,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Ref } from "vue";
|
|
9
9
|
import type { User } from "@htlkg/core/types";
|
|
10
|
-
import { createDataHook, type BaseHookOptions } from "
|
|
10
|
+
import { createDataHook, type BaseHookOptions } from "../createDataHook";
|
|
11
11
|
|
|
12
12
|
export interface UseUsersOptions extends BaseHookOptions {
|
|
13
13
|
/** Filter criteria for users */
|