382 lines
17 KiB
TypeScript
382 lines
17 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import { useNavigate, useLocation } from 'react-router-dom';
|
||
import { useAuth } from '../contexts/AuthContext';
|
||
import { useOrg } from '../contexts/OrgContext';
|
||
import { Card, Button } from '../components/UiKit';
|
||
import { EMPLOYEE_QUESTIONS, EmployeeSubmissionAnswers } from '../employeeQuestions';
|
||
import { Question } from '../components/ui/Question';
|
||
import { QuestionInput } from '../components/ui/QuestionInput';
|
||
import { FigmaQuestion } from '../components/figma/FigmaQuestion';
|
||
import { EnhancedFigmaQuestion } from '../components/figma/EnhancedFigmaQuestion';
|
||
import { LinearProgress } from '../components/ui/Progress';
|
||
import { Alert } from '../components/ui/Alert';
|
||
|
||
const EmployeeQuestionnaireSteps: React.FC = () => {
|
||
const navigate = useNavigate();
|
||
const location = useLocation();
|
||
const { user } = useAuth();
|
||
const { submitEmployeeAnswers, generateEmployeeReport, employees } = useOrg();
|
||
const [answers, setAnswers] = useState<EmployeeSubmissionAnswers>({});
|
||
const [currentStep, setCurrentStep] = useState(0);
|
||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||
const [error, setError] = useState('');
|
||
|
||
// Get employee info from multiple sources
|
||
const invitedEmployee = location.state?.invitedEmployee;
|
||
|
||
// Find current employee info - try multiple strategies
|
||
let currentEmployee = invitedEmployee || employees.find(emp => emp.email === user?.email);
|
||
|
||
// Additional matching strategies for edge cases
|
||
if (!currentEmployee && user?.email) {
|
||
// Try case-insensitive email matching
|
||
currentEmployee = employees.find(emp =>
|
||
emp.email?.toLowerCase() === user.email?.toLowerCase()
|
||
);
|
||
|
||
// Try matching by name if email doesn't work (for invite flow)
|
||
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];
|
||
}
|
||
|
||
// Filter out followup questions that shouldn't be shown yet
|
||
const getVisibleQuestions = () => {
|
||
return EMPLOYEE_QUESTIONS.filter(question => {
|
||
if (!question.followupTo) return true;
|
||
|
||
const parentAnswer = answers[question.followupTo];
|
||
if (question.followupTo === 'has_kpis') {
|
||
return parentAnswer === 'Yes';
|
||
}
|
||
if (question.followupTo === 'unclear_responsibilities') {
|
||
return parentAnswer === 'Yes';
|
||
}
|
||
if (question.followupTo === 'role_shift_interest') {
|
||
return parentAnswer === 'Yes';
|
||
}
|
||
return false;
|
||
});
|
||
};
|
||
|
||
const visibleQuestions = getVisibleQuestions();
|
||
const currentQuestion = visibleQuestions[currentStep];
|
||
|
||
const handleAnswerChange = (value: string) => {
|
||
if (currentQuestion) {
|
||
setAnswers(prev => ({ ...prev, [currentQuestion.id]: value }));
|
||
}
|
||
};
|
||
|
||
const handleNext = () => {
|
||
if (currentStep < visibleQuestions.length - 1) {
|
||
setCurrentStep(prev => prev + 1);
|
||
} else {
|
||
handleSubmit();
|
||
}
|
||
};
|
||
|
||
const handleBack = () => {
|
||
if (currentStep > 0) {
|
||
setCurrentStep(prev => prev - 1);
|
||
}
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
setIsSubmitting(true);
|
||
setError('');
|
||
|
||
try {
|
||
// Validate required questions
|
||
const requiredQuestions = visibleQuestions.filter(q => q.required);
|
||
const missingAnswers = requiredQuestions.filter(q => !answers[q.id]?.trim());
|
||
|
||
if (missingAnswers.length > 0) {
|
||
setError(`Please answer all required questions: ${missingAnswers.map(q => q.prompt).join(', ')}`);
|
||
setIsSubmitting(false);
|
||
return;
|
||
}
|
||
|
||
if (!currentEmployee) {
|
||
// Enhanced fallback logic
|
||
if (employees.length > 0) {
|
||
// Try to find employee by matching with the user's email more aggressively
|
||
let fallbackEmployee = employees.find(emp =>
|
||
emp.email?.toLowerCase().includes(user?.email?.toLowerCase().split('@')[0] || '')
|
||
);
|
||
|
||
// If still no match, use the most recent employee or one with matching domain
|
||
if (!fallbackEmployee) {
|
||
const userDomain = user?.email?.split('@')[1];
|
||
fallbackEmployee = employees.find(emp =>
|
||
emp.email?.split('@')[1] === userDomain
|
||
) || employees[employees.length - 1];
|
||
}
|
||
|
||
console.log('Using enhanced fallback employee:', fallbackEmployee);
|
||
|
||
const success = await submitEmployeeAnswers(fallbackEmployee.id, answers);
|
||
if (success) {
|
||
try {
|
||
const report = await generateEmployeeReport(fallbackEmployee);
|
||
if (report) {
|
||
console.log('Report generated successfully for fallback employee:', report);
|
||
}
|
||
} catch (reportError) {
|
||
console.error('Failed to generate report for fallback employee:', reportError);
|
||
}
|
||
navigate('/questionnaire-complete', {
|
||
replace: true,
|
||
state: {
|
||
employeeId: fallbackEmployee.id,
|
||
employeeName: fallbackEmployee.name,
|
||
message: 'Questionnaire submitted successfully! Your responses have been recorded.'
|
||
}
|
||
});
|
||
return;
|
||
}
|
||
}
|
||
setError(`We couldn't match your account (${user?.email}) with an employee record. Please contact your administrator to ensure your invite was set up correctly.`);
|
||
setIsSubmitting(false);
|
||
return;
|
||
}
|
||
|
||
const result = await submitEmployeeAnswers(currentEmployee.id, answers);
|
||
|
||
if (result.success) {
|
||
const message = result.reportGenerated
|
||
? 'Questionnaire submitted successfully! Your AI-powered performance report has been generated.'
|
||
: 'Questionnaire submitted successfully! Your report will be available shortly.';
|
||
|
||
navigate('/questionnaire-complete', {
|
||
state: {
|
||
employeeId: currentEmployee.id,
|
||
employeeName: currentEmployee.name,
|
||
reportGenerated: result.reportGenerated,
|
||
message: message
|
||
}
|
||
});
|
||
} else {
|
||
setError(result.message || 'Failed to submit questionnaire');
|
||
}
|
||
} catch (error) {
|
||
console.error('Submission error:', error);
|
||
setError('Failed to submit questionnaire. Please try again.');
|
||
} finally {
|
||
setIsSubmitting(false);
|
||
}
|
||
};
|
||
|
||
const getProgressPercentage = () => {
|
||
return Math.round(((currentStep + 1) / visibleQuestions.length) * 100);
|
||
};
|
||
|
||
const isCurrentQuestionAnswered = () => {
|
||
if (!currentQuestion) return false;
|
||
const answer = answers[currentQuestion.id];
|
||
return answer && answer.trim().length > 0;
|
||
};
|
||
|
||
if (!currentQuestion) {
|
||
return (
|
||
<div className="min-h-screen bg-[--background-primary] py-8 px-4">
|
||
<div className="max-w-4xl mx-auto text-center">
|
||
<h1 className="text-2xl font-bold text-[--text-primary] mb-4">
|
||
No questions available
|
||
</h1>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-[--background-primary] py-8 px-4">
|
||
<div className="max-w-4xl mx-auto">
|
||
{/* Header */}
|
||
<div className="text-center mb-8">
|
||
<div className="w-16 h-16 bg-blue-500 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-[--text-primary] mb-2">
|
||
Welcome to Auditly!
|
||
</h1>
|
||
<p className="text-[--text-secondary] mb-4">
|
||
Please complete this questionnaire to help us understand your role and create personalized insights.
|
||
</p>
|
||
{currentEmployee ? (
|
||
<div className="inline-flex items-center px-4 py-2 bg-blue-100 dark:bg-blue-900 rounded-lg">
|
||
<span className="text-sm text-blue-800 dark:text-blue-200">
|
||
👋 Hello {currentEmployee.name}! {currentEmployee.role && `(${currentEmployee.role})`}
|
||
</span>
|
||
</div>
|
||
) : (
|
||
<div className="space-y-2">
|
||
<div className="inline-flex items-center px-4 py-2 bg-yellow-100 dark:bg-yellow-900 rounded-lg">
|
||
<span className="text-sm text-yellow-800 dark:text-yellow-200">
|
||
⚠️ Employee info not found. User: {user?.email}, Employees: {employees.length}
|
||
</span>
|
||
</div>
|
||
<div className="text-xs text-[--text-secondary] max-w-md mx-auto">
|
||
<p>Don't worry - your account was created successfully! This is likely a temporary sync issue.</p>
|
||
<p className="mt-1">You can still complete the questionnaire, and we'll match it to your profile automatically.</p>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* Progress Bar */}
|
||
<div className="mb-8">
|
||
<div className="flex justify-between items-center mb-2">
|
||
<span className="text-sm text-[--text-secondary]">
|
||
Question {currentStep + 1} of {visibleQuestions.length}
|
||
</span>
|
||
<span className="text-sm text-[--text-secondary]">{getProgressPercentage()}%</span>
|
||
</div>
|
||
<LinearProgress value={getProgressPercentage()} />
|
||
</div>
|
||
|
||
{/* Enhanced Question Card */}
|
||
<div className="flex justify-center mb-8">
|
||
<div className="w-full max-w-2xl">
|
||
<EnhancedFigmaQuestion
|
||
questionNumber={`Q${currentStep + 1}`}
|
||
question={currentQuestion}
|
||
answer={answers[currentQuestion.id] || ''}
|
||
onAnswerChange={handleAnswerChange}
|
||
onBack={currentStep > 0 ? handleBack : undefined}
|
||
onNext={
|
||
currentStep < visibleQuestions.length - 1
|
||
? handleNext
|
||
: (isCurrentQuestionAnswered() || !currentQuestion.required)
|
||
? handleSubmit
|
||
: undefined
|
||
}
|
||
nextLabel={
|
||
currentStep < visibleQuestions.length - 1
|
||
? 'Next'
|
||
: isSubmitting
|
||
? 'Submitting...'
|
||
: 'Submit & Generate Report'
|
||
}
|
||
showNavigation={true}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Original Figma Question Card for Comparison */}
|
||
<div className="flex justify-center mb-8">
|
||
<div className="w-full max-w-2xl">
|
||
<FigmaQuestion
|
||
questionNumber={`Q${currentStep + 1}`}
|
||
title={currentQuestion.prompt}
|
||
description={currentQuestion.required ? 'Required' : 'Optional'}
|
||
answer={answers[currentQuestion.id] || ''}
|
||
onAnswerChange={handleAnswerChange}
|
||
onBack={currentStep > 0 ? handleBack : undefined}
|
||
onNext={
|
||
currentStep < visibleQuestions.length - 1
|
||
? handleNext
|
||
: (isCurrentQuestionAnswered() || !currentQuestion.required)
|
||
? handleSubmit
|
||
: undefined
|
||
}
|
||
nextLabel={
|
||
currentStep < visibleQuestions.length - 1
|
||
? 'Next'
|
||
: isSubmitting
|
||
? 'Submitting...'
|
||
: 'Submit & Generate Report'
|
||
}
|
||
showNavigation={true}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Alternative Input for Different Question Types */}
|
||
<div className="flex justify-center mb-8">
|
||
<div className="w-full max-w-2xl">
|
||
<Card className="p-6">
|
||
<Question
|
||
label={`${currentStep + 1}. ${currentQuestion.prompt}`}
|
||
required={currentQuestion.required}
|
||
description={`Category: ${currentQuestion.category}`}
|
||
>
|
||
<QuestionInput
|
||
question={currentQuestion}
|
||
value={answers[currentQuestion.id] || ''}
|
||
onChange={handleAnswerChange}
|
||
/>
|
||
</Question>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Error Display */}
|
||
{error && (
|
||
<div className="mb-6 flex justify-center">
|
||
<div className="w-full max-w-2xl">
|
||
<Alert variant="error" title="Error">
|
||
{error}
|
||
</Alert>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Navigation Buttons */}
|
||
<div className="flex justify-center gap-4">
|
||
{currentStep > 0 && (
|
||
<Button
|
||
variant="secondary"
|
||
onClick={handleBack}
|
||
disabled={isSubmitting}
|
||
>
|
||
Back
|
||
</Button>
|
||
)}
|
||
|
||
{currentStep < visibleQuestions.length - 1 ? (
|
||
<Button
|
||
onClick={handleNext}
|
||
disabled={currentQuestion.required && !isCurrentQuestionAnswered()}
|
||
>
|
||
Next
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
onClick={handleSubmit}
|
||
disabled={
|
||
isSubmitting ||
|
||
(currentQuestion.required && !isCurrentQuestionAnswered())
|
||
}
|
||
>
|
||
{isSubmitting ? 'Submitting & Generating Report...' : 'Submit & Generate AI Report'}
|
||
</Button>
|
||
)}
|
||
</div>
|
||
|
||
{/* Help Text */}
|
||
<div className="text-center mt-6">
|
||
<p className="text-sm text-[--text-secondary]">
|
||
{currentQuestion.required && !isCurrentQuestionAnswered()
|
||
? 'Please answer this required question to continue.'
|
||
: 'You can skip optional questions or come back to them later.'
|
||
}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default EmployeeQuestionnaireSteps;
|