Files
auditly/src/pages/EmployeeQuestionnaireNew.tsx

600 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import { EmployeeSubmissionAnswers } from '../employeeQuestions';
import { API_URL } from '../constants';
import {
WelcomeScreen,
SectionIntro,
PersonalInfoForm,
TextAreaQuestion,
RatingScaleQuestion,
YesNoChoice,
ThankYouPage
} from '../components/figma/FigmaEmployeeForms';
/**
* Employee Questionnaire with Invite-Only Flow
*
* Features:
* - Invite-based flow (no authentication required)
* - 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 params = useParams();
const inviteCode = params.inviteCode;
// Component state
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState<any>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState('');
const [inviteEmployee, setInviteEmployee] = useState<any>(null);
const [isLoadingInvite, setIsLoadingInvite] = useState(false);
// Load invite details on component mount
useEffect(() => {
if (inviteCode) {
loadInviteDetails(inviteCode);
} else {
setError('No invite code provided');
}
}, [inviteCode]);
const loadInviteDetails = async (code: string) => {
setIsLoadingInvite(true);
try {
const response = await fetch(`${API_URL}/getInvitationStatus?code=${code}`);
if (response.ok) {
const data = await response.json();
if (data.used) {
setError('This invitation has already been used');
} else if (data.employee) {
setInviteEmployee(data.employee);
// Pre-populate form data with invite metadata
setFormData({
name: data.employee.name || '',
email: data.employee.email || '',
company: data.employee.company || data.employee.department || ''
});
setError('');
} else {
setError('Invalid invitation data');
}
} else {
const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
setError(errorData.error || 'Invalid or expired invitation link');
}
} catch (err) {
console.error('Error loading invite details:', err);
setError('Failed to load invitation details');
} finally {
setIsLoadingInvite(false);
}
};
// Get employee info from invite data
const currentEmployee = inviteEmployee;
const submitViaInvite = async (answers: EmployeeSubmissionAnswers, inviteCode: string) => {
try {
// Submit the questionnaire answers directly using Cloud Function
// The cloud function will handle invite consumption and report generation
const submitResponse = await fetch(`${API_URL}/submitEmployeeAnswers`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
inviteCode: inviteCode,
answers: answers
})
});
if (!submitResponse.ok) {
const errorData = await submitResponse.json();
throw new Error(errorData.error || 'Failed to submit questionnaire');
}
const result = await submitResponse.json();
return { success: true, reportGenerated: true };
} catch (error) {
console.error('Invite submission error:', error);
return { success: false, error: error.message };
}
};
const handleSubmit = async () => {
setIsSubmitting(true);
setError('');
try {
// Convert form data to answers format for backend
const answers: EmployeeSubmissionAnswers = {};
// Map form data to question IDs
if (formData.name) answers['full_name'] = formData.name;
if (formData.email) answers['email'] = formData.email;
if (formData.company) answers['company_department'] = formData.company;
// Add all other form data fields
Object.keys(formData).forEach(key => {
if (formData[key] && !answers[key]) {
answers[key] = formData[key];
}
});
// Submit answers via invite flow
const result = await submitViaInvite(answers, inviteCode!);
if (result.success) {
// Show thank you page
setCurrentStep(999);
} else {
setError(result.error || 'Failed to submit questionnaire');
}
} catch (error) {
console.error('Submission error:', error);
setError('Failed to submit questionnaire. Please try again.');
} finally {
setIsSubmitting(false);
}
};
const handleNext = (stepData?: any) => {
if (stepData) {
const newFormData = { ...formData, ...stepData };
setFormData(newFormData);
}
setCurrentStep(currentStep + 1);
};
const handleBack = () => {
setCurrentStep(currentStep - 1);
};
// Early return for loading state
if (isLoadingInvite) {
return (
<div className="min-h-screen bg-[--Neutrals-NeutralSlate0] py-8 px-4 flex items-center justify-center">
<div className="max-w-4xl mx-auto text-center">
<div className="w-16 h-16 bg-[--Brand-Orange] rounded-full flex items-center justify-center font-bold text-white text-2xl mx-auto mb-4">
A
</div>
<h1 className="text-3xl font-bold text-[--Neutrals-NeutralSlate950] mb-4">Loading Your Invitation...</h1>
<p className="text-[--Neutrals-NeutralSlate500]">Please wait while we verify your invitation.</p>
</div>
</div>
);
}
// Early return for error state
if (error && currentStep === 1) {
return (
<div className="min-h-screen bg-[--Neutrals-NeutralSlate0] py-8 px-4 flex items-center justify-center">
<div className="max-w-4xl mx-auto text-center">
<div className="w-16 h-16 bg-red-500 rounded-full flex items-center justify-center font-bold text-white text-2xl mx-auto mb-4">
!
</div>
<h1 className="text-3xl font-bold text-[--Neutrals-NeutralSlate950] mb-4">Invitation Error</h1>
<p className="text-[--Neutrals-NeutralSlate500] mb-6">{error}</p>
<button
onClick={() => window.location.href = '/'}
className="px-6 py-3 bg-[--Brand-Orange] text-white rounded-lg hover:bg-orange-600"
>
Return to Homepage
</button>
</div>
</div>
);
}
const renderStep = () => {
switch (currentStep) {
case 1:
return (
<WelcomeScreen
onStart={() => handleNext()}
/>
);
case 2:
return (
<PersonalInfoForm
formData={{
email: formData.email || '',
name: formData.name || '',
company: formData.company || ''
}}
onChange={(data) => setFormData({ ...formData, ...data })}
onNext={() => handleNext()}
/>
);
case 3:
return (
<SectionIntro
sectionNumber="1 of 6"
title="Your Role & Responsibilities"
description="Let's start by understanding your current role and daily responsibilities."
onStart={() => handleNext()}
/>
);
case 4:
return (
<TextAreaQuestion
question="What is your current title and department?"
value={formData.titleAndDepartment || ''}
onChange={(value) => setFormData({ ...formData, titleAndDepartment: value })}
onNext={() => handleNext()}
currentStep={1}
totalSteps={7}
sectionName="Your Role & Responsibilities"
/>
);
case 5:
return (
<TextAreaQuestion
question="Describe your core daily responsibilities"
value={formData.dailyResponsibilities || ''}
onChange={(value) => setFormData({ ...formData, dailyResponsibilities: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={2}
totalSteps={7}
sectionName="Your Role & Responsibilities"
placeholder="Describe what you do on a typical day..."
/>
);
case 6:
return (
<RatingScaleQuestion
question="How clearly do you understand your role and responsibilities?"
leftLabel="Not clear"
rightLabel="Very clear"
value={formData.roleClarity}
onChange={(value) => setFormData({ ...formData, roleClarity: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={3}
totalSteps={7}
sectionName="Your Role & Responsibilities"
scale={10}
/>
);
case 7:
return (
<SectionIntro
sectionNumber="2 of 6"
title="Output & Accountability"
description="Let's explore your work output, goals, and accountability measures."
onStart={() => handleNext()}
/>
);
case 8:
return (
<RatingScaleQuestion
question="How would you rate your weekly output (volume & quality)?"
leftLabel="Very little"
rightLabel="Very High"
value={formData.weeklyOutput}
onChange={(value) => setFormData({ ...formData, weeklyOutput: value })}
onNext={() => handleNext()}
currentStep={1}
totalSteps={7}
sectionName="Output & Accountability"
scale={10}
/>
);
case 9:
return (
<TextAreaQuestion
question="What are your top 23 recurring deliverables?"
value={formData.topDeliverables || ''}
onChange={(value) => setFormData({ ...formData, topDeliverables: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={2}
totalSteps={7}
sectionName="Output & Accountability"
/>
);
case 10:
return (
<YesNoChoice
question="Do you have weekly KPIs or goals?"
value={formData.hasKPIs}
onChange={(value) => setFormData({ ...formData, hasKPIs: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={3}
totalSteps={7}
sectionName="Output & Accountability"
/>
);
case 11:
return (
<TextAreaQuestion
question="Who do you report to? How often do you meet/check-in?"
value={formData.reportingStructure || ''}
onChange={(value) => setFormData({ ...formData, reportingStructure: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={4}
totalSteps={7}
sectionName="Output & Accountability"
/>
);
case 12:
return (
<SectionIntro
sectionNumber="3 of 6"
title="Team & Collaboration"
description="Let's understand your team dynamics and collaboration patterns."
onStart={() => handleNext()}
/>
);
case 13:
return (
<TextAreaQuestion
question="Who do you work most closely with?"
value={formData.closeCollaborators || ''}
onChange={(value) => setFormData({ ...formData, closeCollaborators: value })}
onNext={() => handleNext()}
currentStep={1}
totalSteps={7}
sectionName="Team & Collaboration"
/>
);
case 14:
return (
<RatingScaleQuestion
question="How would you rate team communication overall?"
leftLabel="Poor"
rightLabel="Excellent"
value={formData.teamCommunication}
onChange={(value) => setFormData({ ...formData, teamCommunication: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={2}
totalSteps={7}
sectionName="Team & Collaboration"
scale={10}
/>
);
case 15:
return (
<TextAreaQuestion
question="Do you feel supported by your team? How?"
value={formData.teamSupport || ''}
onChange={(value) => setFormData({ ...formData, teamSupport: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={3}
totalSteps={7}
sectionName="Team & Collaboration"
/>
);
case 16:
return (
<SectionIntro
sectionNumber="4 of 6"
title="Tools & Resources"
description="Let's examine the tools and resources available to support your work."
onStart={() => handleNext()}
/>
);
case 17:
return (
<TextAreaQuestion
question="What tools and software do you currently use?"
value={formData.currentTools || ''}
onChange={(value) => setFormData({ ...formData, currentTools: value })}
onNext={() => handleNext()}
currentStep={1}
totalSteps={7}
sectionName="Tools & Resources"
/>
);
case 18:
return (
<RatingScaleQuestion
question="How effective are your current tools?"
leftLabel="Not effective"
rightLabel="Very effective"
value={formData.toolEffectiveness}
onChange={(value) => setFormData({ ...formData, toolEffectiveness: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={2}
totalSteps={7}
sectionName="Tools & Resources"
scale={10}
/>
);
case 19:
return (
<TextAreaQuestion
question="What tools or resources are you missing to do your job more effectively?"
value={formData.missingTools || ''}
onChange={(value) => setFormData({ ...formData, missingTools: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={3}
totalSteps={7}
sectionName="Tools & Resources"
/>
);
case 20:
return (
<SectionIntro
sectionNumber="5 of 6"
title="Skills & Development"
description="Let's explore your skills, growth opportunities, and career development."
onStart={() => handleNext()}
/>
);
case 21:
return (
<TextAreaQuestion
question="What are your key skills and strengths?"
value={formData.keySkills || ''}
onChange={(value) => setFormData({ ...formData, keySkills: value })}
onNext={() => handleNext()}
currentStep={1}
totalSteps={7}
sectionName="Skills & Development"
/>
);
case 22:
return (
<TextAreaQuestion
question="What skills would you like to develop or improve?"
value={formData.skillDevelopment || ''}
onChange={(value) => setFormData({ ...formData, skillDevelopment: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={2}
totalSteps={7}
sectionName="Skills & Development"
/>
);
case 23:
return (
<YesNoChoice
question="Are you aware of current training opportunities?"
value={formData.awareOfTraining}
onChange={(value) => setFormData({ ...formData, awareOfTraining: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={3}
totalSteps={7}
sectionName="Skills & Development"
/>
);
case 24:
return (
<TextAreaQuestion
question="What are your career goals within the company?"
value={formData.careerGoals || ''}
onChange={(value) => setFormData({ ...formData, careerGoals: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={4}
totalSteps={7}
sectionName="Skills & Development"
/>
);
case 25:
return (
<SectionIntro
sectionNumber="6 of 6"
title="Feedback & Improvement"
description="Finally, let's gather your thoughts on company improvements and overall satisfaction."
onStart={() => handleNext()}
/>
);
case 26:
return (
<TextAreaQuestion
question="What improvements would you suggest for the company?"
value={formData.companyImprovements || ''}
onChange={(value) => setFormData({ ...formData, companyImprovements: value })}
onNext={() => handleNext()}
currentStep={1}
totalSteps={7}
sectionName="Feedback & Improvement"
/>
);
case 27:
return (
<RatingScaleQuestion
question="How satisfied are you with your current job overall?"
leftLabel="Not satisfied"
rightLabel="Very satisfied"
value={formData.jobSatisfaction}
onChange={(value) => setFormData({ ...formData, jobSatisfaction: value })}
onBack={() => handleBack()}
onNext={() => handleNext()}
currentStep={2}
totalSteps={7}
sectionName="Feedback & Improvement"
scale={10}
/>
);
case 28:
return (
<TextAreaQuestion
question="Any additional feedback or suggestions for the company?"
value={formData.additionalFeedback || ''}
onChange={(value) => setFormData({ ...formData, additionalFeedback: value })}
onBack={() => handleBack()}
onNext={() => handleSubmit()}
currentStep={3}
totalSteps={7}
sectionName="Feedback & Improvement"
placeholder="Share any thoughts, suggestions, or feedback..."
/>
);
case 999: // Thank you page
return <ThankYouPage />;
default:
return <ThankYouPage />;
}
};
if (isSubmitting) {
return (
<div className="min-h-screen bg-[--Neutrals-NeutralSlate0] py-8 px-4 flex items-center justify-center">
<div className="max-w-4xl mx-auto text-center">
<div className="w-16 h-16 bg-[--Brand-Orange] rounded-full flex items-center justify-center font-bold text-white text-2xl mx-auto mb-4 animate-pulse">
A
</div>
<h1 className="text-3xl font-bold text-[--Neutrals-NeutralSlate950] mb-4">Submitting Your Responses...</h1>
<p className="text-[--Neutrals-NeutralSlate500]">Please wait while we process your assessment and generate your report.</p>
</div>
</div>
);
}
return (
<div className="flex h-screen bg-[--Neutrals-NeutralSlate0]">
{renderStep()}
{error && (
<div className="fixed bottom-4 right-4 bg-red-500 text-white p-4 rounded-lg shadow-lg z-50">
{error}
</div>
)}
</div>
);
};
export default EmployeeQuestionnaire;