From cfeac891fbf430956490310eed3d65cc2d2acb69 Mon Sep 17 00:00:00 2001 From: VPS 4 Builder Date: Mon, 6 Jul 2026 19:39:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(wa-inbox):=20bot=C3=A3o=20liga/desliga=20d?= =?UTF-8?q?a=20Secret=C3=A1ria=20por=20sess=C3=A3o=20na=20thin=20sidebar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Botão Power na thin sidebar do /wa-inbox age SÓ na sessão/número aberto (verde=on, vermelho pulsante=off). Abre modal com textarea de motivo (obrigatório p/ desligar), histórico de quem ligou/desligou e aviso de rate-limit (1x/hora). secSessionPowerApi + SessaoSecretariaPower.tsx. Co-Authored-By: Claude Opus 4.8 --- .../components/SessaoSecretariaPower.tsx | 134 ++++++++++++++++++ .../views/newwhats/components/ThinSidebar.tsx | 3 + .../views/newwhats/services/secretariaApi.ts | 16 +++ 3 files changed, 153 insertions(+) create mode 100644 frontend/views/newwhats/components/SessaoSecretariaPower.tsx diff --git a/frontend/views/newwhats/components/SessaoSecretariaPower.tsx b/frontend/views/newwhats/components/SessaoSecretariaPower.tsx new file mode 100644 index 0000000..91ae9fa --- /dev/null +++ b/frontend/views/newwhats/components/SessaoSecretariaPower.tsx @@ -0,0 +1,134 @@ +// Liga/desliga a Secretária IA APENAS desta sessão (instância aberta no /wa-inbox). +// Rate-limit 1x/hora, motivo obrigatório ao desligar, histórico de quem apertou. +// Fica na thin sidebar; abre um modal com o textarea de motivo + histórico. +import React, { useState, useEffect, useCallback } from 'react'; +import { Power, X, Loader2, History, AlertTriangle } from 'lucide-react'; +import { secSessionPowerApi, SessionPower } from '../services/secretariaApi'; + +const userName = (() => { + try { return JSON.parse(localStorage.getItem('SCOREODONTO_USER_DATA') || '{}')?.nome || 'Usuário'; } + catch { return 'Usuário'; } +})(); + +const fmt = (iso?: string | null) => { + if (!iso) return ''; + try { return new Date(iso).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' }); } + catch { return ''; } +}; + +export const SessaoSecretariaPower: React.FC<{ instanceId: string | null }> = ({ instanceId }) => { + const [sp, setSp] = useState(null); + const [open, setOpen] = useState(false); + const [reason, setReason] = useState(''); + const [busy, setBusy] = useState(false); + const [erro, setErro] = useState(null); + + const carregar = useCallback(async () => { + if (!instanceId) return; + try { setSp(await secSessionPowerApi.get(instanceId)); } catch { /* silencioso */ } + }, [instanceId]); + + useEffect(() => { carregar(); }, [carregar]); + + if (!instanceId) return null; + const enabled = sp ? sp.enabled : true; + + const toggle = async () => { + if (!sp?.can_toggle) return; + const proximo = !enabled; + if (proximo === false && !reason.trim()) { setErro('Escreva o motivo para desligar.'); return; } + setBusy(true); setErro(null); + try { + await secSessionPowerApi.set(instanceId, proximo, reason.trim(), userName); + setReason(''); + await carregar(); + } catch (e: any) { + setErro(e?.message || 'Não foi possível alterar.'); + await carregar(); + } finally { setBusy(false); } + }; + + return ( + <> + + + {open && ( +
setOpen(false)}> +
e.stopPropagation()}> +
+
+
+
+

Secretária desta sessão

+

{enabled ? 'Ligada' : 'Desligada'} · só este número

+
+
+ +
+ + {erro &&
{erro}
} + +
+ {!enabled && sp?.reason && ( +
+ Desligada. Motivo: {sp.reason} + {sp.changed_by &&
por {sp.changed_by} · {fmt(sp.changed_at)}
} +
+ )} + + {/* Motivo (só ao desligar) */} + {enabled && ( +
+ +