@crmy/web 0.5.1
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/index.html +23 -0
- package/package.json +76 -0
- package/postcss.config.js +6 -0
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +13 -0
- package/public/site.webmanifest +1 -0
- package/src/App.tsx +158 -0
- package/src/api/client.ts +82 -0
- package/src/api/hooks.ts +689 -0
- package/src/assets/crmy-logo.png +0 -0
- package/src/components/CustomFields.tsx +240 -0
- package/src/components/NavLink.tsx +28 -0
- package/src/components/crm/AIFab.tsx +37 -0
- package/src/components/crm/AccountDrawer.tsx +372 -0
- package/src/components/crm/ActivityTimeline.tsx +115 -0
- package/src/components/crm/AssignmentDrawer.tsx +396 -0
- package/src/components/crm/BriefingPanel.tsx +217 -0
- package/src/components/crm/CommandPalette.tsx +254 -0
- package/src/components/crm/ContactAvatar.tsx +49 -0
- package/src/components/crm/ContactDrawer.tsx +438 -0
- package/src/components/crm/ContextPanel.tsx +200 -0
- package/src/components/crm/CrmWidgets.tsx +417 -0
- package/src/components/crm/DrawerShell.tsx +77 -0
- package/src/components/crm/ListToolbar.tsx +252 -0
- package/src/components/crm/OpportunityDrawer.tsx +372 -0
- package/src/components/crm/PaginationBar.tsx +111 -0
- package/src/components/crm/QuickAddDrawer.tsx +652 -0
- package/src/components/crm/ShortcutsOverlay.tsx +65 -0
- package/src/components/crm/UseCaseDrawer.tsx +454 -0
- package/src/components/layout/MobileNav.tsx +49 -0
- package/src/components/layout/Sidebar.tsx +157 -0
- package/src/components/layout/TopBar.tsx +54 -0
- package/src/components/settings/ActorsSettings.tsx +1190 -0
- package/src/components/ui/accordion.tsx +52 -0
- package/src/components/ui/alert-dialog.tsx +104 -0
- package/src/components/ui/alert.tsx +43 -0
- package/src/components/ui/aspect-ratio.tsx +5 -0
- package/src/components/ui/avatar.tsx +38 -0
- package/src/components/ui/badge.tsx +29 -0
- package/src/components/ui/breadcrumb.tsx +90 -0
- package/src/components/ui/button.tsx +47 -0
- package/src/components/ui/calendar.tsx +54 -0
- package/src/components/ui/card.tsx +43 -0
- package/src/components/ui/carousel.tsx +224 -0
- package/src/components/ui/chart.tsx +303 -0
- package/src/components/ui/checkbox.tsx +26 -0
- package/src/components/ui/collapsible.tsx +9 -0
- package/src/components/ui/command.tsx +132 -0
- package/src/components/ui/context-menu.tsx +178 -0
- package/src/components/ui/date-picker.tsx +313 -0
- package/src/components/ui/dialog.tsx +95 -0
- package/src/components/ui/drawer.tsx +87 -0
- package/src/components/ui/dropdown-menu.tsx +179 -0
- package/src/components/ui/form.tsx +129 -0
- package/src/components/ui/hover-card.tsx +27 -0
- package/src/components/ui/input-otp.tsx +61 -0
- package/src/components/ui/input.tsx +22 -0
- package/src/components/ui/label.tsx +17 -0
- package/src/components/ui/menubar.tsx +207 -0
- package/src/components/ui/navigation-menu.tsx +120 -0
- package/src/components/ui/pagination.tsx +81 -0
- package/src/components/ui/popover.tsx +29 -0
- package/src/components/ui/progress.tsx +23 -0
- package/src/components/ui/radio-group.tsx +36 -0
- package/src/components/ui/resizable.tsx +37 -0
- package/src/components/ui/scroll-area.tsx +38 -0
- package/src/components/ui/select.tsx +143 -0
- package/src/components/ui/separator.tsx +20 -0
- package/src/components/ui/sheet.tsx +107 -0
- package/src/components/ui/sidebar.tsx +637 -0
- package/src/components/ui/skeleton.tsx +7 -0
- package/src/components/ui/slider.tsx +23 -0
- package/src/components/ui/sonner.tsx +24 -0
- package/src/components/ui/switch.tsx +27 -0
- package/src/components/ui/table.tsx +72 -0
- package/src/components/ui/tabs.tsx +53 -0
- package/src/components/ui/textarea.tsx +21 -0
- package/src/components/ui/toast.tsx +111 -0
- package/src/components/ui/toaster.tsx +24 -0
- package/src/components/ui/toggle-group.tsx +49 -0
- package/src/components/ui/toggle.tsx +37 -0
- package/src/components/ui/tooltip.tsx +28 -0
- package/src/components/ui/use-toast.ts +1 -0
- package/src/components/ui/utils.ts +9 -0
- package/src/contexts/AgentSettingsContext.tsx +24 -0
- package/src/hooks/use-mobile.tsx +19 -0
- package/src/hooks/use-toast.ts +186 -0
- package/src/hooks/useKeyboardShortcuts.ts +95 -0
- package/src/hooks/useTheme.ts +24 -0
- package/src/index.css +245 -0
- package/src/lib/entityColors.ts +18 -0
- package/src/lib/stageConfig.ts +32 -0
- package/src/lib/utils.ts +6 -0
- package/src/main.tsx +25 -0
- package/src/pages/Accounts.tsx +205 -0
- package/src/pages/Activities.tsx +251 -0
- package/src/pages/Agent.tsx +237 -0
- package/src/pages/AgentSettings.tsx +544 -0
- package/src/pages/Assignments.tsx +750 -0
- package/src/pages/Contacts.tsx +200 -0
- package/src/pages/Dashboard.tsx +143 -0
- package/src/pages/Inbox.tsx +615 -0
- package/src/pages/NotFound.tsx +24 -0
- package/src/pages/Opportunities.tsx +386 -0
- package/src/pages/SearchResults.tsx +49 -0
- package/src/pages/Settings.tsx +1884 -0
- package/src/pages/UseCases.tsx +396 -0
- package/src/pages/auth/Login.tsx +261 -0
- package/src/pages/hitl/HITL.tsx +101 -0
- package/src/store/appStore.ts +103 -0
- package/src/vite-env.d.ts +14 -0
- package/tailwind.config.js +121 -0
- package/tsconfig.json +24 -0
- package/vite.config.ts +27 -0
package/src/api/hooks.ts
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
// Copyright 2026 CRMy Contributors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
5
|
+
import { api } from './client';
|
|
6
|
+
|
|
7
|
+
// Generic list hook with pagination
|
|
8
|
+
function useList<T>(key: string, path: string, params?: Record<string, string | number | boolean | undefined>) {
|
|
9
|
+
const query = new URLSearchParams();
|
|
10
|
+
if (params) {
|
|
11
|
+
Object.entries(params).forEach(([k, v]) => {
|
|
12
|
+
if (v !== undefined && v !== '') query.set(k, String(v));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const url = query.toString() ? `${path}?${query}` : path;
|
|
16
|
+
return useQuery<{ data: T[]; next_cursor?: string; total: number }>({
|
|
17
|
+
queryKey: [key, params],
|
|
18
|
+
queryFn: () => api.get(url),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Contacts
|
|
23
|
+
export function useContacts(params?: { q?: string; limit?: number; cursor?: string }) {
|
|
24
|
+
return useList('contacts', 'contacts', params);
|
|
25
|
+
}
|
|
26
|
+
export function useContact(id: string) {
|
|
27
|
+
return useQuery({ queryKey: ['contact', id], queryFn: () => api.get(`contacts/${id}`), enabled: !!id });
|
|
28
|
+
}
|
|
29
|
+
export function useCreateContact() {
|
|
30
|
+
const qc = useQueryClient();
|
|
31
|
+
return useMutation({
|
|
32
|
+
mutationFn: (data: Record<string, unknown>) => api.post('contacts', data),
|
|
33
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['contacts'] }),
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function useUpdateContact(id: string) {
|
|
37
|
+
const qc = useQueryClient();
|
|
38
|
+
return useMutation({
|
|
39
|
+
mutationFn: (data: Record<string, unknown>) => api.patch(`contacts/${id}`, data),
|
|
40
|
+
onSuccess: () => {
|
|
41
|
+
qc.invalidateQueries({ queryKey: ['contact', id] });
|
|
42
|
+
qc.invalidateQueries({ queryKey: ['contacts'] });
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function useDeleteContact(id: string) {
|
|
47
|
+
const qc = useQueryClient();
|
|
48
|
+
return useMutation({
|
|
49
|
+
mutationFn: () => api.delete(`contacts/${id}`),
|
|
50
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['contacts'] }),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Accounts
|
|
55
|
+
export function useAccounts(params?: { q?: string; limit?: number; cursor?: string }) {
|
|
56
|
+
return useList('accounts', 'accounts', params);
|
|
57
|
+
}
|
|
58
|
+
export function useAccount(id: string) {
|
|
59
|
+
return useQuery({ queryKey: ['account', id], queryFn: () => api.get(`accounts/${id}`), enabled: !!id });
|
|
60
|
+
}
|
|
61
|
+
export function useCreateAccount() {
|
|
62
|
+
const qc = useQueryClient();
|
|
63
|
+
return useMutation({
|
|
64
|
+
mutationFn: (data: Record<string, unknown>) => api.post('accounts', data),
|
|
65
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['accounts'] }),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export function useUpdateAccount(id: string) {
|
|
69
|
+
const qc = useQueryClient();
|
|
70
|
+
return useMutation({
|
|
71
|
+
mutationFn: (data: Record<string, unknown>) => api.patch(`accounts/${id}`, data),
|
|
72
|
+
onSuccess: () => {
|
|
73
|
+
qc.invalidateQueries({ queryKey: ['account', id] });
|
|
74
|
+
qc.invalidateQueries({ queryKey: ['accounts'] });
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export function useDeleteAccount(id: string) {
|
|
79
|
+
const qc = useQueryClient();
|
|
80
|
+
return useMutation({
|
|
81
|
+
mutationFn: () => api.delete(`accounts/${id}`),
|
|
82
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['accounts'] }),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Opportunities
|
|
87
|
+
export function useOpportunities(params?: { q?: string; stage?: string; limit?: number; cursor?: string }) {
|
|
88
|
+
return useList('opportunities', 'opportunities', params);
|
|
89
|
+
}
|
|
90
|
+
export function useOpportunity(id: string) {
|
|
91
|
+
return useQuery({ queryKey: ['opportunity', id], queryFn: () => api.get(`opportunities/${id}`), enabled: !!id });
|
|
92
|
+
}
|
|
93
|
+
export function useCreateOpportunity() {
|
|
94
|
+
const qc = useQueryClient();
|
|
95
|
+
return useMutation({
|
|
96
|
+
mutationFn: (data: Record<string, unknown>) => api.post('opportunities', data),
|
|
97
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['opportunities'] }),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export function useUpdateOpportunity(id: string) {
|
|
101
|
+
const qc = useQueryClient();
|
|
102
|
+
return useMutation({
|
|
103
|
+
mutationFn: (data: Record<string, unknown>) => api.patch(`opportunities/${id}`, data),
|
|
104
|
+
onSuccess: () => {
|
|
105
|
+
qc.invalidateQueries({ queryKey: ['opportunity', id] });
|
|
106
|
+
qc.invalidateQueries({ queryKey: ['opportunities'] });
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
export function useDeleteOpportunity(id: string) {
|
|
111
|
+
const qc = useQueryClient();
|
|
112
|
+
return useMutation({
|
|
113
|
+
mutationFn: () => api.delete(`opportunities/${id}`),
|
|
114
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['opportunities'] }),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Use Cases
|
|
119
|
+
export function useUseCases(params?: { account_id?: string; stage?: string; q?: string; limit?: number; cursor?: string }) {
|
|
120
|
+
return useList('use-cases', 'use-cases', params);
|
|
121
|
+
}
|
|
122
|
+
export function useUseCase(id: string) {
|
|
123
|
+
return useQuery({ queryKey: ['use-case', id], queryFn: () => api.get(`use-cases/${id}`), enabled: !!id });
|
|
124
|
+
}
|
|
125
|
+
export function useCreateUseCase() {
|
|
126
|
+
const qc = useQueryClient();
|
|
127
|
+
return useMutation({
|
|
128
|
+
mutationFn: (data: Record<string, unknown>) => api.post('use-cases', data),
|
|
129
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['use-cases'] }),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
export function useUpdateUseCase(id: string) {
|
|
133
|
+
const qc = useQueryClient();
|
|
134
|
+
return useMutation({
|
|
135
|
+
mutationFn: (data: Record<string, unknown>) => api.patch(`use-cases/${id}`, data),
|
|
136
|
+
onSuccess: () => {
|
|
137
|
+
qc.invalidateQueries({ queryKey: ['use-case', id] });
|
|
138
|
+
qc.invalidateQueries({ queryKey: ['use-cases'] });
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
export function useDeleteUseCase(id: string) {
|
|
143
|
+
const qc = useQueryClient();
|
|
144
|
+
return useMutation({
|
|
145
|
+
mutationFn: () => api.delete(`use-cases/${id}`),
|
|
146
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['use-cases'] }),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
export function useAdvanceUseCaseStage(id: string) {
|
|
150
|
+
const qc = useQueryClient();
|
|
151
|
+
return useMutation({
|
|
152
|
+
mutationFn: (data: { stage: string; note?: string; attributed_arr?: number }) =>
|
|
153
|
+
api.post(`use-cases/${id}/stage`, data),
|
|
154
|
+
onSuccess: () => {
|
|
155
|
+
qc.invalidateQueries({ queryKey: ['use-case', id] });
|
|
156
|
+
qc.invalidateQueries({ queryKey: ['use-cases'] });
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
export function useSetConsumption(id: string) {
|
|
161
|
+
const qc = useQueryClient();
|
|
162
|
+
return useMutation({
|
|
163
|
+
mutationFn: (data: Record<string, unknown>) => api.post(`use-cases/${id}/consumption`, data),
|
|
164
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['use-case', id] }),
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
export function useSetHealth(id: string) {
|
|
168
|
+
const qc = useQueryClient();
|
|
169
|
+
return useMutation({
|
|
170
|
+
mutationFn: (data: { health_score: number; health_note: string }) => api.post(`use-cases/${id}/health`, data),
|
|
171
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['use-case', id] }),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
export function useUseCaseContacts(id: string) {
|
|
175
|
+
return useQuery({
|
|
176
|
+
queryKey: ['use-case-contacts', id],
|
|
177
|
+
queryFn: () => api.get<{ data: Array<{ contact_id: string; role?: string; contact?: Record<string, unknown> }> }>(`use-cases/${id}/contacts`),
|
|
178
|
+
enabled: !!id,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
export function useAddUseCaseContact(id: string) {
|
|
182
|
+
const qc = useQueryClient();
|
|
183
|
+
return useMutation({
|
|
184
|
+
mutationFn: (data: { contact_id: string; role?: string }) => api.post(`use-cases/${id}/contacts`, data),
|
|
185
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['use-case-contacts', id] }),
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
export function useRemoveUseCaseContact(useCaseId: string) {
|
|
189
|
+
const qc = useQueryClient();
|
|
190
|
+
return useMutation({
|
|
191
|
+
mutationFn: (contactId: string) => api.delete(`use-cases/${useCaseId}/contacts/${contactId}`),
|
|
192
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['use-case-contacts', useCaseId] }),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
export function useUseCaseTimeline(id: string) {
|
|
196
|
+
return useQuery({
|
|
197
|
+
queryKey: ['use-case-timeline', id],
|
|
198
|
+
queryFn: () => api.get(`use-cases/${id}/timeline`),
|
|
199
|
+
enabled: !!id,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Activities
|
|
204
|
+
export function useActivities(params?: {
|
|
205
|
+
contact_id?: string;
|
|
206
|
+
account_id?: string;
|
|
207
|
+
type?: string;
|
|
208
|
+
subject_type?: string;
|
|
209
|
+
subject_id?: string;
|
|
210
|
+
performed_by?: string;
|
|
211
|
+
outcome?: string;
|
|
212
|
+
limit?: number;
|
|
213
|
+
}) {
|
|
214
|
+
return useList('activities', 'activities', params);
|
|
215
|
+
}
|
|
216
|
+
export function useCreateActivity() {
|
|
217
|
+
const qc = useQueryClient();
|
|
218
|
+
return useMutation({
|
|
219
|
+
mutationFn: (data: Record<string, unknown>) => api.post('activities', data),
|
|
220
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['activities'] }),
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Analytics
|
|
225
|
+
export function usePipelineSummary(params?: { owner_id?: string }) {
|
|
226
|
+
return useQuery({
|
|
227
|
+
queryKey: ['analytics-pipeline', params],
|
|
228
|
+
queryFn: () => api.get(`analytics/pipeline${params?.owner_id ? `?owner_id=${params.owner_id}` : ''}`),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
export function usePipelineForecast() {
|
|
232
|
+
return useQuery({
|
|
233
|
+
queryKey: ['analytics-forecast'],
|
|
234
|
+
queryFn: () => api.get('analytics/forecast'),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
export function useUseCaseAnalytics(params?: { account_id?: string; group_by?: string }) {
|
|
238
|
+
const query = new URLSearchParams();
|
|
239
|
+
if (params?.account_id) query.set('account_id', params.account_id);
|
|
240
|
+
if (params?.group_by) query.set('group_by', params.group_by);
|
|
241
|
+
const url = query.toString() ? `analytics/use-cases?${query}` : 'analytics/use-cases';
|
|
242
|
+
return useQuery({ queryKey: ['analytics-use-cases', params], queryFn: () => api.get(url) });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// HITL
|
|
246
|
+
export function useHITLRequests() {
|
|
247
|
+
return useQuery({
|
|
248
|
+
queryKey: ['hitl'],
|
|
249
|
+
queryFn: () => api.get('hitl'),
|
|
250
|
+
refetchInterval: 10_000,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
export function useResolveHITL() {
|
|
254
|
+
const qc = useQueryClient();
|
|
255
|
+
return useMutation({
|
|
256
|
+
mutationFn: ({ id, ...data }: { id: string; status: string; note?: string }) =>
|
|
257
|
+
api.post(`hitl/${id}/resolve`, data),
|
|
258
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['hitl'] }),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Webhooks
|
|
263
|
+
export function useWebhooks() {
|
|
264
|
+
return useList('webhooks', 'webhooks');
|
|
265
|
+
}
|
|
266
|
+
export function useCreateWebhook() {
|
|
267
|
+
const qc = useQueryClient();
|
|
268
|
+
return useMutation({
|
|
269
|
+
mutationFn: (data: Record<string, unknown>) => api.post('webhooks', data),
|
|
270
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['webhooks'] }),
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
export function useDeleteWebhook() {
|
|
274
|
+
const qc = useQueryClient();
|
|
275
|
+
return useMutation({
|
|
276
|
+
mutationFn: (id: string) => api.delete(`webhooks/${id}`),
|
|
277
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['webhooks'] }),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Custom Fields
|
|
282
|
+
export function useCustomFields(objectType: string) {
|
|
283
|
+
return useQuery({
|
|
284
|
+
queryKey: ['custom-fields', objectType],
|
|
285
|
+
queryFn: () => api.get(`custom-fields?object_type=${objectType}`),
|
|
286
|
+
enabled: !!objectType,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
export function useCreateCustomField() {
|
|
290
|
+
const qc = useQueryClient();
|
|
291
|
+
return useMutation({
|
|
292
|
+
mutationFn: (data: Record<string, unknown>) => api.post('custom-fields', data),
|
|
293
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['custom-fields'] }),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
export function useUpdateCustomField() {
|
|
297
|
+
const qc = useQueryClient();
|
|
298
|
+
return useMutation({
|
|
299
|
+
mutationFn: ({ id, ...data }: { id: string } & Record<string, unknown>) =>
|
|
300
|
+
api.patch(`custom-fields/${id}`, data),
|
|
301
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['custom-fields'] }),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
export function useDeleteCustomField() {
|
|
305
|
+
const qc = useQueryClient();
|
|
306
|
+
return useMutation({
|
|
307
|
+
mutationFn: (id: string) => api.delete(`custom-fields/${id}`),
|
|
308
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['custom-fields'] }),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Events
|
|
313
|
+
export function useEvents(params?: { object_id?: string; limit?: number }) {
|
|
314
|
+
return useList('events', 'events', params);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Search
|
|
318
|
+
export function useSearch(q: string) {
|
|
319
|
+
return useQuery({
|
|
320
|
+
queryKey: ['search', q],
|
|
321
|
+
queryFn: () => api.get(`search?q=${encodeURIComponent(q)}`),
|
|
322
|
+
enabled: q.length >= 2,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Emails
|
|
327
|
+
export function useEmails(params?: { contact_id?: string; status?: string; limit?: number }) {
|
|
328
|
+
return useList('emails', 'emails', params);
|
|
329
|
+
}
|
|
330
|
+
export function useCreateEmail() {
|
|
331
|
+
const qc = useQueryClient();
|
|
332
|
+
return useMutation({
|
|
333
|
+
mutationFn: (data: Record<string, unknown>) => api.post('emails', data),
|
|
334
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['emails'] }),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Admin: User Management
|
|
339
|
+
type AdminUser = { id: string; email: string; name: string; role: string; created_at: string; updated_at?: string };
|
|
340
|
+
|
|
341
|
+
export function useUsers() {
|
|
342
|
+
return useQuery({
|
|
343
|
+
queryKey: ['admin-users'],
|
|
344
|
+
queryFn: () => api.get<{ data: AdminUser[] }>('admin/users'),
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
export function useCreateUser() {
|
|
348
|
+
const qc = useQueryClient();
|
|
349
|
+
return useMutation({
|
|
350
|
+
mutationFn: (data: { name: string; email: string; password: string; role: string }) =>
|
|
351
|
+
api.post<AdminUser>('admin/users', data),
|
|
352
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['admin-users'] }),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
export function useUpdateUser() {
|
|
356
|
+
const qc = useQueryClient();
|
|
357
|
+
return useMutation({
|
|
358
|
+
mutationFn: ({ id, ...data }: { id: string; name?: string; email?: string; role?: string; password?: string }) =>
|
|
359
|
+
api.patch<AdminUser>(`admin/users/${id}`, data),
|
|
360
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['admin-users'] }),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
export function useDeleteUser() {
|
|
364
|
+
const qc = useQueryClient();
|
|
365
|
+
return useMutation({
|
|
366
|
+
mutationFn: (id: string) => api.delete(`admin/users/${id}`),
|
|
367
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['admin-users'] }),
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Admin: Database Config
|
|
372
|
+
export function useDbConfig() {
|
|
373
|
+
return useQuery({
|
|
374
|
+
queryKey: ['admin-db-config'],
|
|
375
|
+
queryFn: () => api.get<{ host: string; port: string; database: string; user: string; ssl: string | null }>('admin/db-config'),
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
export function useTestDbConfig() {
|
|
379
|
+
return useMutation({
|
|
380
|
+
mutationFn: (connection_string: string) => api.post<{ success: boolean }>('admin/db-config/test', { connection_string }),
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
export function useSaveDbConfig() {
|
|
384
|
+
const qc = useQueryClient();
|
|
385
|
+
return useMutation({
|
|
386
|
+
mutationFn: (connection_string: string) =>
|
|
387
|
+
api.patch<{ success: boolean; message: string }>('admin/db-config', { connection_string }),
|
|
388
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['admin-db-config'] }),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Notes
|
|
393
|
+
export function useNotes(params: { object_type: string; object_id: string }) {
|
|
394
|
+
return useList('notes', 'notes', params);
|
|
395
|
+
}
|
|
396
|
+
export function useCreateNote() {
|
|
397
|
+
const qc = useQueryClient();
|
|
398
|
+
return useMutation({
|
|
399
|
+
mutationFn: (data: Record<string, unknown>) => api.post('notes', data),
|
|
400
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['notes'] }),
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// API Keys
|
|
405
|
+
export function useApiKeys(actorId?: string) {
|
|
406
|
+
const params = actorId ? `?actor_id=${actorId}` : '';
|
|
407
|
+
return useQuery({
|
|
408
|
+
queryKey: ['api-keys', actorId],
|
|
409
|
+
queryFn: () => api.get<{ data: Array<{ id: string; label: string; scopes: string[]; actor_id?: string; actor_name?: string; actor_type?: string; created_at: string; last_used_at?: string }> }>(`/auth/api-keys${params}`),
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
export function useCreateApiKey() {
|
|
413
|
+
const qc = useQueryClient();
|
|
414
|
+
return useMutation({
|
|
415
|
+
mutationFn: (data: { label: string; scopes: string[]; actor_id?: string }) =>
|
|
416
|
+
api.post<{ id: string; key: string; label: string }>('/auth/api-keys', data),
|
|
417
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['api-keys'] }),
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
export function useUpdateApiKey() {
|
|
421
|
+
const qc = useQueryClient();
|
|
422
|
+
return useMutation({
|
|
423
|
+
mutationFn: ({ id, ...data }: { id: string; label?: string; scopes?: string[]; actor_id?: string | null; expires_at?: string | null }) =>
|
|
424
|
+
api.patch(`/auth/api-keys/${id}`, data),
|
|
425
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['api-keys'] }),
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
export function useRevokeApiKey() {
|
|
429
|
+
const qc = useQueryClient();
|
|
430
|
+
return useMutation({
|
|
431
|
+
mutationFn: (id: string) => api.delete(`/auth/api-keys/${id}`),
|
|
432
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['api-keys'] }),
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Profile (self-service)
|
|
437
|
+
export function useUpdateProfile() {
|
|
438
|
+
return useMutation({
|
|
439
|
+
mutationFn: (data: { name?: string; email?: string; current_password?: string; new_password?: string }) =>
|
|
440
|
+
api.patch<{ id: string; email: string; name: string; role: string }>('/auth/profile', data),
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// -- Context Engine hooks (v0.4) --
|
|
445
|
+
|
|
446
|
+
// Actors
|
|
447
|
+
export function useActors(params?: { actor_type?: string; q?: string; is_active?: boolean; limit?: number }) {
|
|
448
|
+
return useList('actors', 'actors', params);
|
|
449
|
+
}
|
|
450
|
+
export function useActor(id: string) {
|
|
451
|
+
return useQuery({ queryKey: ['actor', id], queryFn: () => api.get(`actors/${id}`), enabled: !!id });
|
|
452
|
+
}
|
|
453
|
+
export function useCreateActor() {
|
|
454
|
+
const qc = useQueryClient();
|
|
455
|
+
return useMutation({
|
|
456
|
+
mutationFn: (data: Record<string, unknown>) => api.post('actors', data),
|
|
457
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['actors'] }),
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
export function useUpdateActor() {
|
|
461
|
+
const qc = useQueryClient();
|
|
462
|
+
return useMutation({
|
|
463
|
+
mutationFn: ({ id, ...patch }: { id: string; [key: string]: unknown }) =>
|
|
464
|
+
api.patch(`actors/${id}`, { patch }),
|
|
465
|
+
onSuccess: () => {
|
|
466
|
+
qc.invalidateQueries({ queryKey: ['actors'] });
|
|
467
|
+
qc.invalidateQueries({ queryKey: ['actor'] });
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
export function useWhoAmI() {
|
|
472
|
+
return useQuery({
|
|
473
|
+
queryKey: ['actor-whoami'],
|
|
474
|
+
queryFn: () => api.get('actors/whoami'),
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Assignments
|
|
479
|
+
export function useAssignments(params?: {
|
|
480
|
+
assigned_to?: string;
|
|
481
|
+
assigned_by?: string;
|
|
482
|
+
status?: string;
|
|
483
|
+
priority?: string;
|
|
484
|
+
subject_type?: string;
|
|
485
|
+
subject_id?: string;
|
|
486
|
+
limit?: number;
|
|
487
|
+
}) {
|
|
488
|
+
return useList('assignments', 'assignments', params);
|
|
489
|
+
}
|
|
490
|
+
export function useAssignment(id: string) {
|
|
491
|
+
return useQuery({ queryKey: ['assignment', id], queryFn: () => api.get(`assignments/${id}`), enabled: !!id });
|
|
492
|
+
}
|
|
493
|
+
export function useCreateAssignment() {
|
|
494
|
+
const qc = useQueryClient();
|
|
495
|
+
return useMutation({
|
|
496
|
+
mutationFn: (data: Record<string, unknown>) => api.post('assignments', data),
|
|
497
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['assignments'] }),
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
export function useUpdateAssignment(id: string) {
|
|
501
|
+
const qc = useQueryClient();
|
|
502
|
+
return useMutation({
|
|
503
|
+
mutationFn: (data: Record<string, unknown>) => api.patch(`assignments/${id}`, data),
|
|
504
|
+
onSuccess: () => {
|
|
505
|
+
qc.invalidateQueries({ queryKey: ['assignment', id] });
|
|
506
|
+
qc.invalidateQueries({ queryKey: ['assignments'] });
|
|
507
|
+
},
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
export function useAcceptAssignment() {
|
|
511
|
+
const qc = useQueryClient();
|
|
512
|
+
return useMutation({
|
|
513
|
+
mutationFn: (id: string) => api.post(`assignments/${id}/accept`, {}),
|
|
514
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['assignments'] }),
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
export function useCompleteAssignment() {
|
|
518
|
+
const qc = useQueryClient();
|
|
519
|
+
return useMutation({
|
|
520
|
+
mutationFn: ({ id, ...data }: { id: string; completed_by_activity_id?: string }) =>
|
|
521
|
+
api.post(`assignments/${id}/complete`, data),
|
|
522
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['assignments'] }),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
export function useDeclineAssignment() {
|
|
526
|
+
const qc = useQueryClient();
|
|
527
|
+
return useMutation({
|
|
528
|
+
mutationFn: ({ id, ...data }: { id: string; reason?: string }) =>
|
|
529
|
+
api.post(`assignments/${id}/decline`, data),
|
|
530
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['assignments'] }),
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Context Entries
|
|
535
|
+
export function useContextEntries(params?: {
|
|
536
|
+
subject_type?: string;
|
|
537
|
+
subject_id?: string;
|
|
538
|
+
context_type?: string;
|
|
539
|
+
is_current?: boolean;
|
|
540
|
+
limit?: number;
|
|
541
|
+
}) {
|
|
542
|
+
return useList('context-entries', 'context', params);
|
|
543
|
+
}
|
|
544
|
+
export function useContextEntry(id: string) {
|
|
545
|
+
return useQuery({ queryKey: ['context-entry', id], queryFn: () => api.get(`context/${id}`), enabled: !!id });
|
|
546
|
+
}
|
|
547
|
+
export function useCreateContextEntry() {
|
|
548
|
+
const qc = useQueryClient();
|
|
549
|
+
return useMutation({
|
|
550
|
+
mutationFn: (data: Record<string, unknown>) => api.post('context', data),
|
|
551
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['context-entries'] }),
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
export function useSupersedeContextEntry() {
|
|
555
|
+
const qc = useQueryClient();
|
|
556
|
+
return useMutation({
|
|
557
|
+
mutationFn: ({ id, ...data }: { id: string; body: string; title?: string }) =>
|
|
558
|
+
api.post(`context/${id}/supersede`, data),
|
|
559
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['context-entries'] }),
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
export function useContextSearch(query: string, params?: { subject_type?: string; subject_id?: string; context_type?: string; tag?: string; current_only?: boolean; limit?: number }) {
|
|
563
|
+
const searchParams = new URLSearchParams();
|
|
564
|
+
searchParams.set('query', query);
|
|
565
|
+
if (params) {
|
|
566
|
+
Object.entries(params).forEach(([k, v]) => {
|
|
567
|
+
if (v !== undefined && v !== '') searchParams.set(k, String(v));
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return useQuery({
|
|
571
|
+
queryKey: ['context-search', query, params],
|
|
572
|
+
queryFn: () => api.get(`context/search?${searchParams}`),
|
|
573
|
+
enabled: query.length >= 2,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
export function useReviewContextEntry() {
|
|
577
|
+
const qc = useQueryClient();
|
|
578
|
+
return useMutation({
|
|
579
|
+
mutationFn: (id: string) => api.post(`context/${id}/review`, {}),
|
|
580
|
+
onSuccess: () => {
|
|
581
|
+
qc.invalidateQueries({ queryKey: ['context-entries'] });
|
|
582
|
+
qc.invalidateQueries({ queryKey: ['context-stale'] });
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
export function useStaleContextEntries(params?: { subject_type?: string; subject_id?: string; limit?: number }) {
|
|
587
|
+
return useList('context-stale', 'context/stale', params);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Assignment actions (v0.5)
|
|
591
|
+
export function useStartAssignment() {
|
|
592
|
+
const qc = useQueryClient();
|
|
593
|
+
return useMutation({
|
|
594
|
+
mutationFn: (id: string) => api.post(`assignments/${id}/start`, {}),
|
|
595
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['assignments'] }),
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
export function useBlockAssignment() {
|
|
599
|
+
const qc = useQueryClient();
|
|
600
|
+
return useMutation({
|
|
601
|
+
mutationFn: ({ id, reason }: { id: string; reason?: string }) =>
|
|
602
|
+
api.post(`assignments/${id}/block`, { reason }),
|
|
603
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['assignments'] }),
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
export function useCancelAssignment() {
|
|
607
|
+
const qc = useQueryClient();
|
|
608
|
+
return useMutation({
|
|
609
|
+
mutationFn: ({ id, reason }: { id: string; reason?: string }) =>
|
|
610
|
+
api.post(`assignments/${id}/cancel`, { reason }),
|
|
611
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['assignments'] }),
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Activity Type Registry
|
|
616
|
+
export function useActivityTypes(params?: { category?: string }) {
|
|
617
|
+
return useList('activity-types', 'activity-types', params);
|
|
618
|
+
}
|
|
619
|
+
export function useCreateActivityType() {
|
|
620
|
+
const qc = useQueryClient();
|
|
621
|
+
return useMutation({
|
|
622
|
+
mutationFn: (data: { type_name: string; label: string; category: string; description?: string }) =>
|
|
623
|
+
api.post('activity-types', data),
|
|
624
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['activity-types'] }),
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
export function useDeleteActivityType() {
|
|
628
|
+
const qc = useQueryClient();
|
|
629
|
+
return useMutation({
|
|
630
|
+
mutationFn: (typeName: string) => api.delete(`activity-types/${typeName}`),
|
|
631
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['activity-types'] }),
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Context Type Registry
|
|
636
|
+
export function useContextTypes() {
|
|
637
|
+
return useList('context-types', 'context-types');
|
|
638
|
+
}
|
|
639
|
+
export function useCreateContextType() {
|
|
640
|
+
const qc = useQueryClient();
|
|
641
|
+
return useMutation({
|
|
642
|
+
mutationFn: (data: { type_name: string; label: string; description?: string }) =>
|
|
643
|
+
api.post('context-types', data),
|
|
644
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['context-types'] }),
|
|
645
|
+
});
|
|
646
|
+
}
|
|
647
|
+
export function useDeleteContextType() {
|
|
648
|
+
const qc = useQueryClient();
|
|
649
|
+
return useMutation({
|
|
650
|
+
mutationFn: (typeName: string) => api.delete(`context-types/${typeName}`),
|
|
651
|
+
onSuccess: () => qc.invalidateQueries({ queryKey: ['context-types'] }),
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Inbox counts (HITL pending + my active assignments) — used by nav badge
|
|
656
|
+
export function useInboxCounts() {
|
|
657
|
+
const { data: whoami } = useWhoAmI() as any;
|
|
658
|
+
const myActorId: string | undefined = whoami?.actor_id;
|
|
659
|
+
const hitlQ = useHITLRequests();
|
|
660
|
+
const assignQ = useQuery<{ data: any[]; total: number }>({
|
|
661
|
+
queryKey: ['inbox-assignments', myActorId],
|
|
662
|
+
queryFn: () => api.get(`assignments?assigned_to=${encodeURIComponent(myActorId!)}&limit=200`),
|
|
663
|
+
enabled: !!myActorId,
|
|
664
|
+
refetchInterval: 30_000,
|
|
665
|
+
});
|
|
666
|
+
const hitlCount = ((hitlQ.data as any)?.data ?? []).filter((r: any) => r.status === 'pending').length;
|
|
667
|
+
const assignCount = ((assignQ.data as any)?.assignments ?? []).filter((a: any) =>
|
|
668
|
+
['pending', 'accepted', 'in_progress', 'blocked'].includes(a.status)
|
|
669
|
+
).length;
|
|
670
|
+
return { total: hitlCount + assignCount, hitlCount, assignCount };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Briefing
|
|
674
|
+
export function useBriefing(subjectType: string, subjectId: string, params?: { format?: string; since?: string; context_types?: string; include_stale?: boolean }) {
|
|
675
|
+
const searchParams = new URLSearchParams();
|
|
676
|
+
if (params) {
|
|
677
|
+
Object.entries(params).forEach(([k, v]) => {
|
|
678
|
+
if (v !== undefined && v !== '') searchParams.set(k, String(v));
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
const qs = searchParams.toString();
|
|
682
|
+
const url = `briefing/${subjectType}/${subjectId}${qs ? `?${qs}` : ''}`;
|
|
683
|
+
return useQuery({
|
|
684
|
+
queryKey: ['briefing', subjectType, subjectId, params],
|
|
685
|
+
queryFn: () => api.get(url),
|
|
686
|
+
enabled: !!subjectType && !!subjectId,
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
|
|
Binary file
|