import React, { useState } from 'react'; import { useApplications, useJobPostings, useScreenAllCandidates, useUpdateApplication } from '@/lib/krowHooks'; import { base44 } from '@/api/base44Client'; import CandidateCard from '@/components/krow/CandidateCard'; import AIInterviewModal from '@/components/krow/AIInterviewModal'; import ScheduleInterviewModal from '@/components/krow/ScheduleInterviewModal'; import MessageCandidateModal from '@/components/krow/MessageCandidateModal'; import { toast } from 'react-hot-toast'; import { AssistantTrigger } from '@/components/ai-assistant'; export default function Candidates() { const { data: applications = [], isLoading } = useApplications(); const { data: postings = [] } = useJobPostings(); const screenAll = useScreenAllCandidates(); const [search, setSearch] = useState(''); const [stageFilter, setStageFilter] = useState('all'); const [scoreFilter, setScoreFilter] = useState('all'); const [positionFilter, setPositionFilter] = useState('all'); const [interviewApp, setInterviewApp] = useState(null); const [scheduleApp, setScheduleApp] = useState(null); const [messageApp, setMessageApp] = useState(null); const updateApp = useUpdateApplication(); const handleDecline = async (app) => { try { await updateApp.mutateAsync({ id: app.id, data: { status: 'rejected' } }); toast.success(`${app.applicant_name} declined`); } catch (e) { toast.error('Failed to decline candidate'); } }; const handleDelete = async (app) => { try { await base44.entities.JobApplication.delete(app.id); toast.success(`${app.applicant_name} deleted`); } catch (e) { toast.error('Failed to delete candidate'); } }; const postingById = Object.fromEntries(postings.map(p => [p.id, p])); const resolveTitle = (a) => a.job_title || postingById[a.job_posting_id]?.title || ''; const sorted = [...applications].sort((a, b) => (b.ai_score || 0) - (a.ai_score || 0)); const positionOptions = Array.from(new Set(applications.map(resolveTitle).filter(Boolean))).sort(); const filtered = sorted.filter(a => { const title = resolveTitle(a).toLowerCase(); const q = search.toLowerCase(); const matchesSearch = !search || a.applicant_name?.toLowerCase().includes(q) || a.email?.toLowerCase().includes(q) || title.includes(q); const matchesStage = stageFilter === 'all' || a.status === stageFilter; const matchesPosition = positionFilter === 'all' || resolveTitle(a) === positionFilter; const matchesScore = scoreFilter === 'all' || (scoreFilter === 'high' && a.ai_score >= 80) || (scoreFilter === 'mid' && a.ai_score >= 60 && a.ai_score < 80) || (scoreFilter === 'low' && a.ai_score > 0 && a.ai_score < 60); return matchesSearch && matchesStage && matchesPosition && matchesScore; }); const handleAction = (app) => { if (app.status === 'interview') { setInterviewApp(app); } else { setScheduleApp(app); } }; const handleScreenAll = async () => { const unscreened = applications.filter(a => a.status === 'applied'); if (unscreened.length === 0) { toast('All candidates have been screened'); return; } const firstJob = postings.find(p => p.id === unscreened[0].job_posting_id); try { await screenAll.mutateAsync({ applications: unscreened, job: firstJob }); toast.success(`Screened ${unscreened.length} candidates`); } catch (e) { toast.error('Screening failed'); } }; return (
No candidates match your filters