@adatechnology/conversations-ui 0.1.0-rc.28 → 0.1.0-rc.29
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/{chunk-UZJBYD5O.js → chunk-GY472G6E.js} +56 -5
- package/dist/index.d.ts +6 -2
- package/dist/index.js +68 -18
- package/dist/preview/index.d.ts +2 -2
- package/dist/preview/index.js +1 -1
- package/dist/{types-BfINicc-.d.ts → types-De5aN-E_.d.ts} +11 -0
- package/package.json +1 -1
- package/src/DocumentsLibrary.tsx +58 -3
- package/src/documents/DocumentsWorkspace.tsx +52 -2
- package/src/documents/labels.ts +4 -0
- package/src/providers/types.ts +11 -0
|
@@ -1966,9 +1966,9 @@ function ConversationDocumentsPanel({
|
|
|
1966
1966
|
}
|
|
1967
1967
|
|
|
1968
1968
|
// src/DocumentsLibrary.tsx
|
|
1969
|
-
import { useEffect as useEffect4, useState as useState11 } from "react";
|
|
1970
|
-
import { ArrowUpDown as ArrowUpDown2, Bot as Bot2, Download as Download2, Eye as Eye2, MessageSquare, Users as Users2 } from "lucide-react";
|
|
1971
|
-
import { jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1969
|
+
import { useEffect as useEffect4, useRef as useRef5, useState as useState11 } from "react";
|
|
1970
|
+
import { ArrowUpDown as ArrowUpDown2, Bot as Bot2, Download as Download2, Eye as Eye2, MessageSquare, Upload, Users as Users2 } from "lucide-react";
|
|
1971
|
+
import { Fragment as Fragment4, jsx as jsx17, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1972
1972
|
var DEFAULT_DOCUMENTS_LIBRARY_LABELS = {
|
|
1973
1973
|
title: "Documentos",
|
|
1974
1974
|
searchPlaceholder: "Buscar por nome do arquivo ou telefone",
|
|
@@ -1985,6 +1985,8 @@ var DEFAULT_DOCUMENTS_LIBRARY_LABELS = {
|
|
|
1985
1985
|
sortMostRecent: "Mais recentes",
|
|
1986
1986
|
sortOldest: "Mais antigos",
|
|
1987
1987
|
clearFilters: "Limpar filtros",
|
|
1988
|
+
upload: "Enviar documento",
|
|
1989
|
+
uploadError: "N\xE3o foi poss\xEDvel enviar o arquivo.",
|
|
1988
1990
|
total: (count) => `${count} arquivo${count === 1 ? "" : "s"}`,
|
|
1989
1991
|
page: (current, last) => `${current} / ${last}`
|
|
1990
1992
|
};
|
|
@@ -2007,9 +2009,14 @@ function DocumentsLibrary({
|
|
|
2007
2009
|
const [total, setTotal] = useState11(0);
|
|
2008
2010
|
const [loading, setLoading] = useState11(false);
|
|
2009
2011
|
const [failed, setFailed] = useState11(false);
|
|
2012
|
+
const [uploading, setUploading] = useState11(false);
|
|
2013
|
+
const [uploadFailed, setUploadFailed] = useState11(false);
|
|
2014
|
+
const [reloadToken, setReloadToken] = useState11(0);
|
|
2015
|
+
const fileInputRef = useRef5(null);
|
|
2010
2016
|
const hasFilters = search !== "" || sourceFilter !== DOCUMENT_SOURCE_FILTER.ALL || sortDirection !== "desc";
|
|
2011
2017
|
const lastPage = Math.max(1, Math.ceil(total / perPage));
|
|
2012
2018
|
const fetchAll = context?.api.getAllDocuments;
|
|
2019
|
+
const uploadDocument = context?.api.uploadDocument;
|
|
2013
2020
|
useEffect4(() => {
|
|
2014
2021
|
if (!fetchAll) return;
|
|
2015
2022
|
let active = true;
|
|
@@ -2033,7 +2040,7 @@ function DocumentsLibrary({
|
|
|
2033
2040
|
return () => {
|
|
2034
2041
|
active = false;
|
|
2035
2042
|
};
|
|
2036
|
-
}, [fetchAll, search, sourceFilter, sortDirection, page, perPage]);
|
|
2043
|
+
}, [fetchAll, search, sourceFilter, sortDirection, page, perPage, reloadToken]);
|
|
2037
2044
|
function applyFilter(change) {
|
|
2038
2045
|
change();
|
|
2039
2046
|
setPage(1);
|
|
@@ -2042,6 +2049,19 @@ function DocumentsLibrary({
|
|
|
2042
2049
|
const url = await context?.api.getDocumentUrl(uploadId, disposition);
|
|
2043
2050
|
if (url) window.open(url, "_blank", "noopener,noreferrer");
|
|
2044
2051
|
}
|
|
2052
|
+
async function handleUpload(file) {
|
|
2053
|
+
if (!uploadDocument) return;
|
|
2054
|
+
setUploading(true);
|
|
2055
|
+
setUploadFailed(false);
|
|
2056
|
+
try {
|
|
2057
|
+
await uploadDocument(file);
|
|
2058
|
+
setReloadToken((token) => token + 1);
|
|
2059
|
+
} catch {
|
|
2060
|
+
setUploadFailed(true);
|
|
2061
|
+
} finally {
|
|
2062
|
+
setUploading(false);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2045
2065
|
if (!fetchAll) return null;
|
|
2046
2066
|
return /* @__PURE__ */ jsxs12("div", { className: cn("space-y-3", classNames?.root, className), children: [
|
|
2047
2067
|
/* @__PURE__ */ jsx17("h2", { className: cn("text-lg font-semibold", classNames?.title), children: labels.title }),
|
|
@@ -2099,10 +2119,41 @@ function DocumentsLibrary({
|
|
|
2099
2119
|
className: cn("cv-header-action", classNames?.clearButton),
|
|
2100
2120
|
children: labels.clearFilters
|
|
2101
2121
|
}
|
|
2102
|
-
) : null
|
|
2122
|
+
) : null,
|
|
2123
|
+
uploadDocument ? /* @__PURE__ */ jsxs12(Fragment4, { children: [
|
|
2124
|
+
/* @__PURE__ */ jsx17(
|
|
2125
|
+
"input",
|
|
2126
|
+
{
|
|
2127
|
+
ref: fileInputRef,
|
|
2128
|
+
type: "file",
|
|
2129
|
+
hidden: true,
|
|
2130
|
+
onChange: (event) => {
|
|
2131
|
+
const file = event.target.files?.[0];
|
|
2132
|
+
event.target.value = "";
|
|
2133
|
+
if (file) void handleUpload(file);
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
),
|
|
2137
|
+
/* @__PURE__ */ jsxs12(
|
|
2138
|
+
"button",
|
|
2139
|
+
{
|
|
2140
|
+
"data-cv-tooltip": labels.upload,
|
|
2141
|
+
"aria-label": labels.upload,
|
|
2142
|
+
type: "button",
|
|
2143
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2144
|
+
disabled: uploading,
|
|
2145
|
+
className: "cv-header-action ml-auto inline-flex items-center gap-1 disabled:opacity-40",
|
|
2146
|
+
children: [
|
|
2147
|
+
/* @__PURE__ */ jsx17(Upload, { size: 14, "aria-hidden": "true" }),
|
|
2148
|
+
labels.upload
|
|
2149
|
+
]
|
|
2150
|
+
}
|
|
2151
|
+
)
|
|
2152
|
+
] }) : null
|
|
2103
2153
|
] }),
|
|
2104
2154
|
loading ? /* @__PURE__ */ jsx17("p", { className: cn("text-sm text-gray-500", classNames?.status), children: labels.loading }) : null,
|
|
2105
2155
|
failed ? /* @__PURE__ */ jsx17("p", { role: "alert", className: cn("text-sm text-red-600 dark:text-red-400", classNames?.status), children: labels.failure }) : null,
|
|
2156
|
+
uploadFailed ? /* @__PURE__ */ jsx17("p", { role: "alert", className: cn("text-sm text-red-600 dark:text-red-400", classNames?.status), children: labels.uploadError }) : null,
|
|
2106
2157
|
!loading && !failed && documents.length === 0 ? /* @__PURE__ */ jsx17("p", { className: cn("text-sm text-gray-500", classNames?.status), children: hasFilters ? labels.noResults : labels.empty }) : null,
|
|
2107
2158
|
/* @__PURE__ */ jsx17("ul", { className: cn("space-y-2", classNames?.list), children: documents.map((document2) => {
|
|
2108
2159
|
const isFromCustomer = !TEAM_SOURCES2.has(document2.source);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import react__default, { ReactNode, CSSProperties, UIEvent, FormEvent, RefObject } from 'react';
|
|
3
|
-
import { G as MessagePayload, N as ResolveMediaUrl, z as InteractiveSelection, J as MessageTranscription, x as InteractivePayload, r as ConversationsFeatures, o as ConversationSummary, j as ConversationChannel, L as ListConversationsParams, q as ConversationsApi, S as SSEProvider, T as TranscriptionMode, B as ListDocumentsParams, k as ConversationDocument, p as ConversationTemplate, f as ChannelFilter, g as ChannelFilterOption } from './types-
|
|
4
|
-
export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, C as CHANNEL_CAPABILITIES, c as CHANNEL_FILTER_ALL, d as CONVERSATION_CHANNEL, e as ChannelCapabilities, h as CompanyDocument, i as CompanyDocumentPage, l as ConversationDocumentPage, m as ConversationEventSource, n as ConversationPage, s as ConversationsTheme, t as ConversationsUIConfig, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, u as DEFAULT_CONVERSATION_CHANNEL, v as DEFAULT_MAX_RECORDING_MILLISECONDS, F as FormatContactHandleParams, H as HANDLE_KIND, w as HandleKind, I as InteractiveOption, y as InteractiveSection, M as MediaRenderer, E as MediaRendererProps, R as REOPEN_MECHANISM, K as ReopenMechanism, O as TranscriptionStatus, P as capabilitiesOf, Q as channelFiltersFor, U as contactFlag, V as formatContactHandle } from './types-
|
|
3
|
+
import { G as MessagePayload, N as ResolveMediaUrl, z as InteractiveSelection, J as MessageTranscription, x as InteractivePayload, r as ConversationsFeatures, o as ConversationSummary, j as ConversationChannel, L as ListConversationsParams, q as ConversationsApi, S as SSEProvider, T as TranscriptionMode, B as ListDocumentsParams, k as ConversationDocument, p as ConversationTemplate, f as ChannelFilter, g as ChannelFilterOption } from './types-De5aN-E_.js';
|
|
4
|
+
export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, C as CHANNEL_CAPABILITIES, c as CHANNEL_FILTER_ALL, d as CONVERSATION_CHANNEL, e as ChannelCapabilities, h as CompanyDocument, i as CompanyDocumentPage, l as ConversationDocumentPage, m as ConversationEventSource, n as ConversationPage, s as ConversationsTheme, t as ConversationsUIConfig, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS, u as DEFAULT_CONVERSATION_CHANNEL, v as DEFAULT_MAX_RECORDING_MILLISECONDS, F as FormatContactHandleParams, H as HANDLE_KIND, w as HandleKind, I as InteractiveOption, y as InteractiveSection, M as MediaRenderer, E as MediaRendererProps, R as REOPEN_MECHANISM, K as ReopenMechanism, O as TranscriptionStatus, P as capabilitiesOf, Q as channelFiltersFor, U as contactFlag, V as formatContactHandle } from './types-De5aN-E_.js';
|
|
5
5
|
|
|
6
6
|
interface MessageBubbleProps {
|
|
7
7
|
message: MessagePayload;
|
|
@@ -727,6 +727,8 @@ interface DocumentsLibraryLabels {
|
|
|
727
727
|
sortMostRecent: string;
|
|
728
728
|
sortOldest: string;
|
|
729
729
|
clearFilters: string;
|
|
730
|
+
upload: string;
|
|
731
|
+
uploadError: string;
|
|
730
732
|
total: (count: number) => string;
|
|
731
733
|
page: (current: number, last: number) => string;
|
|
732
734
|
}
|
|
@@ -835,6 +837,8 @@ interface DocumentsWorkspaceLabels {
|
|
|
835
837
|
readonly bulkDownloadZip: string;
|
|
836
838
|
readonly bulkRemove: (count: number) => string;
|
|
837
839
|
readonly bulkRemoveConfirm: (count: number) => string;
|
|
840
|
+
readonly upload: string;
|
|
841
|
+
readonly uploadError: string;
|
|
838
842
|
readonly columnFilename: string;
|
|
839
843
|
readonly columnContact: string;
|
|
840
844
|
readonly columnType: string;
|
package/dist/index.js
CHANGED
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
useConversationDocuments,
|
|
58
58
|
useConversationLocales,
|
|
59
59
|
useConversations
|
|
60
|
-
} from "./chunk-
|
|
60
|
+
} from "./chunk-GY472G6E.js";
|
|
61
61
|
import {
|
|
62
62
|
ZERO_WIDTH_SPACE,
|
|
63
63
|
htmlToWA,
|
|
@@ -1869,8 +1869,8 @@ function isWindowBlocking(window2) {
|
|
|
1869
1869
|
}
|
|
1870
1870
|
|
|
1871
1871
|
// src/documents/DocumentsWorkspace.tsx
|
|
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";
|
|
1872
|
+
import { useEffect as useEffect7, useMemo as useMemo2, useRef as useRef5, useState as useState11 } from "react";
|
|
1873
|
+
import { Download as Download2, Eye, MessageSquare, Trash2, Upload, X as X3 } from "lucide-react";
|
|
1874
1874
|
|
|
1875
1875
|
// src/listing/index.tsx
|
|
1876
1876
|
import { useState as useState9 } from "react";
|
|
@@ -2092,6 +2092,8 @@ var DEFAULT_DOCUMENTS_WORKSPACE_LABELS = {
|
|
|
2092
2092
|
bulkDownloadZip: "Baixar em zip",
|
|
2093
2093
|
bulkRemove: (count) => `Excluir ${count}`,
|
|
2094
2094
|
bulkRemoveConfirm: (count) => `Excluir ${count} arquivo${count === 1 ? "" : "s"}?`,
|
|
2095
|
+
upload: "Enviar documento",
|
|
2096
|
+
uploadError: "N\xE3o foi poss\xEDvel enviar o arquivo.",
|
|
2095
2097
|
columnFilename: "Arquivo",
|
|
2096
2098
|
columnContact: "Contato",
|
|
2097
2099
|
columnType: "Tipo",
|
|
@@ -2147,10 +2149,14 @@ function DocumentsWorkspace({
|
|
|
2147
2149
|
const [selectedIds, setSelectedIds] = useState11(/* @__PURE__ */ new Set());
|
|
2148
2150
|
const [reloadToken, setReloadToken] = useState11(0);
|
|
2149
2151
|
const [busy, setBusy] = useState11(false);
|
|
2152
|
+
const [uploading, setUploading] = useState11(false);
|
|
2153
|
+
const [uploadFailed, setUploadFailed] = useState11(false);
|
|
2154
|
+
const fileInputRef = useRef5(null);
|
|
2150
2155
|
const debouncedSearch = useDebouncedValue(search);
|
|
2151
2156
|
const fetchAll = context?.api.getAllDocuments;
|
|
2152
2157
|
const removeDocument = context?.api.deleteDocument;
|
|
2153
2158
|
const downloadArchive = context?.api.downloadDocumentsArchiveByIds;
|
|
2159
|
+
const uploadDocument = context?.api.uploadDocument;
|
|
2154
2160
|
const extraKey = JSON.stringify(extra);
|
|
2155
2161
|
const sourceOptions = useMemo2(
|
|
2156
2162
|
() => sources ?? Object.entries(labels.sourceLabels).map(([value, label]) => ({ value, label })),
|
|
@@ -2234,6 +2240,19 @@ function DocumentsWorkspace({
|
|
|
2234
2240
|
setBusy(false);
|
|
2235
2241
|
}
|
|
2236
2242
|
}
|
|
2243
|
+
async function handleUpload(file) {
|
|
2244
|
+
if (!uploadDocument) return;
|
|
2245
|
+
setUploading(true);
|
|
2246
|
+
setUploadFailed(false);
|
|
2247
|
+
try {
|
|
2248
|
+
await uploadDocument(file, Object.keys(extra).length > 0 ? extra : void 0);
|
|
2249
|
+
setReloadToken((token) => token + 1);
|
|
2250
|
+
} catch {
|
|
2251
|
+
setUploadFailed(true);
|
|
2252
|
+
} finally {
|
|
2253
|
+
setUploading(false);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2237
2256
|
async function handleDownloadArchive() {
|
|
2238
2257
|
if (!downloadArchive) return;
|
|
2239
2258
|
setBusy(true);
|
|
@@ -2307,8 +2326,39 @@ function DocumentsWorkspace({
|
|
|
2307
2326
|
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
2327
|
/* @__PURE__ */ jsx16(X3, { size: 12, "aria-hidden": "true" }),
|
|
2309
2328
|
labels.clearFilters
|
|
2329
|
+
] }) : null,
|
|
2330
|
+
uploadDocument ? /* @__PURE__ */ jsxs14(Fragment5, { children: [
|
|
2331
|
+
/* @__PURE__ */ jsx16(
|
|
2332
|
+
"input",
|
|
2333
|
+
{
|
|
2334
|
+
ref: fileInputRef,
|
|
2335
|
+
type: "file",
|
|
2336
|
+
hidden: true,
|
|
2337
|
+
onChange: (event) => {
|
|
2338
|
+
const file = event.target.files?.[0];
|
|
2339
|
+
event.target.value = "";
|
|
2340
|
+
if (file) void handleUpload(file);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
),
|
|
2344
|
+
/* @__PURE__ */ jsxs14(
|
|
2345
|
+
"button",
|
|
2346
|
+
{
|
|
2347
|
+
"data-cv-tooltip": labels.upload,
|
|
2348
|
+
"aria-label": labels.upload,
|
|
2349
|
+
type: "button",
|
|
2350
|
+
onClick: () => fileInputRef.current?.click(),
|
|
2351
|
+
disabled: uploading,
|
|
2352
|
+
className: "cv-header-action ml-auto inline-flex items-center gap-1 disabled:opacity-40",
|
|
2353
|
+
children: [
|
|
2354
|
+
/* @__PURE__ */ jsx16(Upload, { size: 12, "aria-hidden": "true" }),
|
|
2355
|
+
labels.upload
|
|
2356
|
+
]
|
|
2357
|
+
}
|
|
2358
|
+
)
|
|
2310
2359
|
] }) : null
|
|
2311
2360
|
] }),
|
|
2361
|
+
uploadFailed ? /* @__PURE__ */ jsx16("p", { role: "alert", className: cn("text-sm text-red-600 dark:text-red-400", classNames?.status), children: labels.uploadError }) : null,
|
|
2312
2362
|
canSelect ? /* @__PURE__ */ jsxs14(
|
|
2313
2363
|
BulkActionBar,
|
|
2314
2364
|
{
|
|
@@ -2516,7 +2566,7 @@ function downloadTextFile(filename, content) {
|
|
|
2516
2566
|
}
|
|
2517
2567
|
|
|
2518
2568
|
// src/useWaitingNotifications.ts
|
|
2519
|
-
import { useState as useState12, useEffect as useEffect8, useRef as
|
|
2569
|
+
import { useState as useState12, useEffect as useEffect8, useRef as useRef6, useCallback as useCallback5 } from "react";
|
|
2520
2570
|
var DEFAULT_POLL_INTERVAL_MS = 1e4;
|
|
2521
2571
|
var DEFAULT_LIMIT = 50;
|
|
2522
2572
|
var DEFAULT_LABELS = {
|
|
@@ -2526,9 +2576,9 @@ var DEFAULT_LABELS = {
|
|
|
2526
2576
|
function useWaitingNotifications(params) {
|
|
2527
2577
|
const conversationsContext = useConversations();
|
|
2528
2578
|
const [conversations, setConversations] = useState12([]);
|
|
2529
|
-
const previousUnreadMap =
|
|
2530
|
-
const notifiedIds =
|
|
2531
|
-
const isPollingRef =
|
|
2579
|
+
const previousUnreadMap = useRef6(/* @__PURE__ */ new Map());
|
|
2580
|
+
const notifiedIds = useRef6(/* @__PURE__ */ new Set());
|
|
2581
|
+
const isPollingRef = useRef6(false);
|
|
2532
2582
|
const isEnabled = params?.enabled ?? true;
|
|
2533
2583
|
const intervalMs = params?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
2534
2584
|
const icon = params?.icon;
|
|
@@ -3708,16 +3758,16 @@ function useConversationMessages(conversationId, params) {
|
|
|
3708
3758
|
}
|
|
3709
3759
|
|
|
3710
3760
|
// src/hooks/useScrollToLatestMessage.ts
|
|
3711
|
-
import { useCallback as useCallback8, useLayoutEffect, useRef as
|
|
3761
|
+
import { useCallback as useCallback8, useLayoutEffect, useRef as useRef7, useState as useState15 } from "react";
|
|
3712
3762
|
var NEAR_BOTTOM_THRESHOLD_PX = 120;
|
|
3713
3763
|
var SCROLL_BEHAVIOR = "auto";
|
|
3714
3764
|
function useScrollToLatestMessage({
|
|
3715
3765
|
conversationId,
|
|
3716
3766
|
messageCount
|
|
3717
3767
|
}) {
|
|
3718
|
-
const containerRef =
|
|
3768
|
+
const containerRef = useRef7(null);
|
|
3719
3769
|
const [isAwayFromBottom, setIsAwayFromBottom] = useState15(false);
|
|
3720
|
-
const isAwayFromBottomRef =
|
|
3770
|
+
const isAwayFromBottomRef = useRef7(false);
|
|
3721
3771
|
const scrollToBottom = useCallback8((behavior = SCROLL_BEHAVIOR) => {
|
|
3722
3772
|
const container = containerRef.current;
|
|
3723
3773
|
if (!container) return;
|
|
@@ -3730,7 +3780,7 @@ function useScrollToLatestMessage({
|
|
|
3730
3780
|
isAwayFromBottomRef.current = away;
|
|
3731
3781
|
setIsAwayFromBottom((current) => current === away ? current : away);
|
|
3732
3782
|
}, []);
|
|
3733
|
-
const jumpedForConversationRef =
|
|
3783
|
+
const jumpedForConversationRef = useRef7(void 0);
|
|
3734
3784
|
useLayoutEffect(() => {
|
|
3735
3785
|
const isNewConversation = jumpedForConversationRef.current !== conversationId;
|
|
3736
3786
|
if (isNewConversation) {
|
|
@@ -3777,10 +3827,10 @@ function useConversationContext(conversationId) {
|
|
|
3777
3827
|
}
|
|
3778
3828
|
|
|
3779
3829
|
// src/hooks/useConversationRealtime.ts
|
|
3780
|
-
import { useEffect as useEffect10, useRef as
|
|
3830
|
+
import { useEffect as useEffect10, useRef as useRef8 } from "react";
|
|
3781
3831
|
function useConversationRealtime(conversationId, onEvent) {
|
|
3782
3832
|
const sse = useConversations()?.sse;
|
|
3783
|
-
const onEventRef =
|
|
3833
|
+
const onEventRef = useRef8(onEvent);
|
|
3784
3834
|
onEventRef.current = onEvent;
|
|
3785
3835
|
useEffect10(() => {
|
|
3786
3836
|
if (!sse || !conversationId) return;
|
|
@@ -3795,7 +3845,7 @@ function useConversationRealtime(conversationId, onEvent) {
|
|
|
3795
3845
|
}
|
|
3796
3846
|
function useGlobalRealtime(onEvent) {
|
|
3797
3847
|
const sse = useConversations()?.sse;
|
|
3798
|
-
const onEventRef =
|
|
3848
|
+
const onEventRef = useRef8(onEvent);
|
|
3799
3849
|
onEventRef.current = onEvent;
|
|
3800
3850
|
useEffect10(() => {
|
|
3801
3851
|
if (!sse) return;
|
|
@@ -3937,7 +3987,7 @@ function BulkTemplateModal({
|
|
|
3937
3987
|
}
|
|
3938
3988
|
|
|
3939
3989
|
// src/workspace/ConversationPane.tsx
|
|
3940
|
-
import { useEffect as useEffect12, useRef as
|
|
3990
|
+
import { useEffect as useEffect12, useRef as useRef9, useState as useState17 } from "react";
|
|
3941
3991
|
import { jsx as jsx25, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
3942
3992
|
function ConversationPane({
|
|
3943
3993
|
conversation,
|
|
@@ -3977,7 +4027,7 @@ function ConversationPane({
|
|
|
3977
4027
|
const [draft, setDraft] = useState17(initialComposerText ?? "");
|
|
3978
4028
|
const [queuedFiles, setQueuedFiles] = useState17([]);
|
|
3979
4029
|
const [isSendingDraft, setIsSendingDraft] = useState17(false);
|
|
3980
|
-
const sendInFlightRef =
|
|
4030
|
+
const sendInFlightRef = useRef9(false);
|
|
3981
4031
|
useEffect12(() => {
|
|
3982
4032
|
setSelectedMessageIds(/* @__PURE__ */ new Set());
|
|
3983
4033
|
setDraft(initialComposerText ?? "");
|
|
@@ -4383,7 +4433,7 @@ var DEFAULT_CONVERSATIONS_WORKSPACE_LABELS = {
|
|
|
4383
4433
|
};
|
|
4384
4434
|
|
|
4385
4435
|
// src/workspace/useConversationsInbox.ts
|
|
4386
|
-
import { useCallback as useCallback9, useEffect as useEffect13, useMemo as useMemo5, useRef as
|
|
4436
|
+
import { useCallback as useCallback9, useEffect as useEffect13, useMemo as useMemo5, useRef as useRef10, useState as useState18 } from "react";
|
|
4387
4437
|
var CONVERSATIONS_PER_PAGE = 50;
|
|
4388
4438
|
function defaultDescribeFailure(error) {
|
|
4389
4439
|
const status = error.status;
|
|
@@ -4441,7 +4491,7 @@ function useConversationsInbox(params = {}) {
|
|
|
4441
4491
|
setSelectedId(void 0);
|
|
4442
4492
|
}
|
|
4443
4493
|
}, [conversations, selectedId]);
|
|
4444
|
-
const lastMarkReadAttempt =
|
|
4494
|
+
const lastMarkReadAttempt = useRef10(void 0);
|
|
4445
4495
|
useEffect13(() => {
|
|
4446
4496
|
if (!markReadOnOpen || !selectedId) return;
|
|
4447
4497
|
const opened = conversations.find((conversation) => conversation.id === selectedId);
|
package/dist/preview/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { G as MessagePayload, o as ConversationSummary, m as ConversationEventSource, q as ConversationsApi, L as ListConversationsParams, n as ConversationPage, S as SSEProvider, k as ConversationDocument, N as ResolveMediaUrl } from '../types-
|
|
2
|
-
export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS } from '../types-
|
|
1
|
+
import { G as MessagePayload, o as ConversationSummary, m as ConversationEventSource, q as ConversationsApi, L as ListConversationsParams, n as ConversationPage, S as SSEProvider, k as ConversationDocument, N as ResolveMediaUrl } from '../types-De5aN-E_.js';
|
|
2
|
+
export { A as AudioRecorderButton, a as AudioRecorderButtonLabels, b as AudioRecorderButtonProps, D as DEFAULT_AUDIO_RECORDER_BUTTON_LABELS } from '../types-De5aN-E_.js';
|
|
3
3
|
import * as react from 'react';
|
|
4
4
|
import { ReactNode } from 'react';
|
|
5
5
|
import { InteractiveReplyOption, InboundMediaType } from '@adatechnology/meta-whatsapp-contracts/testing';
|
package/dist/preview/index.js
CHANGED
|
@@ -396,6 +396,17 @@ interface ConversationsApi {
|
|
|
396
396
|
* `downloadDocumentsArchive`, que é por conversa. Ausente, a seleção em lote não oferece o botão.
|
|
397
397
|
*/
|
|
398
398
|
downloadDocumentsArchiveByIds?(uploadIds: readonly string[]): Promise<Blob>;
|
|
399
|
+
/**
|
|
400
|
+
* Envia um arquivo avulso direto pra biblioteca, fora do fluxo de uma conversa. **Opcional por
|
|
401
|
+
* capacidade:** cada host tem seu próprio contrato de upload (base64, multipart, presigned URL) —
|
|
402
|
+
* o pacote não escolhe um formato de payload, só entrega o `File` do input e deixa o host montar
|
|
403
|
+
* a chamada do jeito que seu backend espera. Ausente, a tela de biblioteca não desenha o botão de
|
|
404
|
+
* enviar, em vez de oferecer uma ação que sempre falha.
|
|
405
|
+
*
|
|
406
|
+
* `extra` é o mesmo vocabulário livre do produto que já viaja em `renderFilters` — cliente,
|
|
407
|
+
* unidade, campanha — pra associar o arquivo enviado ao contexto que a tela estava filtrando.
|
|
408
|
+
*/
|
|
409
|
+
uploadDocument?(file: File, extra?: Readonly<Record<string, string | number>>): Promise<ConversationDocument>;
|
|
399
410
|
getMediaProxyUrl(mediaId: string): Promise<{
|
|
400
411
|
mimeType: string;
|
|
401
412
|
data: string;
|
package/package.json
CHANGED
package/src/DocumentsLibrary.tsx
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* clicável. Sem essa referência, uma lista global de anexos não responde nenhuma pergunta.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { useEffect, useState } from 'react'
|
|
11
|
-
import { ArrowUpDown, Bot, Download, Eye, MessageSquare, Users } from 'lucide-react'
|
|
10
|
+
import { useEffect, useRef, useState } from 'react'
|
|
11
|
+
import { ArrowUpDown, Bot, Download, Eye, MessageSquare, Upload, Users } from 'lucide-react'
|
|
12
12
|
import { useConversations } from './providers/ConversationsProvider'
|
|
13
13
|
import { DOCUMENT_SOURCE_FILTER, type DocumentSourceFilter } from './ConversationDocumentsPanel'
|
|
14
14
|
import { FileIcon } from './FileIcon'
|
|
@@ -34,6 +34,8 @@ export interface DocumentsLibraryLabels {
|
|
|
34
34
|
sortMostRecent: string
|
|
35
35
|
sortOldest: string
|
|
36
36
|
clearFilters: string
|
|
37
|
+
upload: string
|
|
38
|
+
uploadError: string
|
|
37
39
|
total: (count: number) => string
|
|
38
40
|
page: (current: number, last: number) => string
|
|
39
41
|
}
|
|
@@ -54,6 +56,8 @@ export const DEFAULT_DOCUMENTS_LIBRARY_LABELS: DocumentsLibraryLabels = {
|
|
|
54
56
|
sortMostRecent: 'Mais recentes',
|
|
55
57
|
sortOldest: 'Mais antigos',
|
|
56
58
|
clearFilters: 'Limpar filtros',
|
|
59
|
+
upload: 'Enviar documento',
|
|
60
|
+
uploadError: 'Não foi possível enviar o arquivo.',
|
|
57
61
|
total: (count: number) => `${count} arquivo${count === 1 ? '' : 's'}`,
|
|
58
62
|
page: (current: number, last: number) => `${current} / ${last}`,
|
|
59
63
|
}
|
|
@@ -106,9 +110,15 @@ export function DocumentsLibrary({
|
|
|
106
110
|
const [loading, setLoading] = useState(false)
|
|
107
111
|
const [failed, setFailed] = useState(false)
|
|
108
112
|
|
|
113
|
+
const [uploading, setUploading] = useState(false)
|
|
114
|
+
const [uploadFailed, setUploadFailed] = useState(false)
|
|
115
|
+
const [reloadToken, setReloadToken] = useState(0)
|
|
116
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
117
|
+
|
|
109
118
|
const hasFilters = search !== '' || sourceFilter !== DOCUMENT_SOURCE_FILTER.ALL || sortDirection !== 'desc'
|
|
110
119
|
const lastPage = Math.max(1, Math.ceil(total / perPage))
|
|
111
120
|
const fetchAll = context?.api.getAllDocuments
|
|
121
|
+
const uploadDocument = context?.api.uploadDocument
|
|
112
122
|
|
|
113
123
|
useEffect(() => {
|
|
114
124
|
if (!fetchAll) return
|
|
@@ -140,7 +150,7 @@ export function DocumentsLibrary({
|
|
|
140
150
|
return () => {
|
|
141
151
|
active = false
|
|
142
152
|
}
|
|
143
|
-
}, [fetchAll, search, sourceFilter, sortDirection, page, perPage])
|
|
153
|
+
}, [fetchAll, search, sourceFilter, sortDirection, page, perPage, reloadToken])
|
|
144
154
|
|
|
145
155
|
function applyFilter(change: () => void): void {
|
|
146
156
|
change()
|
|
@@ -152,6 +162,20 @@ export function DocumentsLibrary({
|
|
|
152
162
|
if (url) window.open(url, '_blank', 'noopener,noreferrer')
|
|
153
163
|
}
|
|
154
164
|
|
|
165
|
+
async function handleUpload(file: File): Promise<void> {
|
|
166
|
+
if (!uploadDocument) return
|
|
167
|
+
setUploading(true)
|
|
168
|
+
setUploadFailed(false)
|
|
169
|
+
try {
|
|
170
|
+
await uploadDocument(file)
|
|
171
|
+
setReloadToken((token) => token + 1)
|
|
172
|
+
} catch {
|
|
173
|
+
setUploadFailed(true)
|
|
174
|
+
} finally {
|
|
175
|
+
setUploading(false)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
155
179
|
// Host sem `getAllDocuments` não tem o que mostrar aqui — some, em vez de renderizar vazio para
|
|
156
180
|
// sempre e fazer parecer que a empresa não tem arquivo nenhum.
|
|
157
181
|
if (!fetchAll) return null
|
|
@@ -207,6 +231,32 @@ export function DocumentsLibrary({
|
|
|
207
231
|
{labels.clearFilters}
|
|
208
232
|
</button>
|
|
209
233
|
) : null}
|
|
234
|
+
|
|
235
|
+
{uploadDocument ? (
|
|
236
|
+
<>
|
|
237
|
+
<input
|
|
238
|
+
ref={fileInputRef}
|
|
239
|
+
type="file"
|
|
240
|
+
hidden
|
|
241
|
+
onChange={(event) => {
|
|
242
|
+
const file = event.target.files?.[0]
|
|
243
|
+
event.target.value = ''
|
|
244
|
+
if (file) void handleUpload(file)
|
|
245
|
+
}}
|
|
246
|
+
/>
|
|
247
|
+
<button
|
|
248
|
+
data-cv-tooltip={labels.upload}
|
|
249
|
+
aria-label={labels.upload}
|
|
250
|
+
type="button"
|
|
251
|
+
onClick={() => fileInputRef.current?.click()}
|
|
252
|
+
disabled={uploading}
|
|
253
|
+
className="cv-header-action ml-auto inline-flex items-center gap-1 disabled:opacity-40"
|
|
254
|
+
>
|
|
255
|
+
<Upload size={14} aria-hidden="true" />
|
|
256
|
+
{labels.upload}
|
|
257
|
+
</button>
|
|
258
|
+
</>
|
|
259
|
+
) : null}
|
|
210
260
|
</div>
|
|
211
261
|
|
|
212
262
|
{loading ? <p className={cn('text-sm text-gray-500', classNames?.status)}>{labels.loading}</p> : null}
|
|
@@ -215,6 +265,11 @@ export function DocumentsLibrary({
|
|
|
215
265
|
{labels.failure}
|
|
216
266
|
</p>
|
|
217
267
|
) : null}
|
|
268
|
+
{uploadFailed ? (
|
|
269
|
+
<p role="alert" className={cn('text-sm text-red-600 dark:text-red-400', classNames?.status)}>
|
|
270
|
+
{labels.uploadError}
|
|
271
|
+
</p>
|
|
272
|
+
) : null}
|
|
218
273
|
{!loading && !failed && documents.length === 0 ? (
|
|
219
274
|
<p className={cn('text-sm text-gray-500', classNames?.status)}>
|
|
220
275
|
{hasFilters ? labels.noResults : labels.empty}
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* remontasse isso à mão voltaria a divergir dos outros — foi o que aconteceu antes desta tela existir.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { useEffect, useMemo, useState, type ReactNode } from 'react'
|
|
13
|
-
import { Download, Eye, MessageSquare, Trash2, X } from 'lucide-react'
|
|
12
|
+
import { useEffect, useMemo, useRef, useState, type ReactNode } from 'react'
|
|
13
|
+
import { Download, Eye, MessageSquare, Trash2, Upload, X } from 'lucide-react'
|
|
14
14
|
|
|
15
15
|
import { useConversations } from '../providers/ConversationsProvider'
|
|
16
16
|
import { FileIcon } from '../FileIcon'
|
|
@@ -106,11 +106,15 @@ export function DocumentsWorkspace({
|
|
|
106
106
|
const [selectedIds, setSelectedIds] = useState<ReadonlySet<string>>(new Set())
|
|
107
107
|
const [reloadToken, setReloadToken] = useState(0)
|
|
108
108
|
const [busy, setBusy] = useState(false)
|
|
109
|
+
const [uploading, setUploading] = useState(false)
|
|
110
|
+
const [uploadFailed, setUploadFailed] = useState(false)
|
|
111
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
109
112
|
|
|
110
113
|
const debouncedSearch = useDebouncedValue(search)
|
|
111
114
|
const fetchAll = context?.api.getAllDocuments
|
|
112
115
|
const removeDocument = context?.api.deleteDocument
|
|
113
116
|
const downloadArchive = context?.api.downloadDocumentsArchiveByIds
|
|
117
|
+
const uploadDocument = context?.api.uploadDocument
|
|
114
118
|
const extraKey = JSON.stringify(extra)
|
|
115
119
|
|
|
116
120
|
const sourceOptions = useMemo<readonly FilterOption[]>(
|
|
@@ -224,6 +228,20 @@ export function DocumentsWorkspace({
|
|
|
224
228
|
}
|
|
225
229
|
}
|
|
226
230
|
|
|
231
|
+
async function handleUpload(file: File): Promise<void> {
|
|
232
|
+
if (!uploadDocument) return
|
|
233
|
+
setUploading(true)
|
|
234
|
+
setUploadFailed(false)
|
|
235
|
+
try {
|
|
236
|
+
await uploadDocument(file, Object.keys(extra).length > 0 ? extra : undefined)
|
|
237
|
+
setReloadToken((token) => token + 1)
|
|
238
|
+
} catch {
|
|
239
|
+
setUploadFailed(true)
|
|
240
|
+
} finally {
|
|
241
|
+
setUploading(false)
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
227
245
|
async function handleDownloadArchive(): Promise<void> {
|
|
228
246
|
if (!downloadArchive) return
|
|
229
247
|
setBusy(true)
|
|
@@ -304,8 +322,40 @@ export function DocumentsWorkspace({
|
|
|
304
322
|
{labels.clearFilters}
|
|
305
323
|
</button>
|
|
306
324
|
) : null}
|
|
325
|
+
|
|
326
|
+
{uploadDocument ? (
|
|
327
|
+
<>
|
|
328
|
+
<input
|
|
329
|
+
ref={fileInputRef}
|
|
330
|
+
type="file"
|
|
331
|
+
hidden
|
|
332
|
+
onChange={(event) => {
|
|
333
|
+
const file = event.target.files?.[0]
|
|
334
|
+
event.target.value = ''
|
|
335
|
+
if (file) void handleUpload(file)
|
|
336
|
+
}}
|
|
337
|
+
/>
|
|
338
|
+
<button
|
|
339
|
+
data-cv-tooltip={labels.upload}
|
|
340
|
+
aria-label={labels.upload}
|
|
341
|
+
type="button"
|
|
342
|
+
onClick={() => fileInputRef.current?.click()}
|
|
343
|
+
disabled={uploading}
|
|
344
|
+
className="cv-header-action ml-auto inline-flex items-center gap-1 disabled:opacity-40"
|
|
345
|
+
>
|
|
346
|
+
<Upload size={12} aria-hidden="true" />
|
|
347
|
+
{labels.upload}
|
|
348
|
+
</button>
|
|
349
|
+
</>
|
|
350
|
+
) : null}
|
|
307
351
|
</div>
|
|
308
352
|
|
|
353
|
+
{uploadFailed ? (
|
|
354
|
+
<p role="alert" className={cn('text-sm text-red-600 dark:text-red-400', classNames?.status)}>
|
|
355
|
+
{labels.uploadError}
|
|
356
|
+
</p>
|
|
357
|
+
) : null}
|
|
358
|
+
|
|
309
359
|
{canSelect ? (
|
|
310
360
|
<BulkActionBar
|
|
311
361
|
selectedCount={selectedIds.size}
|
package/src/documents/labels.ts
CHANGED
|
@@ -28,6 +28,8 @@ export interface DocumentsWorkspaceLabels {
|
|
|
28
28
|
readonly bulkDownloadZip: string
|
|
29
29
|
readonly bulkRemove: (count: number) => string
|
|
30
30
|
readonly bulkRemoveConfirm: (count: number) => string
|
|
31
|
+
readonly upload: string
|
|
32
|
+
readonly uploadError: string
|
|
31
33
|
readonly columnFilename: string
|
|
32
34
|
readonly columnContact: string
|
|
33
35
|
readonly columnType: string
|
|
@@ -70,6 +72,8 @@ export const DEFAULT_DOCUMENTS_WORKSPACE_LABELS: DocumentsWorkspaceLabels = {
|
|
|
70
72
|
bulkDownloadZip: 'Baixar em zip',
|
|
71
73
|
bulkRemove: (count) => `Excluir ${count}`,
|
|
72
74
|
bulkRemoveConfirm: (count) => `Excluir ${count} arquivo${count === 1 ? '' : 's'}?`,
|
|
75
|
+
upload: 'Enviar documento',
|
|
76
|
+
uploadError: 'Não foi possível enviar o arquivo.',
|
|
73
77
|
columnFilename: 'Arquivo',
|
|
74
78
|
columnContact: 'Contato',
|
|
75
79
|
columnType: 'Tipo',
|
package/src/providers/types.ts
CHANGED
|
@@ -145,6 +145,17 @@ export interface ConversationsApi {
|
|
|
145
145
|
* `downloadDocumentsArchive`, que é por conversa. Ausente, a seleção em lote não oferece o botão.
|
|
146
146
|
*/
|
|
147
147
|
downloadDocumentsArchiveByIds?(uploadIds: readonly string[]): Promise<Blob>
|
|
148
|
+
/**
|
|
149
|
+
* Envia um arquivo avulso direto pra biblioteca, fora do fluxo de uma conversa. **Opcional por
|
|
150
|
+
* capacidade:** cada host tem seu próprio contrato de upload (base64, multipart, presigned URL) —
|
|
151
|
+
* o pacote não escolhe um formato de payload, só entrega o `File` do input e deixa o host montar
|
|
152
|
+
* a chamada do jeito que seu backend espera. Ausente, a tela de biblioteca não desenha o botão de
|
|
153
|
+
* enviar, em vez de oferecer uma ação que sempre falha.
|
|
154
|
+
*
|
|
155
|
+
* `extra` é o mesmo vocabulário livre do produto que já viaja em `renderFilters` — cliente,
|
|
156
|
+
* unidade, campanha — pra associar o arquivo enviado ao contexto que a tela estava filtrando.
|
|
157
|
+
*/
|
|
158
|
+
uploadDocument?(file: File, extra?: Readonly<Record<string, string | number>>): Promise<ConversationDocument>
|
|
148
159
|
getMediaProxyUrl(mediaId: string): Promise<{ mimeType: string; data: string }>
|
|
149
160
|
|
|
150
161
|
/**
|