@greatapps/greatchat-ui 0.1.0 → 0.1.2
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/index.d.ts +283 -1
- package/dist/index.js +1472 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/src/components/chat-input.tsx +54 -0
- package/src/components/chat-view.tsx +135 -0
- package/src/components/index.ts +8 -0
- package/src/components/message-bubble.tsx +394 -0
- package/src/components/ui/alert-dialog.tsx +167 -0
- package/src/components/ui/badge.tsx +44 -0
- package/src/components/ui/button.tsx +62 -0
- package/src/components/ui/dropdown-menu.tsx +173 -0
- package/src/components/ui/select.tsx +156 -0
- package/src/components/ui/skeleton.tsx +16 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/hooks/index.ts +14 -0
- package/src/hooks/types.ts +40 -0
- package/src/hooks/use-channels.ts +163 -0
- package/src/hooks/use-contacts.ts +94 -0
- package/src/hooks/use-inbox-messages.ts +405 -0
- package/src/hooks/use-inboxes.ts +127 -0
- package/src/index.ts +8 -0
- package/src/utils/format-date.ts +13 -0
- package/src/utils/group-messages.ts +22 -0
- package/src/utils/index.ts +2 -0
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
2
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import type { InboxMessage } from "../types";
|
|
4
|
+
import {
|
|
5
|
+
type GchatHookConfig,
|
|
6
|
+
DEFAULT_MESSAGES_POLLING,
|
|
7
|
+
useGchatClient,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Optimistic message store (module-level — survives server refetches)
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
const optimisticStore = new Map<string, InboxMessage[]>();
|
|
15
|
+
let nextOptimisticId = -1;
|
|
16
|
+
|
|
17
|
+
// useSyncExternalStore subscription — guarantees synchronous re-renders
|
|
18
|
+
let storeVersion = 0;
|
|
19
|
+
const listeners = new Set<() => void>();
|
|
20
|
+
|
|
21
|
+
function subscribeStore(listener: () => void) {
|
|
22
|
+
listeners.add(listener);
|
|
23
|
+
return () => {
|
|
24
|
+
listeners.delete(listener);
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getStoreVersion() {
|
|
29
|
+
return storeVersion;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function storeKey(accountId: number, idInbox: number) {
|
|
33
|
+
return `${accountId}:${idInbox}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getOptimistic(accountId: number, idInbox: number): InboxMessage[] {
|
|
37
|
+
return optimisticStore.get(storeKey(accountId, idInbox)) || [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function setOptimistic(
|
|
41
|
+
accountId: number,
|
|
42
|
+
idInbox: number,
|
|
43
|
+
msgs: InboxMessage[],
|
|
44
|
+
) {
|
|
45
|
+
const key = storeKey(accountId, idInbox);
|
|
46
|
+
if (msgs.length === 0) {
|
|
47
|
+
optimisticStore.delete(key);
|
|
48
|
+
} else {
|
|
49
|
+
optimisticStore.set(key, msgs);
|
|
50
|
+
}
|
|
51
|
+
storeVersion++;
|
|
52
|
+
listeners.forEach((fn) => fn());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Silently clean up the store without notifying listeners (avoids render loops).
|
|
57
|
+
* Used inside useMemo to remove optimistic messages confirmed by the server.
|
|
58
|
+
*/
|
|
59
|
+
function cleanupOptimistic(
|
|
60
|
+
accountId: number,
|
|
61
|
+
idInbox: number,
|
|
62
|
+
msgs: InboxMessage[],
|
|
63
|
+
) {
|
|
64
|
+
const key = storeKey(accountId, idInbox);
|
|
65
|
+
if (msgs.length === 0) {
|
|
66
|
+
optimisticStore.delete(key);
|
|
67
|
+
} else {
|
|
68
|
+
optimisticStore.set(key, msgs);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// Query
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
export function useInboxMessages(
|
|
77
|
+
config: GchatHookConfig,
|
|
78
|
+
idInbox: number | null,
|
|
79
|
+
pollingInterval?: number,
|
|
80
|
+
) {
|
|
81
|
+
const client = useGchatClient(config);
|
|
82
|
+
|
|
83
|
+
// Subscribe to optimistic store — re-renders when store changes
|
|
84
|
+
const optimisticVersion = useSyncExternalStore(
|
|
85
|
+
subscribeStore,
|
|
86
|
+
getStoreVersion,
|
|
87
|
+
getStoreVersion,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const query = useQuery({
|
|
91
|
+
queryKey: ["greatchat", "inbox-messages", config.accountId, idInbox],
|
|
92
|
+
queryFn: () =>
|
|
93
|
+
client.listInboxMessages(config.accountId, {
|
|
94
|
+
id_inbox: String(idInbox!),
|
|
95
|
+
sort: "datetime_add:asc",
|
|
96
|
+
limit: "100",
|
|
97
|
+
}),
|
|
98
|
+
enabled: !!config.accountId && !!config.token && !!idInbox,
|
|
99
|
+
refetchInterval: pollingInterval ?? DEFAULT_MESSAGES_POLLING,
|
|
100
|
+
select: (res) => res.data || [],
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Merge server data with optimistic messages
|
|
104
|
+
const messages = useMemo(() => {
|
|
105
|
+
const serverMessages = query.data || [];
|
|
106
|
+
if (!config.accountId || !idInbox) return serverMessages;
|
|
107
|
+
|
|
108
|
+
const optimistic = getOptimistic(config.accountId, idInbox);
|
|
109
|
+
if (!optimistic.length) return serverMessages;
|
|
110
|
+
|
|
111
|
+
// Separate overrides (positive ID = retry) from new messages (negative ID = send)
|
|
112
|
+
const overrides = new Map<number, InboxMessage>();
|
|
113
|
+
const newOptimistic: InboxMessage[] = [];
|
|
114
|
+
for (const om of optimistic) {
|
|
115
|
+
if (om.id > 0) {
|
|
116
|
+
overrides.set(om.id, om);
|
|
117
|
+
} else {
|
|
118
|
+
newOptimistic.push(om);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Apply overrides to server messages (replace in-place)
|
|
123
|
+
const merged =
|
|
124
|
+
overrides.size > 0
|
|
125
|
+
? serverMessages.map((sm) => overrides.get(sm.id) ?? sm)
|
|
126
|
+
: serverMessages;
|
|
127
|
+
|
|
128
|
+
// Auto-cleanup: remove overrides when server no longer shows "failed"
|
|
129
|
+
if (overrides.size > 0) {
|
|
130
|
+
for (const [id] of overrides) {
|
|
131
|
+
const sm = serverMessages.find((s) => s.id === id);
|
|
132
|
+
if (sm && sm.status !== "failed") {
|
|
133
|
+
overrides.delete(id);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// One-to-one matching for new optimistic messages (negative IDs)
|
|
139
|
+
const stillNeeded: InboxMessage[] = [];
|
|
140
|
+
const claimed = new Set<number>();
|
|
141
|
+
|
|
142
|
+
for (const om of newOptimistic) {
|
|
143
|
+
// Always keep failed messages (until user retries or dismisses)
|
|
144
|
+
if (om.status === "failed") {
|
|
145
|
+
stillNeeded.push(om);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Find a matching server message that hasn't been claimed yet
|
|
150
|
+
const matchIdx = merged.findIndex(
|
|
151
|
+
(sm, i) =>
|
|
152
|
+
!claimed.has(i) &&
|
|
153
|
+
sm.direction === "outbound" &&
|
|
154
|
+
sm.content === om.content &&
|
|
155
|
+
sm.content_type === om.content_type &&
|
|
156
|
+
sm.source === om.source,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
if (matchIdx >= 0) {
|
|
160
|
+
claimed.add(matchIdx); // server confirmed this one
|
|
161
|
+
} else {
|
|
162
|
+
stillNeeded.push(om); // keep showing optimistic
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Silently clean up confirmed messages (no re-notification)
|
|
167
|
+
const allKept = [...Array.from(overrides.values()), ...stillNeeded];
|
|
168
|
+
if (allKept.length !== optimistic.length) {
|
|
169
|
+
cleanupOptimistic(config.accountId, idInbox, allKept);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Sort by datetime to keep chronological order
|
|
173
|
+
const result = [...merged, ...stillNeeded];
|
|
174
|
+
result.sort(
|
|
175
|
+
(a, b) =>
|
|
176
|
+
new Date(a.datetime_add).getTime() -
|
|
177
|
+
new Date(b.datetime_add).getTime(),
|
|
178
|
+
);
|
|
179
|
+
return result;
|
|
180
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
181
|
+
}, [query.data, config.accountId, idInbox, optimisticVersion]);
|
|
182
|
+
|
|
183
|
+
return { ...query, data: messages };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Send mutation
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
export function useSendMessage(config: GchatHookConfig) {
|
|
191
|
+
const client = useGchatClient(config);
|
|
192
|
+
const queryClient = useQueryClient();
|
|
193
|
+
|
|
194
|
+
return useMutation({
|
|
195
|
+
mutationFn: async ({
|
|
196
|
+
idInbox,
|
|
197
|
+
content,
|
|
198
|
+
}: {
|
|
199
|
+
idInbox: number;
|
|
200
|
+
content: string;
|
|
201
|
+
}) => {
|
|
202
|
+
const result = await client.sendMessage(config.accountId, {
|
|
203
|
+
id_inbox: idInbox,
|
|
204
|
+
content,
|
|
205
|
+
content_type: "text",
|
|
206
|
+
source: "agent",
|
|
207
|
+
direction: "outbound",
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (result.status === 0) {
|
|
211
|
+
throw new Error(result.message || "Erro desconhecido ao enviar");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return result;
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
onMutate: (variables) => {
|
|
218
|
+
const msg: InboxMessage = {
|
|
219
|
+
id: nextOptimisticId--,
|
|
220
|
+
id_account: config.accountId,
|
|
221
|
+
id_inbox: variables.idInbox,
|
|
222
|
+
id_contact: null,
|
|
223
|
+
direction: "outbound",
|
|
224
|
+
content: variables.content,
|
|
225
|
+
content_type: "text",
|
|
226
|
+
content_url: null,
|
|
227
|
+
metadata: null,
|
|
228
|
+
external_id: null,
|
|
229
|
+
status: "pending",
|
|
230
|
+
source: "agent",
|
|
231
|
+
is_private: false,
|
|
232
|
+
datetime_add: new Date().toISOString(),
|
|
233
|
+
datetime_alt: null,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const current = getOptimistic(config.accountId, variables.idInbox);
|
|
237
|
+
setOptimistic(config.accountId, variables.idInbox, [...current, msg]);
|
|
238
|
+
|
|
239
|
+
return { optimisticMsg: msg };
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
onError: (error, variables, context) => {
|
|
243
|
+
if (!context) return;
|
|
244
|
+
const { optimisticMsg } = context;
|
|
245
|
+
|
|
246
|
+
const current = getOptimistic(config.accountId, variables.idInbox);
|
|
247
|
+
setOptimistic(
|
|
248
|
+
config.accountId,
|
|
249
|
+
variables.idInbox,
|
|
250
|
+
current.map((m) =>
|
|
251
|
+
m.id === optimisticMsg.id
|
|
252
|
+
? { ...m, status: "failed" as const, _error: error.message }
|
|
253
|
+
: m,
|
|
254
|
+
),
|
|
255
|
+
);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
onSuccess: (_result, variables, context) => {
|
|
259
|
+
if (!context) return;
|
|
260
|
+
const { optimisticMsg } = context;
|
|
261
|
+
|
|
262
|
+
const current = getOptimistic(config.accountId, variables.idInbox);
|
|
263
|
+
setOptimistic(
|
|
264
|
+
config.accountId,
|
|
265
|
+
variables.idInbox,
|
|
266
|
+
current.filter((m) => m.id !== optimisticMsg.id),
|
|
267
|
+
);
|
|
268
|
+
queryClient.invalidateQueries({
|
|
269
|
+
queryKey: [
|
|
270
|
+
"greatchat",
|
|
271
|
+
"inbox-messages",
|
|
272
|
+
config.accountId,
|
|
273
|
+
variables.idInbox,
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
queryClient.invalidateQueries({
|
|
277
|
+
queryKey: ["greatchat", "inboxes"],
|
|
278
|
+
});
|
|
279
|
+
},
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ---------------------------------------------------------------------------
|
|
284
|
+
// Retry failed message (updates in-place, same bubble transitions states)
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
export function useRetryMessage(config: GchatHookConfig) {
|
|
288
|
+
const client = useGchatClient(config);
|
|
289
|
+
const queryClient = useQueryClient();
|
|
290
|
+
|
|
291
|
+
return async (message: InboxMessage) => {
|
|
292
|
+
if (!config.accountId || !config.token || !message.content) return;
|
|
293
|
+
|
|
294
|
+
// Add as override with status "pending"
|
|
295
|
+
const current = getOptimistic(config.accountId, message.id_inbox);
|
|
296
|
+
setOptimistic(config.accountId, message.id_inbox, [
|
|
297
|
+
...current.filter((m) => m.id !== message.id),
|
|
298
|
+
{
|
|
299
|
+
...message,
|
|
300
|
+
status: "pending" as const,
|
|
301
|
+
_error: undefined,
|
|
302
|
+
datetime_add: new Date().toISOString(),
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
305
|
+
|
|
306
|
+
try {
|
|
307
|
+
const result = await client.sendMessage(config.accountId, {
|
|
308
|
+
id_inbox: message.id_inbox,
|
|
309
|
+
content: message.content,
|
|
310
|
+
content_type: message.content_type,
|
|
311
|
+
source: "agent",
|
|
312
|
+
direction: "outbound",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
if (result.status === 0) {
|
|
316
|
+
throw new Error(result.message || "Erro desconhecido ao enviar");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const after = getOptimistic(config.accountId, message.id_inbox);
|
|
320
|
+
setOptimistic(
|
|
321
|
+
config.accountId,
|
|
322
|
+
message.id_inbox,
|
|
323
|
+
after.filter((m) => m.id !== message.id),
|
|
324
|
+
);
|
|
325
|
+
queryClient.invalidateQueries({
|
|
326
|
+
queryKey: [
|
|
327
|
+
"greatchat",
|
|
328
|
+
"inbox-messages",
|
|
329
|
+
config.accountId,
|
|
330
|
+
message.id_inbox,
|
|
331
|
+
],
|
|
332
|
+
});
|
|
333
|
+
queryClient.invalidateQueries({
|
|
334
|
+
queryKey: ["greatchat", "inboxes"],
|
|
335
|
+
});
|
|
336
|
+
} catch (err) {
|
|
337
|
+
const errorMessage =
|
|
338
|
+
err instanceof Error ? err.message : "Erro ao enviar mensagem";
|
|
339
|
+
const after = getOptimistic(config.accountId, message.id_inbox);
|
|
340
|
+
setOptimistic(
|
|
341
|
+
config.accountId,
|
|
342
|
+
message.id_inbox,
|
|
343
|
+
after.map((m) =>
|
|
344
|
+
m.id === message.id
|
|
345
|
+
? { ...m, status: "failed" as const, _error: errorMessage }
|
|
346
|
+
: m,
|
|
347
|
+
),
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ---------------------------------------------------------------------------
|
|
354
|
+
// Revoke message
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
|
|
357
|
+
export function useRevokeMessage(config: GchatHookConfig) {
|
|
358
|
+
const client = useGchatClient(config);
|
|
359
|
+
const queryClient = useQueryClient();
|
|
360
|
+
|
|
361
|
+
return useMutation({
|
|
362
|
+
mutationFn: async ({ id }: { id: number; idInbox: number }) =>
|
|
363
|
+
client.revokeMessage(config.accountId, id),
|
|
364
|
+
onSuccess: (_data, variables) => {
|
|
365
|
+
queryClient.invalidateQueries({
|
|
366
|
+
queryKey: [
|
|
367
|
+
"greatchat",
|
|
368
|
+
"inbox-messages",
|
|
369
|
+
config.accountId,
|
|
370
|
+
variables.idInbox,
|
|
371
|
+
],
|
|
372
|
+
});
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ---------------------------------------------------------------------------
|
|
378
|
+
// Edit message
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
export function useEditMessage(config: GchatHookConfig) {
|
|
382
|
+
const client = useGchatClient(config);
|
|
383
|
+
const queryClient = useQueryClient();
|
|
384
|
+
|
|
385
|
+
return useMutation({
|
|
386
|
+
mutationFn: async ({
|
|
387
|
+
id,
|
|
388
|
+
content,
|
|
389
|
+
}: {
|
|
390
|
+
id: number;
|
|
391
|
+
idInbox: number;
|
|
392
|
+
content: string;
|
|
393
|
+
}) => client.editMessage(config.accountId, id, { content }),
|
|
394
|
+
onSuccess: (_data, variables) => {
|
|
395
|
+
queryClient.invalidateQueries({
|
|
396
|
+
queryKey: [
|
|
397
|
+
"greatchat",
|
|
398
|
+
"inbox-messages",
|
|
399
|
+
config.accountId,
|
|
400
|
+
variables.idInbox,
|
|
401
|
+
],
|
|
402
|
+
});
|
|
403
|
+
},
|
|
404
|
+
});
|
|
405
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
2
|
+
import type { Inbox, InboxStats } from "../types";
|
|
3
|
+
import {
|
|
4
|
+
type GchatHookConfig,
|
|
5
|
+
DEFAULT_INBOX_POLLING,
|
|
6
|
+
useGchatClient,
|
|
7
|
+
} from "./types";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Queries
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export function useInboxes(
|
|
14
|
+
config: GchatHookConfig,
|
|
15
|
+
statusFilter?: string,
|
|
16
|
+
pollingInterval?: number,
|
|
17
|
+
) {
|
|
18
|
+
const client = useGchatClient(config);
|
|
19
|
+
|
|
20
|
+
return useQuery({
|
|
21
|
+
queryKey: ["greatchat", "inboxes", config.accountId, statusFilter],
|
|
22
|
+
queryFn: () => {
|
|
23
|
+
const params: Record<string, string> = {};
|
|
24
|
+
if (statusFilter && statusFilter !== "all") {
|
|
25
|
+
params.status = statusFilter;
|
|
26
|
+
}
|
|
27
|
+
return client.listInboxes(config.accountId, params);
|
|
28
|
+
},
|
|
29
|
+
enabled: !!config.accountId && !!config.token,
|
|
30
|
+
refetchInterval: pollingInterval ?? DEFAULT_INBOX_POLLING,
|
|
31
|
+
select: (res) => res.data || [],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useInbox(config: GchatHookConfig, id: number | null) {
|
|
36
|
+
const client = useGchatClient(config);
|
|
37
|
+
|
|
38
|
+
return useQuery({
|
|
39
|
+
queryKey: ["greatchat", "inbox", config.accountId, id],
|
|
40
|
+
queryFn: () => client.getInbox(config.accountId, id!),
|
|
41
|
+
enabled: !!config.accountId && !!config.token && !!id,
|
|
42
|
+
select: (res) => {
|
|
43
|
+
const d = res.data;
|
|
44
|
+
return (Array.isArray(d) ? d[0] : d) as Inbox;
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useInboxStats(
|
|
50
|
+
config: GchatHookConfig,
|
|
51
|
+
pollingInterval?: number,
|
|
52
|
+
) {
|
|
53
|
+
const client = useGchatClient(config);
|
|
54
|
+
|
|
55
|
+
return useQuery({
|
|
56
|
+
queryKey: ["greatchat", "inbox-stats", config.accountId],
|
|
57
|
+
queryFn: () => client.getInboxStats(config.accountId),
|
|
58
|
+
enabled: !!config.accountId && !!config.token,
|
|
59
|
+
refetchInterval: pollingInterval ?? DEFAULT_INBOX_POLLING,
|
|
60
|
+
select: (res) => {
|
|
61
|
+
const d = res.data;
|
|
62
|
+
return (Array.isArray(d) ? d[0] : d) as InboxStats;
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Mutations
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
export function useCreateInbox(config: GchatHookConfig) {
|
|
72
|
+
const client = useGchatClient(config);
|
|
73
|
+
const queryClient = useQueryClient();
|
|
74
|
+
|
|
75
|
+
return useMutation({
|
|
76
|
+
mutationFn: (body: { id_channel: number; id_contact: number }) =>
|
|
77
|
+
client.createInbox(config.accountId, body),
|
|
78
|
+
onSuccess: () => {
|
|
79
|
+
queryClient.invalidateQueries({
|
|
80
|
+
queryKey: ["greatchat", "inboxes"],
|
|
81
|
+
});
|
|
82
|
+
queryClient.invalidateQueries({
|
|
83
|
+
queryKey: ["greatchat", "inbox-stats"],
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function useUpdateInbox(config: GchatHookConfig) {
|
|
90
|
+
const client = useGchatClient(config);
|
|
91
|
+
const queryClient = useQueryClient();
|
|
92
|
+
|
|
93
|
+
return useMutation({
|
|
94
|
+
mutationFn: ({
|
|
95
|
+
id,
|
|
96
|
+
body,
|
|
97
|
+
}: {
|
|
98
|
+
id: number;
|
|
99
|
+
body: Partial<Pick<Inbox, "status" | "id_agent">>;
|
|
100
|
+
}) => client.updateInbox(config.accountId, id, body),
|
|
101
|
+
onSuccess: () => {
|
|
102
|
+
queryClient.invalidateQueries({
|
|
103
|
+
queryKey: ["greatchat", "inboxes"],
|
|
104
|
+
});
|
|
105
|
+
queryClient.invalidateQueries({
|
|
106
|
+
queryKey: ["greatchat", "inbox-stats"],
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function useDeleteInbox(config: GchatHookConfig) {
|
|
113
|
+
const client = useGchatClient(config);
|
|
114
|
+
const queryClient = useQueryClient();
|
|
115
|
+
|
|
116
|
+
return useMutation({
|
|
117
|
+
mutationFn: (id: number) => client.deleteInbox(config.accountId, id),
|
|
118
|
+
onSuccess: () => {
|
|
119
|
+
queryClient.invalidateQueries({
|
|
120
|
+
queryKey: ["greatchat", "inboxes"],
|
|
121
|
+
});
|
|
122
|
+
queryClient.invalidateQueries({
|
|
123
|
+
queryKey: ["greatchat", "inbox-stats"],
|
|
124
|
+
});
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -16,3 +16,11 @@ export type { GchatClientConfig } from "./client";
|
|
|
16
16
|
|
|
17
17
|
// Utils
|
|
18
18
|
export { cn } from "./lib/utils";
|
|
19
|
+
export { groupMessagesByDate, formatDateGroup, formatMessageTime } from "./utils";
|
|
20
|
+
|
|
21
|
+
// Hooks
|
|
22
|
+
export * from "./hooks";
|
|
23
|
+
|
|
24
|
+
// Components
|
|
25
|
+
export { ChatView, ChatInput, MessageBubble } from "./components";
|
|
26
|
+
export type { ChatViewProps, ChatInputProps, MessageBubbleProps } from "./components";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { format, isToday, isYesterday } from "date-fns";
|
|
2
|
+
import { ptBR } from "date-fns/locale";
|
|
3
|
+
|
|
4
|
+
export function formatDateGroup(dateStr: string): string {
|
|
5
|
+
const date = new Date(dateStr);
|
|
6
|
+
if (isToday(date)) return "Hoje";
|
|
7
|
+
if (isYesterday(date)) return "Ontem";
|
|
8
|
+
return format(date, "dd 'de' MMMM", { locale: ptBR });
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function formatMessageTime(dateStr: string): string {
|
|
12
|
+
return format(new Date(dateStr), "HH:mm");
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { InboxMessage } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Groups messages by date. Assumes messages are pre-sorted by datetime_add (ascending).
|
|
5
|
+
*/
|
|
6
|
+
export function groupMessagesByDate(
|
|
7
|
+
messages: InboxMessage[],
|
|
8
|
+
): { date: string; messages: InboxMessage[] }[] {
|
|
9
|
+
const groups: { date: string; messages: InboxMessage[] }[] = [];
|
|
10
|
+
let currentDate = "";
|
|
11
|
+
|
|
12
|
+
for (const msg of messages) {
|
|
13
|
+
const dateStr = msg.datetime_add.split("T")[0];
|
|
14
|
+
if (dateStr !== currentDate) {
|
|
15
|
+
currentDate = dateStr;
|
|
16
|
+
groups.push({ date: msg.datetime_add, messages: [] });
|
|
17
|
+
}
|
|
18
|
+
groups[groups.length - 1].messages.push(msg);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return groups;
|
|
22
|
+
}
|