Files
dashboards/apps/super-admin/src/components/UserSearchInput.tsx
T
Alireza HassaniandCursor f566387c61 Initial commit: Meshkee dashboards monorepo.
Includes business, customer, and super-admin apps with shared packages and production deploy scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 13:48:53 +03:30

121 lines
3.3 KiB
TypeScript

import { useEffect, useState } from 'react'
import { isAbortError } from '../lib/api'
import { searchUsers } from '../services/userService'
import type { UserSearchItem } from '../types/user'
import styles from './UserSearchInput.module.css'
const DEFAULT_MIN_CHARS = 2
export interface UserSearchInputProps {
id?: string
label?: string
placeholder?: string
disabled?: boolean
minChars?: number
selectedUser: UserSearchItem | null
onSelect: (user: UserSearchItem | null) => void
}
export function UserSearchInput({
id = 'user-search',
label = 'User',
placeholder = 'Search by name, email, or phone',
disabled = false,
minChars = DEFAULT_MIN_CHARS,
selectedUser,
onSelect,
}: UserSearchInputProps) {
const [query, setQuery] = useState(selectedUser?.label ?? '')
const [results, setResults] = useState<UserSearchItem[]>([])
const [searching, setSearching] = useState(false)
useEffect(() => {
setQuery(selectedUser?.label ?? '')
}, [selectedUser])
useEffect(() => {
if (query.trim().length < minChars || selectedUser) {
setResults([])
setSearching(false)
return
}
const controller = new AbortController()
const timer = setTimeout(() => {
setSearching(true)
void searchUsers(query.trim(), 20, controller.signal)
.then((result) => {
if (!controller.signal.aborted) setResults(result.items)
})
.catch((err) => {
if (!isAbortError(err) && !controller.signal.aborted) {
setResults([])
}
})
.finally(() => {
if (!controller.signal.aborted) setSearching(false)
})
}, 300)
return () => {
clearTimeout(timer)
controller.abort()
}
}, [query, minChars, selectedUser])
const showDropdown = query.trim().length >= minChars && !selectedUser
function handleQueryChange(value: string) {
setQuery(value)
if (selectedUser && value !== selectedUser.label) {
onSelect(null)
}
}
function pickUser(user: UserSearchItem) {
onSelect(user)
setQuery(user.label)
setResults([])
}
return (
<div>
<label htmlFor={id}>{label}</label>
<div className={styles.wrap}>
<input
id={id}
value={query}
onChange={(e) => handleQueryChange(e.target.value)}
placeholder={placeholder}
autoComplete="off"
disabled={disabled}
/>
{showDropdown && (
<div className={styles.dropdown}>
{searching ? (
<div className={styles.empty}>Searching...</div>
) : results.length === 0 ? (
<div className={styles.empty}>No users found.</div>
) : (
results.map((user) => (
<button
key={user.id}
type="button"
className={styles.item}
onClick={() => pickUser(user)}
>
{user.label}
</button>
))
)}
</div>
)}
</div>
{query.trim().length > 0 && query.trim().length < minChars && (
<div className={styles.hint}>Type at least {minChars} characters to search.</div>
)}
{selectedUser && <div className={styles.selected}>Selected: {selectedUser.label}</div>}
</div>
)
}