Some checks are pending
E2E / Playwright e2e (push) Waiting to run
- Introduced new components for managing admin settings, including AdminListControls, AdminSettingsCard, and TechBrandSelectLabel. - Implemented dynamic loading for admin settings sections to optimize performance. - Enhanced the layout of various admin settings sections for better user experience. - Updated the AiAssistantSection to include LLM provider management and improved model selection. - Refactored authentication settings to streamline configuration and improve accessibility.
243 lines
7.5 KiB
TypeScript
243 lines
7.5 KiB
TypeScript
"use client"
|
|
|
|
import { useState } from "react"
|
|
import { Ban, RotateCcw, Trash2, UserCog, UsersRound, X } from "lucide-react"
|
|
import { toast } from "sonner"
|
|
import type { AdminBulkUsersAction, AdminUserGroup, AdminUserRole } from "@/lib/api/admin-types"
|
|
import { useBulkAdminUsers } from "@/lib/api/hooks/use-admin-mutations"
|
|
import {
|
|
USER_ROLE_ICONS,
|
|
USER_ROLE_LABELS,
|
|
} from "@/lib/admin/user-role"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu"
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select"
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog"
|
|
import { Label } from "@/components/ui/label"
|
|
|
|
const ROLE_OPTIONS: AdminUserRole[] = ["admin", "user", "guest", "suspended"]
|
|
|
|
export function UsersBulkToolbar({
|
|
selectedIds,
|
|
groups,
|
|
onClear,
|
|
}: {
|
|
selectedIds: string[]
|
|
groups: AdminUserGroup[]
|
|
onClear: () => void
|
|
}) {
|
|
const bulk = useBulkAdminUsers()
|
|
const [roleDialogOpen, setRoleDialogOpen] = useState(false)
|
|
const [groupDialogOpen, setGroupDialogOpen] = useState(false)
|
|
const [groupAction, setGroupAction] = useState<"add_to_group" | "remove_from_group">(
|
|
"add_to_group"
|
|
)
|
|
const [selectedRole, setSelectedRole] = useState<AdminUserRole>("user")
|
|
const [selectedGroupId, setSelectedGroupId] = useState<string>("")
|
|
|
|
const count = selectedIds.length
|
|
if (count === 0) return null
|
|
|
|
async function run(action: AdminBulkUsersAction, extra?: { role?: AdminUserRole; group_id?: string }) {
|
|
try {
|
|
const result = await bulk.mutateAsync({
|
|
user_ids: selectedIds,
|
|
action,
|
|
...extra,
|
|
})
|
|
if (result.failed?.length) {
|
|
toast.warning(
|
|
`${result.success_count} réussi(s), ${result.failed.length} échec(s)`
|
|
)
|
|
} else {
|
|
toast.success(`${result.success_count} utilisateur(s) mis à jour`)
|
|
}
|
|
onClear()
|
|
} catch {
|
|
toast.error("Action de masse impossible")
|
|
}
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<div className="mb-3 flex flex-wrap items-center gap-2 rounded-lg border bg-muted/30 px-3 py-2">
|
|
<Button variant="ghost" size="icon" className="size-8" onClick={onClear}>
|
|
<X className="size-4" />
|
|
</Button>
|
|
<span className="text-sm font-medium">
|
|
{count} sélectionné{count > 1 ? "s" : ""}
|
|
</span>
|
|
<div className="ml-auto flex flex-wrap gap-1">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={bulk.isPending}
|
|
onClick={() => void run("disable")}
|
|
>
|
|
<Ban className="mr-2 size-4" />
|
|
Suspendre
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={bulk.isPending}
|
|
onClick={() => void run("reactivate")}
|
|
>
|
|
<RotateCcw className="mr-2 size-4" />
|
|
Réactiver
|
|
</Button>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
disabled={bulk.isPending}
|
|
onClick={() => setRoleDialogOpen(true)}
|
|
>
|
|
<UserCog className="mr-2 size-4" />
|
|
Changer le type
|
|
</Button>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button variant="outline" size="sm" disabled={bulk.isPending || groups.length === 0}>
|
|
<UsersRound className="mr-2 size-4" />
|
|
Groupes
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent align="end">
|
|
<DropdownMenuItem
|
|
onClick={() => {
|
|
setGroupAction("add_to_group")
|
|
setSelectedGroupId(groups[0]?.id ?? "")
|
|
setGroupDialogOpen(true)
|
|
}}
|
|
>
|
|
Ajouter à un groupe
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={() => {
|
|
setGroupAction("remove_from_group")
|
|
setSelectedGroupId(groups[0]?.id ?? "")
|
|
setGroupDialogOpen(true)
|
|
}}
|
|
>
|
|
Retirer d'un groupe
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
className="text-destructive"
|
|
disabled={bulk.isPending}
|
|
onClick={() => {
|
|
if (confirm(`Supprimer ${count} utilisateur(s) ?`)) {
|
|
void run("delete")
|
|
}
|
|
}}
|
|
>
|
|
<Trash2 className="mr-2 size-4" />
|
|
Supprimer
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<Dialog open={roleDialogOpen} onOpenChange={setRoleDialogOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Changer le type</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-2">
|
|
<Label>Type d'utilisateur</Label>
|
|
<Select value={selectedRole} onValueChange={(v) => setSelectedRole(v as AdminUserRole)}>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{ROLE_OPTIONS.map((role) => {
|
|
const Icon = USER_ROLE_ICONS[role]
|
|
return (
|
|
<SelectItem key={role} value={role}>
|
|
<span className="flex items-center gap-2">
|
|
<Icon className="size-4" />
|
|
{USER_ROLE_LABELS[role]}
|
|
</span>
|
|
</SelectItem>
|
|
)
|
|
})}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setRoleDialogOpen(false)}>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
onClick={() => {
|
|
void run("set_role", { role: selectedRole })
|
|
setRoleDialogOpen(false)
|
|
}}
|
|
>
|
|
Appliquer
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
<Dialog open={groupDialogOpen} onOpenChange={setGroupDialogOpen}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{groupAction === "add_to_group" ? "Ajouter au groupe" : "Retirer du groupe"}
|
|
</DialogTitle>
|
|
</DialogHeader>
|
|
<div className="space-y-2">
|
|
<Label>Groupe</Label>
|
|
<Select value={selectedGroupId} onValueChange={setSelectedGroupId}>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Choisir un groupe" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{groups.map((group) => (
|
|
<SelectItem key={group.id} value={group.id}>
|
|
{group.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button variant="outline" onClick={() => setGroupDialogOpen(false)}>
|
|
Annuler
|
|
</Button>
|
|
<Button
|
|
disabled={!selectedGroupId}
|
|
onClick={() => {
|
|
void run(groupAction, { group_id: selectedGroupId })
|
|
setGroupDialogOpen(false)
|
|
}}
|
|
>
|
|
Appliquer
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</>
|
|
)
|
|
}
|