mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
POST /api/v1/admin/tenants inseria status 'onboarding', valor que a check constraint de organizations.status (active|suspended|redacted|archived) nunca aceitou — a rota 500ava em qualquer criação. O marcador real de onboarding no produto é onboarded_at null (mesmo modelo do signup self-service e do gate de /onboarding), então: - insert passa a usar status 'active' (onboarded_at null = em onboarding) - filtro GET status=onboarding vira estado derivado: active + onboarded_at is null (o dropdown "Onboarding" do admin, que nunca casava nada, agora funciona) - badge da listagem deriva "Onboarding" de active + onboarded_at null - tipos de linha (useTenantDetail, TenantActions) deixam de aceitar um status que o banco não produz; o tipo de FILTRO mantém 'onboarding' Sem migration: nenhuma linha com status inválido pode existir (a constraint sempre bloqueou o INSERT). Claude-Session: https://claude.ai/code/session_01454SGvQ7wBAvah8ZPnnWXv Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
96 lines
2.7 KiB
TypeScript
96 lines
2.7 KiB
TypeScript
"use client";
|
|
import { useState } from "react";
|
|
import { Button } from "@/components/ui/button";
|
|
import { SuspendDialog } from "./SuspendDialog";
|
|
import { ReactivateDialog } from "./ReactivateDialog";
|
|
import { ImpersonateButton } from "@/components/admin/ImpersonateButton";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Props
|
|
// ---------------------------------------------------------------------------
|
|
|
|
interface TenantActionsProps {
|
|
organizationId: string;
|
|
status: "active" | "suspended" | "redacted";
|
|
displayName: string;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Component
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export function TenantActions({
|
|
organizationId,
|
|
status,
|
|
displayName,
|
|
}: TenantActionsProps) {
|
|
const [suspendOpen, setSuspendOpen] = useState(false);
|
|
const [reactivateOpen, setReactivateOpen] = useState(false);
|
|
|
|
const canSuspend = status === "active";
|
|
const isSuspended = status === "suspended";
|
|
const isRedacted = status === "redacted";
|
|
|
|
return (
|
|
<>
|
|
<div className="rounded-lg border bg-card p-5 space-y-4">
|
|
<h2 className="text-sm font-semibold text-muted-foreground uppercase tracking-wider">
|
|
Ações
|
|
</h2>
|
|
|
|
{/* Impersonate (S-11.07) */}
|
|
<ImpersonateButton
|
|
organizationId={organizationId}
|
|
displayName={displayName}
|
|
disabled={isRedacted}
|
|
disabledReason={
|
|
isRedacted ? "Tenant redigido — ação não disponível" : undefined
|
|
}
|
|
/>
|
|
|
|
{/* Suspend */}
|
|
{canSuspend && (
|
|
<Button
|
|
className="w-full"
|
|
variant="destructive"
|
|
onClick={() => setSuspendOpen(true)}
|
|
aria-label="Suspender tenant"
|
|
>
|
|
Suspender tenant
|
|
</Button>
|
|
)}
|
|
|
|
{/* Reactivate */}
|
|
{isSuspended && (
|
|
<Button
|
|
className="w-full"
|
|
variant="outline"
|
|
onClick={() => setReactivateOpen(true)}
|
|
aria-label="Reativar tenant"
|
|
>
|
|
Reativar tenant
|
|
</Button>
|
|
)}
|
|
|
|
{isRedacted && (
|
|
<p className="text-xs text-muted-foreground text-center py-2">
|
|
Tenant redigido — ações de gestão não disponíveis.
|
|
</p>
|
|
)}
|
|
</div>
|
|
|
|
<SuspendDialog
|
|
open={suspendOpen}
|
|
onClose={() => setSuspendOpen(false)}
|
|
organizationId={organizationId}
|
|
/>
|
|
|
|
<ReactivateDialog
|
|
open={reactivateOpen}
|
|
onClose={() => setReactivateOpen(false)}
|
|
organizationId={organizationId}
|
|
/>
|
|
</>
|
|
);
|
|
}
|