profit-planet-frontend/src/app/admin/pool-management/manage/components/PoolSearchModal.tsx
DeathKaioken 646c293bc1 .
Co-authored-by: Copilot <copilot@github.com>
2026-05-04 06:22:10 +02:00

190 lines
8.0 KiB
TypeScript

import { MagnifyingGlassIcon, UsersIcon, XMarkIcon } from '@heroicons/react/24/outline'
import type { UserCandidate } from '../hooks/usePoolManageState'
type Translator = (key: string, params?: Record<string, string | number>) => string
type Props = {
t: Translator
searchOpen: boolean
query: string
setQuery: (value: string) => void
loading: boolean
error: string
hasSearched: boolean
candidates: UserCandidate[]
selectedCandidates: Set<string>
savingMembers: boolean
onClose: () => void
onSearch: () => Promise<void>
onClear: () => void
onToggleCandidate: (id: string) => void
onAddSingle: (candidate: UserCandidate) => Promise<void>
onAddSelected: () => Promise<void>
}
export default function PoolSearchModal({
t,
searchOpen,
query,
setQuery,
loading,
error,
hasSearched,
candidates,
selectedCandidates,
savingMembers,
onClose,
onSearch,
onClear,
onToggleCandidate,
onAddSingle,
onAddSelected,
}: Props) {
if (!searchOpen) return null
return (
<div className="fixed inset-0 z-50">
<div className="absolute inset-0 bg-black/40 backdrop-blur-sm" onClick={onClose} />
<div className="absolute inset-0 flex items-center justify-center p-4 sm:p-6">
<div className="w-full max-w-3xl max-h-[90vh] rounded-2xl overflow-hidden bg-white shadow-2xl ring-1 ring-black/10 flex flex-col">
<div className="px-6 py-5 border-b border-slate-100 flex items-center justify-between gap-3">
<h4 className="text-lg font-semibold text-slate-900 break-words">{t('autofix.ka6be28d2')}</h4>
<button
onClick={onClose}
className="p-1.5 rounded-md text-slate-500 hover:bg-slate-100 hover:text-slate-700 transition"
aria-label={t('common.close')}
>
<XMarkIcon className="h-5 w-5" />
</button>
</div>
<form
onSubmit={(event) => {
event.preventDefault()
void onSearch()
}}
className="px-6 py-4 grid grid-cols-1 md:grid-cols-5 gap-3 border-b border-slate-100"
>
<div className="md:col-span-3">
<div className="relative">
<MagnifyingGlassIcon className="pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
<input
value={query}
onChange={(event) => setQuery(event.target.value)}
placeholder={t('autofix.kb35549bb')}
className="w-full rounded-md bg-slate-50 border border-slate-300 text-sm text-slate-900 placeholder-slate-400 pl-8 pr-3 py-2 focus:ring-2 focus:ring-slate-900 focus:border-transparent transition"
/>
</div>
</div>
<div className="flex gap-2 md:col-span-2">
<button
type="submit"
disabled={loading || query.trim().length < 3}
className="flex-1 rounded-md bg-slate-900 hover:bg-slate-800 disabled:opacity-50 text-white px-3 py-2 text-sm font-medium shadow-sm transition"
>
{loading ? t('autofix.kf5d7b213') : t('common.search')}
</button>
<button
type="button"
onClick={onClear}
className="rounded-md border border-slate-300 bg-white px-3 py-2 text-sm text-slate-700 hover:bg-slate-50 transition"
>
{t('autofix.k76f12c8a')}
</button>
</div>
</form>
<div className="px-6 pt-1 pb-3 text-right text-xs text-slate-500">{t('autofix.ke4c4a858')}</div>
<div className="px-6 py-4 overflow-y-auto min-h-0 flex-1">
{error && <div className="text-sm text-red-600 mb-3 break-words">{error}</div>}
{!error && query.trim().length < 3 && (
<div className="py-8 text-sm text-slate-500 text-center">{t('autofix.kb87eb38b')}</div>
)}
{!error && hasSearched && loading && candidates.length === 0 && (
<ul className="space-y-0 divide-y divide-slate-200 border border-slate-200 rounded-md bg-slate-50">
{Array.from({ length: 5 }).map((_, index) => (
<li key={index} className="animate-pulse px-4 py-3">
<div className="h-3.5 w-36 bg-slate-200 rounded" />
<div className="mt-2 h-3 w-56 bg-slate-100 rounded" />
</li>
))}
</ul>
)}
{!error && hasSearched && !loading && candidates.length === 0 && (
<div className="py-8 text-sm text-slate-500 text-center">{t('autofix.k54f49724')}</div>
)}
{!error && candidates.length > 0 && (
<ul className="divide-y divide-slate-200 border border-slate-200 rounded-lg bg-white">
{candidates.map((candidate) => (
<li key={candidate.id} className="px-4 py-3 flex flex-wrap items-center justify-between gap-3 hover:bg-slate-50 transition">
<label className="min-w-0 flex items-start gap-3 cursor-pointer">
<input
type="checkbox"
className="mt-1 h-4 w-4 rounded border-slate-300 text-slate-900 focus:ring-slate-900"
checked={selectedCandidates.has(candidate.id)}
onChange={() => onToggleCandidate(candidate.id)}
/>
<div className="min-w-0">
<div className="flex items-center gap-2">
<UsersIcon className="h-4 w-4 text-slate-900" />
<span className="text-sm font-medium text-slate-900 break-words">{candidate.name}</span>
</div>
<div className="mt-0.5 text-[11px] text-slate-600 break-all">{candidate.email}</div>
</div>
</label>
<button
onClick={() => void onAddSingle(candidate)}
className="shrink-0 inline-flex items-center rounded-md bg-slate-900 hover:bg-slate-800 text-white px-3 py-1.5 text-xs font-medium shadow-sm transition"
>
{t('autofix.k8c011ed3')}
</button>
</li>
))}
</ul>
)}
{loading && candidates.length > 0 && (
<div className="pointer-events-none relative">
<div className="absolute inset-0 flex items-center justify-center bg-white/60">
<span className="h-5 w-5 rounded-full border-2 border-slate-900 border-b-transparent animate-spin" />
</div>
</div>
)}
</div>
<div className="px-6 py-3 border-t border-slate-100 flex flex-wrap items-center justify-between gap-3 bg-slate-50">
<div className="text-xs text-slate-600">
{selectedCandidates.size > 0
? t('autofix.k3ab09ef0').replace('{count}', String(selectedCandidates.size))
: t('autofix.k2042d9f2')}
</div>
<div className="flex items-center gap-2">
<button
onClick={onClose}
className="text-sm rounded-md px-4 py-2 font-medium bg-white text-slate-700 border border-slate-300 hover:bg-slate-50 transition"
>
{t('autofix.k0f13bc22')}
</button>
<button
onClick={() => void onAddSelected()}
disabled={selectedCandidates.size === 0 || savingMembers}
className="text-sm rounded-md px-4 py-2 font-medium bg-slate-900 text-white hover:bg-slate-800 transition disabled:opacity-50 disabled:cursor-not-allowed"
>
{savingMembers ? t('autofix.k89bc3412') : t('autofix.k7e44aa19')}
</button>
</div>
</div>
</div>
</div>
</div>
)
}