omnichannel-frontend/src/modules/management/components/TemplateManagementPanel.jsx

348 lines
12 KiB
React
Raw Normal View History

import { useEffect, useMemo, useState } from 'react';
import { DataPanel } from './DataPanel';
import {
approveTemplateByAdmin,
deleteTemplate,
listTemplates,
rejectTemplateByAdmin,
saveTemplate,
} 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,
};
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)',
},
};
function getTemplateStatus(template) {
return statusMeta[template.status] || statusMeta.approved;
}
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 fake disponível ao atualizar.';
const minutes = Math.ceil(remainingMs / 60000);
return `Aprovação fake em aproximadamente ${minutes} min.`;
}
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: '' });
const [statusMessage, setStatusMessage] = useState('');
const [isLoading, setIsLoading] = useState(true);
const isAdmin = mode === 'admin';
const visibleAreas = isAdmin
? areas
: areas.filter((area) => managedAreaNames.includes(area.nome));
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]);
async function loadTemplates() {
setIsLoading(true);
try {
const data = await listTemplates();
setTemplates(Array.isArray(data) ? data : []);
setStatusMessage('');
} catch (error) {
setStatusMessage(error.message);
} finally {
setIsLoading(false);
}
}
useEffect(() => {
loadTemplates();
}, []);
async function submitTemplate(event) {
event.preventDefault();
const name = form.name.trim();
const content = form.content.trim();
if (!name || !content) return;
try {
await saveTemplate({
name,
content,
areaId: Number(form.areaId) || null,
requestedByRole: isAdmin ? 'admin' : 'supervisor',
});
setForm({ name: '', content: '', areaId: '' });
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 fake 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);
}
}
return (
<section style={{ display: 'grid', gap: '1rem' }}>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<label style={{ display: 'grid', gap: '0.35rem', width: isMobile ? '100%' : 280 }}>
<span style={{ fontWeight: 800 }}>Filtrar por especialidade</span>
<select value={selectedArea} onChange={(event) => setSelectedArea(event.target.value)} style={fieldStyle}>
<option value="all">Todas as especialidades</option>
{visibleAreas.map((area) => (
<option key={area.id} value={area.id}>
{area.nome}
</option>
))}
</select>
</label>
</div>
<DataPanel
title={isAdmin ? 'Templates WhatsApp' : 'Solicitar template'}
description={
isAdmin
? 'Crie templates e aprove solicitações de supervisores antes do envio fake para a Meta.'
: 'Templates enviados por supervisor passam primeiro pela aprovação do admin.'
}
>
<form onSubmit={submitTemplate} style={{ display: 'grid', gap: '0.85rem' }}>
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : 'minmax(0, 0.8fr) minmax(0, 0.7fr)', gap: '0.85rem' }}>
<input
type="text"
value={form.name}
onChange={(event) => setForm((current) => ({ ...current, name: event.target.value }))}
placeholder="Identificador do template"
style={fieldStyle}
/>
<select
value={form.areaId}
onChange={(event) => setForm((current) => ({ ...current, areaId: event.target.value }))}
style={fieldStyle}
>
<option value="">Sem especialidade</option>
{visibleAreas.map((area) => (
<option key={area.id} value={area.id}>
{area.nome}
</option>
))}
</select>
</div>
<textarea
value={form.content}
onChange={(event) => setForm((current) => ({ ...current, content: event.target.value }))}
placeholder="Mensagem do template. Ex: Olá, {nome}. Podemos seguir com seu atendimento por aqui?"
rows={4}
style={{ ...fieldStyle, resize: 'vertical', lineHeight: 1.5 }}
/>
<button
type="submit"
style={{
border: 'none',
borderRadius: 16,
padding: '0.9rem 1rem',
background: 'var(--color-primary)',
color: '#fff',
fontWeight: 800,
width: 'fit-content',
}}
>
{isAdmin ? 'Enviar para aprovação' : 'Enviar para admin'}
</button>
</form>
{statusMessage ? (
<div style={{ marginTop: '0.85rem', color: 'var(--color-primary)', fontWeight: 800 }}>
{statusMessage}
</div>
) : null}
</DataPanel>
<DataPanel title="Lista de templates" description={isLoading ? 'Carregando templates...' : 'Status do fluxo de aprovação.'}>
<div style={{ display: 'grid', gap: '0.75rem', maxHeight: 520, overflowY: 'auto', paddingRight: '0.2rem' }}>
{filteredTemplates.map((template) => {
const status = getTemplateStatus(template);
const remainingMetaText = getRemainingMetaText(template);
return (
<article
key={template.id}
style={{
border: '1px solid var(--color-border)',
borderRadius: 18,
padding: '1rem',
background: '#fff',
display: 'grid',
gap: '0.65rem',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', gap: '1rem', flexWrap: 'wrap' }}>
<div>
<strong style={{ display: 'block' }}>{template.name}</strong>
<span style={{ color: 'var(--color-text-soft)', fontSize: '0.9rem' }}>
{template.area_nome || 'Sem especialidade'}
</span>
</div>
<span
style={{
width: 'fit-content',
borderRadius: 999,
padding: '0.3rem 0.65rem',
background: status.background,
color: status.color,
fontWeight: 800,
fontSize: '0.82rem',
}}
>
{status.label}
</span>
</div>
<p style={{ margin: 0, color: 'var(--color-text-soft)', lineHeight: 1.5 }}>
{template.content}
</p>
{remainingMetaText ? (
<span style={{ color: '#8a5a00', fontWeight: 700 }}>{remainingMetaText}</span>
) : null}
{isAdmin ? (
<div style={{ display: 'flex', gap: '0.55rem', flexWrap: 'wrap' }}>
{template.status === 'admin_review' ? (
<>
<button
type="button"
onClick={() => approveTemplate(template.id)}
style={{
border: 'none',
borderRadius: 14,
padding: '0.75rem 0.9rem',
background: 'var(--color-primary)',
color: '#fff',
fontWeight: 800,
}}
>
Aprovar e enviar para Meta
</button>
<button
type="button"
onClick={() => rejectTemplate(template.id)}
style={{
border: 'none',
borderRadius: 14,
padding: '0.75rem 0.9rem',
background: 'rgba(181, 31, 31, 0.1)',
color: 'var(--color-secondary)',
fontWeight: 800,
}}
>
Reprovar
</button>
</>
) : null}
<button
type="button"
onClick={() => removeTemplate(template.id)}
style={{
border: '1px solid rgba(181, 31, 31, 0.22)',
borderRadius: 14,
padding: '0.75rem 0.9rem',
background: '#fff',
color: 'var(--color-secondary)',
fontWeight: 800,
}}
>
Excluir
</button>
</div>
) : null}
</article>
);
})}
{!filteredTemplates.length ? (
<span style={{ color: 'var(--color-text-soft)', fontWeight: 700 }}>
Nenhum template encontrado para o filtro atual.
</span>
) : null}
</div>
</DataPanel>
</section>
);
}