diff --git a/src/modules/management/components/TemplateManagementPanel.jsx b/src/modules/management/components/TemplateManagementPanel.jsx index f80d65f..ee65763 100644 --- a/src/modules/management/components/TemplateManagementPanel.jsx +++ b/src/modules/management/components/TemplateManagementPanel.jsx @@ -1,445 +1,712 @@ -import { useEffect, useMemo, useState } from 'react'; -import { DataPanel } from './DataPanel'; +import { useState, useEffect } from 'react'; import { - approveTemplateByAdmin, - deleteTemplate, listTemplates, - rejectTemplateByAdmin, saveTemplate, + updateTemplate, + deleteTemplate, + submitTemplateToMeta, + syncTemplatesFromMeta, } from '../services/templateService'; -const fieldStyle = { - width: '100%', - border: '1px solid var(--color-border)', - borderRadius: '14px', - padding: '0.75rem 0.85rem', - background: '#fff', - color: 'var(--color-text)', - fontWeight: 600, +// ── Constants ────────────────────────────────────────────────────────────── + +const META_STATUS_MAP = { + APPROVED: { label: 'Aprovado', color: '#1a7a44', bg: '#edfaf3', border: '#b6e8c8' }, + PENDING: { label: 'Aguardando Meta', color: '#a05a00', bg: '#fff8f0', border: '#f5d9a8' }, + REJECTED: { label: 'Rejeitado', color: '#b00020', bg: '#fff2f2', border: '#f5c6c6' }, + PAUSED: { label: 'Pausado', color: '#6b4700', bg: '#fffbf0', border: '#f5e0a8' }, + DISABLED: { label: 'Desabilitado', color: '#555', bg: '#f5f5f5', border: '#ddd' }, }; -const statusMeta = { - approved: { - label: 'Aprovado pela Meta', - background: 'rgba(34, 197, 94, 0.12)', - color: '#15803d', - }, - meta_review: { - label: 'Em análise pela Meta', - background: 'rgba(229, 162, 42, 0.16)', - color: '#8a5a00', - }, - admin_review: { - label: 'Aguardando aprovação do admin', - background: 'rgba(0, 49, 80, 0.1)', - color: 'var(--color-primary)', - }, - rejected: { - label: 'Reprovado pelo admin', - background: 'rgba(181, 31, 31, 0.1)', - color: 'var(--color-secondary)', - }, -}; +const DRAFT_STATUS = { label: 'Rascunho', color: '#555', bg: '#f0f2f5', border: '#dde1e7' }; -const templateCategories = [ - { id: 'UTILITY', label: 'UTILITY', detail: 'Confirmações, lembretes e atualizações', cost: 'Menor custo' }, - { id: 'MARKETING', label: 'MARKETING', detail: 'Promoções, ofertas e engajamento', cost: 'Maior custo' }, - { id: 'AUTHENTICATION', label: 'AUTHENTICATION', detail: 'OTP e códigos de verificação', cost: 'Menor custo' }, +function getMetaStatus(t) { + return t.meta_status ? (META_STATUS_MAP[t.meta_status] || DRAFT_STATUS) : DRAFT_STATUS; +} + +const CATEGORIES = [ + { value: 'UTILITY', label: 'Utilidade' }, + { value: 'MARKETING', label: 'Marketing' }, + { value: 'AUTHENTICATION', label: 'Autenticação' }, ]; -function getTemplateStatus(template) { - return statusMeta[template.status] || statusMeta.approved; +const LANGUAGES = [ + { value: 'pt_BR', label: 'Português (Brasil)' }, + { value: 'en_US', label: 'English (US)' }, + { value: 'es', label: 'Español' }, +]; + +const BUTTON_TYPES = [ + { value: 'QUICK_REPLY', label: 'Resposta rápida' }, + { value: 'URL', label: 'Abrir URL' }, + { value: 'PHONE_NUMBER', label: 'Ligar' }, +]; + +const EMPTY_FORM = { + name: '', language: 'pt_BR', category: 'UTILITY', areaId: '', + headerType: '', headerText: '', content: '', + bodyVariables: [], footerText: '', buttons: [], +}; + +// ── Helpers ───────────────────────────────────────────────────────────────── + +function toMetaName(name) { + return String(name || '') + .normalize('NFD').replace(/[̀-ͯ]/g, '') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_+|_+$/g, '') + .substring(0, 60) || ''; } -function getRemainingMetaText(template) { - if (template.status !== 'meta_review' || !template.meta_submitted_at) return ''; - const submittedAt = new Date(template.meta_submitted_at).getTime(); - const approvedAt = submittedAt + 15 * 60 * 1000; - const remainingMs = approvedAt - Date.now(); - if (remainingMs <= 0) return 'Aprovação disponível ao atualizar.'; - const minutes = Math.ceil(remainingMs / 60000); - return `Aprovação em aproximadamente ${minutes} min.`; +function syncBodyVars(content, prevVars) { + const matches = [...content.matchAll(/\{\{(\d+)\}\}/g)]; + const indices = [...new Set(matches.map(m => Number(m[1])))].sort((a, b) => a - b); + return indices.map(i => prevVars.find(v => v.index === i) || { index: i, label: '', example: '' }); } -function renderTemplatePreview(content) { - return String(content || 'Digite a mensagem do template...') - .replace(/\{nome\}/gi, 'Ana Paula') - .replace(/\{cliente\}/gi, 'Ana Paula') - .replace(/\{data\}/gi, '26/05/2026') - .replace(/\{link\}/gi, 'https://sothis.com.br/rh') - .replace(/\{variavel\}/gi, 'informação personalizada') - .replace(/\{variável\}/gi, 'informação personalizada'); +function renderPreviewBody(content, bodyVariables) { + return content.replace(/\{\{(\d+)\}\}/g, (_, n) => { + const v = bodyVariables.find(v => v.index === Number(n)); + return v?.example || (v?.label ? `[${v.label}]` : `{{${n}}}`); + }); } -export function TemplateManagementPanel({ - areas = [], - mode = 'admin', - managedAreaNames = [], - isMobile = false, -}) { - const [templates, setTemplates] = useState([]); - const [selectedArea, setSelectedArea] = useState('all'); - const [form, setForm] = useState({ name: '', content: '', areaId: '', category: 'UTILITY' }); - const [statusMessage, setStatusMessage] = useState(''); - const [isLoading, setIsLoading] = useState(true); +// ── Styles ──────────────────────────────────────────────────────────────── - const isAdmin = mode === 'admin'; - const visibleAreas = isAdmin - ? areas - : areas.filter((area) => managedAreaNames.includes(area.nome)); +const fieldStyle = { + width: '100%', border: '1px solid var(--color-border)', borderRadius: 10, + padding: '0.6rem 0.75rem', fontSize: '0.9rem', fontWeight: 600, + color: 'var(--color-text)', background: '#fff', boxSizing: 'border-box', +}; - const filteredTemplates = useMemo(() => { - return templates.filter((template) => { - const areaMatches = selectedArea === 'all' || String(template.area_id || '') === selectedArea; - const supervisorAreaMatches = - isAdmin || - !template.area_nome || - managedAreaNames.includes(template.area_nome); - return areaMatches && supervisorAreaMatches; - }); - }, [templates, selectedArea, isAdmin, managedAreaNames]); +const labelStyle = { + display: 'block', fontSize: '0.78rem', fontWeight: 800, + color: 'var(--color-text-soft)', textTransform: 'uppercase', + letterSpacing: '0.04em', marginBottom: '0.3rem', +}; - async function loadTemplates() { - setIsLoading(true); - try { - const data = await listTemplates(); - setTemplates(Array.isArray(data) ? data : []); - setStatusMessage(''); - } catch (error) { - setStatusMessage(error.message); - } finally { - setIsLoading(false); - } - } +function pill(bg, color) { + return { fontSize: '0.7rem', fontWeight: 700, background: bg, color, borderRadius: 6, padding: '0.15rem 0.45rem' }; +} - useEffect(() => { - loadTemplates(); - }, []); +function actionBtn(bg, color, disabled = false) { + return { + border: 'none', borderRadius: 8, padding: '0.4rem 0.75rem', + background: disabled ? '#f0f2f5' : bg, color: disabled ? '#aaa' : color, + fontWeight: 800, fontSize: '0.8rem', cursor: disabled ? 'not-allowed' : 'pointer', + }; +} - async function submitTemplate(event) { - event.preventDefault(); - const name = form.name.trim(); - const content = form.content.trim(); - if (!name || !content) return; +// ── WhatsApp Preview ────────────────────────────────────────────────────── - try { - await saveTemplate({ - name, - content, - category: form.category, - areaId: Number(form.areaId) || null, - requestedByRole: isAdmin ? 'admin' : 'supervisor', - }); - setForm({ name: '', content: '', areaId: '', category: 'UTILITY' }); - setStatusMessage( - isAdmin - ? 'Template enviado para aprovação.' - : 'Template enviado para aprovação do admin.', - ); - await loadTemplates(); - } catch (error) { - setStatusMessage(error.message); - } - } - - async function approveTemplate(templateId) { - try { - await approveTemplateByAdmin(templateId); - setStatusMessage('Template aprovado pelo admin e enviado para análise da Meta.'); - await loadTemplates(); - } catch (error) { - setStatusMessage(error.message); - } - } - - async function rejectTemplate(templateId) { - try { - await rejectTemplateByAdmin(templateId); - setStatusMessage('Template reprovado pelo admin.'); - await loadTemplates(); - } catch (error) { - setStatusMessage(error.message); - } - } - - async function removeTemplate(templateId) { - try { - await deleteTemplate(templateId); - setStatusMessage('Template excluído.'); - await loadTemplates(); - } catch (error) { - setStatusMessage(error.message); - } - } +function WhatsAppPreview({ form }) { + const { headerType, headerText, content, bodyVariables, footerText, buttons } = form; + const bodyText = renderPreviewBody(content || '', bodyVariables); return ( -
-
- -
+
+ Pré-visualização +
+
+
+
- -
-
-
- setForm((current) => ({ ...current, name: event.target.value }))} - placeholder="Identificador do template" - style={fieldStyle} - /> - - + {headerType === 'TEXT' && headerText && ( +

+ {headerText} +

+ )} + + {headerType === 'IMAGE' && ( +
+ 📷 Imagem
+ )} -
- {['{nome}', '{data}', '{link}', '{variavel}'].map((variable) => ( - - ))} -
+

+ {bodyText || 'Corpo do template…'} +

-