@adatechnology/conversations-ui 0.1.0-rc.26 → 0.1.0-rc.27

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.
Files changed (75) hide show
  1. package/dist/chunk-DXPSPUWF.js +110 -0
  2. package/dist/{chunk-TV4OQRGH.js → chunk-UZJBYD5O.js} +123 -45
  3. package/dist/{chunk-2AYDBWNE.js → chunk-WCBDXZ3X.js} +13 -3
  4. package/dist/flows/index.d.ts +2 -0
  5. package/dist/flows/index.js +61 -18
  6. package/dist/index.d.ts +37 -10
  7. package/dist/index.js +694 -297
  8. package/dist/preview/index.js +3 -3
  9. package/dist/styles.css +119 -6
  10. package/package.json +1 -1
  11. package/src/AudioPlayer.tsx +8 -0
  12. package/src/AudioRecorderButton.test.tsx +3 -3
  13. package/src/AudioRecorderButton.tsx +3 -3
  14. package/src/AudioTranscription.tsx +4 -1
  15. package/src/Avatar.tsx +1 -1
  16. package/src/ConversationContextPanel.tsx +2 -2
  17. package/src/ConversationDocumentsPanel.tsx +11 -6
  18. package/src/ConversationHeader.test.tsx +66 -0
  19. package/src/ConversationHeader.tsx +147 -47
  20. package/src/ConversationListItem.tsx +3 -2
  21. package/src/ConversationRow.tsx +31 -7
  22. package/src/DocumentsLibrary.tsx +9 -4
  23. package/src/EmojiPicker.tsx +2 -1
  24. package/src/InteractiveMessage.tsx +3 -0
  25. package/src/Lightbox.tsx +1 -1
  26. package/src/MediaRenderer.tsx +2 -0
  27. package/src/MessageBubble.test.tsx +41 -0
  28. package/src/MessageBubble.tsx +27 -8
  29. package/src/MessageComposer.tsx +11 -8
  30. package/src/RichMessageComposer.test.tsx +42 -12
  31. package/src/RichMessageComposer.tsx +216 -45
  32. package/src/SimpleEmojiPicker.tsx +5 -3
  33. package/src/StatusTicks.tsx +1 -1
  34. package/src/Toast.tsx +4 -0
  35. package/src/Tooltip.test.ts +42 -0
  36. package/src/Tooltip.tsx +164 -0
  37. package/src/WhatsAppMessageEditor.tsx +4 -4
  38. package/src/WindowExpiredNotice.tsx +12 -4
  39. package/src/composer.constant.ts +33 -0
  40. package/src/conversationWindow.ts +7 -5
  41. package/src/documents/DocumentsWorkspace.tsx +10 -6
  42. package/src/flows/FlowGroupHeader.tsx +2 -2
  43. package/src/flows/FlowMapNode.tsx +2 -1
  44. package/src/flows/FlowNodeCard.tsx +3 -3
  45. package/src/flows/FlowNodePanel.tsx +6 -5
  46. package/src/flows/FlowPalette.tsx +7 -1
  47. package/src/flows/FlowPortalNode.tsx +1 -1
  48. package/src/flows/FlowsWorkspace.tsx +13 -4
  49. package/src/flows/labels.ts +4 -0
  50. package/src/hooks/useContainerWidth.ts +35 -0
  51. package/src/hooks/useConversationRealtime.ts +10 -8
  52. package/src/icon.constant.ts +12 -0
  53. package/src/index.ts +1 -0
  54. package/src/lib/composer-formatting.test.ts +78 -0
  55. package/src/lib/composer-formatting.ts +145 -0
  56. package/src/lib/whatsapp-formatting.test.tsx +37 -0
  57. package/src/lib/whatsapp-formatting.tsx +28 -3
  58. package/src/listing/index.tsx +5 -1
  59. package/src/pagination.constant.ts +10 -0
  60. package/src/preview/ConversationSimulatorPanel.tsx +1 -1
  61. package/src/providers/ConversationsProvider.tsx +8 -6
  62. package/src/settings/MessagesWorkspace.tsx +3 -0
  63. package/src/settings/TopicsForm.tsx +2 -0
  64. package/src/settings/TranscriptionSettingsForm.test.tsx +1 -1
  65. package/src/settings/TranscriptionSettingsForm.tsx +1 -0
  66. package/src/settings/WelcomeFarewellForm.tsx +1 -0
  67. package/src/settings/WhatsAppCreateTemplateForm.tsx +1 -0
  68. package/src/settings/WhatsAppTemplateSettingsForm.tsx +5 -2
  69. package/src/settings/WhatsAppTemplatesSettings.tsx +2 -0
  70. package/src/styles.css +100 -4
  71. package/src/workspace/BulkTemplateModal.tsx +4 -2
  72. package/src/workspace/ConversationPane.tsx +49 -19
  73. package/src/workspace/ConversationsInboxList.tsx +17 -8
  74. package/src/workspace/ConversationsWorkspace.tsx +24 -16
  75. package/src/workspace/useConversationsInbox.ts +5 -2
package/dist/index.js CHANGED
@@ -1,7 +1,17 @@
1
+ import {
2
+ TOOLTIP_ATTRIBUTE,
3
+ TooltipLayer
4
+ } from "./chunk-DXPSPUWF.js";
1
5
  import {
2
6
  AudioPlayer,
3
7
  AudioRecorderButton,
4
8
  AudioTranscription,
9
+ COMPOSER_BAR_CLASS,
10
+ COMPOSER_COMPACT_WIDTH,
11
+ COMPOSER_MONOSPACE_CLASS,
12
+ COMPOSER_TOOL_BUTTON_ACTIVE_CLASS,
13
+ COMPOSER_TOOL_BUTTON_CLASS,
14
+ COMPOSER_TOOL_BUTTON_IDLE_CLASS,
5
15
  ConversationDocumentsPanel,
6
16
  ConversationLocalesProvider,
7
17
  ConversationWallpaper,
@@ -26,6 +36,8 @@ import {
26
36
  MediaRenderer,
27
37
  MessageBubble,
28
38
  MessageComposer,
39
+ PAGINATION_LABELS,
40
+ QUICK_REPLY_PILL_CLASS,
29
41
  StatusTicks,
30
42
  applyQuickReplyVariables,
31
43
  cn,
@@ -45,22 +57,123 @@ import {
45
57
  useConversationDocuments,
46
58
  useConversationLocales,
47
59
  useConversations
48
- } from "./chunk-TV4OQRGH.js";
60
+ } from "./chunk-UZJBYD5O.js";
49
61
  import {
62
+ ZERO_WIDTH_SPACE,
50
63
  htmlToWA,
51
64
  parseWhatsAppFormatting,
52
65
  useDarkMode,
53
66
  useIsDarkTheme,
54
67
  waToHTML,
55
68
  waToHTMLInline
56
- } from "./chunk-2AYDBWNE.js";
69
+ } from "./chunk-WCBDXZ3X.js";
57
70
 
58
71
  // src/RichMessageComposer.tsx
59
- import { forwardRef, useCallback, useEffect as useEffect2, useImperativeHandle, useRef as useRef2, useState as useState2 } from "react";
60
- import { Bold, Italic, Strikethrough, Code, Paperclip, SendHorizonal, Braces } from "lucide-react";
72
+ import { forwardRef, useCallback, useEffect as useEffect3, useImperativeHandle, useRef as useRef2, useState as useState3 } from "react";
73
+ import { Bold, Italic, Strikethrough, Code, Paperclip, SendHorizonal, Braces, Type } from "lucide-react";
74
+
75
+ // src/hooks/useContainerWidth.ts
76
+ import { useEffect, useState } from "react";
77
+ function useContainerWidth(ref) {
78
+ const [width, setWidth] = useState(void 0);
79
+ useEffect(() => {
80
+ const element = ref.current;
81
+ if (!element || typeof ResizeObserver === "undefined") return;
82
+ const measure = () => setWidth(element.getBoundingClientRect().width);
83
+ measure();
84
+ const observer = new ResizeObserver(measure);
85
+ observer.observe(element);
86
+ return () => observer.disconnect();
87
+ }, [ref]);
88
+ return width;
89
+ }
90
+
91
+ // src/lib/composer-formatting.ts
92
+ var FORMATTING_ACTION = {
93
+ BOLD: "bold",
94
+ ITALIC: "italic",
95
+ STRIKETHROUGH: "strikethrough",
96
+ MONOSPACE: "monospace"
97
+ };
98
+ var EXEC_COMMAND_BY_ACTION = {
99
+ [FORMATTING_ACTION.BOLD]: "bold",
100
+ [FORMATTING_ACTION.ITALIC]: "italic",
101
+ [FORMATTING_ACTION.STRIKETHROUGH]: "strikeThrough"
102
+ };
103
+ var FORMATTING_SEPARATOR = "|";
104
+ function codeAncestorOf({ node, editor }) {
105
+ let current = node;
106
+ while (current && current !== editor) {
107
+ if (current instanceof HTMLElement && current.tagName === "CODE") return current;
108
+ current = current.parentNode;
109
+ }
110
+ return void 0;
111
+ }
112
+ function canQueryCommandState() {
113
+ return typeof document !== "undefined" && typeof document.queryCommandState === "function";
114
+ }
115
+ function canExecuteFormattingCommand() {
116
+ return typeof document !== "undefined" && typeof document.execCommand === "function";
117
+ }
118
+ function isCommandActive(command) {
119
+ if (!canQueryCommandState()) return false;
120
+ try {
121
+ return document.queryCommandState(command);
122
+ } catch {
123
+ return false;
124
+ }
125
+ }
126
+ function activeFormattingIn(editor) {
127
+ const selection = typeof window !== "undefined" ? window.getSelection() : null;
128
+ const anchor = selection?.anchorNode;
129
+ if (!anchor || !editor.contains(anchor)) return "";
130
+ const active = [];
131
+ for (const [action, command] of Object.entries(EXEC_COMMAND_BY_ACTION)) {
132
+ if (isCommandActive(command)) active.push(action);
133
+ }
134
+ if (codeAncestorOf({ node: anchor, editor })) active.push(FORMATTING_ACTION.MONOSPACE);
135
+ return active.join(FORMATTING_SEPARATOR);
136
+ }
137
+ function isFormattingActive({ active, action }) {
138
+ return active.split(FORMATTING_SEPARATOR).includes(action);
139
+ }
140
+ function startMonospaceAt(range) {
141
+ const code = document.createElement("code");
142
+ code.className = COMPOSER_MONOSPACE_CLASS;
143
+ code.textContent = ZERO_WIDTH_SPACE;
144
+ range.deleteContents();
145
+ range.insertNode(code);
146
+ placeCaretAfter(code.firstChild, ZERO_WIDTH_SPACE.length);
147
+ }
148
+ function exitMonospaceAfter(code) {
149
+ const anchor = document.createTextNode(ZERO_WIDTH_SPACE);
150
+ code.after(anchor);
151
+ placeCaretAfter(anchor, ZERO_WIDTH_SPACE.length);
152
+ }
153
+ function unwrapMonospace(code) {
154
+ code.replaceWith(document.createTextNode(code.textContent?.replace(ZERO_WIDTH_SPACE, "") ?? ""));
155
+ }
156
+ function placeCaretAfter(node, offset) {
157
+ const caret = document.createRange();
158
+ caret.setStart(node, offset);
159
+ caret.collapse(true);
160
+ const selection = window.getSelection();
161
+ selection?.removeAllRanges();
162
+ selection?.addRange(caret);
163
+ }
164
+ function toggleFormattingCommand(action) {
165
+ const command = EXEC_COMMAND_BY_ACTION[action];
166
+ if (!command || !canExecuteFormattingCommand()) return false;
167
+ try {
168
+ document.execCommand("styleWithCSS", false, "false");
169
+ return document.execCommand(command);
170
+ } catch {
171
+ return false;
172
+ }
173
+ }
61
174
 
62
175
  // src/SimpleEmojiPicker.tsx
63
- import { useEffect, useRef, useState } from "react";
176
+ import { useEffect as useEffect2, useRef, useState as useState2 } from "react";
64
177
  import { Smile } from "lucide-react";
65
178
  import { jsx, jsxs } from "react/jsx-runtime";
