HR-ATS-Portal/frontend/src/app/GlobalSearch.jsx

101 lines
4.0 KiB
JavaScript

/* Global search — App.search from js/app.js:171-191. Same sources and the same
4/4/3 caps. The inline onclick="App.searchGo(...)" strings become navigate()
calls, and the setTimeout(cb, 120) hack that waited for the old router to
swap innerHTML is gone: the target screen reads `state.open` instead. */
import { useMemo, useRef, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useQuery } from '@tanstack/react-query'
import { seedQuery } from '../data/seedQueries'
import { Avatar, Icon } from '../ui/primitives'
export default function GlobalSearch({ inputRef }) {
const navigate = useNavigate()
const [q, setQ] = useState('')
const [open, setOpen] = useState(false)
const boxRef = useRef(null)
const { data: jobs = [] } = useQuery(seedQuery('jobs'))
const { data: candidates = [] } = useQuery(seedQuery('candidates'))
const { data: managers = [] } = useQuery(seedQuery('managers'))
const results = useMemo(() => {
const term = q.trim().toLowerCase()
if (!term) return null
return {
jobs: jobs.filter((j) => (j.title + j.id + j.department).toLowerCase().includes(term)).slice(0, 4),
candidates: candidates.filter((c) => (c.name + c.email + c.jobTitle).toLowerCase().includes(term)).slice(0, 4),
managers: managers.filter((m) => m.name.toLowerCase().includes(term)).slice(0, 3),
}
}, [q, jobs, candidates, managers])
function go(path, state) {
setQ('')
setOpen(false)
navigate(path, { state })
}
const empty = results && !results.jobs.length && !results.candidates.length && !results.managers.length
return (
<div className="topbar-search" onClick={(e) => e.stopPropagation()}>
<Icon name="search" />
<input
type="text"
ref={inputRef}
value={q}
placeholder="Search jobs, candidates, managers…"
autoComplete="off"
onChange={(e) => {
setQ(e.target.value)
setOpen(Boolean(e.target.value.trim()))
}}
onFocus={() => setOpen(Boolean(q.trim()))}
/>
<div className={`search-results${open && results ? ' open' : ''}`} ref={boxRef}>
{results && (
<>
{results.jobs.length > 0 && <div className="search-group-label">Jobs</div>}
{results.jobs.map((j) => (
<div key={j.id} className="search-item" onClick={() => go('/jobs', { openJob: j.id })}>
<span className="kpi-icn i-indigo" style={{ width: 32, height: 32, borderRadius: 8 }}>
<Icon name="briefcase" />
</span>
<div>
<div className="si-title">{j.title}</div>
<div className="si-sub">{j.id} · {j.department}</div>
</div>
</div>
))}
{results.candidates.length > 0 && <div className="search-group-label">Candidates</div>}
{results.candidates.map((c) => (
<div key={c.id} className="search-item" onClick={() => go('/candidates', { openCandidate: c.id })}>
<Avatar name={c.name} initials={c.initials} color={c.color} />
<div>
<div className="si-title">{c.name}</div>
<div className="si-sub">{c.jobTitle}</div>
</div>
</div>
))}
{results.managers.length > 0 && <div className="search-group-label">Hiring Managers</div>}
{results.managers.map((m) => (
<div key={m.id} className="search-item" onClick={() => go('/managers', { openManager: m.id })}>
<Avatar name={m.name} initials={m.initials} color={m.color} />
<div>
<div className="si-title">{m.name}</div>
<div className="si-sub">{m.title}</div>
</div>
</div>
))}
{empty && <div className="search-empty">No results for &ldquo;{q}&rdquo;</div>}
</>
)}
</div>
<kbd className="search-kbd">K</kbd>
</div>
)
}