|
|
|
@@ -0,0 +1,449 @@
|
|
|
|
|
import { useMemo, useState, type ChangeEvent } from "react";
|
|
|
|
|
import {
|
|
|
|
|
AlertDialog,
|
|
|
|
|
AlertDialogAction,
|
|
|
|
|
AlertDialogCancel,
|
|
|
|
|
AlertDialogContent,
|
|
|
|
|
AlertDialogDescription,
|
|
|
|
|
AlertDialogFooter,
|
|
|
|
|
AlertDialogHeader,
|
|
|
|
|
AlertDialogTitle,
|
|
|
|
|
} from "@/components/ui/alert-dialog";
|
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
|
|
|
import { Input } from "@/components/ui/input";
|
|
|
|
|
import { Label } from "@/components/ui/label";
|
|
|
|
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|
|
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
|
|
|
import { Copy, Download, FileText, Loader2, Plus, Upload, X } from "lucide-react";
|
|
|
|
|
import { InteractiveHoverButton } from "@/components/ui/interactive-hover-button";
|
|
|
|
|
import { toast } from "sonner";
|
|
|
|
|
import { commanderCsvService, type CommanderCsvMode } from "@/services/commanderCsv";
|
|
|
|
|
|
|
|
|
|
type HeaderItem = {
|
|
|
|
|
original: string;
|
|
|
|
|
slug: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toSlug = (value: string) =>
|
|
|
|
|
value
|
|
|
|
|
.normalize("NFD")
|
|
|
|
|
.replace(/[\u0300-\u036f]/g, "")
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
.replace(/[^a-z0-9]+/g, "_")
|
|
|
|
|
.replace(/_+/g, "_")
|
|
|
|
|
.replace(/^_+|_+$/g, "");
|
|
|
|
|
|
|
|
|
|
export function CommanderCsvView() {
|
|
|
|
|
const [mode, setMode] = useState<CommanderCsvMode>("pdf");
|
|
|
|
|
const [headerInput, setHeaderInput] = useState("");
|
|
|
|
|
const [headers, setHeaders] = useState<HeaderItem[]>([]);
|
|
|
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
|
|
|
const [content, setContent] = useState("");
|
|
|
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
|
|
|
const [isHealthLoading, setIsHealthLoading] = useState(false);
|
|
|
|
|
const [healthStatus, setHealthStatus] = useState<"idle" | "ok" | "error">("idle");
|
|
|
|
|
const [responseData, setResponseData] = useState<unknown>(null);
|
|
|
|
|
const [isClearHeadersOpen, setIsClearHeadersOpen] = useState(false);
|
|
|
|
|
const [lastSentAt, setLastSentAt] = useState<Date | null>(null);
|
|
|
|
|
const [lastSentMode, setLastSentMode] = useState<CommanderCsvMode | null>(null);
|
|
|
|
|
|
|
|
|
|
const headersCsv = useMemo(() => headers.map((header) => header.slug).join(","), [headers]);
|
|
|
|
|
|
|
|
|
|
const addHeader = () => {
|
|
|
|
|
const original = headerInput.trim();
|
|
|
|
|
if (!original) return;
|
|
|
|
|
|
|
|
|
|
const slug = toSlug(original);
|
|
|
|
|
if (!slug) {
|
|
|
|
|
toast.error("Cabecalho invalido. Digite letras ou numeros.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (headers.some((header) => header.slug === slug)) {
|
|
|
|
|
toast.error("Esse cabecalho ja foi adicionado.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setHeaders((prev) => [...prev, { original, slug }]);
|
|
|
|
|
setHeaderInput("");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const removeHeader = (slug: string) => {
|
|
|
|
|
setHeaders((prev) => prev.filter((header) => header.slug !== slug));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const file = event.target.files?.[0] ?? null;
|
|
|
|
|
if (!file) return;
|
|
|
|
|
|
|
|
|
|
const fileName = file.name.toLowerCase();
|
|
|
|
|
if (!fileName.endsWith(".pdf")) {
|
|
|
|
|
toast.error("Envie apenas arquivo PDF.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setSelectedFile(file);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const runHealthcheck = async () => {
|
|
|
|
|
setIsHealthLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
await commanderCsvService.healthcheck();
|
|
|
|
|
setHealthStatus("ok");
|
|
|
|
|
toast.success("API online.");
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
setHealthStatus("error");
|
|
|
|
|
const message = error && typeof error === "object" && "message" in error
|
|
|
|
|
? (error as { message: string }).message
|
|
|
|
|
: "Falha no healthcheck.";
|
|
|
|
|
toast.error(message);
|
|
|
|
|
} finally {
|
|
|
|
|
setIsHealthLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const submit = async () => {
|
|
|
|
|
if (headers.length === 0) {
|
|
|
|
|
toast.error("Adicione pelo menos um cabecalho.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mode === "pdf" && !selectedFile) {
|
|
|
|
|
toast.error("Selecione um PDF para enviar.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mode === "text" && !content.trim()) {
|
|
|
|
|
toast.error("Preencha o conteudo de texto.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setIsSubmitting(true);
|
|
|
|
|
try {
|
|
|
|
|
const result = mode === "pdf"
|
|
|
|
|
? await commanderCsvService.pdf2csv(selectedFile as File, headersCsv)
|
|
|
|
|
: await commanderCsvService.txt2csv(content.trim(), headersCsv);
|
|
|
|
|
|
|
|
|
|
setResponseData(result);
|
|
|
|
|
setLastSentAt(new Date());
|
|
|
|
|
setLastSentMode(mode);
|
|
|
|
|
toast.success("Processamento concluido.");
|
|
|
|
|
} catch (error: unknown) {
|
|
|
|
|
const message = error && typeof error === "object" && "message" in error
|
|
|
|
|
? (error as { message: string }).message
|
|
|
|
|
: "Erro ao enviar dados para API.";
|
|
|
|
|
toast.error(message);
|
|
|
|
|
} finally {
|
|
|
|
|
setIsSubmitting(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const responseContent = useMemo(() => {
|
|
|
|
|
if (responseData && typeof responseData === "object" && "content" in responseData) {
|
|
|
|
|
const content = (responseData as { content?: unknown }).content;
|
|
|
|
|
if (typeof content === "string") return content;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}, [responseData]);
|
|
|
|
|
|
|
|
|
|
const copyResponse = async () => {
|
|
|
|
|
const textToCopy = responseContent ?? (responseData ? JSON.stringify(responseData, null, 2) : "");
|
|
|
|
|
if (!textToCopy) {
|
|
|
|
|
toast.error("Nao ha resposta para copiar.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await navigator.clipboard.writeText(textToCopy);
|
|
|
|
|
toast.success("Resposta copiada.");
|
|
|
|
|
} catch {
|
|
|
|
|
toast.error("Falha ao copiar resposta.");
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const downloadCsv = () => {
|
|
|
|
|
if (!responseContent) {
|
|
|
|
|
toast.error("Nao ha CSV para baixar.");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const utf8Bom = "\uFEFF";
|
|
|
|
|
const blob = new Blob([utf8Bom, responseContent], { type: "text/csv;charset=utf-8;" });
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
|
const anchor = document.createElement("a");
|
|
|
|
|
anchor.href = url;
|
|
|
|
|
anchor.download = `commander-csv-${new Date().toISOString().slice(0, 19).replace(/[:T]/g, "-")}.csv`;
|
|
|
|
|
document.body.appendChild(anchor);
|
|
|
|
|
anchor.click();
|
|
|
|
|
document.body.removeChild(anchor);
|
|
|
|
|
URL.revokeObjectURL(url);
|
|
|
|
|
toast.success("Download do CSV iniciado.");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const sendBlockReason = useMemo(() => {
|
|
|
|
|
if (headers.length === 0) return "Adicione pelo menos um cabecalho.";
|
|
|
|
|
if (mode === "pdf" && !selectedFile) return "Selecione um PDF para envio.";
|
|
|
|
|
if (mode === "text" && !content.trim()) return "Preencha o conteudo de texto.";
|
|
|
|
|
return null;
|
|
|
|
|
}, [headers.length, mode, selectedFile, content]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex flex-col h-full bg-background pb-16 md:pb-0">
|
|
|
|
|
<div className="border-b border-border p-3 md:p-6">
|
|
|
|
|
<div className="flex flex-col gap-2 md:flex-row md:items-center md:justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<h1 className="text-xl md:text-3xl font-bold">Commander CSV</h1>
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
Configure os cabecalhos, escolha o modo de envio e gere o CSV com mais controle.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<div
|
|
|
|
|
className={`inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-semibold ${
|
|
|
|
|
healthStatus === "ok"
|
|
|
|
|
? "border-emerald-500/30 bg-emerald-500/15 text-emerald-700 dark:text-emerald-300"
|
|
|
|
|
: healthStatus === "error"
|
|
|
|
|
? "border-destructive/30 bg-destructive/10 text-destructive"
|
|
|
|
|
: "border-border bg-muted/40 text-muted-foreground"
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
<span
|
|
|
|
|
className={`h-2 w-2 rounded-full ${
|
|
|
|
|
healthStatus === "ok"
|
|
|
|
|
? "bg-emerald-500 animate-online-pulse"
|
|
|
|
|
: healthStatus === "error"
|
|
|
|
|
? "bg-destructive"
|
|
|
|
|
: "bg-muted-foreground/70"
|
|
|
|
|
}`}
|
|
|
|
|
/>
|
|
|
|
|
{healthStatus === "ok" ? "API online" : healthStatus === "error" ? "API indisponivel" : "Status da API"}
|
|
|
|
|
</div>
|
|
|
|
|
<Button variant="outline" onClick={runHealthcheck} disabled={isHealthLoading}>
|
|
|
|
|
{isHealthLoading ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : null}
|
|
|
|
|
Healthcheck
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex-1 overflow-auto p-3 md:p-6">
|
|
|
|
|
<div className="mx-auto w-full max-w-6xl space-y-6">
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Cabecalhos</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
Defina os campos do CSV. O sistema gera slug automaticamente em minusculo com underline.
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-4">
|
|
|
|
|
<div className="flex flex-wrap items-center justify-between gap-2">
|
|
|
|
|
<Input
|
|
|
|
|
value={headerInput}
|
|
|
|
|
onChange={(event) => setHeaderInput(event.target.value)}
|
|
|
|
|
placeholder="Ex: Data de Aniversario"
|
|
|
|
|
className="min-w-[220px] flex-1"
|
|
|
|
|
onKeyDown={(event) => {
|
|
|
|
|
if (event.key === "Enter") {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
addHeader();
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
variant="outline"
|
|
|
|
|
onClick={() => setIsClearHeadersOpen(true)}
|
|
|
|
|
disabled={headers.length === 0}
|
|
|
|
|
>
|
|
|
|
|
Limpar todos
|
|
|
|
|
</Button>
|
|
|
|
|
<Button type="button" onClick={addHeader} disabled={!headerInput.trim()}>
|
|
|
|
|
<Plus className="w-4 h-4 mr-1" />
|
|
|
|
|
Adicionar
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-wrap gap-2 rounded-xl border bg-muted/20 p-3">
|
|
|
|
|
{headers.length === 0 ? (
|
|
|
|
|
<p className="text-sm text-muted-foreground">
|
|
|
|
|
Adicione seu primeiro cabecalho para habilitar o envio.
|
|
|
|
|
</p>
|
|
|
|
|
) : (
|
|
|
|
|
headers.map((header) => (
|
|
|
|
|
<div
|
|
|
|
|
key={header.slug}
|
|
|
|
|
className="group inline-flex items-center gap-2 rounded-full border border-primary/20 bg-primary/5 px-3 py-1.5 shadow-sm transition-colors hover:border-primary/40 hover:bg-primary/10"
|
|
|
|
|
>
|
|
|
|
|
<p className="text-xs font-semibold text-foreground">{header.slug}</p>
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon"
|
|
|
|
|
onClick={() => removeHeader(header.slug)}
|
|
|
|
|
title="Remover cabecalho"
|
|
|
|
|
className="h-5 w-5 rounded-full text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
|
|
|
|
|
>
|
|
|
|
|
<X className="h-3 w-3" />
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
))
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<CardTitle>Envio</CardTitle>
|
|
|
|
|
<CardDescription>
|
|
|
|
|
Alterne entre PDF ou Texto com os mesmos cabecalhos configurados.
|
|
|
|
|
</CardDescription>
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent className="space-y-6">
|
|
|
|
|
<RadioGroup
|
|
|
|
|
value={mode}
|
|
|
|
|
onValueChange={(value) => setMode(value as CommanderCsvMode)}
|
|
|
|
|
className="grid gap-3 sm:grid-cols-2"
|
|
|
|
|
>
|
|
|
|
|
<label className="flex cursor-pointer items-center gap-3 rounded-md border p-4">
|
|
|
|
|
<RadioGroupItem value="pdf" id="csv-mode-pdf" />
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium">Modo PDF</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground">Upload de arquivo PDF + headers</p>
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
<label className="flex cursor-pointer items-center gap-3 rounded-md border p-4">
|
|
|
|
|
<RadioGroupItem value="text" id="csv-mode-text" />
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium">Modo Texto</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground">Conteudo em texto + headers</p>
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
</RadioGroup>
|
|
|
|
|
|
|
|
|
|
{mode === "pdf" ? (
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<Label htmlFor="csv-pdf-input">Arquivo PDF</Label>
|
|
|
|
|
<input
|
|
|
|
|
id="csv-pdf-input"
|
|
|
|
|
type="file"
|
|
|
|
|
accept=".pdf,application/pdf"
|
|
|
|
|
className="hidden"
|
|
|
|
|
onChange={handleFileChange}
|
|
|
|
|
/>
|
|
|
|
|
<label
|
|
|
|
|
htmlFor="csv-pdf-input"
|
|
|
|
|
className="flex cursor-pointer flex-col items-center justify-center gap-3 rounded-lg border-2 border-dashed p-8 text-center transition-colors hover:border-primary/50"
|
|
|
|
|
>
|
|
|
|
|
<Upload className="h-7 w-7 text-primary" />
|
|
|
|
|
<div>
|
|
|
|
|
<p className="text-sm font-medium">
|
|
|
|
|
{selectedFile ? selectedFile.name : "Clique para anexar um PDF"}
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
{selectedFile ? `${(selectedFile.size / 1024 / 1024).toFixed(2)} MB` : "Somente .pdf"}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
<Label htmlFor="csv-text-input">Conteudo para converter</Label>
|
|
|
|
|
<Textarea
|
|
|
|
|
id="csv-text-input"
|
|
|
|
|
value={content}
|
|
|
|
|
onChange={(event) => setContent(event.target.value)}
|
|
|
|
|
placeholder="Cole aqui os dados em texto que serao convertidos para CSV..."
|
|
|
|
|
rows={10}
|
|
|
|
|
className="resize-y min-h-[220px]"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<div className="sticky bottom-0 z-10 -mx-6 rounded-b-lg border-t bg-card/95 px-6 py-4 backdrop-blur">
|
|
|
|
|
{isSubmitting ? (
|
|
|
|
|
<div className="mb-3 flex items-center gap-2 rounded-md border border-primary/20 bg-primary/5 px-3 py-2 text-xs text-primary">
|
|
|
|
|
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
|
|
|
Processando arquivo e gerando CSV...
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
{sendBlockReason ?? "Tudo certo. Pronto para enviar."}
|
|
|
|
|
</p>
|
|
|
|
|
<InteractiveHoverButton
|
|
|
|
|
type="button"
|
|
|
|
|
onClick={submit}
|
|
|
|
|
disabled={isSubmitting || Boolean(sendBlockReason)}
|
|
|
|
|
className="min-w-44 disabled:opacity-50 disabled:pointer-events-none"
|
|
|
|
|
>
|
|
|
|
|
{isSubmitting ? "Enviando..." : "Enviar para API"}
|
|
|
|
|
</InteractiveHoverButton>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
|
|
|
|
|
<Card>
|
|
|
|
|
<CardHeader>
|
|
|
|
|
<div className="flex items-center justify-between gap-3">
|
|
|
|
|
<CardTitle className="text-lg flex items-center gap-2">
|
|
|
|
|
<FileText className="w-5 h-5" />
|
|
|
|
|
Resposta da API
|
|
|
|
|
</CardTitle>
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Button type="button" variant="outline" size="sm" onClick={copyResponse}>
|
|
|
|
|
<Copy className="w-4 h-4 mr-2" />
|
|
|
|
|
Copiar
|
|
|
|
|
</Button>
|
|
|
|
|
<Button type="button" variant="outline" size="sm" onClick={downloadCsv} disabled={!responseContent}>
|
|
|
|
|
<Download className="w-4 h-4 mr-2" />
|
|
|
|
|
Baixar CSV
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{lastSentAt ? (
|
|
|
|
|
<p className="text-xs text-muted-foreground">
|
|
|
|
|
Ultimo envio: {lastSentAt.toLocaleTimeString("pt-BR")} ({lastSentMode === "pdf" ? "PDF" : "Texto"})
|
|
|
|
|
</p>
|
|
|
|
|
) : null}
|
|
|
|
|
</CardHeader>
|
|
|
|
|
<CardContent>
|
|
|
|
|
<Textarea
|
|
|
|
|
value={responseContent ?? (responseData ? JSON.stringify(responseData, null, 2) : "Nenhuma resposta ainda.")}
|
|
|
|
|
readOnly
|
|
|
|
|
className="min-h-[62vh] font-mono text-xs leading-relaxed resize-y"
|
|
|
|
|
/>
|
|
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<AlertDialog open={isClearHeadersOpen} onOpenChange={setIsClearHeadersOpen}>
|
|
|
|
|
<AlertDialogContent>
|
|
|
|
|
<AlertDialogHeader>
|
|
|
|
|
<AlertDialogTitle>Limpar todos os cabecalhos?</AlertDialogTitle>
|
|
|
|
|
<AlertDialogDescription>
|
|
|
|
|
Essa acao remove todos os cabecalhos adicionados nesta tela.
|
|
|
|
|
</AlertDialogDescription>
|
|
|
|
|
</AlertDialogHeader>
|
|
|
|
|
<AlertDialogFooter>
|
|
|
|
|
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
|
|
|
|
<AlertDialogAction
|
|
|
|
|
onClick={() => {
|
|
|
|
|
setHeaders([]);
|
|
|
|
|
setIsClearHeadersOpen(false);
|
|
|
|
|
}}
|
|
|
|
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
|
|
|
>
|
|
|
|
|
Limpar tudo
|
|
|
|
|
</AlertDialogAction>
|
|
|
|
|
</AlertDialogFooter>
|
|
|
|
|
</AlertDialogContent>
|
|
|
|
|
</AlertDialog>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|