armando cf16bf1fb6 Show integration health in the admin and keep clients on their own screen.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-16 03:43:18 -03:00

121 lines
3.9 KiB
TypeScript

"use client";
import { FormEvent, useEffect, useState } from "react";
import { api, apiForm } from "@/lib/api";
import { Modal, RegistryTable } from "@/components/registry-table";
type OperationFile = {
id: string;
name: string;
type: string;
sizeBytes: number;
createdAt: string;
};
function formatSize(bytes: number) {
if (bytes < 1024) {
return `${bytes} B`;
}
return `${(bytes / 1024).toFixed(1)} KB`;
}
export default function ArquivosPage() {
const [rows, setRows] = useState<OperationFile[]>([]);
const [file, setFile] = useState<File | null>(null);
const [open, setOpen] = useState(false);
const [error, setError] = useState("");
const [saving, setSaving] = useState(false);
async function load() {
setRows(await api<OperationFile[]>("/api/files"));
}
useEffect(() => {
load().catch((err: unknown) => setError(err instanceof Error ? err.message : "Erro"));
}, []);
function closeModal() {
setOpen(false);
setFile(null);
setError("");
}
async function onSubmit(event: FormEvent) {
event.preventDefault();
if (!file) {
setError("Selecione um arquivo.");
return;
}
setError("");
setSaving(true);
try {
const body = new FormData();
body.append("file", file);
await apiForm("/api/files", body);
closeModal();
await load();
} catch (err) {
setError(err instanceof Error ? err.message : "Não foi possível anexar.");
} finally {
setSaving(false);
}
}
return (
<section>
<p className="text-xs font-semibold uppercase tracking-[0.28em] text-[#82C85C]">Arquivos</p>
<h1 className="font-display mt-2 text-4xl font-extrabold uppercase">Anexos da operação</h1>
{error && !open ? <p className="mt-4 text-sm text-red-300">{error}</p> : null}
<RegistryTable
countLabel={`${rows.length} arquivo${rows.length === 1 ? "" : "s"}`}
columns={["Nome", "Tipo", "Tamanho", "Data", ""]}
isEmpty={rows.length === 0}
empty="Nenhum arquivo anexado."
action={
<button className="btn-sinka px-4 py-2 text-xs" type="button" onClick={() => setOpen(true)}>
Anexar
</button>
}
>
{rows.map((row) => (
<tr key={row.id} className="border-t border-white/10">
<td className="px-5 py-3 font-medium">{row.name}</td>
<td className="px-5 py-3 text-[#E8F4E5]/70">{row.type}</td>
<td className="px-5 py-3 text-[#E8F4E5]/70">{formatSize(row.sizeBytes)}</td>
<td className="px-5 py-3 text-[#E8F4E5]/70">{new Date(row.createdAt).toLocaleString("pt-BR")}</td>
<td className="px-5 py-3 text-right">
<a href={`/api/files/${row.id}/download`} className="btn-ghost cursor-pointer px-3 py-1.5 text-[11px]">
Baixar
</a>
</td>
</tr>
))}
</RegistryTable>
{open ? (
<Modal onClose={closeModal}>
<form className="flex flex-col gap-3" onSubmit={onSubmit}>
<h2 className="font-display text-2xl font-bold uppercase">Anexar arquivo</h2>
{error ? <p className="text-sm text-red-300">{error}</p> : null}
<input
className="text-sm file:mr-3 file:rounded-lg file:border-0 file:bg-[#82C85C] file:px-3 file:py-2 file:text-xs file:font-semibold file:text-black"
type="file"
accept=".csv,.xlsx,.xls,.pdf,.txt"
onChange={(event) => setFile(event.target.files?.[0] ?? null)}
/>
<div className="mt-2 flex gap-2">
<button className="btn-sinka" disabled={saving} type="submit">
{saving ? "Enviando…" : "Anexar"}
</button>
<button className="btn-ghost" type="button" onClick={closeModal}>
Cancelar
</button>
</div>
</form>
</Modal>
) : null}
</section>
);
}