"use client" import { useMemo, useState } from "react" import { useQuery } from "convex/react" import type { Id } from "@/convex/_generated/dataModel" import { api } from "@/convex/_generated/api" import { toast } from "sonner" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Button } from "@/components/ui/button" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox" type QuickCreateUserDialogProps = { open: boolean onOpenChange(open: boolean): void tenantId: string viewerId: string | null onSuccess?(payload: { email: string }): void } type PortalRole = "MANAGER" | "COLLABORATOR" const ROLE_OPTIONS: ReadonlyArray<{ value: PortalRole; label: string }> = [ { value: "MANAGER", label: "Gestor" }, { value: "COLLABORATOR", label: "Colaborador" }, ] const ROLE_TO_PAYLOAD: Record = { MANAGER: "manager", COLLABORATOR: "collaborator", } const NO_COMPANY_VALUE = "__none__" const NO_MANAGER_VALUE = "__no_manager__" type QuickUserFormState = { name: string email: string jobTitle: string role: PortalRole companyId: string managerId: string } function defaultForm(): QuickUserFormState { return { name: "", email: "", jobTitle: "", role: "COLLABORATOR", companyId: NO_COMPANY_VALUE, managerId: NO_MANAGER_VALUE, } } export function QuickCreateUserDialog({ open, onOpenChange, tenantId, viewerId, onSuccess }: QuickCreateUserDialogProps) { const [form, setForm] = useState(defaultForm) const [isSubmitting, setIsSubmitting] = useState(false) const canQuery = open && Boolean(viewerId) const companies = useQuery( api.companies.list, canQuery ? { tenantId, viewerId: viewerId as Id<"users"> } : "skip" ) as Array<{ id: string; name: string; slug?: string }> | undefined const customers = useQuery( api.users.listCustomers, canQuery ? { tenantId, viewerId: viewerId as Id<"users"> } : "skip" ) as Array<{ id: string; name: string; email: string; role: string }> | undefined const companyOptions = useMemo(() => { const base: SearchableComboboxOption[] = [{ value: NO_COMPANY_VALUE, label: "Sem empresa vinculada" }] if (!companies) return base const sorted = [...companies].sort((a, b) => a.name.localeCompare(b.name, "pt-BR")) return [ ...base, ...sorted.map((company) => ({ value: company.id, label: company.name, })), ] }, [companies]) const managerOptions = useMemo(() => { const base: SearchableComboboxOption[] = [{ value: NO_MANAGER_VALUE, label: "Sem gestor" }] if (!customers) return base const managers = customers.filter((user) => user.role === "MANAGER") return [ ...base, ...managers.map((manager) => ({ value: manager.id, label: manager.name, description: manager.email, })), ] }, [customers]) const resetForm = () => { setForm(defaultForm) } const handleClose = () => { if (isSubmitting) return resetForm() onOpenChange(false) } const handleSubmit = async (event: React.FormEvent) => { event.preventDefault() if (!viewerId) { toast.error("Não foi possível identificar o usuário atual.") return } const name = form.name.trim() const email = form.email.trim().toLowerCase() if (!name) { toast.error("Informe o nome do usuário.") return } if (!email || !email.includes("@")) { toast.error("Informe um e-mail válido.") return } const jobTitle = form.jobTitle.trim() const managerId = form.managerId !== NO_MANAGER_VALUE ? form.managerId : null const companyId = form.companyId !== NO_COMPANY_VALUE ? form.companyId : null const payload = { name, email, role: ROLE_TO_PAYLOAD[form.role], tenantId, jobTitle: jobTitle || null, managerId, } setIsSubmitting(true) try { const response = await fetch("/api/admin/users", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify(payload), }) if (!response.ok) { const data = await response.json().catch(() => null) throw new Error(data?.error ?? "Não foi possível criar o usuário.") } const data = (await response.json()) as { user: { email: string }; temporaryPassword?: string } if (companyId) { const assignResponse = await fetch("/api/admin/users/assign-company", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ email, companyId }), }) if (!assignResponse.ok) { const assignData = await assignResponse.json().catch(() => null) toast.error(assignData?.error ?? "Usuário criado, mas não foi possível vinculá-lo à empresa.") } } resetForm() onOpenChange(false) onSuccess?.({ email }) toast.success("Usuário criado com sucesso.", { description: data.temporaryPassword ? `Senha temporária: ${data.temporaryPassword}` : undefined, action: data.temporaryPassword ? { label: "Copiar", onClick: async () => { try { await navigator.clipboard?.writeText?.(data.temporaryPassword ?? "") toast.success("Senha copiada para a área de transferência.") } catch (error) { const message = error instanceof Error ? error.message : "Não foi possível copiar a senha." toast.error(message) } }, } : undefined, }) } catch (error) { const message = error instanceof Error ? error.message : "Erro ao criar usuário." toast.error(message) } finally { setIsSubmitting(false) } } return ( (next ? onOpenChange(next) : handleClose())}> Novo usuário Crie acessos para gestores ou colaboradores sem sair da página atual.
setForm((prev) => ({ ...prev, name: event.target.value }))} placeholder="Nome completo" disabled={isSubmitting} required />
setForm((prev) => ({ ...prev, email: event.target.value }))} placeholder="usuario@empresa.com" disabled={isSubmitting} required />
setForm((prev) => ({ ...prev, jobTitle: event.target.value }))} placeholder="Ex.: Analista de Suporte" disabled={isSubmitting} />
setForm((prev) => ({ ...prev, managerId: value === null ? NO_MANAGER_VALUE : value, })) } options={managerOptions} placeholder="Sem gestor definido" searchPlaceholder="Buscar gestor..." disabled={isSubmitting || !customers} allowClear clearLabel="Remover gestor" />
setForm((prev) => ({ ...prev, companyId: value === null ? NO_COMPANY_VALUE : value, })) } options={companyOptions} placeholder="Sem empresa vinculada" searchPlaceholder="Buscar empresa..." disabled={isSubmitting || !companies} />
) }