ultisuite-client/components/admin/settings/sections/users-groups-dialog.tsx
R3D347HR4Y 9e9fd208ad
Some checks are pending
E2E / Playwright e2e (push) Waiting to run
feat(admin-settings): enhance admin settings with new components and layout improvements
- 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.
2026-06-15 00:22:20 +02:00

214 lines
6.0 KiB
TypeScript

"use client"
import { useEffect, useMemo, useState } from "react"
import { Pencil, Plus, Trash2, UsersRound } from "lucide-react"
import { useAdminUserGroups } from "@/lib/api/hooks/use-admin-queries"
import {
useCreateAdminUserGroup,
useDeleteAdminUserGroup,
useUpdateAdminUserGroup,
} from "@/lib/api/hooks/use-admin-mutations"
import type { AdminUserGroup } from "@/lib/api/admin-types"
import { Button } from "@/components/ui/button"
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Textarea } from "@/components/ui/textarea"
export function UsersGroupsDialog({
open,
onOpenChange,
}: {
open: boolean
onOpenChange: (open: boolean) => void
}) {
const [q, setQ] = useState("")
const [editorOpen, setEditorOpen] = useState(false)
const [editing, setEditing] = useState<AdminUserGroup | null>(null)
const queryParams = useMemo(
() => ({ page: 1, page_size: 100, q: q.trim() || undefined }),
[q]
)
const { data, isFetching } = useAdminUserGroups(queryParams)
const groups = data?.groups ?? []
function openCreate() {
setEditing(null)
setEditorOpen(true)
}
function openEdit(group: AdminUserGroup) {
setEditing(group)
setEditorOpen(true)
}
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-lg">
<DialogHeader>
<DialogTitle>Groupes d&apos;utilisateurs</DialogTitle>
</DialogHeader>
<div className="space-y-4">
<div className="flex gap-2">
<Input
value={q}
onChange={(e) => setQ(e.target.value)}
placeholder="Rechercher un groupe"
className="h-9"
/>
<Button className="h-9 shrink-0" onClick={openCreate}>
<Plus className="mr-2 size-4" />
Créer
</Button>
</div>
<div className="max-h-80 space-y-2 overflow-y-auto rounded-lg border p-2">
{isFetching ? (
<p className="px-2 py-4 text-sm text-muted-foreground">Chargement</p>
) : groups.length === 0 ? (
<p className="px-2 py-4 text-sm text-muted-foreground">Aucun groupe.</p>
) : (
groups.map((group) => (
<GroupRow key={group.id} group={group} onEdit={() => openEdit(group)} />
))
)}
</div>
</div>
</DialogContent>
</Dialog>
<GroupEditorDialog
open={editorOpen}
onOpenChange={setEditorOpen}
group={editing}
/>
</>
)
}
function GroupRow({
group,
onEdit,
}: {
group: AdminUserGroup
onEdit: () => void
}) {
const deleteGroup = useDeleteAdminUserGroup()
return (
<div className="flex items-start gap-3 rounded-md px-2 py-2 hover:bg-muted/50">
<UsersRound className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
<div className="min-w-0 flex-1">
<div className="font-medium">{group.name}</div>
{group.description ? (
<div className="text-xs text-muted-foreground">{group.description}</div>
) : null}
<div className="mt-1 text-xs text-muted-foreground">
{group.member_count.toLocaleString("fr-FR")} membre(s)
</div>
</div>
<div className="flex shrink-0 gap-1">
<Button variant="ghost" size="icon" className="size-8" onClick={onEdit}>
<Pencil className="size-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="size-8 text-destructive"
onClick={() => {
if (confirm(`Supprimer le groupe « ${group.name} » ?`)) {
void deleteGroup.mutateAsync(group.id)
}
}}
>
<Trash2 className="size-4" />
</Button>
</div>
</div>
)
}
function GroupEditorDialog({
open,
onOpenChange,
group,
}: {
open: boolean
onOpenChange: (open: boolean) => void
group: AdminUserGroup | null
}) {
const createGroup = useCreateAdminUserGroup()
const updateGroup = useUpdateAdminUserGroup(group?.id ?? "")
const [name, setName] = useState("")
const [description, setDescription] = useState("")
const isEdit = Boolean(group)
useEffect(() => {
if (!open) return
setName(group?.name ?? "")
setDescription(group?.description ?? "")
}, [open, group])
async function submit() {
const trimmedName = name.trim()
if (!trimmedName) return
if (isEdit && group) {
await updateGroup.mutateAsync({
name: trimmedName,
description: description.trim(),
})
} else {
await createGroup.mutateAsync({
name: trimmedName,
description: description.trim() || undefined,
})
}
onOpenChange(false)
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle>{isEdit ? "Modifier le groupe" : "Nouveau groupe"}</DialogTitle>
</DialogHeader>
<div className="space-y-3">
<div>
<Label>Nom</Label>
<Input className="mt-1" value={name} onChange={(e) => setName(e.target.value)} />
</div>
<div>
<Label>Description (optionnel)</Label>
<Textarea
className="mt-1 min-h-20"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => onOpenChange(false)}>
Annuler
</Button>
<Button
disabled={!name.trim() || createGroup.isPending || updateGroup.isPending}
onClick={() => void submit()}
>
{isEdit ? "Enregistrer" : "Créer"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}