import React, { createContext, useContext, useEffect, useState } from 'react'; import { collection, doc, getDoc, getDocs, onSnapshot, setDoc } from 'firebase/firestore'; import { db, isFirebaseConfigured } from '../services/firebase'; import { useAuth } from './AuthContext'; import { Employee, Report, Submission, CompanyReport } from '../types'; import { REPORT_DATA, SUBMISSIONS_DATA, SAMPLE_COMPANY_REPORT, API_URL } from '../constants'; import { demoStorage } from '../services/demoStorage'; interface OrgData { orgId: string; name: string; industry?: string; size?: string; description?: string; mission?: string; vision?: string; values?: string; foundingYear?: string; evolution?: string; majorMilestones?: string; advantages?: string; vulnerabilities?: string; competitors?: string; marketPosition?: string; currentChallenges?: string; shortTermGoals?: string; longTermGoals?: string; keyMetrics?: string; cultureDescription?: string; workEnvironment?: string; leadershipStyle?: string; communicationStyle?: string; additionalContext?: string; onboardingCompleted?: boolean; } interface OrgContextType { org: OrgData | null; orgId: string; employees: Employee[]; submissions: Record; reports: Record; upsertOrg: (data: Partial) => Promise; saveReport: (employeeId: string, report: Report) => 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; emailLink: string; employee: any }>; getInviteStatus: (code: string) => Promise<{ used: boolean; employee: any } | null>; consumeInvite: (code: string) => Promise<{ employee: any } | null>; getReportVersions: (employeeId: string) => Promise>; saveReportVersion: (employeeId: string, report: Report) => 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?: Report; error?: string }>; getEmployeeReports: () => Promise<{ success: boolean; reports?: Report[]; 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([]); // Use the provided selectedOrgId instead of deriving from user const orgId = selectedOrgId; useEffect(() => { console.log('OrgContext effect running, orgId:', orgId, 'isFirebaseConfigured:', isFirebaseConfigured); if (!orgId) return; // Wait for orgId to be available if (!isFirebaseConfigured) { // Demo mode data - use persistent localStorage with proper initialization console.log('Setting up demo org data with persistence'); // Get or create persistent demo org let demoOrg = demoStorage.getOrganization(orgId); if (!demoOrg) { demoOrg = { orgId: orgId, name: 'Demo Company', onboardingCompleted: false }; demoStorage.saveOrganization(demoOrg); // Initialize with empty employee list for clean start // (Removed automatic seeding of 6 default employees per user feedback) // Create sample submissions for multiple employees const sampleSubmissions = [ { employeeId: 'AG', orgId, createdAt: Date.now(), answers: { role_clarity: "I understand my role very clearly as Influencer Coordinator & Business Development Outreach.", key_outputs: "Recruited 15 new influencers, managed 8 campaigns, initiated 3 business development partnerships.", bottlenecks: "Campaign organization could be better, sometimes unclear on priorities between recruiting and outreach.", hidden_talent: "Strong relationship building skills that could be leveraged for client-facing work.", retention_risk: "Happy with the company but would like more structure and clearer processes.", energy_distribution: "50% influencer recruiting, 30% campaign support, 20% business development outreach.", performance_indicators: "Good influencer relationships, but delivery timeline improvements needed.", workflow: "Morning outreach, afternoon campaign work, weekly business development calls." } }, { employeeId: 'MB', orgId, createdAt: Date.now(), answers: { role_clarity: "I understand my role as a Senior Developer very clearly. I'm responsible for architecting solutions, code reviews, and mentoring junior developers.", key_outputs: "Delivered 3 major features this quarter, reduced technical debt by 20%, and led code review process improvements.", bottlenecks: "Sometimes waiting for design specs from the product team, and occasional deployment pipeline issues.", hidden_talent: "I have strong business analysis skills and could help bridge the gap between technical and business requirements.", retention_risk: "I'm satisfied with my current role and compensation. The only concern would be limited growth opportunities.", energy_distribution: "80% development work, 15% mentoring, 5% planning and architecture.", performance_indicators: "Code quality metrics improved, zero production bugs in my recent releases, positive peer feedback.", workflow: "Morning standup, focused coding blocks, afternoon reviews and collaboration, weekly planning sessions." } }, { employeeId: 'KT', orgId, createdAt: Date.now(), answers: { role_clarity: "My role as Marketing Manager is clear - I oversee campaigns, analyze performance metrics, and coordinate with sales.", key_outputs: "Launched 5 successful campaigns this quarter, increased lead quality by 30%, improved attribution tracking.", bottlenecks: "Limited budget for premium tools, sometimes slow approval process for creative assets.", hidden_talent: "I have experience with data science and could help build predictive models for customer behavior.", retention_risk: "Overall happy, but would like more strategic input in product positioning and pricing decisions.", energy_distribution: "40% campaign execution, 30% analysis and reporting, 20% strategy, 10% team coordination.", performance_indicators: "Campaign ROI improved by 25%, lead conversion rates increased, better cross-team collaboration.", workflow: "Weekly campaign planning, daily performance monitoring, bi-weekly strategy reviews, monthly board reporting." } } ]; // Save all sample submissions sampleSubmissions.forEach(submission => { demoStorage.saveSubmission(submission); }); // Save sample employee report (only for AG initially) demoStorage.saveEmployeeReport(orgId, REPORT_DATA.employeeId, REPORT_DATA); // Save sample company report demoStorage.saveCompanyReport(orgId, SAMPLE_COMPANY_REPORT); } // Load persistent demo data setOrg({ orgId, name: demoOrg.name, onboardingCompleted: demoOrg.onboardingCompleted }); // Convert employees to expected format const demoEmployees = demoStorage.getEmployeesByOrg(orgId); const convertedEmployees: Employee[] = demoEmployees.map(emp => ({ id: emp.id, name: emp.name, email: emp.email, initials: emp.name ? emp.name.split(' ').map(n => n[0]).join('').toUpperCase() : emp.email.substring(0, 2).toUpperCase(), department: emp.department, role: emp.role, isOwner: emp.id === user?.uid })); setEmployees(convertedEmployees); // Convert submissions to expected format const orgSubmissions = demoStorage.getSubmissionsByOrg(orgId); const convertedSubmissions: Record = {}; Object.entries(orgSubmissions).forEach(([employeeId, demoSub]) => { convertedSubmissions[employeeId] = { employeeId, answers: Object.entries(demoSub.answers).map(([question, answer]) => ({ question, answer })) }; }); setSubmissions(convertedSubmissions); // Convert reports to expected format const orgReports = demoStorage.getEmployeeReportsByOrg(orgId); setReports(orgReports); // Get company reports const companyReports = demoStorage.getCompanyReportsByOrg(orgId); setFullCompanyReports(companyReports); return; } console.log('Setting up Firebase org data'); const orgRef = doc(db, 'orgs', orgId); getDoc(orgRef).then(async (snap) => { if (snap.exists()) { setOrg({ orgId, ...(snap.data() as any) }); } else { const seed = { name: 'Your Company', onboardingCompleted: false }; await setDoc(orgRef, seed); setOrg({ orgId, ...(seed as any) }); } }); const employeesCol = collection(db, 'orgs', orgId, 'employees'); const unsubEmp = onSnapshot(employeesCol, (snap) => { const arr: Employee[] = []; snap.forEach((d) => arr.push({ id: d.id, ...(d.data() as any) })); setEmployees(arr); }); const submissionsCol = collection(db, 'orgs', orgId, 'submissions'); const unsubSub = onSnapshot(submissionsCol, (snap) => { const map: Record = {}; snap.forEach((d) => (map[d.id] = { employeeId: d.id, ...(d.data() as any) })); setSubmissions(map); }); const reportsCol = collection(db, 'orgs', orgId, 'reports'); const unsubRep = onSnapshot(reportsCol, (snap) => { const map: Record = {}; snap.forEach((d) => (map[d.id] = { employeeId: d.id, ...(d.data() as any) } as Report)); setReports(map); }); return () => { unsubEmp(); unsubSub(); unsubRep(); }; }, [orgId]); const upsertOrg = async (data: Partial) => { if (!isFirebaseConfigured) { const updatedOrg = { ...(org || { orgId, name: 'Demo Company' }), ...data } as OrgData; setOrg(updatedOrg); // Also sync with server for multi-tenant persistence try { const response = await fetch(`${API_URL}/api/organizations/${orgId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) { console.warn('Failed to sync organization data with server'); } } catch (error) { console.warn('Failed to sync organization data:', error); } } else { // Firebase mode - save to Firestore const orgRef = doc(db, 'orgs', orgId); await setDoc(orgRef, data, { merge: true }); // Update local state const updatedOrg = { ...(org || { orgId, name: 'Your Company' }), ...data } as OrgData; setOrg(updatedOrg); } }; const updateOrg = async (data: Partial) => { if (!isFirebaseConfigured) { const updatedOrg = { ...(org || { orgId, name: 'Demo Company' }), ...data } as OrgData; setOrg(updatedOrg); // Also sync with server for multi-tenant persistence try { const response = await fetch(`${API_URL}/api/organizations/${orgId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); if (!response.ok) { console.warn('Failed to sync organization data with server'); } } catch (error) { console.warn('Failed to sync organization data:', error); } return; } const orgRef = doc(db, 'orgs', orgId); await setDoc(orgRef, data, { merge: true }); }; const saveReport = async (employeeId: string, report: Report) => { if (!isFirebaseConfigured) { setReports(prev => ({ ...prev, [employeeId]: report })); // Persist to localStorage demoStorage.saveEmployeeReport(orgId, employeeId, report); return; } const ref = doc(db, 'orgs', orgId, 'reports', employeeId); await setDoc(ref, report, { merge: true }); }; const inviteEmployee = async ({ name, email }: { name: string; email: string }) => { // Always use Cloud Functions for invites to ensure multi-tenant compliance const response = await fetch(`${API_URL}/createInvitation`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, orgId }) }); if (!response.ok) { throw new Error(`Failed to create invite: ${response.status}`); } const data = await response.json(); const { code, employee, inviteLink } = data; // Store employee locally for immediate UI update if (!isFirebaseConfigured) { const newEmployee = { ...employee, orgId }; setEmployees(prev => { if (prev.find(e => e.id === employee.id)) return prev; return [...prev, newEmployee]; }); demoStorage.saveEmployee(newEmployee); } else { // For Firebase, the employee will be created when they accept the invite // But we can add them to local state for immediate UI update const newEmployee = { ...employee, orgId }; setEmployees(prev => { if (prev.find(e => e.id === employee.id)) return prev; return [...prev, newEmployee]; }); } return { employeeId: employee.id, inviteLink }; }; const getReportVersions = async (employeeId: string) => { if (!isFirebaseConfigured) { return reportVersions[employeeId] || []; } const col = collection(db, 'orgs', orgId, 'reports', employeeId, 'versions'); const snap = await getDocs(col); const arr: Array<{ id: string; createdAt: number; report: Report }> = []; snap.forEach(d => { const data = d.data() as any; arr.push({ id: d.id, createdAt: data.createdAt ?? 0, report: data.report as Report }); }); return arr.sort((a, b) => b.createdAt - a.createdAt); }; const saveReportVersion = async (employeeId: string, report: Report) => { const version = { id: Date.now().toString(), createdAt: Date.now(), report }; if (!isFirebaseConfigured) { setReportVersions(prev => ({ ...prev, [employeeId]: [version, ...(prev[employeeId] || [])] })); return; } const ref = doc(db, 'orgs', orgId, 'reports', employeeId, 'versions', version.id); await setDoc(ref, { createdAt: version.createdAt, report }); }; const acceptInvite = async (code: string) => { if (!code) return; if (!isFirebaseConfigured) { // Demo mode: mark invite as used demoStorage.markInviteUsed(code); return; } const inviteRef = doc(db, 'orgs', orgId, 'invites', code); const snap = await getDoc(inviteRef); if (!snap.exists()) return; const data = snap.data() as any; // Minimal: mark accepted await setDoc(inviteRef, { ...data, acceptedAt: Date.now() }, { merge: true }); }; const saveCompanyReport = async (summary: string) => { const id = Date.now().toString(); const createdAt = Date.now(); if (!isFirebaseConfigured) { const reportData = { id, createdAt, summary }; setCompanyReports(prev => [reportData, ...prev]); // Persist to localStorage (note: this method stores simple reports) return; } const ref = doc(db, 'orgs', orgId, 'companyReports', id); await setDoc(ref, { createdAt, summary }); }; const getCompanyReportHistory = async () => { if (!isFirebaseConfigured) { return companyReports; } const col = collection(db, 'orgs', orgId, 'companyReports'); const snap = await getDocs(col); const arr: Array<{ id: string; createdAt: number; summary: string }> = []; snap.forEach(d => { const data = d.data() as any; arr.push({ id: d.id, createdAt: data.createdAt ?? 0, summary: data.summary ?? '' }); }); return arr.sort((a, b) => b.createdAt - a.createdAt); }; const seedInitialData = async () => { if (!isFirebaseConfigured) { // Start with empty employee list for clean demo experience setEmployees([]); setSubmissions({ [SUBMISSIONS_DATA.employeeId]: SUBMISSIONS_DATA }); setReports({ [REPORT_DATA.employeeId]: REPORT_DATA }); setFullCompanyReports([SAMPLE_COMPANY_REPORT]); return; } // Start with clean slate - let users invite their own employees // (Removed automatic seeding per user feedback) }; const saveFullCompanyReport = async (report: CompanyReport) => { if (!isFirebaseConfigured) { setFullCompanyReports(prev => [report, ...prev]); // Persist to localStorage demoStorage.saveCompanyReport(orgId, report); return; } const ref = doc(db, 'orgs', orgId, 'fullCompanyReports', report.id); await setDoc(ref, report); }; const getFullCompanyReportHistory = async (): Promise => { if (!isFirebaseConfigured) { return fullCompanyReports; } const col = collection(db, 'orgs', orgId, 'fullCompanyReports'); const snap = await getDocs(col); const arr: CompanyReport[] = []; snap.forEach(d => { arr.push({ id: d.id, ...d.data() } as CompanyReport); }); return arr.sort((a, b) => b.createdAt - a.createdAt); }; const generateCompanyReport = async (): Promise => { // Generate comprehensive company report based on current data const totalEmployees = employees.length; const submittedEmployees = Object.keys(submissions).length; const submissionRate = totalEmployees > 0 ? (submittedEmployees / totalEmployees) * 100 : 0; // Department breakdown const deptMap = new Map(); employees.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 })); // Analyze employee reports for insights const reportValues = Object.values(reports) as Report[]; const organizationalStrengths: string[] = []; const organizationalRisks: string[] = []; reportValues.forEach(report => { if (report.strengths) { organizationalStrengths.push(...report.strengths); } if (report.risks) { organizationalRisks.push(...report.risks); } }); // Remove duplicates and take top items const uniqueStrengths = [...new Set(organizationalStrengths)].slice(0, 5); const uniqueRisks = [...new Set(organizationalRisks)].slice(0, 5); const gradingBreakdown = [ { category: 'Execution', value: 70 + Math.random() * 15 }, { category: 'People', value: 70 + Math.random() * 15 }, { category: 'Strategy', value: 65 + Math.random() * 15 }, { category: 'Risk', value: 60 + Math.random() * 15 } ]; const legacy = gradingBreakdown.reduce>((acc, g) => { acc[g.category.toLowerCase()] = Math.round((g.value / 100) * 5 * 10) / 10; return acc; }, {}); const report: CompanyReport = { id: Date.now().toString(), createdAt: Date.now(), overview: { totalEmployees, departmentBreakdown, submissionRate, lastUpdated: Date.now(), averagePerformanceScore: gradingBreakdown.reduce((a, g) => a + g.value, 0) / gradingBreakdown.length / 20, riskLevel: uniqueRisks.length > 4 ? 'High' : uniqueRisks.length > 2 ? 'Medium' : 'Low' }, personnelChanges: { newHires: [], promotions: [], departures: [] }, immediateHiringNeeds: [], operatingPlan: { nextQuarterGoals: ['Increase productivity', 'Implement review system'], keyInitiatives: ['Mentorship program'], resourceNeeds: ['Senior engineer'], riskMitigation: ['Cross-training'] }, forwardOperatingPlan: { // legacy fields quarterlyGoals: ['Increase productivity'], resourceNeeds: ['Senior engineer'], riskMitigation: ['Cross-training'] }, organizationalStrengths: uniqueStrengths.map(s => ({ area: s, description: s })), organizationalRisks: uniqueRisks, organizationalImpactSummary: 'Impact summary placeholder', gradingBreakdown, gradingOverview: legacy, executiveSummary: `Company overview for ${org?.name || 'Organization'} as of ${new Date().toLocaleDateString()}. Total workforce: ${totalEmployees}. Submission rate: ${submissionRate.toFixed(1)}%. Key strengths: ${uniqueStrengths.slice(0, 2).join(', ')}. Risks: ${uniqueRisks.slice(0, 2).join(', ')}.` }; await saveFullCompanyReport(report); return report; }; const generateCompanyWiki = async (orgOverride?: OrgData): Promise => { const orgData = orgOverride || org; try { const res = await fetch(`${API_URL}/generateCompanyWiki`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ org: orgData, submissions }) }); if (!res.ok) throw new Error('Failed to generate company wiki'); const payload = await res.json(); const data: CompanyReport = payload.report || payload; // backward compatibility 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 (isFirebaseConfigured && user) { // Firebase implementation const reportDoc = await getDoc(doc(db, 'organizations', orgId, 'employeeReports', employeeId)); if (reportDoc.exists()) { return { success: true, report: reportDoc.data() }; } return { success: false, error: 'Report not found' }; } else { // Demo mode - call API const response = await fetch(`${API_URL}/api/employee-report/${employeeId}`); const result = await response.json(); return result; } } catch (error) { console.error('Error fetching employee report:', error); return { success: false, error: error.message }; } }; const getEmployeeReports = async () => { try { if (isFirebaseConfigured && user) { // Firebase implementation const reportsSnapshot = await getDocs(collection(db, 'organizations', orgId, 'employeeReports')); const reports = reportsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); return { success: true, reports }; } else { // Demo mode - call API const response = await fetch(`${API_URL}/api/employee-reports`); const result = await response.json(); return result; } } catch (error) { console.error('Error fetching employee reports:', error); return { success: false, error: error.message }; } }; const value = { org, orgId, employees, submissions, reports, upsertOrg, saveReport, inviteEmployee, getReportVersions, saveReportVersion, acceptInvite, saveCompanyReport, getCompanyReportHistory, saveFullCompanyReport, getFullCompanyReportHistory, generateCompanyReport, generateCompanyWiki, seedInitialData, isOwner, issueInviteViaApi: async ({ name, email, role, department }) => { try { const res = await fetch(`${API_URL}/createInvitation`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email, role, department, orgId }) }); if (!res.ok) throw new Error('invite creation failed'); const json = await res.json(); // Optimistically add employee shell (not yet active until consume) setEmployees(prev => prev.find(e => e.id === json.employee.id) ? prev : [...prev, { ...json.employee }]); return json; } catch (e) { console.error('issueInviteViaApi error', e); throw e; } }, getInviteStatus: async (code: string) => { if (!isFirebaseConfigured) { // Demo mode: check localStorage first, then server const invite = demoStorage.getInvite(code); if (invite) { return { used: invite.used, employee: invite.employee }; } } try { const res = await fetch(`${API_URL}/getInvitationStatus?code=${code}`); if (!res.ok) return null; return await res.json(); } catch (e) { console.error('getInviteStatus error', e); return null; } }, consumeInvite: async (code: string) => { if (!isFirebaseConfigured) { // Demo mode: mark invite as used in localStorage and update state const invite = demoStorage.getInvite(code); if (invite && !invite.used) { demoStorage.markInviteUsed(code); // Ensure employee is in the list with proper typing const convertedEmployee: Employee = { id: invite.employee.id, name: invite.employee.name, email: invite.employee.email, initials: invite.employee.name ? invite.employee.name.split(' ').map(n => n[0]).join('').toUpperCase() : invite.employee.email.substring(0, 2).toUpperCase(), department: invite.employee.department, role: invite.employee.role }; setEmployees(prev => prev.find(e => e.id === invite.employee.id) ? prev : [...prev, convertedEmployee]); return { employee: convertedEmployee }; } return null; } try { const res = await fetch(`${API_URL}/consumeInvitation?code=${code}`, { method: 'POST' }); if (!res.ok) return null; const json = await res.json(); // Mark employee as active (could set a flag later) setEmployees(prev => prev.find(e => e.id === json.employee.id) ? prev : [...prev, json.employee]); return json; } catch (e) { console.error('consumeInvite error', e); return null; } }, submitEmployeeAnswers: async (employeeId: string, answers: Record) => { if (!isFirebaseConfigured) { // Demo mode: save to localStorage and call server endpoint try { const submission = { employeeId, orgId, answers, createdAt: Date.now() }; // Save to localStorage for persistence demoStorage.saveSubmission(submission); // Also call Cloud Function for processing with orgId const employee = employees.find(e => e.id === employeeId); const res = await fetch(`${API_URL}/submitEmployeeAnswers`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ employeeId, answers, orgId, employee }) }); if (!res.ok) throw new Error('Failed to submit to server'); // Update local state for UI with proper typing 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; } } // Firebase mode: save to Firestore try { const ref = doc(db, 'orgs', orgId, 'submissions', employeeId); await setDoc(ref, { ...answers, createdAt: Date.now() }, { merge: true }); return true; } catch (e) { console.error('submitEmployeeAnswers error', e); return false; } }, generateEmployeeReport: async (employee: Employee) => { try { const submission = submissions[employee.id]?.answers || submissions[employee.id] || {}; const res = await fetch(`${API_URL}/generateEmployeeReport`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ employee, submission }) }); if (!res.ok) throw new Error('failed to generate'); const json = await res.json(); if (json.report) { setReports(prev => ({ ...prev, [employee.id]: json.report })); return json.report as Report; } } catch (e) { console.error('generateEmployeeReport error', e); } return null; }, getEmployeeReport: async (employeeId: string) => { try { if (isFirebaseConfigured && user) { // Firebase implementation const reportDoc = await getDoc(doc(db, 'organizations', orgId, 'employeeReports', employeeId)); if (reportDoc.exists()) { return { success: true, report: reportDoc.data() }; } return { success: false, error: 'Report not found' }; } else { // Demo mode - call Cloud Function const response = await fetch(`${API_URL}/generateEmployeeReport?employeeId=${employeeId}`); const result = await response.json(); return result; } } catch (error) { console.error('Error fetching employee report:', error); return { success: false, error: error.message }; } }, getEmployeeReports: async () => { try { if (isFirebaseConfigured && user) { // Firebase implementation const reportsSnapshot = await getDocs(collection(db, 'organizations', orgId, 'employeeReports')); const reports = reportsSnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() })); return { success: true, reports }; } else { // Demo mode - call Cloud Function const response = await fetch(`${API_URL}/generateEmployeeReport`); const result = await response.json(); return result; } } catch (error) { console.error('Error fetching employee reports:', error); return { success: false, error: error.message }; } }, }; return ( {children} ); }; export const useOrg = () => { const ctx = useContext(OrgContext); if (!ctx) throw new Error('useOrg must be used within OrgProvider'); return ctx; };