66
179
  var EMOJIS = [
@@ -146,9 +259,9 @@ var EMOJIS = [
146
259
  "\u{1F6E1}\uFE0F"
147
260
  ];
148
261
  function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px", pickerMaxHeight = "220px" }) {
149
- const [open, setOpen] = useState(false);
262
+ const [open, setOpen] = useState2(false);
150
263
  const containerRef = useRef(null);
151
- useEffect(() => {
264
+ useEffect2(() => {
152
265
  if (!open) return;
153
266
  const handleClickOutside = (e) => {
154
267
  if (containerRef.current && !containerRef.current.contains(e.target)) {
@@ -164,9 +277,10 @@ function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px",
164
277
  {
165
278
  type: "button",
166
279
  onClick: () => setOpen((v) => !v),
167
- className: "w-7 h-7 flex items-center justify-center rounded-lg text-gray-400 dark:text-gray-500 hover:text-teal-600 dark:hover:text-teal-400 hover:bg-teal-50 dark:hover:bg-teal-900/30 transition-colors",
168
- title: label,
169
- children: /* @__PURE__ */ jsx(Smile, { size: 15 })
280
+ className: "w-9 h-9 flex items-center justify-center rounded-full text-gray-400 dark:text-gray-500 hover:text-teal-600 dark:hover:text-teal-400 hover:bg-teal-50 dark:hover:bg-teal-900/30 transition-colors",
281
+ "data-cv-tooltip": label,
282
+ "aria-label": label,
283
+ children: /* @__PURE__ */ jsx(Smile, { size: 18 })
170
284
  }
171
285
  ),
172
286
  open && /* @__PURE__ */ jsx("div", { className: "absolute bottom-full left-1/2 -translate-x-1/2 mb-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-600 rounded-xl shadow-xl p-2 z-50", children: /* @__PURE__ */ jsx(
@@ -177,6 +291,8 @@ function SimpleEmojiPicker({ onSelect, label = "Emojis", pickerWidth = "280px",
177
291
  children: EMOJIS.map((emoji) => /* @__PURE__ */ jsx(
178
292
  "button",
179
293
  {
294
+ "data-cv-tooltip": emoji,
295
+ "aria-label": emoji,
180
296
  type: "button",
181
297
  onClick: () => {
182
298
  onSelect(emoji);
@@ -211,7 +327,8 @@ var DEFAULT_RICH_COMPOSER_TOOLTIPS = {
211
327
  emoji: "Emojis",
212
328
  attach: "Anexar arquivo",
213
329
  send: "Enviar",
214
- variables: "Vari\xE1veis dispon\xEDveis"
330
+ variables: "Vari\xE1veis dispon\xEDveis",
331
+ formatting: "Formata\xE7\xE3o"
215
332
  };
216
333
  var FORMATTING_ELEMENT = {
217
334
  bold: "strong",
@@ -219,7 +336,6 @@ var FORMATTING_ELEMENT = {
219
336
  strikethrough: "del",
220
337
  monospace: "code"
221
338
  };
222
- var MONOSPACE_CLASS = "bg-black/5 dark:bg-white/10 rounded px-0.5 font-mono text-sm";
223
339
  var RichMessageComposer = forwardRef(function RichMessageComposer2({
224
340
  value,
225
341
  onChange,
@@ -234,14 +350,41 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
234
350
  isSending = false,
235
351
  idleAction,
236
352
  attachmentsPreview,
353
+ hasQueuedAttachments = false,
237
354
  acceptedFileTypes = DEFAULT_ACCEPTED_FILE_TYPES,
238
355
  className
239
356
  }, ref) {
240
357
  const editorRef = useRef2(null);
241
358
  const fileInputRef = useRef2(null);
242
359
  const variablesRef = useRef2(null);
243
- const [isVariablesOpen, setIsVariablesOpen] = useState2(false);
244
- useEffect2(() => {
360
+ const formattingRef = useRef2(null);
361
+ const barRef = useRef2(null);
362
+ const [isVariablesOpen, setIsVariablesOpen] = useState3(false);
363
+ const [isFormattingOpen, setIsFormattingOpen] = useState3(false);
364
+ const [activeFormatting, setActiveFormatting] = useState3("");
365
+ const barWidth = useContainerWidth(barRef);
366
+ const isCompact = barWidth !== void 0 && barWidth < COMPOSER_COMPACT_WIDTH;
367
+ const lastEmittedRef = useRef2("");
368
+ const emitChange = useCallback((html) => {
369
+ const text = htmlToWA(html);
370
+ lastEmittedRef.current = text;
371
+ onChange(text);
372
+ }, [onChange]);
373
+ useEffect3(() => {
374
+ const editor = editorRef.current;
375
+ if (!editor || value === lastEmittedRef.current) return;
376
+ lastEmittedRef.current = value;
377
+ editor.innerHTML = waToHTML(value);
378
+ }, [value]);
379
+ const refreshActiveFormatting = useCallback(() => {
380
+ const editor = editorRef.current;
381
+ if (editor) setActiveFormatting(activeFormattingIn(editor));
382
+ }, []);
383
+ useEffect3(() => {
384
+ document.addEventListener("selectionchange", refreshActiveFormatting);
385
+ return () => document.removeEventListener("selectionchange", refreshActiveFormatting);
386
+ }, [refreshActiveFormatting]);
387
+ useEffect3(() => {
245
388
  if (!isVariablesOpen) return;
246
389
  const closeOnOutsideClick = (event) => {
247
390
  if (!variablesRef.current?.contains(event.target)) setIsVariablesOpen(false);
@@ -249,25 +392,36 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
249
392
  document.addEventListener("mousedown", closeOnOutsideClick);
250
393
  return () => document.removeEventListener("mousedown", closeOnOutsideClick);
251
394
  }, [isVariablesOpen]);
395
+ useEffect3(() => {
396
+ if (!isFormattingOpen) return;
397
+ const closeOnOutsideClick = (event) => {
398
+ if (!formattingRef.current?.contains(event.target)) setIsFormattingOpen(false);
399
+ };
400
+ document.addEventListener("mousedown", closeOnOutsideClick);
401
+ return () => document.removeEventListener("mousedown", closeOnOutsideClick);
402
+ }, [isFormattingOpen]);
403
+ useEffect3(() => {
404
+ if (!isCompact) setIsFormattingOpen(false);
405
+ }, [isCompact]);
252
406
  const tooltipOf = (action) => tooltips?.[action] ?? DEFAULT_RICH_COMPOSER_TOOLTIPS[action];
253
407
  const shows = (action) => toolbar?.[action] !== false;
254
408
  const replaceContent = useCallback((text) => {
255
409
  const editor = editorRef.current;
256
410
  if (!editor) return;
257
411
  editor.innerHTML = waToHTML(text);
258
- onChange(htmlToWA(editor.innerHTML));
412
+ emitChange(editor.innerHTML);
259
413
  editor.focus();
260
- }, [onChange]);
414
+ }, [emitChange]);
261
415
  useImperativeHandle(ref, () => ({
262
416
  setContent: replaceContent,
263
417
  clear: () => {
264
418
  const editor = editorRef.current;
265
419
  if (!editor) return;
266
420
  editor.innerHTML = "";
267
- onChange("");
421
+ emitChange("");
268
422
  },
269
423
  focus: () => editorRef.current?.focus()
270
- }), [onChange, replaceContent]);
424
+ }), [emitChange, replaceContent]);
271
425
  const currentRange = useCallback(() => {
272
426
  const editor = editorRef.current;
273
427
  if (!editor) return void 0;
@@ -282,8 +436,8 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
282
436
  range.collapse(true);
283
437
  selection?.removeAllRanges();
284
438
  selection?.addRange(range);
285
- onChange(htmlToWA(editorRef.current?.innerHTML ?? ""));
286
- }, [onChange]);
439
+ emitChange(editorRef.current?.innerHTML ?? "");
440
+ }, [emitChange]);
287
441
  const insertAtCursor = useCallback((fragment) => {
288
442
  const range = currentRange();
289
443
  if (!range) return;
@@ -298,26 +452,74 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
298
452
  if (!range || !selectedText) return;
299
453
  range.deleteContents();
300
454
  const wrapper = document.createElement(FORMATTING_ELEMENT[action]);
301
- if (action === "monospace") wrapper.className = MONOSPACE_CLASS;
455
+ if (action === "monospace") wrapper.className = COMPOSER_MONOSPACE_CLASS;
302
456
  wrapper.textContent = selectedText;
303
457
  range.insertNode(wrapper);
304
458
  commitRange(range, wrapper);
305
459
  }, [commitRange, currentRange]);
460
+ const applyFormatting = useCallback((action) => {
461
+ const editor = editorRef.current;
462
+ const range = currentRange();
463
+ if (!editor || !range) return;
464
+ if (action === FORMATTING_ACTION.MONOSPACE) {
465
+ const code = codeAncestorOf({ node: range.startContainer, editor });
466
+ if (range.collapsed) {
467
+ if (code) exitMonospaceAfter(code);
468
+ else startMonospaceAt(range);
469
+ } else if (code) {
470
+ unwrapMonospace(code);
471
+ } else {
472
+ wrapSelection(action);
473
+ }
474
+ emitChange(editor.innerHTML);
475
+ refreshActiveFormatting();
476
+ return;
477
+ }
478
+ if (toggleFormattingCommand(action)) emitChange(editor.innerHTML);
479
+ else wrapSelection(action);
480
+ refreshActiveFormatting();
481
+ }, [currentRange, emitChange, refreshActiveFormatting, wrapSelection]);
306
482
  const handleInput = useCallback((event) => {
307
- onChange(htmlToWA(event.currentTarget.innerHTML));
308
- }, [onChange]);
483
+ emitChange(event.currentTarget.innerHTML);
484
+ refreshActiveFormatting();
485
+ }, [emitChange, refreshActiveFormatting]);
309
486
  const handleKeyDown = useCallback((event) => {
310
487
  if (event.key === "Enter" && !event.shiftKey) {
311
488
  event.preventDefault();
312
- if (!disabled) onSend();
489
+ if (!disabled && !isSending) onSend();
313
490
  }
314
- }, [disabled, onSend]);
491
+ }, [disabled, isSending, onSend]);
315
492
  const handleFileChange = useCallback((event) => {
316
493
  if (event.target.files?.length) onAttachFiles?.(event.target.files);
317
494
  if (fileInputRef.current) fileInputRef.current.value = "";
318
495
  }, [onAttachFiles]);
319
- const canSend = value.trim().length > 0;
320
- return /* @__PURE__ */ jsxs2("div", { className: cn("flex flex-col", className), children: [
496
+ const canSend = value.trim().length > 0 || hasQueuedAttachments;
497
+ const formattingButtons = [
498
+ [RICH_COMPOSER_ACTION.BOLD, Bold],
499
+ [RICH_COMPOSER_ACTION.ITALIC, Italic],
500
+ [RICH_COMPOSER_ACTION.STRIKETHROUGH, Strikethrough],
501
+ [RICH_COMPOSER_ACTION.MONOSPACE, Code]
502
+ ].filter(([action]) => shows(action)).map(([action, Icon]) => {
503
+ const isActive = isFormattingActive({ active: activeFormatting, action });
504
+ return /* @__PURE__ */ jsx2(
505
+ "button",
506
+ {
507
+ type: "button",
508
+ onMouseDown: (event) => event.preventDefault(),
509
+ onClick: () => applyFormatting(action),
510
+ "data-cv-tooltip": tooltipOf(action),
511
+ "aria-label": tooltipOf(action),
512
+ "aria-pressed": isActive,
513
+ className: cn(
514
+ COMPOSER_TOOL_BUTTON_CLASS,
515
+ isActive ? COMPOSER_TOOL_BUTTON_ACTIVE_CLASS : COMPOSER_TOOL_BUTTON_IDLE_CLASS
516
+ ),
517
+ children: /* @__PURE__ */ jsx2(Icon, { size: 18 })
518
+ },
519
+ action
520
+ );
521
+ });
522
+ return /* @__PURE__ */ jsxs2("div", { className: cn(COMPOSER_BAR_CLASS, "flex flex-col", className), children: [
321
523
  attachmentsPreview,
322
524
  quickReplies?.length ? (
323
525
  /* Uma linha só, rolando na horizontal. Deixar quebrar em várias linhas faz a barra crescer
@@ -327,32 +529,35 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
327
529
  "button",
328
530
  {
329
531
  type: "button",
330
- title: quickReply.tooltip,
532
+ "data-cv-tooltip": quickReply.tooltip,
533
+ "aria-label": quickReply.tooltip,
331
534
  onClick: () => replaceContent(quickReply.text),
332
- className: "flex-shrink-0 whitespace-nowrap rounded-full border border-gray-200 bg-white px-2 py-1 text-xs text-gray-600 transition-colors hover:border-teal-200 hover:bg-teal-50 hover:text-teal-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400 dark:hover:border-teal-800 dark:hover:bg-teal-950/40 dark:hover:text-teal-400",
535
+ className: cn(QUICK_REPLY_PILL_CLASS, "flex-shrink-0"),
333
536
  children: quickReply.label
334
537
  },
335
538
  quickReply.id
336
539
  )) })
337
540
  ) : null,
338
- /* @__PURE__ */ jsxs2("div", { className: "flex flex-wrap items-end gap-2", children: [
339
- /* @__PURE__ */ jsx2("div", { className: "mb-0.5 hidden items-center gap-0.5 sm:flex", children: [
340
- [RICH_COMPOSER_ACTION.BOLD, Bold],
341
- [RICH_COMPOSER_ACTION.ITALIC, Italic],
342
- [RICH_COMPOSER_ACTION.STRIKETHROUGH, Strikethrough],
343
- [RICH_COMPOSER_ACTION.MONOSPACE, Code]
344
- ].filter(([action]) => shows(action)).map(([action, Icon]) => /* @__PURE__ */ jsx2(
345
- "button",
346
- {
347
- type: "button",
348
- onClick: () => wrapSelection(action),
349
- title: tooltipOf(action),
350
- "aria-label": tooltipOf(action),
351
- className: "flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-lg text-gray-400 transition-colors hover:bg-teal-50 hover:text-teal-600 dark:text-gray-500 dark:hover:bg-teal-900/30 dark:hover:text-teal-400",
352
- children: /* @__PURE__ */ jsx2(Icon, { size: 15 })
353
- },
354
- action
355
- )) }),
541
+ /* @__PURE__ */ jsxs2("div", { ref: barRef, className: "flex flex-nowrap items-end gap-1", children: [
542
+ formattingButtons.length > 0 ? isCompact ? /* @__PURE__ */ jsxs2("div", { ref: formattingRef, className: "relative hidden flex-shrink-0 sm:block", children: [
543
+ /* @__PURE__ */ jsx2(
544
+ "button",
545
+ {
546
+ type: "button",
547
+ onMouseDown: (event) => event.preventDefault(),
548
+ onClick: () => setIsFormattingOpen((open) => !open),
549
+ "data-cv-tooltip": tooltipOf("formatting"),
550
+ "aria-label": tooltipOf("formatting"),
551
+ "aria-expanded": isFormattingOpen,
552
+ className: cn(
553
+ COMPOSER_TOOL_BUTTON_CLASS,
554
+ activeFormatting || isFormattingOpen ? COMPOSER_TOOL_BUTTON_ACTIVE_CLASS : COMPOSER_TOOL_BUTTON_IDLE_CLASS
555
+ ),
556
+ children: /* @__PURE__ */ jsx2(Type, { size: 18 })
557
+ }
558
+ ),
559
+ isFormattingOpen ? /* @__PURE__ */ jsx2("div", { className: "absolute bottom-full left-0 z-20 mb-2 flex items-center gap-1 rounded-full border border-gray-200 bg-white px-1 py-1 shadow-lg dark:border-gray-700 dark:bg-gray-800", children: formattingButtons }) : null
560
+ ] }) : /* @__PURE__ */ jsx2("div", { className: "hidden flex-shrink-0 items-center gap-1 sm:flex", children: formattingButtons }) : null,
356
561
  shows(RICH_COMPOSER_ACTION.EMOJI) ? /* @__PURE__ */ jsx2(SimpleEmojiPicker, { onSelect: insertAtCursor, label: tooltipOf("emoji") }) : null,
357
562
  variables?.length && shows(RICH_COMPOSER_ACTION.VARIABLES) ? /* @__PURE__ */ jsxs2("div", { ref: variablesRef, className: "relative", children: [
358
563
  /* @__PURE__ */ jsx2(
@@ -360,10 +565,10 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
360
565
  {
361
566
  type: "button",
362
567
  onClick: () => setIsVariablesOpen((open) => !open),
363
- title: tooltipOf("variables"),
568
+ "data-cv-tooltip": tooltipOf("variables"),
364
569
  "aria-label": tooltipOf("variables"),
365
570
  "aria-expanded": isVariablesOpen,
366
- className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-teal-50 hover:text-teal-600 dark:text-gray-500 dark:hover:bg-teal-900/30 dark:hover:text-teal-400",
571
+ className: cn(COMPOSER_TOOL_BUTTON_CLASS, COMPOSER_TOOL_BUTTON_IDLE_CLASS),
367
572
  children: /* @__PURE__ */ jsx2(Braces, { size: 18 })
368
573
  }
369
574
  ),
@@ -371,7 +576,8 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
371
576
  "button",
372
577
  {
373
578
  type: "button",
374
- title: variable.tooltip ?? variable.value,
579
+ "data-cv-tooltip": variable.tooltip ?? variable.value,
580
+ "aria-label": variable.tooltip ?? variable.value,
375
581
  onClick: () => {
376
582
  insertAtCursor(variable.value);
377
583
  setIsVariablesOpen(false);
@@ -402,9 +608,9 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
402
608
  {
403
609
  type: "button",
404
610
  onClick: () => fileInputRef.current?.click(),
405
- title: tooltipOf("attach"),
611
+ "data-cv-tooltip": tooltipOf("attach"),
406
612
  "aria-label": tooltipOf("attach"),
407
- className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full text-gray-400 transition-colors hover:bg-teal-50 hover:text-teal-600 dark:text-gray-500 dark:hover:bg-teal-900/30 dark:hover:text-teal-400",
613
+ className: cn(COMPOSER_TOOL_BUTTON_CLASS, COMPOSER_TOOL_BUTTON_IDLE_CLASS),
408
614
  children: /* @__PURE__ */ jsx2(Paperclip, { size: 18 })
409
615
  }
410
616
  )
@@ -422,7 +628,7 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
422
628
  onInput: handleInput,
423
629
  onKeyDown: handleKeyDown,
424
630
  style: { minHeight: "36px", maxHeight: "120px" },
425
- className: "flex-1 min-w-[180px] overflow-y-auto rounded-2xl border border-gray-200 bg-gray-50 px-4 py-2 text-sm text-gray-800 transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-teal-300 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-100 empty:before:content-[attr(data-placeholder)] empty:before:text-gray-400 dark:empty:before:text-gray-500 before:pointer-events-none [&_strong]:font-semibold [&_em]:italic [&_del]:line-through [&_code]:rounded [&_code]:bg-black/5 [&_code]:px-0.5 [&_code]:font-mono [&_code]:text-sm dark:[&_code]:bg-white/10"
631
+ className: "ml-1 min-w-0 flex-1 overflow-y-auto rounded-2xl border border-transparent bg-white px-4 py-2 text-sm text-gray-800 transition-all focus:border-transparent focus:outline-none focus:ring-2 focus:ring-teal-300 dark:border-gray-700 dark:bg-gray-700 dark:text-gray-100 empty:before:content-[attr(data-placeholder)] empty:before:text-gray-400 dark:empty:before:text-gray-500 before:pointer-events-none [&_strong]:font-semibold [&_b]:font-semibold [&_em]:italic [&_i]:italic [&_del]:line-through [&_s]:line-through [&_strike]:line-through [&_code]:rounded [&_code]:bg-black/5 [&_code]:px-0.5 [&_code]:font-mono [&_code]:text-sm dark:[&_code]:bg-white/10"
426
632
  }
427
633
  ),
428
634
  !canSend && idleAction ? /* @__PURE__ */ jsx2("div", { className: "flex-shrink-0", children: idleAction }) : /* @__PURE__ */ jsx2(
@@ -431,7 +637,7 @@ var RichMessageComposer = forwardRef(function RichMessageComposer2({
431
637
  type: "button",
432
638
  onClick: onSend,
433
639
  disabled: disabled || isSending || !canSend,
434
- title: tooltipOf("send"),
640
+ "data-cv-tooltip": tooltipOf("send"),
435
641
  "aria-label": tooltipOf("send"),
436
642
  className: "flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full bg-teal-600 text-white transition-colors hover:bg-teal-700 disabled:cursor-not-allowed disabled:opacity-40",
437
643
  children: isSending ? /* @__PURE__ */ jsx2("span", { className: "text-xs", children: "\u23F3" }) : /* @__PURE__ */ jsx2(SendHorizonal, { size: 16 })
@@ -523,9 +729,9 @@ function WhatsAppMessageEditor({
523
729
  const toolbarButtonClass = "inline-flex items-center justify-center h-8 w-8 rounded-lg border border-gray-200 dark:border-gray-600 text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors";
524
730
  return /* @__PURE__ */ jsxs3("div", { children: [
525
731
  /* @__PURE__ */ jsxs3("div", { className: "flex flex-wrap items-center gap-1.5 mb-2", children: [
526
- /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("*"), className: toolbarButtonClass, title: editorLabels.boldHint, "aria-label": editorLabels.bold, children: /* @__PURE__ */ jsx3(Bold2, { size: 15 }) }),
527
- /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("_"), className: toolbarButtonClass, title: editorLabels.italicHint, "aria-label": editorLabels.italic, children: /* @__PURE__ */ jsx3(Italic2, { size: 15 }) }),
528
- /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("~"), className: toolbarButtonClass, title: editorLabels.strikethroughHint, "aria-label": editorLabels.strikethrough, children: /* @__PURE__ */ jsx3(Strikethrough2, { size: 15 }) }),
732
+ /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("*"), className: toolbarButtonClass, "data-cv-tooltip": editorLabels.boldHint, "aria-label": editorLabels.bold, children: /* @__PURE__ */ jsx3(Bold2, { size: 15 }) }),
733
+ /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("_"), className: toolbarButtonClass, "data-cv-tooltip": editorLabels.italicHint, "aria-label": editorLabels.italic, children: /* @__PURE__ */ jsx3(Italic2, { size: 15 }) }),
734
+ /* @__PURE__ */ jsx3("button", { type: "button", onClick: () => wrapSelection("~"), className: toolbarButtonClass, "data-cv-tooltip": editorLabels.strikethroughHint, "aria-label": editorLabels.strikethrough, children: /* @__PURE__ */ jsx3(Strikethrough2, { size: 15 }) }),
529
735
  placeholders.length > 0 && /* @__PURE__ */ jsxs3(Fragment2, { children: [
530
736
  /* @__PURE__ */ jsx3("span", { className: "mx-1 h-5 w-px bg-gray-200 dark:bg-gray-600" }),
531
737
  placeholders.map((token) => /* @__PURE__ */ jsx3(
@@ -534,7 +740,8 @@ function WhatsAppMessageEditor({
534
740
  type: "button",
535
741
  onClick: () => insertAtCursor(token),
536
742
  className: "inline-flex items-center h-8 px-2 rounded-lg border border-gray-200 dark:border-gray-600 text-xs text-blue-600 dark:text-blue-300 hover:bg-blue-50 dark:hover:bg-blue-900/40 transition-colors",
537
- title: editorLabels.insertPlaceholder(token),
743
+ "data-cv-tooltip": editorLabels.insertPlaceholder(token),
744
+ "aria-label": editorLabels.insertPlaceholder(token),
538
745
  children: token
539
746
  },
540
747
  token
@@ -618,7 +825,7 @@ function Avatar({ name, avatarUrl, size = "md", className = "", labels }) {
618
825
  "div",
619
826
  {
620
827
  className: `${sizeClass} ${bgColor} rounded-full flex items-center justify-center text-white font-semibold flex-shrink-0 ${className}`,
621
- title: name,
828
+ "data-cv-tooltip": name,
622
829
  children: getInitials(name)
623
830
  }
624
831
  );
@@ -793,6 +1000,8 @@ var ConversationListItem = ({
793
1000
  return /* @__PURE__ */ jsxs4(
794
1001
  "button",
795
1002
  {
1003
+ "data-cv-tooltip": name,
1004
+ "aria-label": name,
796
1005
  onClick: handleClick,
797
1006
  className: `w-full flex items-center gap-3 px-3 py-2.5 text-left transition-colors ${highlightActive ? "hover:bg-[#f0f2f5]" : ""} ${showDivider ? "border-b border-[#e9edef]" : ""}`,
798
1007
  style: {
@@ -808,8 +1017,8 @@ var ConversationListItem = ({
808
1017
  /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1.5 min-w-0", children: [
809
1018
  !conversation.clientName && flag ? /* @__PURE__ */ jsx5("span", { "aria-hidden": true, children: flag }) : null,
810
1019
  /* @__PURE__ */ jsx5("span", { className: "text-[16px] text-[#111b21] truncate", children: name }),
811
- windowStatus && windowStatus.label === "expired" && /* @__PURE__ */ jsx5("span", { className: "w-2 h-2 rounded-full bg-red-500 flex-shrink-0", title: expiredWindowLabel }),
812
- windowStatus && windowStatus.label === "warning" && /* @__PURE__ */ jsx5("span", { className: "w-2 h-2 rounded-full bg-orange-500 flex-shrink-0", title: warningWindowLabel })
1020
+ windowStatus && windowStatus.label === "expired" && /* @__PURE__ */ jsx5("span", { className: "w-2 h-2 rounded-full bg-red-500 flex-shrink-0", "data-cv-tooltip": expiredWindowLabel }),
1021
+ windowStatus && windowStatus.label === "warning" && /* @__PURE__ */ jsx5("span", { className: "w-2 h-2 rounded-full bg-orange-500 flex-shrink-0", "data-cv-tooltip": warningWindowLabel })
813
1022
  ] }),
814
1023
  timestamp && /* @__PURE__ */ jsx5("span", { className: "text-xs text-[#667781] flex-shrink-0", children: timestamp })
815
1024
  ] }),
@@ -835,7 +1044,7 @@ var ConversationListItem = ({
835
1044
  };
836
1045
 
837
1046
  // src/Toast.tsx
838
- import { createContext, useContext, useState as useState3, useCallback as useCallback2, useEffect as useEffect3 } from "react";
1047
+ import { createContext, useContext, useState as useState4, useCallback as useCallback2, useEffect as useEffect4 } from "react";
839
1048
  import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
840
1049
  var ToastContext = createContext(null);
841
1050
  var nextId = 0;
@@ -847,6 +1056,7 @@ var toast = {
847
1056
  }
848
1057
  }
849
1058
  };
1059
+ var TOAST_CLOSE_LABEL = "Fechar aviso";
850
1060
  var typeStyles = {
851
1061
  success: "bg-green-600",
852
1062
  error: "bg-red-600",
@@ -865,8 +1075,8 @@ var typeIcons = {
865
1075
  ] })
866
1076
  };
867
1077
  function ToastItemComponent({ item, onRemove }) {
868
- const [exiting, setExiting] = useState3(false);
869
- useEffect3(() => {
1078
+ const [exiting, setExiting] = useState4(false);
1079
+ useEffect4(() => {
870
1080
  const timer = setTimeout(() => {
871
1081
  setExiting(true);
872
1082
  setTimeout(() => onRemove(item.id), 300);
@@ -887,6 +1097,8 @@ function ToastItemComponent({ item, onRemove }) {
887
1097
  setExiting(true);
888
1098
  setTimeout(() => onRemove(item.id), 300);
889
1099
  },
1100
+ "data-cv-tooltip": TOAST_CLOSE_LABEL,
1101
+ "aria-label": TOAST_CLOSE_LABEL,
890
1102
  className: "flex-shrink-0 opacity-70 hover:opacity-100 transition-opacity",
891
1103
  children: /* @__PURE__ */ jsxs5("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
892
1104
  /* @__PURE__ */ jsx6("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
@@ -899,7 +1111,7 @@ function ToastItemComponent({ item, onRemove }) {
899
1111
  );
900
1112
  }
901
1113
  function ToastProvider({ children }) {
902
- const [items, setItems] = useState3([]);
1114
+ const [items, setItems] = useState4([]);
903
1115
  const show = useCallback2((type, message) => {
904
1116
  const id = ++nextId;
905
1117
  setItems((prev) => [...prev, { id, type, message }]);
@@ -907,7 +1119,7 @@ function ToastProvider({ children }) {
907
1119
  const remove = useCallback2((id) => {
908
1120
  setItems((prev) => prev.filter((item) => item.id !== id));
909
1121
  }, []);
910
- useEffect3(() => {
1122
+ useEffect4(() => {
911
1123
  singletonShow = show;
912
1124
  return () => {
913
1125
  singletonShow = null;
@@ -939,10 +1151,10 @@ function useToast() {
939
1151
  }
940
1152
 
941
1153
  // src/MessageText.tsx
942
- import { useCallback as useCallback3, useState as useState4 } from "react";
1154
+ import { useCallback as useCallback3, useState as useState5 } from "react";
943
1155
  import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
944
1156
  function MessageText({ message }) {
945
- const [copied, setCopied] = useState4(false);
1157
+ const [copied, setCopied] = useState5(false);
946
1158
  const handleCopy = useCallback3(async () => {
947
1159
  if (!message.content) return;
948
1160
  try {
@@ -996,11 +1208,11 @@ var CONVERSATION_WINDOW = {
996
1208
  EXPIRED: "expired"
997
1209
  };
998
1210
  var WINDOW_FILTERS = [
999
- { value: CONVERSATION_WINDOW.ALL, label: "Todas", dotClass: "" },
1000
- { value: CONVERSATION_WINDOW.FRESH, label: "<12h", dotClass: "bg-green-500" },
1001
- { value: CONVERSATION_WINDOW.WARNING, label: "12-21h", dotClass: "bg-yellow-500" },
1002
- { value: CONVERSATION_WINDOW.CRITICAL, label: "21-24h", dotClass: "bg-red-500" },
1003
- { value: CONVERSATION_WINDOW.EXPIRED, label: ">24h", dotClass: "bg-gray-400" }
1211
+ { value: CONVERSATION_WINDOW.ALL, label: "Todas", tone: "" },
1212
+ { value: CONVERSATION_WINDOW.FRESH, label: "<12h", tone: "fresh" },
1213
+ { value: CONVERSATION_WINDOW.WARNING, label: "12-21h", tone: "warning" },
1214
+ { value: CONVERSATION_WINDOW.CRITICAL, label: "21-24h", tone: "critical" },
1215
+ { value: CONVERSATION_WINDOW.EXPIRED, label: ">24h", tone: "expired" }
1004
1216
  ];
1005
1217
  var WARNING_RATIO = 0.5;
1006
1218
  var CRITICAL_RATIO = 0.875;
@@ -1025,6 +1237,14 @@ function formatStalledFor(lastAt, now) {
1025
1237
  return `${minutes}m`;
1026
1238
  }
1027
1239
 
1240
+ // src/ConversationRow.tsx
1241
+ import { AlarmClock, Bot, Hourglass, Play, UserRound } from "lucide-react";
1242
+
1243
+ // src/icon.constant.ts
1244
+ var ICON_SIZE_PILL = 12;
1245
+ var ICON_SIZE_INLINE = 14;
1246
+ var ICON_SIZE_ACTION = 16;
1247
+
1028
1248
  // src/ChannelIcon.tsx
1029
1249
  import { Fragment as Fragment3, jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
1030
1250
  var CHANNEL_BRAND_COLOR = {
@@ -1093,6 +1313,7 @@ var WINDOW_TITLE = {
1093
1313
  [CONVERSATION_WINDOW.EXPIRED]: "Janela de 24h expirada \u2014 s\xF3 template"
1094
1314
  };
1095
1315
  var STALLED_THRESHOLD_MS = 60 * 60 * 1e3;
1316
+ var TAKEOVER_LABEL = "Continuar Atendimento";
1096
1317
  var WINDOW_BAR_CLASS = {
1097
1318
  [CONVERSATION_WINDOW.ALL]: "bg-transparent",
1098
1319
  [CONVERSATION_WINDOW.FRESH]: "bg-green-500",
@@ -1122,7 +1343,7 @@ function ConversationRow({
1122
1343
  "span",
1123
1344
  {
1124
1345
  className: cn("w-1 shrink-0", WINDOW_BAR_CLASS[window2], classNames?.windowBar),
1125
- title: WINDOW_TITLE[window2],
1346
+ "data-cv-tooltip": WINDOW_TITLE[window2],
1126
1347
  "aria-label": WINDOW_TITLE[window2]
1127
1348
  }
1128
1349
  ),
@@ -1155,25 +1376,56 @@ function ConversationRow({
1155
1376
  " ",
1156
1377
  capabilities.label
1157
1378
  ] }),
1158
- conversation.mode === "human" ? /* @__PURE__ */ jsx11("span", { className: "cv-pill cv-pill--success", children: "\u{1F9D1}\u200D\u{1F4BC} em atendimento" }) : null,
1159
- isWaiting ? /* @__PURE__ */ jsx11("span", { className: "cv-pill cv-pill--warning", children: "\u23F3 aguardando atendimento" }) : null,
1160
- !isWaiting && conversation.mode === "bot" ? /* @__PURE__ */ jsx11("span", { className: "cv-pill", children: "\u{1F916} bot ativo" }) : null,
1379
+ conversation.mode === "human" ? /* @__PURE__ */ jsxs9("span", { className: "cv-pill cv-pill--success", children: [
1380
+ /* @__PURE__ */ jsx11(UserRound, { size: ICON_SIZE_PILL, "aria-hidden": "true" }),
1381
+ " em atendimento"
1382
+ ] }) : null,
1383
+ isWaiting ? /* @__PURE__ */ jsxs9("span", { className: "cv-pill cv-pill--warning", children: [
1384
+ /* @__PURE__ */ jsx11(Hourglass, { size: ICON_SIZE_PILL, "aria-hidden": "true" }),
1385
+ " aguardando atendimento"
1386
+ ] }) : null,
1387
+ !isWaiting && conversation.mode === "bot" ? /* @__PURE__ */ jsxs9("span", { className: "cv-pill", children: [
1388
+ /* @__PURE__ */ jsx11(Bot, { size: ICON_SIZE_PILL, "aria-hidden": "true" }),
1389
+ " bot ativo"
1390
+ ] }) : null,
1161
1391
  isStalled ? /* @__PURE__ */ jsxs9("span", { className: "cv-pill cv-pill--danger", children: [
1162
- "\u23F1\uFE0F parada h\xE1 ",
1392
+ /* @__PURE__ */ jsx11(AlarmClock, { size: ICON_SIZE_PILL, "aria-hidden": "true" }),
1393
+ " parada h\xE1",
1394
+ " ",
1163
1395
  formatStalledFor(conversation.lastAt, now)
1164
1396
  ] }) : null,
1165
- isWaiting ? /* @__PURE__ */ jsx11("button", { type: "button", onClick: onTakeover, disabled: busy, className: "cv-header-action", children: "\u25B6\uFE0F Continuar Atendimento" }) : null
1397
+ isWaiting ? /* @__PURE__ */ jsxs9(
1398
+ "button",
1399
+ {
1400
+ type: "button",
1401
+ onClick: onTakeover,
1402
+ disabled: busy,
1403
+ "data-cv-tooltip": TAKEOVER_LABEL,
1404
+ "aria-label": TAKEOVER_LABEL,
1405
+ className: "cv-header-action",
1406
+ children: [
1407
+ /* @__PURE__ */ jsx11(Play, { size: ICON_SIZE_ACTION, "aria-hidden": "true" }),
1408
+ " ",
1409
+ TAKEOVER_LABEL
1410
+ ]
1411
+ }
1412
+ ) : null
1166
1413
  ] })
1167
1414
  ] })
1168
1415
  ] });
1169
1416
  }
1170
1417
 
1171
1418
  // src/ConversationHeader.tsx
1172
- import { useState as useState5 } from "react";
1419
+ import { useRef as useRef4, useState as useState6 } from "react";
1420
+ import { ArrowLeft, Bot as Bot2, Download, FileText, Headset, MoreVertical, UserRound as UserRound2, X } from "lucide-react";
1173
1421
  import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
1422
+ var HEADER_UTILITIES_MIN_WIDTH = 720;
1423
+ var HEADER_ACTIONS_MIN_WIDTH = 600;
1174
1424
  var DEFAULT_CONVERSATION_HEADER_LABELS = {
1175
- botMode: "\u{1F916} atendimento autom\xE1tico",
1176
- humanMode: "\u{1F9D1}\u200D\u{1F4BC} atendimento humano",
1425
+ // Sem emoji no rótulo: quem desenha o modo é o ícone ao lado, e o emoji embutido no texto voltava
1426
+ // a aparecer no tooltip e no leitor de tela como um caractere sem nome.
1427
+ botMode: "atendimento autom\xE1tico",
1428
+ humanMode: "atendimento humano",
1177
1429
  returnToBot: "Devolver ao bot",
1178
1430
  finish: "Finalizar",
1179
1431
  takeover: "Assumir atendimento",
@@ -1203,31 +1455,71 @@ function ConversationHeader({
1203
1455
  const displayHandle = formatContactHandle({ handle, channel: conversation.channel });
1204
1456
  const flag = contactFlag({ handle, channel: conversation.channel });
1205
1457
  const capabilities = capabilitiesOf(conversation.channel);
1206
- const [menuOpen, setMenuOpen] = useState5(false);
1207
- const utilities = [
1208
- onOpenDocuments ? { key: "documents", icon: "\u{1F4C4}", label: labels.documents, run: onOpenDocuments, active: documentsOpen } : void 0,
1209
- onDownload ? { key: "download", icon: "\u2B07\uFE0F", label: labels.download, run: onDownload, active: false } : void 0,
1458
+ const [menuOpen, setMenuOpen] = useState6(false);
1459
+ const headerRef = useRef4(null);
1460
+ const headerWidth = useContainerWidth(headerRef);
1461
+ const showsUtilitiesInline = headerWidth === void 0 || headerWidth >= HEADER_UTILITIES_MIN_WIDTH;
1462
+ const showsActionsInline = headerWidth === void 0 || headerWidth >= HEADER_ACTIONS_MIN_WIDTH;
1463
+ const utilityCandidates = [
1464
+ onOpenDocuments ? {
1465
+ key: "documents",
1466
+ icon: /* @__PURE__ */ jsx12(FileText, { size: ICON_SIZE_ACTION }),
1467
+ label: labels.documents,
1468
+ run: onOpenDocuments,
1469
+ active: documentsOpen
1470
+ } : void 0,
1471
+ onDownload ? {
1472
+ key: "download",
1473
+ icon: /* @__PURE__ */ jsx12(Download, { size: ICON_SIZE_ACTION }),
1474
+ label: labels.download,
1475
+ run: onDownload,
1476
+ active: false
1477
+ } : void 0,
1210
1478
  ...(extraUtilities ?? []).map((utility) => ({ ...utility, active: utility.active ?? false }))
1211
- ].filter(
1212
- (utility) => Boolean(utility)
1213
- );
1214
- const actions = [
1215
- !isHuman && onTakeover ? { key: "takeover", label: labels.takeover, run: onTakeover, className: "cv-header-action--primary" } : void 0,
1216
- isHuman && onReturnToBot ? { key: "release", label: labels.returnToBot, run: onReturnToBot, className: "" } : void 0,
1217
- isHuman && onFinish ? { key: "finish", label: `\u2715 ${labels.finish}`, run: onFinish, className: "cv-header-action--danger" } : void 0
1218
- ].filter((action) => Boolean(action));
1479
+ ];
1480
+ const utilities = utilityCandidates.filter((utility) => Boolean(utility));
1481
+ const actionCandidates = [
1482
+ !isHuman && onTakeover ? {
1483
+ key: "takeover",
1484
+ icon: /* @__PURE__ */ jsx12(Headset, { size: ICON_SIZE_ACTION }),
1485
+ label: labels.takeover,
1486
+ hint: labels.takeover,
1487
+ run: onTakeover,
1488
+ className: "cv-header-action--primary"
1489
+ } : void 0,
1490
+ isHuman && onReturnToBot ? {
1491
+ key: "release",
1492
+ icon: /* @__PURE__ */ jsx12(Bot2, { size: ICON_SIZE_ACTION }),
1493
+ label: labels.returnToBot,
1494
+ hint: labels.returnToBot,
1495
+ run: onReturnToBot,
1496
+ className: ""
1497
+ } : void 0,
1498
+ isHuman && onFinish ? {
1499
+ key: "finish",
1500
+ icon: /* @__PURE__ */ jsx12(X, { size: ICON_SIZE_ACTION }),
1501
+ label: labels.finish,
1502
+ hint: labels.finish,
1503
+ run: onFinish,
1504
+ className: "cv-header-action--danger"
1505
+ } : void 0
1506
+ ];
1507
+ const actions = actionCandidates.filter((action) => Boolean(action));
1219
1508
  const menuItems = [
1220
- ...utilities.map((utility) => ({
1509
+ ...showsUtilitiesInline ? [] : utilities.map((utility) => ({
1221
1510
  key: utility.key,
1222
- label: `${utility.icon} ${utility.label}`,
1511
+ icon: utility.icon,
1512
+ label: utility.label,
1513
+ hint: utility.label,
1223
1514
  run: utility.run,
1224
1515
  className: ""
1225
1516
  })),
1226
- ...actions
1517
+ ...showsActionsInline ? [] : actions
1227
1518
  ];
1228
1519
  return /* @__PURE__ */ jsxs10(
1229
1520
  "header",
1230
1521
  {
1522
+ ref: headerRef,
1231
1523
  className: cn(
1232
1524
  "flex flex-nowrap items-center justify-between gap-3 border-b px-4 py-3",
1233
1525
  classNames?.root,
@@ -1235,51 +1527,67 @@ function ConversationHeader({
1235
1527
  ),
1236
1528
  children: [
1237
1529
  /* @__PURE__ */ jsxs10("div", { className: cn("flex min-w-0 flex-1 items-center gap-3", classNames?.identity), children: [
1238
- onBack ? /* @__PURE__ */ jsx12("button", { type: "button", onClick: onBack, className: "cv-back cv-touch", "aria-label": labels.back, title: labels.back, children: "\u2190" }) : null,
1530
+ onBack ? /* @__PURE__ */ jsx12("button", { type: "button", onClick: onBack, className: "cv-back cv-touch", "aria-label": labels.back, "data-cv-tooltip": labels.back, children: /* @__PURE__ */ jsx12(ArrowLeft, { size: ICON_SIZE_ACTION, "aria-hidden": "true" }) }) : null,
1239
1531
  /* @__PURE__ */ jsx12(Avatar, { name: conversation.clientName, size: "md" }),
1240
1532
  /* @__PURE__ */ jsxs10("div", { className: "min-w-0", children: [
1241
1533
  /* @__PURE__ */ jsx12("p", { className: cn("truncate font-medium", classNames?.name), children: conversation.clientName ?? displayHandle }),
1242
- /* @__PURE__ */ jsxs10("p", { className: cn("truncate text-xs text-gray-500", classNames?.meta), children: [
1243
- flag ? `${flag} ` : "",
1244
- displayHandle,
1245
- /* @__PURE__ */ jsxs10("span", { className: "ml-2 inline-flex items-center gap-1", children: [
1534
+ /* @__PURE__ */ jsxs10("p", { className: cn("flex min-w-0 flex-nowrap items-center gap-2 overflow-hidden text-xs text-gray-500", classNames?.meta), children: [
1535
+ /* @__PURE__ */ jsxs10("span", { className: "truncate", children: [
1536
+ flag ? `${flag} ` : "",
1537
+ displayHandle
1538
+ ] }),
1539
+ /* @__PURE__ */ jsxs10("span", { className: "inline-flex flex-shrink-0 items-center gap-1", children: [
1246
1540
  /* @__PURE__ */ jsx12(ChannelIcon, { channel: conversation.channel }),
1247
- /* @__PURE__ */ jsx12("span", { className: "cv-only-wide", children: capabilities.label })
1541
+ showsUtilitiesInline ? /* @__PURE__ */ jsx12("span", { children: capabilities.label }) : null
1248
1542
  ] }),
1249
- /* @__PURE__ */ jsx12("span", { className: "ml-2 cv-only-wide", children: isHuman ? labels.humanMode : labels.botMode }),
1250
- /* @__PURE__ */ jsx12("span", { className: "ml-1 cv-only-narrow", title: isHuman ? labels.humanMode : labels.botMode, children: isHuman ? "\u{1F9D1}\u200D\u{1F4BC}" : "\u{1F916}" })
1543
+ /* @__PURE__ */ jsxs10(
1544
+ "span",
1545
+ {
1546
+ className: "inline-flex flex-shrink-0 items-center gap-1",
1547
+ ...showsUtilitiesInline ? {} : { role: "img", "aria-label": isHuman ? labels.humanMode : labels.botMode, "data-cv-tooltip": isHuman ? labels.humanMode : labels.botMode },
1548
+ children: [
1549
+ isHuman ? /* @__PURE__ */ jsx12(UserRound2, { size: ICON_SIZE_INLINE, "aria-hidden": "true" }) : /* @__PURE__ */ jsx12(Bot2, { size: ICON_SIZE_INLINE, "aria-hidden": "true" }),
1550
+ showsUtilitiesInline ? /* @__PURE__ */ jsx12("span", { children: isHuman ? labels.humanMode : labels.botMode }) : null
1551
+ ]
1552
+ }
1553
+ )
1251
1554
  ] })
1252
1555
  ] })
1253
1556
  ] }),
1254
1557
  /* @__PURE__ */ jsxs10("div", { className: cn("flex shrink-0 items-center gap-2", classNames?.actions), children: [
1255
- /* @__PURE__ */ jsxs10("div", { className: cn("hidden items-center gap-2 lg:flex", classNames?.desktopActions), children: [
1256
- utilities.map((utility) => /* @__PURE__ */ jsx12(
1558
+ /* @__PURE__ */ jsxs10("div", { className: cn("flex items-center gap-2", classNames?.desktopActions), children: [
1559
+ (showsUtilitiesInline ? utilities : []).map((utility) => /* @__PURE__ */ jsx12(
1257
1560
  "button",
1258
1561
  {
1259
1562
  type: "button",
1260
1563
  onClick: utility.run,
1261
1564
  disabled: busy,
1262
1565
  "aria-pressed": utility.active,
1263
- title: utility.label,
1566
+ "data-cv-tooltip": utility.label,
1264
1567
  "aria-label": utility.label,
1265
1568
  className: `cv-header-icon ${utility.active ? "cv-header-icon--active" : ""}`,
1266
1569
  children: utility.icon
1267
1570
  },
1268
1571
  utility.key
1269
1572
  )),
1270
- actions.map((action) => /* @__PURE__ */ jsx12(
1573
+ (showsActionsInline ? actions : []).map((action) => /* @__PURE__ */ jsxs10(
1271
1574
  "button",
1272
1575
  {
1273
1576
  type: "button",
1274
1577
  onClick: action.run,
1275
1578
  disabled: busy,
1579
+ "data-cv-tooltip": action.hint,
1580
+ "aria-label": action.hint,
1276
1581
  className: `cv-header-action ${action.className}`,
1277
- children: action.label
1582
+ children: [
1583
+ action.icon,
1584
+ action.label
1585
+ ]
1278
1586
  },
1279
1587
  action.key
1280
1588
  ))
1281
1589
  ] }),
1282
- menuItems.length > 0 ? /* @__PURE__ */ jsxs10("div", { className: cn("cv-menu lg:hidden", classNames?.mobileMenu), children: [
1590
+ menuItems.length > 0 ? /* @__PURE__ */ jsxs10("div", { className: cn("cv-menu", classNames?.mobileMenu), children: [
1283
1591
  /* @__PURE__ */ jsx12(
1284
1592
  "button",
1285
1593
  {
@@ -1287,12 +1595,12 @@ function ConversationHeader({
1287
1595
  onClick: () => setMenuOpen(!menuOpen),
1288
1596
  "aria-expanded": menuOpen,
1289
1597
  "aria-label": labels.moreActions,
1290
- title: labels.moreActions,
1598
+ "data-cv-tooltip": labels.moreActions,
1291
1599
  className: "cv-header-icon cv-touch",
1292
- children: "\u22EE"
1600
+ children: /* @__PURE__ */ jsx12(MoreVertical, { size: ICON_SIZE_ACTION, "aria-hidden": "true" })
1293
1601
  }
1294
1602
  ),
1295
- menuOpen ? /* @__PURE__ */ jsx12("div", { className: "cv-menu-panel", role: "menu", children: menuItems.map((item) => /* @__PURE__ */ jsx12(
1603
+ menuOpen ? /* @__PURE__ */ jsx12("div", { className: "cv-menu-panel", role: "menu", children: menuItems.map((item) => /* @__PURE__ */ jsxs10(
1296
1604
  "button",
1297
1605
  {
1298
1606
  type: "button",
@@ -1302,8 +1610,13 @@ function ConversationHeader({
1302
1610
  item.run();
1303
1611
  },
1304
1612
  disabled: busy,
1613
+ "data-cv-tooltip": item.hint,
1614
+ "aria-label": item.hint,
1305
1615
  className: `cv-header-action cv-touch ${item.className}`,
1306
- children: item.label
1616
+ children: [
1617
+ item.icon,
1618
+ item.label
1619
+ ]
1307
1620
  },
1308
1621
  item.key
1309
1622
  )) }) : null
@@ -1315,17 +1628,17 @@ function ConversationHeader({
1315
1628
  }
1316
1629
 
1317
1630
  // src/ConversationContextPanel.tsx
1318
- import { useState as useState7 } from "react";
1631
+ import { useState as useState8 } from "react";
1319
1632
  import { Check, ChevronDown, ChevronUp, Clock, Pencil } from "lucide-react";
1320
1633
 
1321
1634
  // src/useIsNarrow.ts
1322
- import { useEffect as useEffect4, useState as useState6 } from "react";
1635
+ import { useEffect as useEffect5, useState as useState7 } from "react";
1323
1636
  var NARROW_MAX_WIDTH_PX = 1023;
1324
1637
  function useIsNarrow() {
1325
- const [isNarrow, setIsNarrow] = useState6(
1638
+ const [isNarrow, setIsNarrow] = useState7(
1326
1639
  () => typeof window !== "undefined" && window.matchMedia(`(max-width: ${NARROW_MAX_WIDTH_PX}px)`).matches
1327
1640
  );
1328
- useEffect4(() => {
1641
+ useEffect5(() => {
1329
1642
  const query = window.matchMedia(`(max-width: ${NARROW_MAX_WIDTH_PX}px)`);
1330
1643
  const handleChange = (event) => setIsNarrow(event.matches);
1331
1644
  query.addEventListener("change", handleChange);
@@ -1387,7 +1700,7 @@ function ConversationContextPanel({
1387
1700
  const counts = { completed: 0, pending: 0, editing: 0 };
1388
1701
  for (const entry of entries) counts[statusOf(entry)] += 1;
1389
1702
  const isNarrow = useIsNarrow();
1390
- const [manualOpen, setManualOpen] = useState7(void 0);
1703
+ const [manualOpen, setManualOpen] = useState8(void 0);
1391
1704
  const open = manualOpen ?? defaultOpen ?? (!isNarrow && counts.completed > 0);
1392
1705
  return (
1393
1706
  // Faixa azul clara com separador, como o wrapper do financiamento: sem ela o card encostava
@@ -1406,7 +1719,8 @@ function ConversationContextPanel({
1406
1719
  type: "button",
1407
1720
  onClick: () => setManualOpen(!open),
1408
1721
  "aria-expanded": open,
1409
- title: open ? labels.collapse : labels.expand,
1722
+ "data-cv-tooltip": open ? labels.collapse : labels.expand,
1723
+ "aria-label": open ? labels.collapse : labels.expand,
1410
1724
  className: cn(
1411
1725
  "flex w-full cursor-pointer items-center justify-between gap-2 px-4 py-2.5 text-left transition-colors hover:bg-blue-100/40 dark:hover:bg-blue-900/20",
1412
1726
  classNames?.toggle
@@ -1504,7 +1818,7 @@ function ConversationContextPanel({
1504
1818
  "truncate text-center text-xs font-semibold",
1505
1819
  entry.value ? "text-gray-900 dark:text-gray-100" : "italic font-normal text-gray-400 dark:text-gray-500"
1506
1820
  ),
1507
- title: entry.value ?? labels.notCollected,
1821
+ "data-cv-tooltip": entry.value ?? labels.notCollected,
1508
1822
  children: entry.value ?? labels.notCollected
1509
1823
  }
1510
1824
  )
@@ -1524,11 +1838,12 @@ function ConversationContextPanel({
1524
1838
  }
1525
1839
 
1526
1840
  // src/WindowExpiredNotice.tsx
1841
+ import { AlarmClock as AlarmClock2, Send } from "lucide-react";
1527
1842
  import { jsx as jsx14, jsxs as jsxs12 } from "react/jsx-runtime";
1528
1843
  var DEFAULT_WINDOW_EXPIRED_LABELS = {
1529
- title: "\u23F0 Janela de 24h expirada \u2014 o WhatsApp n\xE3o aceita mensagens livres.",
1844
+ title: "Janela de 24h expirada \u2014 o WhatsApp n\xE3o aceita mensagens livres.",
1530
1845
  description: "Envie uma mensagem de template para reabrir a conversa com o cliente.",
1531
- sendTemplate: "\u{1F4E8} Enviar Template (HSM)"
1846
+ sendTemplate: "Enviar Template (HSM)"
1532
1847
  };
1533
1848
  function WindowExpiredNotice({
1534
1849
  onSendTemplate,
@@ -1538,9 +1853,15 @@ function WindowExpiredNotice({
1538
1853
  }) {
1539
1854
  const labels = { ...DEFAULT_WINDOW_EXPIRED_LABELS, ...labelsOverride };
1540
1855
  return /* @__PURE__ */ jsxs12("div", { role: "status", className: cn("border-t bg-yellow-50 px-4 py-3 text-sm dark:bg-yellow-950", className), children: [
1541
- /* @__PURE__ */ jsx14("p", { className: "font-medium text-yellow-900 dark:text-yellow-200", children: labels.title }),
1856
+ /* @__PURE__ */ jsxs12("p", { className: "flex items-center gap-2 font-medium text-yellow-900 dark:text-yellow-200", children: [
1857
+ /* @__PURE__ */ jsx14(AlarmClock2, { size: ICON_SIZE_INLINE, "aria-hidden": "true", className: "shrink-0" }),
1858
+ labels.title
1859
+ ] }),
1542
1860
  /* @__PURE__ */ jsx14("p", { className: "text-yellow-800 dark:text-yellow-300", children: labels.description }),
1543
- onSendTemplate ? /* @__PURE__ */ jsx14("button", { type: "button", onClick: onSendTemplate, disabled, className: "cv-header-action mt-2", children: labels.sendTemplate }) : null
1861
+ onSendTemplate ? /* @__PURE__ */ jsxs12("button", { "data-cv-tooltip": labels.sendTemplate, "aria-label": labels.sendTemplate, type: "button", onClick: onSendTemplate, disabled, className: "cv-header-action mt-2", children: [
1862
+ /* @__PURE__ */ jsx14(Send, { size: ICON_SIZE_ACTION, "aria-hidden": "true" }),
1863
+ labels.sendTemplate
1864
+ ] }) : null
1544
1865
  ] });
1545
1866
  }
1546
1867
  function isWindowBlocking(window2) {
@@ -1548,12 +1869,12 @@ function isWindowBlocking(window2) {
1548
1869
  }
1549
1870
 
1550
1871
  // src/documents/DocumentsWorkspace.tsx
1551
- import { useEffect as useEffect6, useMemo as useMemo2, useState as useState10 } from "react";
1552
- import { Download, Eye, MessageSquare, Trash2, X as X2 } from "lucide-react";
1872
+ import { useEffect as useEffect7, useMemo as useMemo2, useState as useState11 } from "react";
1873
+ import { Download as Download2, Eye, MessageSquare, Trash2, X as X3 } from "lucide-react";
1553
1874
 
1554
1875
  // src/listing/index.tsx
1555
- import { useState as useState8 } from "react";
1556
- import { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown as ChevronDown2, X } from "lucide-react";
1876
+ import { useState as useState9 } from "react";
1877
+ import { ArrowDown, ArrowUp, ArrowUpDown, ChevronDown as ChevronDown2, X as X2 } from "lucide-react";
1557
1878
  import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs13 } from "react/jsx-runtime";
1558
1879
  function SortableHead({ label, field, activeField, direction, onSort, className }) {
1559
1880
  const isActive = activeField === field;
@@ -1561,6 +1882,7 @@ function SortableHead({ label, field, activeField, direction, onSort, className
1561
1882
  return /* @__PURE__ */ jsx15("th", { scope: "col", className: cn("px-3 py-2 text-left text-xs font-medium", className), children: /* @__PURE__ */ jsxs13(
1562
1883
  "button",
1563
1884
  {
1885
+ "data-cv-tooltip": label,
1564
1886
  type: "button",
1565
1887
  onClick: () => onSort(field),
1566
1888
  "aria-label": label,
@@ -1573,7 +1895,7 @@ function SortableHead({ label, field, activeField, direction, onSort, className
1573
1895
  ) });
1574
1896
  }
1575
1897
  function MultiSelectFilter({ label, options, selected, onChange, className }) {
1576
- const [open, setOpen] = useState8(false);
1898
+ const [open, setOpen] = useState9(false);
1577
1899
  function toggle(value) {
1578
1900
  onChange(selected.includes(value) ? selected.filter((item) => item !== value) : [...selected, value]);
1579
1901
  }
@@ -1581,6 +1903,8 @@ function MultiSelectFilter({ label, options, selected, onChange, className }) {
1581
1903
  /* @__PURE__ */ jsxs13(
1582
1904
  "button",
1583
1905
  {
1906
+ "data-cv-tooltip": label,
1907
+ "aria-label": label,
1584
1908
  type: "button",
1585
1909
  onClick: () => setOpen((current) => !current),
1586
1910
  className: "cv-header-action inline-flex items-center gap-1",
@@ -1613,8 +1937,8 @@ function BulkActionBar({ selectedCount, selectedLabel, clearLabel, onClear, chil
1613
1937
  return /* @__PURE__ */ jsxs13("div", { className: "cv-bulk-bar", role: "toolbar", "aria-label": selectedLabel(selectedCount), children: [
1614
1938
  /* @__PURE__ */ jsx15("span", { className: "text-xs font-medium", children: selectedLabel(selectedCount) }),
1615
1939
  children,
1616
- /* @__PURE__ */ jsxs13("button", { type: "button", onClick: onClear, className: "cv-header-action ml-auto inline-flex items-center gap-1", children: [
1617
- /* @__PURE__ */ jsx15(X, { size: 12, "aria-hidden": "true" }),
1940
+ /* @__PURE__ */ jsxs13("button", { "data-cv-tooltip": clearLabel, "aria-label": clearLabel, type: "button", onClick: onClear, className: "cv-header-action ml-auto inline-flex items-center gap-1", children: [
1941
+ /* @__PURE__ */ jsx15(X2, { size: 12, "aria-hidden": "true" }),
1618
1942
  clearLabel
1619
1943
  ] })
1620
1944
  ] });
@@ -1651,6 +1975,7 @@ function ListingPagination({
1651
1975
  /* @__PURE__ */ jsx15(
1652
1976
  "button",
1653
1977
  {
1978
+ "data-cv-tooltip": labels.previous,
1654
1979
  type: "button",
1655
1980
  onClick: () => onPageChange(page - 1),
1656
1981
  disabled: page <= 1,
@@ -1663,6 +1988,7 @@ function ListingPagination({
1663
1988
  /* @__PURE__ */ jsx15(
1664
1989
  "button",
1665
1990
  {
1991
+ "data-cv-tooltip": labels.next,
1666
1992
  type: "button",
1667
1993
  onClick: () => onPageChange(page + 1),
1668
1994
  disabled: page >= lastPage,
@@ -1676,7 +2002,7 @@ function ListingPagination({
1676
2002
  }
1677
2003
 
1678
2004
  // src/hooks/useUrlFilterState.ts
1679
- import { useCallback as useCallback4, useEffect as useEffect5, useState as useState9 } from "react";
2005
+ import { useCallback as useCallback4, useEffect as useEffect6, useState as useState10 } from "react";
1680
2006
  var LIST_SEPARATOR = ",";
1681
2007
  function readParams() {
1682
2008
  if (typeof window === "undefined") return new URLSearchParams();
@@ -1691,7 +2017,7 @@ function writeParam(key, value) {
1691
2017
  window.history.replaceState(null, "", query ? `${window.location.pathname}?${query}` : window.location.pathname);
1692
2018
  }
1693
2019
  function useUrlStringState(key, initial, { enabled = true } = {}) {
1694
- const [value, setValue] = useState9(() => enabled ? readParams().get(key) ?? initial : initial);
2020
+ const [value, setValue] = useState10(() => enabled ? readParams().get(key) ?? initial : initial);
1695
2021
  const update = useCallback4(
1696
2022
  (next) => {
1697
2023
  setValue(next);
@@ -1702,7 +2028,7 @@ function useUrlStringState(key, initial, { enabled = true } = {}) {
1702
2028
  return [value, update];
1703
2029
  }
1704
2030
  function useUrlNumberState(key, initial, { enabled = true } = {}) {
1705
- const [value, setValue] = useState9(() => {
2031
+ const [value, setValue] = useState10(() => {
1706
2032
  if (!enabled) return initial;
1707
2033
  const raw = Number(readParams().get(key));
1708
2034
  return Number.isFinite(raw) && raw > 0 ? raw : initial;
@@ -1717,7 +2043,7 @@ function useUrlNumberState(key, initial, { enabled = true } = {}) {
1717
2043
  return [value, update];
1718
2044
  }
1719
2045
  function useUrlArrayState(key, { enabled = true } = {}) {
1720
- const [value, setValue] = useState9(() => {
2046
+ const [value, setValue] = useState10(() => {
1721
2047
  if (!enabled) return [];
1722
2048
  const raw = readParams().get(key);
1723
2049
  return raw ? raw.split(LIST_SEPARATOR).filter(Boolean) : [];
@@ -1732,8 +2058,8 @@ function useUrlArrayState(key, { enabled = true } = {}) {
1732
2058
  return [value, update];
1733
2059
  }
1734
2060
  function useDebouncedValue(value, delayMs = 300) {
1735
- const [debounced, setDebounced] = useState9(value);
1736
- useEffect5(() => {
2061
+ const [debounced, setDebounced] = useState10(value);
2062
+ useEffect6(() => {
1737
2063
  const timer = setTimeout(() => setDebounced(value), delayMs);
1738
2064
  return () => clearTimeout(timer);
1739
2065
  }, [value, delayMs]);
@@ -1813,14 +2139,14 @@ function DocumentsWorkspace({
1813
2139
  const [sortDirection, setSortDirection] = useUrlStringState("sortDirection", "desc", urlOptions);
1814
2140
  const [page, setPage] = useUrlNumberState("page", 1, urlOptions);
1815
2141
  const [perPage, setPerPage] = useUrlNumberState("limit", initialPerPage, urlOptions);
1816
- const [extra, setExtra] = useState10({});
1817
- const [documents, setDocuments] = useState10([]);
1818
- const [total, setTotal] = useState10(0);
1819
- const [loading, setLoading] = useState10(false);
1820
- const [failed, setFailed] = useState10(false);
1821
- const [selectedIds, setSelectedIds] = useState10(/* @__PURE__ */ new Set());
1822
- const [reloadToken, setReloadToken] = useState10(0);
1823
- const [busy, setBusy] = useState10(false);
2142
+ const [extra, setExtra] = useState11({});
2143
+ const [documents, setDocuments] = useState11([]);
2144
+ const [total, setTotal] = useState11(0);
2145
+ const [loading, setLoading] = useState11(false);
2146
+ const [failed, setFailed] = useState11(false);
2147
+ const [selectedIds, setSelectedIds] = useState11(/* @__PURE__ */ new Set());
2148
+ const [reloadToken, setReloadToken] = useState11(0);
2149
+ const [busy, setBusy] = useState11(false);
1824
2150
  const debouncedSearch = useDebouncedValue(search);
1825
2151
  const fetchAll = context?.api.getAllDocuments;
1826
2152
  const removeDocument = context?.api.deleteDocument;
@@ -1835,7 +2161,7 @@ function DocumentsWorkspace({
1835
2161
  [categories, labels.categoryLabels]
1836
2162
  );
1837
2163
  const hasFilters = debouncedSearch !== "" || source.length > 0 || category.length > 0 || startDate !== "" || endDate !== "" || Object.keys(extra).length > 0;
1838
- useEffect6(() => {
2164
+ useEffect7(() => {
1839
2165
  if (!fetchAll) return;
1840
2166
  let active = true;
1841
2167
  setLoading(true);
@@ -1864,7 +2190,7 @@ function DocumentsWorkspace({
1864
2190
  active = false;
1865
2191
  };
1866
2192
  }, [fetchAll, debouncedSearch, source, category, startDate, endDate, sortField, sortDirection, page, perPage, extraKey, reloadToken]);
1867
- useEffect6(() => {
2193
+ useEffect7(() => {
1868
2194
  setPage(1);
1869
2195
  setSelectedIds(/* @__PURE__ */ new Set());
1870
2196
  }, [debouncedSearch, source, category, startDate, endDate, perPage, extraKey]);
@@ -1928,6 +2254,7 @@ function DocumentsWorkspace({
1928
2254
  const direction = sortDirection === "asc" ? "asc" : "desc";
1929
2255
  const canSelect = Boolean(removeDocument ?? downloadArchive);
1930
2256
  return /* @__PURE__ */ jsxs14("div", { className: cn("space-y-4", classNames?.root, className), children: [
2257
+ /* @__PURE__ */ jsx16(TooltipLayer, {}),
1931
2258
  /* @__PURE__ */ jsxs14("header", { className: cn("space-y-0.5", classNames?.header), children: [
1932
2259
  /* @__PURE__ */ jsx16("h2", { className: "text-lg font-semibold", children: labels.title }),
1933
2260
  /* @__PURE__ */ jsx16("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: labels.subtitle(total) })
@@ -1977,8 +2304,8 @@ function DocumentsWorkspace({
1977
2304
  )
1978
2305
  ] }) : null,
1979
2306
  renderFilters?.({ extra, setExtra }),
1980
- hasFilters ? /* @__PURE__ */ jsxs14("button", { type: "button", onClick: clearFilters, className: "cv-header-action inline-flex items-center gap-1", children: [
1981
- /* @__PURE__ */ jsx16(X2, { size: 12, "aria-hidden": "true" }),
2307
+ hasFilters ? /* @__PURE__ */ jsxs14("button", { "data-cv-tooltip": labels.clearFilters, "aria-label": labels.clearFilters, type: "button", onClick: clearFilters, className: "cv-header-action inline-flex items-center gap-1", children: [
2308
+ /* @__PURE__ */ jsx16(X3, { size: 12, "aria-hidden": "true" }),
1982
2309
  labels.clearFilters
1983
2310
  ] }) : null
1984
2311
  ] }),
@@ -1993,12 +2320,14 @@ function DocumentsWorkspace({
1993
2320
  downloadArchive ? /* @__PURE__ */ jsxs14(
1994
2321
  "button",
1995
2322
  {
2323
+ "data-cv-tooltip": labels.bulkDownloadZip,
2324
+ "aria-label": labels.bulkDownloadZip,
1996
2325
  type: "button",
1997
2326
  onClick: () => void handleDownloadArchive(),
1998
2327
  disabled: busy,
1999
2328
  className: "cv-header-action inline-flex items-center gap-1 disabled:opacity-40",
2000
2329
  children: [
2001
- /* @__PURE__ */ jsx16(Download, { size: 12, "aria-hidden": "true" }),
2330
+ /* @__PURE__ */ jsx16(Download2, { size: 12, "aria-hidden": "true" }),
2002
2331
  labels.bulkDownloadZip
2003
2332
  ]
2004
2333
  }
@@ -2006,6 +2335,8 @@ function DocumentsWorkspace({
2006
2335
  removeDocument ? /* @__PURE__ */ jsxs14(
2007
2336
  "button",
2008
2337
  {
2338
+ "data-cv-tooltip": labels.bulkRemove(selectedIds.size),
2339
+ "aria-label": labels.bulkRemove(selectedIds.size),
2009
2340
  type: "button",
2010
2341
  onClick: () => {
2011
2342
  if (window.confirm(labels.bulkRemoveConfirm(selectedIds.size))) void handleRemove(Array.from(selectedIds));
@@ -2057,14 +2388,15 @@ function DocumentsWorkspace({
2057
2388
  ) }) : null,
2058
2389
  /* @__PURE__ */ jsx16("td", { className: "max-w-xs px-3 py-2", children: /* @__PURE__ */ jsxs14("div", { className: "flex min-w-0 items-center gap-2", children: [
2059
2390
  /* @__PURE__ */ jsx16(FileIcon, { filename: document2.filename, mimeType: document2.mimeType }),
2060
- /* @__PURE__ */ jsx16("span", { className: "truncate font-medium", title: document2.filename, children: document2.filename })
2391
+ /* @__PURE__ */ jsx16("span", { className: "truncate font-medium", "data-cv-tooltip": document2.filename, children: document2.filename })
2061
2392
  ] }) }),
2062
2393
  /* @__PURE__ */ jsx16("td", { className: "hidden px-3 py-2 sm:table-cell", children: onOpenConversation ? /* @__PURE__ */ jsxs14(
2063
2394
  "button",
2064
2395
  {
2065
2396
  type: "button",
2066
2397
  onClick: () => onOpenConversation(document2.conversationId),
2067
- title: labels.openConversation,
2398
+ "data-cv-tooltip": labels.openConversation,
2399
+ "aria-label": labels.openConversation,
2068
2400
  className: "cv-header-action inline-flex items-center gap-1",
2069
2401
  children: [
2070
2402
  /* @__PURE__ */ jsx16(MessageSquare, { size: 12, "aria-hidden": "true" }),
@@ -2082,7 +2414,7 @@ function DocumentsWorkspace({
2082
2414
  {
2083
2415
  type: "button",
2084
2416
  onClick: () => void handleOpen(document2.id, "inline"),
2085
- title: labels.view,
2417
+ "data-cv-tooltip": labels.view,
2086
2418
  "aria-label": `${labels.view}: ${document2.filename}`,
2087
2419
  className: "cv-header-icon",
2088
2420
  children: /* @__PURE__ */ jsx16(Eye, { size: 14 })
@@ -2093,10 +2425,10 @@ function DocumentsWorkspace({
2093
2425
  {
2094
2426
  type: "button",
2095
2427
  onClick: () => void handleOpen(document2.id, "attachment"),
2096
- title: labels.download,
2428
+ "data-cv-tooltip": labels.download,
2097
2429
  "aria-label": `${labels.download}: ${document2.filename}`,
2098
2430
  className: "cv-header-icon",
2099
- children: /* @__PURE__ */ jsx16(Download, { size: 14 })
2431
+ children: /* @__PURE__ */ jsx16(Download2, { size: 14 })
2100
2432
  }
2101
2433
  ),
2102
2434
  removeDocument ? /* @__PURE__ */ jsx16(
@@ -2107,7 +2439,7 @@ function DocumentsWorkspace({
2107
2439
  if (window.confirm(labels.removeConfirm(document2.filename))) void handleRemove([document2.id]);
2108
2440
  },
2109
2441
  disabled: busy,
2110
- title: labels.remove,
2442
+ "data-cv-tooltip": labels.remove,
2111
2443
  "aria-label": `${labels.remove}: ${document2.filename}`,
2112
2444
  className: "cv-header-icon disabled:opacity-40",
2113
2445
  children: /* @__PURE__ */ jsx16(Trash2, { size: 14, className: "text-red-500" })
@@ -2184,7 +2516,7 @@ function downloadTextFile(filename, content) {
2184
2516
  }
2185
2517
 
2186
2518
  // src/useWaitingNotifications.ts
2187
- import { useState as useState11, useEffect as useEffect7, useRef as useRef4, useCallback as useCallback5 } from "react";
2519
+ import { useState as useState12, useEffect as useEffect8, useRef as useRef5, useCallback as useCallback5 } from "react";
2188
2520
  var DEFAULT_POLL_INTERVAL_MS = 1e4;
2189
2521
  var DEFAULT_LIMIT = 50;
2190
2522
  var DEFAULT_LABELS = {
@@ -2193,10 +2525,10 @@ var DEFAULT_LABELS = {
2193
2525
  };
2194
2526
  function useWaitingNotifications(params) {
2195
2527
  const conversationsContext = useConversations();
2196
- const [conversations, setConversations] = useState11([]);
2197
- const previousUnreadMap = useRef4(/* @__PURE__ */ new Map());
2198
- const notifiedIds = useRef4(/* @__PURE__ */ new Set());
2199
- const isPollingRef = useRef4(false);
2528
+ const [conversations, setConversations] = useState12([]);
2529
+ const previousUnreadMap = useRef5(/* @__PURE__ */ new Map());
2530
+ const notifiedIds = useRef5(/* @__PURE__ */ new Set());
2531
+ const isPollingRef = useRef5(false);
2200
2532
  const isEnabled = params?.enabled ?? true;
2201
2533
  const intervalMs = params?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
2202
2534
  const icon = params?.icon;
@@ -2228,7 +2560,7 @@ function useWaitingNotifications(params) {
2228
2560
  isPollingRef.current = false;
2229
2561
  }
2230
2562
  }, [conversationsContext, listParams, icon, labelTitle, labelBody]);
2231
- useEffect7(() => {
2563
+ useEffect8(() => {
2232
2564
  if (!isEnabled) return;
2233
2565
  if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "default") {
2234
2566
  Notification.requestPermission();
@@ -2314,6 +2646,8 @@ function WhatsAppTemplateSettingsForm({
2314
2646
  /* @__PURE__ */ jsxs15(
2315
2647
  "button",
2316
2648
  {
2649
+ "data-cv-tooltip": labels.refreshList,
2650
+ "aria-label": labels.refreshList,
2317
2651
  type: "button",
2318
2652
  onClick: onRefreshTemplates,
2319
2653
  disabled: loadingTemplates,
@@ -2363,6 +2697,8 @@ function WhatsAppTemplateSettingsForm({
2363
2697
  /* @__PURE__ */ jsxs15(
2364
2698
  "button",
2365
2699
  {
2700
+ "data-cv-tooltip": labels.addVariable,
2701
+ "aria-label": labels.addVariable,
2366
2702
  type: "button",
2367
2703
  onClick: () => onVariablesChange([...variables, ""]),
2368
2704
  className: "flex items-center gap-1 text-xs text-blue-600 dark:text-blue-400 hover:underline",
@@ -2380,7 +2716,8 @@ function WhatsAppTemplateSettingsForm({
2380
2716
  {
2381
2717
  type: "button",
2382
2718
  onClick: () => insertVariable(available.token),
2383
- title: `Inserir ${available.token}`,
2719
+ "data-cv-tooltip": `Inserir ${available.token}`,
2720
+ "aria-label": `Inserir ${available.token}`,
2384
2721
  className: "inline-flex items-center gap-1 rounded-md border border-gray-200 dark:border-gray-600 bg-white dark:bg-gray-800 px-2 py-1 text-xs hover:border-blue-400 dark:hover:border-blue-500 transition-colors",
2385
2722
  children: [
2386
2723
  /* @__PURE__ */ jsx17("code", { className: "text-blue-600 dark:text-blue-300", children: available.token }),
@@ -2414,7 +2751,8 @@ function WhatsAppTemplateSettingsForm({
2414
2751
  type: "button",
2415
2752
  onClick: () => removeVariable(index),
2416
2753
  className: "text-gray-400 hover:text-red-500 dark:hover:text-red-400 transition-colors flex-shrink-0",
2417
- title: labels.removeVariable,
2754
+ "data-cv-tooltip": labels.removeVariable,
2755
+ "aria-label": labels.removeVariable,
2418
2756
  children: /* @__PURE__ */ jsx17(Trash22, { size: 14 })
2419
2757
  }
2420
2758
  )
@@ -2424,6 +2762,8 @@ function WhatsAppTemplateSettingsForm({
2424
2762
  /* @__PURE__ */ jsx17("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs15(
2425
2763
  "button",
2426
2764
  {
2765
+ "data-cv-tooltip": saving ? labels.saving : labels.save,
2766
+ "aria-label": saving ? labels.saving : labels.save,
2427
2767
  type: "submit",
2428
2768
  disabled: saving || !selectedTemplateName.trim(),
2429
2769
  className: "flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm",
@@ -2661,6 +3001,8 @@ function WhatsAppCreateTemplateForm({
2661
3001
  /* @__PURE__ */ jsx18("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs16(
2662
3002
  "button",
2663
3003
  {
3004
+ "data-cv-tooltip": submitting ? labels.sending : labels.sendForApproval,
3005
+ "aria-label": submitting ? labels.sending : labels.sendForApproval,
2664
3006
  type: "submit",
2665
3007
  disabled: submitting || !value.name || !value.bodyText,
2666
3008
  className: "flex items-center gap-2 bg-emerald-600 hover:bg-emerald-700 disabled:bg-emerald-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm",
@@ -2743,6 +3085,8 @@ function WelcomeFarewellForm({
2743
3085
  /* @__PURE__ */ jsx19("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs17(
2744
3086
  "button",
2745
3087
  {
3088
+ "data-cv-tooltip": saving ? labels.saving : labels.save,
3089
+ "aria-label": saving ? labels.saving : labels.save,
2746
3090
  type: "submit",
2747
3091
  disabled: saving,
2748
3092
  className: "flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm",
@@ -2835,6 +3179,8 @@ function TranscriptionSettingsForm({
2835
3179
  /* @__PURE__ */ jsx20("div", { className: "flex justify-end", children: /* @__PURE__ */ jsxs18(
2836
3180
  "button",
2837
3181
  {
3182
+ "data-cv-tooltip": saving ? labels.saving : labels.save,
3183
+ "aria-label": saving ? labels.saving : labels.save,
2838
3184
  type: "submit",
2839
3185
  disabled: saving || !isAvailable,
2840
3186
  className: "flex items-center gap-2 bg-blue-600 hover:bg-blue-700 disabled:bg-blue-400 text-white font-semibold px-5 py-2.5 rounded-xl transition-colors text-sm",
@@ -2881,7 +3227,7 @@ function ModeOption({
2881
3227
  }
2882
3228
 
2883
3229
  // src/settings/WhatsAppTemplatesSettings.tsx
2884
- import { useState as useState12 } from "react";
3230
+ import { useState as useState13 } from "react";
2885
3231
  import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
2886
3232
  var TEMPLATE_SETTINGS_TAB = {
2887
3233
  SELECT: "select",
@@ -2898,12 +3244,14 @@ function WhatsAppTemplatesSettings({
2898
3244
  ...settingsProps
2899
3245
  }) {
2900
3246
  const labels = { ...DEFAULT_TEMPLATES_SETTINGS_LABELS, ...labelsOverride };
2901
- const [tab, setTab] = useState12(TEMPLATE_SETTINGS_TAB.SELECT);
3247
+ const [tab, setTab] = useState13(TEMPLATE_SETTINGS_TAB.SELECT);
2902
3248
  return /* @__PURE__ */ jsxs19("div", { className: "space-y-4", children: [
2903
3249
  /* @__PURE__ */ jsxs19("nav", { className: "flex gap-1 border-b", role: "tablist", children: [
2904
3250
  /* @__PURE__ */ jsx21(
2905
3251
  "button",
2906
3252
  {
3253
+ "data-cv-tooltip": labels.selectTab,
3254
+ "aria-label": labels.selectTab,
2907
3255
  type: "button",
2908
3256
  role: "tab",
2909
3257
  "aria-selected": tab === TEMPLATE_SETTINGS_TAB.SELECT,
@@ -2915,6 +3263,8 @@ function WhatsAppTemplatesSettings({
2915
3263
  create ? /* @__PURE__ */ jsx21(
2916
3264
  "button",
2917
3265
  {
3266
+ "data-cv-tooltip": labels.createTab,
3267
+ "aria-label": labels.createTab,
2918
3268
  type: "button",
2919
3269
  role: "tab",
2920
3270
  "aria-selected": tab === TEMPLATE_SETTINGS_TAB.CREATE,
@@ -2963,6 +3313,8 @@ function TopicsForm({
2963
3313
  /* @__PURE__ */ jsx22(
2964
3314
  "button",
2965
3315
  {
3316
+ "data-cv-tooltip": topic.label,
3317
+ "aria-label": topic.label,
2966
3318
  type: "button",
2967
3319
  onClick: () => onToggle(topic.key),
2968
3320
  className: `relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${topic.enabled ? "bg-blue-600 dark:bg-blue-500" : "bg-gray-200 dark:bg-gray-700"}`,
@@ -2993,6 +3345,8 @@ function TopicsForm({
2993
3345
  /* @__PURE__ */ jsxs20(
2994
3346
  "button",
2995
3347
  {
3348
+ "data-cv-tooltip": labels.saveButton,
3349
+ "aria-label": labels.saveButton,
2996
3350
  type: "submit",
2997
3351
  disabled: saving,
2998
3352
  className: "inline-flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50",
@@ -3009,7 +3363,7 @@ function TopicsForm({
3009
3363
  }
3010
3364
 
3011
3365
  // src/settings/MessagesWorkspace.tsx
3012
- import { useCallback as useCallback6, useEffect as useEffect8, useState as useState13 } from "react";
3366
+ import { useCallback as useCallback6, useEffect as useEffect9, useState as useState14 } from "react";
3013
3367
  import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
3014
3368
  var DEFAULT_LABELS7 = {
3015
3369
  title: "Mensagens",
@@ -3065,26 +3419,26 @@ function MessagesWorkspace({
3065
3419
  const hasTopics = Boolean(api.getTopics && api.saveTopics);
3066
3420
  const hasTemplates = Boolean(api.getTemplateSettings && api.saveTemplateSettings);
3067
3421
  const hasTranscription = Boolean(api.getTranscription && api.saveTranscription);
3068
- const [tab, setTab] = useState13(MESSAGES_TAB.BOT);
3069
- const [loadState, setLoadState] = useState13("loading");
3070
- const [messages, setMessages] = useState13(EMPTY_MESSAGES);
3071
- const [savingMessages, setSavingMessages] = useState13(false);
3072
- const [messagesSaved, setMessagesSaved] = useState13(false);
3073
- const [topics, setTopics] = useState13([]);
3074
- const [savingTopics, setSavingTopics] = useState13(false);
3075
- const [topicsSaved, setTopicsSaved] = useState13(false);
3076
- const [templateSettings, setTemplateSettings] = useState13(EMPTY_TEMPLATE_SETTINGS);
3077
- const [templates, setTemplates] = useState13([]);
3078
- const [loadingTemplates, setLoadingTemplates] = useState13(false);
3079
- const [templatesError, setTemplatesError] = useState13(false);
3080
- const [savingTemplate, setSavingTemplate] = useState13(false);
3081
- const [templateSaved, setTemplateSaved] = useState13(false);
3082
- const [createTemplate, setCreateTemplate] = useState13(EMPTY_CREATE_TEMPLATE);
3083
- const [creatingTemplate, setCreatingTemplate] = useState13(false);
3084
- const [createResult, setCreateResult] = useState13(null);
3085
- const [transcription, setTranscription] = useState13(UNDECIDED_TRANSCRIPTION);
3086
- const [savingTranscription, setSavingTranscription] = useState13(false);
3087
- const [transcriptionSaved, setTranscriptionSaved] = useState13(false);
3422
+ const [tab, setTab] = useState14(MESSAGES_TAB.BOT);
3423
+ const [loadState, setLoadState] = useState14("loading");
3424
+ const [messages, setMessages] = useState14(EMPTY_MESSAGES);
3425
+ const [savingMessages, setSavingMessages] = useState14(false);
3426
+ const [messagesSaved, setMessagesSaved] = useState14(false);
3427
+ const [topics, setTopics] = useState14([]);
3428
+ const [savingTopics, setSavingTopics] = useState14(false);
3429
+ const [topicsSaved, setTopicsSaved] = useState14(false);
3430
+ const [templateSettings, setTemplateSettings] = useState14(EMPTY_TEMPLATE_SETTINGS);
3431
+ const [templates, setTemplates] = useState14([]);
3432
+ const [loadingTemplates, setLoadingTemplates] = useState14(false);
3433
+ const [templatesError, setTemplatesError] = useState14(false);
3434
+ const [savingTemplate, setSavingTemplate] = useState14(false);
3435
+ const [templateSaved, setTemplateSaved] = useState14(false);
3436
+ const [createTemplate, setCreateTemplate] = useState14(EMPTY_CREATE_TEMPLATE);
3437
+ const [creatingTemplate, setCreatingTemplate] = useState14(false);
3438
+ const [createResult, setCreateResult] = useState14(null);
3439
+ const [transcription, setTranscription] = useState14(UNDECIDED_TRANSCRIPTION);
3440
+ const [savingTranscription, setSavingTranscription] = useState14(false);
3441
+ const [transcriptionSaved, setTranscriptionSaved] = useState14(false);
3088
3442
  const reloadTemplates = useCallback6(async () => {
3089
3443
  if (!api.listTemplates) return;
3090
3444
  setLoadingTemplates(true);
@@ -3097,7 +3451,7 @@ function MessagesWorkspace({
3097
3451
  setLoadingTemplates(false);
3098
3452
  }
3099
3453
  }, [api]);
3100
- useEffect8(() => {
3454
+ useEffect9(() => {
3101
3455
  let active = true;
3102
3456
  async function load() {
3103
3457
  try {
@@ -3122,7 +3476,7 @@ function MessagesWorkspace({
3122
3476
  active = false;
3123
3477
  };
3124
3478
  }, [api]);
3125
- useEffect8(() => {
3479
+ useEffect9(() => {
3126
3480
  void reloadTemplates();
3127
3481
  }, [reloadTemplates]);
3128
3482
  function flash(setter) {
@@ -3213,6 +3567,7 @@ function MessagesWorkspace({
3213
3567
  ...hasTranscription ? [{ id: MESSAGES_TAB.TRANSCRIPTION, label: labels.tabTranscription }] : []
3214
3568
  ];
3215
3569
  return /* @__PURE__ */ jsxs21("div", { className: `space-y-6 p-4 lg:p-0 ${className ?? ""}`, children: [
3570
+ /* @__PURE__ */ jsx23(TooltipLayer, {}),
3216
3571
  /* @__PURE__ */ jsxs21("header", { children: [
3217
3572
  /* @__PURE__ */ jsx23("h1", { className: "text-xl font-semibold text-gray-900 dark:text-gray-100", children: labels.title }),
3218
3573
  /* @__PURE__ */ jsx23("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: labels.subtitle })
@@ -3220,6 +3575,8 @@ function MessagesWorkspace({
3220
3575
  tabs.length > 1 ? /* @__PURE__ */ jsx23("nav", { className: "flex gap-1 border-b border-gray-200 dark:border-gray-700", role: "tablist", children: tabs.map((item) => /* @__PURE__ */ jsx23(
3221
3576
  "button",
3222
3577
  {
3578
+ "data-cv-tooltip": item.label,
3579
+ "aria-label": item.label,
3223
3580
  type: "button",
3224
3581
  role: "tab",
3225
3582
  "aria-selected": tab === item.id,
@@ -3351,16 +3708,16 @@ function useConversationMessages(conversationId, params) {
3351
3708
  }
3352
3709
 
3353
3710
  // src/hooks/useScrollToLatestMessage.ts
3354
- import { useCallback as useCallback8, useLayoutEffect, useRef as useRef5, useState as useState14 } from "react";
3711
+ import { useCallback as useCallback8, useLayoutEffect, useRef as useRef6, useState as useState15 } from "react";
3355
3712
  var NEAR_BOTTOM_THRESHOLD_PX = 120;
3356
3713
  var SCROLL_BEHAVIOR = "auto";
3357
3714
  function useScrollToLatestMessage({
3358
3715
  conversationId,
3359
3716
  messageCount
3360
3717
  }) {
3361
- const containerRef = useRef5(null);
3362
- const [isAwayFromBottom, setIsAwayFromBottom] = useState14(false);
3363
- const isAwayFromBottomRef = useRef5(false);
3718
+ const containerRef = useRef6(null);
3719
+ const [isAwayFromBottom, setIsAwayFromBottom] = useState15(false);
3720
+ const isAwayFromBottomRef = useRef6(false);
3364
3721
  const scrollToBottom = useCallback8((behavior = SCROLL_BEHAVIOR) => {
3365
3722
  const container = containerRef.current;
3366
3723
  if (!container) return;
@@ -3373,7 +3730,7 @@ function useScrollToLatestMessage({
3373
3730
  isAwayFromBottomRef.current = away;
3374
3731
  setIsAwayFromBottom((current) => current === away ? current : away);
3375
3732
  }, []);
3376
- const jumpedForConversationRef = useRef5(void 0);
3733
+ const jumpedForConversationRef = useRef6(void 0);
3377
3734
  useLayoutEffect(() => {
3378
3735
  const isNewConversation = jumpedForConversationRef.current !== conversationId;
3379
3736
  if (isNewConversation) {
@@ -3420,36 +3777,36 @@ function useConversationContext(conversationId) {
3420
3777
  }
3421
3778
 
3422
3779
  // src/hooks/useConversationRealtime.ts
3423
- import { useEffect as useEffect9, useRef as useRef6 } from "react";
3780
+ import { useEffect as useEffect10, useRef as useRef7 } from "react";
3424
3781
  function useConversationRealtime(conversationId, onEvent) {
3425
- const context = useConversations();
3426
- const onEventRef = useRef6(onEvent);
3782
+ const sse = useConversations()?.sse;
3783
+ const onEventRef = useRef7(onEvent);
3427
3784
  onEventRef.current = onEvent;
3428
- useEffect9(() => {
3429
- if (!context || !conversationId) return;
3430
- const source = context.sse.connectConversationStream(conversationId);
3785
+ useEffect10(() => {
3786
+ if (!sse || !conversationId) return;
3787
+ const source = sse.connectConversationStream(conversationId);
3431
3788
  const handler = (event) => onEventRef.current(event);
3432
3789
  source.addEventListener("message", handler);
3433
3790
  return () => {
3434
3791
  source.removeEventListener("message", handler);
3435
3792
  source.close();
3436
3793
  };
3437
- }, [context, conversationId]);
3794
+ }, [sse, conversationId]);
3438
3795
  }
3439
3796
  function useGlobalRealtime(onEvent) {
3440
- const context = useConversations();
3441
- const onEventRef = useRef6(onEvent);
3797
+ const sse = useConversations()?.sse;
3798
+ const onEventRef = useRef7(onEvent);
3442
3799
  onEventRef.current = onEvent;
3443
- useEffect9(() => {
3444
- if (!context) return;
3445
- const source = context.sse.connectGlobalStream();
3800
+ useEffect10(() => {
3801
+ if (!sse) return;
3802
+ const source = sse.connectGlobalStream();
3446
3803
  const handler = (event) => onEventRef.current(event);
3447
3804
  source.addEventListener("message", handler);
3448
3805
  return () => {
3449
3806
  source.removeEventListener("message", handler);
3450
3807
  source.close();
3451
3808
  };
3452
- }, [context]);
3809
+ }, [sse]);
3453
3810
  }
3454
3811
 
3455
3812
  // src/hooks/useConversationActions.ts
@@ -3485,10 +3842,11 @@ function useInboxActions() {
3485
3842
  }
3486
3843
 
3487
3844
  // src/workspace/ConversationsWorkspace.tsx
3488
- import { useEffect as useEffect13, useMemo as useMemo6, useState as useState18 } from "react";
3845
+ import { useEffect as useEffect14, useMemo as useMemo6, useState as useState19 } from "react";
3846
+ import { Check as Check2, CheckCheck, FlaskConical, Hourglass as Hourglass2, Mail as Mail2, MessagesSquare } from "lucide-react";
3489
3847
 
3490
3848
  // src/workspace/BulkTemplateModal.tsx
3491
- import { useEffect as useEffect10, useMemo as useMemo4, useState as useState15 } from "react";
3849
+ import { useEffect as useEffect11, useMemo as useMemo4, useState as useState16 } from "react";
3492
3850
  import { jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
3493
3851
  function BulkTemplateModal({
3494
3852
  labels,
@@ -3502,10 +3860,10 @@ function BulkTemplateModal({
3502
3860
  throw new Error("BulkTemplateModal requires an ancestor <ConversationsProvider>");
3503
3861
  }
3504
3862
  const { api } = context;
3505
- const [templates, setTemplates] = useState15([]);
3506
- const [search, setSearch] = useState15("");
3507
- const [selected, setSelected] = useState15("");
3508
- useEffect10(() => {
3863
+ const [templates, setTemplates] = useState16([]);
3864
+ const [search, setSearch] = useState16("");
3865
+ const [selected, setSelected] = useState16("");
3866
+ useEffect11(() => {
3509
3867
  let active = true;
3510
3868
  void api.listTemplates?.().then((loaded) => {
3511
3869
  if (active) setTemplates(loaded);
@@ -3526,7 +3884,7 @@ function BulkTemplateModal({
3526
3884
  /* @__PURE__ */ jsx24("h3", { children: labels.templateModalTitle }),
3527
3885
  /* @__PURE__ */ jsx24("p", { children: labels.templateModalAvailable(templates.length) })
3528
3886
  ] }),
3529
- /* @__PURE__ */ jsx24("button", { type: "button", onClick: onClose, "aria-label": labels.templateModalCancel, children: "\u2715" })
3887
+ /* @__PURE__ */ jsx24("button", { "data-cv-tooltip": labels.templateModalCancel, type: "button", onClick: onClose, "aria-label": labels.templateModalCancel, children: "\u2715" })
3530
3888
  ] }),
3531
3889
  /* @__PURE__ */ jsx24("div", { className: "cv-workspace-modal__search", children: /* @__PURE__ */ jsx24(
3532
3890
  "input",
@@ -3543,6 +3901,8 @@ function BulkTemplateModal({
3543
3901
  filtered.map((template) => /* @__PURE__ */ jsxs22(
3544
3902
  "button",
3545
3903
  {
3904
+ "data-cv-tooltip": template.name,
3905
+ "aria-label": template.name,
3546
3906
  type: "button",
3547
3907
  onClick: () => setSelected(template.name === selected ? "" : template.name),
3548
3908
  "aria-pressed": template.name === selected,
@@ -3560,10 +3920,12 @@ function BulkTemplateModal({
3560
3920
  ] }),
3561
3921
  expiredCount === 0 ? /* @__PURE__ */ jsx24("p", { className: "cv-workspace-modal__warning", children: labels.templateModalNoneExpired }) : null,
3562
3922
  /* @__PURE__ */ jsxs22("footer", { className: "cv-workspace-modal__footer", children: [
3563
- /* @__PURE__ */ jsx24("button", { type: "button", onClick: onClose, children: labels.templateModalCancel }),
3923
+ /* @__PURE__ */ jsx24("button", { "data-cv-tooltip": labels.templateModalCancel, "aria-label": labels.templateModalCancel, type: "button", onClick: onClose, children: labels.templateModalCancel }),
3564
3924
  /* @__PURE__ */ jsx24(
3565
3925
  "button",
3566
3926
  {
3927
+ "data-cv-tooltip": sending ? labels.templateModalSending : labels.templateModalSend(expiredCount),
3928
+ "aria-label": sending ? labels.templateModalSending : labels.templateModalSend(expiredCount),
3567
3929
  type: "button",
3568
3930
  disabled: sending || expiredCount === 0,
3569
3931
  onClick: () => onSend(selected || void 0),
@@ -3575,7 +3937,7 @@ function BulkTemplateModal({
3575
3937
  }
3576
3938
 
3577
3939
  // src/workspace/ConversationPane.tsx
3578
- import { useEffect as useEffect11, useState as useState16 } from "react";
3940
+ import { useEffect as useEffect12, useRef as useRef8, useState as useState17 } from "react";
3579
3941
  import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
3580
3942
  function ConversationPane({
3581
3943
  conversation,
@@ -3609,12 +3971,14 @@ function ConversationPane({
3609
3971
  const { api } = context;
3610
3972
  const { messages, refetch } = useConversationMessages(conversation.id);
3611
3973
  const { context: conversationContext } = useConversationContext(conversation.id);
3612
- const [documentsOpen, setDocumentsOpen] = useState16(false);
3613
- const [sendFailure, setSendFailure] = useState16(void 0);
3614
- const [selectedMessageIds, setSelectedMessageIds] = useState16(/* @__PURE__ */ new Set());
3615
- const [draft, setDraft] = useState16(initialComposerText ?? "");
3616
- const [queuedFiles, setQueuedFiles] = useState16([]);
3617
- useEffect11(() => {
3974
+ const [documentsOpen, setDocumentsOpen] = useState17(false);
3975
+ const [sendFailure, setSendFailure] = useState17(void 0);
3976
+ const [selectedMessageIds, setSelectedMessageIds] = useState17(/* @__PURE__ */ new Set());
3977
+ const [draft, setDraft] = useState17(initialComposerText ?? "");
3978
+ const [queuedFiles, setQueuedFiles] = useState17([]);
3979
+ const [isSendingDraft, setIsSendingDraft] = useState17(false);
3980
+ const sendInFlightRef = useRef8(false);
3981
+ useEffect12(() => {
3618
3982
  setSelectedMessageIds(/* @__PURE__ */ new Set());
3619
3983
  setDraft(initialComposerText ?? "");
3620
3984
  setQueuedFiles([]);
@@ -3646,25 +4010,40 @@ function ConversationPane({
3646
4010
  try {
3647
4011
  await action();
3648
4012
  await refetch();
4013
+ return true;
3649
4014
  } catch (error) {
3650
4015
  setSendFailure(error instanceof Error ? error.message : fallback);
4016
+ return false;
3651
4017
  }
3652
4018
  }
3653
4019
  async function handleSend(text) {
3654
- await runSend(() => api.sendMessage(conversation.id, text), labels.sendFailure);
4020
+ return runSend(() => api.sendMessage(conversation.id, text), labels.sendFailure);
4021
+ }
4022
+ async function handleSendTemplate() {
4023
+ if (conversation.mode !== "human" && onTakeover) await onTakeover();
4024
+ await runSend(() => api.sendTemplate(conversation.id, {}), labels.sendFailure);
3655
4025
  }
3656
4026
  async function handleRichSend() {
3657
- if (onSendAttachments && queuedFiles.length > 0) {
3658
- const files = queuedFiles;
3659
- const caption = draft;
3660
- await runSend(() => onSendAttachments(files, caption), labels.attachFailure);
3661
- setQueuedFiles([]);
3662
- setDraft("");
3663
- return;
4027
+ if (sendInFlightRef.current) return;
4028
+ sendInFlightRef.current = true;
4029
+ setIsSendingDraft(true);
4030
+ try {
4031
+ if (onSendAttachments && queuedFiles.length > 0) {
4032
+ const files = queuedFiles;
4033
+ const caption = draft;
4034
+ const didSend = await runSend(() => onSendAttachments(files, caption), labels.attachFailure);
4035
+ if (didSend) {
4036
+ setQueuedFiles([]);
4037
+ setDraft("");
4038
+ }
4039
+ return;
4040
+ }
4041
+ if (!draft.trim()) return;
4042
+ if (await handleSend(draft)) setDraft("");
4043
+ } finally {
4044
+ sendInFlightRef.current = false;
4045
+ setIsSendingDraft(false);
3664
4046
  }
3665
- if (!draft.trim()) return;
3666
- await handleSend(draft);
3667
- setDraft("");
3668
4047
  }
3669
4048
  async function handleAttach(file) {
3670
4049
  if (!onAttach) return;
@@ -3738,8 +4117,8 @@ function ConversationPane({
3738
4117
  messageSelection && selectedMessageIds.size > 0 ? /* @__PURE__ */ jsxs23("div", { className: "cv-workspace-selection", children: [
3739
4118
  /* @__PURE__ */ jsx25("span", { children: labels.messagesSelected(selectedMessageIds.size) }),
3740
4119
  /* @__PURE__ */ jsxs23("div", { className: "cv-workspace-selection__actions", children: [
3741
- /* @__PURE__ */ jsx25("button", { type: "button", onClick: () => setSelectedMessageIds(/* @__PURE__ */ new Set()), children: labels.bulkClear }),
3742
- /* @__PURE__ */ jsx25("button", { type: "button", onClick: copySelectedMessages, children: labels.copySelected })
4120
+ /* @__PURE__ */ jsx25("button", { "data-cv-tooltip": labels.bulkClear, "aria-label": labels.bulkClear, type: "button", onClick: () => setSelectedMessageIds(/* @__PURE__ */ new Set()), children: labels.bulkClear }),
4121
+ /* @__PURE__ */ jsx25("button", { "data-cv-tooltip": labels.copySelected, "aria-label": labels.copySelected, type: "button", onClick: copySelectedMessages, children: labels.copySelected })
3743
4122
  ] })
3744
4123
  ] }) : null,
3745
4124
  sendFailure ? /* @__PURE__ */ jsx25("p", { role: "alert", className: "cv-workspace-alert", children: sendFailure }) : null,
@@ -3747,7 +4126,7 @@ function ConversationPane({
3747
4126
  WindowExpiredNotice,
3748
4127
  {
3749
4128
  disabled: busy,
3750
- onSendTemplate: () => void api.sendTemplate(conversation.id, {}).then(() => refetch())
4129
+ onSendTemplate: () => void handleSendTemplate()
3751
4130
  }
3752
4131
  ) : botOwnsConversation ? (
3753
4132
  // Responder com a conversa no bot atropelaria o fluxo automático no meio de uma pergunta.
@@ -3764,7 +4143,8 @@ function ConversationPane({
3764
4143
  }
3765
4144
  } : {},
3766
4145
  placeholder: labels.composerPlaceholder,
3767
- isSending: busy,
4146
+ isSending: busy || isSendingDraft,
4147
+ hasQueuedAttachments: queuedFiles.length > 0,
3768
4148
  ...onRecordAudio ? {
3769
4149
  idleAction: /* @__PURE__ */ jsx25(
3770
4150
  AudioRecorderButton,
@@ -3780,6 +4160,7 @@ function ConversationPane({
3780
4160
  /* @__PURE__ */ jsx25(
3781
4161
  "button",
3782
4162
  {
4163
+ "data-cv-tooltip": labels.attachmentRemove,
3783
4164
  type: "button",
3784
4165
  "aria-label": labels.attachmentRemove,
3785
4166
  onClick: () => setQueuedFiles((current) => current.filter((_, position) => position !== index)),
@@ -3822,11 +4203,13 @@ function ConversationsInboxList({
3822
4203
  hasSelection ? /* @__PURE__ */ jsxs24("div", { className: "cv-workspace-bulk", children: [
3823
4204
  /* @__PURE__ */ jsx26("span", { className: "cv-workspace-bulk__count", children: labels.bulkSelected(inbox.selectedIds.size) }),
3824
4205
  /* @__PURE__ */ jsxs24("div", { className: "cv-workspace-bulk__actions", children: [
3825
- /* @__PURE__ */ jsx26("button", { type: "button", onClick: inbox.clearBulkSelection, children: labels.bulkClear }),
3826
- onSendTemplateToSelected ? /* @__PURE__ */ jsx26("button", { type: "button", onClick: onSendTemplateToSelected, disabled: inbox.busy, children: labels.bulkTemplate }) : null,
4206
+ /* @__PURE__ */ jsx26("button", { "data-cv-tooltip": labels.bulkClear, "aria-label": labels.bulkClear, type: "button", onClick: inbox.clearBulkSelection, children: labels.bulkClear }),
4207
+ onSendTemplateToSelected ? /* @__PURE__ */ jsx26("button", { "data-cv-tooltip": labels.bulkTemplate, "aria-label": labels.bulkTemplate, type: "button", onClick: onSendTemplateToSelected, disabled: inbox.busy, children: labels.bulkTemplate }) : null,
3827
4208
  inbox.canFinalize ? /* @__PURE__ */ jsx26(
3828
4209
  "button",
3829
4210
  {
4211
+ "data-cv-tooltip": labels.bulkFinalize,
4212
+ "aria-label": labels.bulkFinalize,
3830
4213
  type: "button",
3831
4214
  disabled: inbox.busy,
3832
4215
  onClick: () => {
@@ -3853,17 +4236,20 @@ function ConversationsInboxList({
3853
4236
  ),
3854
4237
  /* @__PURE__ */ jsxs24("div", { className: "cv-workspace-chips", children: [
3855
4238
  /* @__PURE__ */ jsx26("span", { className: "cv-workspace-chips__legend", children: labels.windowLegend }),
3856
- WINDOW_FILTERS.map((filter) => /* @__PURE__ */ jsxs24(
4239
+ WINDOW_FILTERS.map((filter) => /* @__PURE__ */ jsx26(
3857
4240
  "button",
3858
4241
  {
3859
4242
  type: "button",
3860
4243
  onClick: () => inbox.setWindowFilter(filter.value),
3861
4244
  "aria-pressed": inbox.windowFilter === filter.value,
3862
- className: `cv-workspace-chip${inbox.windowFilter === filter.value ? " cv-workspace-chip--on" : ""}`,
3863
- children: [
3864
- filter.dotClass ? /* @__PURE__ */ jsx26("span", { className: `cv-workspace-dot ${filter.dotClass}` }) : null,
3865
- filter.label
3866
- ]
4245
+ "data-cv-tooltip": filter.label,
4246
+ "aria-label": filter.label,
4247
+ className: [
4248
+ "cv-workspace-chip",
4249
+ filter.tone ? `cv-workspace-chip--${filter.tone}` : "",
4250
+ inbox.windowFilter === filter.value ? "cv-workspace-chip--on" : ""
4251
+ ].filter(Boolean).join(" "),
4252
+ children: filter.label
3867
4253
  },
3868
4254
  filter.value
3869
4255
  ))
@@ -3873,6 +4259,8 @@ function ConversationsInboxList({
3873
4259
  inbox.channelFilters.map((filter) => /* @__PURE__ */ jsxs24(
3874
4260
  "button",
3875
4261
  {
4262
+ "data-cv-tooltip": filter.label,
4263
+ "aria-label": filter.label,
3876
4264
  type: "button",
3877
4265
  onClick: () => inbox.setChannelFilter(filter.value),
3878
4266
  "aria-pressed": inbox.channelFilter === filter.value,
@@ -3913,14 +4301,15 @@ function ConversationsInboxList({
3913
4301
  /* @__PURE__ */ jsxs24("div", { className: "cv-workspace-pager", children: [
3914
4302
  /* @__PURE__ */ jsx26("span", { children: labels.rangeOf(inbox.firstOnPage, inbox.lastOnPage, inbox.filteredCount) }),
3915
4303
  /* @__PURE__ */ jsxs24("div", { className: "cv-workspace-pager__buttons", children: [
3916
- /* @__PURE__ */ jsx26("button", { type: "button", onClick: () => inbox.goToPage(1), disabled: inbox.page === 1, "aria-label": "Primeira p\xE1gina", children: "\xAB" }),
4304
+ /* @__PURE__ */ jsx26("button", { type: "button", onClick: () => inbox.goToPage(1), disabled: inbox.page === 1, "aria-label": PAGINATION_LABELS.first, "data-cv-tooltip": PAGINATION_LABELS.first, children: "\xAB" }),
3917
4305
  /* @__PURE__ */ jsx26(
3918
4306
  "button",
3919
4307
  {
3920
4308
  type: "button",
3921
4309
  onClick: () => inbox.goToPage(inbox.page - 1),
3922
4310
  disabled: inbox.page === 1,
3923
- "aria-label": "P\xE1gina anterior",
4311
+ "aria-label": PAGINATION_LABELS.previous,
4312
+ "data-cv-tooltip": PAGINATION_LABELS.previous,
3924
4313
  children: "\u2039"
3925
4314
  }
3926
4315
  ),
@@ -3931,7 +4320,8 @@ function ConversationsInboxList({
3931
4320
  type: "button",
3932
4321
  onClick: () => inbox.goToPage(inbox.page + 1),
3933
4322
  disabled: inbox.page === inbox.pageCount,
3934
- "aria-label": "Pr\xF3xima p\xE1gina",
4323
+ "aria-label": PAGINATION_LABELS.next,
4324
+ "data-cv-tooltip": PAGINATION_LABELS.next,
3935
4325
  children: "\u203A"
3936
4326
  }
3937
4327
  ),
@@ -3941,7 +4331,8 @@ function ConversationsInboxList({
3941
4331
  type: "button",
3942
4332
  onClick: () => inbox.goToPage(inbox.pageCount),
3943
4333
  disabled: inbox.page === inbox.pageCount,
3944
- "aria-label": "\xDAltima p\xE1gina",
4334
+ "aria-label": PAGINATION_LABELS.last,
4335
+ "data-cv-tooltip": PAGINATION_LABELS.last,
3945
4336
  children: "\xBB"
3946
4337
  }
3947
4338
  )
@@ -3992,7 +4383,7 @@ var DEFAULT_CONVERSATIONS_WORKSPACE_LABELS = {
3992
4383
  };
3993
4384
 
3994
4385
  // src/workspace/useConversationsInbox.ts
3995
- import { useCallback as useCallback9, useEffect as useEffect12, useMemo as useMemo5, useRef as useRef7, useState as useState17 } from "react";
4386
+ import { useCallback as useCallback9, useEffect as useEffect13, useMemo as useMemo5, useRef as useRef9, useState as useState18 } from "react";
3996
4387
  var CONVERSATIONS_PER_PAGE = 50;
3997
4388
  function defaultDescribeFailure(error) {
3998
4389
  const status = error.status;
@@ -4010,14 +4401,14 @@ function useConversationsInbox(params = {}) {
4010
4401
  const perPage = params.perPage ?? CONVERSATIONS_PER_PAGE;
4011
4402
  const markReadOnOpen = params.markReadOnOpen ?? true;
4012
4403
  const describeFailure = params.describeFailure ?? defaultDescribeFailure;
4013
- const [waitingOnly, setWaitingOnly] = useState17(false);
4014
- const [windowFilter, setWindowFilter] = useState17(CONVERSATION_WINDOW.ALL);
4015
- const [channelFilter, setChannelFilter] = useState17(CHANNEL_FILTER_ALL);
4016
- const [search, setSearch] = useState17("");
4017
- const [page, setPage] = useState17(1);
4018
- const [selectedId, setSelectedId] = useState17(void 0);
4019
- const [selectedIds, setSelectedIds] = useState17(/* @__PURE__ */ new Set());
4020
- const [busy, setBusy] = useState17(false);
4404
+ const [waitingOnly, setWaitingOnly] = useState18(false);
4405
+ const [windowFilter, setWindowFilter] = useState18(CONVERSATION_WINDOW.ALL);
4406
+ const [channelFilter, setChannelFilter] = useState18(CHANNEL_FILTER_ALL);
4407
+ const [search, setSearch] = useState18("");
4408
+ const [page, setPage] = useState18(1);
4409
+ const [selectedId, setSelectedId] = useState18(void 0);
4410
+ const [selectedIds, setSelectedIds] = useState18(/* @__PURE__ */ new Set());
4411
+ const [busy, setBusy] = useState18(false);
4021
4412
  const { conversations, total, loading, error, refetch } = useConversationList({
4022
4413
  ...waitingOnly ? { waitingHuman: true } : {},
4023
4414
  ...search ? { search } : {},
@@ -4042,16 +4433,16 @@ function useConversationsInbox(params = {}) {
4042
4433
  const pageCount = Math.max(1, Math.ceil(totalForPaging / perPage));
4043
4434
  const currentPage = Math.min(page, pageCount);
4044
4435
  const pageConversations = params.serverPaginated ? filtered : filtered.slice((currentPage - 1) * perPage, currentPage * perPage);
4045
- useEffect12(() => {
4436
+ useEffect13(() => {
4046
4437
  setPage(1);
4047
4438
  }, [search, waitingOnly, windowFilter, channelFilter]);
4048
- useEffect12(() => {
4439
+ useEffect13(() => {
4049
4440
  if (selectedId && !conversations.some((conversation) => conversation.id === selectedId)) {
4050
4441
  setSelectedId(void 0);
4051
4442
  }
4052
4443
  }, [conversations, selectedId]);
4053
- const lastMarkReadAttempt = useRef7(void 0);
4054
- useEffect12(() => {
4444
+ const lastMarkReadAttempt = useRef9(void 0);
4445
+ useEffect13(() => {
4055
4446
  if (!markReadOnOpen || !selectedId) return;
4056
4447
  const opened = conversations.find((conversation) => conversation.id === selectedId);
4057
4448
  if (!opened || opened.unread === 0) return;
@@ -4111,8 +4502,11 @@ function useConversationsInbox(params = {}) {
4111
4502
  selectedConversation: conversations.find((conversation) => conversation.id === selectedId),
4112
4503
  loading,
4113
4504
  now,
4114
- totalCount: conversations.length,
4115
- unreadCount: conversations.reduce((total2, conversation) => total2 + conversation.unread, 0),
4505
+ // Com paginação no servidor `conversations` é só a página, então contar o array mostrava o
4506
+ // tamanho da página no lugar do tamanho da base. Não lidas e aguardando seguem sendo da página:
4507
+ // somar a base inteira exige agregado do servidor, que a listagem não devolve.
4508
+ totalCount: params.serverPaginated ? total : conversations.length,
4509
+ unreadCount: conversations.reduce((sum, conversation) => sum + conversation.unread, 0),
4116
4510
  waitingCount: conversations.filter((conversation) => conversation.waitingHuman).length,
4117
4511
  filteredCount: totalForPaging,
4118
4512
  expiredSelectedCount: conversations.filter(
@@ -4211,10 +4605,10 @@ function ConversationsWorkspace({
4211
4605
  ...markReadOnOpen === void 0 ? {} : { markReadOnOpen },
4212
4606
  ...serverPaginated ? { serverPaginated } : {}
4213
4607
  });
4214
- const [simulatorOpen, setSimulatorOpen] = useState18(false);
4215
- const [templateModalOpen, setTemplateModalOpen] = useState18(false);
4216
- const [openedFromLink, setOpenedFromLink] = useState18(void 0);
4217
- useEffect13(() => {
4608
+ const [simulatorOpen, setSimulatorOpen] = useState19(false);
4609
+ const [templateModalOpen, setTemplateModalOpen] = useState19(false);
4610
+ const [openedFromLink, setOpenedFromLink] = useState19(void 0);
4611
+ useEffect14(() => {
4218
4612
  const link = initialConversationId ?? initialWhatsappNumber;
4219
4613
  if (!link || openedFromLink === link) return;
4220
4614
  const target = inbox.conversations.find(
@@ -4235,7 +4629,7 @@ function ConversationsWorkspace({
4235
4629
  ...fromProduct,
4236
4630
  {
4237
4631
  key: "simulator",
4238
- icon: simulator?.icon ?? "\u{1F9EA}",
4632
+ icon: simulator?.icon ?? /* @__PURE__ */ jsx27(FlaskConical, { size: ICON_SIZE_ACTION }),
4239
4633
  label: simulator?.label ?? "Simular cliente",
4240
4634
  active: simulatorOpen,
4241
4635
  run: () => setSimulatorOpen((open) => !open)
@@ -4243,28 +4637,32 @@ function ConversationsWorkspace({
4243
4637
  ];
4244
4638
  }, [selected, extraUtilitiesFor, simulatorEnabled, simulator, simulatorOpen]);
4245
4639
  return /* @__PURE__ */ jsxs25("div", { className: `cv-workspace${className ? ` ${className}` : ""}`, children: [
4640
+ /* @__PURE__ */ jsx27(TooltipLayer, {}),
4246
4641
  /* @__PURE__ */ jsxs25("header", { className: "cv-workspace-header", children: [
4247
4642
  /* @__PURE__ */ jsxs25("div", { className: "cv-workspace-header__titles", children: [
4248
4643
  /* @__PURE__ */ jsx27("h1", { children: labels.title }),
4249
4644
  /* @__PURE__ */ jsxs25("p", { children: [
4250
- /* @__PURE__ */ jsxs25("span", { title: labels.conversations, children: [
4251
- "\u{1F4AC} ",
4645
+ /* @__PURE__ */ jsxs25("span", { "data-cv-tooltip": labels.conversations, className: "cv-stat", children: [
4646
+ /* @__PURE__ */ jsx27(MessagesSquare, { size: ICON_SIZE_INLINE, "aria-hidden": "true" }),
4647
+ " ",
4252
4648
  inbox.totalCount,
4253
4649
  /* @__PURE__ */ jsxs25("span", { className: "cv-only-wide", children: [
4254
4650
  " ",
4255
4651
  labels.conversations
4256
4652
  ] })
4257
4653
  ] }),
4258
- /* @__PURE__ */ jsxs25("span", { title: labels.waiting, className: "cv-workspace-header__waiting", children: [
4259
- "\u23F3 ",
4654
+ /* @__PURE__ */ jsxs25("span", { "data-cv-tooltip": labels.waiting, className: "cv-stat cv-workspace-header__waiting", children: [
4655
+ /* @__PURE__ */ jsx27(Hourglass2, { size: ICON_SIZE_INLINE, "aria-hidden": "true" }),
4656
+ " ",
4260
4657
  inbox.waitingCount,
4261
4658
  /* @__PURE__ */ jsxs25("span", { className: "cv-only-wide", children: [
4262
4659
  " ",
4263
4660
  labels.waiting
4264
4661
  ] })
4265
4662
  ] }),
4266
- /* @__PURE__ */ jsxs25("span", { title: labels.unread, children: [
4267
- "\u2709\uFE0F ",
4663
+ /* @__PURE__ */ jsxs25("span", { "data-cv-tooltip": labels.unread, className: "cv-stat", children: [
4664
+ /* @__PURE__ */ jsx27(Mail2, { size: ICON_SIZE_INLINE, "aria-hidden": "true" }),
4665
+ " ",
4268
4666
  inbox.unreadCount,
4269
4667
  /* @__PURE__ */ jsxs25("span", { className: "cv-only-wide", children: [
4270
4668
  " ",
@@ -4280,14 +4678,12 @@ function ConversationsWorkspace({
4280
4678
  type: "button",
4281
4679
  onClick: () => inbox.setWaitingOnly(!inbox.waitingOnly),
4282
4680
  "aria-pressed": inbox.waitingOnly,
4283
- title: labels.waitingOnly,
4681
+ "data-cv-tooltip": labels.waitingOnly,
4682
+ "aria-label": labels.waitingOnly,
4284
4683
  className: inbox.waitingOnly ? "cv-workspace-toggle cv-workspace-toggle--on" : "cv-workspace-toggle",
4285
4684
  children: [
4286
- "\u23F3",
4287
- /* @__PURE__ */ jsxs25("span", { className: "cv-only-wide", children: [
4288
- " ",
4289
- labels.waitingOnly
4290
- ] })
4685
+ /* @__PURE__ */ jsx27(Hourglass2, { size: ICON_SIZE_ACTION, "aria-hidden": "true" }),
4686
+ /* @__PURE__ */ jsx27("span", { className: "cv-only-wide", children: labels.waitingOnly })
4291
4687
  ]
4292
4688
  }
4293
4689
  ),
@@ -4297,16 +4693,17 @@ function ConversationsWorkspace({
4297
4693
  type: "button",
4298
4694
  onClick: () => void inbox.markSelectedAsRead(),
4299
4695
  disabled: inbox.selectedIds.size === 0 || inbox.busy,
4300
- title: labels.markSelectedAsRead,
4696
+ "data-cv-tooltip": labels.markSelectedAsRead,
4301
4697
  "aria-label": labels.markSelectedAsRead,
4302
4698
  className: "cv-workspace-toggle",
4303
4699
  children: [
4304
- "\u2713",
4305
- /* @__PURE__ */ jsxs25("span", { className: "cv-only-wide", children: [
4306
- " ",
4307
- labels.markSelectedAsRead
4308
- ] }),
4309
- inbox.selectedIds.size > 0 ? ` (${inbox.selectedIds.size})` : ""
4700
+ /* @__PURE__ */ jsx27(Check2, { size: ICON_SIZE_ACTION, "aria-hidden": "true" }),
4701
+ /* @__PURE__ */ jsx27("span", { className: "cv-only-wide", children: labels.markSelectedAsRead }),
4702
+ inbox.selectedIds.size > 0 ? /* @__PURE__ */ jsxs25("span", { children: [
4703
+ "(",
4704
+ inbox.selectedIds.size,
4705
+ ")"
4706
+ ] }) : null
4310
4707
  ]
4311
4708
  }
4312
4709
  ),
@@ -4316,14 +4713,12 @@ function ConversationsWorkspace({
4316
4713
  type: "button",
4317
4714
  onClick: () => void inbox.markAllAsRead(),
4318
4715
  disabled: inbox.busy,
4319
- title: labels.markAllAsRead,
4716
+ "data-cv-tooltip": labels.markAllAsRead,
4717
+ "aria-label": labels.markAllAsRead,
4320
4718
  className: "cv-workspace-toggle",
4321
4719
  children: [
4322
- "\u2713\u2713",
4323
- /* @__PURE__ */ jsxs25("span", { className: "cv-only-wide", children: [
4324
- " ",
4325
- labels.markAllAsRead
4326
- ] })
4720
+ /* @__PURE__ */ jsx27(CheckCheck, { size: ICON_SIZE_ACTION, "aria-hidden": "true" }),
4721
+ /* @__PURE__ */ jsx27("span", { className: "cv-only-wide", children: labels.markAllAsRead })
4327
4722
  ]
4328
4723
  }
4329
4724
  ) : null,
@@ -4362,7 +4757,7 @@ function ConversationsWorkspace({
4362
4757
  now: inbox.now,
4363
4758
  busy: inbox.busy,
4364
4759
  labels,
4365
- ...inbox.canTakeover ? { onTakeover: () => void inbox.takeover(selected.id) } : {},
4760
+ ...inbox.canTakeover ? { onTakeover: () => inbox.takeover(selected.id) } : {},
4366
4761
  ...inbox.canTakeover ? { onReturnToBot: () => void inbox.releaseToBot(selected.id) } : {},
4367
4762
  ...inbox.canFinalize ? { onFinish: () => void inbox.finalize(selected.id) } : {},
4368
4763
  ...flowLabelOf ? { flowLabel: flowLabelOf(selected) } : {},
@@ -4478,7 +4873,9 @@ export {
4478
4873
  SortableHead,
4479
4874
  StatusTicks,
4480
4875
  TEMPLATE_SETTINGS_TAB,
4876
+ TOOLTIP_ATTRIBUTE,
4481
4877
  ToastProvider,
4878
+ TooltipLayer,
4482
4879
  TopicsForm,
4483
4880
  TranscriptionSettingsForm,
4484
4881
  WINDOW_FILTERS,