'use client'; import * as React from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Plus } from 'lucide-react'; import { CANAL_TEMPLATE_LABEL, CANAIS_TEMPLATE, PLACEHOLDERS_POR_TIPO, TIPO_TEMPLATE_LABEL, TIPOS_TEMPLATE, createAlertTemplate, deleteAlertTemplate, listAlertTemplates, updateAlertTemplate, type AlertTemplate, type CanalTemplate, type TipoTemplate, } from '@/lib/api/alert-templates'; import { alertTemplateSchema, type AlertTemplateFormValues } from '@/lib/validators/alert-templates'; import { ApiError } from '@/lib/api/client'; import { toast } from '@/lib/toast'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { DataTable, type DataTableColumn } from '@/components/ui/data-table'; import { FormField } from '@/components/ui/form-field'; import { Input } from '@/components/ui/input'; import { ResponsiveDialog } from '@/components/ui/responsive-dialog'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { Textarea } from '@/components/ui/textarea'; import { ConfirmDialog } from '@/components/domain/confirm-dialog'; const TIPO_OPTIONS: { value: TipoTemplate | 'todos'; label: string }[] = [ { value: 'todos', label: 'Todos os tipos' }, ...TIPOS_TEMPLATE.map((tipo) => ({ value: tipo, label: TIPO_TEMPLATE_LABEL[tipo] })), ]; const CANAL_OPTIONS: { value: CanalTemplate | 'todos'; label: string }[] = [ { value: 'todos', label: 'Todos os canais' }, ...CANAIS_TEMPLATE.map((canal) => ({ value: canal, label: CANAL_TEMPLATE_LABEL[canal] })), ]; const LIMIT = 20; export default function AlertTemplatesPage() { const queryClient = useQueryClient(); const [tipo, setTipo] = React.useState('todos'); const [canal, setCanal] = React.useState('todos'); const [page, setPage] = React.useState(1); const [editing, setEditing] = React.useState(null); const [formOpen, setFormOpen] = React.useState(false); const [deleting, setDeleting] = React.useState(null); const { data, isLoading } = useQuery({ queryKey: ['master', 'alert-templates', { tipo, canal, page }], queryFn: () => listAlertTemplates({ tipo: tipo === 'todos' ? undefined : tipo, canal: canal === 'todos' ? undefined : canal, page, limit: LIMIT, }), placeholderData: (prev) => prev, }); const form = useForm({ resolver: zodResolver(alertTemplateSchema) }); const corpoFieldRef = React.useRef(null); const { ref: corpoRegisterRef, ...corpoRegister } = form.register('corpo'); const watchedTipo = form.watch('tipo'); const watchedCanal = form.watch('canal'); function openCreate() { setEditing(null); form.reset({ tipo: 'mensalidade_tenant', canal: 'whatsapp', assunto: '', corpo: '', ativo: true }); setFormOpen(true); } function openEdit(template: AlertTemplate) { setEditing(template); form.reset({ tipo: template.tipo, canal: template.canal, assunto: template.assunto ?? '', corpo: template.corpo, ativo: template.ativo, }); setFormOpen(true); } function insertPlaceholder(chave: string) { const token = `{{${chave}}}`; const el = corpoFieldRef.current; const current = form.getValues('corpo') ?? ''; if (!el) { form.setValue('corpo', current + token, { shouldValidate: true }); return; } const start = el.selectionStart ?? current.length; const end = el.selectionEnd ?? current.length; const next = current.slice(0, start) + token + current.slice(end); form.setValue('corpo', next, { shouldValidate: true }); requestAnimationFrame(() => { el.focus(); const pos = start + token.length; el.setSelectionRange(pos, pos); }); } const saveMutation = useMutation({ mutationFn: (values: AlertTemplateFormValues) => { const payload = { ...values, assunto: values.canal === 'email' ? values.assunto : undefined }; return editing ? updateAlertTemplate(editing.id, payload) : createAlertTemplate(payload); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['master', 'alert-templates'] }); toast.success(editing ? 'Template atualizado.' : 'Template criado.'); setFormOpen(false); }, onError: (err) => { toast.error(err instanceof ApiError ? err.message : 'Não foi possível salvar o template.'); }, }); const deleteMutation = useMutation({ mutationFn: (id: string) => deleteAlertTemplate(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['master', 'alert-templates'] }); toast.success('Template desativado.'); setDeleting(null); }, onError: (err) => { toast.error(err instanceof ApiError ? err.message : 'Não foi possível desativar o template.'); setDeleting(null); }, }); const columns: DataTableColumn[] = [ { id: 'tipo', header: 'Tipo', primary: true, cell: (row) => TIPO_TEMPLATE_LABEL[row.tipo] }, { id: 'canal', header: 'Canal', primary: true, cell: (row) => CANAL_TEMPLATE_LABEL[row.canal] }, { id: 'corpo', header: 'Prévia', cell: (row) => {row.corpo}, }, { id: 'status', header: 'Status', cell: (row) => {row.ativo ? 'Ativo' : 'Inativo'}, }, ]; const placeholders = watchedTipo ? PLACEHOLDERS_POR_TIPO[watchedTipo] : []; return (

Templates de alerta

Padrões de mensagem da plataforma

row.id} loading={isLoading} emptyTitle="Nenhum template cadastrado" emptyAction={ } rowActions={(row) => ( <> )} pagination={ data ? { page: data.meta.page, limit: data.meta.limit, total: data.meta.total, onPageChange: setPage } : undefined } /> } >
saveMutation.mutate(values))} >
{watchedCanal === 'email' && ( )}