Bin
2025-12-16 9e0b2ba2c317b1a86212f24cbae3195ad1f3dbfa
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import { observer } from "mobx-react";
import { useState, useCallback, useMemo } from "react";
import { debounce } from "../../utils/debounce";
import { useDataManagerUsers } from "../../hooks/useUsers";
import { Select, Tooltip, Userpic } from "@humansignal/ui";
import { cn } from "../../utils/bem";
import { SelectSize } from "@humansignal/ui/lib/select/types";
import { userDisplayName } from "@humansignal/core/lib/utils/helpers";
 
const DEBOUNCE_DELAY = 300;
 
export const UserSelect = observer(({ filter, onChange, multiple, value, placeholder, disabled }) => {
  const [search, setSearch] = useState(null);
  const [selectedValue, setSelectedValue] = useState(value);
 
  // Get project ID from the filter context or use a default
  const projectId = filter?.view?.project?.id || 1;
  const optionsPerRequest = 10;
 
  const debouncedSearch = useCallback(
    debounce((val) => setSearch(val), DEBOUNCE_DELAY),
    [],
  );
 
  const { users, hasMore, total, loadMore } = useDataManagerUsers(
    projectId,
    optionsPerRequest,
    false,
    null,
    search,
    selectedValue,
  );
  const options = useMemo(() => {
    return users.map((user) => {
      const displayName = userDisplayName(user);
      user.displayName = displayName;
      return {
        value: user.id,
        raw: { id: user.id, email: user.email, displayName, username: user.username },
        label: (
          <Tooltip title={user.displayName} alignment="top-left">
            <div className="flex gap-2 w-full items-center">
              <Userpic user={user} size={16} key={`user-${user.id}`} showName={true} />
              <span className="text-ellipsis text-nowrap overflow-hidden w-full">{user.displayName}</span>
            </div>
          </Tooltip>
        ),
      };
    });
  }, [users, hasMore, loadMore]);
 
  const _onChange = useCallback(
    (val) => {
      setSelectedValue(val);
      onChange?.(val);
      setSearch(null);
    },
    [onChange],
  );
 
  const searchFilter = useCallback((option: any, queryString: string) => {
    const user = option.raw;
    return (
      user.id?.toString().toLowerCase().includes(queryString.toLowerCase()) ||
      user.email?.toLowerCase().includes(queryString.toLowerCase()) ||
      user.displayName?.toLowerCase().includes(queryString.toLowerCase()) ||
      user.username?.toLowerCase().includes(queryString.toLowerCase())
    );
  }, []);
 
  // Convert users data to options format for Select component
  return (
    <Select
      options={options}
      value={selectedValue}
      onChange={_onChange}
      triggerClassName={`${cn("form-select").elem("list").toString()} w-[200px]`}
      loadMore={loadMore}
      size={SelectSize.SMALL}
      placeholder={placeholder}
      disabled={disabled}
      multiple={multiple}
      isVirtualList={true}
      searchable={true}
      onSearch={debouncedSearch}
      searchFilter={searchFilter}
      itemCount={total}
    />
  );
});