'use client'; import * as React from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Plus, Trash2, UploadCloud } from 'lucide-react'; import { PageHeader } from '@/components/layout/page-header'; import { RequirePermission } from '@/components/require-permission'; import { DataTable, type DataTableColumn } from '@/components/data-table/data-table'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog'; import { useToast } from '@/components/ui/toast'; import { useAuth } from '@/hooks/use-auth'; import { useDebounce } from '@/hooks/use-debounce'; import { suppressionService } from '@/services/suppression'; import type { SuppressionEntry } from '@/types'; import { errorMessage } from '@/lib/error-message'; import { formatDateTime } from '@/lib/utils'; function AddDialog({ open, onOpenChange }: { open: boolean; onOpenChange: (v: boolean) => void }) { const queryClient = useQueryClient(); const { toast } = useToast(); const [phone, setPhone] = React.useState(''); const [reason, setReason] = React.useState(''); React.useEffect(() => { if (open) { setPhone(''); setReason(''); } }, [open]); const mutation = useMutation({ mutationFn: () => suppressionService.add(phone, reason || undefined), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['suppression'] }); toast({ title: 'Número bloqueado', variant: 'success' }); onOpenChange(false); }, onError: (err) => toast({ title: 'Erro', description: errorMessage(err), variant: 'destructive' }), }); return ( Bloquear número { e.preventDefault(); mutation.mutate(); }} className="flex flex-col gap-4" > Telefone setPhone(e.target.value)} /> Motivo (opcional) setReason(e.target.value)} /> Bloquear ); } function SuppressionContent() { const { can } = useAuth(); const { toast } = useToast(); const queryClient = useQueryClient(); const [search, setSearch] = React.useState(''); const debouncedSearch = useDebounce(search); const [page, setPage] = React.useState(1); const [dialogOpen, setDialogOpen] = React.useState(false); const fileInputRef = React.useRef(null); const { data, isLoading, isError, refetch } = useQuery({ queryKey: ['suppression', debouncedSearch, page], queryFn: () => suppressionService.query({ search: debouncedSearch || undefined, page, pageSize: 25 }), }); const remove = useMutation({ mutationFn: ({ id, removalReason }: { id: string; removalReason: string }) => suppressionService.remove(id, removalReason), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['suppression'] }); toast({ title: 'Número desbloqueado', variant: 'success' }); }, onError: (err) => toast({ title: 'Erro', description: errorMessage(err), variant: 'destructive' }), }); const importCsv = useMutation({ mutationFn: (file: File) => suppressionService.importCsv(file), onSuccess: (result) => { queryClient.invalidateQueries({ queryKey: ['suppression'] }); toast({ title: 'Importação concluída', description: `${result.added} adicionados, ${result.invalid} inválidos de ${result.total}.`, variant: 'success', }); }, onError: (err) => toast({ title: 'Erro', description: errorMessage(err), variant: 'destructive' }), }); const columns: DataTableColumn[] = [ { key: 'phoneNormalized', header: 'Telefone', render: (r) => r.phoneNormalized }, { key: 'reason', header: 'Motivo', render: (r) => r.reason ?? '—' }, { key: 'createdAt', header: 'Adicionado em', render: (r) => formatDateTime(r.createdAt) }, { key: 'actions', header: '', className: 'text-right', render: (r) => can('campaigns.update') ? ( { const removalReason = window.prompt( `Motivo para desbloquear ${r.phoneNormalized} (obrigatório):`, ); if (removalReason && removalReason.trim().length > 0) { remove.mutate({ id: r.id, removalReason: removalReason.trim() }); } }} > ) : null, }, ]; return ( <> { const file = e.target.files?.[0]; if (file) importCsv.mutate(file); e.target.value = ''; }} /> fileInputRef.current?.click()}> Importar CSV setDialogOpen(true)}> Bloquear número ) } /> refetch()} rowKey={(r) => r.id} searchValue={search} onSearchChange={(v) => { setSearch(v); setPage(1); }} searchPlaceholder="Buscar por telefone..." page={data?.page ?? page} pageSize={data?.pageSize ?? 25} total={data?.total ?? 0} onPageChange={setPage} emptyMessage="Nenhum número bloqueado." /> > ); } export default function SuppressionPage() { return ( ); }