HR-ATS-Portal/frontend/src/ui/SearchSelect.jsx

95 lines
3.1 KiB
JavaScript

/* ============================================================
SearchSelect — type-to-filter picker whose value is always an option id.
Moved out of screens/Jobs.jsx so Requisitions can use the same control.
Options are `{ id, name, email?, role_name? }`. Pass `onQueryChange` when the
caller filters server-side; without it the list is filtered in place.
============================================================ */
import { useEffect, useRef, useState } from 'react'
/**
* Searchable picker: type to filter, click a row to store the id.
* Not free-text — the value is always an option id (or '' when allowEmpty).
*/
export default function SearchSelect({
options = [],
value,
onChange,
placeholder = 'Search…',
disabled = false,
loading = false,
allowEmpty = false,
emptyLabel = 'Unassigned',
error = false,
onQueryChange,
}) {
const [q, setQ] = useState('')
const [open, setOpen] = useState(false)
const root = useRef(null)
const selected = options.find((o) => String(o.id) === String(value || ''))
useEffect(() => {
function onDoc(e) {
if (root.current && !root.current.contains(e.target)) setOpen(false)
}
document.addEventListener('mousedown', onDoc)
return () => document.removeEventListener('mousedown', onDoc)
}, [])
useEffect(() => {
if (!onQueryChange || !open) return
onQueryChange(q)
}, [q, open, onQueryChange])
const term = q.trim().toLowerCase()
const filtered = onQueryChange
? options
: options.filter((o) => {
if (!term) return true
const hay = [o.name, o.email, o.role_name].filter(Boolean).join(' ').toLowerCase()
return hay.includes(term)
})
return (
<div className={`dropdown${open ? ' open' : ''}`} ref={root} style={{ width: '100%' }}>
<input
className={error ? 'err' : ''}
value={open ? q : (selected?.name || '')}
disabled={disabled || loading}
placeholder={loading ? 'Loading…' : placeholder}
autoComplete="off"
onFocus={() => { setOpen(true); setQ('') }}
onChange={(e) => { setQ(e.target.value); setOpen(true) }}
/>
{open && !disabled && !loading && (
<div className="dropdown-menu" style={{ left: 0, right: 'auto', width: '100%', minWidth: 0, maxHeight: 240, overflowY: 'auto' }}>
{allowEmpty && (
<button
type="button"
className="dropdown-link"
onClick={() => { onChange(''); setQ(''); setOpen(false) }}
>
{emptyLabel}
</button>
)}
{filtered.length === 0 && (
<div className="dropdown-link" style={{ color: 'var(--text-3)' }}>No matches</div>
)}
{filtered.map((o) => (
<button
type="button"
key={o.id}
className="dropdown-link"
onClick={() => { onChange(String(o.id)); setQ(''); setOpen(false) }}
>
{o.name}
{o.email ? <span className="cell-sub" style={{ marginLeft: 8 }}>{o.email}</span> : null}
</button>
))}
</div>
)}
</div>
)
}