diff --git a/src/modules/attendance/pages/NewAttendancePage.jsx b/src/modules/attendance/pages/NewAttendancePage.jsx index 3592589..3b206fb 100644 --- a/src/modules/attendance/pages/NewAttendancePage.jsx +++ b/src/modules/attendance/pages/NewAttendancePage.jsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { BrandMark } from '../../../shared/components/BrandMark'; import { useViewport } from '../../../shared/hooks/useViewport'; @@ -117,15 +117,22 @@ function applyPhoneMask(value, countryId) { } function requiresUnsupportedTemplateFields(template) { - const allowedFields = new Set(['nome', 'cliente']); - const placeholders = String(template?.content || '').matchAll(/\{([a-zA-Z0-9_]+)\}/g); - return Array.from(placeholders).some((match) => !allowedFields.has(String(match[1]).toLowerCase())); + const allowedFields = new Set(['nome', 'cliente', 'data', 'link', 'variavel']); + const placeholders = String(template?.content || '').matchAll(/\{([^{}]+)\}/g); + return Array.from(placeholders).some((match) => { + const key = String(match[1]).trim().toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, ''); + return !allowedFields.has(key); + }); } -function renderTemplatePreview(content, form) { +function renderTemplatePreview(content, form, variables) { return String(content || '') .replace(/\{nome\}/gi, form.name.trim() || 'cliente') - .replace(/\{cliente\}/gi, form.name.trim() || 'cliente'); + .replace(/\{cliente\}/gi, form.name.trim() || 'cliente') + .replace(/\{data\}/gi, variables.date.trim() || '{data}') + .replace(/\{link\}/gi, variables.link.trim() || '{link}') + .replace(/\{variavel\}/gi, variables.custom.trim() || '{variavel}') + .replace(/\{variável\}/gi, variables.custom.trim() || '{variável}'); } function formatLastContact(value) { @@ -186,7 +193,7 @@ async function startWhatsappAttendance(payload) { return response.json(); } -export function NewAttendancePage() { +export function NewAttendancePage({ embedded = false }) { const navigate = useNavigate(); const { isWideDesktop, isDesktop, isTablet, isMobile } = useViewport(); const currentUser = getCurrentUser(); @@ -201,6 +208,7 @@ export function NewAttendancePage() { const [selectedTemplateId, setSelectedTemplateId] = useState(''); const [selectedCountryId, setSelectedCountryId] = useState('br'); const [form, setForm] = useState({ phone: '', name: '', company: '', note: '' }); + const [templateVariables, setTemplateVariables] = useState({ date: '', link: '', custom: '' }); const [isLoadingContacts, setIsLoadingContacts] = useState(false); const [isStarting, setIsStarting] = useState(false); const [error, setError] = useState(''); @@ -314,7 +322,7 @@ export function NewAttendancePage() { note: form.note, userId: currentUserId, }); - await startWhatsappAttendance({ + const startedAttendance = await startWhatsappAttendance({ to: saved.chat_id || chatId, templateId: Number(selectedTemplateId), userId: currentUserId, @@ -322,10 +330,14 @@ export function NewAttendancePage() { variables: { nome: form.name, cliente: form.name, + data: templateVariables.date, + link: templateVariables.link, + variavel: templateVariables.custom, + 'variável': templateVariables.custom, }, }); setError(''); - navigate(`/chat?chatId=${encodeURIComponent(saved.chat_id || chatId)}`); + navigate(`/chat?chatId=${encodeURIComponent(startedAttendance?.chatId || saved.chat_id || chatId)}`); } catch (err) { setError(err.message); } finally { @@ -333,20 +345,20 @@ export function NewAttendancePage() { } } - return ( -
-
+ const content = ( +
+ {!embedded ? (
+ ) : null}
+
+ + + +
+ {selectedTemplate ? (
- - Preview do template - - {renderTemplatePreview(selectedTemplate.content, form)} +
+ + Preview WhatsApp + +
+ {renderTemplatePreview(selectedTemplate.content, form, templateVariables)} + + 10:42 + +
+
) : null} @@ -668,7 +768,7 @@ export function NewAttendancePage() { Número: {buildInternationalPhone(form.phone, selectedCountryId) ? `+${buildInternationalPhone(form.phone, selectedCountryId)}` : 'Não informado'} - Empresa: {form.company || 'Não informada'} + Tag: {form.company || 'Não informada'} Origem: {selectedContactId ? 'Agenda' : 'Novo contato'} @@ -728,6 +828,17 @@ export function NewAttendancePage() {
+ ); + + if (embedded) { + return content; + } + + return ( +
+ {content}
); } + + diff --git a/src/modules/call/pages/CallPage.jsx b/src/modules/call/pages/CallPage.jsx index f238ba2..634db29 100644 --- a/src/modules/call/pages/CallPage.jsx +++ b/src/modules/call/pages/CallPage.jsx @@ -197,7 +197,7 @@ export function CallPage() { color: 'rgba(255, 255, 255, 0.72)', }} > - Gravação mock: Habilitada + Gravação: Habilitada ) : null} {canReply ? ( - + <> + + + ) : null} + ) : null} + + + {isAgent ? ( + + Fila: {node.area_nome || areasById.get(Number(node.area_id))?.nome || 'não definida'} + + ) : isClose ? ( + + {node.message_text || 'Fecha o atendimento sem enviar para agente.'} + + ) : ( + + {node.message_text || 'Sem mensagem configurada.'} + + )} + + {!isRoot && keywords.length ? ( +
+ {keywords.slice(0, 8).map((keyword) => ( + + {keyword} + + ))} +
+ ) : null} + +
+ {onEdit ? ( + + ) : null} + {!isRoot && onDelete ? ( + + ) : null} +
+ + + {node.children?.length ? ( + <> +
+
+ {node.children.map((child) => ( + + ))} +
+ + ) : null} +
+ ); +} + +function NodeModal({ mode, node, parent, areas, draft, onDraftChange, onClose, onSave }) { + if (!mode) return null; + const isEdit = mode === 'edit'; + const isRoot = node?.node_type === 'greeting'; + const isAgent = draft.nodeType === 'agent' || node?.node_type === 'agent'; + const isClose = draft.nodeType === 'close' || node?.node_type === 'close'; + const canChooseType = !isEdit; + + function change(key, value) { + onDraftChange((current) => ({ ...current, [key]: value })); + } + + return ( +
+
+
+
+

+ {isEdit ? 'Editar nó' : `Adicionar filho em ${parent?.title}`} +

+

+ Configure a mensagem, as palavras que ativam o caminho e o destino quando for terminal. +

+
+ +
+ + {canChooseType ? ( +
+ {[ + ['question', 'Adicionar pergunta'], + ['agent', 'Enviar para agente'], + ['close', 'Encerrar pelo bot'], + ].map(([type, label]) => ( + + ))} +
+ ) : null} + +
+ + + {!isRoot ? ( + + ) : null} + + {isAgent ? ( + + ) : isClose ? ( + <> +