650 lines
26 KiB
TypeScript
650 lines
26 KiB
TypeScript
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<string, any>;
|
|
companyLogo?: string;
|
|
updatedAt?: number;
|
|
onboardingCompleted?: boolean;
|
|
}
|
|
|
|
interface OrgContextType {
|
|
org: OrgData | null;
|
|
user?: User;
|
|
orgId: string;
|
|
employees: Employee[];
|
|
submissions: Record<string, Submission>;
|
|
reports: Record<string, EmployeeReport>;
|
|
loading: boolean;
|
|
upsertOrg: (data: Partial<OrgData>) => Promise<void>;
|
|
saveReport: (employeeId: string, report: EmployeeReport) => Promise<void>;
|
|
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<Array<{ id: string; createdAt: number; report: EmployeeReport }>>;
|
|
saveReportVersion: (employeeId: string, report: EmployeeReport) => Promise<void>;
|
|
acceptInvite: (code: string) => Promise<void>;
|
|
saveCompanyReport: (summary: string) => Promise<void>;
|
|
getCompanyReportHistory: () => Promise<Array<{ id: string; createdAt: number; summary: string }>>;
|
|
saveFullCompanyReport: (report: CompanyReport) => Promise<void>;
|
|
getFullCompanyReportHistory: () => Promise<CompanyReport[]>;
|
|
generateCompanyReport: () => Promise<CompanyReport>;
|
|
generateCompanyWiki: (orgOverride?: OrgData) => Promise<CompanyReport>;
|
|
seedInitialData: () => Promise<void>;
|
|
isOwner: (employeeId?: string) => boolean;
|
|
submitEmployeeAnswers: (employeeId: string, answers: Record<string, string>) => Promise<any>;
|
|
generateEmployeeReport: (employee: Employee) => Promise<EmployeeReport | null>;
|
|
getEmployeeReport: (employeeId: string) => Promise<{ success: boolean; report?: EmployeeReport; error?: string }>;
|
|
getEmployeeReports: () => Promise<{ success: boolean; reports?: EmployeeReport[]; error?: string }>;
|
|
}
|
|
|
|
const OrgContext = createContext<OrgContextType | undefined>(undefined);
|
|
|
|
export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: string }> = ({ children, selectedOrgId }) => {
|
|
const { user } = useAuth();
|
|
const [org, setOrg] = useState<OrgData | null>(null);
|
|
const [employees, setEmployees] = useState<Employee[]>([]);
|
|
const [submissions, setSubmissions] = useState<Record<string, Submission>>({});
|
|
const [reports, setReports] = useState<Record<string, EmployeeReport>>({});
|
|
const [reportVersions, setReportVersions] = useState<Record<string, Array<{ id: string; createdAt: number; report: EmployeeReport }>>>({});
|
|
const [companyReports, setCompanyReports] = useState<Array<{ id: string; createdAt: number; summary: string }>>([]);
|
|
const [fullCompanyReports, setFullCompanyReports] = useState<CompanyReport[]>([]);
|
|
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<string, EmployeeReport>);
|
|
} 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<OrgData>) => {
|
|
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<CompanyReport[]> => {
|
|
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<CompanyReport> => {
|
|
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<string, number>();
|
|
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<CompanyReport> => {
|
|
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<string, string>) => {
|
|
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<string, string> = {};
|
|
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<string, string>);
|
|
} else {
|
|
// If answers is already a key-value object
|
|
submissionAnswers = submission.answers as Record<string, string>;
|
|
}
|
|
}
|
|
|
|
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 (
|
|
<OrgContext.Provider value={value}>
|
|
{children}
|
|
</OrgContext.Provider>
|
|
);
|
|
};
|
|
|
|
export const useOrg = () => {
|
|
const ctx = useContext(OrgContext);
|
|
if (!ctx) throw new Error('useOrg must be used within OrgProvider');
|
|
return ctx;
|
|
};
|