import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { base44 } from '@/api/base44Client'; import { generateJobDescription, screenCandidate, matchTalentForJob } from './krowAi'; import { recalcProfilePatch } from './krowScore'; import { logActivity } from './userTracking'; import { evaluateChallenge } from './provingGround'; /** * The signed-in user, on the `['user']` key the assistant and the 404 page * already read. One cache entry means a profile edit reaches the navbar, the * Profile page and Owliver's greeting without any of them knowing about each * other. */ export function useCurrentUser() { return useQuery({ queryKey: ['user'], queryFn: () => base44.auth.me().catch(() => null), staleTime: Infinity, }); } /** Saves profile fields and refreshes every consumer of `['user']`. */ export function useUpdateProfile() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (patch) => base44.auth.updateMe(patch), onSuccess: (user) => { queryClient.setQueryData(['user'], user); queryClient.invalidateQueries({ queryKey: ['user'] }); }, }); } /** * Product preferences, stored on the user record. * * Reads fall back to the synchronous accessor so the first render already has * the real values rather than a default that corrects itself a tick later. */ export function usePreferences() { const { data: user } = useCurrentUser(); return { ...base44.auth.preferences(), ...(user?.preferences || {}) }; } /** Writes one or more preferences and refreshes every consumer of `['user']`. */ export function useUpdatePreferences() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (patch) => base44.auth.updatePreferences(patch), onSuccess: (user) => { queryClient.setQueryData(['user'], user); queryClient.invalidateQueries({ queryKey: ['user'] }); }, }); } export function useJobPostings() { return useQuery({ queryKey: ['jobPostings'], queryFn: () => base44.entities.JobPosting.list('-created_date', 100), }); } export function useJobPosting(id) { return useQuery({ queryKey: ['jobPosting', id], queryFn: () => base44.entities.JobPosting.get(id), enabled: !!id, }); } export function useApplications(jobPostingId) { return useQuery({ queryKey: ['applications', jobPostingId || 'all'], queryFn: () => jobPostingId ? base44.entities.JobApplication.filter({ job_posting_id: jobPostingId }, '-ai_score', 200) : base44.entities.JobApplication.list('-ai_score', 200), }); } export function useInterviews() { return useQuery({ queryKey: ['interviews'], queryFn: () => base44.entities.AIInterview.list('-created_date', 100), }); } export function useStaff() { return useQuery({ queryKey: ['staff'], queryFn: () => base44.entities.Staff.list('-created_date', 100), }); } export function useUpdateStaff() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }) => base44.entities.Staff.update(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['staff'] }); }, }); } export function useRoleCategories() { return useQuery({ queryKey: ['roleCategories'], queryFn: () => base44.entities.RoleCategory.list('-created_date', 100), }); } export function useCertifications() { return useQuery({ queryKey: ['certifications'], queryFn: () => base44.entities.Certification.list('-created_date', 200), }); } export function useCreateCertification() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data) => base44.entities.Certification.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['certifications'] }); }, }); } export function useDeleteCertification() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (id) => base44.entities.Certification.delete(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['certifications'] }); }, }); } export function useCreateRoleCategory() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data) => base44.entities.RoleCategory.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['roleCategories'] }); }, }); } export function useUserActivity() { return useQuery({ queryKey: ['userActivity'], queryFn: () => base44.entities.UserActivity.list('-created_date', 500), }); } export function useCreateJobPosting() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data) => base44.entities.JobPosting.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['jobPostings'] }); logActivity('create_position'); }, }); } export function useUpdateJobPosting() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }) => base44.entities.JobPosting.update(id, data), onSuccess: (_data, variables) => { queryClient.invalidateQueries({ queryKey: ['jobPostings'] }); queryClient.invalidateQueries({ queryKey: ['jobPosting', variables.id] }); }, }); } export function useCreateApplication() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data) => base44.entities.JobApplication.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['applications'] }); logActivity('apply_job'); }, }); } export function useUpdateApplication() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }) => base44.entities.JobApplication.update(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['applications'] }); }, }); } export function useScreenCandidate() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ application, job }) => { const result = await screenCandidate(application, job); return base44.entities.JobApplication.update(application.id, { status: 'ai_screened', ai_score: result.overall_score, ai_match_label: result.match_label, ai_summary: result.summary, ai_strengths: result.strengths, ai_gaps: result.gaps, ai_recommendation: result.recommendation, score_breakdown: result.score_breakdown, }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['applications'] }); logActivity('screen_candidate'); }, }); } export function useScreenAllCandidates() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ applications, job }) => { const results = []; for (const app of applications) { if (app.status === 'applied' || app.status === 'ai_screened') { const result = await screenCandidate(app, job); const updated = await base44.entities.JobApplication.update(app.id, { status: 'ai_screened', ai_score: result.overall_score, ai_match_label: result.match_label, ai_summary: result.summary, ai_strengths: result.strengths, ai_gaps: result.gaps, ai_recommendation: result.recommendation, score_breakdown: result.score_breakdown, }); results.push(updated); } } return results; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['applications'] }); logActivity('screen_candidate'); }, }); } export function useGenerateJobDescription() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ data, draftId }) => { const result = await generateJobDescription(data); if (draftId) { await base44.entities.JobPosting.update(draftId, { description: result.description, responsibilities: result.responsibilities, qualifications: result.qualifications, nice_to_haves: result.nice_to_haves, ai_generated: true, }); queryClient.invalidateQueries({ queryKey: ['jobPostings'] }); queryClient.invalidateQueries({ queryKey: ['jobPosting', draftId] }); } return result; }, }); } export function useHireCandidate() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ application, job }) => { const tier = application.ai_score >= 80 ? 'Skilled' : application.ai_score >= 60 ? 'Cross-Trained' : 'Beginner'; const updated = await base44.entities.JobApplication.update(application.id, { status: 'hired' }); await base44.entities.Staff.create({ name: application.applicant_name, email: application.email, phone: application.phone, role: job?.title || job?.role_category || 'Staff', profile_tier: tier, hire_date: new Date().toISOString().split('T')[0], application_id: application.id, job_posting_id: application.job_posting_id, ai_score: application.ai_score, status: 'onboarding', }); return updated; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['applications'] }); queryClient.invalidateQueries({ queryKey: ['staff'] }); logActivity('hire_candidate'); }, }); } export function useMatchTalentForJob() { return useMutation({ mutationFn: async ({ job, profiles }) => matchTalentForJob(job, profiles), }); } export function useCreateInterview() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data) => base44.entities.AIInterview.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['interviews'] }); queryClient.invalidateQueries({ queryKey: ['applications'] }); logActivity('start_interview'); }, }); } /* ===== University + Worker Intelligence Profile ===== */ export function useCourses() { return useQuery({ queryKey: ['courses'], queryFn: () => base44.entities.Course.list('-created_date', 200) }); } export function useCourse(id) { return useQuery({ queryKey: ['course', id], queryFn: () => base44.entities.Course.get(id), enabled: !!id }); } /** * Author a skill in KROW Forge. * * A skill and the challenge that verifies it are one Course record — the entity * already carries the skill (title, category, difficulty, proof_skill), the * training (quiz, estimated_minutes), the proof (challenge.type) and the * evaluation Owliver runs (challenge.rubric). So Forge authoring is a create on * the entity the whole product already reads, not a parallel store. */ export function useCreateCourse() { const queryClient = useQueryClient(); return useMutation({ mutationFn: (data) => base44.entities.Course.create(data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['courses'] }); logActivity('create_skill_training'); }, }); } /** Status changes and edits to an existing skill. */ export function useUpdateCourse() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }) => base44.entities.Course.update(id, data), onSuccess: (_data, variables) => { queryClient.invalidateQueries({ queryKey: ['courses'] }); queryClient.invalidateQueries({ queryKey: ['course', variables.id] }); }, }); } export function useLearningPaths() { return useQuery({ queryKey: ['learningPaths'], queryFn: () => base44.entities.LearningPath.list('-created_date', 100) }); } export function useBadges() { return useQuery({ queryKey: ['badges'], queryFn: () => base44.entities.Badge.list('-created_date', 200) }); } /** * The signed-in worker's profile, created on first access if absent. * * `enabled: false` skips it entirely — callers that only sometimes need the * profile must gate it, because the query has a side effect (it creates the * record) and should not run for employer or admin sessions. */ export function useWorkerProfile({ enabled = true } = {}) { return useQuery({ enabled, queryKey: ['workerProfile'], queryFn: async () => { const me = await base44.auth.me(); const list = await base44.entities.WorkerProfile.filter({ email: me.email }, '-created_date', 1); if (list.length > 0) return list[0]; return base44.entities.WorkerProfile.create({ full_name: me.full_name || (me.email ? me.email.split('@')[0] : 'Worker'), email: me.email, experience_years: 0, completed_courses: [], earned_badges: [], skills: [], languages: [], availability: [], experience: [], xp: 0, krow_score: 0, reliability_score: 0, profile_completion: 10, ai_interview_score: 0, attendance_score: 100, performance_score: 0, client_rating: 0, supervisor_rating: 0, }); }, }); } export function useWorkerProfiles() { return useQuery({ queryKey: ['workerProfiles'], queryFn: () => base44.entities.WorkerProfile.list('-krow_score', 500), }); } export function useUpdateWorkerProfile() { const queryClient = useQueryClient(); return useMutation({ mutationFn: ({ id, data }) => base44.entities.WorkerProfile.update(id, data), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['workerProfile'] }), }); } export function useCompleteCourse() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ profile, course, quizScore }) => { const already = (profile.completed_courses || []).some(c => c.course_id === course.id); const completed_courses = already ? profile.completed_courses : [ ...profile.completed_courses, { course_id: course.id, title: course.title, score: quizScore, completed_date: new Date().toISOString() }, ]; const level = course.difficulty === 'beginner' ? 'bronze' : course.difficulty === 'intermediate' ? 'silver' : 'gold'; const earned_badges = (already || !course.badge_reward) ? profile.earned_badges : [ ...profile.earned_badges, { name: course.badge_reward, level, course_id: course.id, earned_date: new Date().toISOString() }, ]; const xp = (profile.xp || 0) + (already ? 0 : (course.xp || 0)); const merged = { ...profile, completed_courses, earned_badges, xp }; const patch = { completed_courses, earned_badges, xp, ...recalcProfilePatch(merged) }; return base44.entities.WorkerProfile.update(profile.id, patch); }, onSuccess: () => queryClient.invalidateQueries({ queryKey: ['workerProfile'] }), }); } /* ===== Proving Ground — evidence-based challenges ===== */ export function useEvidenceList(workerEmail) { return useQuery({ queryKey: ['evidence', workerEmail || 'all'], queryFn: () => workerEmail ? base44.entities.Evidence.filter({ worker_email: workerEmail }, '-created_date', 200) : base44.entities.Evidence.list('-created_date', 200), enabled: !!workerEmail, }); } export function useSubmitChallenge() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ profile, course, type, mediaUrl, transcript, identified }) => { const result = await evaluateChallenge(course, { type, mediaUrl, transcript, identified, workerName: profile?.full_name }); const verdict = result.verdict || 'needs_work'; const score = Math.round(Number(result.score) || 0); const passScore = Number(course.pass_score) || 70; const passed = verdict === 'verified' && score >= passScore; const evidence = await base44.entities.Evidence.create({ course_id: course.id, course_title: course.title, skill: course.proof_skill || course.title, worker_email: profile.email, worker_name: profile.full_name, type, media_url: mediaUrl || '', transcript: identified ? JSON.stringify(identified) : (transcript || ''), ai_verdict: verdict, ai_score: score, ai_rubric: result.rubric || {}, ai_feedback: result.feedback || '', }); if (!passed) return { evidence, passed, result }; const already = (profile.completed_courses || []).some((c) => c.course_id === course.id); const completed_courses = already ? profile.completed_courses : [ ...profile.completed_courses, { course_id: course.id, title: course.title, score, completed_date: new Date().toISOString() }, ]; const level = course.difficulty === 'beginner' ? 'bronze' : course.difficulty === 'intermediate' ? 'silver' : 'gold'; const earned_badges = (already || !course.badge_reward) ? profile.earned_badges : [ ...profile.earned_badges, { name: course.badge_reward, level, course_id: course.id, earned_date: new Date().toISOString() }, ]; const skill = course.proof_skill || course.title; const capabilities = (profile.capabilities || []).some((c) => c.skill === skill) ? profile.capabilities : [...(profile.capabilities || []), { skill, level: 'Verified', evidence_id: evidence.id, status: 'verified', verified_date: new Date().toISOString() }]; const xp = (profile.xp || 0) + (already ? 0 : (course.xp || 0)); const merged = { ...profile, completed_courses, earned_badges, capabilities, xp }; const patch = { completed_courses, earned_badges, capabilities, xp, ...recalcProfilePatch(merged) }; await base44.entities.WorkerProfile.update(profile.id, patch); return { evidence, passed, result }; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['workerProfile'] }); queryClient.invalidateQueries({ queryKey: ['evidence'] }); }, }); } export function useVerifyEvidence() { const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ evidenceId }) => base44.entities.Evidence.update(evidenceId, { supervisor_verified: true, supervisor_name: 'Supervisor', verified_date: new Date().toISOString(), }), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['evidence'] }), }); }