From 42ce36a43983321f7e94c796e1c54d294839d224 Mon Sep 17 00:00:00 2001 From: rustmailer Date: Sun, 28 Jun 2026 11:35:39 +0800 Subject: [PATCH] feat: add searchable account selector with alphabetical sort in user dialog #289 --- .../users/users/components/action-dialog.tsx | 84 +++++++++++++++++-- 1 file changed, 77 insertions(+), 7 deletions(-) diff --git a/web/src/features/users/users/components/action-dialog.tsx b/web/src/features/users/users/components/action-dialog.tsx index f6dad0b..3ab3b65 100644 --- a/web/src/features/users/users/components/action-dialog.tsx +++ b/web/src/features/users/users/components/action-dialog.tsx @@ -20,7 +20,7 @@ import { useState, useMemo } from 'react' import { useFieldArray, useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { useMutation, useQueryClient } from '@tanstack/react-query' -import { Loader2, Shield, Settings2, UserIcon, Plus, Trash2, Mail } from 'lucide-react' +import { Loader2, Shield, Settings2, UserIcon, Plus, Trash2, Mail, Check, ChevronsUpDown } from 'lucide-react' import { AxiosError } from 'axios' import { Button } from '@/components/ui/button' @@ -43,11 +43,21 @@ import { import { Input } from '@/components/ui/input' import { Textarea } from '@/components/ui/textarea' import { ScrollArea } from '@/components/ui/scroll-area' +import { cn } from '@/lib/utils' import { Checkbox } from '@/components/ui/checkbox' import { Switch } from '@/components/ui/switch' import { Separator } from '@/components/ui/separator' import { toast } from '@/hooks/use-toast' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command" import { create_user, update_user, User } from '@/api/users/api' import useMinimalAccountList from '@/hooks/use-minimal-account-list' @@ -65,6 +75,66 @@ interface Props { onOpenChange: (open: boolean) => void } +function AccountSearchSelect({ + accounts, + value, + onChange, + placeholder, +}: { + accounts: { id: number; email: string; name?: string }[] + value: number + onChange: (v: number) => void + placeholder: string +}) { + const [open, setOpen] = useState(false) + const [search, setSearch] = useState('') + + const selectedAccount = accounts.find(a => a.id === value) + + const filtered = useMemo(() => { + const q = search.toLowerCase() + return accounts + .filter(a => + !q || a.email.toLowerCase().includes(q) || a.name?.toLowerCase().includes(q) || String(a.id).includes(q) + ) + .sort((a, b) => a.email.toLowerCase().localeCompare(b.email.toLowerCase())) + }, [accounts, search]) + + return ( + { setOpen(o); if (!o) setSearch('') }}> + + + + + + + + + + No account found + + {filtered.map(acc => ( + { onChange(acc.id); setOpen(false) }} + > + + {acc.email} + {acc.name && {acc.name}} + + ))} + + + + + + ) +} + export function UserActionDialog({ currentRow, open, onOpenChange }: Props) { const { t } = useTranslation() const isEdit = !!currentRow @@ -299,12 +369,12 @@ export function UserActionDialog({ currentRow, open, onOpenChange }: Props) {
( - + field.onChange(v)} + placeholder={t('users.actions.fields.select_account')} + /> )} />