@fayz-ai/plugin-conversations 0.16.1 → 0.17.1
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.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/index.js +432 -60
- package/dist/index.js.map +1 -1
- package/dist/lib/campainha.d.ts +31 -0
- package/dist/lib/campainha.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +4 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/store.d.ts +2 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/ConversationList.d.ts.map +1 -1
- package/dist/views/InboxView.d.ts.map +1 -1
- package/dist/views/useCaixaAoVivo.d.ts +27 -0
- package/dist/views/useCaixaAoVivo.d.ts.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UMA assinatura do tópico da conta, partilhada por quem precisar.
|
|
3
|
+
*
|
|
4
|
+
* ── por que não cada um a sua ───────────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* Dois consumidores querem o mesmo sinal: o vigia global (som, contagem,
|
|
7
|
+
* notificação do sistema) e a caixa aberta (lista e thread ao vivo). O jeito
|
|
8
|
+
* óbvio é cada um chamar `supabase.channel('conversas:<tenant>')` — e é o que
|
|
9
|
+
* eu fiz, e está errado.
|
|
10
|
+
*
|
|
11
|
+
* Dois canais com o MESMO tópico no mesmo cliente não recebem os dois: um
|
|
12
|
+
* vence. Testados em separado, ambos funcionam; juntos, um emudece. Em
|
|
13
|
+
* produção quem venceu foi a tela — a mensagem aparecia sozinha e a notificação
|
|
14
|
+
* do sistema não saía, sem erro nenhum para explicar.
|
|
15
|
+
*
|
|
16
|
+
* É o tipo de defeito que só aparece com as duas peças no ar ao mesmo tempo, e
|
|
17
|
+
* por isso passou por dois testes verdes.
|
|
18
|
+
*
|
|
19
|
+
* ── o desenho ───────────────────────────────────────────────────────────
|
|
20
|
+
*
|
|
21
|
+
* Um canal por conta, contado por referências. O primeiro a chegar abre, o
|
|
22
|
+
* último a sair fecha, e todos recebem a mesma campainha.
|
|
23
|
+
*/
|
|
24
|
+
export interface Chegada {
|
|
25
|
+
conversationId?: string;
|
|
26
|
+
}
|
|
27
|
+
type Ouvinte = (c: Chegada) => void;
|
|
28
|
+
/** Escuta a chegada de mensagem nesta conta. Devolve como deixar de escutar. */
|
|
29
|
+
export declare function assinarChegada(tenantId: string, ouvinte: Ouvinte): () => void;
|
|
30
|
+
export {};
|
|
31
|
+
//# sourceMappingURL=campainha.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"campainha.d.ts","sourceRoot":"","sources":["../../src/lib/campainha.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,MAAM,WAAW,OAAO;IACtB,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,KAAK,OAAO,GAAG,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;AASnC,gFAAgF;AAChF,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,IAAI,CAkC7E"}
|
package/dist/locales/en.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAsGrC,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"pt-BR.d.ts","sourceRoot":"","sources":["../../src/locales/pt-BR.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAsGvC,CAAA"}
|
|
@@ -6,6 +6,10 @@ export declare const MIGRATION_004_A_TELA_SABE_SE_PODE_FALAR_ANTES_DE_DEIXAR_ESC
|
|
|
6
6
|
export declare const MIGRATION_005_A_CONVERSA_CONTA_AO_FUNIL_QUE_HOUVE_CONTATO = "-- ---------------------------------------------------------------------------\n-- 005_a_conversa_conta_ao_funil_que_houve_contato.sql \u2014 a ponte que faltava.\n--\n-- \u2500\u2500 o buraco, medido \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\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- No cluster: 8 conversas, 42 mensagens trocadas \u2014 incluindo respostas de\n-- gente de verdade \u2014 e 315 neg\u00F3cios TODOS parados em \"Novo (feira)\", com\n-- `contact_status = 'not_contacted'`. Zero atividades vindas da caixa.\n--\n-- N\u00E3o era defeito: n\u00E3o existia liga\u00E7\u00E3o nenhuma. Nenhum `INSERT` em\n-- `plg_crm_activities` fora do plugin-crm, nenhuma fonte de linha do tempo\n-- registrada. A caixa de entrada e o funil olhavam para as mesmas pessoas e\n-- n\u00E3o se falavam.\n--\n-- O maquin\u00E1rio do CRM j\u00E1 estava pronto e correto: `crm_recompute_deal_status`\n-- (crm/020) l\u00EA as atividades, pega o \u00DALTIMO `outcome` e deriva o status; o\n-- gatilho `crm_activity_restatus` dispara a cada atividade inserida. Faltava\n-- algu\u00E9m inserir.\n--\n-- \u2500\u2500 por que o `outcome` \u00E9 o que \u00E9 \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\n--\n-- mensagem RECEBIDA \u2192 `replied` o lead respondeu. \u00C9 o fato mais forte\n-- que a caixa produz, e o que mais move\n-- o status (\"Respondeu\").\n-- mensagem ENVIADA \u2192 `left_message` deixamos recado. Mandar N\u00C3O \u00E9 ser\n-- lido: tratar envio como contato feito\n-- encheria o funil de \"conectado\" sem\n-- ningu\u00E9m do outro lado. A 020 mapeia\n-- este valor para \"N\u00E3o atendeu\", que \u00E9\n-- literalmente o estado \u2014 ainda sem\n-- resposta.\n--\n-- O vocabul\u00E1rio \u00E9 FECHADO por CHECK (crm/020:49): `connected, no_answer,\n-- left_message, replied, scheduled`. Conferido antes de escrever \u2014 a primeira\n-- vers\u00E3o desta migration usava `attempted`, que a regra da 020 nomeia mas a\n-- restri\u00E7\u00E3o da coluna N\u00C3O aceita, e teria falhado em toda mensagem enviada.\n--\n-- \u2500\u2500 e o `deal_id` n\u00E3o \u00E9 opcional \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\n--\n-- `crm_recompute_deal_status` agrega `WHERE a.deal_id = p_deal_id`. Uma\n-- atividade s\u00F3 com `lead_id` n\u00E3o entra na conta e n\u00E3o move status nenhum \u2014\n-- por isso o gatilho procura o neg\u00F3cio do lead e o carimba.\n--\n-- \u2500\u2500 uma atividade por CONVERSA E POR DIA, n\u00E3o por mensagem \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Uma conversa de trinta mensagens n\u00E3o s\u00E3o trinta contatos: \u00E9 um. Registrar\n-- cada mensagem encheria a linha do tempo de ru\u00EDdo e afogaria o que importa \u2014\n-- a liga\u00E7\u00E3o, a visita, a proposta. Ent\u00E3o a primeira mensagem do dia abre a\n-- atividade e as seguintes ATUALIZAM a mesma linha (o corpo mais recente, e o\n-- `outcome` mais forte do dia).\n--\n-- Atualizar tamb\u00E9m dispara o `crm_activity_restatus` (o gatilho \u00E9 INSERT OR\n-- UPDATE), ent\u00E3o uma resposta que chega depois de um envio move o status no\n-- mesmo dia, que \u00E9 o comportamento que se quer.\n--\n-- \u2500\u2500 e se o contato n\u00E3o for um lead \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\n--\n-- `contact_person_id` \u00E9 nulo enquanto ningu\u00E9m casou o telefone com uma pessoa.\n-- A\u00ED n\u00E3o h\u00E1 a quem pendurar o fato e a fun\u00E7\u00E3o sai calada. N\u00E3o inventa lead: a\n-- caixa recebe mensagem de entregador, de banco e de engano, e cada um deles\n-- viraria um card no funil de algu\u00E9m.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.conversations_log_crm_activity() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER\n SET search_path TO ''\n AS $$\nDECLARE\n v_tenant uuid;\n v_person uuid;\n v_canal text;\n v_deal uuid;\n v_outcome text;\n v_titulo text;\n v_corpo text;\n v_dia date := (NEW.at AT TIME ZONE 'UTC')::date;\n v_ativ uuid;\nBEGIN\n -- Sem CRM montado n\u00E3o h\u00E1 o que escrever. `to_regclass` em vez de refer\u00EAncia\n -- direta porque conversas \u00E9 plugin OPCIONAL e pode viver num cluster que n\u00E3o\n -- tem funil nenhum.\n IF to_regclass('public.plg_crm_activities') IS NULL THEN RETURN NULL; END IF;\n\n SELECT c.tenant_id, c.contact_person_id, c.channel\n INTO v_tenant, v_person, v_canal\n FROM public.plg_conversations c WHERE c.id = NEW.conversation_id;\n\n IF v_person IS NULL THEN RETURN NULL; END IF;\n\n SELECT d.id INTO v_deal\n FROM public.plg_crm_deals d\n WHERE d.tenant_id = v_tenant AND d.lead_id = v_person\n ORDER BY d.created_at DESC LIMIT 1;\n\n IF NEW.direction = 'inbound' THEN\n v_outcome := 'replied';\n v_titulo := 'Respondeu no ' || coalesce(v_canal, 'canal');\n ELSE\n v_outcome := 'left_message';\n v_titulo := 'Mensagem enviada no ' || coalesce(v_canal, 'canal');\n END IF;\n\n v_corpo := left(coalesce(NEW.body, ''), 2000);\n\n -- A atividade do dia, se j\u00E1 houver.\n SELECT a.id INTO v_ativ\n FROM public.plg_crm_activities a\n WHERE a.tenant_id = v_tenant\n AND a.lead_id = v_person\n AND a.activity_type = 'whatsapp'\n AND (a.completed_at AT TIME ZONE 'UTC')::date = v_dia\n ORDER BY a.completed_at DESC LIMIT 1;\n\n IF v_ativ IS NULL THEN\n INSERT INTO public.plg_crm_activities (\n tenant_id, deal_id, lead_id, contact_id, activity_type,\n title, description, outcome, completed_at, created_by_name\n ) VALUES (\n v_tenant, v_deal, v_person, v_person, 'whatsapp',\n v_titulo, v_corpo, v_outcome, NEW.at,\n -- Quem fez foi a caixa, n\u00E3o uma pessoa: o autor em uuid seria mentira,\n -- e \"(sistema)\" \u00E9 a resposta honesta para quem l\u00EA a linha do tempo.\n 'Caixa de entrada'\n );\n ELSE\n UPDATE public.plg_crm_activities a SET\n -- `replied` NUNCA \u00E9 rebaixado por um envio posterior: o lead ter\n -- respondido \u00E9 o fato do dia, e mandar outra mensagem depois n\u00E3o o\n -- desfaz.\n outcome = CASE WHEN a.outcome = 'replied' THEN 'replied' ELSE v_outcome END,\n title = CASE WHEN a.outcome = 'replied' THEN a.title ELSE v_titulo END,\n description = v_corpo,\n completed_at = NEW.at,\n deal_id = coalesce(a.deal_id, v_deal)\n WHERE a.id = v_ativ;\n END IF;\n\n RETURN NULL;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.conversations_log_crm_activity() FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.conversations_log_crm_activity() TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.conversations_log_crm_activity() IS\n 'Uma mensagem vira contato na linha do tempo do lead e do neg\u00F3cio, e o status do card deriva disso pelo gatilho da crm/020. Uma atividade por conversa por dia (005).';\n\n-- AFTER: falhar ao registrar no CRM n\u00E3o pode recusar uma mensagem que j\u00E1\n-- chegou do provedor. A caixa \u00E9 a fonte; o funil \u00E9 quem escuta.\nDROP TRIGGER IF EXISTS conversations_log_crm_activity ON public.plg_conversation_messages;\nCREATE TRIGGER conversations_log_crm_activity\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.conversations_log_crm_activity();\n\n-- O \u00EDndice que a busca da atividade do dia usa. Sem ele, cada mensagem varre\n-- as atividades do inquilino inteiro \u2014 e mensagem \u00E9 o que mais chega aqui.\nDO $$\nBEGIN\n -- Guarda pela mesma raz\u00E3o da fun\u00E7\u00E3o: conversas \u00E9 opcional e pode viver sem\n -- CRM. Um \u00EDndice sobre tabela que n\u00E3o existe recusa o arquivo inteiro.\n IF to_regclass('public.plg_crm_activities') IS NULL THEN RETURN; END IF;\n CREATE INDEX IF NOT EXISTS plg_crm_activities_lead_day\n ON public.plg_crm_activities (tenant_id, lead_id, activity_type, completed_at DESC);\nEND $$;\n";
|
|
7
7
|
export declare const MIGRATION_006_A_CAIXA_AVISA_QUE_CHEGOU = "-- ---------------------------------------------------------------------------\n-- 006_a_caixa_avisa_que_chegou.sql\n--\n-- \u2500\u2500 o sil\u00EAncio, 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\n--\n-- A caixa recebia mensagens e ningu\u00E9m era avisado. Sem som, sem contagem no\n-- menu, sem destaque na lista \u2014 e a suspeita natural \u00E9 que falta a tela que\n-- avisa. N\u00E3o faltava: o evento nunca sa\u00EDa do banco.\n--\n-- `plg_conversation_messages` e `plg_conversations` n\u00E3o estavam em\n-- `supabase_realtime`. A publica\u00E7\u00E3o existe (`puballtables = false`, ou seja,\n-- cada tabela entra nomeada) e nenhuma das duas fora nomeada. Qualquer cliente\n-- que assinasse `postgres_changes` sobre elas ficaria subscrito para sempre a\n-- um canal por onde nada passa \u2014 e subscrever com sucesso a um canal mudo \u00E9 o\n-- modo de falhar mais convincente que h\u00E1: o c\u00F3digo parece certo, o console\n-- fica limpo, e nada acontece.\n--\n-- \u2500\u2500 as duas, e n\u00E3o s\u00F3 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\n--\n-- A mensagem \u00E9 o que dispara o alerta; a CONVERSA \u00E9 o que carrega o estado que\n-- a lista pinta (`status`, `unread_count`, `last_message_direction`). Publicar\n-- s\u00F3 a primeira daria o som sem o badge parar de mentir depois de algu\u00E9m ler.\n--\n-- \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\n--\n-- Publicar n\u00E3o abre nada: o Realtime do Supabase aplica as pol\u00EDticas da tabela\n-- por assinante, e quem n\u00E3o pode ler a linha n\u00E3o a recebe. O que a publica\u00E7\u00E3o\n-- decide \u00E9 se o evento EXISTE, n\u00E3o quem o v\u00EA.\n--\n-- \u2500\u2500 idempotente, e silenciosa onde n\u00E3o 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\n--\n-- `ALTER PUBLICATION ... ADD TABLE` rebenta se a tabela j\u00E1 l\u00E1 est\u00E1, e a\n-- publica\u00E7\u00E3o `supabase_realtime` s\u00F3 existe num Postgres da Supabase. As duas\n-- condi\u00E7\u00F5es s\u00E3o verificadas antes, porque esta migration tamb\u00E9m roda no banco\n-- de teste, que n\u00E3o tem nenhuma delas.\n-- ---------------------------------------------------------------------------\n\nDO $$\nDECLARE\n t text;\n v_n integer := 0;\nBEGIN\n IF NOT EXISTS (SELECT 1 FROM pg_publication WHERE pubname = 'supabase_realtime') THEN\n RAISE NOTICE 'conversations/006: sem publica\u00E7\u00E3o supabase_realtime \u2014 nada a fazer';\n RETURN;\n END IF;\n\n FOREACH t IN ARRAY ARRAY['plg_conversation_messages', 'plg_conversations'] LOOP\n CONTINUE WHEN to_regclass('public.' || t) IS NULL;\n CONTINUE WHEN EXISTS (\n SELECT 1 FROM pg_publication_tables\n WHERE pubname = 'supabase_realtime' AND schemaname = 'public' AND tablename = t);\n EXECUTE format('ALTER PUBLICATION supabase_realtime ADD TABLE public.%I', t);\n v_n := v_n + 1;\n END LOOP;\n\n RAISE NOTICE 'conversations/006: % tabela(s) publicada(s) no realtime', v_n;\nEND $$;\n\n-- `REPLICA IDENTITY FULL` na conversa: sem ela o UPDATE chega com a chave\n-- prim\u00E1ria e mais nada, e a tela n\u00E3o consegue saber que `unread_count` mudou \u2014\n-- que \u00E9 exatamente o UPDATE que interessa aqui. Na tabela de mensagens n\u00E3o \u00E9\n-- preciso: o que importa \u00E9 o INSERT, e esse vem inteiro de qualquer forma.\nDO $$\nBEGIN\n IF to_regclass('public.plg_conversations') IS NOT NULL THEN\n ALTER TABLE public.plg_conversations REPLICA IDENTITY FULL;\n END IF;\nEND $$;\n\n-- \u2500\u2500 e a conta de n\u00E3o lidas, que ningu\u00E9m 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\n--\n-- `unread_count` estava em ZERO nas oito conversas do cluster, incluindo as\n-- quatro cuja \u00FAltima mensagem \u00E9 de entrada. N\u00E3o era um zero verdadeiro: n\u00E3o h\u00E1\n-- (n\u00E3o havia) nada que a incremente. A coluna existe desde a baseline, a tela\n-- da lista decide o destaque por ela (`unreadCount > 0`) e a RPC de marcar como\n-- lida zera-a \u2014 s\u00F3 faltava o lado que soma.\n--\n-- Por isso `isWaitingOnUs` recorre a `lastMessageDirection` quando a conta \u00E9\n-- zero: foi escrita j\u00E1 sabendo que a conta n\u00E3o era de fiar. Esta migration\n-- torna-a de fiar, e a recorr\u00EAncia volta a ser o que devia ser \u2014 uma rede, e\n-- n\u00E3o o caminho normal.\n--\n-- S\u00F3 o que CHEGA conta. Somar o que n\u00F3s enviamos faria a caixa dizer que h\u00E1\n-- doze por ler logo depois de algu\u00E9m responder doze vezes.\nCREATE OR REPLACE FUNCTION public.plg_conversations_bump_unread() RETURNS trigger\n LANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''\n AS $$\nBEGIN\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN RETURN NULL; END IF;\n UPDATE public.plg_conversations\n SET unread_count = coalesce(unread_count, 0) + 1\n WHERE id = NEW.conversation_id;\n RETURN NULL;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.plg_conversations_bump_unread() FROM PUBLIC;\nGRANT EXECUTE ON FUNCTION public.plg_conversations_bump_unread() TO authenticated, service_role;\n\nCOMMENT ON FUNCTION public.plg_conversations_bump_unread() IS\n 'Soma uma n\u00E3o lida por mensagem que CHEGA. A coluna existia e era zerada ao ler, mas nada a incrementava (006).';\n\n-- AFTER, como o irm\u00E3o que carimba a dire\u00E7\u00E3o: falhar a contar n\u00E3o pode recusar\n-- uma mensagem que o provedor j\u00E1 entregou.\nDROP TRIGGER IF EXISTS plg_conversation_messages_bump_unread ON public.plg_conversation_messages;\nCREATE TRIGGER plg_conversation_messages_bump_unread\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.plg_conversations_bump_unread();\n\n-- O retroativo, e o que ele pode e n\u00E3o pode saber.\n--\n-- N\u00E3o h\u00E1 coluna de \"lido at\u00E9 quando\" nesta tabela \u2014 `markRead` zera a conta e\n-- mais nada. Ent\u00E3o n\u00E3o d\u00E1 para reconstituir quantas mensagens ficaram por ler:\n-- o que d\u00E1 para saber \u00E9 que a conversa EST\u00C1 \u00E0 espera, porque a \u00FAltima mensagem\n-- dela \u00E9 de entrada.\n--\n-- Nesse caso a conta vira 1. Um n\u00FAmero inventado seria pior que um n\u00FAmero\n-- modesto: 1 diz \"h\u00E1 algo aqui\", que \u00E9 verdade e \u00E9 o que faz a linha destacar;\n-- dizer \"7\" alegaria uma precis\u00E3o que esta tabela nunca guardou. A partir de\n-- agora o gatilho conta de verdade.\nUPDATE public.plg_conversations\n SET unread_count = 1\n WHERE last_message_direction = 'inbound'\n AND status = 'open'\n AND coalesce(unread_count, 0) = 0;\n";
|
|
8
8
|
export declare const MIGRATION_007_A_CONVERSA_DIZ_DE_QUEM_E_O_LEAD = "-- ---------------------------------------------------------------------------\n-- 007_a_conversa_diz_de_quem_e_o_lead.sql\n--\n-- \u2500\u2500 duas coisas chamadas \"respons\u00E1vel\" \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\n--\n-- A caixa j\u00E1 mostra um \"Respons\u00E1vel\", e ele \u00E9 `plg_conversations.assigned_to`:\n-- quem est\u00E1 ATENDENDO aquela conversa. N\u00E3o \u00E9 o dono do lead.\n--\n-- Os dois convivem e precisam conviver: a Carolina \u00E9 dona do lead, e quem\n-- responde a mensagem que chegou \u00E0s 2h da manh\u00E3 pode ser outra pessoa. Colapsar\n-- num s\u00F3 campo faria uma das duas perguntas ficar sem resposta \u2014 e a que\n-- desapareceria \u00E9 a do funil, que \u00E9 a que decide quem faz o follow-up.\n--\n-- Esta view acrescenta a SEGUNDA, com nome pr\u00F3prio (`lead_owner_*`), sem tocar\n-- na primeira.\n--\n-- \u2500\u2500 por que view, e n\u00E3o uma coluna na conversa \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\n--\n-- O dono do lead muda \u2014 algu\u00E9m clica \"Assumir\" no funil e a carteira troca de\n-- m\u00E3os. Uma coluna copiada aqui seria uma segunda verdade que envelhece em\n-- sil\u00EAncio: a conversa diria Carolina depois de o lead j\u00E1 ser do Julio, e\n-- ningu\u00E9m saberia dizer qual das duas telas est\u00E1 certa.\n--\n-- \u00C9 exatamente o problema que a 047 acabou de resolver do outro lado, onde o\n-- dono estava escrito em dois lugares que n\u00E3o se falavam. Derivar \u00E9 o que\n-- impede a repeti\u00E7\u00E3o.\n--\n-- \u2500\u2500 e por que `v_leads` e n\u00E3o `plg_crm_deals` \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\n--\n-- `v_leads.assigned_to_id` j\u00E1 resolve a preced\u00EAncia entre o que a PESSOA\n-- declara (`metadata.assignedToId`) e o que o NEG\u00D3CIO diz (047). Ir direto ao\n-- neg\u00F3cio aqui reimplementaria essa regra, e as duas divergiriam no dia em que\n-- uma mudasse.\n--\n-- \u2500\u2500 o CRM \u00E9 opcional \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\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `plugin-conversations` roda em casa sem funil nenhum. `v_leads` \u00E9 da espinha\n-- (n\u00E3o do CRM), ent\u00E3o a depend\u00EAncia \u00E9 leg\u00EDtima \u2014 mas a jun\u00E7\u00E3o \u00E9 LEFT: uma\n-- conversa de quem n\u00E3o \u00E9 lead continua aparecendo, com o dono nulo.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE VIEW public.v_conversations WITH (security_invoker = true) AS\n SELECT c.*,\n l.assigned_to_id AS lead_owner_id,\n p.name AS lead_owner_name\n FROM public.plg_conversations c\n -- Pelo contato: \u00E9 `contact_person_id` que liga a conversa a uma pessoa da\n -- base. Nulo enquanto ningu\u00E9m casou o telefone com algu\u00E9m, e a\u00ED n\u00E3o h\u00E1\n -- lead a quem perguntar o dono.\n LEFT JOIN public.v_leads l\n ON l.id = c.contact_person_id\n -- O NOME do dono, que \u00E9 o que a tela mostra. O id sozinho obrigaria cada\n -- tela a resolver o mesmo de-para \u2014 foi o que fez o filtro de leads exibir\n -- UUID em produ\u00E7\u00E3o por um dia.\n LEFT JOIN public.v_team_members m\n ON m.user_id::text = l.assigned_to_id AND m.tenant_id = c.tenant_id\n LEFT JOIN public.people p\n ON p.id = m.person_id;\n\nCOMMENT ON VIEW public.v_conversations IS\n 'A conversa com o DONO DO LEAD junto \u2014 diferente de `assigned_to`, que \u00E9 quem atende a conversa. Derivado de `v_leads` para n\u00E3o virar uma segunda verdade que envelhece (007).';\n";
|
|
9
|
+
export declare const MIGRATION_008_DAR_POR_VISTA_E_UMA_DECISAO = "-- ---------------------------------------------------------------------------\n-- 008_dar_por_vista_e_uma_decisao.sql\n--\n-- \u2500\u2500 o buraco \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- A bolinha vermelha da caixa n\u00E3o \u00E9 \"n\u00E3o lido\": \u00E9 `isWaitingOnUs`, que olha a\n-- DIRE\u00C7\u00C3O da \u00FAltima mensagem. Se a \u00FAltima \u00E9 de entrada, a conversa espera\n-- resposta \u2014 e continua esperando por mais que algu\u00E9m abra e leia.\n--\n-- Isso est\u00E1 certo enquanto a pergunta \u00E9 \"o que falta responder\". Mas deixa sem\n-- sa\u00EDda o caso comum: a pessoa leu, decidiu que n\u00E3o cabe resposta (\"ok\",\n-- \"obrigado\", um \u00E1udio que j\u00E1 foi tratado por telefone) e n\u00E3o tem como tirar a\n-- conversa da fila. `unread_count = 0` n\u00E3o resolve, porque n\u00E3o \u00E9 ele que\n-- acende a bolinha.\n--\n-- \u2500\u2500 por que um instante, e n\u00E3o um booleano \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\n--\n-- `lida boolean` exigiria algu\u00E9m lembrar de apag\u00E1-la quando chegasse mensagem\n-- nova \u2014 e o dia em que esquecessem, a conversa sumiria da fila com o cliente\n-- esperando. \u00C9 o pior defeito poss\u00EDvel numa caixa de entrada: silencioso, e do\n-- lado de perder trabalho.\n--\n-- Um INSTANTE se compara com `last_message_at` e se invalida sozinho: deu por\n-- vista \u00E0s 14h, chegou mensagem \u00E0s 15h, a conversa volta \u00E0 fila sem ningu\u00E9m\n-- fazer nada. A regra \u00E9 uma compara\u00E7\u00E3o, n\u00E3o um estado a manter.\n--\n-- \u2500\u2500 o que N\u00C3O muda \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- `unread_count` continua onde est\u00E1. Ele conta mensagens e serve ao contador\n-- do menu; esta coluna responde outra pergunta (\"algu\u00E9m j\u00E1 decidiu sobre\n-- isto?\"). Colapsar as duas faria a contagem do menu mentir no dia em que\n-- algu\u00E9m desse por vista sem ler.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversations\n ADD COLUMN IF NOT EXISTS acknowledged_at timestamptz;\n\nCOMMENT ON COLUMN public.plg_conversations.acknowledged_at IS\n 'Quando algu\u00E9m deu a conversa por vista. Vale enquanto for POSTERIOR a `last_message_at` \u2014 mensagem nova devolve a conversa \u00E0 fila sozinha (008).';\n\n-- A fila \u00E9 lida por `last_message_at` e por esta coluna juntas.\nCREATE INDEX IF NOT EXISTS plg_conversations_fila_idx\n ON public.plg_conversations (tenant_id, status, last_message_at DESC)\n WHERE status = 'open';\n";
|
|
10
|
+
export declare const MIGRATION_009_A_MENSAGEM_INTEIRA_ATRAVESSA_O_REALTIME = "-- ---------------------------------------------------------------------------\n-- 009_a_mensagem_inteira_atravessa_o_realtime.sql\n--\n-- \u2500\u2500 o buraco \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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- O vigia da caixa assinava `plg_conversation_messages`, recebia `SUBSCRIBED`\n-- do servidor, e nunca recebia um \u00FAnico evento. Nenhum erro, nenhum aviso: a\n-- assinatura dizia que estava tudo bem e n\u00E3o chegava nada.\n--\n-- A causa \u00E9 a combina\u00E7\u00E3o de duas coisas que, sozinhas, est\u00E3o certas:\n--\n-- 1. a tabela tem RLS ligada, como toda tabela desta base;\n-- 2. a tabela estava com `REPLICA IDENTITY` no padr\u00E3o \u2014 s\u00F3 a chave prim\u00E1ria.\n--\n-- O Realtime do Supabase aplica a POL\u00CDTICA DE LEITURA do usu\u00E1rio a cada evento\n-- antes de entreg\u00E1-lo. Para isso ele precisa da linha inteira: a pol\u00EDtica fala\n-- de `tenant_id`, e `tenant_id` n\u00E3o \u00E9 a chave prim\u00E1ria. Com s\u00F3 a PK no WAL, n\u00E3o\n-- h\u00E1 o que avaliar \u2014 e o evento \u00E9 descartado sem ru\u00EDdo.\n--\n-- `plg_conversations` j\u00E1 estava `FULL` (006). A tabela de MENSAGENS ficou de\n-- fora daquela migration, e \u00E9 justamente ela que o vigia escuta.\n--\n-- \u2500\u2500 por que isso \u00E9 caro e ainda assim certo \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\n--\n-- `FULL` faz o WAL carregar a linha inteira em cada UPDATE e DELETE, n\u00E3o s\u00F3 as\n-- colunas mudadas. Numa tabela de mensagens isso \u00E9 peso real.\n--\n-- A alternativa \u2014 desligar a RLS para o Realtime enxergar \u2014 trocaria um custo\n-- de disco por um vazamento: as mensagens de uma casa chegariam ao navegador\n-- de outra. Entre pagar WAL e vazar conversa, paga-se o WAL.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nALTER TABLE public.plg_conversation_messages REPLICA IDENTITY FULL;\n\n-- Garante a publica\u00E7\u00E3o tamb\u00E9m \u2014 uma tabela com identidade cheia e fora da\n-- publica\u00E7\u00E3o continua sem emitir nada, e o sintoma seria id\u00EAntico.\nDO $$\nBEGIN\n IF NOT EXISTS (\n SELECT 1 FROM pg_publication_tables\n WHERE pubname = 'supabase_realtime'\n AND schemaname = 'public'\n AND tablename = 'plg_conversation_messages'\n ) THEN\n EXECUTE 'ALTER PUBLICATION supabase_realtime ADD TABLE public.plg_conversation_messages';\n END IF;\nEND $$;\n";
|
|
11
|
+
export declare const MIGRATION_010_A_CHEGADA_E_ANUNCIADA_POR_CONTA = "-- ---------------------------------------------------------------------------\n-- 010_a_chegada_e_anunciada_por_conta.sql\n--\n-- \u2500\u2500 por que `postgres_changes` nunca funcionou aqui \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- O vigia assinava a TABELA de mensagens e recebia `SUBSCRIBED` do servidor.\n-- Nenhum evento chegava. Nenhum erro, nenhum aviso \u2014 a assinatura dizia que\n-- estava tudo bem.\n--\n-- O Realtime aplica a pol\u00EDtica de leitura a cada evento antes de entreg\u00E1-lo. A\n-- pol\u00EDtica desta base resolve o tenant por, nesta ordem:\n--\n-- \u2460 a claim `tenant_id` do JWT\n-- \u2461 o CABE\u00C7ALHO da requisi\u00E7\u00E3o (`app.requested_tenant()`)\n-- \u2462 a filia\u00E7\u00E3o \u00FAnica, quando a pessoa s\u00F3 tem uma\n--\n-- O Realtime manda o JWT e N\u00C3O manda cabe\u00E7alho. E o JWT desta casa n\u00E3o carrega\n-- `tenant_id` \u2014 a conta ativa \u00E9 escolhida por cabe\u00E7alho, porque a mesma pessoa\n-- atende sete contas. Sobra \u2462, que devolve nulo para quem tem mais de uma.\n--\n-- Resultado: a pol\u00EDtica recusa, o evento \u00E9 descartado em sil\u00EAncio, e o defeito\n-- parece \"o realtime n\u00E3o funciona\" quando na verdade \u00E9 \"o realtime n\u00E3o sabe em\n-- que conta voc\u00EA est\u00E1\".\n--\n-- \u2500\u2500 a sa\u00EDda: anunciar, em vez de espiar a tabela \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Broadcast n\u00E3o passa pela RLS da tabela: o servidor entrega a quem est\u00E1 no\n-- T\u00D3PICO. Ent\u00E3o o t\u00F3pico carrega a conta no nome \u2014 `conversas:<tenant>` \u2014 e\n-- quem escuta j\u00E1 teve de saber em que conta est\u00E1 para montar o nome.\n--\n-- Isso inverte a depend\u00EAncia de forma saud\u00E1vel: em vez de o banco adivinhar o\n-- contexto do navegador, o navegador declara o contexto que j\u00E1 tem.\n--\n-- \u2500\u2500 o que vai no an\u00FAncio \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\u2500\u2500\n--\n-- O M\u00CDNIMO para decidir: id da conversa, canal, e um trecho. N\u00E3o a mensagem\n-- inteira \u2014 um broadcast \u00E9 entregue a todos do t\u00F3pico, e mandar o corpo\n-- completo por ali seria espalhar conversa por um caminho que n\u00E3o \u00E9 o que\n-- guarda conversa. Quem quiser o texto l\u00EA a tabela, com RLS, como sempre.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.plg_conversations_anunciar_chegada()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nDECLARE\n v_nome text;\nBEGIN\n -- S\u00F3 o que CHEGA. A resposta que o operador manda tamb\u00E9m \u00E9 um INSERT, e\n -- anunci\u00E1-la faria o sistema apitar para quem acabou de digitar.\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN\n RETURN NEW;\n END IF;\n\n SELECT c.contact_name INTO v_nome\n FROM public.plg_conversations c\n WHERE c.id = NEW.conversation_id;\n\n PERFORM realtime.send(\n jsonb_build_object(\n 'conversationId', NEW.conversation_id,\n 'channel', NEW.channel,\n 'contactName', coalesce(v_nome, ''),\n -- Um trecho, n\u00E3o o corpo: o suficiente para a notifica\u00E7\u00E3o do sistema\n -- dizer do que se trata sem espalhar a conversa pelo broadcast.\n 'preview', left(coalesce(NEW.body, ''), 120),\n 'at', NEW.at\n ),\n 'inbound',\n 'conversas:' || NEW.tenant_id::text,\n -- Privado: exige que quem assina esteja autenticado. P\u00FAblico deixaria\n -- qualquer um com o nome do t\u00F3pico ouvir a chegada.\n true\n );\n RETURN NEW;\nEND $$;\n\nREVOKE ALL ON FUNCTION public.plg_conversations_anunciar_chegada() FROM public, anon, authenticated;\n\nDROP TRIGGER IF EXISTS plg_conversations_anunciar_chegada ON public.plg_conversation_messages;\nCREATE TRIGGER plg_conversations_anunciar_chegada\n AFTER INSERT ON public.plg_conversation_messages\n FOR EACH ROW EXECUTE FUNCTION public.plg_conversations_anunciar_chegada();\n\nCOMMENT ON FUNCTION public.plg_conversations_anunciar_chegada() IS\n 'Anuncia a chegada no t\u00F3pico `conversas:<tenant>`. Broadcast e n\u00E3o `postgres_changes` porque a RLS da tabela resolve o tenant por cabe\u00E7alho, e o Realtime n\u00E3o manda cabe\u00E7alho (010).';\n";
|
|
12
|
+
export declare const MIGRATION_011_O_ANUNCIO_E_UMA_CAMPAINHA_NAO_UM_RECADO = "-- ---------------------------------------------------------------------------\n-- 011_o_anuncio_e_uma_campainha_nao_um_recado.sql\n--\n-- \u2500\u2500 por que a 010 n\u00E3o podia ficar como estava \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\n--\n-- A 010 anunciava num t\u00F3pico PRIVADO e mandava, no an\u00FAncio, o nome do contato e\n-- um trecho da mensagem. T\u00F3pico privado exige pol\u00EDtica em `realtime.messages` \u2014\n-- e essa tabela \u00E9 do Supabase, dona `supabase_realtime_admin`, um papel que\n-- esta conex\u00E3o n\u00E3o pode assumir:\n--\n-- ERROR: 42501: permission denied to set role \"supabase_realtime_admin\"\n--\n-- Sem a pol\u00EDtica, o servidor recusa a assinatura e nada chega. Com o t\u00F3pico\n-- p\u00FAblico, qualquer um que soubesse o nome do t\u00F3pico leria o trecho da\n-- conversa \u2014 o que n\u00E3o se faz.\n--\n-- \u2500\u2500 a sa\u00EDda: separar o AVISO do CONTE\u00DADO \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\n--\n-- O an\u00FAncio passa a ser uma campainha: diz que chegou algo e em que conversa,\n-- e mais nada. Nome e texto o navegador busca pela via de sempre \u2014 a tabela,\n-- com RLS, onde o cabe\u00E7alho da conta existe e a pol\u00EDtica funciona.\n--\n-- O t\u00F3pico continua carregando o uuid da conta no nome, que \u00E9 o que separa uma\n-- casa da outra. Ele n\u00E3o \u00E9 adivinh\u00E1vel, e mesmo que fosse, o que se ouviria \u00E9\n-- \"chegou mensagem\", sem nome nem texto. O sigilo fica onde sempre esteve: na\n-- leitura da tabela.\n--\n-- \u00C9 uma troca deliberada de superf\u00EDcie: em vez de tentar proteger um caminho\n-- novo, n\u00E3o se p\u00F5e nada sens\u00EDvel nele.\n--\n-- Idempotente e replay-safe.\n-- ---------------------------------------------------------------------------\n\nCREATE OR REPLACE FUNCTION public.plg_conversations_anunciar_chegada()\nRETURNS trigger\nLANGUAGE plpgsql\nSECURITY DEFINER\nSET search_path = ''\nAS $$\nBEGIN\n -- S\u00F3 o que CHEGA. A resposta que o operador manda tamb\u00E9m \u00E9 um INSERT, e\n -- anunci\u00E1-la faria o sistema apitar para quem acabou de digitar.\n IF NEW.direction IS DISTINCT FROM 'inbound' THEN\n RETURN NEW;\n END IF;\n\n PERFORM realtime.send(\n -- S\u00D3 o id da conversa. Nem nome, nem trecho, nem corpo: quem ouve vai\n -- buscar isso na tabela, com a pol\u00EDtica de leitura no meio do caminho.\n -- Um uuid sozinho n\u00E3o conta nada a quem n\u00E3o pode ler a conversa.\n jsonb_build_object('conversationId', NEW.conversation_id),\n 'inbound',\n 'conversas:' || NEW.tenant_id::text,\n -- P\u00DABLICO, e por isso o payload \u00E9 vazio de conte\u00FAdo. Privado exigiria uma\n -- pol\u00EDtica numa tabela que n\u00E3o \u00E9 nossa.\n false\n );\n RETURN NEW;\nEND $$;\n\nCOMMENT ON FUNCTION public.plg_conversations_anunciar_chegada() IS\n 'Toca a campainha no t\u00F3pico `conversas:<tenant>` \u2014 s\u00F3 o id da conversa. O conte\u00FAdo \u00E9 lido da tabela, com RLS (011).';\n";
|
|
9
13
|
export declare const MIGRATIONS: Array<{
|
|
10
14
|
id: string;
|
|
11
15
|
sql: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,2rhCAkgBlC,CAAA;AAED,eAAO,MAAM,gDAAgD,i0LAuG5D,CAAA;AAED,eAAO,MAAM,oDAAoD,m5bAuOhE,CAAA;AAED,eAAO,MAAM,0DAA0D,81rBAwWtE,CAAA;AAED,eAAO,MAAM,gEAAgE,+lJA2E5E,CAAA;AAED,eAAO,MAAM,yDAAyD,w1RAmKrE,CAAA;AAED,eAAO,MAAM,sCAAsC,mtOA6HlD,CAAA;AAED,eAAO,MAAM,6CAA6C,qmIA6DzD,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,sBAAsB,2rhCAkgBlC,CAAA;AAED,eAAO,MAAM,gDAAgD,i0LAuG5D,CAAA;AAED,eAAO,MAAM,oDAAoD,m5bAuOhE,CAAA;AAED,eAAO,MAAM,0DAA0D,81rBAwWtE,CAAA;AAED,eAAO,MAAM,gEAAgE,+lJA2E5E,CAAA;AAED,eAAO,MAAM,yDAAyD,w1RAmKrE,CAAA;AAED,eAAO,MAAM,sCAAsC,mtOA6HlD,CAAA;AAED,eAAO,MAAM,6CAA6C,qmIA6DzD,CAAA;AAED,eAAO,MAAM,yCAAyC,ywGA8CrD,CAAA;AAED,eAAO,MAAM,qDAAqD,40FAiDjE,CAAA;AAED,eAAO,MAAM,6CAA6C,6oJA0FzD,CAAA;AAED,eAAO,MAAM,qDAAqD,itGA8DjE,CAAA;AAED,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAazD,CAAA"}
|
package/dist/store.d.ts
CHANGED
|
@@ -24,6 +24,8 @@ export interface ConversationsUIState {
|
|
|
24
24
|
sending: boolean;
|
|
25
25
|
load(): Promise<void>;
|
|
26
26
|
select(id: string): Promise<void>;
|
|
27
|
+
/** Dar por vista SEM abrir — o "ok, obrigado" que não pede resposta. */
|
|
28
|
+
markRead(id: string): Promise<void>;
|
|
27
29
|
deselect(): void;
|
|
28
30
|
setChannelFilter(channel: Channel | 'all'): Promise<void>;
|
|
29
31
|
setLeadOwnerFilter(ownerId?: string | 'none'): Promise<void>;
|
package/dist/store.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EACP,OAAO,EACP,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,SAAS,CAAA;AAEhB;kEACkE;AAClE,MAAM,WAAW,kBAAkB;IACjC;4EACwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;CACjB;AAUD,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,aAAa,EAAE,OAAO,GAAG,KAAK,CAAA;IAC9B,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAEhB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,QAAQ,IAAI,IAAI,CAAA;IAChB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC7D,gFAAgF;IAChF,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC/D,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrD;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,qBAAqB,GAC9B,QAAQ,CAAC,oBAAoB,CAAC,
|
|
1
|
+
{"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAC5D,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EACV,YAAY,EACZ,OAAO,EACP,OAAO,EACP,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,SAAS,CAAA;AAEhB;kEACkE;AAClE,MAAM,WAAW,kBAAkB;IACjC;4EACwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;CACjB;AAUD,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,YAAY,EAAE,CAAA;IAC7B,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACpC,aAAa,EAAE,OAAO,GAAG,KAAK,CAAA;IAC9B,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAEhB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACrB,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,wEAAwE;IACxE,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACnC,QAAQ,IAAI,IAAI,CAAA;IAChB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACzD,kBAAkB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5D,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxC,MAAM,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC7D,gFAAgF;IAChF,aAAa,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAC/D,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACjC,SAAS,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACrD;AAED,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,qBAAqB,GAC9B,QAAQ,CAAC,oBAAoB,CAAC,CAgMhC"}
|
package/dist/types.d.ts
CHANGED
|
@@ -29,6 +29,11 @@ export interface Conversation {
|
|
|
29
29
|
*/
|
|
30
30
|
lastMessageDirection?: MessageDirection;
|
|
31
31
|
unreadCount: number;
|
|
32
|
+
/** Quando alguém deu a conversa por vista.
|
|
33
|
+
*
|
|
34
|
+
* Vale enquanto for POSTERIOR a `lastMessageAt`: mensagem nova devolve a
|
|
35
|
+
* conversa à fila sozinha, sem ninguém ter de lembrar de apagar nada. */
|
|
36
|
+
acknowledgedAt?: string | null;
|
|
32
37
|
status: ConversationStatus;
|
|
33
38
|
/** Quem ATENDE esta conversa. */
|
|
34
39
|
assignedTo?: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;AAE5E,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAA;AAE9D,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,CAAA;AAErD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB;;;;;;;;;;;OAWG;IACH,oBAAoB,CAAC,EAAE,gBAAgB,CAAA;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;gFAE4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,EAAE,CAAA;IAEd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,gBAAgB,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd;;6EAEyE;IACzE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;IACzB,MAAM,CAAC,EAAE,kBAAkB,GAAG,KAAK,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;yBAEqB;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAMlD,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,UAAU,GAAG,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;AAE5E,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,CAAA;AAE9D,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,UAAU,CAAA;AAErD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAA;IACrB,OAAO,EAAE,OAAO,CAAA;IAChB,kBAAkB,EAAE,MAAM,CAAA;IAC1B,aAAa,EAAE,MAAM,CAAA;IACrB;;;;;;;;;;;OAWG;IACH,oBAAoB,CAAC,EAAE,gBAAgB,CAAA;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB;;;8EAG0E;IAC1E,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,MAAM,EAAE,kBAAkB,CAAA;IAC1B,iCAAiC;IACjC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;gFAE4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,EAAE,CAAA;IAEd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,SAAS,EAAE,gBAAgB,CAAA;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd;;6EAEyE;IACzE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAA;IACzB,MAAM,CAAC,EAAE,kBAAkB,GAAG,KAAK,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;yBAEqB;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,MAAM,CAAA;IACtB,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAA;IACnB,+EAA+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,OAAO,CAAA;IAChB,yEAAyE;IACzE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6DAA6D;IAC7D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAMlD,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,YAAY,GAAG,OAAO,CAYjE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/views/ConversationList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,
|
|
1
|
+
{"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/views/ConversationList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,wBAAgB,gBAAgB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,qBAwLrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboxView.d.ts","sourceRoot":"","sources":["../../src/views/InboxView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"InboxView.d.ts","sourceRoot":"","sources":["../../src/views/InboxView.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAazB,wBAAgB,SAAS,sBA6ExB"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A caixa aberta se atualiza sozinha quando chega mensagem.
|
|
3
|
+
*
|
|
4
|
+
* ── por que o mesmo tópico do vigia ─────────────────────────────────────
|
|
5
|
+
*
|
|
6
|
+
* O aviso global (som, contagem, notificação do sistema) já escuta
|
|
7
|
+
* `conversas:<tenant>`. A tela aberta precisa exatamente do mesmo sinal, e
|
|
8
|
+
* assinar a TABELA aqui reintroduziria o defeito que levou dois dias para
|
|
9
|
+
* achar: o Realtime resolve a conta pelo JWT, a política resolve por cabeçalho,
|
|
10
|
+
* e quem atende mais de uma casa nunca recebe evento — sem erro nenhum.
|
|
11
|
+
*
|
|
12
|
+
* Dois ouvintes no mesmo tópico não se atrapalham: o servidor entrega a ambos,
|
|
13
|
+
* e cada um faz o que lhe cabe. O vigia avisa; esta tela redesenha.
|
|
14
|
+
*
|
|
15
|
+
* ── por que recarregar em vez de aplicar o payload ──────────────────────
|
|
16
|
+
*
|
|
17
|
+
* A campainha traz só o id da conversa, de propósito — o conteúdo não viaja
|
|
18
|
+
* pelo broadcast. Então a tela vai buscar: `load()` para a lista (que precisa
|
|
19
|
+
* reordenar, atualizar a prévia e a hora) e `refreshMessages()` para a thread,
|
|
20
|
+
* que só escreve se algo mudou e por isso não rouba a seleção de quem lê.
|
|
21
|
+
*
|
|
22
|
+
* É mais uma ida ao servidor do que aplicar o payload à mão, e é o que mantém
|
|
23
|
+
* a tela com a MESMA verdade que um F5 mostraria — sem uma segunda montagem de
|
|
24
|
+
* estado que envelhece em silêncio.
|
|
25
|
+
*/
|
|
26
|
+
export declare function useCaixaAoVivo(): void;
|
|
27
|
+
//# sourceMappingURL=useCaixaAoVivo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCaixaAoVivo.d.ts","sourceRoot":"","sources":["../../src/views/useCaixaAoVivo.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,SAuB7B"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"status": "preview",
|
|
5
5
|
"dependencies": []
|
|
6
6
|
},
|
|
7
|
-
"version": "0.
|
|
7
|
+
"version": "0.17.1",
|
|
8
8
|
"description": "[experimental] Fayz SDK — unified conversations / omni-channel inbox plugin",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"sideEffects": false,
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@fayz-ai/core": "^0.28.0",
|
|
31
|
-
"@fayz-ai/
|
|
32
|
-
"@fayz-ai/
|
|
31
|
+
"@fayz-ai/admin": "^0.29.1",
|
|
32
|
+
"@fayz-ai/ui": "^0.29.1"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/react": "^18.3.0",
|