@adatechnology/conversations-ui 0.1.0-rc.4 → 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.
- package/dist/{chunk-4R6Y43DQ.js → chunk-YWITIIHD.js} +18 -16
- package/dist/index.d.ts +102 -25
- package/dist/index.js +371 -60
- package/dist/preview/index.d.ts +17 -3
- package/dist/preview/index.js +324 -103
- package/dist/{types-C0PtaO7S.d.ts → types-C2Yexi8A.d.ts} +103 -13
- package/package.json +2 -2
- package/src/ConversationDocumentsPanel.tsx +342 -24
- package/src/FileIcon.test.ts +38 -0
- package/src/FileIcon.tsx +15 -4
- package/src/MediaRenderer.tsx +5 -2
- package/src/hooks/useConversationActions.ts +56 -0
- package/src/hooks/useConversationDocuments.ts +11 -7
- package/src/hooks/useConversationList.ts +15 -9
- package/src/hooks/useConversationMessages.ts +2 -2
- package/src/index.ts +15 -1
- package/src/lib/cn.test.ts +29 -0
- package/src/lib/paginated.test.ts +33 -0
- package/src/lib/paginated.ts +26 -0
- package/src/preview/createMockConversationsApi.ts +113 -7
- package/src/preview/index.ts +1 -1
- package/src/preview/preview.test.ts +5 -3
- package/src/preview/previewFixtures.ts +156 -1
- package/src/providers/types.ts +110 -9
- package/src/useWaitingNotifications.ts +74 -29
package/dist/index.js
CHANGED
|
@@ -15,10 +15,12 @@ import {
|
|
|
15
15
|
MessageComposer,
|
|
16
16
|
StatusTicks,
|
|
17
17
|
cn,
|
|
18
|
+
formatDateTime,
|
|
18
19
|
formatFileSize,
|
|
19
20
|
formatTimestamp,
|
|
21
|
+
isSameDay,
|
|
20
22
|
useConversationLocales
|
|
21
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-YWITIIHD.js";
|
|
22
24
|
import {
|
|
23
25
|
htmlToWA,
|
|
24
26
|
parseWhatsAppFormatting,
|
|
@@ -1148,6 +1150,7 @@ function isWindowBlocking(window2) {
|
|
|
1148
1150
|
|
|
1149
1151
|
// src/ConversationDocumentsPanel.tsx
|
|
1150
1152
|
import { useState as useState8 } from "react";
|
|
1153
|
+
import { ArrowUpDown, Bot, Download, Eye, Users } from "lucide-react";
|
|
1151
1154
|
|
|
1152
1155
|
// src/providers/ConversationsProvider.tsx
|
|
1153
1156
|
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
@@ -1190,6 +1193,17 @@ function useAsyncResource(fetcher, deps) {
|
|
|
1190
1193
|
return { data, loading, error, refetch: load };
|
|
1191
1194
|
}
|
|
1192
1195
|
|
|
1196
|
+
// src/lib/paginated.ts
|
|
1197
|
+
function conversationsOf(result) {
|
|
1198
|
+
return Array.isArray(result) ? result : result.conversations;
|
|
1199
|
+
}
|
|
1200
|
+
function documentsOf(result) {
|
|
1201
|
+
return Array.isArray(result) ? result : result.documents;
|
|
1202
|
+
}
|
|
1203
|
+
function totalOf(result) {
|
|
1204
|
+
return Array.isArray(result) ? result.length : result.total;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1193
1207
|
// src/hooks/useConversationDocuments.ts
|
|
1194
1208
|
function useConversationDocuments(conversationId, params) {
|
|
1195
1209
|
const context = useConversations();
|
|
@@ -1199,25 +1213,49 @@ function useConversationDocuments(conversationId, params) {
|
|
|
1199
1213
|
const { api } = context;
|
|
1200
1214
|
const { data, loading, error, refetch } = useAsyncResource(
|
|
1201
1215
|
() => conversationId ? api.getDocuments(conversationId, params) : Promise.resolve([]),
|
|
1202
|
-
[conversationId, params?.search, params?.page]
|
|
1216
|
+
[conversationId, params?.search, params?.page, params?.limit, params?.source, params?.sortDirection]
|
|
1203
1217
|
);
|
|
1204
|
-
|
|
1218
|
+
if (data === void 0) {
|
|
1219
|
+
return { documents: [], total: 0, loading, error, refetch };
|
|
1220
|
+
}
|
|
1221
|
+
return { documents: documentsOf(data), total: totalOf(data), loading, error, refetch };
|
|
1205
1222
|
}
|
|
1206
1223
|
|
|
1207
1224
|
// src/ConversationDocumentsPanel.tsx
|
|
1208
1225
|
import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
1226
|
+
var DOCUMENT_SOURCE_FILTER = {
|
|
1227
|
+
ALL: "all",
|
|
1228
|
+
CUSTOMER: "customer",
|
|
1229
|
+
TEAM: "team"
|
|
1230
|
+
};
|
|
1231
|
+
var TEAM_SOURCES = /* @__PURE__ */ new Set(["agent", "bot"]);
|
|
1209
1232
|
var DEFAULT_CONVERSATION_DOCUMENTS_LABELS = {
|
|
1210
1233
|
toggle: "\u{1F4CE} Arquivos",
|
|
1211
1234
|
title: "Arquivos da conversa",
|
|
1212
|
-
|
|
1235
|
+
noResults: "Nenhum arquivo encontrado para os filtros aplicados",
|
|
1236
|
+
view: "Visualizar",
|
|
1237
|
+
sourceFilterAll: "Todas as origens",
|
|
1238
|
+
sourceFilterCustomer: "Cliente",
|
|
1239
|
+
sourceFilterTeam: "Equipe",
|
|
1240
|
+
sortMostRecent: "Mais recentes",
|
|
1241
|
+
sortOldest: "Mais antigos",
|
|
1242
|
+
clearFilters: "Limpar filtros",
|
|
1243
|
+
selectAll: "Selecionar todos desta p\xE1gina",
|
|
1244
|
+
downloadSelected: (count) => `Baixar ${count} selecionado${count === 1 ? "" : "s"} (.zip)`,
|
|
1245
|
+
archiveFailed: "N\xE3o foi poss\xEDvel montar o arquivo compactado.",
|
|
1246
|
+
total: (count) => `${count} arquivo${count === 1 ? "" : "s"}`,
|
|
1247
|
+
page: (current, last) => `${current} / ${last}`,
|
|
1248
|
+
searchPlaceholder: "Buscar por nome do arquivo",
|
|
1213
1249
|
empty: "Nenhum arquivo nesta conversa.",
|
|
1214
1250
|
loading: "Carregando arquivos\u2026",
|
|
1215
1251
|
failure: "N\xE3o foi poss\xEDvel carregar os arquivos.",
|
|
1216
1252
|
download: "Baixar"
|
|
1217
1253
|
};
|
|
1254
|
+
var DEFAULT_PER_PAGE = 10;
|
|
1218
1255
|
function ConversationDocumentsPanel({
|
|
1219
1256
|
conversationId,
|
|
1220
1257
|
open,
|
|
1258
|
+
perPage = DEFAULT_PER_PAGE,
|
|
1221
1259
|
labels: labelsOverride,
|
|
1222
1260
|
className,
|
|
1223
1261
|
classNames
|
|
@@ -1225,34 +1263,253 @@ function ConversationDocumentsPanel({
|
|
|
1225
1263
|
const labels = { ...DEFAULT_CONVERSATION_DOCUMENTS_LABELS, ...labelsOverride };
|
|
1226
1264
|
const context = useConversations();
|
|
1227
1265
|
const [search, setSearch] = useState8("");
|
|
1228
|
-
const
|
|
1229
|
-
|
|
1230
|
-
|
|
1266
|
+
const [sourceFilter, setSourceFilter] = useState8(DOCUMENT_SOURCE_FILTER.ALL);
|
|
1267
|
+
const [sortDirection, setSortDirection] = useState8("desc");
|
|
1268
|
+
const [page, setPage] = useState8(1);
|
|
1269
|
+
const [selectedIds, setSelectedIds] = useState8([]);
|
|
1270
|
+
const [archiveError, setArchiveError] = useState8(false);
|
|
1271
|
+
const hasFilters = search !== "" || sourceFilter !== DOCUMENT_SOURCE_FILTER.ALL || sortDirection !== "desc";
|
|
1272
|
+
const { documents, total, loading, error } = useConversationDocuments(open ? conversationId : void 0, {
|
|
1273
|
+
search,
|
|
1274
|
+
page,
|
|
1275
|
+
limit: perPage,
|
|
1276
|
+
sortDirection,
|
|
1277
|
+
...sourceFilter === DOCUMENT_SOURCE_FILTER.ALL ? {} : { source: sourceFilter }
|
|
1278
|
+
});
|
|
1279
|
+
const lastPage = Math.max(1, Math.ceil(total / perPage));
|
|
1280
|
+
function applyFilter(change) {
|
|
1281
|
+
change();
|
|
1282
|
+
setPage(1);
|
|
1283
|
+
}
|
|
1284
|
+
async function handleOpen(uploadId, disposition) {
|
|
1285
|
+
const url = await context?.api.getDocumentUrl(uploadId, disposition);
|
|
1231
1286
|
if (url) window.open(url, "_blank", "noopener,noreferrer");
|
|
1232
1287
|
}
|
|
1288
|
+
const canArchive = typeof context?.api.downloadDocumentsArchive === "function";
|
|
1289
|
+
const pageIds = documents.map((document2) => document2.id);
|
|
1290
|
+
const allOnPageSelected = pageIds.length > 0 && pageIds.every((id) => selectedIds.includes(id));
|
|
1291
|
+
function toggleSelected(uploadId) {
|
|
1292
|
+
setArchiveError(false);
|
|
1293
|
+
setSelectedIds(
|
|
1294
|
+
(current) => current.includes(uploadId) ? current.filter((id) => id !== uploadId) : [...current, uploadId]
|
|
1295
|
+
);
|
|
1296
|
+
}
|
|
1297
|
+
function toggleAllOnPage() {
|
|
1298
|
+
setArchiveError(false);
|
|
1299
|
+
setSelectedIds(
|
|
1300
|
+
(current) => allOnPageSelected ? current.filter((id) => !pageIds.includes(id)) : [...current, ...pageIds.filter((id) => !current.includes(id))]
|
|
1301
|
+
);
|
|
1302
|
+
}
|
|
1303
|
+
async function handleDownloadSelected() {
|
|
1304
|
+
const archive = context?.api.downloadDocumentsArchive;
|
|
1305
|
+
if (!archive || selectedIds.length === 0) return;
|
|
1306
|
+
setArchiveError(false);
|
|
1307
|
+
try {
|
|
1308
|
+
const blob = await archive(conversationId, selectedIds);
|
|
1309
|
+
const url = URL.createObjectURL(blob);
|
|
1310
|
+
const anchor = document.createElement("a");
|
|
1311
|
+
anchor.href = url;
|
|
1312
|
+
anchor.download = `conversa-${conversationId}.zip`;
|
|
1313
|
+
anchor.click();
|
|
1314
|
+
URL.revokeObjectURL(url);
|
|
1315
|
+
setSelectedIds([]);
|
|
1316
|
+
} catch {
|
|
1317
|
+
setArchiveError(true);
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1233
1320
|
if (!open) return null;
|
|
1234
1321
|
return /* @__PURE__ */ jsx15("div", { className: cn("border-b", classNames?.root, className), children: /* @__PURE__ */ jsxs12("section", { className: cn("px-4 py-3", classNames?.body), children: [
|
|
1235
|
-
/* @__PURE__ */ jsx15("p", { className: "mb-2 text-sm font-medium", children: labels.title }),
|
|
1236
|
-
/* @__PURE__ */
|
|
1237
|
-
|
|
1322
|
+
/* @__PURE__ */ jsx15("p", { className: cn("mb-2 text-sm font-medium", classNames?.title), children: labels.title }),
|
|
1323
|
+
/* @__PURE__ */ jsxs12("div", { className: cn("mb-2 flex flex-wrap items-center gap-2 border-b pb-2", classNames?.filters), children: [
|
|
1324
|
+
/* @__PURE__ */ jsx15(
|
|
1325
|
+
"input",
|
|
1326
|
+
{
|
|
1327
|
+
type: "search",
|
|
1328
|
+
value: search,
|
|
1329
|
+
onChange: (event) => applyFilter(() => setSearch(event.target.value)),
|
|
1330
|
+
placeholder: labels.searchPlaceholder,
|
|
1331
|
+
"aria-label": labels.searchPlaceholder,
|
|
1332
|
+
className: cn("w-full rounded-md border px-3 py-2 text-sm sm:w-52", classNames?.search)
|
|
1333
|
+
}
|
|
1334
|
+
),
|
|
1335
|
+
/* @__PURE__ */ jsxs12(
|
|
1336
|
+
"select",
|
|
1337
|
+
{
|
|
1338
|
+
value: sourceFilter,
|
|
1339
|
+
onChange: (event) => applyFilter(() => setSourceFilter(event.target.value)),
|
|
1340
|
+
"aria-label": labels.sourceFilterAll,
|
|
1341
|
+
className: cn("w-full rounded-md border px-2 py-2 text-sm sm:w-36", classNames?.sourceSelect),
|
|
1342
|
+
children: [
|
|
1343
|
+
/* @__PURE__ */ jsx15("option", { value: DOCUMENT_SOURCE_FILTER.ALL, children: labels.sourceFilterAll }),
|
|
1344
|
+
/* @__PURE__ */ jsx15("option", { value: DOCUMENT_SOURCE_FILTER.CUSTOMER, children: labels.sourceFilterCustomer }),
|
|
1345
|
+
/* @__PURE__ */ jsx15("option", { value: DOCUMENT_SOURCE_FILTER.TEAM, children: labels.sourceFilterTeam })
|
|
1346
|
+
]
|
|
1347
|
+
}
|
|
1348
|
+
),
|
|
1349
|
+
/* @__PURE__ */ jsxs12(
|
|
1350
|
+
"button",
|
|
1351
|
+
{
|
|
1352
|
+
type: "button",
|
|
1353
|
+
onClick: () => applyFilter(() => setSortDirection(sortDirection === "desc" ? "asc" : "desc")),
|
|
1354
|
+
className: cn("cv-header-action inline-flex items-center gap-1", classNames?.sortButton),
|
|
1355
|
+
children: [
|
|
1356
|
+
/* @__PURE__ */ jsx15(ArrowUpDown, { size: 14 }),
|
|
1357
|
+
sortDirection === "desc" ? labels.sortMostRecent : labels.sortOldest
|
|
1358
|
+
]
|
|
1359
|
+
}
|
|
1360
|
+
),
|
|
1361
|
+
hasFilters ? /* @__PURE__ */ jsx15(
|
|
1362
|
+
"button",
|
|
1363
|
+
{
|
|
1364
|
+
type: "button",
|
|
1365
|
+
onClick: () => applyFilter(() => {
|
|
1366
|
+
setSearch("");
|
|
1367
|
+
setSourceFilter(DOCUMENT_SOURCE_FILTER.ALL);
|
|
1368
|
+
setSortDirection("desc");
|
|
1369
|
+
}),
|
|
1370
|
+
className: cn("cv-header-action", classNames?.clearButton),
|
|
1371
|
+
children: labels.clearFilters
|
|
1372
|
+
}
|
|
1373
|
+
) : null
|
|
1374
|
+
] }),
|
|
1375
|
+
loading ? /* @__PURE__ */ jsx15("p", { className: cn("text-xs text-gray-500", classNames?.status), children: labels.loading }) : null,
|
|
1376
|
+
error ? /* @__PURE__ */ jsx15("p", { role: "alert", className: cn("text-xs text-red-600 dark:text-red-400", classNames?.status), children: labels.failure }) : null,
|
|
1377
|
+
!loading && !error && documents.length === 0 ? /* @__PURE__ */ jsx15("p", { className: cn("text-xs text-gray-500", classNames?.status), children: hasFilters ? labels.noResults : labels.empty }) : null,
|
|
1378
|
+
canArchive && documents.length > 0 ? /* @__PURE__ */ jsxs12("div", { className: cn("mb-2 flex flex-wrap items-center gap-3 text-xs", classNames?.selectionBar), children: [
|
|
1379
|
+
/* @__PURE__ */ jsxs12("label", { className: "inline-flex items-center gap-1.5", children: [
|
|
1380
|
+
/* @__PURE__ */ jsx15(
|
|
1381
|
+
"input",
|
|
1382
|
+
{
|
|
1383
|
+
type: "checkbox",
|
|
1384
|
+
checked: allOnPageSelected,
|
|
1385
|
+
onChange: toggleAllOnPage,
|
|
1386
|
+
className: cn(classNames?.checkbox)
|
|
1387
|
+
}
|
|
1388
|
+
),
|
|
1389
|
+
labels.selectAll
|
|
1390
|
+
] }),
|
|
1391
|
+
selectedIds.length > 0 ? /* @__PURE__ */ jsx15("button", { type: "button", onClick: () => void handleDownloadSelected(), className: "cv-header-action", children: labels.downloadSelected(selectedIds.length) }) : null,
|
|
1392
|
+
archiveError ? /* @__PURE__ */ jsx15("span", { role: "alert", className: "text-red-600 dark:text-red-400", children: labels.archiveFailed }) : null
|
|
1393
|
+
] }) : null,
|
|
1394
|
+
/* @__PURE__ */ jsx15("ul", { className: cn("space-y-2", classNames?.list), children: documents.map((document2) => {
|
|
1395
|
+
const isFromCustomer = !TEAM_SOURCES.has(document2.source);
|
|
1396
|
+
const SourceIcon = isFromCustomer ? Users : Bot;
|
|
1397
|
+
return /* @__PURE__ */ jsxs12(
|
|
1398
|
+
"li",
|
|
1399
|
+
{
|
|
1400
|
+
className: cn(
|
|
1401
|
+
"flex items-center justify-between gap-2 rounded-lg border px-3 py-2 dark:border-gray-700",
|
|
1402
|
+
classNames?.item
|
|
1403
|
+
),
|
|
1404
|
+
children: [
|
|
1405
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: [
|
|
1406
|
+
canArchive ? /* @__PURE__ */ jsx15(
|
|
1407
|
+
"input",
|
|
1408
|
+
{
|
|
1409
|
+
type: "checkbox",
|
|
1410
|
+
checked: selectedIds.includes(document2.id),
|
|
1411
|
+
onChange: () => toggleSelected(document2.id),
|
|
1412
|
+
"aria-label": `${labels.download}: ${document2.filename}`,
|
|
1413
|
+
className: cn("shrink-0", classNames?.checkbox)
|
|
1414
|
+
}
|
|
1415
|
+
) : null,
|
|
1416
|
+
/* @__PURE__ */ jsx15(FileIcon, { filename: document2.filename, mimeType: document2.mimeType }),
|
|
1417
|
+
/* @__PURE__ */ jsxs12("div", { className: "min-w-0 flex-1", children: [
|
|
1418
|
+
/* @__PURE__ */ jsxs12(
|
|
1419
|
+
"div",
|
|
1420
|
+
{
|
|
1421
|
+
className: cn(
|
|
1422
|
+
"mb-0.5 flex items-center gap-1 text-[11px] font-medium",
|
|
1423
|
+
isFromCustomer ? "text-blue-600 dark:text-blue-400" : "text-emerald-600 dark:text-emerald-400",
|
|
1424
|
+
classNames?.sourceBadge
|
|
1425
|
+
),
|
|
1426
|
+
children: [
|
|
1427
|
+
/* @__PURE__ */ jsx15(SourceIcon, { size: 11 }),
|
|
1428
|
+
isFromCustomer ? labels.sourceFilterCustomer : labels.sourceFilterTeam
|
|
1429
|
+
]
|
|
1430
|
+
}
|
|
1431
|
+
),
|
|
1432
|
+
/* @__PURE__ */ jsx15(
|
|
1433
|
+
"div",
|
|
1434
|
+
{
|
|
1435
|
+
className: cn("truncate text-sm font-medium", classNames?.filename),
|
|
1436
|
+
title: document2.filename,
|
|
1437
|
+
children: document2.filename
|
|
1438
|
+
}
|
|
1439
|
+
),
|
|
1440
|
+
/* @__PURE__ */ jsxs12("div", { className: cn("text-xs text-gray-500 dark:text-gray-400", classNames?.meta), children: [
|
|
1441
|
+
formatDateTime(document2.linkedAt),
|
|
1442
|
+
" \xB7 ",
|
|
1443
|
+
formatFileSize(document2.sizeBytes)
|
|
1444
|
+
] })
|
|
1445
|
+
] })
|
|
1446
|
+
] }),
|
|
1447
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex shrink-0 gap-1", children: [
|
|
1448
|
+
/* @__PURE__ */ jsx15(
|
|
1449
|
+
"button",
|
|
1450
|
+
{
|
|
1451
|
+
type: "button",
|
|
1452
|
+
onClick: () => void handleOpen(document2.id, "inline"),
|
|
1453
|
+
title: labels.view,
|
|
1454
|
+
"aria-label": `${labels.view}: ${document2.filename}`,
|
|
1455
|
+
className: cn("cv-header-icon", classNames?.viewButton),
|
|
1456
|
+
children: /* @__PURE__ */ jsx15(Eye, { size: 14 })
|
|
1457
|
+
}
|
|
1458
|
+
),
|
|
1459
|
+
/* @__PURE__ */ jsx15(
|
|
1460
|
+
"button",
|
|
1461
|
+
{
|
|
1462
|
+
type: "button",
|
|
1463
|
+
onClick: () => void handleOpen(document2.id, "attachment"),
|
|
1464
|
+
title: labels.download,
|
|
1465
|
+
"aria-label": `${labels.download}: ${document2.filename}`,
|
|
1466
|
+
className: cn("cv-header-icon", classNames?.downloadButton),
|
|
1467
|
+
children: /* @__PURE__ */ jsx15(Download, { size: 14 })
|
|
1468
|
+
}
|
|
1469
|
+
)
|
|
1470
|
+
] })
|
|
1471
|
+
]
|
|
1472
|
+
},
|
|
1473
|
+
document2.id
|
|
1474
|
+
);
|
|
1475
|
+
}) }),
|
|
1476
|
+
total > perPage ? /* @__PURE__ */ jsxs12(
|
|
1477
|
+
"div",
|
|
1238
1478
|
{
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1479
|
+
className: cn(
|
|
1480
|
+
"mt-2 flex items-center justify-between border-t pt-2 text-xs dark:border-gray-700",
|
|
1481
|
+
classNames?.pagination
|
|
1482
|
+
),
|
|
1483
|
+
children: [
|
|
1484
|
+
/* @__PURE__ */ jsx15("span", { className: "text-gray-400", children: labels.total(total) }),
|
|
1485
|
+
/* @__PURE__ */ jsxs12("div", { className: "flex items-center gap-2", children: [
|
|
1486
|
+
/* @__PURE__ */ jsx15(
|
|
1487
|
+
"button",
|
|
1488
|
+
{
|
|
1489
|
+
type: "button",
|
|
1490
|
+
onClick: () => setPage(page - 1),
|
|
1491
|
+
disabled: page <= 1,
|
|
1492
|
+
"aria-label": labels.sortOldest,
|
|
1493
|
+
className: "cv-header-icon disabled:opacity-40",
|
|
1494
|
+
children: "\u2039"
|
|
1495
|
+
}
|
|
1496
|
+
),
|
|
1497
|
+
/* @__PURE__ */ jsx15("span", { className: "text-gray-500", children: labels.page(page, lastPage) }),
|
|
1498
|
+
/* @__PURE__ */ jsx15(
|
|
1499
|
+
"button",
|
|
1500
|
+
{
|
|
1501
|
+
type: "button",
|
|
1502
|
+
onClick: () => setPage(page + 1),
|
|
1503
|
+
disabled: page >= lastPage,
|
|
1504
|
+
"aria-label": labels.sortMostRecent,
|
|
1505
|
+
className: "cv-header-icon disabled:opacity-40",
|
|
1506
|
+
children: "\u203A"
|
|
1507
|
+
}
|
|
1508
|
+
)
|
|
1509
|
+
] })
|
|
1510
|
+
]
|
|
1244
1511
|
}
|
|
1245
|
-
)
|
|
1246
|
-
loading ? /* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-500", children: labels.loading }) : null,
|
|
1247
|
-
error ? /* @__PURE__ */ jsx15("p", { className: "text-xs text-red-600 dark:text-red-400", children: labels.failure }) : null,
|
|
1248
|
-
!loading && !error && documents.length === 0 ? /* @__PURE__ */ jsx15("p", { className: "text-xs text-gray-500", children: labels.empty }) : null,
|
|
1249
|
-
/* @__PURE__ */ jsx15("ul", { className: "space-y-1", children: documents.map((document2) => /* @__PURE__ */ jsxs12("li", { className: "flex items-center gap-2 text-xs", children: [
|
|
1250
|
-
/* @__PURE__ */ jsx15(FileIcon, { mimeType: document2.mimeType }),
|
|
1251
|
-
/* @__PURE__ */ jsx15("span", { className: "min-w-0 flex-1 truncate", children: document2.filename }),
|
|
1252
|
-
/* @__PURE__ */ jsx15("span", { className: "text-gray-500", children: formatFileSize(document2.sizeBytes) }),
|
|
1253
|
-
/* @__PURE__ */ jsx15("span", { className: "text-gray-500", children: formatTimestamp(document2.linkedAt) }),
|
|
1254
|
-
/* @__PURE__ */ jsx15("button", { type: "button", onClick: () => void handleDownload(document2.id), className: "cv-header-action", children: labels.download })
|
|
1255
|
-
] }, document2.id)) })
|
|
1512
|
+
) : null
|
|
1256
1513
|
] }) });
|
|
1257
1514
|
}
|
|
1258
1515
|
|
|
@@ -1293,52 +1550,66 @@ function downloadTextFile(filename, content) {
|
|
|
1293
1550
|
|
|
1294
1551
|
// src/useWaitingNotifications.ts
|
|
1295
1552
|
import { useState as useState9, useEffect as useEffect5, useRef as useRef4, useCallback as useCallback4 } from "react";
|
|
1296
|
-
|
|
1297
|
-
|
|
1553
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e4;
|
|
1554
|
+
var DEFAULT_LIMIT = 50;
|
|
1555
|
+
var DEFAULT_LABELS = {
|
|
1556
|
+
title: (conversation) => conversation.clientName ?? conversation.whatsappNumber,
|
|
1557
|
+
body: (conversation) => conversation.lastContent ?? "Nova mensagem"
|
|
1558
|
+
};
|
|
1559
|
+
function useWaitingNotifications(params) {
|
|
1560
|
+
const conversationsContext = useConversations();
|
|
1298
1561
|
const [conversations, setConversations] = useState9([]);
|
|
1299
1562
|
const previousUnreadMap = useRef4(/* @__PURE__ */ new Map());
|
|
1300
1563
|
const notifiedIds = useRef4(/* @__PURE__ */ new Set());
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1564
|
+
const isPollingRef = useRef4(false);
|
|
1565
|
+
const isEnabled = params?.enabled ?? true;
|
|
1566
|
+
const intervalMs = params?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
1567
|
+
const icon = params?.icon;
|
|
1568
|
+
const listParams = params?.params;
|
|
1569
|
+
const labelTitle = params?.labels?.title ?? DEFAULT_LABELS.title;
|
|
1570
|
+
const labelBody = params?.labels?.body ?? DEFAULT_LABELS.body;
|
|
1571
|
+
const refresh = useCallback4(async () => {
|
|
1572
|
+
if (!conversationsContext || isPollingRef.current) return;
|
|
1573
|
+
isPollingRef.current = true;
|
|
1303
1574
|
try {
|
|
1304
|
-
const result =
|
|
1575
|
+
const result = conversationsOf(
|
|
1576
|
+
await conversationsContext.api.fetchConversations({ limit: DEFAULT_LIMIT, ...listParams })
|
|
1577
|
+
);
|
|
1305
1578
|
setConversations(result);
|
|
1306
1579
|
const currentMap = /* @__PURE__ */ new Map();
|
|
1307
|
-
for (const
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
if (
|
|
1312
|
-
|
|
1313
|
-
if (
|
|
1314
|
-
|
|
1315
|
-
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "granted") {
|
|
1316
|
-
const title = conv.clientName ?? conv.whatsappNumber;
|
|
1317
|
-
const body = conv.lastContent ?? "Nova mensagem";
|
|
1318
|
-
new Notification(title, { body, icon: "/favicon.ico" });
|
|
1319
|
-
}
|
|
1580
|
+
for (const conversation of result) currentMap.set(conversation.id, conversation.unread);
|
|
1581
|
+
for (const conversation of result) {
|
|
1582
|
+
if (conversation.unread <= 0) continue;
|
|
1583
|
+
const previousUnread = previousUnreadMap.current.get(conversation.id) ?? 0;
|
|
1584
|
+
if (conversation.unread <= previousUnread || notifiedIds.current.has(conversation.id)) continue;
|
|
1585
|
+
notifiedIds.current.add(conversation.id);
|
|
1586
|
+
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "granted") {
|
|
1587
|
+
new Notification(labelTitle(conversation), { body: labelBody(conversation), ...icon ? { icon } : {} });
|
|
1320
1588
|
}
|
|
1321
1589
|
}
|
|
1322
1590
|
previousUnreadMap.current = currentMap;
|
|
1323
1591
|
} catch {
|
|
1592
|
+
} finally {
|
|
1593
|
+
isPollingRef.current = false;
|
|
1324
1594
|
}
|
|
1325
|
-
}, [
|
|
1595
|
+
}, [conversationsContext, listParams, icon, labelTitle, labelBody]);
|
|
1326
1596
|
useEffect5(() => {
|
|
1597
|
+
if (!isEnabled) return;
|
|
1327
1598
|
if (typeof window !== "undefined" && "Notification" in window && Notification.permission === "default") {
|
|
1328
1599
|
Notification.requestPermission();
|
|
1329
1600
|
}
|
|
1330
|
-
|
|
1331
|
-
const interval = setInterval(
|
|
1601
|
+
void refresh();
|
|
1602
|
+
const interval = setInterval(() => void refresh(), intervalMs);
|
|
1332
1603
|
return () => clearInterval(interval);
|
|
1333
|
-
}, [
|
|
1334
|
-
const unreadCount = conversations.reduce((sum,
|
|
1335
|
-
return { unreadCount, conversations };
|
|
1604
|
+
}, [refresh, isEnabled, intervalMs]);
|
|
1605
|
+
const unreadCount = conversations.reduce((sum, conversation) => sum + conversation.unread, 0);
|
|
1606
|
+
return { unreadCount, conversations, refresh };
|
|
1336
1607
|
}
|
|
1337
1608
|
|
|
1338
1609
|
// src/settings/WhatsAppTemplateSettingsForm.tsx
|
|
1339
1610
|
import { Plus, RefreshCw, Save, Trash2 } from "lucide-react";
|
|
1340
1611
|
import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
1341
|
-
var
|
|
1612
|
+
var DEFAULT_LABELS2 = {
|
|
1342
1613
|
sectionTitle: "WhatsApp",
|
|
1343
1614
|
sectionDescription: "Template usado para reabrir a janela de 24h com o cliente.",
|
|
1344
1615
|
templateLabel: "Template aprovado",
|
|
@@ -1372,7 +1643,7 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1372
1643
|
onSave,
|
|
1373
1644
|
labels: labelsOverride
|
|
1374
1645
|
}) {
|
|
1375
|
-
const labels = { ...
|
|
1646
|
+
const labels = { ...DEFAULT_LABELS2, ...labelsOverride };
|
|
1376
1647
|
const selected = templates.find((template) => template.name === selectedTemplateName);
|
|
1377
1648
|
function handleSelect(name) {
|
|
1378
1649
|
const template = templates.find((t) => t.name === name);
|
|
@@ -1534,7 +1805,7 @@ function WhatsAppTemplateSettingsForm({
|
|
|
1534
1805
|
// src/settings/WhatsAppCreateTemplateForm.tsx
|
|
1535
1806
|
import { SendHorizonal } from "lucide-react";
|
|
1536
1807
|
import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
1537
|
-
var
|
|
1808
|
+
var DEFAULT_LABELS3 = {
|
|
1538
1809
|
sectionTitle: "Criar novo template",
|
|
1539
1810
|
sectionDescription: "Envia um template para aprova\xE7\xE3o da Meta.",
|
|
1540
1811
|
nameLabel: "Nome do template",
|
|
@@ -1584,7 +1855,7 @@ function WhatsAppCreateTemplateForm({
|
|
|
1584
1855
|
labels: labelsOverride,
|
|
1585
1856
|
variableExamples = DEFAULT_VARIABLE_EXAMPLES
|
|
1586
1857
|
}) {
|
|
1587
|
-
const labels = { ...
|
|
1858
|
+
const labels = { ...DEFAULT_LABELS3, ...labelsOverride };
|
|
1588
1859
|
const detectedVariables = detectVariables(value.bodyText);
|
|
1589
1860
|
function set(key, next) {
|
|
1590
1861
|
onChange({ ...value, [key]: next });
|
|
@@ -1770,7 +2041,7 @@ function WhatsAppCreateTemplateForm({
|
|
|
1770
2041
|
// src/settings/WelcomeFarewellForm.tsx
|
|
1771
2042
|
import { LogOut, Mail, Save as Save2 } from "lucide-react";
|
|
1772
2043
|
import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
1773
|
-
var
|
|
2044
|
+
var DEFAULT_LABELS4 = {
|
|
1774
2045
|
sectionTitle: "Boas-vindas e encerramento",
|
|
1775
2046
|
welcomeMessage: "Mensagem de boas-vindas",
|
|
1776
2047
|
welcomeMessagePlaceholder: "Ol\xE1! Sou o assistente virtual da {empresa}...",
|
|
@@ -1796,7 +2067,7 @@ function WelcomeFarewellForm({
|
|
|
1796
2067
|
farewellPlaceholders = DEFAULT_FAREWELL_PLACEHOLDERS,
|
|
1797
2068
|
labels: labelsOverride
|
|
1798
2069
|
}) {
|
|
1799
|
-
const labels = { ...
|
|
2070
|
+
const labels = { ...DEFAULT_LABELS4, ...labelsOverride };
|
|
1800
2071
|
return /* @__PURE__ */ jsxs15("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1801
2072
|
/* @__PURE__ */ jsx18("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: /* @__PURE__ */ jsx18("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: labels.sectionTitle }) }),
|
|
1802
2073
|
/* @__PURE__ */ jsxs15("form", { onSubmit: onSave, className: "p-6 space-y-5", children: [
|
|
@@ -1899,7 +2170,7 @@ function WhatsAppTemplatesSettings({
|
|
|
1899
2170
|
// src/settings/TopicsForm.tsx
|
|
1900
2171
|
import { Megaphone, Save as Save3 } from "lucide-react";
|
|
1901
2172
|
import { jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
1902
|
-
var
|
|
2173
|
+
var DEFAULT_LABELS5 = {
|
|
1903
2174
|
sectionTitle: "T\xF3picos intermedi\xE1rios",
|
|
1904
2175
|
sectionDescription: "Mensagens autom\xE1ticas para assuntos espec\xEDficos, disparadas quando o cliente demonstra interesse.",
|
|
1905
2176
|
messagePlaceholder: "Digite a mensagem autom\xE1tica para este t\xF3pico...",
|
|
@@ -1915,7 +2186,7 @@ function TopicsForm({
|
|
|
1915
2186
|
saveSuccess = false,
|
|
1916
2187
|
labels: labelsOverride
|
|
1917
2188
|
}) {
|
|
1918
|
-
const labels = { ...
|
|
2189
|
+
const labels = { ...DEFAULT_LABELS5, ...labelsOverride };
|
|
1919
2190
|
return /* @__PURE__ */ jsxs17("section", { className: "bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden", children: [
|
|
1920
2191
|
/* @__PURE__ */ jsxs17("div", { className: "px-6 py-4 border-b border-gray-100 dark:border-gray-700", children: [
|
|
1921
2192
|
/* @__PURE__ */ jsxs17("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100 flex items-center gap-2", children: [
|
|
@@ -2022,11 +2293,15 @@ function useConversationList(params) {
|
|
|
2022
2293
|
throw new Error("useConversationList requires an ancestor <ConversationsProvider>");
|
|
2023
2294
|
}
|
|
2024
2295
|
const { api } = context;
|
|
2296
|
+
const filtersKey = JSON.stringify(params?.filters ?? {});
|
|
2025
2297
|
const { data, loading, error, refetch } = useAsyncResource(
|
|
2026
2298
|
() => api.fetchConversations(params),
|
|
2027
|
-
[params?.page, params?.limit, params?.waitingHuman, params?.search]
|
|
2299
|
+
[params?.page, params?.limit, params?.waitingHuman, params?.search, filtersKey]
|
|
2028
2300
|
);
|
|
2029
|
-
|
|
2301
|
+
if (data === void 0) {
|
|
2302
|
+
return { conversations: [], total: 0, loading, error, refetch };
|
|
2303
|
+
}
|
|
2304
|
+
return { conversations: conversationsOf(data), total: totalOf(data), loading, error, refetch };
|
|
2030
2305
|
}
|
|
2031
2306
|
|
|
2032
2307
|
// src/hooks/useConversationContext.ts
|
|
@@ -2072,6 +2347,38 @@ function useGlobalRealtime(onEvent) {
|
|
|
2072
2347
|
};
|
|
2073
2348
|
}, [context]);
|
|
2074
2349
|
}
|
|
2350
|
+
|
|
2351
|
+
// src/hooks/useConversationActions.ts
|
|
2352
|
+
import { useMemo as useMemo2 } from "react";
|
|
2353
|
+
function useConversationActions(conversationId) {
|
|
2354
|
+
const context = useConversations();
|
|
2355
|
+
if (!context) {
|
|
2356
|
+
throw new Error("useConversationActions requires an ancestor <ConversationsProvider>");
|
|
2357
|
+
}
|
|
2358
|
+
const { api } = context;
|
|
2359
|
+
return useMemo2(
|
|
2360
|
+
() => ({
|
|
2361
|
+
takeover: api.takeover ? () => api.takeover(conversationId) : void 0,
|
|
2362
|
+
release: api.release ? () => api.release(conversationId) : void 0,
|
|
2363
|
+
finalize: api.finalize ? () => api.finalize(conversationId) : void 0
|
|
2364
|
+
}),
|
|
2365
|
+
[api, conversationId]
|
|
2366
|
+
);
|
|
2367
|
+
}
|
|
2368
|
+
function useInboxActions() {
|
|
2369
|
+
const context = useConversations();
|
|
2370
|
+
if (!context) {
|
|
2371
|
+
throw new Error("useInboxActions requires an ancestor <ConversationsProvider>");
|
|
2372
|
+
}
|
|
2373
|
+
const { api } = context;
|
|
2374
|
+
return useMemo2(
|
|
2375
|
+
() => ({
|
|
2376
|
+
markAllRead: api.markAllRead ? () => api.markAllRead() : void 0,
|
|
2377
|
+
listTemplates: api.listTemplates ? () => api.listTemplates() : void 0
|
|
2378
|
+
}),
|
|
2379
|
+
[api]
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2075
2382
|
export {
|
|
2076
2383
|
AudioPlayer,
|
|
2077
2384
|
Avatar,
|
|
@@ -2127,15 +2434,18 @@ export {
|
|
|
2127
2434
|
contactFlag,
|
|
2128
2435
|
downloadTextFile,
|
|
2129
2436
|
formatContactHandle,
|
|
2437
|
+
formatDateTime,
|
|
2130
2438
|
formatFileSize,
|
|
2131
2439
|
formatPhone,
|
|
2132
2440
|
formatStalledFor,
|
|
2133
2441
|
formatTimestamp,
|
|
2134
2442
|
htmlToWA,
|
|
2443
|
+
isSameDay,
|
|
2135
2444
|
isWindowBlocking,
|
|
2136
2445
|
parseWhatsAppFormatting,
|
|
2137
2446
|
phoneInitials,
|
|
2138
2447
|
toast,
|
|
2448
|
+
useConversationActions,
|
|
2139
2449
|
useConversationContext,
|
|
2140
2450
|
useConversationDocuments,
|
|
2141
2451
|
useConversationList,
|
|
@@ -2145,6 +2455,7 @@ export {
|
|
|
2145
2455
|
useConversations,
|
|
2146
2456
|
useDarkMode,
|
|
2147
2457
|
useGlobalRealtime,
|
|
2458
|
+
useInboxActions,
|
|
2148
2459
|
useIsDarkTheme,
|
|
2149
2460
|
useIsNarrow,
|
|
2150
2461
|
useToast,
|
package/dist/preview/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { M as MessagePayload,
|
|
1
|
+
import { M as MessagePayload, k as ConversationSummary, i as ConversationEventSource, m as ConversationsApi, L as ListConversationsParams, j as ConversationPage, S as SSEProvider, g as ConversationDocument } from '../types-C2Yexi8A.js';
|
|
2
2
|
import * as react from 'react';
|
|
3
3
|
import { InteractiveReplyOption } from '@adatechnology/meta-whatsapp-contracts/testing';
|
|
4
4
|
|
|
@@ -81,7 +81,15 @@ type CreateMockConversationsApiParams = {
|
|
|
81
81
|
readonly latencyMs?: number;
|
|
82
82
|
readonly agentName?: string;
|
|
83
83
|
};
|
|
84
|
-
|
|
84
|
+
/**
|
|
85
|
+
* O mock satisfaz `ConversationsApi`, mas com o retorno de `fetchConversations` ESTREITADO para a
|
|
86
|
+
* forma paginada. Sem isto o contrato — que aceita array ou página — obrigaria todo consumidor do
|
|
87
|
+
* preview a desempacotar uma união que aqui nunca varia.
|
|
88
|
+
*/
|
|
89
|
+
type MockConversationsApi = Omit<ConversationsApi, 'fetchConversations'> & {
|
|
90
|
+
fetchConversations(params?: ListConversationsParams): Promise<ConversationPage>;
|
|
91
|
+
};
|
|
92
|
+
declare function createMockConversationsApi(params: CreateMockConversationsApiParams): MockConversationsApi;
|
|
85
93
|
|
|
86
94
|
/**
|
|
87
95
|
* `SSEProvider` servido pelo store em memória. Mesmo mapeamento de canal do servidor
|
|
@@ -101,6 +109,12 @@ declare function createMockSSEProvider(params: CreateMockSSEProviderParams): SSE
|
|
|
101
109
|
|
|
102
110
|
declare const PREVIEW_CONVERSATIONS: readonly ConversationSummary[];
|
|
103
111
|
declare const PREVIEW_MESSAGES: Readonly<Record<string, readonly MessagePayload[]>>;
|
|
112
|
+
/**
|
|
113
|
+
* A biblioteca de arquivos da conversa, como o backend a devolveria. Deriva das mensagens de
|
|
114
|
+
* documento acima em vez de repetir os dados: fixture duplicada divergiria na primeira edição, e o
|
|
115
|
+
* painel passaria a mostrar arquivo que a thread não tem.
|
|
116
|
+
*/
|
|
117
|
+
declare const PREVIEW_DOCUMENTS: Readonly<Record<string, readonly ConversationDocument[]>>;
|
|
104
118
|
|
|
105
119
|
/**
|
|
106
120
|
* Cliente que entrega mensagens do preview no webhook real, assinadas com HMAC — a mesma validação
|
|
@@ -169,4 +183,4 @@ type StartPreviewScriptParams = {
|
|
|
169
183
|
};
|
|
170
184
|
declare function startPreviewScript(params: StartPreviewScriptParams): () => void;
|
|
171
185
|
|
|
172
|
-
export { type AppendMessageParams, ConversationPreview, type ConversationPreviewProps, type CreateMockConversationsApiParams, type CreateMockSSEProviderParams, type CreatePreviewStoreParams, type CreatePreviewWebhookClientParams, DEFAULT_PREVIEW_SCRIPT, GLOBAL_CHANNEL, type ListConversationsFilters, type MockEventSource, PREVIEW_CONVERSATIONS, PREVIEW_MESSAGES, type PreviewEmission, PreviewInProductionError, type PreviewScriptStep, type PreviewStore, type PreviewStoreListener, type PreviewWebhookClient, PreviewWebhookRejectedError, type SetModeParams, type StartPreviewScriptParams, assertPreviewEnvironment, conversationChannel, createMockConversationsApi, createMockEventSource, createMockSSEProvider, createPreviewStore, createPreviewWebhookClient, startPreviewScript };
|
|
186
|
+
export { type AppendMessageParams, ConversationPreview, type ConversationPreviewProps, type CreateMockConversationsApiParams, type CreateMockSSEProviderParams, type CreatePreviewStoreParams, type CreatePreviewWebhookClientParams, DEFAULT_PREVIEW_SCRIPT, GLOBAL_CHANNEL, type ListConversationsFilters, type MockEventSource, PREVIEW_CONVERSATIONS, PREVIEW_DOCUMENTS, PREVIEW_MESSAGES, type PreviewEmission, PreviewInProductionError, type PreviewScriptStep, type PreviewStore, type PreviewStoreListener, type PreviewWebhookClient, PreviewWebhookRejectedError, type SetModeParams, type StartPreviewScriptParams, assertPreviewEnvironment, conversationChannel, createMockConversationsApi, createMockEventSource, createMockSSEProvider, createPreviewStore, createPreviewWebhookClient, startPreviewScript };
|