import React, { createContext, useContext, useEffect, useState } from 'react'; import { useAuth } from './AuthContext'; import { Employee, EmployeeReport, Submission, CompanyReport } from '../types'; import { SAMPLE_COMPANY_REPORT, API_URL } from '../constants'; import { apiPost, apiPut } from '../services/api'; import { User } from 'firebase/auth'; import { EmployeeSubmissionAnswers } from '../employeeQuestions'; import { secureApi } from '../services/secureApi'; interface OrgData { orgId: string; companyName?: string; onboardingData?: Record; companyLogo?: string; updatedAt?: number; onboardingCompleted?: boolean; } interface OrgContextType { org: OrgData | null; user?: User; orgId: string; employees: Employee[]; submissions: Record; reports: Record; loading: boolean; upsertOrg: (data: Partial) => Promise; saveReport: (employeeId: string, report: EmployeeReport) => Promise; inviteEmployee: (args: { name: string; email: string }) => Promise<{ employeeId: string; inviteLink: string }>; issueInviteViaApi: (args: { name: string; email: string; role?: string; department?: string }) => Promise<{ code: string; inviteLink: string; employee: any }>; getInviteStatus: (code: string) => Promise<{ used: boolean; employee: any } | null>; consumeInvite: (code: string) => Promise<{ employee: any; orgId?: string } | null>; getReportVersions: (employeeId: string) => Promise>; saveReportVersion: (employeeId: string, report: EmployeeReport) => Promise; acceptInvite: (code: string) => Promise; saveCompanyReport: (summary: string) => Promise; getCompanyReportHistory: () => Promise>; saveFullCompanyReport: (report: CompanyReport) => Promise; getFullCompanyReportHistory: () => Promise; generateCompanyReport: () => Promise; generateCompanyWiki: (orgOverride?: OrgData) => Promise; seedInitialData: () => Promise; isOwner: (employeeId?: string) => boolean; submitEmployeeAnswers: (employeeId: string, answers: Record) => Promise; generateEmployeeReport: (employee: Employee) => Promise; getEmployeeReport: (employeeId: string) => Promise<{ success: boolean; report?: EmployeeReport; error?: string }>; getEmployeeReports: () => Promise<{ success: boolean; reports?: EmployeeReport[]; error?: string }>; } const OrgContext = createContext(undefined); export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: string }> = ({ children, selectedOrgId }) => { const { user } = useAuth(); const [org, setOrg] = useState(null); const [employees, setEmployees] = useState([]); const [submissions, setSubmissions] = useState>({}); const [reports, setReports] = useState>({}); const [reportVersions, setReportVersions] = useState>>({}); const [companyReports, setCompanyReports] = useState>([]); const [fullCompanyReports, setFullCompanyReports] = useState([]); const [loading, setLoading] = useState(true); // Use the provided selectedOrgId instead of deriving from user const orgId = selectedOrgId; // Load initial data using secure API useEffect(() => { if (!orgId || !user?.uid) { setLoading(false); return; } console.log('OrgContext: Loading data via secure API for orgId:', orgId); const loadOrgData = async () => { try { setLoading(true); // Load organization data try { const orgData = await secureApi.getOrgData(); setOrg({ orgId, ...orgData }); } catch (error) { console.warn('Could not load org data, creating default:', error); // Create default org if not found const defaultOrg = { name: 'Your Company', onboardingCompleted: false }; await secureApi.updateOrgData(defaultOrg); setOrg({ orgId, ...defaultOrg }); } // Load employees try { const employeesData = await secureApi.getEmployees(); setEmployees(employeesData.map(emp => ({ ...emp, initials: emp.name ? emp.name.split(' ').map(n => n[0]).join('').toUpperCase() : emp.email?.substring(0, 2).toUpperCase() || 'U' }))); } catch (error) { console.warn('Could not load employees:', error); setEmployees([]); } // Load submissions try { const submissionsData = await secureApi.getSubmissions(); setSubmissions(submissionsData); } catch (error) { console.warn('Could not load submissions:', error); setSubmissions({}); } // Load reports try { const reportsData = await secureApi.getReports(); setReports(reportsData as Record); } catch (error) { console.warn('Could not load reports:', error); setReports({}); } // Load company reports try { const companyReportsData = await secureApi.getCompanyReports(); setFullCompanyReports(companyReportsData); } catch (error) { console.warn('Could not load company reports:', error); setFullCompanyReports([]); } } catch (error) { console.error('Failed to load org data:', error); } finally { setLoading(false); } }; loadOrgData(); }, [orgId, user?.uid]); const upsertOrg = async (data: Partial) => { if (!user?.uid) { throw new Error('User authentication required'); } try { await secureApi.updateOrgData(data); // Update local state const updatedOrg = { ...(org || { orgId, name: 'Your Company' }), ...data } as OrgData; setOrg(updatedOrg); // If onboarding was completed, notify other contexts if (data.onboardingCompleted) { console.log('OrgContext: Onboarding completed, dispatching update event', { orgId: updatedOrg.orgId, onboardingCompleted: true }); window.dispatchEvent(new CustomEvent('organizationUpdated', { detail: { orgId: updatedOrg.orgId, onboardingCompleted: true } })); } } catch (error) { console.error('Failed to update organization:', error); throw error; } }; const saveReport = async (employeeId: string, report: EmployeeReport) => { if (!user?.uid) { throw new Error('User authentication required'); } try { const savedReport = await secureApi.saveReport(employeeId, report); // Update local state setReports(prev => ({ ...prev, [employeeId]: savedReport })); } catch (error) { console.error('Failed to save report:', error); throw error; } }; const inviteEmployee = async ({ name, email, role, department }: { name: string; email: string, role?: string, department?: string }) => { console.log('inviteEmployee called:', { name, email, orgId }); try { // Use secure API for invites const data = await secureApi.createInvitation({ name, email, role, department }); console.log('Invite created successfully:', { code: data.code, employee: data.employee.name, inviteLink: data.inviteLink }); // Store employee locally for immediate UI update with proper typing const newEmployee: Employee = { id: data.employee.id, name: data.employee.name, email: data.employee.email, initials: data.employee.name ? data.employee.name.split(' ').map(n => n[0]).join('').toUpperCase() : data.employee.email.substring(0, 2).toUpperCase(), department: data.employee.department, role: data.employee.role, isOwner: false, status: data.employee.status }; // Add to local state for immediate UI update setEmployees(prev => { if (prev.find(e => e.id === data.employee.id)) return prev; return [...prev, newEmployee]; }); return { employeeId: data.employee.id, inviteLink: data.inviteLink }; } catch (error) { console.error('inviteEmployee error:', error); throw error; } }; const getReportVersions = async (employeeId: string) => { // This feature is not yet implemented in secure API // Return empty array for now until we add version support to cloud functions console.warn('Report versions not yet supported in secure API'); return []; }; const saveReportVersion = async (employeeId: string, report: EmployeeReport) => { // This feature is not yet implemented in secure API console.warn('Report versions not yet supported in secure API'); const version = { id: Date.now().toString(), createdAt: Date.now(), report }; setReportVersions(prev => ({ ...prev, [employeeId]: [version, ...(prev[employeeId] || [])] })); }; const acceptInvite = async (code: string) => { if (!code || !user?.uid) return; try { await secureApi.consumeInvitation(code, user.uid); } catch (error) { console.error('Failed to accept invite:', error); throw error; } }; const saveCompanyReport = async (summary: string) => { if (!user?.uid) { throw new Error('User authentication required'); } try { const report = { id: Date.now().toString(), createdAt: Date.now(), summary }; await secureApi.saveCompanyReport(report); // Update local state setCompanyReports(prev => [{ id: report.id, createdAt: report.createdAt, summary }, ...prev]); } catch (error) { console.error('Failed to save company report:', error); throw error; } }; const getCompanyReportHistory = async () => { if (!user?.uid) { throw new Error('User authentication required'); } try { const reports = await secureApi.getCompanyReports(); return reports.map(report => ({ id: report.id, createdAt: report.createdAt || 0, summary: report.executiveSummary || '' })).sort((a, b) => b.createdAt - a.createdAt); } catch (error) { console.error('Failed to get company report history:', error); return []; } }; const seedInitialData = async () => { // Start with clean slate - let users invite their own employees and generate real data setEmployees([]); setSubmissions({}); setReports({}); setFullCompanyReports([]); }; const saveFullCompanyReport = async (report: CompanyReport) => { if (!orgId) { console.error('Cannot save company report: orgId is undefined'); throw new Error('Organization ID is required to save company report'); } if (!user?.uid) { throw new Error('User authentication required'); } try { await secureApi.saveCompanyReport(report); // Update local state after successful save setFullCompanyReports(prev => [report, ...prev]); } catch (error) { console.error('Failed to save full company report:', error); throw error; } }; const getFullCompanyReportHistory = async (): Promise => { if (!user?.uid) { throw new Error('User authentication required'); } try { const reports = await secureApi.getCompanyReports(); return reports.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)); } catch (error) { console.error('Failed to get full company report history:', error); return []; } }; const generateCompanyReport = async (): Promise => { console.log('generateCompanyReport called for org:', orgId); if (!user?.uid) { throw new Error('User authentication required'); } // Calculate concrete metrics from actual data (no AI needed) // Exclude owners from employee counts - they are company wiki contributors, not employees const actualEmployees = employees.filter(emp => !emp.isOwner); const totalEmployees = actualEmployees.length; // Only count submissions from non-owner employees const employeeSubmissions = Object.fromEntries( Object.entries(submissions).filter(([employeeId]) => { const employee = employees.find(emp => emp.id === employeeId); return employee && !employee.isOwner; }) ); const submittedEmployees = Object.keys(employeeSubmissions).length; const submissionRate = totalEmployees > 0 ? (submittedEmployees / totalEmployees) * 100 : 0; // Department breakdown (concrete data) - exclude owners const deptMap = new Map(); actualEmployees.forEach(emp => { const dept = emp.department || 'Unassigned'; deptMap.set(dept, (deptMap.get(dept) || 0) + 1); }); const departmentBreakdown = Array.from(deptMap.entries()).map(([department, count]) => ({ department, count })); try { // Use secure API for AI generation const data = await secureApi.generateCompanyWiki({ ...org, metrics: { totalEmployees, submissionRate, departmentBreakdown } }, Object.values(employeeSubmissions)); console.log('Company insights generated via AI successfully'); // Combine concrete metrics with AI insights const report: CompanyReport = { id: Date.now().toString(), createdAt: Date.now(), // Use AI-generated insights for subjective analysis ...(data as any), // Override with our concrete metrics overview: { totalEmployees, departmentBreakdown, submissionRate, lastUpdated: Date.now(), averagePerformanceScore: (data as any)?.overview?.averagePerformanceScore || 0, riskLevel: (data as any)?.overview?.riskLevel || 'Unknown' } }; console.log('Final company report object:', report); await saveFullCompanyReport(report); return report; } catch (error) { console.error('generateCompanyReport error:', error); throw error; } }; const generateCompanyWiki = async (orgOverride?: OrgData): Promise => { const orgData = orgOverride || org; console.log('generateCompanyWiki called with:', { orgData, orgId, submissionsCount: Object.keys(submissions || {}).length }); if (!orgId) { throw new Error('Organization ID is required to generate company wiki'); } if (!user?.uid) { throw new Error('User authentication required'); } // Use secure API for wiki generation try { console.log('Making API call to generateCompanyWiki...'); const payload = await secureApi.generateCompanyWiki(orgData, Object.values(submissions || {})); console.log('API success response:', payload); // Ensure the report has all required fields to prevent undefined errors const data: CompanyReport = { id: Date.now().toString(), createdAt: Date.now(), overview: { totalEmployees: employees.length, departmentBreakdown: [], submissionRate: 0, lastUpdated: Date.now(), averagePerformanceScore: 0, riskLevel: 'Unknown' }, gradingBreakdown: [], personnelChanges: { newHires: [], promotions: [], departures: [] }, immediateHiringNeeds: [], forwardOperatingPlan: { quarterlyGoals: [], resourceNeeds: [], riskMitigation: [] }, executiveSummary: 'Company report generated successfully.', // Override with API data if available ...(payload as any || {}) }; await saveFullCompanyReport(data); return data; } catch (e) { console.error('generateCompanyWiki error, falling back to local synthetic:', e); return generateCompanyReport(); } }; const isOwner = (employeeId?: string): boolean => { const currentEmployee = employeeId ? employees.find(e => e.id === employeeId) : employees.find(e => e.email === user?.email); return currentEmployee?.isOwner === true; }; const getEmployeeReport = async (employeeId: string) => { try { if (!user?.uid) { throw new Error('User authentication required'); } // Use secure API for all employee report operations const report = await secureApi.getReports(); const employeeReport = report[employeeId]; if (employeeReport) { return { success: true, report: employeeReport }; } return { success: false, error: 'Report not found' }; } catch (error) { console.error('Error fetching employee report:', error); return { success: false, error: (error as Error).message }; } }; const getEmployeeReports = async () => { try { if (!user?.uid) { throw new Error('User authentication required'); } // Use secure API for all employee report operations const reportsData = await secureApi.getReports(); const reports = Object.values(reportsData); return { success: true, reports }; } catch (error) { console.error('Error fetching employee reports:', error); return { success: false, error: (error as Error).message }; } }; const value = { org, orgId, employees, submissions, reports, loading, upsertOrg, saveReport, inviteEmployee, getReportVersions, saveReportVersion, acceptInvite, saveCompanyReport, getCompanyReportHistory, saveFullCompanyReport, getFullCompanyReportHistory, generateCompanyReport, generateCompanyWiki, seedInitialData, isOwner, issueInviteViaApi: async ({ name, email, role, department }) => { try { if (!user?.uid) { throw new Error('User authentication required'); } const data = await secureApi.createInvitation({ name, email, role, department }); // Optimistically add employee shell (not yet active until consume) setEmployees(prev => prev.find(e => e.id === data.employee.id) ? prev : [...prev, { ...data.employee, initials: data.employee.name ? data.employee.name.split(' ').map((n: string) => n[0]).join('').toUpperCase() : data.employee.email.substring(0, 2).toUpperCase() } as Employee]); return data; } catch (e) { console.error('issueInviteViaApi error', e); throw e; } }, getInviteStatus: async (code: string) => { try { return await secureApi.getInvitationStatus(code); } catch (e) { console.error('getInviteStatus error', e); return null; } }, consumeInvite: async (code: string) => { try { if (!user?.uid) { throw new Error('User authentication required'); } const result = await secureApi.consumeInvitation(code, user.uid); // Mark employee as active if (result && (result as any).employee) { setEmployees(prev => prev.find(e => e.id === (result as any).employee.id) ? prev : [...prev, (result as any).employee]); return { ...(result as any), orgId: org?.orgId }; } return null; } catch (e) { console.error('consumeInvite error', e); return null; } }, submitEmployeeAnswers: async (employeeId: string, answers: Record) => { try { // Use secure API for submission await secureApi.submitEmployeeAnswers(employeeId, answers); // Update local state for immediate UI feedback const convertedSubmission: Submission = { employeeId, answers: Object.entries(answers).map(([question, answer]) => ({ question, answer })) }; setSubmissions(prev => ({ ...prev, [employeeId]: convertedSubmission })); return true; } catch (e) { console.error('submitEmployeeAnswers error', e); return false; } }, generateEmployeeReport: async (employee: Employee) => { try { console.log('generateEmployeeReport called for:', employee.name, 'in org:', orgId); if (!user?.uid) { throw new Error('User authentication required'); } // Get submission data for this employee const submission = submissions[employee.id]; if (!submission) { throw new Error(`No questionnaire submission found for ${employee.name}. Please ensure they have completed the employee questionnaire first.`); } // Convert submission format for API let submissionAnswers: Record = {}; if (submission.answers) { if (Array.isArray(submission.answers)) { // If answers is an array of {question, answer} objects submissionAnswers = submission.answers.reduce((acc, item: any) => { acc[item.question] = item.answer; return acc; }, {} as Record); } else { // If answers is already a key-value object submissionAnswers = submission.answers as Record; } } console.log('Submission data found:', Object.keys(submissionAnswers).length, 'answers'); // Get company report and wiki data for context let companyWiki = null; try { const companyReports = await getFullCompanyReportHistory(); if (companyReports.length > 0) { companyWiki = { org: org, companyReport: companyReports[0] }; console.log('Including company context in employee report generation'); } } catch (error) { console.warn('Could not fetch company report for context:', error); } const data = await secureApi.generateEmployeeReport(employee, submissionAnswers, companyWiki); if ((data as any).report) { console.log('Employee report generated successfully'); const report = (data as any).report as EmployeeReport; setReports(prev => ({ ...prev, [employee.id]: report })); return report; } else { throw new Error('No report data received from API'); } } catch (e) { console.error('generateEmployeeReport error', e); throw e; // Re-throw to allow caller to handle } }, getEmployeeReport, getEmployeeReports, }; return ( {children} ); }; export const useOrg = () => { const ctx = useContext(OrgContext); if (!ctx) throw new Error('useOrg must be used within OrgProvider'); return ctx; };