From 17f9f00343428c9723fed2ed33e15a21717ecf78 Mon Sep 17 00:00:00 2001 From: Esdras Renan Date: Mon, 13 Oct 2025 15:23:53 -0300 Subject: [PATCH] Add company management editing and deletion --- src/app/api/admin/companies/[id]/route.ts | 34 ++++++ .../companies/admin-companies-manager.tsx | 110 ++++++++++++++++-- 2 files changed, 137 insertions(+), 7 deletions(-) diff --git a/src/app/api/admin/companies/[id]/route.ts b/src/app/api/admin/companies/[id]/route.ts index b8b9c75..d747d1c 100644 --- a/src/app/api/admin/companies/[id]/route.ts +++ b/src/app/api/admin/companies/[id]/route.ts @@ -1,5 +1,6 @@ import { NextResponse } from "next/server" +import { Prisma } from "@prisma/client" import { prisma } from "@/lib/prisma" import { assertAdminSession } from "@/lib/auth-server" @@ -46,3 +47,36 @@ export async function PATCH(request: Request, { params }: { params: Promise<{ id return NextResponse.json({ error: "Falha ao atualizar empresa" }, { status: 500 }) } } + +export async function DELETE(_: Request, { params }: { params: Promise<{ id: string }> }) { + const session = await assertAdminSession() + if (!session) return NextResponse.json({ error: "Não autorizado" }, { status: 401 }) + const { id } = await params + + const company = await prisma.company.findUnique({ + where: { id }, + select: { id: true, tenantId: true, name: true }, + }) + + if (!company) { + return NextResponse.json({ error: "Empresa não encontrada" }, { status: 404 }) + } + + if (company.tenantId !== (session.user.tenantId ?? company.tenantId)) { + return NextResponse.json({ error: "Acesso negado" }, { status: 403 }) + } + + try { + await prisma.company.delete({ where: { id: company.id } }) + return NextResponse.json({ ok: true }) + } catch (error) { + if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === "P2003") { + return NextResponse.json( + { error: "Não é possível remover esta empresa pois existem registros vinculados." }, + { status: 409 } + ) + } + console.error("Failed to delete company", error) + return NextResponse.json({ error: "Falha ao excluir empresa" }, { status: 500 }) + } +} diff --git a/src/components/admin/companies/admin-companies-manager.tsx b/src/components/admin/companies/admin-companies-manager.tsx index 87fd5cd..c50102d 100644 --- a/src/components/admin/companies/admin-companies-manager.tsx +++ b/src/components/admin/companies/admin-companies-manager.tsx @@ -1,6 +1,6 @@ "use client" -import { useCallback, useEffect, useState, useTransition } from "react" +import { useCallback, useEffect, useMemo, useState, useTransition } from "react" import { toast } from "sonner" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" @@ -8,6 +8,7 @@ import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Checkbox } from "@/components/ui/checkbox" +import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Table, TableBody, @@ -37,6 +38,8 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies: const [form, setForm] = useState>({}) const [editingId, setEditingId] = useState(null) const [lastAlerts, setLastAlerts] = useState>({}) + const [deleteId, setDeleteId] = useState(null) + const [isDeleting, setIsDeleting] = useState(false) const resetForm = () => setForm({}) @@ -49,7 +52,10 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies: function handleEdit(c: Company) { setEditingId(c.id) - setForm({ ...c }) + setForm({ + ...c, + contractedHoursPerMonth: c.contractedHoursPerMonth ?? undefined, + }) } const loadLastAlerts = useCallback(async (list: Company[] = companies) => { @@ -68,6 +74,11 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies: async function handleSubmit(e: React.FormEvent) { e.preventDefault() + const contractedHours = + typeof form.contractedHoursPerMonth === "number" && Number.isFinite(form.contractedHoursPerMonth) + ? form.contractedHoursPerMonth + : null + const payload = { name: form.name?.trim(), slug: form.slug?.trim(), @@ -77,6 +88,7 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies: phone: form.phone?.trim() || null, description: form.description?.trim() || null, address: form.address?.trim() || null, + contractedHoursPerMonth: contractedHours, } if (!payload.name || !payload.slug) { toast.error("Informe nome e slug válidos") @@ -123,18 +135,53 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies: }) if (!r.ok) throw new Error("toggle_failed") await refresh() + toast.success(`Cliente ${!c.isAvulso ? "marcado como avulso" : "marcado como recorrente"}`) } catch { toast.error("Não foi possível atualizar o cliente avulso") } }) } + async function handleDeleteConfirmed() { + if (!deleteId) return + setIsDeleting(true) + try { + const response = await fetch(`/api/admin/companies/${deleteId}`, { + method: "DELETE", + credentials: "include", + }) + if (!response.ok) { + const data = await response.json().catch(() => ({})) + throw new Error(data.error ?? "Falha ao excluir empresa") + } + toast.success("Empresa removida") + if (editingId === deleteId) { + resetForm() + setEditingId(null) + } + await refresh() + } catch (error) { + const message = error instanceof Error ? error.message : "Não foi possível remover a empresa" + toast.error(message) + } finally { + setIsDeleting(false) + setDeleteId(null) + } + } + + const editingCompanyName = useMemo(() => companies.find((company) => company.id === editingId)?.name ?? null, [companies, editingId]) + const deleteTarget = useMemo(() => companies.find((company) => company.id === deleteId) ?? null, [companies, deleteId]) + return (
- Nova empresa - Cadastre um cliente/empresa e defina se é avulso. + {editingId ? `Editar empresa${editingCompanyName ? ` · ${editingCompanyName}` : ""}` : "Nova empresa"} + + {editingId + ? "Atualize os dados cadastrais e as informações de faturamento do cliente selecionado." + : "Cadastre um cliente/empresa e defina se é avulso."} +
@@ -146,6 +193,10 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies: setForm((p) => ({ ...p, slug: e.target.value }))} />
+
+ + setForm((p) => ({ ...p, description: e.target.value }))} placeholder="Resumo, segmento ou observações internas" /> +
setForm((p) => ({ ...p, cnpj: e.target.value }))} /> @@ -230,9 +281,19 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies: : "—"} - +
+ + +
))} @@ -240,6 +301,41 @@ export function AdminCompaniesManager({ initialCompanies }: { initialCompanies: + + { + if (!open) { + setDeleteId(null) + } + }} + > + + + Excluir empresa + + Esta operação remove o cadastro do cliente e impede novos vínculos automáticos. + + +
+

+ Confirme a exclusão de{" "} + {deleteTarget?.name ?? "empresa selecionada"}. +

+

+ Registros históricos que apontem para a empresa poderão impedir a exclusão. +

+
+ + + + +
+
) }