@adatechnology/conversations-ui 0.1.0-rc.3 → 0.1.0-rc.5

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.
@@ -18,7 +18,14 @@ var DEFAULT_LOCALES = {
18
18
  viewImage: "Ver imagem",
19
19
  listenAudio: "Ouvir \xE1udio",
20
20
  viewVideo: "Ver v\xEDdeo",
21
- moderationFlagged: "Linguagem ofensiva"
21
+ moderationFlagged: "Linguagem ofensiva",
22
+ mediaLoading: "Carregando...",
23
+ mediaRetry: "Erro \u2014 tentar novamente",
24
+ mediaError: "Erro",
25
+ mediaUnavailable: "M\xEDdia indispon\xEDvel",
26
+ imageAlt: "Imagem",
27
+ untitledDocument: "Documento",
28
+ downloadFile: "Baixar"
22
29
  },
23
30
  selection: {
24
31
  select: "Selecionar"
@@ -160,6 +167,15 @@ function AudioPlayer({ src, isMine = false }) {
160
167
 
161
168
  // src/FileIcon.tsx
162
169
  import { FileArchive, FileSpreadsheet, FileText, File as FileGeneric } from "lucide-react";
170
+
171
+ // src/lib/cn.ts
172
+ import { clsx } from "clsx";
173
+ import { twMerge } from "tailwind-merge";
174
+ function cn(...inputs) {
175
+ return twMerge(clsx(inputs));
176
+ }
177
+
178
+ // src/FileIcon.tsx
163
179
  import { jsx as jsx4 } from "react/jsx-runtime";
164
180
  var EXTENSION_STYLE = {
165
181
  pdf: { Icon: FileText, colorClass: "text-red-500" },
@@ -169,16 +185,16 @@ var EXTENSION_STYLE = {
169
185
  xlsx: { Icon: FileSpreadsheet, colorClass: "text-green-600" },
170
186
  zip: { Icon: FileArchive, colorClass: "text-orange-500" }
171
187
  };
172
- function getExtension(filename, mimeType) {
188
+ function resolveFileIconExtension(filename, mimeType) {
173
189
  const fromFilename = filename?.split(".").pop()?.toLowerCase();
174
190
  if (fromFilename && EXTENSION_STYLE[fromFilename]) return fromFilename;
175
191
  return mimeType?.split("/")[1]?.toLowerCase() ?? "";
176
192
  }
177
- function FileIcon({ filename, mimeType, size = 20, className = "" }) {
178
- const extension = getExtension(filename, mimeType);
193
+ function FileIcon({ filename, mimeType, size = 20, className }) {
194
+ const extension = resolveFileIconExtension(filename, mimeType);
179
195
  const style = EXTENSION_STYLE[extension] ?? { Icon: FileGeneric, colorClass: "text-gray-500" };
180
196
  const { Icon, colorClass } = style;
181
- return /* @__PURE__ */ jsx4(Icon, { size, className: `${colorClass} ${className}`.trim() });
197
+ return /* @__PURE__ */ jsx4(Icon, { size, className: cn(colorClass, className) });
182
198
  }
183
199
 
184
200
  // src/lib/format.ts
@@ -243,7 +259,7 @@ function useLazyMediaUrl(message, onResolveUrl) {
243
259
  };
244
260
  return { url, loading, error, load };
245
261
  }
246
- function MediaRenderer({ message, onLightbox, onResolveUrl }) {
262
+ function MediaRenderer({ message, onLightbox, onResolveUrl, className }) {
247
263
  const { bubble } = useConversationLocales();
248
264
  const eagerSrc = resolveMediaSource(message);
249
265
  const lazy = useLazyMediaUrl(message, onResolveUrl);
@@ -254,9 +270,9 @@ function MediaRenderer({ message, onLightbox, onResolveUrl }) {
254
270
  case "image":
255
271
  case "sticker": {
256
272
  if (!src && canLazyLoad) {
257
- return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? "Carregando..." : lazy.error ? "Erro \u2014 tentar novamente" : bubble.viewImage });
273
+ return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.viewImage });
258
274
  }
259
- return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5("img", { src, alt: message.caption ?? "Image", className: "w-full max-h-80 object-cover cursor-pointer hover:opacity-90 transition-opacity", onClick: () => onLightbox(src), loading: "lazy" }) : /* @__PURE__ */ jsx5("div", { className: "w-full h-40 bg-gray-200 flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs3("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
275
+ return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5("img", { src, alt: message.caption ?? bubble.imageAlt, className: "w-full max-h-80 object-cover cursor-pointer hover:opacity-90 transition-opacity", onClick: () => onLightbox(src), loading: "lazy" }) : /* @__PURE__ */ jsx5("div", { className: "w-full h-40 bg-gray-200 flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs3("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
260
276
  /* @__PURE__ */ jsx5("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }),
261
277
  /* @__PURE__ */ jsx5("circle", { cx: "8.5", cy: "8.5", r: "1.5" }),
262
278
  /* @__PURE__ */ jsx5("polyline", { points: "21 15 16 10 5 21" })
@@ -264,7 +280,7 @@ function MediaRenderer({ message, onLightbox, onResolveUrl }) {
264
280
  }
265
281
  case "video": {
266
282
  if (!src && canLazyLoad) {
267
- return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? "Carregando..." : lazy.error ? "Erro \u2014 tentar novamente" : bubble.viewVideo });
283
+ return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.viewVideo });
268
284
  }
269
285
  return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5("video", { src, className: "w-full max-h-80 rounded-lg", controls: true, preload: "metadata", children: /* @__PURE__ */ jsx5("track", { kind: "captions" }) }) : /* @__PURE__ */ jsx5("div", { className: "w-full h-32 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400", children: /* @__PURE__ */ jsxs3("svg", { width: "32", height: "32", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [
270
286
  /* @__PURE__ */ jsx5("polygon", { points: "23 7 16 12 23 17 23 7" }),
@@ -273,20 +289,20 @@ function MediaRenderer({ message, onLightbox, onResolveUrl }) {
273
289
  }
274
290
  case "audio": {
275
291
  if (!src && canLazyLoad) {
276
- return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? "Carregando..." : lazy.error ? "Erro \u2014 tentar novamente" : bubble.listenAudio });
292
+ return /* @__PURE__ */ jsx5("button", { onClick: lazy.load, className: lazyButtonClass, children: lazy.loading ? bubble.mediaLoading : lazy.error ? bubble.mediaRetry : bubble.listenAudio });
277
293
  }
278
- return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5(AudioPlayer, { src, isMine: message.direction === "outbound" }) : /* @__PURE__ */ jsx5("div", { className: "h-12 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400 text-xs", children: "Audio unavailable" }) });
294
+ return /* @__PURE__ */ jsx5("div", { className: "min-w-[200px]", children: src ? /* @__PURE__ */ jsx5(AudioPlayer, { src, isMine: message.direction === "outbound" }) : /* @__PURE__ */ jsx5("div", { className: "h-12 bg-gray-200 rounded-lg flex items-center justify-center text-gray-400 text-xs", children: bubble.mediaUnavailable }) });
279
295
  }
280
296
  case "document": {
281
297
  const typeLabel = message.mimeType?.split("/")[1]?.toUpperCase() ?? "FILE";
282
298
  const sizeLabel = message.sizeBytes ? formatFileSize(message.sizeBytes) : null;
283
- return /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-3 min-w-[200px]", children: [
299
+ return /* @__PURE__ */ jsxs3("div", { className: cn("flex items-center gap-3 min-w-[200px]", className), children: [
284
300
  /* @__PURE__ */ jsx5("div", { className: "w-10 h-10 bg-gray-200 rounded-lg flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx5(FileIcon, { filename: message.filename, mimeType: message.mimeType }) }),
285
301
  /* @__PURE__ */ jsxs3("div", { className: "flex-1 min-w-0", children: [
286
- /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium truncate", children: message.filename ?? "Document" }),
302
+ /* @__PURE__ */ jsx5("p", { className: "text-sm font-medium truncate", children: message.filename ?? bubble.untitledDocument }),
287
303
  /* @__PURE__ */ jsx5("p", { className: "text-xs text-gray-500", children: sizeLabel ? `${typeLabel} \xB7 ${sizeLabel}` : typeLabel })
288
304
  ] }),
289
- src ? /* @__PURE__ */ jsx5("a", { href: src, download: message.filename, className: "w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 hover:bg-gray-300 flex-shrink-0 transition-colors", "aria-label": "Download", children: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
305
+ src ? /* @__PURE__ */ jsx5("a", { href: src, download: message.filename, className: "w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 hover:bg-gray-300 flex-shrink-0 transition-colors", "aria-label": bubble.downloadFile, children: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
290
306
  /* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
291
307
  /* @__PURE__ */ jsx5("polyline", { points: "7 10 12 15 17 10" }),
292
308
  /* @__PURE__ */ jsx5("line", { x1: "12", y1: "15", x2: "12", y2: "3" })
@@ -296,7 +312,7 @@ function MediaRenderer({ message, onLightbox, onResolveUrl }) {
296
312
  onClick: lazy.load,
297
313
  disabled: lazy.loading,
298
314
  className: "w-8 h-8 flex items-center justify-center rounded-full bg-gray-200 hover:bg-gray-300 flex-shrink-0 transition-colors disabled:opacity-50",
299
- "aria-label": "Download",
315
+ "aria-label": bubble.downloadFile,
300
316
  children: /* @__PURE__ */ jsxs3("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", className: "text-gray-600", children: [
301
317
  /* @__PURE__ */ jsx5("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }),
302
318
  /* @__PURE__ */ jsx5("polyline", { points: "7 10 12 15 17 10" }),
@@ -304,7 +320,7 @@ function MediaRenderer({ message, onLightbox, onResolveUrl }) {
304
320
  ] })
305
321
  }
306
322
  ) : null,
307
- lazy.error && /* @__PURE__ */ jsx5("span", { className: "text-xs text-red-500 flex-shrink-0", children: "Erro" })
323
+ lazy.error && /* @__PURE__ */ jsx5("span", { className: "text-xs text-red-500 flex-shrink-0", children: bubble.mediaError })
308
324
  ] });
309
325
  }
310
326
  default:
@@ -325,15 +341,6 @@ function Lightbox({ imageUrl, caption, onClose }) {
325
341
  // src/MessageBubble.tsx
326
342
  import { useState as useState3 } from "react";
327
343
  import { Check } from "lucide-react";
328
-
329
- // src/lib/cn.ts
330
- import { clsx } from "clsx";
331
- import { twMerge } from "tailwind-merge";
332
- function cn(...inputs) {
333
- return twMerge(clsx(inputs));
334
- }
335
-
336
- // src/MessageBubble.tsx
337
344
  import { Fragment, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
338
345
  var BUBBLE_COLOR = {
339
346
  agent: "bg-[#d9fdd3] dark:bg-[#005c4b]",
@@ -705,12 +712,14 @@ export {
705
712
  useConversationLocales,
706
713
  StatusTicks,
707
714
  AudioPlayer,
715
+ cn,
708
716
  FileIcon,
709
717
  formatTimestamp,
718
+ formatDateTime,
719
+ isSameDay,
710
720
  formatFileSize,
711
721
  MediaRenderer,
712
722
  Lightbox,
713
- cn,
714
723
  MessageBubble,
715
724
  ConversationWallpaper,
716
725
  EmojiPicker,
package/dist/index.d.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  import * as react from 'react';
2
2
  import react__default, { ReactNode, FormEvent } from 'react';
3
- import { M as MessagePayload, k as ConversationsFeatures, i as ConversationSummary, f as ConversationChannel, j as ConversationsApi, S as SSEProvider, g as ConversationDocument } from './types-C0PtaO7S.js';
4
- export { C as CHANNEL_CAPABILITIES, a as CHANNEL_FILTER_ALL, b as CONVERSATION_CHANNEL, c as ChannelCapabilities, d as ChannelFilter, e as ChannelFilterOption, h as ConversationEventSource, l as ConversationsTheme, m as ConversationsUIConfig, D as DEFAULT_CONVERSATION_CHANNEL, F as FormatContactHandleParams, H as HANDLE_KIND, n as HandleKind, R as REOPEN_MECHANISM, o as ReopenMechanism, p as capabilitiesOf, q as channelFiltersFor, r as contactFlag, s as formatContactHandle } from './types-C0PtaO7S.js';
3
+ import { M as MessagePayload, n as ConversationsFeatures, k as ConversationSummary, f as ConversationChannel, L as ListConversationsParams, m as ConversationsApi, S as SSEProvider, r as ListDocumentsParams, g as ConversationDocument, l as ConversationTemplate } from './types-C2Yexi8A.js';
4
+ export { C as CHANNEL_CAPABILITIES, a as CHANNEL_FILTER_ALL, b as CONVERSATION_CHANNEL, c as ChannelCapabilities, d as ChannelFilter, e as ChannelFilterOption, h as ConversationDocumentPage, i as ConversationEventSource, j as ConversationPage, o as ConversationsTheme, p as ConversationsUIConfig, D as DEFAULT_CONVERSATION_CHANNEL, F as FormatContactHandleParams, H as HANDLE_KIND, q as HandleKind, R as REOPEN_MECHANISM, s as ReopenMechanism, t as capabilitiesOf, u as channelFiltersFor, v as contactFlag, w as formatContactHandle } from './types-C2Yexi8A.js';
5
5
 
6
6
  type ResolveMediaUrl = (message: MessagePayload) => Promise<string | null>;
7
7
  interface MediaRendererProps {
8
8
  message: MessagePayload;
9
9
  onLightbox: (src: string) => void;
10
10
  onResolveUrl?: ResolveMediaUrl;
11
+ /** Aplicado no wrapper de cada tipo de mídia — imagem, vídeo, áudio e documento. */
12
+ className?: string;
11
13
  }
12
- declare function MediaRenderer({ message, onLightbox, onResolveUrl }: MediaRendererProps): react.JSX.Element | null;
14
+ declare function MediaRenderer({ message, onLightbox, onResolveUrl, className }: MediaRendererProps): react.JSX.Element | null;
13
15
 
14
16
  interface MessageBubbleProps {
15
17
  message: MessagePayload;
@@ -44,6 +46,13 @@ interface ConversationLocales {
44
46
  listenAudio: string;
45
47
  viewVideo: string;
46
48
  moderationFlagged: string;
49
+ mediaLoading: string;
50
+ mediaRetry: string;
51
+ mediaError: string;
52
+ mediaUnavailable: string;
53
+ imageAlt: string;
54
+ untitledDocument: string;
55
+ downloadFile: string;
47
56
  };
48
57
  selection: {
49
58
  select: string;
@@ -373,37 +382,69 @@ declare function WindowExpiredNotice({ onSendTemplate, disabled, labels: labelsO
373
382
  */
374
383
  declare function isWindowBlocking(window: ConversationWindow): boolean;
375
384
 
376
- /**
377
- * Arquivos trocados na conversa. Sai do transcript e vira lista própria porque anexo é o que o
378
- * atendente mais precisa reencontrar depois — rolar meses de mensagens para achar um comprovante é
379
- * o caso que a busca por documento existe para eliminar.
380
- *
381
- * Usa `useConversationDocuments`, então funciona com qualquer `ConversationsApi`. Host sem
382
- * biblioteca de documentos cai no estado vazio, sem quebrar.
383
- */
384
385
  interface ConversationDocumentsPanelLabels {
385
386
  toggle: string;
386
387
  title: string;
387
388
  searchPlaceholder: string;
388
389
  empty: string;
390
+ /** Distinto de `empty`: sem resultado POR CAUSA do filtro, e não conversa sem anexo nenhum. */
391
+ noResults: string;
389
392
  loading: string;
390
393
  failure: string;
391
394
  download: string;
395
+ view: string;
396
+ sourceFilterAll: string;
397
+ sourceFilterCustomer: string;
398
+ sourceFilterTeam: string;
399
+ sortMostRecent: string;
400
+ sortOldest: string;
401
+ clearFilters: string;
402
+ selectAll: string;
403
+ downloadSelected: (count: number) => string;
404
+ archiveFailed: string;
405
+ total: (count: number) => string;
406
+ page: (current: number, last: number) => string;
392
407
  }
393
408
  declare const DEFAULT_CONVERSATION_DOCUMENTS_LABELS: ConversationDocumentsPanelLabels;
409
+ /**
410
+ * Partes estilizáveis do painel, no mesmo contrato do `ConversationHeader`: `cn` funde por cima da
411
+ * base e conflito de utilitário (padding, tamanho de fonte, borda) fica com o valor do produto.
412
+ *
413
+ * `status` cobre carregando/erro/vazio de uma vez — são a mesma linha de texto auxiliar, e slots
414
+ * separados só multiplicariam chave para quem quer mudar a cor de aviso.
415
+ */
394
416
  interface ConversationDocumentsPanelClassNames {
395
417
  root: string;
396
418
  body: string;
419
+ title: string;
420
+ filters: string;
421
+ search: string;
422
+ sourceSelect: string;
423
+ sortButton: string;
424
+ clearButton: string;
425
+ status: string;
426
+ list: string;
427
+ item: string;
428
+ sourceBadge: string;
429
+ filename: string;
430
+ meta: string;
431
+ viewButton: string;
432
+ downloadButton: string;
433
+ pagination: string;
434
+ selectionBar: string;
435
+ checkbox: string;
397
436
  }
398
437
  interface ConversationDocumentsPanelProps {
399
438
  conversationId: string;
400
439
  /** Controlado de fora porque o gatilho vive no cabeçalho, junto das outras ações da conversa. */
401
440
  open: boolean;
441
+ /** Itens por página. O total vem do servidor; sem paginação no host, a barra não aparece. */
442
+ perPage?: number;
402
443
  labels?: Partial<ConversationDocumentsPanelLabels>;
403
444
  className?: string;
404
445
  classNames?: Partial<ConversationDocumentsPanelClassNames>;
405
446
  }
406
- declare function ConversationDocumentsPanel({ conversationId, open, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
447
+ declare function ConversationDocumentsPanel({ conversationId, open, perPage, labels: labelsOverride, className, classNames, }: ConversationDocumentsPanelProps): react.JSX.Element | null;
407
448
 
408
449
  /**
409
450
  * Serialização do transcript para download. Fica no pacote porque o formato de um histórico de
@@ -454,11 +495,35 @@ declare function useDarkMode(): {
454
495
  declare const NARROW_MAX_WIDTH_PX = 1023;
455
496
  declare function useIsNarrow(): boolean;
456
497
 
498
+ interface UseWaitingNotificationsLabels {
499
+ /** Título da notificação do sistema. Recebe a conversa para o host escolher nome × número. */
500
+ title: (conversation: ConversationSummary) => string;
501
+ body: (conversation: ConversationSummary) => string;
502
+ }
503
+ interface UseWaitingNotificationsParams {
504
+ /**
505
+ * Repassado cru ao `fetchConversations`. É o que permite filtrar não lidas **no servidor** em
506
+ * vez de baixar a lista inteira e contar no cliente: um painel com milhares de conversas não
507
+ * pode paginar 50 por vez atrás de quem tem `unread > 0`.
508
+ */
509
+ readonly params?: ListConversationsParams;
510
+ readonly intervalMs?: number;
511
+ /** Desliga o polling sem desmontar quem chama — útil com a aba em segundo plano. */
512
+ readonly enabled?: boolean;
513
+ readonly icon?: string;
514
+ readonly labels?: Partial<UseWaitingNotificationsLabels>;
515
+ }
457
516
  interface UseWaitingNotificationsResult {
458
517
  unreadCount: number;
459
518
  conversations: ConversationSummary[];
519
+ /**
520
+ * Releitura sob demanda. Existe porque o polling é o piso, não o mecanismo: quem já recebe SSE
521
+ * ou acabou de marcar tudo como lido sabe da mudança antes do próximo tick, e esperar 10s para
522
+ * o contador acompanhar faz a interface parecer travada.
523
+ */
524
+ refresh: () => Promise<void>;
460
525
  }
461
- declare function useWaitingNotifications(): UseWaitingNotificationsResult;
526
+ declare function useWaitingNotifications(params?: UseWaitingNotificationsParams): UseWaitingNotificationsResult;
462
527
 
463
528
  interface ConversationsContextValue {
464
529
  api: ConversationsApi;
@@ -677,7 +742,7 @@ interface UseConversationMessagesResult {
677
742
  caption?: string;
678
743
  }) => Promise<MessagePayload>;
679
744
  sendTemplate: (data: {
680
- templateName: string;
745
+ templateName?: string;
681
746
  languageCode?: string;
682
747
  bodyParams?: string[];
683
748
  }) => Promise<void>;
@@ -688,14 +753,11 @@ declare function useConversationMessages(conversationId: string, params?: {
688
753
  before?: string;
689
754
  }): UseConversationMessagesResult;
690
755
 
691
- interface UseConversationListParams {
692
- page?: number;
693
- limit?: number;
694
- waitingHuman?: boolean;
695
- search?: string;
696
- }
756
+ type UseConversationListParams = ListConversationsParams;
697
757
  interface UseConversationListResult {
698
758
  conversations: ConversationSummary[];
759
+ /** Total no servidor. Cai para o tamanho da página quando a API devolve só o array. */
760
+ total: number;
699
761
  loading: boolean;
700
762
  error: Error | undefined;
701
763
  refetch: () => Promise<void>;
@@ -710,12 +772,11 @@ interface UseConversationContextResult {
710
772
  }
711
773
  declare function useConversationContext(conversationId: string): UseConversationContextResult;
712
774
 
713
- interface UseConversationDocumentsParams {
714
- search?: string;
715
- page?: number;
716
- }
775
+ type UseConversationDocumentsParams = ListDocumentsParams;
717
776
  interface UseConversationDocumentsResult {
718
777
  documents: ConversationDocument[];
778
+ /** Total no servidor. Cai para o tamanho da página quando a API devolve só o array. */
779
+ total: number;
719
780
  loading: boolean;
720
781
  error: Error | undefined;
721
782
  refetch: () => Promise<void>;
@@ -726,6 +787,27 @@ type ConversationRealtimeHandler = (event: MessageEvent) => void;
726
787
  declare function useConversationRealtime(conversationId: string | undefined, onEvent: ConversationRealtimeHandler): void;
727
788
  declare function useGlobalRealtime(onEvent: ConversationRealtimeHandler): void;
728
789
 
790
+ interface UseConversationActionsResult {
791
+ /** `undefined` quando a API do host não implementa a operação — a UI esconde a afordância. */
792
+ takeover: (() => Promise<void>) | undefined;
793
+ release: (() => Promise<void>) | undefined;
794
+ finalize: (() => Promise<void>) | undefined;
795
+ }
796
+ /**
797
+ * Ações de atendimento de UMA conversa, já ligadas ao id.
798
+ *
799
+ * Separado de `useConversationMessages` porque assumir e devolver conversa também acontece a
800
+ * partir da lista, onde nenhuma thread está aberta — embutir nas mensagens obrigaria a carregar
801
+ * a thread inteira só para desenhar um botão na linha.
802
+ */
803
+ declare function useConversationActions(conversationId: string): UseConversationActionsResult;
804
+ interface UseInboxActionsResult {
805
+ markAllRead: (() => Promise<void>) | undefined;
806
+ listTemplates: (() => Promise<ConversationTemplate[]>) | undefined;
807
+ }
808
+ /** Ações que valem para a caixa inteira, sem conversa selecionada. */
809
+ declare function useInboxActions(): UseInboxActionsResult;
810
+
729
811
  declare function parseWhatsAppFormatting(text: string): ReactNode[];
730
812
  declare function waToHTML(text: string): string;
731
813
  declare function htmlToWA(html: string): string;
@@ -735,6 +817,8 @@ declare function formatPhone(number: string): string;
735
817
  declare function phoneInitials(number: string): string;
736
818
 
737
819
  declare function formatTimestamp(timestamp: string): string;
820
+ declare function formatDateTime(iso: string): string;
821
+ declare function isSameDay(a: Date, b: Date): boolean;
738
822
  declare function formatFileSize(bytes: number): string;
739
823
 
740
824
  interface AsyncResourceState<T> {
@@ -744,4 +828,4 @@ interface AsyncResourceState<T> {
744
828
  refetch: () => Promise<void>;
745
829
  }
746
830
 
747
- export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, type AvatarProps, type BuildTranscriptTextParams, CHANNEL_BRAND_COLOR, CONVERSATION_WINDOW, ChannelIcon, type ChannelIconProps, ConversationChannel, type ConversationContextEntry, ConversationContextPanel, type ConversationContextPanelClassNames, type ConversationContextPanelLabels, type ConversationContextPanelProps, ConversationDocument, ConversationDocumentsPanel, type ConversationDocumentsPanelClassNames, type ConversationDocumentsPanelLabels, type ConversationDocumentsPanelProps, ConversationHeader, type ConversationHeaderClassNames, type ConversationHeaderLabels, type ConversationHeaderProps, ConversationListItem, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, ConversationRow, type ConversationRowClassNames, type ConversationRowProps, ConversationSummary, ConversationWallpaper, type ConversationWallpaperProps, type ConversationWindow, ConversationsApi, ConversationsFeatures, ConversationsProvider, DEFAULT_CONVERSATION_CONTEXT_LABELS, DEFAULT_CONVERSATION_DOCUMENTS_LABELS, DEFAULT_CONVERSATION_HEADER_LABELS, DEFAULT_TEMPLATES_SETTINGS_LABELS, DEFAULT_WINDOW_EXPIRED_LABELS, DateDivider, type DateDividerClassNames, type DateDividerProps, EmojiPicker, type EmojiPickerProps, FileIcon, type FileIconProps, Lightbox, type LightboxProps, MediaRenderer, type MediaRendererProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerClassNames, type MessageComposerProps, MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, NARROW_MAX_WIDTH_PX, type ResolveMediaUrl, SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, TEMPLATE_SETTINGS_TAB, type TemplateSettingsTab, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, WINDOW_FILTERS, WelcomeFarewellForm, type WelcomeFarewellFormLabels, type WelcomeFarewellFormProps, WhatsAppCreateTemplateForm, type WhatsAppCreateTemplateFormLabels, type WhatsAppCreateTemplateFormProps, type WhatsAppCreateTemplateResult, type WhatsAppCreateTemplateState, WhatsAppMessageEditor, type WhatsAppMessageEditorProps, type WhatsAppTemplateHeaderType, WhatsAppTemplateSettingsForm, type WhatsAppTemplateSettingsFormLabels, type WhatsAppTemplateSettingsFormProps, type WhatsAppTemplateSummary, type WhatsAppTemplateVariableSuggestion, WhatsAppTemplatesSettings, type WhatsAppTemplatesSettingsLabels, type WhatsAppTemplatesSettingsProps, WindowExpiredNotice, type WindowExpiredNoticeLabels, type WindowExpiredNoticeProps, type WindowOfParams, buildTranscriptFilename, buildTranscriptText, downloadTextFile, formatFileSize, formatPhone, formatStalledFor, formatTimestamp, htmlToWA, isWindowBlocking, parseWhatsAppFormatting, phoneInitials, toast, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useIsDarkTheme, useIsNarrow, useToast, useWaitingNotifications, waToHTML, waToHTMLInline, windowOf };
831
+ export { type AsyncResourceState, AudioPlayer, type AudioPlayerProps, Avatar, type AvatarProps, type BuildTranscriptTextParams, CHANNEL_BRAND_COLOR, CONVERSATION_WINDOW, ChannelIcon, type ChannelIconProps, ConversationChannel, type ConversationContextEntry, ConversationContextPanel, type ConversationContextPanelClassNames, type ConversationContextPanelLabels, type ConversationContextPanelProps, ConversationDocument, ConversationDocumentsPanel, type ConversationDocumentsPanelClassNames, type ConversationDocumentsPanelLabels, type ConversationDocumentsPanelProps, ConversationHeader, type ConversationHeaderClassNames, type ConversationHeaderLabels, type ConversationHeaderProps, ConversationListItem, type ConversationListItemProps, type ConversationLocales, ConversationLocalesProvider, type ConversationLocalesProviderProps, type ConversationRealtimeHandler, ConversationRow, type ConversationRowClassNames, type ConversationRowProps, ConversationSummary, ConversationTemplate, ConversationWallpaper, type ConversationWallpaperProps, type ConversationWindow, ConversationsApi, ConversationsFeatures, ConversationsProvider, DEFAULT_CONVERSATION_CONTEXT_LABELS, DEFAULT_CONVERSATION_DOCUMENTS_LABELS, DEFAULT_CONVERSATION_HEADER_LABELS, DEFAULT_TEMPLATES_SETTINGS_LABELS, DEFAULT_WINDOW_EXPIRED_LABELS, DateDivider, type DateDividerClassNames, type DateDividerProps, EmojiPicker, type EmojiPickerProps, FileIcon, type FileIconProps, Lightbox, type LightboxProps, ListConversationsParams, ListDocumentsParams, MediaRenderer, type MediaRendererProps, MessageBubble, type MessageBubbleProps, MessageComposer, type MessageComposerClassNames, type MessageComposerProps, MessagePayload, MessageTail, MessageText, type MessageTextProps, MessageTimestamp, NARROW_MAX_WIDTH_PX, type ResolveMediaUrl, SSEProvider, SimpleEmojiPicker, type SimpleEmojiPickerProps, StatusTicks, type StatusTicksProps, TEMPLATE_SETTINGS_TAB, type TemplateSettingsTab, ToastProvider, type TopicItem, TopicsForm, type TopicsFormLabels, type TopicsFormProps, type UseConversationActionsResult, type UseConversationContextResult, type UseConversationDocumentsParams, type UseConversationDocumentsResult, type UseConversationListParams, type UseConversationListResult, type UseConversationMessagesResult, type UseInboxActionsResult, type UseWaitingNotificationsLabels, type UseWaitingNotificationsParams, type UseWaitingNotificationsResult, WINDOW_FILTERS, WelcomeFarewellForm, type WelcomeFarewellFormLabels, type WelcomeFarewellFormProps, WhatsAppCreateTemplateForm, type WhatsAppCreateTemplateFormLabels, type WhatsAppCreateTemplateFormProps, type WhatsAppCreateTemplateResult, type WhatsAppCreateTemplateState, WhatsAppMessageEditor, type WhatsAppMessageEditorProps, type WhatsAppTemplateHeaderType, WhatsAppTemplateSettingsForm, type WhatsAppTemplateSettingsFormLabels, type WhatsAppTemplateSettingsFormProps, type WhatsAppTemplateSummary, type WhatsAppTemplateVariableSuggestion, WhatsAppTemplatesSettings, type WhatsAppTemplatesSettingsLabels, type WhatsAppTemplatesSettingsProps, WindowExpiredNotice, type WindowExpiredNoticeLabels, type WindowExpiredNoticeProps, type WindowOfParams, buildTranscriptFilename, buildTranscriptText, downloadTextFile, formatDateTime, formatFileSize, formatPhone, formatStalledFor, formatTimestamp, htmlToWA, isSameDay, isWindowBlocking, parseWhatsAppFormatting, phoneInitials, toast, useConversationActions, useConversationContext, useConversationDocuments, useConversationList, useConversationLocales, useConversationMessages, useConversationRealtime, useConversations, useDarkMode, useGlobalRealtime, useInboxActions, useIsDarkTheme, useIsNarrow, useToast, useWaitingNotifications, waToHTML, waToHTMLInline, windowOf };