import React, { useMemo, useState } from 'react'; import { toast } from '@/components/ds'; import { useWorkerProfiles } from '@/lib/krowHooks'; import { AdminPage } from '@/components/admin/PageShell'; import { UiTreeRenderer } from '@/components/ui-tree/UiTreeRenderer'; import { useUiEditing } from '@/components/ui-tree/UiEditingProvider'; import { UiEditor } from '@/components/ui-editor/UiEditor'; import { composePage } from '@/lib/ui/composition'; import { SORTS } from '@/pages/admin/talent-pool/nodes'; import '@/components/ui-tree/nodeTypes'; import '@/pages/admin/talent-pool/nodes'; /** * Admin Talent Pool — talent intelligence, not a card gallery. */ const BANDS = { all: () => true, elite: (s) => s >= 90, excellent: (s) => s >= 75 && s < 90, solid: (s) => s >= 60 && s < 75, building: (s) => s > 0 && s < 60, unscored: (s) => !s, }; export default function AdminTalentPool() { const { data: profiles = [], isLoading } = useWorkerProfiles(); const [search, setSearch] = useState(''); const [skill, setSkill] = useState('all'); const [experience, setExperience] = useState('all'); const [location, setLocation] = useState('all'); const [band, setBand] = useState('all'); const [availability, setAvailability] = useState('all'); const [sort, setSort] = useState('score'); const [saved, setSaved] = useState([]); const skills = useMemo( () => [...new Set(profiles.flatMap((p) => p.skills || []))].sort(), [profiles] ); const locations = useMemo( () => [...new Set(profiles.map((p) => p.address).filter(Boolean))].sort(), [profiles] ); const availabilities = useMemo( () => [...new Set(profiles.flatMap((p) => p.availability || []))].sort(), [profiles] ); const filtered = useMemo(() => { const q = search.toLowerCase(); return profiles .filter((p) => { if (q && !`${p.full_name} ${p.email} ${p.desired_position} ${p.current_position} ${(p.skills || []).join(' ')}`.toLowerCase().includes(q)) return false; if (skill !== 'all' && !(p.skills || []).includes(skill)) return false; if (location !== 'all' && p.address !== location) return false; if (availability !== 'all' && !(p.availability || []).includes(availability)) return false; if (!BANDS[band](p.krow_score || 0)) return false; if (experience === '5plus' && (p.experience_years || 0) < 5) return false; if (experience === '2to5' && ((p.experience_years || 0) < 2 || (p.experience_years || 0) >= 5)) return false; if (experience === 'under2' && (p.experience_years || 0) >= 2) return false; return true; }) .sort(SORTS[sort].compare); }, [profiles, search, skill, experience, location, band, availability, sort]); const isFiltered = Boolean(search) || [skill, experience, location, band, availability].some((v) => v !== 'all'); const clearFilters = () => { setSearch(''); setSkill('all'); setExperience('all'); setLocation('all'); setBand('all'); setAvailability('all'); }; const segments = useMemo(() => { const getBandMembers = (min, max) => profiles.filter((p) => { const s = p.krow_score || 0; return s >= min && (max == null || s < max); }); return [ { label: 'Elite', hint: '90+', members: getBandMembers(90), tone: 'brand' }, { label: 'Excellent', hint: '75–89', members: getBandMembers(75, 90), tone: 'success' }, { label: 'Solid', hint: '60–74', members: getBandMembers(60, 75), tone: 'default' }, { label: 'Building', hint: 'under 60', members: getBandMembers(1, 60), tone: 'warning' }, { label: 'Unscored', hint: 'no score', members: profiles.filter((p) => !(p.krow_score > 0)), tone: 'risk' }, ].map((s) => ({ ...s, count: s.members.length, available: s.members.filter((p) => (p.availability || []).length > 0).length, avgExperience: (() => { /* 0 years is "not stated", not "first year" — the cards that show it gate on experience_years > 0, so the average is over the same set. */ const stated = s.members.filter((p) => (p.experience_years || 0) > 0); return stated.length ? Math.round(stated.reduce((sum, p) => sum + p.experience_years, 0) / stated.length) : 0; })(), })); }, [profiles]); const toggleSave = (id, name) => { setSaved((prev) => { const next = prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]; toast.success(prev.includes(id) ? `${name} removed from saved` : `${name} saved`); return next; }); }; const context = useMemo(() => ({ profiles, segments, filtered, isLoading, isFiltered, clearFilters, saved, toggleSave, search, setSearch, skill, setSkill, experience, setExperience, location, setLocation, band, setBand, availability, setAvailability, sort, setSort, skills, locations, availabilities, }), [ profiles, segments, filtered, isLoading, isFiltered, saved, search, skill, experience, location, band, availability, sort, skills, locations, availabilities, ]); return ( ); } /** The composed page. */ function TalentPoolComposition({ context }) { const editing = useUiEditing(); const tree = editing?.tree || composePage('talent-pool').tree; return ; }