Refine admin users filters with searchable combobox
This commit is contained in:
parent
b51d0770d3
commit
e47ea5eecc
1 changed files with 98 additions and 83 deletions
|
|
@ -60,6 +60,7 @@ import {
|
|||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
||||
import { SearchableCombobox, type SearchableComboboxOption } from "@/components/ui/searchable-combobox"
|
||||
|
||||
export type AdminAccount = {
|
||||
id: string
|
||||
|
|
@ -196,6 +197,32 @@ function AccountsTable({ initialAccounts }: { initialAccounts: AdminAccount[] })
|
|||
return Array.from(map.entries()).map(([id, name]) => ({ id, name }))
|
||||
}, [accounts])
|
||||
|
||||
const roleSelectOptions = useMemo<SearchableComboboxOption[]>(
|
||||
() => ROLE_OPTIONS_DISPLAY.map((option) => ({ value: option.value, label: option.label })),
|
||||
[],
|
||||
)
|
||||
|
||||
const roleFilterOptions = useMemo<SearchableComboboxOption[]>(
|
||||
() => [{ value: "all", label: "Todos os papéis" }, ...roleSelectOptions],
|
||||
[roleSelectOptions],
|
||||
)
|
||||
|
||||
const companyFilterOptions = useMemo<SearchableComboboxOption[]>(
|
||||
() => [
|
||||
{ value: "all", label: "Todas as empresas" },
|
||||
...companies.map((company) => ({ value: company.id, label: company.name })),
|
||||
],
|
||||
[companies],
|
||||
)
|
||||
|
||||
const editCompanyOptions = useMemo<SearchableComboboxOption[]>(
|
||||
() => [
|
||||
{ value: NO_COMPANY_SELECT_VALUE, label: "Sem empresa vinculada" },
|
||||
...companies.map((company) => ({ value: company.id, label: company.name })),
|
||||
],
|
||||
[companies],
|
||||
)
|
||||
|
||||
const openDeleteDialog = useCallback((ids: string[]) => {
|
||||
setDeleteDialogIds(ids)
|
||||
}, [])
|
||||
|
|
@ -381,30 +408,27 @@ function AccountsTable({ initialAccounts }: { initialAccounts: AdminAccount[] })
|
|||
<div className="flex flex-wrap gap-3">
|
||||
<div className="flex items-center gap-2 text-muted-foreground">
|
||||
<IconFilter className="size-4" />
|
||||
<Select value={roleFilter} onValueChange={(value: typeof roleFilter) => setRoleFilter(value)}>
|
||||
<SelectTrigger className="h-9 w-[12rem]">
|
||||
<SelectValue placeholder="Papel" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Todos os papéis</SelectItem>
|
||||
<SelectItem value="MANAGER">Gestores</SelectItem>
|
||||
<SelectItem value="COLLABORATOR">Colaboradores</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<SearchableCombobox
|
||||
value={roleFilter}
|
||||
onValueChange={(next) => {
|
||||
const normalized =
|
||||
next === "MANAGER" || next === "COLLABORATOR" ? next : "all"
|
||||
setRoleFilter(normalized)
|
||||
}}
|
||||
options={roleFilterOptions}
|
||||
placeholder="Todos os papéis"
|
||||
searchPlaceholder="Buscar papel..."
|
||||
className="md:w-[12rem]"
|
||||
/>
|
||||
</div>
|
||||
<Select value={companyFilter} onValueChange={setCompanyFilter}>
|
||||
<SelectTrigger className="h-9 w-[16rem]">
|
||||
<SelectValue placeholder="Empresa" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">Todas as empresas</SelectItem>
|
||||
{companies.map((company) => (
|
||||
<SelectItem key={company.id} value={company.id}>
|
||||
{company.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<SearchableCombobox
|
||||
value={companyFilter}
|
||||
onValueChange={(next) => setCompanyFilter(next ?? "all")}
|
||||
options={companyFilterOptions}
|
||||
placeholder="Todas as empresas"
|
||||
searchPlaceholder="Buscar empresa..."
|
||||
className="md:w-[16rem]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
|
|
@ -424,18 +448,18 @@ function AccountsTable({ initialAccounts }: { initialAccounts: AdminAccount[] })
|
|||
<Table className="w-full table-fixed text-sm">
|
||||
<TableHeader className="bg-muted">
|
||||
<TableRow>
|
||||
<TableHead>Usuário</TableHead>
|
||||
<TableHead>Empresa</TableHead>
|
||||
<TableHead>Papel</TableHead>
|
||||
<TableHead>Último acesso</TableHead>
|
||||
<TableHead className="text-right">Ações</TableHead>
|
||||
<TableHead className="text-right">Selecionar</TableHead>
|
||||
<TableHead className="px-6">Usuário</TableHead>
|
||||
<TableHead className="px-4">Empresa</TableHead>
|
||||
<TableHead className="px-4">Papel</TableHead>
|
||||
<TableHead className="px-4">Último acesso</TableHead>
|
||||
<TableHead className="px-4 text-right">Ações</TableHead>
|
||||
<TableHead className="px-4 text-right">Selecionar</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filteredAccounts.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="text-center text-sm text-muted-foreground">
|
||||
<TableCell colSpan={6} className="px-6 py-6 text-center text-sm text-muted-foreground">
|
||||
Nenhum usuário encontrado.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -449,7 +473,7 @@ function AccountsTable({ initialAccounts }: { initialAccounts: AdminAccount[] })
|
|||
.join("")
|
||||
return (
|
||||
<TableRow key={account.id} className="hover:bg-muted/40">
|
||||
<TableCell>
|
||||
<TableCell className="px-6 py-3">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="size-9 border border-border/60">
|
||||
<AvatarFallback>{initials || account.email.charAt(0).toUpperCase()}</AvatarFallback>
|
||||
|
|
@ -460,14 +484,16 @@ function AccountsTable({ initialAccounts }: { initialAccounts: AdminAccount[] })
|
|||
</div>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-sm text-muted-foreground">
|
||||
<TableCell className="px-4 py-3 text-sm text-muted-foreground">
|
||||
{account.companyName ?? <span className="italic text-muted-foreground/70">Sem empresa</span>}
|
||||
</TableCell>
|
||||
<TableCell className="text-sm">
|
||||
<TableCell className="px-4 py-3 text-sm">
|
||||
<Badge variant="secondary">{ROLE_LABEL[account.role]}</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="text-xs text-muted-foreground">{formatDate(account.lastSeenAt)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<TableCell className="px-4 py-3 text-xs text-muted-foreground">
|
||||
{formatDate(account.lastSeenAt)}
|
||||
</TableCell>
|
||||
<TableCell className="px-4 py-3 text-right align-middle">
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
|
|
@ -490,13 +516,16 @@ function AccountsTable({ initialAccounts }: { initialAccounts: AdminAccount[] })
|
|||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<TableCell className="px-4 py-3 text-right align-middle">
|
||||
<div className="flex justify-end">
|
||||
<Checkbox
|
||||
checked={rowSelection[account.id] ?? false}
|
||||
onCheckedChange={(checked) =>
|
||||
setRowSelection((prev) => ({ ...prev, [account.id]: Boolean(checked) }))
|
||||
}
|
||||
aria-label={`Selecionar ${account.name}`}
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
|
|
@ -571,50 +600,36 @@ function AccountsTable({ initialAccounts }: { initialAccounts: AdminAccount[] })
|
|||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-role">Papel</Label>
|
||||
<Select
|
||||
<Label>Papel</Label>
|
||||
<SearchableCombobox
|
||||
value={editForm.role}
|
||||
onValueChange={(value) =>
|
||||
setEditForm((prev) => ({ ...prev, role: value as AdminAccount["role"] }))
|
||||
}
|
||||
disabled={isSavingAccount}
|
||||
>
|
||||
<SelectTrigger id="edit-role">
|
||||
<SelectValue placeholder="Selecione" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{ROLE_OPTIONS_DISPLAY.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label htmlFor="edit-company">Empresa vinculada</Label>
|
||||
<Select
|
||||
value={editForm.companyId || NO_COMPANY_SELECT_VALUE}
|
||||
onValueChange={(value) =>
|
||||
onValueChange={(next) =>
|
||||
setEditForm((prev) => ({
|
||||
...prev,
|
||||
companyId: value === NO_COMPANY_SELECT_VALUE ? "" : value,
|
||||
role: next === "MANAGER" || next === "COLLABORATOR" ? next : prev.role,
|
||||
}))
|
||||
}
|
||||
options={roleSelectOptions}
|
||||
placeholder="Selecione"
|
||||
searchPlaceholder="Buscar papel..."
|
||||
disabled={isSavingAccount}
|
||||
>
|
||||
<SelectTrigger id="edit-company">
|
||||
<SelectValue placeholder="Sem empresa vinculada" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value={NO_COMPANY_SELECT_VALUE}>Sem empresa vinculada</SelectItem>
|
||||
{companies.map((company) => (
|
||||
<SelectItem key={company.id} value={company.id}>
|
||||
{company.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<Label>Empresa vinculada</Label>
|
||||
<SearchableCombobox
|
||||
value={editForm.companyId || NO_COMPANY_SELECT_VALUE}
|
||||
onValueChange={(next) =>
|
||||
setEditForm((prev) => ({
|
||||
...prev,
|
||||
companyId: next === NO_COMPANY_SELECT_VALUE || next === null ? "" : next,
|
||||
}))
|
||||
}
|
||||
options={editCompanyOptions}
|
||||
placeholder="Sem empresa vinculada"
|
||||
searchPlaceholder="Buscar empresa..."
|
||||
disabled={isSavingAccount}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue