Update
This commit is contained in:
@@ -1,13 +1,11 @@
|
||||
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 { SAMPLE_COMPANY_REPORT, API_URL } from '../constants';
|
||||
import { demoStorage } from '../services/demoStorage';
|
||||
import { apiPost, apiPut } from '../services/api';
|
||||
import { User } from 'firebase/auth';
|
||||
import { EmployeeSubmissionAnswers } from '../employeeQuestions';
|
||||
import { secureApi } from '../services/secureApi';
|
||||
|
||||
interface OrgData {
|
||||
orgId: string;
|
||||
@@ -44,12 +42,13 @@ interface OrgContextType {
|
||||
employees: Employee[];
|
||||
submissions: Record<string, Submission>;
|
||||
reports: Record<string, Report>;
|
||||
loading: boolean;
|
||||
upsertOrg: (data: Partial<OrgData>) => Promise<void>;
|
||||
saveReport: (employeeId: string, report: Report) => 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; emailLink: string; employee: any }>;
|
||||
getInviteStatus: (code: string) => Promise<{ used: boolean; employee: any } | null>;
|
||||
consumeInvite: (code: string) => Promise<{ employee: any } | null>;
|
||||
consumeInvite: (code: string) => Promise<{ employee: any; orgId?: string } | null>;
|
||||
getReportVersions: (employeeId: string) => Promise<Array<{ id: string; createdAt: number; report: Report }>>;
|
||||
saveReportVersion: (employeeId: string, report: Report) => Promise<void>;
|
||||
acceptInvite: (code: string) => Promise<void>;
|
||||
@@ -78,123 +77,157 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
const [reportVersions, setReportVersions] = useState<Record<string, Array<{ id: string; createdAt: number; report: Report }>>>({});
|
||||
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(() => {
|
||||
console.log('OrgContext effect running, orgId:', orgId, 'isFirebaseConfigured:', isFirebaseConfigured);
|
||||
if (!orgId) return; // Wait for orgId to be available
|
||||
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) });
|
||||
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(orgId, user.uid);
|
||||
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(orgId, user.uid, defaultOrg);
|
||||
setOrg({ orgId, ...defaultOrg });
|
||||
}
|
||||
|
||||
// Load employees
|
||||
try {
|
||||
const employeesData = await secureApi.getEmployees(orgId, user.uid);
|
||||
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(orgId, user.uid);
|
||||
setSubmissions(submissionsData);
|
||||
} catch (error) {
|
||||
console.warn('Could not load submissions:', error);
|
||||
setSubmissions({});
|
||||
}
|
||||
|
||||
// Load reports
|
||||
try {
|
||||
const reportsData = await secureApi.getReports(orgId, user.uid);
|
||||
setReports(reportsData as Record<string, Report>);
|
||||
} catch (error) {
|
||||
console.warn('Could not load reports:', error);
|
||||
setReports({});
|
||||
}
|
||||
|
||||
// Load company reports
|
||||
try {
|
||||
const companyReportsData = await secureApi.getCompanyReports(orgId, user.uid);
|
||||
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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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<string, Submission> = {};
|
||||
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<string, Report> = {};
|
||||
snap.forEach((d) => (map[d.id] = { employeeId: d.id, ...(d.data() as any) } as Report));
|
||||
setReports(map);
|
||||
});
|
||||
|
||||
return () => { unsubEmp(); unsubSub(); unsubRep(); };
|
||||
}, [orgId]);
|
||||
loadOrgData();
|
||||
}, [orgId, user?.uid]);
|
||||
|
||||
const upsertOrg = async (data: Partial<OrgData>) => {
|
||||
const orgRef = doc(db, 'orgs', orgId);
|
||||
await setDoc(orgRef, data, { merge: true });
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
// Update local state
|
||||
const updatedOrg = { ...(org || { orgId, name: 'Your Company' }), ...data } as OrgData;
|
||||
setOrg(updatedOrg);
|
||||
try {
|
||||
await secureApi.updateOrgData(orgId, user.uid, data);
|
||||
|
||||
// If onboarding was completed, notify other contexts
|
||||
if (data.onboardingCompleted) {
|
||||
console.log('OrgContext (Firebase): Onboarding completed, dispatching update event', {
|
||||
orgId: updatedOrg.orgId,
|
||||
onboardingCompleted: true
|
||||
});
|
||||
// Update local state
|
||||
const updatedOrg = { ...(org || { orgId, name: 'Your Company' }), ...data } as OrgData;
|
||||
setOrg(updatedOrg);
|
||||
|
||||
window.dispatchEvent(new CustomEvent('organizationUpdated', {
|
||||
detail: { orgId: updatedOrg.orgId, onboardingCompleted: true }
|
||||
}));
|
||||
// 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: Report) => {
|
||||
const ref = doc(db, 'orgs', orgId, 'reports', employeeId);
|
||||
await setDoc(ref, report, { merge: true });
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
try {
|
||||
const savedReport = await secureApi.saveReport(orgId, user.uid, 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 }: { name: string; email: string }) => {
|
||||
console.log('inviteEmployee called:', { name, email, orgId });
|
||||
|
||||
try {
|
||||
// Always use Cloud Functions for invites to ensure multi-tenant compliance
|
||||
const res = await apiPost('/createInvitation', {
|
||||
name,
|
||||
email
|
||||
}, orgId);
|
||||
// Use secure API for invites
|
||||
const data = await secureApi.createInvitation(orgId, name, email);
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
console.error('Invite creation failed:', errorData);
|
||||
throw new Error(errorData.error || `Failed to create invite: ${res.status}`);
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
const { code, employee, inviteLink } = data;
|
||||
|
||||
console.log('Invite created successfully:', { code, employee: employee.name, inviteLink });
|
||||
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: employee.id,
|
||||
name: employee.name,
|
||||
email: employee.email,
|
||||
initials: employee.name ? employee.name.split(' ').map(n => n[0]).join('').toUpperCase() : employee.email.substring(0, 2).toUpperCase(),
|
||||
department: employee.department,
|
||||
role: employee.role,
|
||||
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
|
||||
};
|
||||
|
||||
if (!isFirebaseConfigured) {
|
||||
const employeeWithOrg = { ...newEmployee, orgId };
|
||||
setEmployees(prev => {
|
||||
if (prev.find(e => e.id === employee.id)) return prev;
|
||||
return [...prev, newEmployee];
|
||||
});
|
||||
demoStorage.saveEmployee(employeeWithOrg);
|
||||
} else {
|
||||
// For Firebase, add to local state for immediate UI update
|
||||
setEmployees(prev => {
|
||||
if (prev.find(e => e.id === employee.id)) return prev;
|
||||
return [...prev, newEmployee];
|
||||
});
|
||||
}
|
||||
// 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: employee.id, inviteLink };
|
||||
return { employeeId: data.employee.id, inviteLink: data.inviteLink };
|
||||
} catch (error) {
|
||||
console.error('inviteEmployee error:', error);
|
||||
throw error;
|
||||
@@ -202,84 +235,76 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
};
|
||||
|
||||
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);
|
||||
// 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: Report) => {
|
||||
// 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 };
|
||||
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 });
|
||||
setReportVersions(prev => ({ ...prev, [employeeId]: [version, ...(prev[employeeId] || [])] }));
|
||||
};
|
||||
|
||||
const acceptInvite = async (code: string) => {
|
||||
if (!code) return;
|
||||
if (!code || !user?.uid) return;
|
||||
|
||||
if (!isFirebaseConfigured) {
|
||||
// Demo mode: mark invite as used
|
||||
demoStorage.markInviteUsed(code);
|
||||
return;
|
||||
try {
|
||||
await secureApi.consumeInvitation(code, user.uid);
|
||||
} catch (error) {
|
||||
console.error('Failed to accept invite:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
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;
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
try {
|
||||
const report = {
|
||||
id: Date.now().toString(),
|
||||
createdAt: Date.now(),
|
||||
summary
|
||||
};
|
||||
|
||||
await secureApi.saveCompanyReport(orgId, 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 ref = doc(db, 'orgs', orgId, 'companyReports', id);
|
||||
await setDoc(ref, { createdAt, summary });
|
||||
};
|
||||
|
||||
const getCompanyReportHistory = async () => {
|
||||
if (!isFirebaseConfigured) {
|
||||
return companyReports;
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
try {
|
||||
const reports = await secureApi.getCompanyReports(orgId, user.uid);
|
||||
return reports.map(report => ({
|
||||
id: report.id,
|
||||
createdAt: report.createdAt || 0,
|
||||
summary: report.summary || ''
|
||||
})).sort((a, b) => b.createdAt - a.createdAt);
|
||||
} catch (error) {
|
||||
console.error('Failed to get company report history:', error);
|
||||
return [];
|
||||
}
|
||||
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 completely clean slate - no sample data
|
||||
setEmployees([]);
|
||||
setSubmissions({});
|
||||
setReports({});
|
||||
setFullCompanyReports([]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Start with clean slate - let users invite their own employees and generate real data
|
||||
setEmployees([]);
|
||||
setSubmissions({});
|
||||
setReports({});
|
||||
setFullCompanyReports([]);
|
||||
};
|
||||
|
||||
const saveFullCompanyReport = async (report: CompanyReport) => {
|
||||
@@ -288,37 +313,42 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
throw new Error('Organization ID is required to save company report');
|
||||
}
|
||||
|
||||
if (!isFirebaseConfigured || !db) {
|
||||
// Fallback to local storage in demo mode
|
||||
setFullCompanyReports(prev => [report, ...prev]);
|
||||
demoStorage.saveCompanyReport(orgId, report);
|
||||
return;
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
// Use direct Firestore operations - much more efficient
|
||||
const ref = doc(db, 'orgs', orgId, 'fullCompanyReports', report.id);
|
||||
await setDoc(ref, report);
|
||||
try {
|
||||
await secureApi.saveCompanyReport(orgId, report);
|
||||
|
||||
// Update local state after successful save
|
||||
setFullCompanyReports(prev => [report, ...prev]);
|
||||
// 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 (!isFirebaseConfigured) {
|
||||
return fullCompanyReports;
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
try {
|
||||
const reports = await secureApi.getCompanyReports(orgId, user.uid);
|
||||
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 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<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);
|
||||
@@ -343,41 +373,32 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
const departmentBreakdown = Array.from(deptMap.entries()).map(([department, count]) => ({ department, count }));
|
||||
|
||||
try {
|
||||
// Use AI only for analysis and insights that require reasoning
|
||||
const res = await apiPost('/generateCompanyWiki', {
|
||||
org: org,
|
||||
submissions: employeeSubmissions, // Only employee submissions, not owner data
|
||||
// Use secure API for AI generation
|
||||
const data = await secureApi.generateCompanyWiki({
|
||||
...org,
|
||||
metrics: {
|
||||
totalEmployees,
|
||||
submissionRate,
|
||||
departmentBreakdown
|
||||
}
|
||||
}, orgId);
|
||||
}, Object.values(employeeSubmissions));
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
console.error('Company report generation failed:', errorData);
|
||||
throw new Error(errorData.error || 'Failed to generate company report');
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
console.log('Company insights generated via AI successfully');
|
||||
console.log('AI response data:', data);
|
||||
|
||||
// Combine concrete metrics with AI insights
|
||||
const report: CompanyReport = {
|
||||
id: Date.now().toString(),
|
||||
createdAt: Date.now(),
|
||||
// Use AI-generated insights for subjective analysis
|
||||
...data.report,
|
||||
...(data as any),
|
||||
// Override with our concrete metrics
|
||||
overview: {
|
||||
totalEmployees,
|
||||
departmentBreakdown,
|
||||
submissionRate,
|
||||
lastUpdated: Date.now(),
|
||||
averagePerformanceScore: data.report?.overview?.averagePerformanceScore || 0,
|
||||
riskLevel: data.report?.overview?.riskLevel || 'Unknown'
|
||||
averagePerformanceScore: (data as any)?.overview?.averagePerformanceScore || 0,
|
||||
riskLevel: (data as any)?.overview?.riskLevel || 'Unknown'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -392,29 +413,21 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
|
||||
const generateCompanyWiki = async (orgOverride?: OrgData): Promise<CompanyReport> => {
|
||||
const orgData = orgOverride || org;
|
||||
console.log('generateCompanyWiki called with:', { orgData, orgId, submissionsCount: Object.keys(submissions || {}).length, isFirebaseConfigured });
|
||||
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');
|
||||
}
|
||||
|
||||
// ALWAYS use API call for wiki generation, with local fallback
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
// Use secure API for wiki generation
|
||||
try {
|
||||
console.log('Making API call to generateCompanyWiki...');
|
||||
const res = await apiPost('/generateCompanyWiki', {
|
||||
org: orgData,
|
||||
submissions: submissions || []
|
||||
}, orgId);
|
||||
const payload = await secureApi.generateCompanyWiki(orgData, Object.values(submissions || {}));
|
||||
|
||||
console.log('API response status:', res.status);
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
console.error('API error response:', errorData);
|
||||
throw new Error(errorData.error || 'Failed to generate company wiki');
|
||||
}
|
||||
|
||||
const payload = await res.json();
|
||||
console.log('API success response:', payload);
|
||||
|
||||
// Ensure the report has all required fields to prevent undefined errors
|
||||
@@ -440,7 +453,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
gradingOverview: {},
|
||||
executiveSummary: 'Company report generated successfully.',
|
||||
// Override with API data if available
|
||||
...(payload.report || payload)
|
||||
...(payload as any || {})
|
||||
};
|
||||
|
||||
await saveFullCompanyReport(data);
|
||||
@@ -459,41 +472,37 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
|
||||
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;
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
// Use secure API for all employee report operations
|
||||
const report = await secureApi.getReports(orgId, user.uid);
|
||||
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.message };
|
||||
return { success: false, error: (error as 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;
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
// Use secure API for all employee report operations
|
||||
const reportsData = await secureApi.getReports(orgId, user.uid);
|
||||
const reports = Object.values(reportsData);
|
||||
return { success: true, reports };
|
||||
} catch (error) {
|
||||
console.error('Error fetching employee reports:', error);
|
||||
return { success: false, error: error.message };
|
||||
return { success: false, error: (error as Error).message };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -503,6 +512,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
employees,
|
||||
submissions,
|
||||
reports,
|
||||
loading,
|
||||
upsertOrg,
|
||||
saveReport,
|
||||
inviteEmployee,
|
||||
@@ -519,125 +529,68 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
isOwner,
|
||||
issueInviteViaApi: async ({ name, email, role, department }) => {
|
||||
try {
|
||||
const res = await apiPost('/createInvitation', {
|
||||
name,
|
||||
email,
|
||||
role,
|
||||
department
|
||||
}, orgId);
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
throw new Error(errorData.error || 'Invite creation failed');
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
const data = await secureApi.createInvitation(orgId, name, email, role, department);
|
||||
|
||||
// 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;
|
||||
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) => {
|
||||
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();
|
||||
return await secureApi.getInvitationStatus(code);
|
||||
} 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 };
|
||||
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;
|
||||
}
|
||||
|
||||
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<string, string>) => {
|
||||
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 authentication and orgId
|
||||
const employee = employees.find(e => e.id === employeeId);
|
||||
const res = await apiPost('/submitEmployeeAnswers', {
|
||||
employeeId,
|
||||
answers,
|
||||
employee
|
||||
}, orgId);
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
throw new Error(errorData.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 });
|
||||
if (!user?.uid) {
|
||||
throw new Error('User authentication required');
|
||||
}
|
||||
|
||||
// Use secure API for submission
|
||||
await secureApi.submitEmployeeAnswers(orgId, 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);
|
||||
@@ -648,6 +601,10 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
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) {
|
||||
@@ -659,7 +616,7 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
if (submission.answers) {
|
||||
if (Array.isArray(submission.answers)) {
|
||||
// If answers is an array of {question, answer} objects
|
||||
submissionAnswers = submission.answers.reduce((acc, item) => {
|
||||
submissionAnswers = submission.answers.reduce((acc, item: any) => {
|
||||
acc[item.question] = item.answer;
|
||||
return acc;
|
||||
}, {} as Record<string, string>);
|
||||
@@ -686,29 +643,13 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
console.warn('Could not fetch company report for context:', error);
|
||||
}
|
||||
|
||||
const res = await apiPost('/generateEmployeeReport', {
|
||||
employee,
|
||||
submission: submissionAnswers,
|
||||
companyWiki
|
||||
}, orgId);
|
||||
const data = await secureApi.generateEmployeeReport(employee, submissionAnswers, companyWiki);
|
||||
|
||||
if (!res.ok) {
|
||||
const errorData = await res.json();
|
||||
console.error('API error response:', errorData);
|
||||
throw new Error(errorData.error || 'Failed to generate employee report');
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
if (json.report) {
|
||||
if ((data as any).report) {
|
||||
console.log('Employee report generated successfully');
|
||||
setReports(prev => ({ ...prev, [employee.id]: json.report }));
|
||||
|
||||
// Also save to persistent storage in demo mode
|
||||
if (!isFirebaseConfigured) {
|
||||
demoStorage.saveEmployeeReport(orgId, employee.id, json.report);
|
||||
}
|
||||
|
||||
return json.report as Report;
|
||||
const report = (data as any).report as Report;
|
||||
setReports(prev => ({ ...prev, [employee.id]: report }));
|
||||
return report;
|
||||
} else {
|
||||
throw new Error('No report data received from API');
|
||||
}
|
||||
@@ -717,45 +658,8 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s
|
||||
throw e; // Re-throw to allow caller to handle
|
||||
}
|
||||
},
|
||||
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 };
|
||||
}
|
||||
},
|
||||
getEmployeeReport,
|
||||
getEmployeeReports,
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user