Add confirmation dialog for client deletion and align machine badges
This commit is contained in:
parent
2400f34c80
commit
1c7309a2b6
2 changed files with 182 additions and 112 deletions
|
|
@ -25,6 +25,14 @@ import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
SelectContent,
|
SelectContent,
|
||||||
|
|
@ -79,6 +87,7 @@ export function AdminClientsManager({ initialClients }: { initialClients: AdminC
|
||||||
const [rowSelection, setRowSelection] = useState({})
|
const [rowSelection, setRowSelection] = useState({})
|
||||||
const [sorting, setSorting] = useState<SortingState>([{ id: "name", desc: false }])
|
const [sorting, setSorting] = useState<SortingState>([{ id: "name", desc: false }])
|
||||||
const [isPending, startTransition] = useTransition()
|
const [isPending, startTransition] = useTransition()
|
||||||
|
const [deleteDialogIds, setDeleteDialogIds] = useState<string[]>([])
|
||||||
|
|
||||||
const companies = useMemo(() => {
|
const companies = useMemo(() => {
|
||||||
const entries = new Map<string, string>()
|
const entries = new Map<string, string>()
|
||||||
|
|
@ -104,6 +113,11 @@ export function AdminClientsManager({ initialClients }: { initialClients: AdminC
|
||||||
})
|
})
|
||||||
}, [clients, roleFilter, companyFilter, search])
|
}, [clients, roleFilter, companyFilter, search])
|
||||||
|
|
||||||
|
const deleteTargets = useMemo(
|
||||||
|
() => clients.filter((client) => deleteDialogIds.includes(client.id)),
|
||||||
|
[clients, deleteDialogIds],
|
||||||
|
)
|
||||||
|
|
||||||
const handleDelete = useCallback(
|
const handleDelete = useCallback(
|
||||||
(ids: string[]) => {
|
(ids: string[]) => {
|
||||||
if (ids.length === 0) return
|
if (ids.length === 0) return
|
||||||
|
|
@ -124,6 +138,7 @@ export function AdminClientsManager({ initialClients }: { initialClients: AdminC
|
||||||
if (deletedIds.length > 0) {
|
if (deletedIds.length > 0) {
|
||||||
setClients((prev) => prev.filter((client) => !deletedIds.includes(client.id)))
|
setClients((prev) => prev.filter((client) => !deletedIds.includes(client.id)))
|
||||||
setRowSelection({})
|
setRowSelection({})
|
||||||
|
setDeleteDialogIds([])
|
||||||
}
|
}
|
||||||
toast.success(
|
toast.success(
|
||||||
deletedIds.length === 1
|
deletedIds.length === 1
|
||||||
|
|
@ -239,14 +254,14 @@ export function AdminClientsManager({ initialClients }: { initialClients: AdminC
|
||||||
size="sm"
|
size="sm"
|
||||||
className="border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700"
|
className="border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700"
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
onClick={() => handleDelete([row.original.id])}
|
onClick={() => setDeleteDialogIds([row.original.id])}
|
||||||
>
|
>
|
||||||
<IconTrash className="mr-2 size-4" /> Remover
|
<IconTrash className="mr-2 size-4" /> Remover
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[handleDelete, isPending]
|
[isPending, setDeleteDialogIds]
|
||||||
)
|
)
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
|
|
@ -268,8 +283,16 @@ export function AdminClientsManager({ initialClients }: { initialClients: AdminC
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().flatRows.map((row) => row.original)
|
const selectedRows = table.getSelectedRowModel().flatRows.map((row) => row.original)
|
||||||
|
const isBulkDelete = deleteTargets.length > 1
|
||||||
|
const dialogTitle = isBulkDelete ? "Remover clientes selecionados" : "Remover cliente"
|
||||||
|
const dialogDescription = isBulkDelete
|
||||||
|
? "Essa ação remove os clientes selecionados e revoga o acesso ao portal."
|
||||||
|
: "Essa ação remove o cliente escolhido e revoga o acesso ao portal."
|
||||||
|
const previewTargets = deleteTargets.slice(0, 3)
|
||||||
|
const remainingCount = deleteTargets.length - previewTargets.length
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex flex-col gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm md:flex-row md:items-center md:justify-between">
|
<div className="flex flex-col gap-3 rounded-xl border border-slate-200 bg-white p-4 shadow-sm md:flex-row md:items-center md:justify-between">
|
||||||
<div className="flex items-center gap-2 text-sm text-neutral-600">
|
<div className="flex items-center gap-2 text-sm text-neutral-600">
|
||||||
|
|
@ -317,7 +340,7 @@ export function AdminClientsManager({ initialClients }: { initialClients: AdminC
|
||||||
size="sm"
|
size="sm"
|
||||||
className="gap-2 border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700 disabled:text-rose-300 disabled:border-rose-100"
|
className="gap-2 border-rose-200 text-rose-600 hover:bg-rose-50 hover:text-rose-700 disabled:text-rose-300 disabled:border-rose-100"
|
||||||
disabled={selectedRows.length === 0 || isPending}
|
disabled={selectedRows.length === 0 || isPending}
|
||||||
onClick={() => handleDelete(selectedRows.map((row) => row.id))}
|
onClick={() => setDeleteDialogIds(selectedRows.map((row) => row.id))}
|
||||||
>
|
>
|
||||||
<IconTrash className="size-4" />
|
<IconTrash className="size-4" />
|
||||||
Excluir selecionados
|
Excluir selecionados
|
||||||
|
|
@ -387,5 +410,52 @@ export function AdminClientsManager({ initialClients }: { initialClients: AdminC
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
open={deleteDialogIds.length > 0}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open && !isPending) {
|
||||||
|
setDeleteDialogIds([])
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{dialogTitle}</DialogTitle>
|
||||||
|
<DialogDescription>{dialogDescription}</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
{deleteTargets.length > 0 ? (
|
||||||
|
<div className="space-y-3 py-2 text-sm text-neutral-600">
|
||||||
|
<p>
|
||||||
|
Confirme a exclusão de {isBulkDelete ? `${deleteTargets.length} clientes selecionados` : "um cliente"}. O acesso ao portal será revogado imediatamente.
|
||||||
|
</p>
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{previewTargets.map((target) => (
|
||||||
|
<li key={target.id} className="rounded-md bg-slate-100 px-3 py-2 text-sm text-neutral-800">
|
||||||
|
<span className="font-medium">{target.name}</span>
|
||||||
|
<span className="text-neutral-500"> — {target.email}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{remainingCount > 0 ? (
|
||||||
|
<li className="px-3 py-1 text-xs text-neutral-500">+ {remainingCount} outro{remainingCount === 1 ? "" : "s"}</li>
|
||||||
|
) : null}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={() => setDeleteDialogIds([])} disabled={isPending}>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
onClick={() => handleDelete(deleteDialogIds)}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
|
{isPending ? "Removendo..." : isBulkDelete ? "Excluir clientes" : "Excluir cliente"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1379,7 +1379,7 @@ export function MachineDetails({ machine }: MachineDetailsProps) {
|
||||||
{isActive ? (togglingActive ? "Desativando..." : "Desativar") : togglingActive ? "Reativando..." : "Reativar"}
|
{isActive ? (togglingActive ? "Desativando..." : "Desativar") : togglingActive ? "Reativando..." : "Reativar"}
|
||||||
</Button>
|
</Button>
|
||||||
{machine.registeredBy ? (
|
{machine.registeredBy ? (
|
||||||
<Badge variant="outline" className="inline-flex h-9 items-center rounded-full border-slate-300 bg-slate-100 px-3 text-sm font-medium text-slate-700">
|
<Badge className="inline-flex h-9 items-center gap-2 rounded-full border border-slate-200 bg-white px-3 text-sm font-semibold text-neutral-700">
|
||||||
Registrada via {machine.registeredBy}
|
Registrada via {machine.registeredBy}
|
||||||
</Badge>
|
</Badge>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue