@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
package/dist/index.js
CHANGED
|
@@ -101,8 +101,1479 @@ import { twMerge } from "tailwind-merge";
|
|
|
101
101
|
function cn(...inputs) {
|
|
102
102
|
return twMerge(clsx(inputs));
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
// src/utils/group-messages.ts
|
|
106
|
+
function groupMessagesByDate(messages) {
|
|
107
|
+
const groups = [];
|
|
108
|
+
let currentDate = "";
|
|
109
|
+
for (const msg of messages) {
|
|
110
|
+
const dateStr = msg.datetime_add.split("T")[0];
|
|
111
|
+
if (dateStr !== currentDate) {
|
|
112
|
+
currentDate = dateStr;
|
|
113
|
+
groups.push({ date: msg.datetime_add, messages: [] });
|
|
114
|
+
}
|
|
115
|
+
groups[groups.length - 1].messages.push(msg);
|
|
116
|
+
}
|
|
117
|
+
return groups;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/utils/format-date.ts
|
|
121
|
+
import { format, isToday, isYesterday } from "date-fns";
|
|
122
|
+
import { ptBR } from "date-fns/locale";
|
|
123
|
+
function formatDateGroup(dateStr) {
|
|
124
|
+
const date = new Date(dateStr);
|
|
125
|
+
if (isToday(date)) return "Hoje";
|
|
126
|
+
if (isYesterday(date)) return "Ontem";
|
|
127
|
+
return format(date, "dd 'de' MMMM", { locale: ptBR });
|
|
128
|
+
}
|
|
129
|
+
function formatMessageTime(dateStr) {
|
|
130
|
+
return format(new Date(dateStr), "HH:mm");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/hooks/types.ts
|
|
134
|
+
import { useMemo } from "react";
|
|
135
|
+
var DEFAULT_INBOX_POLLING = 5e3;
|
|
136
|
+
var DEFAULT_MESSAGES_POLLING = 3e3;
|
|
137
|
+
var DEFAULT_CHANNEL_STATUS_POLLING = 5e3;
|
|
138
|
+
var DEFAULT_QR_POLLING = 2e3;
|
|
139
|
+
function useGchatClient(config) {
|
|
140
|
+
return useMemo(
|
|
141
|
+
() => createGchatClient({
|
|
142
|
+
baseUrl: config.baseUrl,
|
|
143
|
+
token: config.token,
|
|
144
|
+
language: config.language,
|
|
145
|
+
idWl: config.idWl
|
|
146
|
+
}),
|
|
147
|
+
[config.baseUrl, config.token, config.language, config.idWl]
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/hooks/use-inboxes.ts
|
|
152
|
+
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
153
|
+
function useInboxes(config, statusFilter, pollingInterval) {
|
|
154
|
+
const client = useGchatClient(config);
|
|
155
|
+
return useQuery({
|
|
156
|
+
queryKey: ["greatchat", "inboxes", config.accountId, statusFilter],
|
|
157
|
+
queryFn: () => {
|
|
158
|
+
const params = {};
|
|
159
|
+
if (statusFilter && statusFilter !== "all") {
|
|
160
|
+
params.status = statusFilter;
|
|
161
|
+
}
|
|
162
|
+
return client.listInboxes(config.accountId, params);
|
|
163
|
+
},
|
|
164
|
+
enabled: !!config.accountId && !!config.token,
|
|
165
|
+
refetchInterval: pollingInterval ?? DEFAULT_INBOX_POLLING,
|
|
166
|
+
select: (res) => res.data || []
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
function useInbox(config, id) {
|
|
170
|
+
const client = useGchatClient(config);
|
|
171
|
+
return useQuery({
|
|
172
|
+
queryKey: ["greatchat", "inbox", config.accountId, id],
|
|
173
|
+
queryFn: () => client.getInbox(config.accountId, id),
|
|
174
|
+
enabled: !!config.accountId && !!config.token && !!id,
|
|
175
|
+
select: (res) => {
|
|
176
|
+
const d = res.data;
|
|
177
|
+
return Array.isArray(d) ? d[0] : d;
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function useInboxStats(config, pollingInterval) {
|
|
182
|
+
const client = useGchatClient(config);
|
|
183
|
+
return useQuery({
|
|
184
|
+
queryKey: ["greatchat", "inbox-stats", config.accountId],
|
|
185
|
+
queryFn: () => client.getInboxStats(config.accountId),
|
|
186
|
+
enabled: !!config.accountId && !!config.token,
|
|
187
|
+
refetchInterval: pollingInterval ?? DEFAULT_INBOX_POLLING,
|
|
188
|
+
select: (res) => {
|
|
189
|
+
const d = res.data;
|
|
190
|
+
return Array.isArray(d) ? d[0] : d;
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
function useCreateInbox(config) {
|
|
195
|
+
const client = useGchatClient(config);
|
|
196
|
+
const queryClient = useQueryClient();
|
|
197
|
+
return useMutation({
|
|
198
|
+
mutationFn: (body) => client.createInbox(config.accountId, body),
|
|
199
|
+
onSuccess: () => {
|
|
200
|
+
queryClient.invalidateQueries({
|
|
201
|
+
queryKey: ["greatchat", "inboxes"]
|
|
202
|
+
});
|
|
203
|
+
queryClient.invalidateQueries({
|
|
204
|
+
queryKey: ["greatchat", "inbox-stats"]
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
function useUpdateInbox(config) {
|
|
210
|
+
const client = useGchatClient(config);
|
|
211
|
+
const queryClient = useQueryClient();
|
|
212
|
+
return useMutation({
|
|
213
|
+
mutationFn: ({
|
|
214
|
+
id,
|
|
215
|
+
body
|
|
216
|
+
}) => client.updateInbox(config.accountId, id, body),
|
|
217
|
+
onSuccess: () => {
|
|
218
|
+
queryClient.invalidateQueries({
|
|
219
|
+
queryKey: ["greatchat", "inboxes"]
|
|
220
|
+
});
|
|
221
|
+
queryClient.invalidateQueries({
|
|
222
|
+
queryKey: ["greatchat", "inbox-stats"]
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function useDeleteInbox(config) {
|
|
228
|
+
const client = useGchatClient(config);
|
|
229
|
+
const queryClient = useQueryClient();
|
|
230
|
+
return useMutation({
|
|
231
|
+
mutationFn: (id) => client.deleteInbox(config.accountId, id),
|
|
232
|
+
onSuccess: () => {
|
|
233
|
+
queryClient.invalidateQueries({
|
|
234
|
+
queryKey: ["greatchat", "inboxes"]
|
|
235
|
+
});
|
|
236
|
+
queryClient.invalidateQueries({
|
|
237
|
+
queryKey: ["greatchat", "inbox-stats"]
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/hooks/use-inbox-messages.ts
|
|
244
|
+
import { useMemo as useMemo2, useSyncExternalStore } from "react";
|
|
245
|
+
import { useQuery as useQuery2, useMutation as useMutation2, useQueryClient as useQueryClient2 } from "@tanstack/react-query";
|
|
246
|
+
var optimisticStore = /* @__PURE__ */ new Map();
|
|
247
|
+
var nextOptimisticId = -1;
|
|
248
|
+
var storeVersion = 0;
|
|
249
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
250
|
+
function subscribeStore(listener) {
|
|
251
|
+
listeners.add(listener);
|
|
252
|
+
return () => {
|
|
253
|
+
listeners.delete(listener);
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function getStoreVersion() {
|
|
257
|
+
return storeVersion;
|
|
258
|
+
}
|
|
259
|
+
function storeKey(accountId, idInbox) {
|
|
260
|
+
return `${accountId}:${idInbox}`;
|
|
261
|
+
}
|
|
262
|
+
function getOptimistic(accountId, idInbox) {
|
|
263
|
+
return optimisticStore.get(storeKey(accountId, idInbox)) || [];
|
|
264
|
+
}
|
|
265
|
+
function setOptimistic(accountId, idInbox, msgs) {
|
|
266
|
+
const key = storeKey(accountId, idInbox);
|
|
267
|
+
if (msgs.length === 0) {
|
|
268
|
+
optimisticStore.delete(key);
|
|
269
|
+
} else {
|
|
270
|
+
optimisticStore.set(key, msgs);
|
|
271
|
+
}
|
|
272
|
+
storeVersion++;
|
|
273
|
+
listeners.forEach((fn) => fn());
|
|
274
|
+
}
|
|
275
|
+
function cleanupOptimistic(accountId, idInbox, msgs) {
|
|
276
|
+
const key = storeKey(accountId, idInbox);
|
|
277
|
+
if (msgs.length === 0) {
|
|
278
|
+
optimisticStore.delete(key);
|
|
279
|
+
} else {
|
|
280
|
+
optimisticStore.set(key, msgs);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function useInboxMessages(config, idInbox, pollingInterval) {
|
|
284
|
+
const client = useGchatClient(config);
|
|
285
|
+
const optimisticVersion = useSyncExternalStore(
|
|
286
|
+
subscribeStore,
|
|
287
|
+
getStoreVersion,
|
|
288
|
+
getStoreVersion
|
|
289
|
+
);
|
|
290
|
+
const query = useQuery2({
|
|
291
|
+
queryKey: ["greatchat", "inbox-messages", config.accountId, idInbox],
|
|
292
|
+
queryFn: () => client.listInboxMessages(config.accountId, {
|
|
293
|
+
id_inbox: String(idInbox),
|
|
294
|
+
sort: "datetime_add:asc",
|
|
295
|
+
limit: "100"
|
|
296
|
+
}),
|
|
297
|
+
enabled: !!config.accountId && !!config.token && !!idInbox,
|
|
298
|
+
refetchInterval: pollingInterval ?? DEFAULT_MESSAGES_POLLING,
|
|
299
|
+
select: (res) => res.data || []
|
|
300
|
+
});
|
|
301
|
+
const messages = useMemo2(() => {
|
|
302
|
+
const serverMessages = query.data || [];
|
|
303
|
+
if (!config.accountId || !idInbox) return serverMessages;
|
|
304
|
+
const optimistic = getOptimistic(config.accountId, idInbox);
|
|
305
|
+
if (!optimistic.length) return serverMessages;
|
|
306
|
+
const overrides = /* @__PURE__ */ new Map();
|
|
307
|
+
const newOptimistic = [];
|
|
308
|
+
for (const om of optimistic) {
|
|
309
|
+
if (om.id > 0) {
|
|
310
|
+
overrides.set(om.id, om);
|
|
311
|
+
} else {
|
|
312
|
+
newOptimistic.push(om);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
const merged = overrides.size > 0 ? serverMessages.map((sm) => overrides.get(sm.id) ?? sm) : serverMessages;
|
|
316
|
+
if (overrides.size > 0) {
|
|
317
|
+
for (const [id] of overrides) {
|
|
318
|
+
const sm = serverMessages.find((s) => s.id === id);
|
|
319
|
+
if (sm && sm.status !== "failed") {
|
|
320
|
+
overrides.delete(id);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const stillNeeded = [];
|
|
325
|
+
const claimed = /* @__PURE__ */ new Set();
|
|
326
|
+
for (const om of newOptimistic) {
|
|
327
|
+
if (om.status === "failed") {
|
|
328
|
+
stillNeeded.push(om);
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const matchIdx = merged.findIndex(
|
|
332
|
+
(sm, i) => !claimed.has(i) && sm.direction === "outbound" && sm.content === om.content && sm.content_type === om.content_type && sm.source === om.source
|
|
333
|
+
);
|
|
334
|
+
if (matchIdx >= 0) {
|
|
335
|
+
claimed.add(matchIdx);
|
|
336
|
+
} else {
|
|
337
|
+
stillNeeded.push(om);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const allKept = [...Array.from(overrides.values()), ...stillNeeded];
|
|
341
|
+
if (allKept.length !== optimistic.length) {
|
|
342
|
+
cleanupOptimistic(config.accountId, idInbox, allKept);
|
|
343
|
+
}
|
|
344
|
+
const result = [...merged, ...stillNeeded];
|
|
345
|
+
result.sort(
|
|
346
|
+
(a, b) => new Date(a.datetime_add).getTime() - new Date(b.datetime_add).getTime()
|
|
347
|
+
);
|
|
348
|
+
return result;
|
|
349
|
+
}, [query.data, config.accountId, idInbox, optimisticVersion]);
|
|
350
|
+
return { ...query, data: messages };
|
|
351
|
+
}
|
|
352
|
+
function useSendMessage(config) {
|
|
353
|
+
const client = useGchatClient(config);
|
|
354
|
+
const queryClient = useQueryClient2();
|
|
355
|
+
return useMutation2({
|
|
356
|
+
mutationFn: async ({
|
|
357
|
+
idInbox,
|
|
358
|
+
content
|
|
359
|
+
}) => {
|
|
360
|
+
const result = await client.sendMessage(config.accountId, {
|
|
361
|
+
id_inbox: idInbox,
|
|
362
|
+
content,
|
|
363
|
+
content_type: "text",
|
|
364
|
+
source: "agent",
|
|
365
|
+
direction: "outbound"
|
|
366
|
+
});
|
|
367
|
+
if (result.status === 0) {
|
|
368
|
+
throw new Error(result.message || "Erro desconhecido ao enviar");
|
|
369
|
+
}
|
|
370
|
+
return result;
|
|
371
|
+
},
|
|
372
|
+
onMutate: (variables) => {
|
|
373
|
+
const msg = {
|
|
374
|
+
id: nextOptimisticId--,
|
|
375
|
+
id_account: config.accountId,
|
|
376
|
+
id_inbox: variables.idInbox,
|
|
377
|
+
id_contact: null,
|
|
378
|
+
direction: "outbound",
|
|
379
|
+
content: variables.content,
|
|
380
|
+
content_type: "text",
|
|
381
|
+
content_url: null,
|
|
382
|
+
metadata: null,
|
|
383
|
+
external_id: null,
|
|
384
|
+
status: "pending",
|
|
385
|
+
source: "agent",
|
|
386
|
+
is_private: false,
|
|
387
|
+
datetime_add: (/* @__PURE__ */ new Date()).toISOString(),
|
|
388
|
+
datetime_alt: null
|
|
389
|
+
};
|
|
390
|
+
const current = getOptimistic(config.accountId, variables.idInbox);
|
|
391
|
+
setOptimistic(config.accountId, variables.idInbox, [...current, msg]);
|
|
392
|
+
return { optimisticMsg: msg };
|
|
393
|
+
},
|
|
394
|
+
onError: (error, variables, context) => {
|
|
395
|
+
if (!context) return;
|
|
396
|
+
const { optimisticMsg } = context;
|
|
397
|
+
const current = getOptimistic(config.accountId, variables.idInbox);
|
|
398
|
+
setOptimistic(
|
|
399
|
+
config.accountId,
|
|
400
|
+
variables.idInbox,
|
|
401
|
+
current.map(
|
|
402
|
+
(m) => m.id === optimisticMsg.id ? { ...m, status: "failed", _error: error.message } : m
|
|
403
|
+
)
|
|
404
|
+
);
|
|
405
|
+
},
|
|
406
|
+
onSuccess: (_result, variables, context) => {
|
|
407
|
+
if (!context) return;
|
|
408
|
+
const { optimisticMsg } = context;
|
|
409
|
+
const current = getOptimistic(config.accountId, variables.idInbox);
|
|
410
|
+
setOptimistic(
|
|
411
|
+
config.accountId,
|
|
412
|
+
variables.idInbox,
|
|
413
|
+
current.filter((m) => m.id !== optimisticMsg.id)
|
|
414
|
+
);
|
|
415
|
+
queryClient.invalidateQueries({
|
|
416
|
+
queryKey: [
|
|
417
|
+
"greatchat",
|
|
418
|
+
"inbox-messages",
|
|
419
|
+
config.accountId,
|
|
420
|
+
variables.idInbox
|
|
421
|
+
]
|
|
422
|
+
});
|
|
423
|
+
queryClient.invalidateQueries({
|
|
424
|
+
queryKey: ["greatchat", "inboxes"]
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
function useRetryMessage(config) {
|
|
430
|
+
const client = useGchatClient(config);
|
|
431
|
+
const queryClient = useQueryClient2();
|
|
432
|
+
return async (message) => {
|
|
433
|
+
if (!config.accountId || !config.token || !message.content) return;
|
|
434
|
+
const current = getOptimistic(config.accountId, message.id_inbox);
|
|
435
|
+
setOptimistic(config.accountId, message.id_inbox, [
|
|
436
|
+
...current.filter((m) => m.id !== message.id),
|
|
437
|
+
{
|
|
438
|
+
...message,
|
|
439
|
+
status: "pending",
|
|
440
|
+
_error: void 0,
|
|
441
|
+
datetime_add: (/* @__PURE__ */ new Date()).toISOString()
|
|
442
|
+
}
|
|
443
|
+
]);
|
|
444
|
+
try {
|
|
445
|
+
const result = await client.sendMessage(config.accountId, {
|
|
446
|
+
id_inbox: message.id_inbox,
|
|
447
|
+
content: message.content,
|
|
448
|
+
content_type: message.content_type,
|
|
449
|
+
source: "agent",
|
|
450
|
+
direction: "outbound"
|
|
451
|
+
});
|
|
452
|
+
if (result.status === 0) {
|
|
453
|
+
throw new Error(result.message || "Erro desconhecido ao enviar");
|
|
454
|
+
}
|
|
455
|
+
const after = getOptimistic(config.accountId, message.id_inbox);
|
|
456
|
+
setOptimistic(
|
|
457
|
+
config.accountId,
|
|
458
|
+
message.id_inbox,
|
|
459
|
+
after.filter((m) => m.id !== message.id)
|
|
460
|
+
);
|
|
461
|
+
queryClient.invalidateQueries({
|
|
462
|
+
queryKey: [
|
|
463
|
+
"greatchat",
|
|
464
|
+
"inbox-messages",
|
|
465
|
+
config.accountId,
|
|
466
|
+
message.id_inbox
|
|
467
|
+
]
|
|
468
|
+
});
|
|
469
|
+
queryClient.invalidateQueries({
|
|
470
|
+
queryKey: ["greatchat", "inboxes"]
|
|
471
|
+
});
|
|
472
|
+
} catch (err) {
|
|
473
|
+
const errorMessage = err instanceof Error ? err.message : "Erro ao enviar mensagem";
|
|
474
|
+
const after = getOptimistic(config.accountId, message.id_inbox);
|
|
475
|
+
setOptimistic(
|
|
476
|
+
config.accountId,
|
|
477
|
+
message.id_inbox,
|
|
478
|
+
after.map(
|
|
479
|
+
(m) => m.id === message.id ? { ...m, status: "failed", _error: errorMessage } : m
|
|
480
|
+
)
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
function useRevokeMessage(config) {
|
|
486
|
+
const client = useGchatClient(config);
|
|
487
|
+
const queryClient = useQueryClient2();
|
|
488
|
+
return useMutation2({
|
|
489
|
+
mutationFn: async ({ id }) => client.revokeMessage(config.accountId, id),
|
|
490
|
+
onSuccess: (_data, variables) => {
|
|
491
|
+
queryClient.invalidateQueries({
|
|
492
|
+
queryKey: [
|
|
493
|
+
"greatchat",
|
|
494
|
+
"inbox-messages",
|
|
495
|
+
config.accountId,
|
|
496
|
+
variables.idInbox
|
|
497
|
+
]
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
function useEditMessage(config) {
|
|
503
|
+
const client = useGchatClient(config);
|
|
504
|
+
const queryClient = useQueryClient2();
|
|
505
|
+
return useMutation2({
|
|
506
|
+
mutationFn: async ({
|
|
507
|
+
id,
|
|
508
|
+
content
|
|
509
|
+
}) => client.editMessage(config.accountId, id, { content }),
|
|
510
|
+
onSuccess: (_data, variables) => {
|
|
511
|
+
queryClient.invalidateQueries({
|
|
512
|
+
queryKey: [
|
|
513
|
+
"greatchat",
|
|
514
|
+
"inbox-messages",
|
|
515
|
+
config.accountId,
|
|
516
|
+
variables.idInbox
|
|
517
|
+
]
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// src/hooks/use-contacts.ts
|
|
524
|
+
import { useQuery as useQuery3, useMutation as useMutation3, useQueryClient as useQueryClient3 } from "@tanstack/react-query";
|
|
525
|
+
function useContacts(config, params) {
|
|
526
|
+
const client = useGchatClient(config);
|
|
527
|
+
return useQuery3({
|
|
528
|
+
queryKey: ["greatchat", "contacts", config.accountId, params],
|
|
529
|
+
queryFn: () => client.listContacts(config.accountId, params),
|
|
530
|
+
enabled: !!config.accountId && !!config.token,
|
|
531
|
+
select: (res) => ({ data: res.data || [], total: res.total || 0 })
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
function useGetContact(config, contactId) {
|
|
535
|
+
const client = useGchatClient(config);
|
|
536
|
+
return useQuery3({
|
|
537
|
+
queryKey: ["greatchat", "contact", config.accountId, contactId],
|
|
538
|
+
queryFn: () => client.getContact(config.accountId, contactId),
|
|
539
|
+
enabled: !!config.accountId && !!config.token && !!contactId,
|
|
540
|
+
select: (res) => {
|
|
541
|
+
const d = res.data;
|
|
542
|
+
return Array.isArray(d) ? d[0] : d;
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
function useCreateContact(config) {
|
|
547
|
+
const client = useGchatClient(config);
|
|
548
|
+
const queryClient = useQueryClient3();
|
|
549
|
+
return useMutation3({
|
|
550
|
+
mutationFn: (body) => client.createContact(config.accountId, body),
|
|
551
|
+
onSuccess: () => {
|
|
552
|
+
queryClient.invalidateQueries({
|
|
553
|
+
queryKey: ["greatchat", "contacts"]
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
function useUpdateContact(config) {
|
|
559
|
+
const client = useGchatClient(config);
|
|
560
|
+
const queryClient = useQueryClient3();
|
|
561
|
+
return useMutation3({
|
|
562
|
+
mutationFn: ({
|
|
563
|
+
id,
|
|
564
|
+
body
|
|
565
|
+
}) => client.updateContact(config.accountId, id, body),
|
|
566
|
+
onSuccess: () => {
|
|
567
|
+
queryClient.invalidateQueries({
|
|
568
|
+
queryKey: ["greatchat", "contacts"]
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
function useDeleteContact(config) {
|
|
574
|
+
const client = useGchatClient(config);
|
|
575
|
+
const queryClient = useQueryClient3();
|
|
576
|
+
return useMutation3({
|
|
577
|
+
mutationFn: (id) => client.deleteContact(config.accountId, id),
|
|
578
|
+
onSuccess: () => {
|
|
579
|
+
queryClient.invalidateQueries({
|
|
580
|
+
queryKey: ["greatchat", "contacts"]
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/hooks/use-channels.ts
|
|
587
|
+
import { useQuery as useQuery4, useMutation as useMutation4, useQueryClient as useQueryClient4 } from "@tanstack/react-query";
|
|
588
|
+
function useChannels(config) {
|
|
589
|
+
const client = useGchatClient(config);
|
|
590
|
+
return useQuery4({
|
|
591
|
+
queryKey: ["greatchat", "channels", config.accountId],
|
|
592
|
+
queryFn: () => client.listChannels(config.accountId),
|
|
593
|
+
enabled: !!config.accountId && !!config.token,
|
|
594
|
+
select: (res) => res.data || []
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
function useChannelWhatsappStatus(config, channelId, enabled = true, pollingInterval) {
|
|
598
|
+
const client = useGchatClient(config);
|
|
599
|
+
return useQuery4({
|
|
600
|
+
queryKey: ["greatchat", "channel-status", config.accountId, channelId],
|
|
601
|
+
queryFn: () => client.getChannelWhatsappStatus(config.accountId, channelId),
|
|
602
|
+
enabled: !!config.accountId && !!config.token && !!channelId && enabled,
|
|
603
|
+
refetchInterval: pollingInterval ?? DEFAULT_CHANNEL_STATUS_POLLING,
|
|
604
|
+
select: (res) => {
|
|
605
|
+
const d = res.data;
|
|
606
|
+
return Array.isArray(d) ? d[0] : d;
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
function useChannelQR(config, channelId, enabled = false, pollingInterval) {
|
|
611
|
+
const client = useGchatClient(config);
|
|
612
|
+
return useQuery4({
|
|
613
|
+
queryKey: ["greatchat", "channel-qr", config.accountId, channelId],
|
|
614
|
+
queryFn: () => client.getChannelQR(config.accountId, channelId),
|
|
615
|
+
enabled: !!config.accountId && !!config.token && !!channelId && enabled,
|
|
616
|
+
refetchInterval: pollingInterval ?? DEFAULT_QR_POLLING
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
function useCreateChannel(config) {
|
|
620
|
+
const client = useGchatClient(config);
|
|
621
|
+
const queryClient = useQueryClient4();
|
|
622
|
+
return useMutation4({
|
|
623
|
+
mutationFn: (body) => client.createChannel(config.accountId, body),
|
|
624
|
+
onSuccess: () => {
|
|
625
|
+
queryClient.invalidateQueries({
|
|
626
|
+
queryKey: ["greatchat", "channels"]
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
function useUpdateChannel(config) {
|
|
632
|
+
const client = useGchatClient(config);
|
|
633
|
+
const queryClient = useQueryClient4();
|
|
634
|
+
return useMutation4({
|
|
635
|
+
mutationFn: ({
|
|
636
|
+
id,
|
|
637
|
+
body
|
|
638
|
+
}) => client.updateChannel(config.accountId, id, body),
|
|
639
|
+
onSuccess: () => {
|
|
640
|
+
queryClient.invalidateQueries({
|
|
641
|
+
queryKey: ["greatchat", "channels"]
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
function useDeleteChannel(config) {
|
|
647
|
+
const client = useGchatClient(config);
|
|
648
|
+
const queryClient = useQueryClient4();
|
|
649
|
+
return useMutation4({
|
|
650
|
+
mutationFn: (channelId) => client.deleteChannel(config.accountId, channelId),
|
|
651
|
+
onSuccess: () => {
|
|
652
|
+
queryClient.invalidateQueries({
|
|
653
|
+
queryKey: ["greatchat", "channels"]
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
function useConnectChannel(config) {
|
|
659
|
+
const client = useGchatClient(config);
|
|
660
|
+
return useMutation4({
|
|
661
|
+
mutationFn: (channelId) => client.connectChannel(config.accountId, channelId)
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function useDisconnectChannel(config) {
|
|
665
|
+
const client = useGchatClient(config);
|
|
666
|
+
const queryClient = useQueryClient4();
|
|
667
|
+
return useMutation4({
|
|
668
|
+
mutationFn: (channelId) => client.disconnectChannel(config.accountId, channelId),
|
|
669
|
+
onSuccess: () => {
|
|
670
|
+
queryClient.invalidateQueries({
|
|
671
|
+
queryKey: ["greatchat", "channel-status"]
|
|
672
|
+
});
|
|
673
|
+
queryClient.invalidateQueries({
|
|
674
|
+
queryKey: ["greatchat", "channels"]
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
function useLogoutChannel(config) {
|
|
680
|
+
const client = useGchatClient(config);
|
|
681
|
+
const queryClient = useQueryClient4();
|
|
682
|
+
return useMutation4({
|
|
683
|
+
mutationFn: (channelId) => client.logoutChannel(config.accountId, channelId),
|
|
684
|
+
onSuccess: () => {
|
|
685
|
+
queryClient.invalidateQueries({
|
|
686
|
+
queryKey: ["greatchat", "channel-status"]
|
|
687
|
+
});
|
|
688
|
+
queryClient.invalidateQueries({
|
|
689
|
+
queryKey: ["greatchat", "channels"]
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// src/components/chat-view.tsx
|
|
696
|
+
import { useRef as useRef2, useEffect } from "react";
|
|
697
|
+
|
|
698
|
+
// src/components/message-bubble.tsx
|
|
699
|
+
import { useState } from "react";
|
|
700
|
+
import {
|
|
701
|
+
Check as Check2,
|
|
702
|
+
CheckCheck,
|
|
703
|
+
AlertCircle,
|
|
704
|
+
Clock,
|
|
705
|
+
Video,
|
|
706
|
+
File,
|
|
707
|
+
MapPin,
|
|
708
|
+
Headphones,
|
|
709
|
+
RotateCcw,
|
|
710
|
+
MoreHorizontal,
|
|
711
|
+
Trash2,
|
|
712
|
+
Pencil,
|
|
713
|
+
Ban
|
|
714
|
+
} from "lucide-react";
|
|
715
|
+
|
|
716
|
+
// src/components/ui/dropdown-menu.tsx
|
|
717
|
+
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui";
|
|
718
|
+
import { Check, ChevronRight } from "lucide-react";
|
|
719
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
720
|
+
function DropdownMenu({
|
|
721
|
+
...props
|
|
722
|
+
}) {
|
|
723
|
+
return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Root, { "data-slot": "dropdown-menu", ...props });
|
|
724
|
+
}
|
|
725
|
+
function DropdownMenuTrigger({
|
|
726
|
+
...props
|
|
727
|
+
}) {
|
|
728
|
+
return /* @__PURE__ */ jsx(
|
|
729
|
+
DropdownMenuPrimitive.Trigger,
|
|
730
|
+
{
|
|
731
|
+
"data-slot": "dropdown-menu-trigger",
|
|
732
|
+
...props
|
|
733
|
+
}
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
function DropdownMenuContent({
|
|
737
|
+
className,
|
|
738
|
+
align = "start",
|
|
739
|
+
sideOffset = 4,
|
|
740
|
+
...props
|
|
741
|
+
}) {
|
|
742
|
+
return /* @__PURE__ */ jsx(DropdownMenuPrimitive.Portal, { children: /* @__PURE__ */ jsx(
|
|
743
|
+
DropdownMenuPrimitive.Content,
|
|
744
|
+
{
|
|
745
|
+
"data-slot": "dropdown-menu-content",
|
|
746
|
+
sideOffset,
|
|
747
|
+
align,
|
|
748
|
+
className: cn(
|
|
749
|
+
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 bg-popover text-popover-foreground min-w-32 rounded-md p-1 shadow-md ring-1 duration-100 z-50 overflow-x-hidden overflow-y-auto",
|
|
750
|
+
className
|
|
751
|
+
),
|
|
752
|
+
...props
|
|
753
|
+
}
|
|
754
|
+
) });
|
|
755
|
+
}
|
|
756
|
+
function DropdownMenuItem({
|
|
757
|
+
className,
|
|
758
|
+
inset,
|
|
759
|
+
variant = "default",
|
|
760
|
+
...props
|
|
761
|
+
}) {
|
|
762
|
+
return /* @__PURE__ */ jsx(
|
|
763
|
+
DropdownMenuPrimitive.Item,
|
|
764
|
+
{
|
|
765
|
+
"data-slot": "dropdown-menu-item",
|
|
766
|
+
"data-inset": inset,
|
|
767
|
+
"data-variant": variant,
|
|
768
|
+
className: cn(
|
|
769
|
+
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive gap-2 rounded-sm px-2 py-1.5 text-sm data-inset:pl-8 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
770
|
+
className
|
|
771
|
+
),
|
|
772
|
+
...props
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// src/components/ui/alert-dialog.tsx
|
|
778
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui";
|
|
779
|
+
|
|
780
|
+
// src/components/ui/button.tsx
|
|
781
|
+
import { cva } from "class-variance-authority";
|
|
782
|
+
import { Slot } from "radix-ui";
|
|
783
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
784
|
+
var buttonVariants = cva(
|
|
785
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border border-transparent text-sm font-medium focus-visible:ring-3 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none select-none",
|
|
786
|
+
{
|
|
787
|
+
variants: {
|
|
788
|
+
variant: {
|
|
789
|
+
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
790
|
+
outline: "border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 shadow-xs",
|
|
791
|
+
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
|
792
|
+
ghost: "hover:bg-muted hover:text-foreground dark:hover:bg-muted/50",
|
|
793
|
+
destructive: "bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:bg-destructive/20 text-destructive",
|
|
794
|
+
link: "text-primary underline-offset-4 hover:underline"
|
|
795
|
+
},
|
|
796
|
+
size: {
|
|
797
|
+
default: "h-9 gap-1.5 px-2.5",
|
|
798
|
+
xs: "h-6 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3",
|
|
799
|
+
sm: "h-8 gap-1 px-2.5",
|
|
800
|
+
lg: "h-10 gap-1.5 px-2.5",
|
|
801
|
+
icon: "size-9",
|
|
802
|
+
"icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
|
|
803
|
+
"icon-sm": "size-8",
|
|
804
|
+
"icon-lg": "size-10"
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
defaultVariants: {
|
|
808
|
+
variant: "default",
|
|
809
|
+
size: "default"
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
function Button({
|
|
814
|
+
className,
|
|
815
|
+
variant = "default",
|
|
816
|
+
size = "default",
|
|
817
|
+
asChild = false,
|
|
818
|
+
...props
|
|
819
|
+
}) {
|
|
820
|
+
const Comp = asChild ? Slot.Root : "button";
|
|
821
|
+
return /* @__PURE__ */ jsx2(
|
|
822
|
+
Comp,
|
|
823
|
+
{
|
|
824
|
+
"data-slot": "button",
|
|
825
|
+
className: cn(buttonVariants({ variant, size, className })),
|
|
826
|
+
...props
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/components/ui/alert-dialog.tsx
|
|
832
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
833
|
+
function AlertDialog({
|
|
834
|
+
...props
|
|
835
|
+
}) {
|
|
836
|
+
return /* @__PURE__ */ jsx3(AlertDialogPrimitive.Root, { "data-slot": "alert-dialog", ...props });
|
|
837
|
+
}
|
|
838
|
+
function AlertDialogPortal({
|
|
839
|
+
...props
|
|
840
|
+
}) {
|
|
841
|
+
return /* @__PURE__ */ jsx3(AlertDialogPrimitive.Portal, { "data-slot": "alert-dialog-portal", ...props });
|
|
842
|
+
}
|
|
843
|
+
function AlertDialogOverlay({
|
|
844
|
+
className,
|
|
845
|
+
...props
|
|
846
|
+
}) {
|
|
847
|
+
return /* @__PURE__ */ jsx3(
|
|
848
|
+
AlertDialogPrimitive.Overlay,
|
|
849
|
+
{
|
|
850
|
+
"data-slot": "alert-dialog-overlay",
|
|
851
|
+
className: cn(
|
|
852
|
+
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 z-50",
|
|
853
|
+
className
|
|
854
|
+
),
|
|
855
|
+
...props
|
|
856
|
+
}
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
function AlertDialogContent({
|
|
860
|
+
className,
|
|
861
|
+
...props
|
|
862
|
+
}) {
|
|
863
|
+
return /* @__PURE__ */ jsxs2(AlertDialogPortal, { children: [
|
|
864
|
+
/* @__PURE__ */ jsx3(AlertDialogOverlay, {}),
|
|
865
|
+
/* @__PURE__ */ jsx3(
|
|
866
|
+
AlertDialogPrimitive.Content,
|
|
867
|
+
{
|
|
868
|
+
"data-slot": "alert-dialog-content",
|
|
869
|
+
className: cn(
|
|
870
|
+
"data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-background ring-foreground/10 gap-6 rounded-xl p-6 ring-1 duration-100 sm:max-w-lg fixed top-1/2 left-1/2 z-50 grid w-full max-w-xs -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
871
|
+
className
|
|
872
|
+
),
|
|
873
|
+
...props
|
|
874
|
+
}
|
|
875
|
+
)
|
|
876
|
+
] });
|
|
877
|
+
}
|
|
878
|
+
function AlertDialogHeader({
|
|
879
|
+
className,
|
|
880
|
+
...props
|
|
881
|
+
}) {
|
|
882
|
+
return /* @__PURE__ */ jsx3(
|
|
883
|
+
"div",
|
|
884
|
+
{
|
|
885
|
+
"data-slot": "alert-dialog-header",
|
|
886
|
+
className: cn("grid gap-1.5 text-center sm:text-left", className),
|
|
887
|
+
...props
|
|
888
|
+
}
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
function AlertDialogFooter({
|
|
892
|
+
className,
|
|
893
|
+
...props
|
|
894
|
+
}) {
|
|
895
|
+
return /* @__PURE__ */ jsx3(
|
|
896
|
+
"div",
|
|
897
|
+
{
|
|
898
|
+
"data-slot": "alert-dialog-footer",
|
|
899
|
+
className: cn(
|
|
900
|
+
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
901
|
+
className
|
|
902
|
+
),
|
|
903
|
+
...props
|
|
904
|
+
}
|
|
905
|
+
);
|
|
906
|
+
}
|
|
907
|
+
function AlertDialogTitle({
|
|
908
|
+
className,
|
|
909
|
+
...props
|
|
910
|
+
}) {
|
|
911
|
+
return /* @__PURE__ */ jsx3(
|
|
912
|
+
AlertDialogPrimitive.Title,
|
|
913
|
+
{
|
|
914
|
+
"data-slot": "alert-dialog-title",
|
|
915
|
+
className: cn("text-lg font-medium", className),
|
|
916
|
+
...props
|
|
917
|
+
}
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
function AlertDialogDescription({
|
|
921
|
+
className,
|
|
922
|
+
...props
|
|
923
|
+
}) {
|
|
924
|
+
return /* @__PURE__ */ jsx3(
|
|
925
|
+
AlertDialogPrimitive.Description,
|
|
926
|
+
{
|
|
927
|
+
"data-slot": "alert-dialog-description",
|
|
928
|
+
className: cn("text-muted-foreground text-sm", className),
|
|
929
|
+
...props
|
|
930
|
+
}
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
function AlertDialogAction({
|
|
934
|
+
className,
|
|
935
|
+
variant = "default",
|
|
936
|
+
size = "default",
|
|
937
|
+
...props
|
|
938
|
+
}) {
|
|
939
|
+
return /* @__PURE__ */ jsx3(Button, { variant, size, asChild: true, children: /* @__PURE__ */ jsx3(
|
|
940
|
+
AlertDialogPrimitive.Action,
|
|
941
|
+
{
|
|
942
|
+
"data-slot": "alert-dialog-action",
|
|
943
|
+
className: cn(className),
|
|
944
|
+
...props
|
|
945
|
+
}
|
|
946
|
+
) });
|
|
947
|
+
}
|
|
948
|
+
function AlertDialogCancel({
|
|
949
|
+
className,
|
|
950
|
+
variant = "outline",
|
|
951
|
+
size = "default",
|
|
952
|
+
...props
|
|
953
|
+
}) {
|
|
954
|
+
return /* @__PURE__ */ jsx3(Button, { variant, size, asChild: true, children: /* @__PURE__ */ jsx3(
|
|
955
|
+
AlertDialogPrimitive.Cancel,
|
|
956
|
+
{
|
|
957
|
+
"data-slot": "alert-dialog-cancel",
|
|
958
|
+
className: cn(className),
|
|
959
|
+
...props
|
|
960
|
+
}
|
|
961
|
+
) });
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/components/ui/textarea.tsx
|
|
965
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
966
|
+
function Textarea({ className, ...props }) {
|
|
967
|
+
return /* @__PURE__ */ jsx4(
|
|
968
|
+
"textarea",
|
|
969
|
+
{
|
|
970
|
+
"data-slot": "textarea",
|
|
971
|
+
className: cn(
|
|
972
|
+
"border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-3 md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
|
973
|
+
className
|
|
974
|
+
),
|
|
975
|
+
...props
|
|
976
|
+
}
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/components/message-bubble.tsx
|
|
981
|
+
import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
982
|
+
var statusIcons = {
|
|
983
|
+
pending: /* @__PURE__ */ jsx5(Clock, { className: "h-3 w-3 animate-pulse" }),
|
|
984
|
+
sent: /* @__PURE__ */ jsx5(Check2, { className: "h-3 w-3" }),
|
|
985
|
+
delivered: /* @__PURE__ */ jsx5(CheckCheck, { className: "h-3 w-3" }),
|
|
986
|
+
read: /* @__PURE__ */ jsx5(CheckCheck, { className: "h-3 w-3 text-blue-500" }),
|
|
987
|
+
failed: /* @__PURE__ */ jsx5(AlertCircle, { className: "h-3 w-3 text-destructive" })
|
|
988
|
+
};
|
|
989
|
+
function parseMetadata(metadata) {
|
|
990
|
+
if (!metadata) return {};
|
|
991
|
+
try {
|
|
992
|
+
return JSON.parse(metadata);
|
|
993
|
+
} catch {
|
|
994
|
+
return {};
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
function MediaContent({ message }) {
|
|
998
|
+
switch (message.content_type) {
|
|
999
|
+
case "image":
|
|
1000
|
+
return /* @__PURE__ */ jsxs3("div", { className: "space-y-1", children: [
|
|
1001
|
+
message.content_url && /* @__PURE__ */ jsx5(
|
|
1002
|
+
"img",
|
|
1003
|
+
{
|
|
1004
|
+
src: message.content_url,
|
|
1005
|
+
alt: "Imagem",
|
|
1006
|
+
className: "max-w-[240px] rounded-md"
|
|
1007
|
+
}
|
|
1008
|
+
),
|
|
1009
|
+
message.content && /* @__PURE__ */ jsx5("p", { className: "text-sm", children: message.content })
|
|
1010
|
+
] });
|
|
1011
|
+
case "audio":
|
|
1012
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
1013
|
+
/* @__PURE__ */ jsx5(Headphones, { className: "h-4 w-4" }),
|
|
1014
|
+
/* @__PURE__ */ jsx5("span", { className: "text-sm", children: "Mensagem de \xE1udio" })
|
|
1015
|
+
] });
|
|
1016
|
+
case "video":
|
|
1017
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
1018
|
+
/* @__PURE__ */ jsx5(Video, { className: "h-4 w-4" }),
|
|
1019
|
+
/* @__PURE__ */ jsx5("span", { className: "text-sm", children: "V\xEDdeo" }),
|
|
1020
|
+
message.content && /* @__PURE__ */ jsx5("p", { className: "text-sm", children: message.content })
|
|
1021
|
+
] });
|
|
1022
|
+
case "document": {
|
|
1023
|
+
const meta = parseMetadata(message.metadata);
|
|
1024
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
1025
|
+
/* @__PURE__ */ jsx5(File, { className: "h-4 w-4" }),
|
|
1026
|
+
/* @__PURE__ */ jsx5("span", { className: "text-sm", children: meta.filename || "Documento" })
|
|
1027
|
+
] });
|
|
1028
|
+
}
|
|
1029
|
+
case "location":
|
|
1030
|
+
return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
|
|
1031
|
+
/* @__PURE__ */ jsx5(MapPin, { className: "h-4 w-4" }),
|
|
1032
|
+
/* @__PURE__ */ jsx5("span", { className: "text-sm", children: "Localiza\xE7\xE3o" })
|
|
1033
|
+
] });
|
|
1034
|
+
default:
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
function MessageBubble({
|
|
1039
|
+
message,
|
|
1040
|
+
onRetry,
|
|
1041
|
+
onRevoke,
|
|
1042
|
+
onEdit,
|
|
1043
|
+
renderActions
|
|
1044
|
+
}) {
|
|
1045
|
+
const [showRevokeDialog, setShowRevokeDialog] = useState(false);
|
|
1046
|
+
const [editing, setEditing] = useState(false);
|
|
1047
|
+
const [editContent, setEditContent] = useState(message.content || "");
|
|
1048
|
+
const isOutbound = message.direction === "outbound";
|
|
1049
|
+
const isPending = message.status === "pending";
|
|
1050
|
+
const isFailed = message.status === "failed";
|
|
1051
|
+
const time = formatMessageTime(message.datetime_add);
|
|
1052
|
+
const meta = parseMetadata(message.metadata);
|
|
1053
|
+
const isRevoked = meta.revoked === true;
|
|
1054
|
+
const isEdited = meta.edited === true;
|
|
1055
|
+
const agentLabel = isOutbound && message.source !== "contact" ? meta.agent_name || (message.source === "bot" ? "Bot" : "Agente") : null;
|
|
1056
|
+
const canAct = isOutbound && message.id > 0 && !isPending && !isFailed && !isRevoked && !!message.external_id;
|
|
1057
|
+
const canEdit = canAct && message.content_type === "text";
|
|
1058
|
+
function handleSaveEdit() {
|
|
1059
|
+
const trimmed = editContent.trim();
|
|
1060
|
+
if (!trimmed || trimmed === message.content) {
|
|
1061
|
+
setEditing(false);
|
|
1062
|
+
return;
|
|
1063
|
+
}
|
|
1064
|
+
onEdit?.(message, trimmed);
|
|
1065
|
+
setEditing(false);
|
|
1066
|
+
}
|
|
1067
|
+
if (isRevoked) {
|
|
1068
|
+
return /* @__PURE__ */ jsx5(
|
|
1069
|
+
"div",
|
|
1070
|
+
{
|
|
1071
|
+
className: cn("flex", isOutbound ? "justify-end" : "justify-start"),
|
|
1072
|
+
children: /* @__PURE__ */ jsx5("div", { className: "max-w-[75%]", children: /* @__PURE__ */ jsxs3(
|
|
1073
|
+
"div",
|
|
1074
|
+
{
|
|
1075
|
+
className: cn(
|
|
1076
|
+
"rounded-lg px-3 py-2",
|
|
1077
|
+
isOutbound ? "bg-primary/40" : "bg-muted/60"
|
|
1078
|
+
),
|
|
1079
|
+
children: [
|
|
1080
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-1.5", children: [
|
|
1081
|
+
/* @__PURE__ */ jsx5(
|
|
1082
|
+
Ban,
|
|
1083
|
+
{
|
|
1084
|
+
className: cn(
|
|
1085
|
+
"h-3.5 w-3.5",
|
|
1086
|
+
isOutbound ? "text-primary-foreground/50" : "text-muted-foreground"
|
|
1087
|
+
)
|
|
1088
|
+
}
|
|
1089
|
+
),
|
|
1090
|
+
/* @__PURE__ */ jsx5(
|
|
1091
|
+
"p",
|
|
1092
|
+
{
|
|
1093
|
+
className: cn(
|
|
1094
|
+
"text-sm italic",
|
|
1095
|
+
isOutbound ? "text-primary-foreground/50" : "text-muted-foreground"
|
|
1096
|
+
),
|
|
1097
|
+
children: "Mensagem apagada"
|
|
1098
|
+
}
|
|
1099
|
+
)
|
|
1100
|
+
] }),
|
|
1101
|
+
/* @__PURE__ */ jsx5(
|
|
1102
|
+
"div",
|
|
1103
|
+
{
|
|
1104
|
+
className: cn(
|
|
1105
|
+
"mt-1 flex items-center justify-end gap-1",
|
|
1106
|
+
isOutbound ? "text-primary-foreground/40" : "text-muted-foreground/60"
|
|
1107
|
+
),
|
|
1108
|
+
children: /* @__PURE__ */ jsx5("span", { className: "text-[10px]", children: time })
|
|
1109
|
+
}
|
|
1110
|
+
)
|
|
1111
|
+
]
|
|
1112
|
+
}
|
|
1113
|
+
) })
|
|
1114
|
+
}
|
|
1115
|
+
);
|
|
1116
|
+
}
|
|
1117
|
+
return /* @__PURE__ */ jsxs3(
|
|
1118
|
+
"div",
|
|
1119
|
+
{
|
|
1120
|
+
className: cn(
|
|
1121
|
+
"flex group",
|
|
1122
|
+
isOutbound ? "justify-end" : "justify-start"
|
|
1123
|
+
),
|
|
1124
|
+
children: [
|
|
1125
|
+
/* @__PURE__ */ jsxs3("div", { className: "max-w-[75%] relative", children: [
|
|
1126
|
+
canAct && /* @__PURE__ */ jsx5("div", { className: "absolute -top-1 right-1 opacity-0 group-hover:opacity-100 transition-opacity z-10", children: /* @__PURE__ */ jsxs3(DropdownMenu, { children: [
|
|
1127
|
+
/* @__PURE__ */ jsx5(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx5("button", { className: "h-6 w-6 rounded-full bg-background/80 shadow-sm flex items-center justify-center hover:bg-background", children: /* @__PURE__ */ jsx5(MoreHorizontal, { className: "h-3.5 w-3.5 text-muted-foreground" }) }) }),
|
|
1128
|
+
/* @__PURE__ */ jsxs3(DropdownMenuContent, { align: "end", className: "w-40", children: [
|
|
1129
|
+
canEdit && /* @__PURE__ */ jsxs3(
|
|
1130
|
+
DropdownMenuItem,
|
|
1131
|
+
{
|
|
1132
|
+
onSelect: (e) => {
|
|
1133
|
+
e.preventDefault();
|
|
1134
|
+
setEditing(true);
|
|
1135
|
+
setEditContent(message.content || "");
|
|
1136
|
+
},
|
|
1137
|
+
children: [
|
|
1138
|
+
/* @__PURE__ */ jsx5(Pencil, { className: "h-4 w-4 mr-2" }),
|
|
1139
|
+
"Editar"
|
|
1140
|
+
]
|
|
1141
|
+
}
|
|
1142
|
+
),
|
|
1143
|
+
/* @__PURE__ */ jsxs3(
|
|
1144
|
+
DropdownMenuItem,
|
|
1145
|
+
{
|
|
1146
|
+
onSelect: (e) => {
|
|
1147
|
+
e.preventDefault();
|
|
1148
|
+
setShowRevokeDialog(true);
|
|
1149
|
+
},
|
|
1150
|
+
className: "text-destructive focus:text-destructive",
|
|
1151
|
+
children: [
|
|
1152
|
+
/* @__PURE__ */ jsx5(Trash2, { className: "h-4 w-4 mr-2" }),
|
|
1153
|
+
"Apagar para todos"
|
|
1154
|
+
]
|
|
1155
|
+
}
|
|
1156
|
+
)
|
|
1157
|
+
] })
|
|
1158
|
+
] }) }),
|
|
1159
|
+
renderActions?.(message),
|
|
1160
|
+
/* @__PURE__ */ jsxs3(
|
|
1161
|
+
"div",
|
|
1162
|
+
{
|
|
1163
|
+
className: cn(
|
|
1164
|
+
"rounded-lg px-3 py-2",
|
|
1165
|
+
isOutbound ? "bg-primary text-primary-foreground" : "bg-muted",
|
|
1166
|
+
isPending && "opacity-70",
|
|
1167
|
+
isFailed && "bg-destructive/10 border border-destructive/30"
|
|
1168
|
+
),
|
|
1169
|
+
children: [
|
|
1170
|
+
agentLabel && /* @__PURE__ */ jsx5(
|
|
1171
|
+
"p",
|
|
1172
|
+
{
|
|
1173
|
+
className: cn(
|
|
1174
|
+
"mb-0.5 text-[10px] font-medium",
|
|
1175
|
+
isFailed ? "text-destructive/70" : isOutbound ? "text-primary-foreground/70" : "text-muted-foreground"
|
|
1176
|
+
),
|
|
1177
|
+
children: agentLabel
|
|
1178
|
+
}
|
|
1179
|
+
),
|
|
1180
|
+
editing ? /* @__PURE__ */ jsxs3("div", { className: "space-y-2", children: [
|
|
1181
|
+
/* @__PURE__ */ jsx5(
|
|
1182
|
+
Textarea,
|
|
1183
|
+
{
|
|
1184
|
+
value: editContent,
|
|
1185
|
+
onChange: (e) => setEditContent(e.target.value),
|
|
1186
|
+
rows: 2,
|
|
1187
|
+
className: "min-h-[36px] max-h-[120px] resize-none bg-background text-foreground text-sm",
|
|
1188
|
+
autoFocus: true,
|
|
1189
|
+
onKeyDown: (e) => {
|
|
1190
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
1191
|
+
e.preventDefault();
|
|
1192
|
+
handleSaveEdit();
|
|
1193
|
+
}
|
|
1194
|
+
if (e.key === "Escape") {
|
|
1195
|
+
setEditing(false);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
),
|
|
1200
|
+
/* @__PURE__ */ jsxs3("div", { className: "flex justify-end gap-1.5", children: [
|
|
1201
|
+
/* @__PURE__ */ jsx5(
|
|
1202
|
+
Button,
|
|
1203
|
+
{
|
|
1204
|
+
size: "sm",
|
|
1205
|
+
variant: "ghost",
|
|
1206
|
+
className: "h-6 text-xs px-2",
|
|
1207
|
+
onClick: () => setEditing(false),
|
|
1208
|
+
children: "Cancelar"
|
|
1209
|
+
}
|
|
1210
|
+
),
|
|
1211
|
+
/* @__PURE__ */ jsx5(
|
|
1212
|
+
Button,
|
|
1213
|
+
{
|
|
1214
|
+
size: "sm",
|
|
1215
|
+
className: "h-6 text-xs px-2",
|
|
1216
|
+
onClick: handleSaveEdit,
|
|
1217
|
+
disabled: !editContent.trim() || editContent.trim() === message.content,
|
|
1218
|
+
children: "Salvar"
|
|
1219
|
+
}
|
|
1220
|
+
)
|
|
1221
|
+
] })
|
|
1222
|
+
] }) : message.content_type === "text" ? /* @__PURE__ */ jsx5(
|
|
1223
|
+
"p",
|
|
1224
|
+
{
|
|
1225
|
+
className: cn(
|
|
1226
|
+
"text-sm whitespace-pre-wrap break-words",
|
|
1227
|
+
isFailed && "text-foreground"
|
|
1228
|
+
),
|
|
1229
|
+
children: message.content
|
|
1230
|
+
}
|
|
1231
|
+
) : /* @__PURE__ */ jsx5(MediaContent, { message }),
|
|
1232
|
+
!editing && /* @__PURE__ */ jsxs3(
|
|
1233
|
+
"div",
|
|
1234
|
+
{
|
|
1235
|
+
className: cn(
|
|
1236
|
+
"mt-1 flex items-center justify-end gap-1",
|
|
1237
|
+
isFailed ? "text-destructive" : isOutbound ? "text-primary-foreground/60" : "text-muted-foreground"
|
|
1238
|
+
),
|
|
1239
|
+
children: [
|
|
1240
|
+
isEdited && /* @__PURE__ */ jsx5("span", { className: "text-[10px] italic", children: "editada" }),
|
|
1241
|
+
/* @__PURE__ */ jsx5("span", { className: "text-[10px]", children: time }),
|
|
1242
|
+
isOutbound && statusIcons[message.status]
|
|
1243
|
+
]
|
|
1244
|
+
}
|
|
1245
|
+
)
|
|
1246
|
+
]
|
|
1247
|
+
}
|
|
1248
|
+
),
|
|
1249
|
+
isFailed && /* @__PURE__ */ jsxs3("div", { className: "mt-1 flex items-center gap-1.5 justify-end px-1", children: [
|
|
1250
|
+
/* @__PURE__ */ jsx5(AlertCircle, { className: "h-3 w-3 shrink-0 text-destructive" }),
|
|
1251
|
+
/* @__PURE__ */ jsx5("span", { className: "text-[11px] text-destructive", children: message._error || "Erro ao enviar" }),
|
|
1252
|
+
onRetry && /* @__PURE__ */ jsxs3(
|
|
1253
|
+
"button",
|
|
1254
|
+
{
|
|
1255
|
+
onClick: () => onRetry(message),
|
|
1256
|
+
className: "inline-flex items-center gap-0.5 text-[11px] text-destructive font-medium hover:text-destructive/80 hover:underline cursor-pointer ml-1",
|
|
1257
|
+
children: [
|
|
1258
|
+
/* @__PURE__ */ jsx5(RotateCcw, { className: "h-3 w-3" }),
|
|
1259
|
+
"Reenviar"
|
|
1260
|
+
]
|
|
1261
|
+
}
|
|
1262
|
+
)
|
|
1263
|
+
] })
|
|
1264
|
+
] }),
|
|
1265
|
+
/* @__PURE__ */ jsx5(AlertDialog, { open: showRevokeDialog, onOpenChange: setShowRevokeDialog, children: /* @__PURE__ */ jsxs3(AlertDialogContent, { children: [
|
|
1266
|
+
/* @__PURE__ */ jsxs3(AlertDialogHeader, { children: [
|
|
1267
|
+
/* @__PURE__ */ jsx5(AlertDialogTitle, { children: "Apagar mensagem?" }),
|
|
1268
|
+
/* @__PURE__ */ jsx5(AlertDialogDescription, { children: 'A mensagem ser\xE1 apagada para todos no WhatsApp. Ela continuar\xE1 vis\xEDvel aqui como "mensagem apagada".' })
|
|
1269
|
+
] }),
|
|
1270
|
+
/* @__PURE__ */ jsxs3(AlertDialogFooter, { children: [
|
|
1271
|
+
/* @__PURE__ */ jsx5(AlertDialogCancel, { children: "Cancelar" }),
|
|
1272
|
+
/* @__PURE__ */ jsx5(
|
|
1273
|
+
AlertDialogAction,
|
|
1274
|
+
{
|
|
1275
|
+
className: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
|
1276
|
+
onClick: () => onRevoke?.(message),
|
|
1277
|
+
children: "Apagar para todos"
|
|
1278
|
+
}
|
|
1279
|
+
)
|
|
1280
|
+
] })
|
|
1281
|
+
] }) })
|
|
1282
|
+
]
|
|
1283
|
+
}
|
|
1284
|
+
);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// src/components/chat-input.tsx
|
|
1288
|
+
import { useState as useState2, useRef } from "react";
|
|
1289
|
+
import { Send } from "lucide-react";
|
|
1290
|
+
import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1291
|
+
function ChatInput({ onSend, disabled }) {
|
|
1292
|
+
const [content, setContent] = useState2("");
|
|
1293
|
+
const textareaRef = useRef(null);
|
|
1294
|
+
function handleSend() {
|
|
1295
|
+
const text = content.trim();
|
|
1296
|
+
if (!text || disabled) return;
|
|
1297
|
+
setContent("");
|
|
1298
|
+
onSend(text);
|
|
1299
|
+
textareaRef.current?.focus();
|
|
1300
|
+
}
|
|
1301
|
+
function handleKeyDown(e) {
|
|
1302
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
1303
|
+
e.preventDefault();
|
|
1304
|
+
handleSend();
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
return /* @__PURE__ */ jsx6("div", { className: "border-t p-3", children: /* @__PURE__ */ jsxs4("div", { className: "flex items-end gap-2", children: [
|
|
1308
|
+
/* @__PURE__ */ jsx6(
|
|
1309
|
+
Textarea,
|
|
1310
|
+
{
|
|
1311
|
+
ref: textareaRef,
|
|
1312
|
+
placeholder: "Digite uma mensagem...",
|
|
1313
|
+
value: content,
|
|
1314
|
+
onChange: (e) => setContent(e.target.value),
|
|
1315
|
+
onKeyDown: handleKeyDown,
|
|
1316
|
+
rows: 1,
|
|
1317
|
+
className: "min-h-[40px] max-h-[120px] resize-none"
|
|
1318
|
+
}
|
|
1319
|
+
),
|
|
1320
|
+
/* @__PURE__ */ jsx6(
|
|
1321
|
+
Button,
|
|
1322
|
+
{
|
|
1323
|
+
size: "icon",
|
|
1324
|
+
onClick: handleSend,
|
|
1325
|
+
disabled: !content.trim() || disabled,
|
|
1326
|
+
className: "shrink-0",
|
|
1327
|
+
children: /* @__PURE__ */ jsx6(Send, { className: "h-4 w-4" })
|
|
1328
|
+
}
|
|
1329
|
+
)
|
|
1330
|
+
] }) });
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/components/ui/skeleton.tsx
|
|
1334
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
1335
|
+
function Skeleton({
|
|
1336
|
+
className,
|
|
1337
|
+
...props
|
|
1338
|
+
}) {
|
|
1339
|
+
return /* @__PURE__ */ jsx7(
|
|
1340
|
+
"div",
|
|
1341
|
+
{
|
|
1342
|
+
"data-slot": "skeleton",
|
|
1343
|
+
className: cn("bg-muted rounded-md animate-pulse", className),
|
|
1344
|
+
...props
|
|
1345
|
+
}
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
// src/components/ui/select.tsx
|
|
1350
|
+
import { Select as SelectPrimitive } from "radix-ui";
|
|
1351
|
+
import { Check as Check3, ChevronDown, ChevronUp, ChevronsUpDown } from "lucide-react";
|
|
1352
|
+
import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1353
|
+
function Select({
|
|
1354
|
+
...props
|
|
1355
|
+
}) {
|
|
1356
|
+
return /* @__PURE__ */ jsx8(SelectPrimitive.Root, { "data-slot": "select", ...props });
|
|
1357
|
+
}
|
|
1358
|
+
function SelectValue({
|
|
1359
|
+
...props
|
|
1360
|
+
}) {
|
|
1361
|
+
return /* @__PURE__ */ jsx8(SelectPrimitive.Value, { "data-slot": "select-value", ...props });
|
|
1362
|
+
}
|
|
1363
|
+
function SelectTrigger({
|
|
1364
|
+
className,
|
|
1365
|
+
size = "default",
|
|
1366
|
+
children,
|
|
1367
|
+
...props
|
|
1368
|
+
}) {
|
|
1369
|
+
return /* @__PURE__ */ jsxs5(
|
|
1370
|
+
SelectPrimitive.Trigger,
|
|
1371
|
+
{
|
|
1372
|
+
"data-slot": "select-trigger",
|
|
1373
|
+
"data-size": size,
|
|
1374
|
+
className: cn(
|
|
1375
|
+
"border-input data-placeholder:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 gap-1.5 rounded-md border bg-transparent py-2 pr-2 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] focus-visible:ring-3 data-[size=default]:h-9 data-[size=sm]:h-8 [&_svg:not([class*='size-'])]:size-4 flex w-fit items-center justify-between whitespace-nowrap outline-none disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
1376
|
+
className
|
|
1377
|
+
),
|
|
1378
|
+
...props,
|
|
1379
|
+
children: [
|
|
1380
|
+
children,
|
|
1381
|
+
/* @__PURE__ */ jsx8(SelectPrimitive.Icon, { asChild: true, children: /* @__PURE__ */ jsx8(ChevronsUpDown, { className: "text-muted-foreground size-4 pointer-events-none" }) })
|
|
1382
|
+
]
|
|
1383
|
+
}
|
|
1384
|
+
);
|
|
1385
|
+
}
|
|
1386
|
+
function SelectContent({
|
|
1387
|
+
className,
|
|
1388
|
+
children,
|
|
1389
|
+
position = "item-aligned",
|
|
1390
|
+
align = "center",
|
|
1391
|
+
...props
|
|
1392
|
+
}) {
|
|
1393
|
+
return /* @__PURE__ */ jsx8(SelectPrimitive.Portal, { children: /* @__PURE__ */ jsxs5(
|
|
1394
|
+
SelectPrimitive.Content,
|
|
1395
|
+
{
|
|
1396
|
+
"data-slot": "select-content",
|
|
1397
|
+
className: cn(
|
|
1398
|
+
"bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/10 min-w-36 rounded-md shadow-md ring-1 duration-100 relative z-50 max-h-(--radix-select-content-available-height) overflow-x-hidden overflow-y-auto",
|
|
1399
|
+
position === "popper" && "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
|
1400
|
+
className
|
|
1401
|
+
),
|
|
1402
|
+
position,
|
|
1403
|
+
align,
|
|
1404
|
+
...props,
|
|
1405
|
+
children: [
|
|
1406
|
+
/* @__PURE__ */ jsx8(SelectScrollUpButton, {}),
|
|
1407
|
+
/* @__PURE__ */ jsx8(SelectPrimitive.Viewport, { children }),
|
|
1408
|
+
/* @__PURE__ */ jsx8(SelectScrollDownButton, {})
|
|
1409
|
+
]
|
|
1410
|
+
}
|
|
1411
|
+
) });
|
|
1412
|
+
}
|
|
1413
|
+
function SelectItem({
|
|
1414
|
+
className,
|
|
1415
|
+
children,
|
|
1416
|
+
...props
|
|
1417
|
+
}) {
|
|
1418
|
+
return /* @__PURE__ */ jsxs5(
|
|
1419
|
+
SelectPrimitive.Item,
|
|
1420
|
+
{
|
|
1421
|
+
"data-slot": "select-item",
|
|
1422
|
+
className: cn(
|
|
1423
|
+
"focus:bg-accent focus:text-accent-foreground gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm [&_svg:not([class*='size-'])]:size-4 relative flex w-full cursor-default items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
1424
|
+
className
|
|
1425
|
+
),
|
|
1426
|
+
...props,
|
|
1427
|
+
children: [
|
|
1428
|
+
/* @__PURE__ */ jsx8("span", { className: "pointer-events-none absolute right-2 flex size-4 items-center justify-center", children: /* @__PURE__ */ jsx8(SelectPrimitive.ItemIndicator, { children: /* @__PURE__ */ jsx8(Check3, { className: "pointer-events-none" }) }) }),
|
|
1429
|
+
/* @__PURE__ */ jsx8(SelectPrimitive.ItemText, { children })
|
|
1430
|
+
]
|
|
1431
|
+
}
|
|
1432
|
+
);
|
|
1433
|
+
}
|
|
1434
|
+
function SelectScrollUpButton({
|
|
1435
|
+
className,
|
|
1436
|
+
...props
|
|
1437
|
+
}) {
|
|
1438
|
+
return /* @__PURE__ */ jsx8(
|
|
1439
|
+
SelectPrimitive.ScrollUpButton,
|
|
1440
|
+
{
|
|
1441
|
+
"data-slot": "select-scroll-up-button",
|
|
1442
|
+
className: cn(
|
|
1443
|
+
"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
1444
|
+
className
|
|
1445
|
+
),
|
|
1446
|
+
...props,
|
|
1447
|
+
children: /* @__PURE__ */ jsx8(ChevronUp, {})
|
|
1448
|
+
}
|
|
1449
|
+
);
|
|
1450
|
+
}
|
|
1451
|
+
function SelectScrollDownButton({
|
|
1452
|
+
className,
|
|
1453
|
+
...props
|
|
1454
|
+
}) {
|
|
1455
|
+
return /* @__PURE__ */ jsx8(
|
|
1456
|
+
SelectPrimitive.ScrollDownButton,
|
|
1457
|
+
{
|
|
1458
|
+
"data-slot": "select-scroll-down-button",
|
|
1459
|
+
className: cn(
|
|
1460
|
+
"bg-popover z-10 flex cursor-default items-center justify-center py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
1461
|
+
className
|
|
1462
|
+
),
|
|
1463
|
+
...props,
|
|
1464
|
+
children: /* @__PURE__ */ jsx8(ChevronDown, {})
|
|
1465
|
+
}
|
|
1466
|
+
);
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/components/chat-view.tsx
|
|
1470
|
+
import { jsx as jsx9, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1471
|
+
function ChatView({
|
|
1472
|
+
messages,
|
|
1473
|
+
isLoading,
|
|
1474
|
+
inboxStatus,
|
|
1475
|
+
onStatusChange,
|
|
1476
|
+
onSend,
|
|
1477
|
+
onRetry,
|
|
1478
|
+
onRevoke,
|
|
1479
|
+
onEdit,
|
|
1480
|
+
sendDisabled,
|
|
1481
|
+
renderHeader,
|
|
1482
|
+
renderStatusDropdown
|
|
1483
|
+
}) {
|
|
1484
|
+
const scrollRef = useRef2(null);
|
|
1485
|
+
const prevMessageCount = useRef2(0);
|
|
1486
|
+
useEffect(() => {
|
|
1487
|
+
if (messages && messages.length > prevMessageCount.current) {
|
|
1488
|
+
const el = scrollRef.current;
|
|
1489
|
+
if (el) {
|
|
1490
|
+
setTimeout(() => {
|
|
1491
|
+
el.scrollTop = el.scrollHeight;
|
|
1492
|
+
}, 50);
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
prevMessageCount.current = messages?.length || 0;
|
|
1496
|
+
}, [messages]);
|
|
1497
|
+
const groups = messages ? groupMessagesByDate(messages) : [];
|
|
1498
|
+
return /* @__PURE__ */ jsxs6("div", { className: "flex h-full flex-col", children: [
|
|
1499
|
+
renderHeader !== void 0 ? renderHeader : /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
|
|
1500
|
+
/* @__PURE__ */ jsx9("div", { className: "flex-1" }),
|
|
1501
|
+
renderStatusDropdown !== void 0 ? renderStatusDropdown : onStatusChange ? /* @__PURE__ */ jsxs6(
|
|
1502
|
+
Select,
|
|
1503
|
+
{
|
|
1504
|
+
value: inboxStatus,
|
|
1505
|
+
onValueChange: onStatusChange,
|
|
1506
|
+
children: [
|
|
1507
|
+
/* @__PURE__ */ jsx9(SelectTrigger, { className: "w-[130px] h-8 text-xs", children: /* @__PURE__ */ jsx9(SelectValue, { placeholder: "Status" }) }),
|
|
1508
|
+
/* @__PURE__ */ jsxs6(SelectContent, { children: [
|
|
1509
|
+
/* @__PURE__ */ jsx9(SelectItem, { value: "open", children: "Aberta" }),
|
|
1510
|
+
/* @__PURE__ */ jsx9(SelectItem, { value: "pending", children: "Pendente" }),
|
|
1511
|
+
/* @__PURE__ */ jsx9(SelectItem, { value: "resolved", children: "Resolvida" })
|
|
1512
|
+
] })
|
|
1513
|
+
]
|
|
1514
|
+
}
|
|
1515
|
+
) : null
|
|
1516
|
+
] }),
|
|
1517
|
+
/* @__PURE__ */ jsx9("div", { ref: scrollRef, className: "flex-1 overflow-y-auto p-4", children: isLoading ? /* @__PURE__ */ jsx9("div", { className: "space-y-4", children: Array.from({ length: 4 }).map((_, i) => /* @__PURE__ */ jsx9(
|
|
1518
|
+
"div",
|
|
1519
|
+
{
|
|
1520
|
+
className: `flex ${i % 2 === 0 ? "justify-start" : "justify-end"}`,
|
|
1521
|
+
children: /* @__PURE__ */ jsx9(Skeleton, { className: "h-12 w-64 rounded-lg" })
|
|
1522
|
+
},
|
|
1523
|
+
i
|
|
1524
|
+
)) }) : groups.length === 0 ? /* @__PURE__ */ jsx9("div", { className: "flex h-full items-center justify-center text-sm text-muted-foreground", children: "Nenhuma mensagem ainda" }) : /* @__PURE__ */ jsx9("div", { className: "space-y-4", children: groups.map((group, gi) => /* @__PURE__ */ jsxs6("div", { children: [
|
|
1525
|
+
/* @__PURE__ */ jsx9("div", { className: "mb-3 flex justify-center", children: /* @__PURE__ */ jsx9("span", { className: "rounded-full bg-muted px-3 py-1 text-[11px] text-muted-foreground", children: formatDateGroup(group.date) }) }),
|
|
1526
|
+
/* @__PURE__ */ jsx9("div", { className: "space-y-1", children: group.messages.map((msg) => /* @__PURE__ */ jsx9(
|
|
1527
|
+
MessageBubble,
|
|
1528
|
+
{
|
|
1529
|
+
message: msg,
|
|
1530
|
+
onRetry,
|
|
1531
|
+
onRevoke,
|
|
1532
|
+
onEdit
|
|
1533
|
+
},
|
|
1534
|
+
msg.id
|
|
1535
|
+
)) })
|
|
1536
|
+
] }, gi)) }) }),
|
|
1537
|
+
/* @__PURE__ */ jsx9(ChatInput, { onSend, disabled: sendDisabled })
|
|
1538
|
+
] });
|
|
1539
|
+
}
|
|
104
1540
|
export {
|
|
1541
|
+
ChatInput,
|
|
1542
|
+
ChatView,
|
|
1543
|
+
DEFAULT_CHANNEL_STATUS_POLLING,
|
|
1544
|
+
DEFAULT_INBOX_POLLING,
|
|
1545
|
+
DEFAULT_MESSAGES_POLLING,
|
|
1546
|
+
DEFAULT_QR_POLLING,
|
|
1547
|
+
MessageBubble,
|
|
105
1548
|
cn,
|
|
106
|
-
createGchatClient
|
|
1549
|
+
createGchatClient,
|
|
1550
|
+
formatDateGroup,
|
|
1551
|
+
formatMessageTime,
|
|
1552
|
+
groupMessagesByDate,
|
|
1553
|
+
useChannelQR,
|
|
1554
|
+
useChannelWhatsappStatus,
|
|
1555
|
+
useChannels,
|
|
1556
|
+
useConnectChannel,
|
|
1557
|
+
useContacts,
|
|
1558
|
+
useCreateChannel,
|
|
1559
|
+
useCreateContact,
|
|
1560
|
+
useCreateInbox,
|
|
1561
|
+
useDeleteChannel,
|
|
1562
|
+
useDeleteContact,
|
|
1563
|
+
useDeleteInbox,
|
|
1564
|
+
useDisconnectChannel,
|
|
1565
|
+
useEditMessage,
|
|
1566
|
+
useGetContact,
|
|
1567
|
+
useInbox,
|
|
1568
|
+
useInboxMessages,
|
|
1569
|
+
useInboxStats,
|
|
1570
|
+
useInboxes,
|
|
1571
|
+
useLogoutChannel,
|
|
1572
|
+
useRetryMessage,
|
|
1573
|
+
useRevokeMessage,
|
|
1574
|
+
useSendMessage,
|
|
1575
|
+
useUpdateChannel,
|
|
1576
|
+
useUpdateContact,
|
|
1577
|
+
useUpdateInbox
|
|
107
1578
|
};
|
|
108
1579
|
//# sourceMappingURL=index.js.map
|