@htlkg/data 0.0.20 → 0.0.21

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.
@@ -0,0 +1,176 @@
1
+ /**
2
+ * useContacts Hook
3
+ *
4
+ * Vue composable for fetching and managing contact data with reactive state.
5
+ * Provides loading states, error handling, search, pagination, and refetch capabilities.
6
+ */
7
+
8
+ import type { Ref, ComputedRef } from "vue";
9
+ import type { Contact } from "@htlkg/core/types";
10
+ import { createDataHook, type BaseHookOptions } from "./createDataHook";
11
+
12
+ export interface UseContactsOptions extends BaseHookOptions {
13
+ /** Filter by brand ID */
14
+ brandId?: string;
15
+ /** Search query (searches email, firstName, lastName) */
16
+ search?: string;
17
+ /** Filter by GDPR consent */
18
+ gdprConsent?: boolean;
19
+ /** Filter by marketing opt-in */
20
+ marketingOptIn?: boolean;
21
+ /** Filter by tags (contact must have at least one of these tags) */
22
+ tags?: string[];
23
+ /** Pagination token for fetching next page */
24
+ nextToken?: string;
25
+ }
26
+
27
+ export interface UseContactsReturn {
28
+ /** Reactive array of contacts */
29
+ contacts: Ref<Contact[]>;
30
+ /** Computed array of contacts with GDPR consent */
31
+ consentedContacts: ComputedRef<Contact[]>;
32
+ /** Computed array of contacts opted-in for marketing */
33
+ marketingContacts: ComputedRef<Contact[]>;
34
+ /** Loading state */
35
+ loading: Ref<boolean>;
36
+ /** Error state */
37
+ error: Ref<Error | null>;
38
+ /** Refetch contacts */
39
+ refetch: () => Promise<void>;
40
+ }
41
+
42
+ /**
43
+ * Build filter from hook options
44
+ */
45
+ function buildFilter(options: UseContactsOptions): any {
46
+ const conditions: any[] = [];
47
+
48
+ // Filter by brand
49
+ if (options.brandId) {
50
+ conditions.push({ brandId: { eq: options.brandId } });
51
+ }
52
+
53
+ // Search across email, firstName, lastName
54
+ if (options.search) {
55
+ conditions.push({
56
+ or: [
57
+ { email: { contains: options.search } },
58
+ { firstName: { contains: options.search } },
59
+ { lastName: { contains: options.search } },
60
+ ],
61
+ });
62
+ }
63
+
64
+ // Filter by GDPR consent
65
+ if (options.gdprConsent !== undefined) {
66
+ conditions.push({ gdprConsent: { eq: options.gdprConsent } });
67
+ }
68
+
69
+ // Filter by marketing opt-in
70
+ if (options.marketingOptIn !== undefined) {
71
+ conditions.push({ marketingOptIn: { eq: options.marketingOptIn } });
72
+ }
73
+
74
+ // Filter by tags (contact must have at least one of these tags)
75
+ if (options.tags && options.tags.length > 0) {
76
+ const tagConditions = options.tags.map((tag) => ({
77
+ tags: { contains: tag },
78
+ }));
79
+ conditions.push({ or: tagConditions });
80
+ }
81
+
82
+ // Include any additional custom filter
83
+ if (options.filter) {
84
+ conditions.push(options.filter);
85
+ }
86
+
87
+ if (conditions.length === 0) {
88
+ return undefined;
89
+ }
90
+
91
+ if (conditions.length === 1) {
92
+ return conditions[0];
93
+ }
94
+
95
+ return { and: conditions };
96
+ }
97
+
98
+ /**
99
+ * Internal hook created by factory
100
+ */
101
+ const useContactsInternal = createDataHook<
102
+ Contact,
103
+ UseContactsOptions,
104
+ { consentedContacts: Contact[]; marketingContacts: Contact[] }
105
+ >({
106
+ model: "Contact",
107
+ dataPropertyName: "contacts",
108
+ buildFilter,
109
+ computedProperties: {
110
+ consentedContacts: (contacts) =>
111
+ contacts.filter((c) => c.gdprConsent === true),
112
+ marketingContacts: (contacts) =>
113
+ contacts.filter((c) => c.marketingOptIn === true),
114
+ },
115
+ });
116
+
117
+ /**
118
+ * Composable for fetching and managing contacts
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * import { useContacts } from '@htlkg/data/hooks';
123
+ *
124
+ * const { contacts, loading, error, refetch } = useContacts({
125
+ * brandId: 'brand-123',
126
+ * limit: 25
127
+ * });
128
+ * ```
129
+ *
130
+ * @example With search
131
+ * ```typescript
132
+ * const { contacts, loading } = useContacts({
133
+ * brandId: 'brand-123',
134
+ * search: 'john',
135
+ * limit: 25
136
+ * });
137
+ * ```
138
+ *
139
+ * @example With GDPR and marketing filters
140
+ * ```typescript
141
+ * const { contacts, marketingContacts } = useContacts({
142
+ * brandId: 'brand-123',
143
+ * gdprConsent: true,
144
+ * marketingOptIn: true
145
+ * });
146
+ * ```
147
+ *
148
+ * @example With computed properties
149
+ * ```typescript
150
+ * const { contacts, consentedContacts, marketingContacts } = useContacts({
151
+ * brandId: 'brand-123'
152
+ * });
153
+ *
154
+ * // consentedContacts - contacts with GDPR consent
155
+ * // marketingContacts - contacts opted-in for marketing
156
+ * ```
157
+ *
158
+ * @example With tag filtering
159
+ * ```typescript
160
+ * const { contacts } = useContacts({
161
+ * brandId: 'brand-123',
162
+ * tags: ['vip', 'returning']
163
+ * });
164
+ * ```
165
+ */
166
+ export function useContacts(options: UseContactsOptions = {}): UseContactsReturn {
167
+ const result = useContactsInternal(options);
168
+ return {
169
+ contacts: result.contacts as Ref<Contact[]>,
170
+ consentedContacts: result.consentedContacts as ComputedRef<Contact[]>,
171
+ marketingContacts: result.marketingContacts as ComputedRef<Contact[]>,
172
+ loading: result.loading,
173
+ error: result.error,
174
+ refetch: result.refetch,
175
+ };
176
+ }