@adatechnology/conversations-ui 0.1.0-rc.21 → 0.1.0-rc.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2586,6 +2586,854 @@ function useInboxActions() {
2586
2586
  [api]
2587
2587
  );
2588
2588
  }
2589
+
2590
+ // src/workspace/ConversationsWorkspace.tsx
2591
+ import { useEffect as useEffect10, useMemo as useMemo5, useState as useState14 } from "react";
2592
+
2593
+ // src/workspace/BulkTemplateModal.tsx
2594
+ import { useEffect as useEffect7, useMemo as useMemo3, useState as useState11 } from "react";
2595
+ import { jsx as jsx21, jsxs as jsxs19 } from "react/jsx-runtime";
2596
+ function BulkTemplateModal({
2597
+ labels,
2598
+ expiredCount,
2599
+ sending,
2600
+ onClose,
2601
+ onSend
2602
+ }) {
2603
+ const context = useConversations();
2604
+ if (!context) {
2605
+ throw new Error("BulkTemplateModal requires an ancestor <ConversationsProvider>");
2606
+ }
2607
+ const { api } = context;
2608
+ const [templates, setTemplates] = useState11([]);
2609
+ const [search, setSearch] = useState11("");
2610
+ const [selected, setSelected] = useState11("");
2611
+ useEffect7(() => {
2612
+ let active = true;
2613
+ void api.listTemplates?.().then((loaded) => {
2614
+ if (active) setTemplates(loaded);
2615
+ }).catch(() => {
2616
+ });
2617
+ return () => {
2618
+ active = false;
2619
+ };
2620
+ }, [api]);
2621
+ const filtered = useMemo3(() => {
2622
+ const term = search.trim().toLowerCase();
2623
+ if (!term) return templates;
2624
+ return templates.filter((template) => template.name.toLowerCase().includes(term));
2625
+ }, [templates, search]);
2626
+ return /* @__PURE__ */ jsx21("div", { className: "cv-workspace-modal", role: "dialog", "aria-modal": "true", "aria-label": labels.templateModalTitle, children: /* @__PURE__ */ jsxs19("div", { className: "cv-workspace-modal__box", children: [
2627
+ /* @__PURE__ */ jsxs19("header", { className: "cv-workspace-modal__header", children: [
2628
+ /* @__PURE__ */ jsxs19("div", { children: [
2629
+ /* @__PURE__ */ jsx21("h3", { children: labels.templateModalTitle }),
2630
+ /* @__PURE__ */ jsx21("p", { children: labels.templateModalAvailable(templates.length) })
2631
+ ] }),
2632
+ /* @__PURE__ */ jsx21("button", { type: "button", onClick: onClose, "aria-label": labels.templateModalCancel, children: "\u2715" })
2633
+ ] }),
2634
+ /* @__PURE__ */ jsx21("div", { className: "cv-workspace-modal__search", children: /* @__PURE__ */ jsx21(
2635
+ "input",
2636
+ {
2637
+ type: "search",
2638
+ value: search,
2639
+ onChange: (event) => setSearch(event.target.value),
2640
+ placeholder: labels.templateModalSearch,
2641
+ autoFocus: true
2642
+ }
2643
+ ) }),
2644
+ /* @__PURE__ */ jsxs19("div", { className: "cv-workspace-modal__list", children: [
2645
+ filtered.length === 0 ? /* @__PURE__ */ jsx21("p", { className: "cv-workspace-empty", children: labels.templateModalEmpty }) : null,
2646
+ filtered.map((template) => /* @__PURE__ */ jsxs19(
2647
+ "button",
2648
+ {
2649
+ type: "button",
2650
+ onClick: () => setSelected(template.name === selected ? "" : template.name),
2651
+ "aria-pressed": template.name === selected,
2652
+ className: `cv-workspace-modal__item${template.name === selected ? " cv-workspace-modal__item--on" : ""}`,
2653
+ children: [
2654
+ /* @__PURE__ */ jsx21("span", { className: "cv-workspace-modal__item-name", children: template.name }),
2655
+ /* @__PURE__ */ jsxs19("span", { className: "cv-workspace-modal__item-meta", children: [
2656
+ template.language,
2657
+ template.category ? ` \xB7 ${template.category.toLowerCase()}` : ""
2658
+ ] })
2659
+ ]
2660
+ },
2661
+ template.name
2662
+ ))
2663
+ ] }),
2664
+ expiredCount === 0 ? /* @__PURE__ */ jsx21("p", { className: "cv-workspace-modal__warning", children: labels.templateModalNoneExpired }) : null,
2665
+ /* @__PURE__ */ jsxs19("footer", { className: "cv-workspace-modal__footer", children: [
2666
+ /* @__PURE__ */ jsx21("button", { type: "button", onClick: onClose, children: labels.templateModalCancel }),
2667
+ /* @__PURE__ */ jsx21(
2668
+ "button",
2669
+ {
2670
+ type: "button",
2671
+ disabled: sending || expiredCount === 0,
2672
+ onClick: () => onSend(selected || void 0),
2673
+ children: sending ? labels.templateModalSending : labels.templateModalSend(expiredCount)
2674
+ }
2675
+ )
2676
+ ] })
2677
+ ] }) });
2678
+ }
2679
+
2680
+ // src/workspace/ConversationPane.tsx
2681
+ import { useEffect as useEffect8, useState as useState12 } from "react";
2682
+ import { jsx as jsx22, jsxs as jsxs20 } from "react/jsx-runtime";
2683
+ function ConversationPane({
2684
+ conversation,
2685
+ now,
2686
+ busy,
2687
+ labels,
2688
+ onTakeover,
2689
+ onReturnToBot,
2690
+ onFinish,
2691
+ onBack,
2692
+ extraUtilities,
2693
+ contextEntriesOf,
2694
+ quickReplies,
2695
+ quickReplyVariablesFor,
2696
+ flowLabel,
2697
+ onDownload,
2698
+ requireTakeoverToReply,
2699
+ messageSelection,
2700
+ initialComposerText,
2701
+ renderAboveTranscript,
2702
+ onAttach
2703
+ }) {
2704
+ const context = useConversations();
2705
+ if (!context) {
2706
+ throw new Error("ConversationPane requires an ancestor <ConversationsProvider>");
2707
+ }
2708
+ const { api } = context;
2709
+ const { messages, refetch } = useConversationMessages(conversation.id);
2710
+ const { context: conversationContext } = useConversationContext(conversation.id);
2711
+ const [documentsOpen, setDocumentsOpen] = useState12(false);
2712
+ const [sendFailure, setSendFailure] = useState12(void 0);
2713
+ const [selectedMessageIds, setSelectedMessageIds] = useState12(/* @__PURE__ */ new Set());
2714
+ const [draft, setDraft] = useState12(initialComposerText ?? "");
2715
+ useEffect8(() => {
2716
+ setSelectedMessageIds(/* @__PURE__ */ new Set());
2717
+ setDraft(initialComposerText ?? "");
2718
+ }, [conversation.id, initialComposerText]);
2719
+ function toggleMessageSelected(messageId) {
2720
+ setSelectedMessageIds((current) => {
2721
+ const next = new Set(current);
2722
+ if (next.has(messageId)) next.delete(messageId);
2723
+ else next.add(messageId);
2724
+ return next;
2725
+ });
2726
+ }
2727
+ function copySelectedMessages() {
2728
+ const transcript = messages.filter((message) => selectedMessageIds.has(message.id)).map(
2729
+ (message) => `[${new Date(message.timestamp).toLocaleString()}] ${message.sender}: ${message.content ?? `(${message.type})`}`
2730
+ ).join("\n");
2731
+ void navigator.clipboard.writeText(transcript);
2732
+ setSelectedMessageIds(/* @__PURE__ */ new Set());
2733
+ }
2734
+ const scroll = useScrollToLatestMessage({ conversationId: conversation.id, messageCount: messages.length });
2735
+ useConversationRealtime(conversation.id, () => {
2736
+ void refetch();
2737
+ });
2738
+ const blocked = isWindowBlocking(
2739
+ windowOf({ lastInboundAt: conversation.lastInboundAt, now, channel: conversation.channel })
2740
+ );
2741
+ async function runSend(action, fallback) {
2742
+ setSendFailure(void 0);
2743
+ try {
2744
+ await action();
2745
+ await refetch();
2746
+ } catch (error) {
2747
+ setSendFailure(error instanceof Error ? error.message : fallback);
2748
+ }
2749
+ }
2750
+ async function handleSend(text) {
2751
+ await runSend(() => api.sendMessage(conversation.id, text), labels.sendFailure);
2752
+ }
2753
+ async function handleAttach(file) {
2754
+ if (!onAttach) return;
2755
+ await runSend(() => onAttach(file), labels.attachFailure);
2756
+ }
2757
+ function handleDownload() {
2758
+ downloadTextFile(
2759
+ buildTranscriptFilename(conversation.whatsappNumber, /* @__PURE__ */ new Date()),
2760
+ buildTranscriptText({
2761
+ messages,
2762
+ whatsappNumber: conversation.whatsappNumber,
2763
+ clientName: conversation.clientName
2764
+ })
2765
+ );
2766
+ }
2767
+ const contextEntries = contextEntriesOf?.(conversationContext);
2768
+ const botOwnsConversation = Boolean(requireTakeoverToReply) && conversation.mode !== "human";
2769
+ return /* @__PURE__ */ jsxs20("div", { className: "cv-workspace-pane", children: [
2770
+ /* @__PURE__ */ jsx22(
2771
+ ConversationHeader,
2772
+ {
2773
+ conversation,
2774
+ busy,
2775
+ ...onTakeover ? { onTakeover } : {},
2776
+ ...onReturnToBot ? { onReturnToBot } : {},
2777
+ ...onFinish ? { onFinish } : {},
2778
+ onDownload: onDownload ?? handleDownload,
2779
+ onBack,
2780
+ onOpenDocuments: () => setDocumentsOpen(!documentsOpen),
2781
+ documentsOpen,
2782
+ ...extraUtilities ? { extraUtilities } : {}
2783
+ }
2784
+ ),
2785
+ contextEntries && contextEntries.length > 0 || flowLabel ? /* @__PURE__ */ jsx22(ConversationContextPanel, { entries: contextEntries ?? [], ...flowLabel ? { flowLabel } : {} }) : null,
2786
+ /* @__PURE__ */ jsx22(ConversationDocumentsPanel, { conversationId: conversation.id, open: documentsOpen }),
2787
+ renderAboveTranscript?.(conversation),
2788
+ /* @__PURE__ */ jsx22(
2789
+ ConversationWallpaper,
2790
+ {
2791
+ ref: scroll.containerRef,
2792
+ onScroll: scroll.handleScroll,
2793
+ className: "cv-workspace-transcript",
2794
+ children: messages.map((message, index) => {
2795
+ const previous = index > 0 ? messages[index - 1] : void 0;
2796
+ const startsNewDay = !previous || new Date(message.timestamp).toDateString() !== new Date(previous.timestamp).toDateString();
2797
+ return /* @__PURE__ */ jsxs20("div", { children: [
2798
+ startsNewDay ? /* @__PURE__ */ jsx22(DateDivider, { iso: message.timestamp }) : null,
2799
+ /* @__PURE__ */ jsx22(
2800
+ MessageBubble,
2801
+ {
2802
+ message,
2803
+ isMine: message.direction === "outbound",
2804
+ isFirstInGroup: !previous || previous.sender !== message.sender,
2805
+ ...messageSelection ? {
2806
+ isSelecting: selectedMessageIds.size > 0,
2807
+ isSelected: selectedMessageIds.has(message.id),
2808
+ onToggleSelect: () => toggleMessageSelected(message.id)
2809
+ } : {}
2810
+ }
2811
+ )
2812
+ ] }, message.id);
2813
+ })
2814
+ }
2815
+ ),
2816
+ messageSelection && selectedMessageIds.size > 0 ? /* @__PURE__ */ jsxs20("div", { className: "cv-workspace-selection", children: [
2817
+ /* @__PURE__ */ jsx22("span", { children: labels.messagesSelected(selectedMessageIds.size) }),
2818
+ /* @__PURE__ */ jsxs20("div", { className: "cv-workspace-selection__actions", children: [
2819
+ /* @__PURE__ */ jsx22("button", { type: "button", onClick: () => setSelectedMessageIds(/* @__PURE__ */ new Set()), children: labels.bulkClear }),
2820
+ /* @__PURE__ */ jsx22("button", { type: "button", onClick: copySelectedMessages, children: labels.copySelected })
2821
+ ] })
2822
+ ] }) : null,
2823
+ sendFailure ? /* @__PURE__ */ jsx22("p", { role: "alert", className: "cv-workspace-alert", children: sendFailure }) : null,
2824
+ blocked ? /* @__PURE__ */ jsx22(
2825
+ WindowExpiredNotice,
2826
+ {
2827
+ disabled: busy,
2828
+ onSendTemplate: () => void api.sendTemplate(conversation.id, {}).then(() => refetch())
2829
+ }
2830
+ ) : botOwnsConversation ? (
2831
+ // Responder com a conversa no bot atropelaria o fluxo automático no meio de uma pergunta.
2832
+ /* @__PURE__ */ jsx22("p", { className: "cv-workspace-notice", children: labels.takeoverToReply })
2833
+ ) : /* @__PURE__ */ jsx22(
2834
+ MessageComposer,
2835
+ {
2836
+ value: draft,
2837
+ onChange: setDraft,
2838
+ onSend: (text) => void handleSend(text),
2839
+ ...onAttach ? { onAttach: (file) => void handleAttach(file) } : {},
2840
+ placeholder: labels.composerPlaceholder,
2841
+ ...quickReplies ? { quickReplies } : {},
2842
+ ...quickReplyVariablesFor ? { quickReplyVariables: quickReplyVariablesFor(conversation, conversationContext) } : {}
2843
+ }
2844
+ )
2845
+ ] });
2846
+ }
2847
+
2848
+ // src/workspace/ConversationsInboxList.tsx
2849
+ import { jsx as jsx23, jsxs as jsxs21 } from "react/jsx-runtime";
2850
+ function ConversationsInboxList({
2851
+ inbox,
2852
+ labels,
2853
+ className,
2854
+ renderFilters,
2855
+ renderBulkActions,
2856
+ renderRow,
2857
+ onSendTemplateToSelected
2858
+ }) {
2859
+ const hasSelection = inbox.selectedIds.size > 0;
2860
+ return /* @__PURE__ */ jsxs21("aside", { className, children: [
2861
+ hasSelection ? /* @__PURE__ */ jsxs21("div", { className: "cv-workspace-bulk", children: [
2862
+ /* @__PURE__ */ jsx23("span", { className: "cv-workspace-bulk__count", children: labels.bulkSelected(inbox.selectedIds.size) }),
2863
+ /* @__PURE__ */ jsxs21("div", { className: "cv-workspace-bulk__actions", children: [
2864
+ /* @__PURE__ */ jsx23("button", { type: "button", onClick: inbox.clearBulkSelection, children: labels.bulkClear }),
2865
+ onSendTemplateToSelected ? /* @__PURE__ */ jsx23("button", { type: "button", onClick: onSendTemplateToSelected, disabled: inbox.busy, children: labels.bulkTemplate }) : null,
2866
+ inbox.canFinalize ? /* @__PURE__ */ jsx23(
2867
+ "button",
2868
+ {
2869
+ type: "button",
2870
+ disabled: inbox.busy,
2871
+ onClick: () => {
2872
+ if (window.confirm(labels.bulkFinalizeConfirm(inbox.selectedIds.size))) {
2873
+ void inbox.finalizeSelected();
2874
+ }
2875
+ },
2876
+ children: labels.bulkFinalize
2877
+ }
2878
+ ) : null,
2879
+ renderBulkActions?.(inbox)
2880
+ ] })
2881
+ ] }) : null,
2882
+ /* @__PURE__ */ jsxs21("div", { className: "cv-workspace-filters", children: [
2883
+ /* @__PURE__ */ jsx23(
2884
+ "input",
2885
+ {
2886
+ type: "search",
2887
+ value: inbox.search,
2888
+ onChange: (event) => inbox.setSearch(event.target.value),
2889
+ placeholder: labels.search,
2890
+ className: "cv-workspace-search"
2891
+ }
2892
+ ),
2893
+ /* @__PURE__ */ jsxs21("div", { className: "cv-workspace-chips", children: [
2894
+ /* @__PURE__ */ jsx23("span", { className: "cv-workspace-chips__legend", children: labels.windowLegend }),
2895
+ WINDOW_FILTERS.map((filter) => /* @__PURE__ */ jsxs21(
2896
+ "button",
2897
+ {
2898
+ type: "button",
2899
+ onClick: () => inbox.setWindowFilter(filter.value),
2900
+ "aria-pressed": inbox.windowFilter === filter.value,
2901
+ className: `cv-workspace-chip${inbox.windowFilter === filter.value ? " cv-workspace-chip--on" : ""}`,
2902
+ children: [
2903
+ filter.dotClass ? /* @__PURE__ */ jsx23("span", { className: `cv-workspace-dot ${filter.dotClass}` }) : null,
2904
+ filter.label
2905
+ ]
2906
+ },
2907
+ filter.value
2908
+ ))
2909
+ ] }),
2910
+ inbox.channelFilters.length > 0 ? /* @__PURE__ */ jsxs21("div", { className: "cv-workspace-chips", children: [
2911
+ /* @__PURE__ */ jsx23("span", { className: "cv-workspace-chips__legend", children: labels.channelLegend }),
2912
+ inbox.channelFilters.map((filter) => /* @__PURE__ */ jsxs21(
2913
+ "button",
2914
+ {
2915
+ type: "button",
2916
+ onClick: () => inbox.setChannelFilter(filter.value),
2917
+ "aria-pressed": inbox.channelFilter === filter.value,
2918
+ className: `cv-workspace-chip${inbox.channelFilter === filter.value ? " cv-workspace-chip--on" : ""}`,
2919
+ children: [
2920
+ filter.value === CHANNEL_FILTER_ALL ? null : /* @__PURE__ */ jsx23(ChannelIcon, { channel: filter.value }),
2921
+ filter.label
2922
+ ]
2923
+ },
2924
+ filter.value
2925
+ ))
2926
+ ] }) : null,
2927
+ renderFilters?.(inbox),
2928
+ /* @__PURE__ */ jsxs21("label", { className: "cv-workspace-selectall", children: [
2929
+ /* @__PURE__ */ jsx23("input", { type: "checkbox", checked: inbox.allOnPageSelected, onChange: inbox.toggleSelectAllOnPage }),
2930
+ labels.selectAll
2931
+ ] })
2932
+ ] }),
2933
+ /* @__PURE__ */ jsxs21("div", { className: "cv-workspace-rows", children: [
2934
+ inbox.pageConversations.map(
2935
+ (conversation) => renderRow ? /* @__PURE__ */ jsx23("div", { children: renderRow(conversation) }, conversation.id) : /* @__PURE__ */ jsx23(
2936
+ ConversationRow,
2937
+ {
2938
+ conversation,
2939
+ active: conversation.id === inbox.selectedId,
2940
+ selected: inbox.selectedIds.has(conversation.id),
2941
+ now: inbox.now,
2942
+ busy: inbox.busy,
2943
+ onOpen: () => inbox.selectConversation(conversation.id),
2944
+ onToggleSelected: () => inbox.toggleSelected(conversation.id),
2945
+ onTakeover: () => void inbox.takeover(conversation.id)
2946
+ },
2947
+ conversation.id
2948
+ )
2949
+ ),
2950
+ !inbox.loading && inbox.filteredCount === 0 ? /* @__PURE__ */ jsx23("p", { className: "cv-workspace-empty", children: labels.emptyList }) : null
2951
+ ] }),
2952
+ /* @__PURE__ */ jsxs21("div", { className: "cv-workspace-pager", children: [
2953
+ /* @__PURE__ */ jsx23("span", { children: labels.rangeOf(inbox.firstOnPage, inbox.lastOnPage, inbox.filteredCount) }),
2954
+ /* @__PURE__ */ jsxs21("div", { className: "cv-workspace-pager__buttons", children: [
2955
+ /* @__PURE__ */ jsx23("button", { type: "button", onClick: () => inbox.goToPage(1), disabled: inbox.page === 1, "aria-label": "Primeira p\xE1gina", children: "\xAB" }),
2956
+ /* @__PURE__ */ jsx23(
2957
+ "button",
2958
+ {
2959
+ type: "button",
2960
+ onClick: () => inbox.goToPage(inbox.page - 1),
2961
+ disabled: inbox.page === 1,
2962
+ "aria-label": "P\xE1gina anterior",
2963
+ children: "\u2039"
2964
+ }
2965
+ ),
2966
+ /* @__PURE__ */ jsx23("span", { children: labels.pageOf(inbox.page, inbox.pageCount) }),
2967
+ /* @__PURE__ */ jsx23(
2968
+ "button",
2969
+ {
2970
+ type: "button",
2971
+ onClick: () => inbox.goToPage(inbox.page + 1),
2972
+ disabled: inbox.page === inbox.pageCount,
2973
+ "aria-label": "Pr\xF3xima p\xE1gina",
2974
+ children: "\u203A"
2975
+ }
2976
+ ),
2977
+ /* @__PURE__ */ jsx23(
2978
+ "button",
2979
+ {
2980
+ type: "button",
2981
+ onClick: () => inbox.goToPage(inbox.pageCount),
2982
+ disabled: inbox.page === inbox.pageCount,
2983
+ "aria-label": "\xDAltima p\xE1gina",
2984
+ children: "\xBB"
2985
+ }
2986
+ )
2987
+ ] })
2988
+ ] })
2989
+ ] });
2990
+ }
2991
+
2992
+ // src/workspace/labels.ts
2993
+ var DEFAULT_CONVERSATIONS_WORKSPACE_LABELS = {
2994
+ title: "Conversas",
2995
+ conversations: "conversas",
2996
+ waiting: "aguardando",
2997
+ unread: "n\xE3o lidas",
2998
+ waitingOnly: "Aguardando atendimento",
2999
+ markSelectedAsRead: "Marcar selecionadas como lidas",
3000
+ search: "Buscar conversa...",
3001
+ windowLegend: "Janela:",
3002
+ channelLegend: "Canal:",
3003
+ selectAll: "Selecionar todas",
3004
+ emptyList: "Nenhuma conversa encontrada.",
3005
+ emptyDetail: "Selecione uma conversa.",
3006
+ bulkSelected: (count) => `${count} selecionada${count === 1 ? "" : "s"}`,
3007
+ bulkClear: "Limpar sele\xE7\xE3o",
3008
+ bulkFinalize: "Finalizar",
3009
+ bulkFinalizeConfirm: (count) => `Finalizar ${count} conversa${count === 1 ? "" : "s"}?`,
3010
+ bulkTemplate: "Enviar template",
3011
+ markAllAsRead: "Marcar todas como lidas",
3012
+ templateModalTitle: "Escolher template",
3013
+ templateModalSearch: "Buscar template...",
3014
+ templateModalEmpty: "Nenhum template encontrado.",
3015
+ templateModalCancel: "Cancelar",
3016
+ templateModalSending: "Enviando\u2026",
3017
+ templateModalNoneExpired: "Nenhuma das conversas selecionadas est\xE1 fora da janela de 24h.",
3018
+ templateModalAvailable: (count) => `${count} template${count === 1 ? "" : "s"} dispon\xEDve${count === 1 ? "l" : "is"}`,
3019
+ templateModalSend: (count) => `Enviar para ${count}`,
3020
+ messagesSelected: (count) => `${count} mensagem${count === 1 ? "" : "s"} selecionada${count === 1 ? "" : "s"}`,
3021
+ copySelected: "Copiar",
3022
+ composerPlaceholder: "Responder como atendente\u2026",
3023
+ attachFailure: "Falha ao enviar o arquivo.",
3024
+ sendFailure: "Falha ao enviar a mensagem.",
3025
+ takeoverToReply: "Assuma o atendimento para responder diretamente ao cliente.",
3026
+ signIn: "Entrar no painel",
3027
+ pageOf: (page, pageCount) => `${page} / ${pageCount}`,
3028
+ rangeOf: (first, last, total) => total === 0 ? `0 de 0` : `${first}\u2013${last} de ${total}`
3029
+ };
3030
+
3031
+ // src/workspace/useConversationsInbox.ts
3032
+ import { useCallback as useCallback7, useEffect as useEffect9, useMemo as useMemo4, useRef as useRef7, useState as useState13 } from "react";
3033
+ var CONVERSATIONS_PER_PAGE = 50;
3034
+ function defaultDescribeFailure(error) {
3035
+ const status = error.status;
3036
+ if (status === 401 || status === 403) {
3037
+ return "Sess\xE3o expirada nesta aba \u2014 entre no painel de novo para ver as conversas.";
3038
+ }
3039
+ return error instanceof Error && error.message ? `N\xE3o foi poss\xEDvel carregar as conversas: ${error.message}` : "N\xE3o foi poss\xEDvel carregar as conversas.";
3040
+ }
3041
+ function useConversationsInbox(params = {}) {
3042
+ const context = useConversations();
3043
+ if (!context) {
3044
+ throw new Error("useConversationsInbox requires an ancestor <ConversationsProvider>");
3045
+ }
3046
+ const { api } = context;
3047
+ const perPage = params.perPage ?? CONVERSATIONS_PER_PAGE;
3048
+ const markReadOnOpen = params.markReadOnOpen ?? true;
3049
+ const describeFailure = params.describeFailure ?? defaultDescribeFailure;
3050
+ const [waitingOnly, setWaitingOnly] = useState13(false);
3051
+ const [windowFilter, setWindowFilter] = useState13(CONVERSATION_WINDOW.ALL);
3052
+ const [channelFilter, setChannelFilter] = useState13(CHANNEL_FILTER_ALL);
3053
+ const [search, setSearch] = useState13("");
3054
+ const [page, setPage] = useState13(1);
3055
+ const [selectedId, setSelectedId] = useState13(void 0);
3056
+ const [selectedIds, setSelectedIds] = useState13(/* @__PURE__ */ new Set());
3057
+ const [busy, setBusy] = useState13(false);
3058
+ const { conversations, total, loading, error, refetch } = useConversationList({
3059
+ ...waitingOnly ? { waitingHuman: true } : {},
3060
+ ...search ? { search } : {},
3061
+ ...params.filters ? { filters: params.filters } : {},
3062
+ ...params.serverPaginated ? { page, limit: perPage } : {}
3063
+ });
3064
+ useGlobalRealtime(
3065
+ useCallback7(() => {
3066
+ void refetch();
3067
+ }, [refetch])
3068
+ );
3069
+ const now = useMemo4(() => Date.now(), [conversations]);
3070
+ const filtered = useMemo4(
3071
+ () => conversations.filter(
3072
+ (conversation) => channelFilter === CHANNEL_FILTER_ALL || (conversation.channel ?? DEFAULT_CONVERSATION_CHANNEL) === channelFilter
3073
+ ).filter(
3074
+ (conversation) => windowFilter === CONVERSATION_WINDOW.ALL || windowOf({ lastInboundAt: conversation.lastInboundAt, now, channel: conversation.channel }) === windowFilter
3075
+ ),
3076
+ [conversations, windowFilter, channelFilter, now]
3077
+ );
3078
+ const totalForPaging = params.serverPaginated ? total : filtered.length;
3079
+ const pageCount = Math.max(1, Math.ceil(totalForPaging / perPage));
3080
+ const currentPage = Math.min(page, pageCount);
3081
+ const pageConversations = params.serverPaginated ? filtered : filtered.slice((currentPage - 1) * perPage, currentPage * perPage);
3082
+ useEffect9(() => {
3083
+ setPage(1);
3084
+ }, [search, waitingOnly, windowFilter, channelFilter]);
3085
+ useEffect9(() => {
3086
+ if (selectedId && !conversations.some((conversation) => conversation.id === selectedId)) {
3087
+ setSelectedId(void 0);
3088
+ }
3089
+ }, [conversations, selectedId]);
3090
+ const lastMarkReadAttempt = useRef7(void 0);
3091
+ useEffect9(() => {
3092
+ if (!markReadOnOpen || !selectedId) return;
3093
+ const opened = conversations.find((conversation) => conversation.id === selectedId);
3094
+ if (!opened || opened.unread === 0) return;
3095
+ const attempt = `${selectedId}:${opened.unread}`;
3096
+ if (lastMarkReadAttempt.current === attempt) return;
3097
+ lastMarkReadAttempt.current = attempt;
3098
+ void api.markRead(selectedId).then(() => refetch()).catch(() => {
3099
+ });
3100
+ }, [markReadOnOpen, selectedId, conversations, refetch, api]);
3101
+ const toggleSelected = useCallback7((conversationId) => {
3102
+ setSelectedIds((current) => {
3103
+ const next = new Set(current);
3104
+ if (next.has(conversationId)) next.delete(conversationId);
3105
+ else next.add(conversationId);
3106
+ return next;
3107
+ });
3108
+ }, []);
3109
+ const pageIds = pageConversations.map((conversation) => conversation.id);
3110
+ const allOnPageSelected = pageIds.length > 0 && pageIds.every((id) => selectedIds.has(id));
3111
+ const toggleSelectAllOnPage = useCallback7(() => {
3112
+ setSelectedIds((current) => {
3113
+ const next = new Set(current);
3114
+ if (allOnPageSelected) pageIds.forEach((id) => next.delete(id));
3115
+ else pageIds.forEach((id) => next.add(id));
3116
+ return next;
3117
+ });
3118
+ }, [allOnPageSelected, pageIds]);
3119
+ const runOnSelection = useCallback7(
3120
+ async (action) => {
3121
+ setBusy(true);
3122
+ try {
3123
+ await Promise.all([...selectedIds].map(action));
3124
+ setSelectedIds(/* @__PURE__ */ new Set());
3125
+ await refetch();
3126
+ } finally {
3127
+ setBusy(false);
3128
+ }
3129
+ },
3130
+ [selectedIds, refetch]
3131
+ );
3132
+ const runConversationAction = useCallback7(
3133
+ async (action, conversationId) => {
3134
+ if (!action) return;
3135
+ setBusy(true);
3136
+ try {
3137
+ await action(conversationId);
3138
+ await refetch();
3139
+ } finally {
3140
+ setBusy(false);
3141
+ }
3142
+ },
3143
+ [refetch]
3144
+ );
3145
+ return {
3146
+ conversations,
3147
+ pageConversations,
3148
+ selectedConversation: conversations.find((conversation) => conversation.id === selectedId),
3149
+ loading,
3150
+ now,
3151
+ totalCount: conversations.length,
3152
+ unreadCount: conversations.reduce((total2, conversation) => total2 + conversation.unread, 0),
3153
+ waitingCount: conversations.filter((conversation) => conversation.waitingHuman).length,
3154
+ filteredCount: totalForPaging,
3155
+ expiredSelectedCount: conversations.filter(
3156
+ (conversation) => selectedIds.has(conversation.id) && isWindowBlocking(windowOf({ lastInboundAt: conversation.lastInboundAt, now, channel: conversation.channel }))
3157
+ ).length,
3158
+ page: currentPage,
3159
+ pageCount,
3160
+ firstOnPage: (currentPage - 1) * perPage + 1,
3161
+ lastOnPage: Math.min(currentPage * perPage, totalForPaging),
3162
+ selectedId,
3163
+ selectedIds,
3164
+ allOnPageSelected,
3165
+ waitingOnly,
3166
+ windowFilter,
3167
+ channelFilter,
3168
+ channelFilters: channelFiltersFor(conversations),
3169
+ search,
3170
+ busy,
3171
+ loadFailure: error ? describeFailure(error) : void 0,
3172
+ canTakeover: Boolean(api.takeover),
3173
+ canFinalize: Boolean(api.finalize),
3174
+ canListTemplates: Boolean(api.listTemplates),
3175
+ canMarkAllRead: Boolean(api.markAllRead),
3176
+ refetch,
3177
+ selectConversation: setSelectedId,
3178
+ clearSelection: () => setSelectedId(void 0),
3179
+ toggleSelected,
3180
+ toggleSelectAllOnPage,
3181
+ clearBulkSelection: () => setSelectedIds(/* @__PURE__ */ new Set()),
3182
+ setWaitingOnly,
3183
+ setWindowFilter,
3184
+ setChannelFilter,
3185
+ setSearch,
3186
+ goToPage: setPage,
3187
+ markSelectedAsRead: () => runOnSelection((conversationId) => api.markRead(conversationId)),
3188
+ markAllAsRead: async () => {
3189
+ if (!api.markAllRead) return;
3190
+ setBusy(true);
3191
+ try {
3192
+ await api.markAllRead();
3193
+ await refetch();
3194
+ } finally {
3195
+ setBusy(false);
3196
+ }
3197
+ },
3198
+ takeover: (conversationId) => runConversationAction(api.takeover, conversationId),
3199
+ releaseToBot: (conversationId) => runConversationAction(api.release, conversationId),
3200
+ finalize: (conversationId) => runConversationAction(api.finalize, conversationId),
3201
+ finalizeSelected: () => runOnSelection(async (conversationId) => {
3202
+ await api.finalize?.(conversationId);
3203
+ }),
3204
+ sendTemplateToSelected: (templateName) => runOnSelection(async (conversationId) => {
3205
+ await api.sendTemplate(conversationId, templateName ? { templateName } : {});
3206
+ })
3207
+ };
3208
+ }
3209
+
3210
+ // src/workspace/ConversationsWorkspace.tsx
3211
+ import { Fragment as Fragment4, jsx as jsx24, jsxs as jsxs22 } from "react/jsx-runtime";
3212
+ function ConversationsWorkspace({
3213
+ labels: labelsOverride,
3214
+ filters,
3215
+ perPage,
3216
+ markReadOnOpen,
3217
+ serverPaginated,
3218
+ initialConversationId,
3219
+ initialWhatsappNumber,
3220
+ simulator,
3221
+ quickReplies,
3222
+ quickReplyVariablesFor,
3223
+ flowLabelOf,
3224
+ onDownload,
3225
+ requireTakeoverToReply,
3226
+ messageSelection,
3227
+ initialComposerText,
3228
+ contextEntriesOf,
3229
+ onAttach,
3230
+ extraUtilitiesFor,
3231
+ renderFilters,
3232
+ renderBulkActions,
3233
+ renderRow,
3234
+ renderAboveTranscript,
3235
+ renderHeaderActions,
3236
+ onSendTemplateToSelected,
3237
+ signInHref,
3238
+ className
3239
+ }) {
3240
+ const labels = { ...DEFAULT_CONVERSATIONS_WORKSPACE_LABELS, ...labelsOverride };
3241
+ const inbox = useConversationsInbox({
3242
+ ...filters ? { filters } : {},
3243
+ ...perPage ? { perPage } : {},
3244
+ ...markReadOnOpen === void 0 ? {} : { markReadOnOpen },
3245
+ ...serverPaginated ? { serverPaginated } : {}
3246
+ });
3247
+ const [simulatorOpen, setSimulatorOpen] = useState14(false);
3248
+ const [templateModalOpen, setTemplateModalOpen] = useState14(false);
3249
+ const [openedFromLink, setOpenedFromLink] = useState14(void 0);
3250
+ useEffect10(() => {
3251
+ const link = initialConversationId ?? initialWhatsappNumber;
3252
+ if (!link || openedFromLink === link) return;
3253
+ const target = inbox.conversations.find(
3254
+ (conversation) => conversation.id === initialConversationId || conversation.whatsappNumber === initialWhatsappNumber
3255
+ );
3256
+ if (!target) return;
3257
+ inbox.selectConversation(target.id);
3258
+ setOpenedFromLink(link);
3259
+ }, [initialConversationId, initialWhatsappNumber, openedFromLink, inbox]);
3260
+ const selected = inbox.selectedConversation;
3261
+ const simulatorEnabled = Boolean(simulator && (simulator.enabled ?? true));
3262
+ const showSimulator = simulatorEnabled && simulatorOpen && Boolean(selected);
3263
+ const paneUtilities = useMemo5(() => {
3264
+ if (!selected) return void 0;
3265
+ const fromProduct = extraUtilitiesFor?.(selected) ?? [];
3266
+ if (!simulatorEnabled) return fromProduct;
3267
+ return [
3268
+ ...fromProduct,
3269
+ {
3270
+ key: "simulator",
3271
+ icon: simulator?.icon ?? "\u{1F9EA}",
3272
+ label: simulator?.label ?? "Simular cliente",
3273
+ active: simulatorOpen,
3274
+ run: () => setSimulatorOpen((open) => !open)
3275
+ }
3276
+ ];
3277
+ }, [selected, extraUtilitiesFor, simulatorEnabled, simulator, simulatorOpen]);
3278
+ return /* @__PURE__ */ jsxs22("div", { className: `cv-workspace${className ? ` ${className}` : ""}`, children: [
3279
+ /* @__PURE__ */ jsxs22("header", { className: "cv-workspace-header", children: [
3280
+ /* @__PURE__ */ jsxs22("div", { className: "cv-workspace-header__titles", children: [
3281
+ /* @__PURE__ */ jsx24("h1", { children: labels.title }),
3282
+ /* @__PURE__ */ jsxs22("p", { children: [
3283
+ /* @__PURE__ */ jsxs22("span", { title: labels.conversations, children: [
3284
+ "\u{1F4AC} ",
3285
+ inbox.totalCount,
3286
+ /* @__PURE__ */ jsxs22("span", { className: "cv-only-wide", children: [
3287
+ " ",
3288
+ labels.conversations
3289
+ ] })
3290
+ ] }),
3291
+ /* @__PURE__ */ jsxs22("span", { title: labels.waiting, className: "cv-workspace-header__waiting", children: [
3292
+ "\u23F3 ",
3293
+ inbox.waitingCount,
3294
+ /* @__PURE__ */ jsxs22("span", { className: "cv-only-wide", children: [
3295
+ " ",
3296
+ labels.waiting
3297
+ ] })
3298
+ ] }),
3299
+ /* @__PURE__ */ jsxs22("span", { title: labels.unread, children: [
3300
+ "\u2709\uFE0F ",
3301
+ inbox.unreadCount,
3302
+ /* @__PURE__ */ jsxs22("span", { className: "cv-only-wide", children: [
3303
+ " ",
3304
+ labels.unread
3305
+ ] })
3306
+ ] })
3307
+ ] })
3308
+ ] }),
3309
+ /* @__PURE__ */ jsxs22("div", { className: "cv-workspace-header__actions", children: [
3310
+ /* @__PURE__ */ jsxs22(
3311
+ "button",
3312
+ {
3313
+ type: "button",
3314
+ onClick: () => inbox.setWaitingOnly(!inbox.waitingOnly),
3315
+ "aria-pressed": inbox.waitingOnly,
3316
+ title: labels.waitingOnly,
3317
+ className: inbox.waitingOnly ? "cv-workspace-toggle cv-workspace-toggle--on" : "cv-workspace-toggle",
3318
+ children: [
3319
+ "\u23F3",
3320
+ /* @__PURE__ */ jsxs22("span", { className: "cv-only-wide", children: [
3321
+ " ",
3322
+ labels.waitingOnly
3323
+ ] })
3324
+ ]
3325
+ }
3326
+ ),
3327
+ /* @__PURE__ */ jsxs22(
3328
+ "button",
3329
+ {
3330
+ type: "button",
3331
+ onClick: () => void inbox.markSelectedAsRead(),
3332
+ disabled: inbox.selectedIds.size === 0 || inbox.busy,
3333
+ title: labels.markSelectedAsRead,
3334
+ "aria-label": labels.markSelectedAsRead,
3335
+ className: "cv-workspace-toggle",
3336
+ children: [
3337
+ "\u2713",
3338
+ /* @__PURE__ */ jsxs22("span", { className: "cv-only-wide", children: [
3339
+ " ",
3340
+ labels.markSelectedAsRead
3341
+ ] }),
3342
+ inbox.selectedIds.size > 0 ? ` (${inbox.selectedIds.size})` : ""
3343
+ ]
3344
+ }
3345
+ ),
3346
+ inbox.canMarkAllRead && inbox.unreadCount > 0 ? /* @__PURE__ */ jsxs22(
3347
+ "button",
3348
+ {
3349
+ type: "button",
3350
+ onClick: () => void inbox.markAllAsRead(),
3351
+ disabled: inbox.busy,
3352
+ title: labels.markAllAsRead,
3353
+ className: "cv-workspace-toggle",
3354
+ children: [
3355
+ "\u2713\u2713",
3356
+ /* @__PURE__ */ jsxs22("span", { className: "cv-only-wide", children: [
3357
+ " ",
3358
+ labels.markAllAsRead
3359
+ ] })
3360
+ ]
3361
+ }
3362
+ ) : null,
3363
+ renderHeaderActions?.(inbox)
3364
+ ] })
3365
+ ] }),
3366
+ inbox.loadFailure ? /* @__PURE__ */ jsxs22("p", { role: "alert", className: "cv-workspace-failure", children: [
3367
+ inbox.loadFailure,
3368
+ signInHref ? /* @__PURE__ */ jsxs22(Fragment4, { children: [
3369
+ " ",
3370
+ /* @__PURE__ */ jsx24("a", { href: signInHref, className: "cv-workspace-failure__link", children: labels.signIn })
3371
+ ] }) : null
3372
+ ] }) : null,
3373
+ /* @__PURE__ */ jsxs22(
3374
+ "div",
3375
+ {
3376
+ className: `cv-workspace-grid${showSimulator ? " cv-workspace-grid--with-simulator" : ""}`,
3377
+ "data-detail": selected ? "open" : "closed",
3378
+ children: [
3379
+ /* @__PURE__ */ jsx24(
3380
+ ConversationsInboxList,
3381
+ {
3382
+ inbox,
3383
+ labels,
3384
+ className: "cv-workspace-list",
3385
+ ...renderFilters ? { renderFilters } : {},
3386
+ ...renderBulkActions ? { renderBulkActions } : {},
3387
+ ...renderRow ? { renderRow } : {},
3388
+ ...onSendTemplateToSelected ? { onSendTemplateToSelected: () => onSendTemplateToSelected(inbox) } : inbox.canListTemplates ? { onSendTemplateToSelected: () => setTemplateModalOpen(true) } : {}
3389
+ }
3390
+ ),
3391
+ /* @__PURE__ */ jsx24("section", { className: "cv-workspace-detail", children: selected ? /* @__PURE__ */ jsx24(
3392
+ ConversationPane,
3393
+ {
3394
+ conversation: selected,
3395
+ now: inbox.now,
3396
+ busy: inbox.busy,
3397
+ labels,
3398
+ ...inbox.canTakeover ? { onTakeover: () => void inbox.takeover(selected.id) } : {},
3399
+ ...inbox.canTakeover ? { onReturnToBot: () => void inbox.releaseToBot(selected.id) } : {},
3400
+ ...inbox.canFinalize ? { onFinish: () => void inbox.finalize(selected.id) } : {},
3401
+ ...flowLabelOf ? { flowLabel: flowLabelOf(selected) } : {},
3402
+ ...onDownload ? { onDownload: () => onDownload(selected) } : {},
3403
+ ...requireTakeoverToReply ? { requireTakeoverToReply } : {},
3404
+ ...messageSelection ? { messageSelection } : {},
3405
+ ...initialComposerText ? { initialComposerText } : {},
3406
+ onBack: inbox.clearSelection,
3407
+ ...paneUtilities ? { extraUtilities: paneUtilities } : {},
3408
+ ...contextEntriesOf ? { contextEntriesOf } : {},
3409
+ ...quickReplies ? { quickReplies } : {},
3410
+ ...quickReplyVariablesFor ? { quickReplyVariablesFor } : {},
3411
+ ...renderAboveTranscript ? { renderAboveTranscript } : {},
3412
+ ...onAttach ? { onAttach: (file) => onAttach(selected, file) } : {}
3413
+ }
3414
+ ) : /* @__PURE__ */ jsx24("p", { className: "cv-workspace-empty", children: labels.emptyDetail }) }),
3415
+ showSimulator && selected ? (
3416
+ // `min-height:0` junto do `min-width:0`: sem isso a linha do grid cresce com o conteúdo do
3417
+ // painel, o scroll interno nunca ativa e quem rola passa a ser a página inteira.
3418
+ /* @__PURE__ */ jsx24("div", { className: "cv-workspace-simulator", children: simulator?.render({ conversationId: selected.id, close: () => setSimulatorOpen(false) }) })
3419
+ ) : null
3420
+ ]
3421
+ }
3422
+ ),
3423
+ templateModalOpen ? /* @__PURE__ */ jsx24(
3424
+ BulkTemplateModal,
3425
+ {
3426
+ labels,
3427
+ expiredCount: inbox.expiredSelectedCount,
3428
+ sending: inbox.busy,
3429
+ onClose: () => setTemplateModalOpen(false),
3430
+ onSend: (templateName) => {
3431
+ void inbox.sendTemplateToSelected(templateName).then(() => setTemplateModalOpen(false));
3432
+ }
3433
+ }
3434
+ ) : null
3435
+ ] });
3436
+ }
2589
3437
  export {
2590
3438
  AudioPlayer,
2591
3439
  AudioRecorderButton,
@@ -2594,6 +3442,7 @@ export {
2594
3442
  CHANNEL_BRAND_COLOR,
2595
3443
  CHANNEL_CAPABILITIES,
2596
3444
  CHANNEL_FILTER_ALL,
3445
+ CONVERSATIONS_PER_PAGE,
2597
3446
  CONVERSATION_CHANNEL,
2598
3447
  CONVERSATION_WINDOW,
2599
3448
  ChannelIcon,
@@ -2602,12 +3451,16 @@ export {
2602
3451
  ConversationHeader,
2603
3452
  ConversationListItem,
2604
3453
  ConversationLocalesProvider,
3454
+ ConversationPane,
2605
3455
  ConversationRow,
2606
3456
  ConversationWallpaper,
3457
+ ConversationsInboxList,
2607
3458
  ConversationsProvider,
3459
+ ConversationsWorkspace,
2608
3460
  DEFAULT_ACCEPTED_FILE_TYPES,
2609
3461
  DEFAULT_AUDIO_RECORDER_BUTTON_LABELS,
2610
3462
  DEFAULT_AVATAR_LABELS,
3463
+ DEFAULT_CONVERSATIONS_WORKSPACE_LABELS,
2611
3464
  DEFAULT_CONVERSATION_CHANNEL,
2612
3465
  DEFAULT_CONVERSATION_CONTEXT_LABELS,
2613
3466
  DEFAULT_CONVERSATION_DOCUMENTS_LABELS,
@@ -2685,6 +3538,7 @@ export {
2685
3538
  useConversationMessages,
2686
3539
  useConversationRealtime,
2687
3540
  useConversations,
3541
+ useConversationsInbox,
2688
3542
  useDarkMode,
2689
3543
  useGlobalRealtime,
2690
3544
  useInboxActions,