@fayz-ai/plugin-conversations 0.13.0 → 0.14.0
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/components/InboxWatcher.d.ts +11 -0
- package/dist/components/InboxWatcher.d.ts.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +310 -94
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/ConversationList.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function InboxWatcher({ label, route, section, position }: {
|
|
2
|
+
/** O rótulo da entrada de menu que recebe a conta. */
|
|
3
|
+
label: string;
|
|
4
|
+
route?: string;
|
|
5
|
+
/** Onde a entrada original vive. A contribuição tem de cair no MESMO lugar,
|
|
6
|
+
* senão ela não substitui a entrada — acrescenta uma segunda, com a conta,
|
|
7
|
+
* ao lado da que já lá está. */
|
|
8
|
+
section?: string;
|
|
9
|
+
position?: number;
|
|
10
|
+
}): null;
|
|
11
|
+
//# sourceMappingURL=InboxWatcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InboxWatcher.d.ts","sourceRoot":"","sources":["../../src/components/InboxWatcher.tsx"],"names":[],"mappings":"AAiEA,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,KAAwB,EAAE,OAAgB,EAAE,QAAY,EAAE,EAAE;IAChG,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;qCAEiC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,QA8DA"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAEvF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAGlD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAuBzD,MAAM,WAAW,0BAA0B;IACzC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IAEpC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oFAAoF;IACpF,aAAa,CAAC,EAAE,YAAY,CAAA;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,SAAS,CAAA;CAC7B;AA2BD,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,0BAA0B,GAAG,cAAc,CA+Q9F;AAED,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,YAAY,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAA;AACxG,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,EAAE,+BAA+B,EAAE,KAAK,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAC3F,OAAO,EACL,mCAAmC,EACnC,KAAK,2BAA2B,GACjC,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,oBAAoB,EACpB,uBAAuB,EACvB,eAAe,EACf,0BAA0B,EAC1B,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,0BAA0B,CAAA;AACjC,OAAO,EACL,qBAAqB,EACrB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,4BAA4B,GAClC,MAAM,iBAAiB,CAAA;AAGxB,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,oCAAoC,CAAA;AAM3C,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAC1F,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvH,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import * as
|
|
2
|
-
import
|
|
1
|
+
import * as React10 from 'react';
|
|
2
|
+
import React10__default, { useState, useRef, useEffect, useCallback, useMemo } from 'react';
|
|
3
3
|
import { Loader2, MessageSquare, AlertTriangle, Pause, Play, Send, RefreshCw, Plus, Clock, CheckCircle2, Check, ShieldCheck, ShieldAlert, QrCode, Zap, ExternalLink, SquarePen, Globe, Mail, Instagram, Phone, Search, Inbox, ChevronLeft, Archive, PanelRight, X, User, MapPin, Tag, StickyNote, Link2, MoreVertical, CheckCheck, Snowflake, Sun, Flame, Building2 } from 'lucide-react';
|
|
4
4
|
import { toast, Button, Input, defineKpiWidget, defineTableWidget, PageHeaderActions, cn, Badge, Skeleton, KpiCard, TableWidget } from '@fayz-ai/ui';
|
|
5
5
|
import { createConnectionStore, connectionRuns, connectionStatus, getActiveTenantId, getSupabaseClientOptional, useActiveTenantId, registerTranslations, useTranslation, countByTenant, CONNECTOR_RUNTIME_TOKEN_HEADER, connectorRuntimeToken, errorMessage } from '@fayz-ai/core';
|
|
6
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
|
+
import { SettingsGroup, PluginSettingsPanel, useNavContribution, PermissionGate, useLimitGuard, RightRailPage, ContactPicker, invalidateLimit } from '@fayz-ai/admin';
|
|
7
8
|
import { useStore } from 'zustand';
|
|
8
|
-
import { SettingsGroup, PluginSettingsPanel, PermissionGate, useLimitGuard, RightRailPage, ContactPicker, invalidateLimit } from '@fayz-ai/admin';
|
|
9
9
|
import { createStore } from 'zustand/vanilla';
|
|
10
10
|
|
|
11
11
|
// src/index.ts
|
|
@@ -211,12 +211,12 @@ function Bar({ c }) {
|
|
|
211
211
|
] });
|
|
212
212
|
}
|
|
213
213
|
function CampaignsView() {
|
|
214
|
-
const [rows, setRows] =
|
|
215
|
-
const [templates, setTemplates] =
|
|
216
|
-
const [providers, setProviders] =
|
|
217
|
-
const [running, setRunning] =
|
|
218
|
-
const stop =
|
|
219
|
-
const load =
|
|
214
|
+
const [rows, setRows] = React10__default.useState(null);
|
|
215
|
+
const [templates, setTemplates] = React10__default.useState([]);
|
|
216
|
+
const [providers, setProviders] = React10__default.useState([]);
|
|
217
|
+
const [running, setRunning] = React10__default.useState(null);
|
|
218
|
+
const stop = React10__default.useRef(false);
|
|
219
|
+
const load = React10__default.useCallback(async () => {
|
|
220
220
|
try {
|
|
221
221
|
const [c, t, p] = await Promise.all([
|
|
222
222
|
campaignsApi.list(),
|
|
@@ -231,7 +231,7 @@ function CampaignsView() {
|
|
|
231
231
|
setRows([]);
|
|
232
232
|
}
|
|
233
233
|
}, []);
|
|
234
|
-
|
|
234
|
+
React10__default.useEffect(() => {
|
|
235
235
|
void load();
|
|
236
236
|
}, [load]);
|
|
237
237
|
const active = providers.find((p) => p.isActive);
|
|
@@ -376,6 +376,101 @@ function CampaignsView() {
|
|
|
376
376
|
] })
|
|
377
377
|
] });
|
|
378
378
|
}
|
|
379
|
+
|
|
380
|
+
// src/data/tables.ts
|
|
381
|
+
var T = {
|
|
382
|
+
conversations: "plg_conversations",
|
|
383
|
+
messages: "plg_conversation_messages",
|
|
384
|
+
channels: "plg_conversations_channels"
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
// src/types.ts
|
|
388
|
+
var CHANNEL_LABELS = {
|
|
389
|
+
sms: "SMS",
|
|
390
|
+
whatsapp: "WhatsApp",
|
|
391
|
+
instagram: "Instagram",
|
|
392
|
+
email: "Email",
|
|
393
|
+
webchat: "Web Chat"
|
|
394
|
+
};
|
|
395
|
+
function isWaitingOnUs(conversation) {
|
|
396
|
+
if (conversation.status !== "open") return false;
|
|
397
|
+
if (conversation.lastMessageDirection) return conversation.lastMessageDirection === "inbound";
|
|
398
|
+
return conversation.unreadCount > 0;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/components/InboxWatcher.tsx
|
|
402
|
+
function tocar() {
|
|
403
|
+
try {
|
|
404
|
+
const Ctx = window.AudioContext ?? window.webkitAudioContext;
|
|
405
|
+
if (!Ctx) return;
|
|
406
|
+
const ctx = new Ctx();
|
|
407
|
+
const agora = ctx.currentTime;
|
|
408
|
+
for (const [freq, atraso] of [[880, 0], [1320, 0.09]]) {
|
|
409
|
+
const osc = ctx.createOscillator();
|
|
410
|
+
const vol = ctx.createGain();
|
|
411
|
+
osc.type = "sine";
|
|
412
|
+
osc.frequency.value = freq;
|
|
413
|
+
vol.gain.setValueAtTime(1e-4, agora + atraso);
|
|
414
|
+
vol.gain.exponentialRampToValueAtTime(0.18, agora + atraso + 0.02);
|
|
415
|
+
vol.gain.exponentialRampToValueAtTime(1e-4, agora + atraso + 0.28);
|
|
416
|
+
osc.connect(vol);
|
|
417
|
+
vol.connect(ctx.destination);
|
|
418
|
+
osc.start(agora + atraso);
|
|
419
|
+
osc.stop(agora + atraso + 0.3);
|
|
420
|
+
}
|
|
421
|
+
setTimeout(() => void ctx.close().catch(() => {
|
|
422
|
+
}), 900);
|
|
423
|
+
} catch {
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
function InboxWatcher({ label, route = "/conversations", section = "main", position = 1 }) {
|
|
427
|
+
const [naoLidas, setNaoLidas] = React10.useState(0);
|
|
428
|
+
const podeTocar = React10.useRef(false);
|
|
429
|
+
React10.useEffect(() => {
|
|
430
|
+
const liberar = () => {
|
|
431
|
+
podeTocar.current = true;
|
|
432
|
+
};
|
|
433
|
+
for (const ev of ["pointerdown", "keydown", "touchstart"]) {
|
|
434
|
+
window.addEventListener(ev, liberar, { once: true, passive: true });
|
|
435
|
+
}
|
|
436
|
+
return () => {
|
|
437
|
+
for (const ev of ["pointerdown", "keydown", "touchstart"]) window.removeEventListener(ev, liberar);
|
|
438
|
+
};
|
|
439
|
+
}, []);
|
|
440
|
+
const contar = React10.useCallback(async () => {
|
|
441
|
+
const supabase = getSupabaseClientOptional();
|
|
442
|
+
if (!supabase) return;
|
|
443
|
+
const { data } = await supabase.from(T.conversations).select("id, status, last_message_direction, unread_count").eq("status", "open");
|
|
444
|
+
if (!data) return;
|
|
445
|
+
const n = data.filter((r) => isWaitingOnUs({
|
|
446
|
+
status: r.status,
|
|
447
|
+
lastMessageDirection: r.last_message_direction ?? void 0,
|
|
448
|
+
unreadCount: r.unread_count ?? 0
|
|
449
|
+
})).length;
|
|
450
|
+
setNaoLidas(n);
|
|
451
|
+
}, []);
|
|
452
|
+
React10.useEffect(() => {
|
|
453
|
+
void contar();
|
|
454
|
+
}, [contar]);
|
|
455
|
+
React10.useEffect(() => {
|
|
456
|
+
const supabase = getSupabaseClientOptional();
|
|
457
|
+
if (!supabase?.channel) return;
|
|
458
|
+
const canal = supabase.channel("conversas:entrada").on(
|
|
459
|
+
"postgres_changes",
|
|
460
|
+
{ event: "INSERT", schema: "public", table: T.messages },
|
|
461
|
+
(payload) => {
|
|
462
|
+
if (payload?.new?.direction !== "inbound") return;
|
|
463
|
+
void contar();
|
|
464
|
+
if (podeTocar.current) tocar();
|
|
465
|
+
}
|
|
466
|
+
).subscribe();
|
|
467
|
+
return () => {
|
|
468
|
+
void supabase.removeChannel?.(canal);
|
|
469
|
+
};
|
|
470
|
+
}, [contar]);
|
|
471
|
+
useNavContribution("conversations:unread", naoLidas > 0 ? [{ id: "conversations", label, route, badge: naoLidas, section, position }] : []);
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
379
474
|
var STATUS_UI = {
|
|
380
475
|
approved: { icon: CheckCircle2, cls: "text-success", label: "aprovado" },
|
|
381
476
|
submitted: { icon: Clock, cls: "text-warning", label: "em an\xE1lise" },
|
|
@@ -390,14 +485,14 @@ function slots(body) {
|
|
|
390
485
|
return found.size;
|
|
391
486
|
}
|
|
392
487
|
function TemplatesView() {
|
|
393
|
-
const [mine, setMine] =
|
|
394
|
-
const [theirs, setTheirs] =
|
|
395
|
-
const [providers, setProviders] =
|
|
396
|
-
const [busy, setBusy] =
|
|
397
|
-
const [draft, setDraft] =
|
|
488
|
+
const [mine, setMine] = React10__default.useState(null);
|
|
489
|
+
const [theirs, setTheirs] = React10__default.useState([]);
|
|
490
|
+
const [providers, setProviders] = React10__default.useState([]);
|
|
491
|
+
const [busy, setBusy] = React10__default.useState(null);
|
|
492
|
+
const [draft, setDraft] = React10__default.useState({ name: "", body: "", variables: "" });
|
|
398
493
|
const active = providers.find((p) => p.isActive);
|
|
399
494
|
const supportsTemplates = active?.capabilities?.templates !== false;
|
|
400
|
-
const load =
|
|
495
|
+
const load = React10__default.useCallback(async () => {
|
|
401
496
|
try {
|
|
402
497
|
const [m, p] = await Promise.all([campaignsApi.templates(), providersApi.list()]);
|
|
403
498
|
setMine(m);
|
|
@@ -407,7 +502,7 @@ function TemplatesView() {
|
|
|
407
502
|
setMine([]);
|
|
408
503
|
}
|
|
409
504
|
}, []);
|
|
410
|
-
|
|
505
|
+
React10__default.useEffect(() => {
|
|
411
506
|
void load();
|
|
412
507
|
}, [load]);
|
|
413
508
|
const sync = async () => {
|
|
@@ -598,8 +693,8 @@ function TemplatesView() {
|
|
|
598
693
|
var DEFAULT_CONVERSATIONS_CONFIG = {
|
|
599
694
|
contactKind: "contact"
|
|
600
695
|
};
|
|
601
|
-
var StoreContext =
|
|
602
|
-
var ConfigContext =
|
|
696
|
+
var StoreContext = React10__default.createContext(null);
|
|
697
|
+
var ConfigContext = React10__default.createContext(DEFAULT_CONVERSATIONS_CONFIG);
|
|
603
698
|
function ConversationsContextProvider({
|
|
604
699
|
store: store2,
|
|
605
700
|
config = DEFAULT_CONVERSATIONS_CONFIG,
|
|
@@ -608,29 +703,13 @@ function ConversationsContextProvider({
|
|
|
608
703
|
return /* @__PURE__ */ jsx(StoreContext.Provider, { value: store2, children: /* @__PURE__ */ jsx(ConfigContext.Provider, { value: config, children }) });
|
|
609
704
|
}
|
|
610
705
|
function useConversationsStore(selector) {
|
|
611
|
-
const store2 =
|
|
706
|
+
const store2 = React10__default.useContext(StoreContext);
|
|
612
707
|
if (!store2) throw new Error("useConversationsStore must be used within ConversationsPage");
|
|
613
708
|
return useStore(store2, selector);
|
|
614
709
|
}
|
|
615
710
|
function useConversationsConfig() {
|
|
616
|
-
return
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
// src/types.ts
|
|
620
|
-
var CHANNEL_LABELS = {
|
|
621
|
-
sms: "SMS",
|
|
622
|
-
whatsapp: "WhatsApp",
|
|
623
|
-
instagram: "Instagram",
|
|
624
|
-
email: "Email",
|
|
625
|
-
webchat: "Web Chat"
|
|
626
|
-
};
|
|
627
|
-
function isWaitingOnUs(conversation) {
|
|
628
|
-
if (conversation.status !== "open") return false;
|
|
629
|
-
if (conversation.lastMessageDirection) return conversation.lastMessageDirection === "inbound";
|
|
630
|
-
return conversation.unreadCount > 0;
|
|
711
|
+
return React10__default.useContext(ConfigContext);
|
|
631
712
|
}
|
|
632
|
-
|
|
633
|
-
// src/lib/channel.ts
|
|
634
713
|
var CHANNEL_ICON = {
|
|
635
714
|
sms: Phone,
|
|
636
715
|
whatsapp: MessageSquare,
|
|
@@ -646,10 +725,10 @@ var CHANNEL_ACCENT = {
|
|
|
646
725
|
webchat: { color: "#f59e0b", badge: "bg-[#f59e0b]/12 text-[#b45309] dark:text-[#fcd34d]" }
|
|
647
726
|
};
|
|
648
727
|
function useMediaQuery(query) {
|
|
649
|
-
const [matches, setMatches] =
|
|
728
|
+
const [matches, setMatches] = React10__default.useState(
|
|
650
729
|
() => typeof window !== "undefined" && window.matchMedia(query).matches
|
|
651
730
|
);
|
|
652
|
-
|
|
731
|
+
React10__default.useEffect(() => {
|
|
653
732
|
if (typeof window === "undefined") return;
|
|
654
733
|
const mql = window.matchMedia(query);
|
|
655
734
|
const onChange = () => setMatches(mql.matches);
|
|
@@ -780,7 +859,7 @@ function ConversationList({ className }) {
|
|
|
780
859
|
] }),
|
|
781
860
|
conversations.map((c) => {
|
|
782
861
|
const active = c.id === selectedId;
|
|
783
|
-
const unread = c
|
|
862
|
+
const unread = isWaitingOnUs(c);
|
|
784
863
|
return /* @__PURE__ */ jsxs(
|
|
785
864
|
"button",
|
|
786
865
|
{
|
|
@@ -813,7 +892,7 @@ function ConversationList({ className }) {
|
|
|
813
892
|
] }),
|
|
814
893
|
/* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-center justify-between gap-2", children: [
|
|
815
894
|
/* @__PURE__ */ jsx("span", { className: cn("truncate text-xs", unread ? "text-foreground" : "text-muted-foreground"), children: c.lastMessagePreview }),
|
|
816
|
-
unread && /* @__PURE__ */ jsx("span", { className: "flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full bg-
|
|
895
|
+
unread && /* @__PURE__ */ jsx("span", { className: "flex h-[18px] min-w-[18px] shrink-0 items-center justify-center rounded-full bg-destructive px-1.5 text-[10px] font-semibold text-destructive-foreground", children: c.unreadCount > 0 ? c.unreadCount : "\xB7" })
|
|
817
896
|
] })
|
|
818
897
|
] })
|
|
819
898
|
]
|
|
@@ -825,9 +904,9 @@ function ConversationList({ className }) {
|
|
|
825
904
|
] });
|
|
826
905
|
}
|
|
827
906
|
function useSendWindow(conversationId) {
|
|
828
|
-
const [row, setRow] =
|
|
829
|
-
const [providers, setProviders] =
|
|
830
|
-
|
|
907
|
+
const [row, setRow] = React10__default.useState(null);
|
|
908
|
+
const [providers, setProviders] = React10__default.useState(null);
|
|
909
|
+
React10__default.useEffect(() => {
|
|
831
910
|
let alive = true;
|
|
832
911
|
providersApi.list().then((p) => {
|
|
833
912
|
if (alive) setProviders(p);
|
|
@@ -838,7 +917,7 @@ function useSendWindow(conversationId) {
|
|
|
838
917
|
alive = false;
|
|
839
918
|
};
|
|
840
919
|
}, []);
|
|
841
|
-
|
|
920
|
+
React10__default.useEffect(() => {
|
|
842
921
|
let alive = true;
|
|
843
922
|
if (!conversationId) {
|
|
844
923
|
setRow(null);
|
|
@@ -859,7 +938,7 @@ function useSendWindow(conversationId) {
|
|
|
859
938
|
};
|
|
860
939
|
}, [conversationId]);
|
|
861
940
|
const provider = (providers ?? []).find((p) => p.isActive);
|
|
862
|
-
const mode =
|
|
941
|
+
const mode = React10__default.useMemo(() => {
|
|
863
942
|
if (!provider) return { kind: "blocked", reason: "no_provider" };
|
|
864
943
|
if (!provider.capabilities?.requires_template) {
|
|
865
944
|
return { kind: "free", until: null, minutesLeft: null };
|
|
@@ -934,10 +1013,10 @@ function DeliveryMark({ m }) {
|
|
|
934
1013
|
] });
|
|
935
1014
|
}
|
|
936
1015
|
function MessageMenu({ m }) {
|
|
937
|
-
const [open, setOpen] =
|
|
938
|
-
const [detail, setDetail] =
|
|
939
|
-
const [loading, setLoading] =
|
|
940
|
-
const [err, setErr] =
|
|
1016
|
+
const [open, setOpen] = React10__default.useState(false);
|
|
1017
|
+
const [detail, setDetail] = React10__default.useState(null);
|
|
1018
|
+
const [loading, setLoading] = React10__default.useState(false);
|
|
1019
|
+
const [err, setErr] = React10__default.useState(null);
|
|
941
1020
|
async function load() {
|
|
942
1021
|
setOpen(true);
|
|
943
1022
|
if (!m.providerMessageId) return;
|
|
@@ -1028,14 +1107,14 @@ function buildRows(messages) {
|
|
|
1028
1107
|
function MessageThread({ selected, onTogglePanel, panelOpen, onBack, className }) {
|
|
1029
1108
|
const t = useTranslation();
|
|
1030
1109
|
const { messages, sending, send, setStatus, refreshMessages } = useConversationsStore((s) => s);
|
|
1031
|
-
const [draft, setDraft] =
|
|
1110
|
+
const [draft, setDraft] = React10__default.useState("");
|
|
1032
1111
|
const win = useSendWindow(selected.id);
|
|
1033
1112
|
const canType = win.mode.kind === "free";
|
|
1034
|
-
const threadRef =
|
|
1035
|
-
|
|
1113
|
+
const threadRef = React10__default.useRef(null);
|
|
1114
|
+
React10__default.useEffect(() => {
|
|
1036
1115
|
threadRef.current?.scrollTo({ top: threadRef.current.scrollHeight, behavior: "smooth" });
|
|
1037
1116
|
}, [messages.length, selected.id]);
|
|
1038
|
-
|
|
1117
|
+
React10__default.useEffect(() => {
|
|
1039
1118
|
if (!selected.id) return;
|
|
1040
1119
|
let timer = null;
|
|
1041
1120
|
const start = () => {
|
|
@@ -1064,7 +1143,7 @@ function MessageThread({ selected, onTogglePanel, panelOpen, onBack, className }
|
|
|
1064
1143
|
await send(body);
|
|
1065
1144
|
}
|
|
1066
1145
|
const accent = CHANNEL_ACCENT[selected.channel];
|
|
1067
|
-
const rows =
|
|
1146
|
+
const rows = React10__default.useMemo(() => buildRows(messages), [messages]);
|
|
1068
1147
|
return /* @__PURE__ */ jsxs("section", { className: cn("flex min-w-0 flex-1 flex-col bg-muted/20", className), children: [
|
|
1069
1148
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2 border-b border-border bg-card px-3 py-2.5 md:px-5", children: [
|
|
1070
1149
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 items-center gap-2 md:gap-3", children: [
|
|
@@ -1176,8 +1255,8 @@ var TEMP = {
|
|
|
1176
1255
|
cold: { icon: Snowflake, cls: "bg-info-soft text-info-soft-foreground", label: "Frio" }
|
|
1177
1256
|
};
|
|
1178
1257
|
function LeadCard({ personId, handle }) {
|
|
1179
|
-
const [lead, setLead] =
|
|
1180
|
-
|
|
1258
|
+
const [lead, setLead] = React10__default.useState(void 0);
|
|
1259
|
+
React10__default.useEffect(() => {
|
|
1181
1260
|
let alive = true;
|
|
1182
1261
|
const sb = getSupabaseClientOptional();
|
|
1183
1262
|
const tenantId = getActiveTenantId();
|
|
@@ -1340,14 +1419,14 @@ function NewConversationPanel({
|
|
|
1340
1419
|
const create = useConversationsStore((s) => s.create);
|
|
1341
1420
|
const config = useConversationsConfig();
|
|
1342
1421
|
const guardConversations = useLimitGuard("conversations_month");
|
|
1343
|
-
const [channel, setChannel] =
|
|
1344
|
-
const [contact, setContact] =
|
|
1345
|
-
const [typedHandle, setTypedHandle] =
|
|
1346
|
-
const [creatingContact, setCreatingContact] =
|
|
1347
|
-
const [firstMessage, setFirstMessage] =
|
|
1348
|
-
const [submitting, setSubmitting] =
|
|
1349
|
-
const [pickerKey, setPickerKey] =
|
|
1350
|
-
|
|
1422
|
+
const [channel, setChannel] = React10__default.useState("whatsapp");
|
|
1423
|
+
const [contact, setContact] = React10__default.useState(null);
|
|
1424
|
+
const [typedHandle, setTypedHandle] = React10__default.useState("");
|
|
1425
|
+
const [creatingContact, setCreatingContact] = React10__default.useState(false);
|
|
1426
|
+
const [firstMessage, setFirstMessage] = React10__default.useState("");
|
|
1427
|
+
const [submitting, setSubmitting] = React10__default.useState(false);
|
|
1428
|
+
const [pickerKey, setPickerKey] = React10__default.useState(0);
|
|
1429
|
+
React10__default.useEffect(() => {
|
|
1351
1430
|
if (open) {
|
|
1352
1431
|
setChannel("whatsapp");
|
|
1353
1432
|
setContact(null);
|
|
@@ -1465,10 +1544,10 @@ function InboxView() {
|
|
|
1465
1544
|
const t = useTranslation();
|
|
1466
1545
|
const { conversations, selectedId, deselect } = useConversationsStore((s) => s);
|
|
1467
1546
|
const isWidePanel = useMediaQuery("(min-width: 1280px)");
|
|
1468
|
-
const [panelOpen, setPanelOpen] =
|
|
1469
|
-
const [newOpen, setNewOpen] =
|
|
1547
|
+
const [panelOpen, setPanelOpen] = React10__default.useState(false);
|
|
1548
|
+
const [newOpen, setNewOpen] = React10__default.useState(false);
|
|
1470
1549
|
const selected = conversations.find((c) => c.id === selectedId) ?? null;
|
|
1471
|
-
|
|
1550
|
+
React10__default.useEffect(() => {
|
|
1472
1551
|
setPanelOpen(isWidePanel);
|
|
1473
1552
|
}, [isWidePanel]);
|
|
1474
1553
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1541,7 +1620,7 @@ function requestedContact() {
|
|
|
1541
1620
|
}
|
|
1542
1621
|
}
|
|
1543
1622
|
function ConversationsPage({ store: store2, config }) {
|
|
1544
|
-
|
|
1623
|
+
React10__default.useEffect(() => {
|
|
1545
1624
|
const wanted = requestedContact();
|
|
1546
1625
|
if (!wanted) {
|
|
1547
1626
|
void store2.getState().load();
|
|
@@ -1828,15 +1907,6 @@ function createMockConversationsProvider(config) {
|
|
|
1828
1907
|
}
|
|
1829
1908
|
};
|
|
1830
1909
|
}
|
|
1831
|
-
|
|
1832
|
-
// src/data/tables.ts
|
|
1833
|
-
var T = {
|
|
1834
|
-
conversations: "plg_conversations",
|
|
1835
|
-
messages: "plg_conversation_messages",
|
|
1836
|
-
channels: "plg_conversations_channels"
|
|
1837
|
-
};
|
|
1838
|
-
|
|
1839
|
-
// src/data/supabase.ts
|
|
1840
1910
|
function mapConversation(r) {
|
|
1841
1911
|
return {
|
|
1842
1912
|
id: String(r.id),
|
|
@@ -2596,22 +2666,22 @@ function NumberRow({ n }) {
|
|
|
2596
2666
|
] });
|
|
2597
2667
|
}
|
|
2598
2668
|
function WhatsAppProviders() {
|
|
2599
|
-
const [rows, setRows] =
|
|
2600
|
-
const [draft, setDraft] =
|
|
2601
|
-
const [busy, setBusy] =
|
|
2602
|
-
const [numbers, setNumbers] =
|
|
2603
|
-
const [qr, setQr] =
|
|
2604
|
-
const load =
|
|
2669
|
+
const [rows, setRows] = React10__default.useState(null);
|
|
2670
|
+
const [draft, setDraft] = React10__default.useState({});
|
|
2671
|
+
const [busy, setBusy] = React10__default.useState(null);
|
|
2672
|
+
const [numbers, setNumbers] = React10__default.useState({});
|
|
2673
|
+
const [qr, setQr] = React10__default.useState(null);
|
|
2674
|
+
const load = React10__default.useCallback(async () => {
|
|
2605
2675
|
try {
|
|
2606
2676
|
setRows(await providersApi.list());
|
|
2607
2677
|
} catch {
|
|
2608
2678
|
setRows([]);
|
|
2609
2679
|
}
|
|
2610
2680
|
}, []);
|
|
2611
|
-
|
|
2681
|
+
React10__default.useEffect(() => {
|
|
2612
2682
|
void load();
|
|
2613
2683
|
}, [load]);
|
|
2614
|
-
const byId =
|
|
2684
|
+
const byId = React10__default.useMemo(
|
|
2615
2685
|
() => Object.fromEntries((rows ?? []).map((r) => [r.provider, r])),
|
|
2616
2686
|
[rows]
|
|
2617
2687
|
);
|
|
@@ -2846,9 +2916,9 @@ function ChannelRow({ account }) {
|
|
|
2846
2916
|
}
|
|
2847
2917
|
function ConversationsGeneralSettings() {
|
|
2848
2918
|
const t = useTranslation();
|
|
2849
|
-
const [channels, setChannels] =
|
|
2850
|
-
const [failed, setFailed] =
|
|
2851
|
-
|
|
2919
|
+
const [channels, setChannels] = React10.useState(null);
|
|
2920
|
+
const [failed, setFailed] = React10.useState(false);
|
|
2921
|
+
React10.useEffect(() => {
|
|
2852
2922
|
let cancelled = false;
|
|
2853
2923
|
listMessagingChannels().then((rows) => {
|
|
2854
2924
|
if (!cancelled) setChannels(rows);
|
|
@@ -4803,13 +4873,140 @@ BEGIN
|
|
|
4803
4873
|
ON public.plg_crm_activities (tenant_id, lead_id, activity_type, completed_at DESC);
|
|
4804
4874
|
END $$;
|
|
4805
4875
|
`;
|
|
4876
|
+
var MIGRATION_006_A_CAIXA_AVISA_QUE_CHEGOU = `-- ---------------------------------------------------------------------------
|
|
4877
|
+
-- 006_a_caixa_avisa_que_chegou.sql
|
|
4878
|
+
--
|
|
4879
|
+
-- \u2500\u2500 o sil\xEAncio, e de onde ele vinha \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4880
|
+
--
|
|
4881
|
+
-- A caixa recebia mensagens e ningu\xE9m era avisado. Sem som, sem contagem no
|
|
4882
|
+
-- menu, sem destaque na lista \u2014 e a suspeita natural \xE9 que falta a tela que
|
|
4883
|
+
-- avisa. N\xE3o faltava: o evento nunca sa\xEDa do banco.
|
|
4884
|
+
--
|
|
4885
|
+
-- \`plg_conversation_messages\` e \`plg_conversations\` n\xE3o estavam em
|
|
4886
|
+
-- \`supabase_realtime\`. A publica\xE7\xE3o existe (\`puballtables = false\`, ou seja,
|
|
4887
|
+
-- cada tabela entra nomeada) e nenhuma das duas fora nomeada. Qualquer cliente
|
|
4888
|
+
-- que assinasse \`postgres_changes\` sobre elas ficaria subscrito para sempre a
|
|
4889
|
+
-- um canal por onde nada passa \u2014 e subscrever com sucesso a um canal mudo \xE9 o
|
|
4890
|
+
-- modo de falhar mais convincente que h\xE1: o c\xF3digo parece certo, o console
|
|
4891
|
+
-- fica limpo, e nada acontece.
|
|
4892
|
+
--
|
|
4893
|
+
-- \u2500\u2500 as duas, e n\xE3o s\xF3 as mensagens \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4894
|
+
--
|
|
4895
|
+
-- A mensagem \xE9 o que dispara o alerta; a CONVERSA \xE9 o que carrega o estado que
|
|
4896
|
+
-- a lista pinta (\`status\`, \`unread_count\`, \`last_message_direction\`). Publicar
|
|
4897
|
+
-- s\xF3 a primeira daria o som sem o badge parar de mentir depois de algu\xE9m ler.
|
|
4898
|
+
--
|
|
4899
|
+
-- \u2500\u2500 o RLS continua a valer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4900
|
+
--
|
|
4901
|
+
-- Publicar n\xE3o abre nada: o Realtime do Supabase aplica as pol\xEDticas da tabela
|
|
4902
|
+
-- por assinante, e quem n\xE3o pode ler a linha n\xE3o a recebe. O que a publica\xE7\xE3o
|
|
4903
|
+
-- decide \xE9 se o evento EXISTE, n\xE3o quem o v\xEA.
|
|
4904
|
+
--
|
|
4905
|
+
-- \u2500\u2500 idempotente, e silenciosa onde n\xE3o se aplica \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4906
|
+
--
|
|
4907
|
+
-- \`ALTER PUBLICATION ... ADD TABLE\` rebenta se a tabela j\xE1 l\xE1 est\xE1, e a
|
|
4908
|
+
-- publica\xE7\xE3o \`supabase_realtime\` s\xF3 existe num Postgres da Supabase. As duas
|
|
4909
|
+
-- condi\xE7\xF5es s\xE3o verificadas antes, porque esta migration tamb\xE9m roda no banco
|
|
4910
|
+
-- de teste, que n\xE3o tem nenhuma delas.
|
|
4911
|
+
-- ---------------------------------------------------------------------------
|
|
4912
|
+
|
|
4913
|
+
DO $$
|
|
4914
|
+
DECLARE
|
|
4915
|
+
t text;
|
|
4916
|
+
v_n integer := 0;
|
|
4917
|
+
BEGIN
|
|
4918
|
+
IF NOT EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'supabase_realtime') THEN
|
|
4919
|
+
RAISE NOTICE 'conversations/006: sem publica\xE7\xE3o supabase_realtime \u2014 nada a fazer';
|
|
4920
|
+
RETURN;
|
|
4921
|
+
END IF;
|
|
4922
|
+
|
|
4923
|
+
FOREACH t IN ARRAY ARRAY['plg_conversation_messages', 'plg_conversations'] LOOP
|
|
4924
|
+
CONTINUE WHEN to_regclass('public.' || t) IS NULL;
|
|
4925
|
+
CONTINUE WHEN EXISTS (
|
|
4926
|
+
SELECT 1 FROM pg_publication_tables
|
|
4927
|
+
WHERE pubname = 'supabase_realtime' AND schemaname = 'public' AND tablename = t);
|
|
4928
|
+
EXECUTE format('ALTER PUBLICATION supabase_realtime ADD TABLE public.%I', t);
|
|
4929
|
+
v_n := v_n + 1;
|
|
4930
|
+
END LOOP;
|
|
4931
|
+
|
|
4932
|
+
RAISE NOTICE 'conversations/006: % tabela(s) publicada(s) no realtime', v_n;
|
|
4933
|
+
END $$;
|
|
4934
|
+
|
|
4935
|
+
-- \`REPLICA IDENTITY FULL\` na conversa: sem ela o UPDATE chega com a chave
|
|
4936
|
+
-- prim\xE1ria e mais nada, e a tela n\xE3o consegue saber que \`unread_count\` mudou \u2014
|
|
4937
|
+
-- que \xE9 exatamente o UPDATE que interessa aqui. Na tabela de mensagens n\xE3o \xE9
|
|
4938
|
+
-- preciso: o que importa \xE9 o INSERT, e esse vem inteiro de qualquer forma.
|
|
4939
|
+
DO $$
|
|
4940
|
+
BEGIN
|
|
4941
|
+
IF to_regclass('public.plg_conversations') IS NOT NULL THEN
|
|
4942
|
+
ALTER TABLE public.plg_conversations REPLICA IDENTITY FULL;
|
|
4943
|
+
END IF;
|
|
4944
|
+
END $$;
|
|
4945
|
+
|
|
4946
|
+
-- \u2500\u2500 e a conta de n\xE3o lidas, que ningu\xE9m mantinha \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
4947
|
+
--
|
|
4948
|
+
-- \`unread_count\` estava em ZERO nas oito conversas do cluster, incluindo as
|
|
4949
|
+
-- quatro cuja \xFAltima mensagem \xE9 de entrada. N\xE3o era um zero verdadeiro: n\xE3o h\xE1
|
|
4950
|
+
-- (n\xE3o havia) nada que a incremente. A coluna existe desde a baseline, a tela
|
|
4951
|
+
-- da lista decide o destaque por ela (\`unreadCount > 0\`) e a RPC de marcar como
|
|
4952
|
+
-- lida zera-a \u2014 s\xF3 faltava o lado que soma.
|
|
4953
|
+
--
|
|
4954
|
+
-- Por isso \`isWaitingOnUs\` recorre a \`lastMessageDirection\` quando a conta \xE9
|
|
4955
|
+
-- zero: foi escrita j\xE1 sabendo que a conta n\xE3o era de fiar. Esta migration
|
|
4956
|
+
-- torna-a de fiar, e a recorr\xEAncia volta a ser o que devia ser \u2014 uma rede, e
|
|
4957
|
+
-- n\xE3o o caminho normal.
|
|
4958
|
+
--
|
|
4959
|
+
-- S\xF3 o que CHEGA conta. Somar o que n\xF3s enviamos faria a caixa dizer que h\xE1
|
|
4960
|
+
-- doze por ler logo depois de algu\xE9m responder doze vezes.
|
|
4961
|
+
CREATE OR REPLACE FUNCTION public.plg_conversations_bump_unread() RETURNS trigger
|
|
4962
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''
|
|
4963
|
+
AS $$
|
|
4964
|
+
BEGIN
|
|
4965
|
+
IF NEW.direction IS DISTINCT FROM 'inbound' THEN RETURN NULL; END IF;
|
|
4966
|
+
UPDATE public.plg_conversations
|
|
4967
|
+
SET unread_count = coalesce(unread_count, 0) + 1
|
|
4968
|
+
WHERE id = NEW.conversation_id;
|
|
4969
|
+
RETURN NULL;
|
|
4970
|
+
END $$;
|
|
4971
|
+
|
|
4972
|
+
REVOKE ALL ON FUNCTION public.plg_conversations_bump_unread() FROM PUBLIC;
|
|
4973
|
+
GRANT EXECUTE ON FUNCTION public.plg_conversations_bump_unread() TO authenticated, service_role;
|
|
4974
|
+
|
|
4975
|
+
COMMENT ON FUNCTION public.plg_conversations_bump_unread() IS
|
|
4976
|
+
'Soma uma n\xE3o lida por mensagem que CHEGA. A coluna existia e era zerada ao ler, mas nada a incrementava (006).';
|
|
4977
|
+
|
|
4978
|
+
-- AFTER, como o irm\xE3o que carimba a dire\xE7\xE3o: falhar a contar n\xE3o pode recusar
|
|
4979
|
+
-- uma mensagem que o provedor j\xE1 entregou.
|
|
4980
|
+
DROP TRIGGER IF EXISTS plg_conversation_messages_bump_unread ON public.plg_conversation_messages;
|
|
4981
|
+
CREATE TRIGGER plg_conversation_messages_bump_unread
|
|
4982
|
+
AFTER INSERT ON public.plg_conversation_messages
|
|
4983
|
+
FOR EACH ROW EXECUTE FUNCTION public.plg_conversations_bump_unread();
|
|
4984
|
+
|
|
4985
|
+
-- O retroativo, e o que ele pode e n\xE3o pode saber.
|
|
4986
|
+
--
|
|
4987
|
+
-- N\xE3o h\xE1 coluna de "lido at\xE9 quando" nesta tabela \u2014 \`markRead\` zera a conta e
|
|
4988
|
+
-- mais nada. Ent\xE3o n\xE3o d\xE1 para reconstituir quantas mensagens ficaram por ler:
|
|
4989
|
+
-- o que d\xE1 para saber \xE9 que a conversa EST\xC1 \xE0 espera, porque a \xFAltima mensagem
|
|
4990
|
+
-- dela \xE9 de entrada.
|
|
4991
|
+
--
|
|
4992
|
+
-- Nesse caso a conta vira 1. Um n\xFAmero inventado seria pior que um n\xFAmero
|
|
4993
|
+
-- modesto: 1 diz "h\xE1 algo aqui", que \xE9 verdade e \xE9 o que faz a linha destacar;
|
|
4994
|
+
-- dizer "7" alegaria uma precis\xE3o que esta tabela nunca guardou. A partir de
|
|
4995
|
+
-- agora o gatilho conta de verdade.
|
|
4996
|
+
UPDATE public.plg_conversations
|
|
4997
|
+
SET unread_count = 1
|
|
4998
|
+
WHERE last_message_direction = 'inbound'
|
|
4999
|
+
AND status = 'open'
|
|
5000
|
+
AND coalesce(unread_count, 0) = 0;
|
|
5001
|
+
`;
|
|
4806
5002
|
var MIGRATIONS = [
|
|
4807
5003
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
4808
5004
|
{ id: "001_a_caixa_sabe_quem_falou_por_ultimo", sql: MIGRATION_001_A_CAIXA_SABE_QUEM_FALOU_POR_ULTIMO },
|
|
4809
5005
|
{ id: "002_a_casa_escolhe_por_onde_o_whatsapp_sai", sql: MIGRATION_002_A_CASA_ESCOLHE_POR_ONDE_O_WHATSAPP_SAI },
|
|
4810
5006
|
{ id: "003_a_campanha_sai_no_ritmo_que_o_numero_aguenta", sql: MIGRATION_003_A_CAMPANHA_SAI_NO_RITMO_QUE_O_NUMERO_AGUENTA },
|
|
4811
5007
|
{ id: "004_a_tela_sabe_se_pode_falar_antes_de_deixar_escrever", sql: MIGRATION_004_A_TELA_SABE_SE_PODE_FALAR_ANTES_DE_DEIXAR_ESCREVER },
|
|
4812
|
-
{ id: "005_a_conversa_conta_ao_funil_que_houve_contato", sql: MIGRATION_005_A_CONVERSA_CONTA_AO_FUNIL_QUE_HOUVE_CONTATO }
|
|
5008
|
+
{ id: "005_a_conversa_conta_ao_funil_que_houve_contato", sql: MIGRATION_005_A_CONVERSA_CONTA_AO_FUNIL_QUE_HOUVE_CONTATO },
|
|
5009
|
+
{ id: "006_a_caixa_avisa_que_chegou", sql: MIGRATION_006_A_CAIXA_AVISA_QUE_CHEGOU }
|
|
4813
5010
|
];
|
|
4814
5011
|
|
|
4815
5012
|
// src/index.ts
|
|
@@ -4843,11 +5040,11 @@ function createConversationsPlugin(options) {
|
|
|
4843
5040
|
contactEntityDef: options?.contactEntityDef
|
|
4844
5041
|
};
|
|
4845
5042
|
const dashboardWidgets = createConversationsDashboardWidgets({ store: store2, config });
|
|
4846
|
-
const PageComponent = () =>
|
|
5043
|
+
const PageComponent = () => React10__default.createElement(ConversationsPage, { store: store2, config });
|
|
4847
5044
|
PageComponent.displayName = "ConversationsPage";
|
|
4848
|
-
const CampaignsPageComponent = () =>
|
|
5045
|
+
const CampaignsPageComponent = () => React10__default.createElement(CampaignsView);
|
|
4849
5046
|
CampaignsPageComponent.displayName = "ConversationsCampaignsPage";
|
|
4850
|
-
const TemplatesPageComponent = () =>
|
|
5047
|
+
const TemplatesPageComponent = () => React10__default.createElement(TemplatesView);
|
|
4851
5048
|
TemplatesPageComponent.displayName = "ConversationsTemplatesPage";
|
|
4852
5049
|
return {
|
|
4853
5050
|
id: "conversations",
|
|
@@ -4914,7 +5111,26 @@ function createConversationsPlugin(options) {
|
|
|
4914
5111
|
permission: { feature: "conversations", action: "read" }
|
|
4915
5112
|
}
|
|
4916
5113
|
],
|
|
4917
|
-
widgets: [
|
|
5114
|
+
widgets: [
|
|
5115
|
+
{
|
|
5116
|
+
id: "conversations-inbox-watcher",
|
|
5117
|
+
// `shell.topbar.end` é a única zona global que o AdminShell renderiza
|
|
5118
|
+
// FORA do switch de rota: navegar não a desmonta. Um vigia montado
|
|
5119
|
+
// dentro da tela de Conversas morreria ao sair dela — que é justamente
|
|
5120
|
+
// quando ele serve para alguma coisa.
|
|
5121
|
+
zone: "shell.topbar.end",
|
|
5122
|
+
component: (() => {
|
|
5123
|
+
const W = () => React10__default.createElement(InboxWatcher, {
|
|
5124
|
+
label: options?.navLabel ?? "Conversations",
|
|
5125
|
+
section: options?.navSection ?? "main",
|
|
5126
|
+
position: options?.navPosition ?? 1
|
|
5127
|
+
});
|
|
5128
|
+
W.displayName = "InboxWatcher";
|
|
5129
|
+
return W;
|
|
5130
|
+
})(),
|
|
5131
|
+
order: -20
|
|
5132
|
+
}
|
|
5133
|
+
],
|
|
4918
5134
|
dashboardWidgets,
|
|
4919
5135
|
events: [
|
|
4920
5136
|
{ name: "conversations.message.received", description: "An inbound message arrived on any channel" },
|