From 0d7b8d104b8caeaf89ec6df27c5ec7f3af7c5476 Mon Sep 17 00:00:00 2001 From: Ra Date: Mon, 25 Aug 2025 14:42:33 -0700 Subject: [PATCH] fix: simplify EmployeeQuestionnaireNew to use invite-only flow - Remove all authentication and org context dependencies - Simplify component to work only with invite codes from URL - Remove complex user/employee matching logic - Keep exact Figma UI components and styling - Use only submitViaInvite function for API submissions - Employees never need to log in, only use invite link --- functions/index.js | 49 +----- index.css | 119 +++++++++++++ src/App.tsx | 2 +- .../charts/RadarPerformanceChart.tsx | 2 +- src/constants.ts | 96 +++++++---- src/contexts/OrgContext.tsx | 9 - src/pages/EmployeeQuestionnaireNew.tsx | 132 +++----------- src/pages/Onboarding.tsx | 2 +- src/pages/Reports.tsx | 161 ++++++++++-------- src/pages/Submissions.tsx | 2 +- src/server/index.js | 29 +--- src/services/secureApi.ts | 17 -- src/types.ts | 48 +----- 13 files changed, 311 insertions(+), 357 deletions(-) diff --git a/functions/index.js b/functions/index.js index 3bda7f5..230e1e8 100644 --- a/functions/index.js +++ b/functions/index.js @@ -110,20 +110,6 @@ const RESPONSE_FORMAT = { }, required: ["summary", "metrics"] }, - keyPersonnelChanges: { - type: "array", - items: { - type: "object", - additionalProperties: false, - properties: { - person: { type: "string" }, - change: { type: "string" }, // e.g. "Promoted to VP Eng" - impact: { type: "string" }, - effectiveDate: { type: "string" } - }, - required: ["person", "change", "impact", "effectiveDate"] - } - }, immediateHiringNeeds: { type: "array", items: { @@ -181,21 +167,8 @@ const RESPONSE_FORMAT = { required: ["culture", "teamDynamics", "blockers"] }, strengths: { type: "array", items: { type: "string" } }, - gradingOverview: { - type: "array", - items: { - type: "object", - additionalProperties: false, - properties: { - department: { type: "string" }, - grade: { enum: ["A", "B", "C", "D", "F"] }, - notes: { type: "string" } - }, - required: ["department", "grade", "notes"] - } - } }, - required: ["companyPerformance", "keyPersonnelChanges", "immediateHiringNeeds", "forwardOperatingPlan", "organizationalInsights", "strengths", "gradingOverview"] + required: ["companyPerformance", "immediateHiringNeeds", "forwardOperatingPlan", "organizationalInsights", "strengths"] } } @@ -891,14 +864,6 @@ exports.generateCompanyWiki = onRequest({ cors: true }, async (req, res) => { retention: 88, }, }, - keyPersonnelChanges: [ - { - type: "promotion", - employee: "John Doe", - details: "Promoted to Senior Developer", - impact: "positive", - }, - ], immediateHiringNeeds: [ { role: "Frontend Developer", @@ -921,17 +886,7 @@ exports.generateCompanyWiki = onRequest({ cors: true }, async (req, res) => { "Strong technical expertise", "Collaborative team culture", "Innovative problem-solving approach", - ], - gradingOverview: { - averagePerformance: 82, - topPerformers: 3, - needsImprovement: 1, - departmentBreakdown: { - engineering: 85, - design: 80, - product: 78, - }, - }, + ] }; wiki = { diff --git a/index.css b/index.css index 68952e4..df59a87 100644 --- a/index.css +++ b/index.css @@ -14,6 +14,125 @@ } } +@keyframes blinkLightGreen { + + 0%, + 33% { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301), + 0px 0px 0px 2px #a5ffc075; + + } + + 33%, + 66% { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301); + } +} + +@keyframes blinkLightYellow { + + 0%, + 33% { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301), + 0px 0px 0px 2px #f7f3c275; + } + + 33%, + 66% { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301); + } +} + +@keyframes blinkLightBlue { + + 0%, + 33% { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301), + 0px 0px 0px 2px #a5d8ff75; + } + + 33%, + 66% { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301); + } +} + +@keyframes blinkLightRed { + + 0%, + 33% { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301), + 0px 0px 0px 2px #f63d6875; + } + + 33%, + 66% { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301); + } +} + +.blinkLightBlue, +.blinkLightGreen, +.blinkLightRed, +.blinkLightYellow { + animation-duration : 5s; + animation-timing-function: ease-in-out; + animation-iteration-count: infinite; + border : solid 1px transparent; +} + +.blinkLightBlue { + animation-name: blinkLightBlue; + box-shadow : inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301), + 0px 0px 0px 2px #a5d8ff75; + border: solid 1px #54c2e456; +} + +.blinkLightGreen { + animation-name: blinkLightGreen; + box-shadow : inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301), + 0px 0px 0px 2px #a5ffc075; + border: solid 1px rgba(187, 248, 185, 0.31); +} + +.blinkLightRed { + animation-name: blinkLightRed; + box-shadow : inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301), + 0px 0px 0px 2px #f63d6875; + border: solid 1px #e4547656; +} + +.blinkLightYellow { + animation-name: blinkLightYellow; + box-shadow : inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301), + 0px 0px 0px 2px #f7f3c275; + border: solid 1px #e4e25456; +} + +.blinkLightActive { + animation: none !important; +} + +.blinkLightEmpty { + box-shadow: inset 1px 0px 3px 0px rgba(255, 255, 255, 0.329), + inset -1px 0px 3px 0px rgba(78, 78, 78, 0.301); + animation : none !important; + border : none !important; + background-color: var(--Neutrals-NeutralSlate400) +} + :root { diff --git a/src/App.tsx b/src/App.tsx index 0e22328..a0fe118 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -109,7 +109,7 @@ function App() { } /> } /> - } /> + {/* } /> */} {/* Employee questionnaire - no auth needed, uses invite code */} } /> diff --git a/src/components/charts/RadarPerformanceChart.tsx b/src/components/charts/RadarPerformanceChart.tsx index 6c65e3f..d060bd3 100644 --- a/src/components/charts/RadarPerformanceChart.tsx +++ b/src/components/charts/RadarPerformanceChart.tsx @@ -24,7 +24,7 @@ const RadarPerformanceChart: React.FC = ({ title, data, height = 320, col - + diff --git a/src/constants.ts b/src/constants.ts index 27bf9cb..ecb5270 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -176,20 +176,20 @@ export const SAMPLE_COMPANY_REPORT: CompanyReport = { { employeeName: 'Jamie Park', grade: 'B', - reliability: 6, - roleFit: 7, + reliability: 10, + roleFit: 1, scalability: 6, - output: 6, + output: 2, initiative: 7 }, { employeeName: 'Taylor Smith', grade: 'A', reliability: 9, - roleFit: 9, - scalability: 8, - output: 9, - initiative: 8 + roleFit: 7, + scalability: 3, + output: 1, + initiative: 9 }, { employeeName: 'Jordan Lee', @@ -240,7 +240,6 @@ export const SAMPLE_COMPANY_REPORT: CompanyReport = { ] } ], - operatingPlan: { nextQuarterGoals: [], keyInitiatives: [], resourceNeeds: [], riskMitigation: [] }, personnelChanges: { newHires: [ { name: 'Emma Wilson', department: 'Creative', role: 'Graphic Designer', impact: 'Will strengthen visual design capabilities' } @@ -250,7 +249,6 @@ export const SAMPLE_COMPANY_REPORT: CompanyReport = { ], departures: [] }, - keyPersonnelChanges: [], immediateHiringNeeds: [ { department: 'Tech', role: 'Frontend Developer', priority: 'High', reasoning: 'Need to expand web development capacity for growing client demands' }, { department: 'Social Media', role: 'Content Creator', priority: 'Medium', reasoning: 'Additional support needed for video content production' } @@ -278,26 +276,64 @@ export const SAMPLE_COMPANY_REPORT: CompanyReport = { 'Innovative approach to campaign development', 'Reliable project delivery within deadlines' ], - organizationalImpactSummary: { - missionCritical: [ - { employeeName: 'Sarah Johnson', impact: 'Lead Campaign Manager', description: 'Manages top-tier client relationships and drives 40% of company revenue' }, - { employeeName: 'Casey Johnson', impact: 'Tech Infrastructure', description: 'Maintains all technical systems and client delivery platforms' } - ], - highlyValuable: [ - { employeeName: 'Alex Rivera', impact: 'Social Media Strategy', description: 'Develops social strategies that increase client engagement by 150%' }, - { employeeName: 'Taylor Smith', impact: 'Creative Director', description: 'Ensures brand consistency and award-winning creative output' } - ], - coreSupport: [ - { employeeName: 'Mike Chen', impact: 'Campaign Support', description: 'Reliable execution of campaign tasks and client communication' }, - { employeeName: 'Jordan Lee', impact: 'Design Support', description: 'Produces high-quality visual assets and maintains design standards' } - ], - lowCriticality: [ - { employeeName: 'Jamie Park', impact: 'Content Assistant', description: 'Supports content creation with room for skill development' }, - { employeeName: 'Morgan Davis', impact: 'Administrative Support', description: 'Handles routine operations and documentation' } - ] - }, - organizationalStrengths: [], - organizationalRisks: [], - gradingOverview: {}, + organizationalImpactSummary: [ + { + category: 'Mission Critical', + employees: [ + { + employeeName: 'Sarah Johnson', + impact: 'Lead Campaign Manager', + description: 'Manages top-tier client relationships and drives 40% of company revenue', + suggestedPay: '$120,000' + }, + { + employeeName: 'Casey Johnson', + impact: 'Tech Infrastructure', + description: 'Maintains all technical systems and client delivery platforms', + suggestedPay: '$110,000' + } + ] + }, + { + category: 'Highly Valuable', + employees: [ + { + employeeName: 'Alex Rivera', + impact: 'Social Media Strategy', + description: 'Develops social strategies that increase client engagement by 150%', + suggestedPay: '$95,000' + }, + { + employeeName: 'Taylor Smith', + impact: 'Creative Director', + description: 'Ensures brand consistency and award-winning creative output', + suggestedPay: '$92,000' + } + ] + }, + { + category: 'Core Support', + employees: [ + { + employeeName: 'Mike Chen', + impact: 'Campaign Support', + description: 'Reliable execution of campaign tasks and client communication', + suggestedPay: '$70,000' + }, + { + employeeName: 'Jordan Lee', + impact: 'Design Support', + description: 'Produces high-quality visual assets and maintains design standards', + suggestedPay: '$68,000' + } + ] + }, + { + category: 'Low Criticality', + employees: [ + + ] + } + ], executiveSummary: `Welcome to Auditly! Generate your first AI-powered company report by inviting employees and completing the onboarding process.` }; \ No newline at end of file diff --git a/src/contexts/OrgContext.tsx b/src/contexts/OrgContext.tsx index 23f6bb6..defb44a 100644 --- a/src/contexts/OrgContext.tsx +++ b/src/contexts/OrgContext.tsx @@ -424,14 +424,9 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s riskLevel: 'Unknown' }, gradingBreakdown: [], - operatingPlan: { nextQuarterGoals: [], keyInitiatives: [], resourceNeeds: [], riskMitigation: [] }, personnelChanges: { newHires: [], promotions: [], departures: [] }, - keyPersonnelChanges: [], immediateHiringNeeds: [], forwardOperatingPlan: { quarterlyGoals: [], resourceNeeds: [], riskMitigation: [] }, - organizationalStrengths: [], - organizationalRisks: [], - gradingOverview: {}, executiveSummary: 'Company report generated successfully.', // Override with API data if available ...(payload as any || {}) @@ -556,10 +551,6 @@ export const OrgProvider: React.FC<{ children: React.ReactNode; selectedOrgId: s }, submitEmployeeAnswers: async (employeeId: string, answers: Record) => { try { - if (!user?.uid) { - throw new Error('User authentication required'); - } - // Use secure API for submission await secureApi.submitEmployeeAnswers(employeeId, answers); diff --git a/src/pages/EmployeeQuestionnaireNew.tsx b/src/pages/EmployeeQuestionnaireNew.tsx index 6968bd4..156e7f9 100644 --- a/src/pages/EmployeeQuestionnaireNew.tsx +++ b/src/pages/EmployeeQuestionnaireNew.tsx @@ -1,8 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { useNavigate, useLocation, useParams } from 'react-router-dom'; -import { useAuth } from '../contexts/AuthContext'; -import { useOrg } from '../contexts/OrgContext'; -import { EMPLOYEE_QUESTIONS, EmployeeSubmissionAnswers } from '../employeeQuestions'; +import { useParams } from 'react-router-dom'; +import { EmployeeSubmissionAnswers } from '../employeeQuestions'; import { API_URL } from '../constants'; import { WelcomeScreen, @@ -15,39 +13,21 @@ import { } from '../components/figma/FigmaEmployeeForms'; /** - * Enhanced Employee Questionnaire with Exact Figma Design Implementation + * Employee Questionnaire with Invite-Only Flow * * Features: - * - Exact Figma design system styling * - Invite-based flow (no authentication required) - * - Company owner can invite employees with metadata - * - LLM processing via cloud functions - * - Report generation with company context - * - Firestore storage for reports + * - Company owner invites employees with metadata + * - Employee uses invite code to access questionnaire + * - LLM processing via cloud functions with company context + * - Report generation and Firestore storage */ const EmployeeQuestionnaire: React.FC = () => { - const navigate = useNavigate(); - const location = useLocation(); const params = useParams(); - const { user } = useAuth(); - - // Check if this is an invite-based flow (no auth/org needed) const inviteCode = params.inviteCode; - const isInviteFlow = !!inviteCode; - - // Only use org context for authenticated flows - let submitEmployeeAnswers, generateEmployeeReport, employees; - if (!isInviteFlow) { - const orgContext = useOrg(); - ({ submitEmployeeAnswers, generateEmployeeReport, employees } = orgContext); - } else { - // For invite flows, we don't need these functions from org context - submitEmployeeAnswers = null; - generateEmployeeReport = null; - employees = []; - } + // Component state const [currentStep, setCurrentStep] = useState(1); const [formData, setFormData] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); @@ -55,10 +35,12 @@ const EmployeeQuestionnaire: React.FC = () => { const [inviteEmployee, setInviteEmployee] = useState(null); const [isLoadingInvite, setIsLoadingInvite] = useState(false); - // Load invite details if this is an invite flow + // Load invite details on component mount useEffect(() => { if (inviteCode) { loadInviteDetails(inviteCode); + } else { + setError('No invite code provided'); } }, [inviteCode]); @@ -94,39 +76,8 @@ const EmployeeQuestionnaire: React.FC = () => { } }; - // Get employee info from multiple sources - const invitedEmployee = location.state?.invitedEmployee; - - // Determine current employee - for invite flow, use invite employee data - let currentEmployee; - if (isInviteFlow) { - currentEmployee = inviteEmployee; - } else { - // Original auth-based logic - currentEmployee = invitedEmployee || employees.find(emp => emp.email === user?.email); - - if (!currentEmployee && user?.email) { - // Try case-insensitive email matching - currentEmployee = employees.find(emp => - emp.email?.toLowerCase() === user.email?.toLowerCase() - ); - - if (!currentEmployee && invitedEmployee) { - currentEmployee = employees.find(emp => - emp.name === invitedEmployee.name || emp.id === invitedEmployee.id - ); - } - } - - // Demo mode fallbacks - if (!currentEmployee && user?.email === 'demo@auditly.local' && employees.length > 0) { - currentEmployee = employees[employees.length - 1]; - } - - if (!currentEmployee && employees.length === 1) { - currentEmployee = employees[0]; - } - } + // Get employee info from invite data + const currentEmployee = inviteEmployee; const submitViaInvite = async (answers: EmployeeSubmissionAnswers, inviteCode: string) => { try { @@ -176,7 +127,7 @@ const EmployeeQuestionnaire: React.FC = () => { setError(''); try { - // Convert form data to EMPLOYEE_QUESTIONS format for backend + // Convert form data to answers format for backend const answers: EmployeeSubmissionAnswers = {}; // Map form data to question IDs @@ -191,55 +142,14 @@ const EmployeeQuestionnaire: React.FC = () => { } }); - // Submit answers - different logic for invite vs auth flow - let result; - if (isInviteFlow) { - // Direct API submission for invite flow (no auth needed) - result = await submitViaInvite(answers, inviteCode); - } else { - // Use org context for authenticated flow - if (!currentEmployee) { - // Enhanced fallback logic for authenticated users - if (employees.length > 0) { - let fallbackEmployee = employees.find(emp => - emp.email?.toLowerCase().includes(user?.email?.toLowerCase().split('@')[0] || '') - ); - - if (!fallbackEmployee) { - const userDomain = user?.email?.split('@')[1]; - fallbackEmployee = employees.find(emp => - emp.email?.split('@')[1] === userDomain - ) || employees[employees.length - 1]; - } - - const success = await submitEmployeeAnswers(fallbackEmployee.id, answers); - if (success) { - try { - const report = await generateEmployeeReport(fallbackEmployee); - console.log('Report generated successfully:', report); - } catch (reportError) { - console.error('Failed to generate report:', reportError); - } - - // Navigate to completion - setCurrentStep(999); // Thank you page - return; - } - } - - setError(`We couldn't match your account (${user?.email}) with an employee record. Please contact your administrator.`); - setIsSubmitting(false); - return; - } - - result = await submitEmployeeAnswers(currentEmployee.id, answers); - } + // Submit answers via invite flow + const result = await submitViaInvite(answers, inviteCode!); if (result.success) { // Show thank you page setCurrentStep(999); } else { - setError(result.message || 'Failed to submit questionnaire'); + setError(result.error || 'Failed to submit questionnaire'); } } catch (error) { console.error('Submission error:', error); @@ -261,8 +171,8 @@ const EmployeeQuestionnaire: React.FC = () => { setCurrentStep(currentStep - 1); }; - // Early return for invite flow loading state - if (isInviteFlow && isLoadingInvite) { + // Early return for loading state + if (isLoadingInvite) { return (
@@ -276,8 +186,8 @@ const EmployeeQuestionnaire: React.FC = () => { ); } - // Early return for invite flow error state - if (isInviteFlow && error && currentStep === 1) { + // Early return for error state + if (error && currentStep === 1) { return (
diff --git a/src/pages/Onboarding.tsx b/src/pages/Onboarding.tsx index 90f3d54..69d7bd0 100644 --- a/src/pages/Onboarding.tsx +++ b/src/pages/Onboarding.tsx @@ -3,7 +3,7 @@ import { useNavigate } from 'react-router-dom'; import { useAuth } from '../contexts/AuthContext'; import { useOrg } from '../contexts/OrgContext'; import { onboardingSteps, OnboardingData, initializeOnboardingData } from '../data/onboardingSteps'; -import { secureApiPOST, secureApi } from '../services/secureApi'; +import { secureApi } from '../services/secureApi'; import { FigmaOnboardingIntro, FigmaOnboardingQuestion, diff --git a/src/pages/Reports.tsx b/src/pages/Reports.tsx index 23d3b06..5fb7dc1 100644 --- a/src/pages/Reports.tsx +++ b/src/pages/Reports.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import { useOrg } from '../contexts/OrgContext'; -import { secureApiPOST, secureApi } from '../services/secureApi'; +import { secureApi } from '../services/secureApi'; import { CompanyReport, Employee, EmployeeReport } from '../types'; import { SAMPLE_COMPANY_REPORT } from '../constants'; import RadarPerformanceChart from '../components/charts/RadarPerformanceChart'; @@ -44,6 +44,17 @@ const Reports: React.FC = () => { loadCompanyReport(); }, [currentUserIsOwner, getFullCompanyReportHistory]); + const handleEmployeeSelect = useCallback((employee: Employee) => { + const employeeReport = reports[employee.id]; + if (employeeReport) { + setSelectedReport({ + report: employeeReport, + type: 'employee', + employeeName: employee.name + }); + } + }, [reports]); + // Handle navigation from Submissions page useEffect(() => { if (selectedEmployeeId) { @@ -61,17 +72,6 @@ const Reports: React.FC = () => { ).sort((a, b) => a.name.localeCompare(b.name)) : employees.filter(emp => emp.id === user?.uid); - const handleEmployeeSelect = useCallback((employee: Employee) => { - const employeeReport = reports[employee.id]; - if (employeeReport) { - setSelectedReport({ - report: employeeReport, - type: 'employee', - employeeName: employee.name - }); - } - }, [reports]); - const handleCompanyReportSelect = () => { if (companyReport) { setSelectedReport({ report: companyReport, type: 'company' }); @@ -203,6 +203,9 @@ const CompanyReportContent: React.FC<{ const [activeDepartmentTab, setActiveDepartmentTab] = useState(() => report?.gradingBreakdown?.[0]?.departmentNameShort || 'Campaigns' ); + const [activeImpactSummary, setActiveImpactSummary] = useState(() => + report?.organizationalImpactSummary?.[0]?.category || 'Mission Critical' + ); return ( <> @@ -322,24 +325,6 @@ const CompanyReportContent: React.FC<{
Immediate Hiring Needs
- {/*
- {report.immediateHiringNeeds.map((need, index) => ( -
-
{need.role} - {need.department}
-
-
- {need.priority} Priority -
-
-
-
-
-
- ))} -
*/}
{report.immediateHiringNeeds.map((need, index) => ( @@ -420,13 +405,14 @@ const CompanyReportContent: React.FC<{ {/* Organizational Impact Summary */} + {/** {report?.organizationalImpactSummary && (
Organizational Impact Summary
- {/* Mission Critical */} +
@@ -442,7 +428,6 @@ const CompanyReportContent: React.FC<{
- {/* Highly Valuable */}
@@ -458,7 +443,6 @@ const CompanyReportContent: React.FC<{
- {/* Core Support */}
@@ -474,7 +458,6 @@ const CompanyReportContent: React.FC<{
- {/* Low Criticality */}
@@ -492,6 +475,72 @@ const CompanyReportContent: React.FC<{
)} + **/} + +
+
+
Organizational Impact Summary
+
+
+
+ {/* Department Tabs */} + {report?.organizationalImpactSummary.map((dept, index) => ( +
setActiveImpactSummary(dept.category)} + > +
+
+ {dept.category} +
+
+
+ ))} +
+ {/* Content for the currently selected department */} + {(() => { + const currentImpact = report?.organizationalImpactSummary.find(dept => dept.category === activeImpactSummary); + if (!currentImpact) return null; + + return ( +
+ {/* Department Overview Section */} + { + currentImpact && currentImpact?.employees.map((item) => ( +
+
+
+
{item.employeeName} - {item.impact}
+
{item.suggestedPay}
+
+
{item.description}
+
+
+
+ )) + } +
+ ); + })()} +
+
+ {/* Grading Overview */}
@@ -553,11 +602,11 @@ const CompanyReportContent: React.FC<{
{currentDepartment.teamScores?.map((teamScore, index) => { const radarData = [ - { label: 'Reliability', value: teamScore.reliability * 10 }, - { label: 'Role Fit', value: teamScore.roleFit * 10 }, - { label: 'Scalability', value: teamScore.scalability * 10 }, - { label: 'Output', value: teamScore.output * 10 }, - { label: 'Initiative', value: teamScore.initiative * 10 } + { label: 'Reliability', value: teamScore.reliability }, + { label: 'Role Fit', value: teamScore.roleFit }, + { label: 'Scalability', value: teamScore.scalability }, + { label: 'Output', value: teamScore.output }, + { label: 'Initiative', value: teamScore.initiative } ]; return ( @@ -577,10 +626,10 @@ const CompanyReportContent: React.FC<{ data={radarData} height={240} color={ - teamScore.grade.startsWith('A') ? '#22c55e' : - teamScore.grade.startsWith('B') ? '#3b82f6' : - teamScore.grade.startsWith('C') ? '#f59e0b' : - '#ef4444' + teamScore.grade.startsWith('A') ? 'var(--Brand-Orange)' : + teamScore.grade.startsWith('B') ? 'var(--color-green)' : + teamScore.grade.startsWith('C') ? 'var(--color-yellow)' : + 'var(--color-red)' } />
@@ -594,32 +643,6 @@ const CompanyReportContent: React.FC<{ })()}
- - {/* Company Strengths */} - {/* {report?.organizationalStrengths && ( -
-
-
Company Strengths
-
-
- {report.organizationalStrengths.map((strength, index) => ( -
-
-
{strength.title}:
-
{strength.description}
-
- {index < report.organizationalStrengths.length - 1 && ( -
- - - -
- )} -
- ))} -
-
- )} */}
); diff --git a/src/pages/Submissions.tsx b/src/pages/Submissions.tsx index 3ce5cb5..65a4224 100644 --- a/src/pages/Submissions.tsx +++ b/src/pages/Submissions.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { useOrg } from '../contexts/OrgContext'; import { useAuth } from '../contexts/AuthContext'; -import { secureApiPOST, secureApi } from '../services/secureApi'; +import { secureApi } from '../services/secureApi'; import { Employee } from '../types'; import { EMPLOYEE_QUESTIONS } from '../employeeQuestions'; diff --git a/src/server/index.js b/src/server/index.js index dbebb67..d5c3fb9 100644 --- a/src/server/index.js +++ b/src/server/index.js @@ -21,7 +21,7 @@ if (isFirebaseConfigured) { try { const { initializeApp } = await import('firebase-admin/app'); const { getFirestore } = await import('firebase-admin/firestore'); - + // Initialize Firebase Admin with project ID (no service account needed for Firestore in same project) admin = initializeApp({ projectId: process.env.VITE_FIREBASE_PROJECT_ID @@ -597,7 +597,7 @@ app.post('/api/invitations', async (req, res) => { const code = generateInviteCode(); const employeeId = (name.split(/\s+/).map(s => s[0]).join('').slice(0, 4) || 'EMP').toUpperCase(); const employee = { id: employeeId, name, email, role, department }; - + const inviteData = { employee, used: false, @@ -744,7 +744,7 @@ app.post('/api/invitations/:code/consume', async (req, res) => { if (inviteDoc.exists) { inviteData = inviteDoc.data(); orgId = orgDoc.id; - + // Mark as used await inviteRef.update({ used: true }); console.log(`✓ Invite consumed in Firestore: ${code} in org: ${orgId}`); @@ -963,21 +963,13 @@ app.post('/api/company-wiki', (req, res) => { averagePerformanceScore: 4.2, riskLevel: 'Medium' }, - keyPersonnelChanges: [], immediateHiringNeeds: [], forwardOperatingPlan: { quarterlyGoals: [org.shortTermGoals || 'Drive focused growth'], resourceNeeds: ['Structured process improvements'], riskMitigation: ['Knowledge sharing cadence'] }, - organizationalStrengths: [org.advantages || 'Mission-aligned core team'].filter(Boolean), organizationalRisks: [org.vulnerabilities || 'Key-person dependencies'].filter(Boolean), - gradingOverview: departmentBreakdown.map(d => ({ - department: d.department, - averageScore: 4 + Math.random(), - totalEmployees: d.count, - scores: [] - })), executiveSummary: `${org.name || 'Company'} is a ${org.industry || 'technology'} company with ${org.size || '11-50'} employees. Mission: ${org.mission || 'N/A'} | Vision: ${org.vision || 'N/A'}` }; @@ -1067,29 +1059,15 @@ app.post('/api/company-report', (req, res) => { promotions: [], departures: [] }, - keyPersonnelChanges: [ - { employeeName: 'Alex Green', role: 'Influencer Coordinator', department: 'Marketing', changeType: 'newHire', impact: 'Expands outreach capability' } - ], immediateHiringNeeds: [ { department: 'Engineering', role: 'Senior Developer', priority: 'High', reasoning: 'Roadmap acceleration', urgency: 'high' }, { department: 'Marketing', role: 'Content Creator', priority: 'Medium', reasoning: 'Content velocity', urgency: 'medium' } ], - operatingPlan: { - nextQuarterGoals: [org.shortTermGoals || 'Improve collaboration', 'Expand market presence', 'Strengthen documentation'], - keyInitiatives: ['Launch structured onboarding', 'Implement objective tracking'], - resourceNeeds: ['Senior engineering capacity', 'Automation tooling'], - riskMitigation: commonBottlenecks.length > 0 ? [`Address: ${commonBottlenecks[0]}`, 'Cross-training plan'] : ['Cross-training plan', 'Process retros'] - }, forwardOperatingPlan: { quarterlyGoals: [org.shortTermGoals || 'Improve collaboration'], resourceNeeds: ['Senior engineering capacity'], riskMitigation: commonBottlenecks.length > 0 ? [`Address: ${commonBottlenecks[0]}`] : ['Cross-training plan'] }, - organizationalStrengths: [ - { area: org.advantages || 'Technical depth', description: 'Core team demonstrates strong execution speed.' }, - { area: 'Culture', description: org.cultureDescription || 'Collaborative, learning oriented.' }, - ...(underutilizedTalents.length > 0 ? [{ area: 'Hidden Talent', description: `Underutilized capabilities: ${underutilizedTalents.join(', ')}` }] : []) - ], organizationalRisks: [ ...(retentionRisks.length > 0 ? retentionRisks.map(risk => `Retention risk: ${risk}`) : []), ...(commonBottlenecks.length > 0 ? [`Common bottleneck: ${commonBottlenecks[0]}`] : []), @@ -1097,7 +1075,6 @@ app.post('/api/company-report', (req, res) => { ].filter(Boolean), organizationalImpactSummary: `Team impact is strong relative to size (${submissionCount}/${totalEmployees} employees surveyed); scaling processes will unlock further leverage.`, gradingBreakdown: gradingCategories, - gradingOverview: legacyOverview, executiveSummary: generateExecutiveSummary(org, submissionData) }; diff --git a/src/services/secureApi.ts b/src/services/secureApi.ts index f20bfba..3fcb16a 100644 --- a/src/services/secureApi.ts +++ b/src/services/secureApi.ts @@ -347,20 +347,3 @@ class SecureApiService { // Export a singleton instance export const secureApi = new SecureApiService(); export default secureApi; - -// Helper functions for backwards compatibility -export const secureApiPOST = async (endpoint: string, data: any) => { - return secureApi.makeRequest(endpoint, 'POST', data); -}; - -export const secureApiGET = async (endpoint: string) => { - return secureApi.makeRequest(endpoint, 'GET'); -}; - -export const secureApiPUT = async (endpoint: string, data: any) => { - return secureApi.makeRequest(endpoint, 'PUT', data); -}; - -export const secureApiDELETE = async (endpoint: string, data?: any) => { - return secureApi.makeRequest(endpoint, 'DELETE', data); -}; \ No newline at end of file diff --git a/src/types.ts b/src/types.ts index b6ea623..46364ac 100644 --- a/src/types.ts +++ b/src/types.ts @@ -243,27 +243,14 @@ export interface CompanyReport { }[]; strengths: string[]; organizationalImpactSummary?: { - missionCritical: { + category: 'Mission Critical' | 'Highly Valuable' | 'Core Support' | 'Low Criticality'; + employees: { employeeName: string; impact: string; description: string; + suggestedPay: string; }[]; - highlyValuable: { - employeeName: string; - impact: string; - description: string; - }[]; - coreSupport: { - employeeName: string; - impact: string; - description: string; - }[]; - lowCriticality: { - employeeName: string; - impact: string; - description: string; - }[]; - }; + }[]; // Grading: array + map for UI gradingBreakdown: { departmentNameShort: string; // For the tab label @@ -284,34 +271,7 @@ export interface CompanyReport { initiative: number; }[]; }[]; - // gradingBreakdown: { - // departmentName: string; - // lead: string; - // support: string; - // summary: string; - // category: string; - // value: number; // 0-100 - // rationale?: string; - // }[]; - // Do not use any of the properties below!!! they need to be safely removed - organizationalRisks: string[]; - operatingPlan: { // Do not use!!! - nextQuarterGoals: string[]; - keyInitiatives: string[]; - resourceNeeds: string[]; - riskMitigation: string[]; - }; - organizationalStrengths: Array<{ icon?: string; area: string; description: string }>; // Do not use!!! needs to be removed - keyPersonnelChanges?: Array<{ // Do not use!! - employeeName: string; - role: string; - newRole?: string; - department: string; - changeType: 'reassignment' | 'promotion' | 'departure'; - impact?: string; - }>; - gradingOverview?: Record; // legacy (0-5 scale expected by old UI) executiveSummary: string; }