1166 lines
80 KiB
TypeScript
1166 lines
80 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
import { useOrg } from '../contexts/OrgContext';
|
|
import { secureApi } from '../services/secureApi';
|
|
import { CompanyReport, Employee, EmployeeReport } from '../types';
|
|
import { SAMPLE_COMPANY_REPORT } from '../constants';
|
|
import RadarPerformanceChart from '../components/charts/RadarPerformanceChart';
|
|
import { downloadCompanyReportPDF, downloadEmployeeReportPDF } from '../utils/pdfUtils';
|
|
import FigmaPrimaryButton from '../components/figma/FigmaButton';
|
|
import { DownloadIcon } from '../components/figma/figmaIcon';
|
|
import MarkdownRenderer from '../components/MarkdownRenderer';
|
|
|
|
const Reports: React.FC = () => {
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
|
|
// Helper function to extract company report context for AI
|
|
const extractCompanyReportContext = (report: CompanyReport, orgName: string): string => {
|
|
const sections = [];
|
|
|
|
if (report.overview) {
|
|
sections.push(`Company Overview:
|
|
- Total Employees: ${report.overview.totalEmployees}
|
|
- Average Performance Score: ${report.overview.averagePerformanceScore}
|
|
- Risk Level: ${report.overview.riskLevel}
|
|
- Submission Rate: ${report.overview.submissionRate}%`);
|
|
|
|
if (report.overview.departmentBreakdown?.length > 0) {
|
|
const deptInfo = report.overview.departmentBreakdown
|
|
.map(dept => `${dept.department}: ${dept.count} employees`)
|
|
.join(', ');
|
|
sections.push(`Department Breakdown: ${deptInfo}`);
|
|
}
|
|
}
|
|
|
|
if (report.strengths?.length > 0) {
|
|
sections.push(`Company Strengths:\n${report.strengths.map(s => `- ${s}`).join('\n')}`);
|
|
}
|
|
|
|
if (report.weaknesses?.length > 0) {
|
|
sections.push(`Company Weaknesses:\n${report.weaknesses.map(w => `- ${w.title}: ${w.description}`).join('\n')}`);
|
|
}
|
|
|
|
if (report.immediateHiringNeeds?.length > 0) {
|
|
sections.push(`Immediate Hiring Needs:\n${report.immediateHiringNeeds.map(need => `- ${need.role}: ${need.priority} priority - ${need.reasoning}`).join('\n')}`);
|
|
}
|
|
|
|
if (report.personnelChanges) {
|
|
const changes = [];
|
|
if (report.personnelChanges.newHires?.length > 0) {
|
|
changes.push(`New Hires: ${report.personnelChanges.newHires.map(hire => `${hire.name} (${hire.role} in ${hire.department})`).join(', ')}`);
|
|
}
|
|
if (report.personnelChanges.promotions?.length > 0) {
|
|
changes.push(`Promotions: ${report.personnelChanges.promotions.map(promo => `${promo.name}: ${promo.fromRole} → ${promo.toRole}`).join(', ')}`);
|
|
}
|
|
if (report.personnelChanges.departures?.length > 0) {
|
|
changes.push(`Departures: ${report.personnelChanges.departures.map(dep => `${dep.name} (${dep.department}) - ${dep.reason}`).join(', ')}`);
|
|
}
|
|
if (changes.length > 0) {
|
|
sections.push(`Personnel Changes:\n${changes.join('\n')}`);
|
|
}
|
|
}
|
|
|
|
if (report.forwardOperatingPlan?.length > 0) {
|
|
sections.push(`Forward Operating Plan:\n${report.forwardOperatingPlan.map(plan => `- ${plan.title}: ${plan.details.join(', ')}`).join('\n')}`);
|
|
}
|
|
|
|
if (report.gradingBreakdown?.length > 0) {
|
|
const gradingInfo = report.gradingBreakdown.map(dept =>
|
|
`${dept.departmentName}: ${dept.departmentGrade} (Lead: ${dept.lead})`
|
|
).join('\n');
|
|
sections.push(`Performance Grading by Department:\n${gradingInfo}`);
|
|
}
|
|
|
|
return sections.join('\n\n');
|
|
};
|
|
|
|
// Helper function to extract employee report context for AI
|
|
const extractEmployeeReportContext = (report: EmployeeReport, employeeName: string, companyReport?: CompanyReport): { employeeContext: string; companyContext?: string } => {
|
|
const employeeSections = [];
|
|
|
|
if (report.roleAndOutput) {
|
|
employeeSections.push(`Role & Responsibilities: ${report.roleAndOutput.responsibilities}`);
|
|
if (report.roleAndOutput.selfRatedOutput) {
|
|
employeeSections.push(`Self-Rated Output: ${report.roleAndOutput.selfRatedOutput}`);
|
|
}
|
|
}
|
|
|
|
if (report.insights) {
|
|
employeeSections.push(`Personality Traits: ${report.insights.personalityTraits}`);
|
|
if (report.insights.selfAwareness) {
|
|
employeeSections.push(`Self-Awareness: ${report.insights.selfAwareness}`);
|
|
}
|
|
if (report.insights.growthDesire) {
|
|
employeeSections.push(`Growth Desire: ${report.insights.growthDesire}`);
|
|
}
|
|
}
|
|
|
|
if (report.strengths?.length > 0) {
|
|
employeeSections.push(`Strengths:\n${report.strengths.map(s => `- ${s}`).join('\n')}`);
|
|
}
|
|
|
|
if (report.weaknesses?.length > 0) {
|
|
employeeSections.push(`Areas for Improvement:\n${report.weaknesses.map(w => `- ${w}`).join('\n')}`);
|
|
}
|
|
|
|
if (report.recommendations?.length > 0) {
|
|
employeeSections.push(`Recommendations:\n${report.recommendations.map(r => `- ${r}`).join('\n')}`);
|
|
}
|
|
|
|
if (report.gradingOverview) {
|
|
const gradingInfo = `Grade: ${report.gradingOverview.grade}, Reliability: ${report.gradingOverview.reliability}, Role Fit: ${report.gradingOverview.roleFit}, Scalability: ${report.gradingOverview.scalability}, Output: ${report.gradingOverview.output}, Initiative: ${report.gradingOverview.initiative}`;
|
|
employeeSections.push(`Performance Scores: ${gradingInfo}`);
|
|
}
|
|
|
|
const employeeContext = employeeSections.join('\n\n');
|
|
let companyContext;
|
|
|
|
if (companyReport && org?.companyName) {
|
|
companyContext = extractCompanyReportContext(companyReport, org.companyName);
|
|
}
|
|
|
|
return { employeeContext, companyContext };
|
|
};
|
|
|
|
// Handle Chat with AI navigation for company reports
|
|
const handleCompanyChatWithAI = () => {
|
|
if (!companyReport || !org?.companyName) return;
|
|
|
|
const context = extractCompanyReportContext(companyReport, org.companyName);
|
|
|
|
navigate('/chat', {
|
|
state: {
|
|
initialContext: context,
|
|
contextType: 'company',
|
|
companyName: org.companyName
|
|
}
|
|
});
|
|
};
|
|
|
|
// Handle Chat with AI navigation for employee reports
|
|
const handleEmployeeChatWithAI = (employeeName: string, employeeReport: EmployeeReport) => {
|
|
const { employeeContext, companyContext } = extractEmployeeReportContext(employeeReport, employeeName, companyReport);
|
|
|
|
navigate('/chat', {
|
|
state: {
|
|
initialContext: employeeContext,
|
|
companyContext: companyContext,
|
|
contextType: 'employee',
|
|
employeeName: employeeName,
|
|
companyName: org?.companyName
|
|
}
|
|
});
|
|
};
|
|
const { employees, reports, submissions, user, isOwner, getFullCompanyReportHistory, generateEmployeeReport, generateCompanyReport, orgId, org } = useOrg();
|
|
const [companyReport, setCompanyReport] = useState<CompanyReport | null>(null);
|
|
const [selectedReport, setSelectedReport] = useState<{ report: CompanyReport | EmployeeReport; type: 'company' | 'employee'; employeeName?: string; employeeEmail?: string } | null>(null);
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [generatingEmployeeReport, setGeneratingEmployeeReport] = useState<string | null>(null);
|
|
const [generatingCompanyReport, setGeneratingCompanyReport] = useState(false);
|
|
|
|
const currentUserIsOwner = isOwner(user?.uid || '');
|
|
|
|
// Get selected employee ID from navigation state (from Submissions page)
|
|
const selectedEmployeeId = location.state?.selectedEmployeeId;
|
|
|
|
console.log('Reports page state:', {
|
|
employeesCount: employees.length,
|
|
reportsCount: Object.keys(reports).length,
|
|
submissionsCount: Object.keys(submissions).length,
|
|
currentUserIsOwner,
|
|
companyReport: !!companyReport,
|
|
selectedReport: selectedReport?.type,
|
|
employeesList: employees.map(emp => ({ id: emp.id, name: emp.name }))
|
|
});
|
|
|
|
const handleGenerateEmployeeReport = async (employee: Employee) => {
|
|
if (generatingEmployeeReport === employee.id) return; // Prevent double-click
|
|
|
|
setGeneratingEmployeeReport(employee.id);
|
|
try {
|
|
console.log('Generating employee report for:', employee.name);
|
|
const newReport = await generateEmployeeReport(employee);
|
|
if (newReport) {
|
|
setSelectedReport({
|
|
report: newReport,
|
|
type: 'employee',
|
|
employeeName: employee.name
|
|
});
|
|
console.log('Employee report generated successfully');
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to generate employee report:', error);
|
|
alert(`Failed to generate report for ${employee.name}. Please try again.`);
|
|
} finally {
|
|
setGeneratingEmployeeReport(null);
|
|
}
|
|
};
|
|
|
|
// Load company report on component mount
|
|
useEffect(() => {
|
|
const loadCompanyReport = async () => {
|
|
try {
|
|
console.log('Loading company report history...');
|
|
const history = await getFullCompanyReportHistory();
|
|
console.log('Company report history loaded:', history.length, 'reports');
|
|
|
|
if (history.length > 0) {
|
|
console.log('Setting existing company report as default');
|
|
setCompanyReport(history[0]);
|
|
// Auto-select company report by default
|
|
setSelectedReport({ report: history[0], type: 'company' });
|
|
} else {
|
|
console.log('No company reports found, setting placeholder report');
|
|
// Create a placeholder that shows generate button
|
|
const placeholderReport = {
|
|
...SAMPLE_COMPANY_REPORT,
|
|
isPlaceholder: true
|
|
} as CompanyReport;
|
|
setCompanyReport(placeholderReport);
|
|
setSelectedReport({ report: placeholderReport, type: 'company' });
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load company report:', error);
|
|
// On error, show placeholder with generate option
|
|
const placeholderReport = {
|
|
...SAMPLE_COMPANY_REPORT,
|
|
isPlaceholder: true
|
|
} as CompanyReport;
|
|
setCompanyReport(placeholderReport);
|
|
setSelectedReport({ report: placeholderReport, type: 'company' });
|
|
}
|
|
};
|
|
loadCompanyReport();
|
|
}, [currentUserIsOwner, getFullCompanyReportHistory, employees, user?.uid]);
|
|
|
|
const handleEmployeeSelect = useCallback(async (employee: Employee) => {
|
|
const employeeReport = reports[employee.id];
|
|
if (employeeReport) {
|
|
setSelectedReport({
|
|
report: employeeReport,
|
|
type: 'employee',
|
|
employeeName: employee.name,
|
|
employeeEmail: employee.email
|
|
});
|
|
} else {
|
|
// FIXED: Only check if employee has submission - do NOT auto-generate
|
|
const hasSubmission = submissions[employee.id];
|
|
if (hasSubmission) {
|
|
// Show placeholder encouraging manual generation
|
|
setSelectedReport({
|
|
report: {
|
|
employeeId: employee.id,
|
|
roleAndOutput: {
|
|
responsibilities: `${employee.name} has completed their questionnaire but no report has been generated yet.`,
|
|
selfRatedOutput: 'Report generation is available. Click "Generate Report" to create it.'
|
|
},
|
|
insights: {
|
|
personalityTraits: 'Report not generated yet. Employee has completed their questionnaire.',
|
|
selfAwareness: '',
|
|
growthDesire: ''
|
|
},
|
|
strengths: ['Report generation available - click Generate Report button'],
|
|
recommendations: ['Generate the report to view detailed analysis and recommendations']
|
|
} as EmployeeReport,
|
|
type: 'employee',
|
|
employeeName: employee.name,
|
|
employeeEmail: employee.email
|
|
});
|
|
} else {
|
|
// No submission available - show message
|
|
setSelectedReport({
|
|
report: {
|
|
employeeId: employee.id,
|
|
roleAndOutput: {
|
|
responsibilities: `${employee.name} has not completed the employee questionnaire yet.`,
|
|
selfRatedOutput: 'No submission data available.'
|
|
},
|
|
insights: {
|
|
personalityTraits: 'Please ask the employee to complete their questionnaire first.',
|
|
selfAwareness: '',
|
|
growthDesire: ''
|
|
},
|
|
strengths: ['Complete questionnaire to view strengths'],
|
|
recommendations: ['Employee should complete the questionnaire first']
|
|
} as EmployeeReport,
|
|
type: 'employee',
|
|
employeeName: employee.name,
|
|
employeeEmail: employee.email
|
|
});
|
|
}
|
|
}
|
|
}, [reports, submissions]); // FIXED: Removed generateEmployeeReport and generatingReports dependencies
|
|
|
|
// Handle navigation from Submissions page
|
|
useEffect(() => {
|
|
if (selectedEmployeeId) {
|
|
const employee = employees.find(emp => emp.id === selectedEmployeeId);
|
|
if (employee) {
|
|
handleEmployeeSelect(employee);
|
|
}
|
|
}
|
|
}, [selectedEmployeeId, employees, handleEmployeeSelect]);
|
|
|
|
// Filter and sort employees
|
|
const visibleEmployees = employees;
|
|
|
|
console.log('Visible employees:', visibleEmployees.length, 'out of', employees.length, 'total employees');
|
|
console.log('Search query:', searchQuery);
|
|
console.log('Current user is owner:', currentUserIsOwner);
|
|
|
|
const handleCompanyReportSelect = () => {
|
|
if (companyReport) {
|
|
setSelectedReport({ report: companyReport, type: 'company' });
|
|
}
|
|
};
|
|
|
|
const handleGenerateCompanyReport = async () => {
|
|
setGeneratingCompanyReport(true);
|
|
try {
|
|
console.log('Generating new company report with current data...');
|
|
const newReport = await generateCompanyReport();
|
|
setCompanyReport(newReport);
|
|
setSelectedReport({ report: newReport, type: 'company' });
|
|
console.log('Company report generated successfully');
|
|
} catch (error) {
|
|
console.error('Error generating company report:', error);
|
|
// Show error message to user
|
|
alert('Failed to generate company report. Please try again.');
|
|
} finally {
|
|
setGeneratingCompanyReport(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{/* Middle Section - Employee List */}
|
|
<div className="h-full flex-1 self-stretch bg-[--Neutrals-NeutralSlate0] flex justify-start items-center">
|
|
<div className="flex-1 self-stretch max-w-64 min-w-64 border-r border-[--Neutrals-NeutralSlate200] inline-flex flex-col justify-start items-start">
|
|
<div className="self-stretch p-5 inline-flex justify-start items-center gap-2.5">
|
|
<div className="flex-1 justify-start text-[--Text-Gray-950] text-base font-medium font-['Inter'] leading-normal">Employees</div>
|
|
</div>
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-2">
|
|
{/* Search */}
|
|
<div className="self-stretch px-2.5 flex flex-col justify-start items-start gap-2">
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-1">
|
|
<div className="self-stretch px-4 py-3 bg-[--Neutrals-NeutralSlate100] rounded-[999px] inline-flex justify-start items-center gap-2 overflow-hidden">
|
|
<div className="relative">
|
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M17.5 17.5L14.5834 14.5833M16.6667 9.58333C16.6667 13.4954 13.4954 16.6667 9.58333 16.6667C5.67132 16.6667 2.5 13.4954 2.5 9.58333C2.5 5.67132 5.67132 2.5 9.58333 2.5C13.4954 2.5 16.6667 5.67132 16.6667 9.58333Z" stroke="var(--Neutrals-NeutralSlate500, #717680)" strokeWidth="1.5" strokeMiterlimit="10" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</div>
|
|
<input
|
|
type="text"
|
|
placeholder="Search"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
className="flex-1 bg-transparent text-[--Text-Gray-500] text-sm font-normal font-['Inter'] leading-tight outline-none"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Employee List */}
|
|
<div className="self-stretch px-3 flex flex-col justify-start items-start">
|
|
{/* Company Report Item - Always show for consistency */}
|
|
{companyReport && (
|
|
<div
|
|
className={`self-stretch p-2 rounded-full shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)] inline-flex justify-start items-center gap-2 overflow-hidden cursor-pointer ${selectedReport?.type === 'company' ? 'bg-[--Neutrals-NeutralSlate100]' : ''
|
|
}`}
|
|
onClick={handleCompanyReportSelect}
|
|
>
|
|
<div className="w-7 h-7 p-1 bg-[--Brand-Orange] rounded-[666.67px] flex justify-center items-center">
|
|
<div className="text-center justify-start text-[--Text-Gray-0] text-xs font-medium font-['Inter'] leading-none">
|
|
{org?.companyName?.charAt(0)?.toUpperCase() || 'C'}
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 justify-start text-[--Text-Gray-950] text-sm font-normal font-['Inter'] leading-tight">
|
|
{org?.companyName || 'Company Report'}
|
|
</div>
|
|
{/* Status indicator for company report */}
|
|
{(companyReport as any)?.isPlaceholder ? (
|
|
<div className="w-3 h-3 bg-gray-300 rounded-full" title="Report not generated yet" />
|
|
) : (
|
|
<div className="w-3 h-3 bg-green-400 rounded-full" title="Report available" />
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* Employee Items */}
|
|
{visibleEmployees.map((employee) => {
|
|
const hasSubmission = submissions[employee.id];
|
|
const hasReport = reports[employee.id];
|
|
const isGenerating = generatingEmployeeReport === employee.id;
|
|
|
|
return (
|
|
<div
|
|
key={employee.id}
|
|
className={`self-stretch p-2 rounded-full shadow-[0px_1px_2px_0px_rgba(10,13,20,0.03)] inline-flex justify-start items-center gap-2 overflow-hidden cursor-pointer ${selectedReport?.type === 'employee' && selectedReport?.employeeEmail === employee.email ? 'bg-[--Neutrals-NeutralSlate100]' : ''
|
|
}`}
|
|
onClick={() => handleEmployeeSelect(employee)}
|
|
>
|
|
<div className={`w-7 h-7 p-1 ${selectedReport?.type === 'employee' && selectedReport?.employeeEmail === employee.email ? 'bg-[--Neutrals-NeutralSlate700] text-[--Neutrals-NeutralSlate0]' : 'bg-[--Neutrals-NeutralSlate100] text-[--Neutrals-NeutralSlate500]'} rounded-[666.67px] flex justify-center items-center relative`}>
|
|
<div className="text-center justify-start text-xs font-medium font-['Inter'] leading-none">
|
|
{employee.initials}
|
|
</div>
|
|
{/* Status indicator */}
|
|
{isGenerating ? (
|
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-yellow-400 rounded-full animate-pulse" title="Generating report..." />
|
|
) : hasReport ? (
|
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-green-400 rounded-full" title="Report available" />
|
|
) : hasSubmission ? (
|
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-blue-400 rounded-full" title="Submission available, click to generate report" />
|
|
) : (
|
|
<div className="absolute -top-1 -right-1 w-3 h-3 bg-gray-300 rounded-full" title="No submission yet" />
|
|
)}
|
|
</div>
|
|
<div className="flex-1 justify-start text-[--Text-Gray-800] text-sm font-normal font-['Inter'] leading-tight">
|
|
{employee.name}
|
|
</div>
|
|
{isGenerating && (
|
|
<div className="w-4 h-4 animate-spin rounded-full border-2 border-[--Neutrals-NeutralSlate300] border-t-[--Brand-Orange]" />
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main Content Area */}
|
|
<div className="flex-1 self-stretch inline-flex flex-col justify-start items-start">
|
|
{selectedReport ? (
|
|
selectedReport.type === 'company' ? (
|
|
<CompanyReportContent
|
|
report={selectedReport.report as CompanyReport}
|
|
onRegenerate={handleGenerateCompanyReport}
|
|
isGenerating={generatingCompanyReport}
|
|
org={org}
|
|
onChatWithAI={handleCompanyChatWithAI}
|
|
/>
|
|
) : (
|
|
(() => {
|
|
const employeeReport = selectedReport.report as EmployeeReport;
|
|
const employeeId = employeeReport.employeeId;
|
|
return (
|
|
<EmployeeReportContent
|
|
report={employeeReport}
|
|
employeeName={selectedReport.employeeName!}
|
|
employeeEmail={selectedReport.employeeEmail!}
|
|
onGenerateReport={() => {
|
|
const employee = employees.find(emp => emp.email === selectedReport.employeeEmail);
|
|
if (employee) handleGenerateEmployeeReport(employee);
|
|
}}
|
|
isGenerating={generatingEmployeeReport === employeeId}
|
|
hasSubmission={!!submissions[employeeId]}
|
|
showGenerateButton={!reports[employeeId] && !!submissions[employeeId]}
|
|
employees={employees}
|
|
org={org}
|
|
onChatWithAI={() => handleEmployeeChatWithAI(selectedReport.employeeName!, employeeReport)}
|
|
/>
|
|
);
|
|
})()
|
|
)
|
|
) : (
|
|
<div className="flex-1 flex items-center justify-center">
|
|
<div className="text-center">
|
|
<h3 className="text-lg font-semibold text-[--Text-Gray-950] mb-2">
|
|
Select a Report
|
|
</h3>
|
|
<p className="text-[--Text-Gray-500]">
|
|
Choose a company or employee report from the list to view details.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
// Component for displaying company report content
|
|
const CompanyReportContent: React.FC<{
|
|
report: CompanyReport;
|
|
onRegenerate: () => void;
|
|
isGenerating: boolean;
|
|
org?: any;
|
|
onChatWithAI?: () => void;
|
|
}> = ({ report, onRegenerate, isGenerating, org, onChatWithAI }) => {
|
|
// Default to the first department in the array
|
|
const [activeDepartmentTab, setActiveDepartmentTab] = useState(() =>
|
|
report?.gradingBreakdown?.[0]?.departmentNameShort || 'Campaigns'
|
|
);
|
|
const [activeImpactSummary, setActiveImpactSummary] = useState(() =>
|
|
report?.organizationalImpactSummary?.[0]?.category || 'Mission Critical'
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{/* Header */}
|
|
<div className="self-stretch px-5 py-3 inline-flex justify-start items-center gap-2.5">
|
|
<div className="flex-1 justify-start text-[--Text-Gray-800] text-base font-medium font-['Inter'] leading-normal">
|
|
Company Report
|
|
</div>
|
|
<div className="flex justify-start items-center gap-3">
|
|
<button
|
|
onClick={onRegenerate}
|
|
disabled={isGenerating}
|
|
className="px-3 py-2.5 bg-[--Neutrals-NeutralSlate100] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-[--Neutrals-NeutralSlate200] transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<div className="relative">
|
|
{isGenerating ? (
|
|
<div className="w-4 h-4 animate-spin rounded-full border-2 border-[--Neutrals-NeutralSlate300] border-t-[--Brand-Orange]" />
|
|
) : (
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M1.33337 8.00001C1.33337 8.00001 3.00004 4.66668 8.00004 4.66668C13 4.66668 14.6667 8.00001 14.6667 8.00001C14.6667 8.00001 13 11.3333 8.00004 11.3333C3.00004 11.3333 1.33337 8.00001 1.33337 8.00001Z" stroke="var(--Neutrals-NeutralSlate800)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
<path d="M8 9.33334C8.73638 9.33334 9.33333 8.73639 9.33333 8.00001C9.33333 7.26363 8.73638 6.66668 8 6.66668C7.26362 6.66668 6.66667 7.26363 6.66667 8.00001C6.66667 8.73639 7.26362 9.33334 8 9.33334Z" stroke="var(--Neutrals-NeutralSlate800)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
)}
|
|
</div>
|
|
<div className="px-1 flex justify-center items-center">
|
|
<div className="justify-center text-[--Text-Gray-800] text-sm font-medium font-['Inter'] leading-tight">
|
|
{isGenerating ? 'Generating...' : 'Refresh Report'}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
<button
|
|
onClick={onChatWithAI}
|
|
className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors"
|
|
>
|
|
<div className="relative">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M14.6667 7.33333C14.6667 10.647 11.9804 13.3333 8.66667 13.3333C7.95 13.3333 7.27333 13.18 6.66667 12.9067L2 14L3.09333 9.33333C2.82 8.72667 2.66667 8.05 2.66667 7.33333C2.66667 4.01967 5.353 1.33333 8.66667 1.33333C11.9804 1.33333 14.6667 4.01967 14.6667 7.33333Z" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
|
<path d="M6 6.66667H6.00667M8.66667 6.66667H8.67333M11.3333 6.66667H11.34" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
</svg>
|
|
</div>
|
|
<div className="px-1 flex justify-center items-center">
|
|
<div className="justify-center text-white text-sm font-medium font-['Inter'] leading-tight">
|
|
Chat with AI
|
|
</div>
|
|
</div>
|
|
</button>
|
|
<FigmaPrimaryButton
|
|
onClick={() => downloadCompanyReportPDF(report, org?.companyName || 'Company')}
|
|
text="Download as PDF"
|
|
size="tiny"
|
|
iconLeft={<DownloadIcon />}
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-4 px-5 pb-6 overflow-y-auto">
|
|
{/* Company Weaknesses */}
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Company Weaknesses</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-[--Neutrals-NeutralSlate100] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-start gap-4">
|
|
{report?.weaknesses?.map((weakness, index) => (
|
|
<div key={index}>
|
|
<div className="self-stretch inline-flex justify-between items-start">
|
|
<div className="w-48 justify-start text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">{weakness.title}:</div>
|
|
<div className="flex-1 justify-start text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">{weakness.description}</div>
|
|
</div>
|
|
{index < (report?.weaknesses?.length || 0) - 1 && (
|
|
<div className="mt-4 self-stretch w-full flex">
|
|
<svg width="100%" height="2" viewBox="0 0 9999 2" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
|
<line x1="0" y1="1" x2="9999" y2="1" stroke="var(--Neutrals-NeutralSlate300)" stroke-width="2" />
|
|
</svg>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)) || (
|
|
<div className="self-stretch inline-flex justify-between items-start">
|
|
<div className="w-48 justify-start text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Lack of Structure:</div>
|
|
<div className="flex-1 justify-start text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">People are confused about their roles. No one knows who's responsible.</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Personnel Changes */}
|
|
{report?.personnelChanges && (
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Personnel Changes</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-[--Neutrals-NeutralSlate50] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-start gap-6">
|
|
{/* New Hires */}
|
|
{report.personnelChanges.newHires && report.personnelChanges.newHires.length > 0 && (
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">New Hires</div>
|
|
{report.personnelChanges.newHires.map((hire, index) => (
|
|
<div key={index} className="self-stretch p-3 bg-green-50 rounded-lg border border-green-200">
|
|
<div className="font-medium text-green-800">{hire.name} - {hire.role}</div>
|
|
<div className="text-sm text-green-600">Department: {hire.department}</div>
|
|
{hire.impact && <div className="text-sm text-green-600 mt-1">{hire.impact}</div>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Promotions */}
|
|
{report.personnelChanges.promotions && report.personnelChanges.promotions.length > 0 && (
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Promotions</div>
|
|
{report.personnelChanges.promotions.map((promotion, index) => (
|
|
<div key={index} className="self-stretch p-3 bg-blue-50 rounded-lg border border-blue-200">
|
|
<div className="font-medium text-blue-800">{promotion.name}</div>
|
|
<div className="text-sm text-blue-600">{promotion.fromRole} → {promotion.toRole}</div>
|
|
{promotion.impact && <div className="text-sm text-blue-600 mt-1">{promotion.impact}</div>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* Departures */}
|
|
{report.personnelChanges.departures && report.personnelChanges.departures.length > 0 && (
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Departures</div>
|
|
{report.personnelChanges.departures.map((departure, index) => (
|
|
<div key={index} className="self-stretch p-3 bg-red-50 rounded-lg border border-red-200">
|
|
<div className="font-medium text-red-800">{departure.name}</div>
|
|
<div className="text-sm text-red-600">Department: {departure.department}</div>
|
|
<div className="text-sm text-red-600">Reason: {departure.reason}</div>
|
|
{departure.impact && <div className="text-sm text-red-600 mt-1">{departure.impact}</div>}
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
{/* No changes message */}
|
|
{(!report.personnelChanges.newHires || report.personnelChanges.newHires.length === 0) &&
|
|
(!report.personnelChanges.promotions || report.personnelChanges.promotions.length === 0) &&
|
|
(!report.personnelChanges.departures || report.personnelChanges.departures.length === 0) && (
|
|
<div className="self-stretch text-[--Text-Gray-500] text-base font-normal font-['Inter'] leading-normal">
|
|
No personnel changes to report.
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Hiring Needs */}
|
|
{report?.immediateHiringNeeds && report.immediateHiringNeeds.length > 0 && (
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Immediate Hiring Needs</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-[--Neutrals-NeutralSlate50] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] inline-flex flex-col justify-start items-start gap-4">
|
|
{report.immediateHiringNeeds.map((need, index) => (
|
|
<React.Fragment key={index}>
|
|
<div key={index} className="self-stretch content-end inline-flex justify-around items-center ">
|
|
<div className="flex-2 w-max p-3 justify-start text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">{need.role} - {need.department}</div>
|
|
<div className="flex-9 justify-start text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">{need.reasoning}</div>
|
|
</div>
|
|
{index !== report.immediateHiringNeeds.length - 1 && (
|
|
<div className="inline-flex self-stretch" data-svg-wrapper>
|
|
<svg width="100%" height="4" viewBox="0 0 9999 5" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M 0,0 H9999 M 0,1 H9999 M 0,2 H9999 M 0,3 H9999 M 0,4 H9999" stroke="var(--Neutrals-NeutralSlate900)" />
|
|
</svg>
|
|
</div>
|
|
)}
|
|
</React.Fragment>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Forward Plan */}
|
|
{report?.forwardOperatingPlan && (
|
|
<div className="w-full self-stretch p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Forward Plan</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-[--Neutrals-NeutralSlate50] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-start gap-4">
|
|
{
|
|
report.forwardOperatingPlan.map((plan, index) => (
|
|
<div key={index} className="self-stretch flex flex-col justify-start items-start gap-4">
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-4">
|
|
<div className="justify-start text-[--Text-Gray-800] text-base font-normal tracking-wide font-['Neue_Montreal'] leading-normal">{plan.title}</div>
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<ul className="list-none space-y-1">
|
|
{plan.details.map((detail, idx) => (
|
|
<li key={idx} className="ps-2 flex items-center gap-3 font-['Neue_Montreal'] tracking-wide">
|
|
<span className="h-1 w-1 rounded-full bg-[--Neutrals-NeutralSlate800] flex-shrink-0"></span>
|
|
<span>{detail}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
{index !== report.forwardOperatingPlan.length - 1 && <div className="self-stretch h-0 outline outline-1 outline-offset-[-0.50px] outline-[--Neutrals-NeutralSlate200]" />}
|
|
</div>
|
|
))
|
|
}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/*
|
|
Strengths goes here
|
|
*/}
|
|
{report?.strengths && (
|
|
<div className="self-stretch p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] inline-flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Strengths</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-[--Neutrals-NeutralSlate50] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-start gap-4">
|
|
{report.strengths.map((strength, idx) => (
|
|
<div key={idx} className="self-stretch inline-flex justify-start items-start gap-4">
|
|
<div className="w-6 h-6 relative rounded-[999px] overflow-hidden">
|
|
<div className="w-6 h-6 left-0 top-0 absolute bg-[--Other-Green] rounded-full" />
|
|
<div data-svg-wrapper className="left-[5px] top-[5px] absolute">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M11.6666 3.5L5.24998 9.91667L2.33331 7" stroke="var(--Neutrals-NeutralSlate0)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 justify-start text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">{strength}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/*
|
|
Organizational Impact Summary
|
|
*/}
|
|
{/**
|
|
{report?.organizationalImpactSummary && (
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1 overflow-hidden">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Organizational Impact Summary</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-[--Neutrals-NeutralSlate50] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-start gap-6">
|
|
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="flex justify-start items-center gap-2">
|
|
<div className="w-3 h-3 bg-red-500 rounded-full"></div>
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Mission Critical</div>
|
|
</div>
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
{report.organizationalImpactSummary.missionCritical.map((employee, index) => (
|
|
<div key={index} className="self-stretch flex justify-between items-start">
|
|
<div className="w-48 text-[--Text-Gray-800] text-sm font-medium font-['Inter'] leading-tight">{employee.employeeName}</div>
|
|
<div className="flex-1 text-[--Text-Gray-800] text-sm font-normal font-['Inter'] leading-tight">{employee.description}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="flex justify-start items-center gap-2">
|
|
<div className="w-3 h-3 bg-orange-500 rounded-full"></div>
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Highly Valuable</div>
|
|
</div>
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
{report.organizationalImpactSummary.highlyValuable.map((employee, index) => (
|
|
<div key={index} className="self-stretch flex justify-between items-start">
|
|
<div className="w-48 text-[--Text-Gray-800] text-sm font-medium font-['Inter'] leading-tight">{employee.employeeName}</div>
|
|
<div className="flex-1 text-[--Text-Gray-800] text-sm font-normal font-['Inter'] leading-tight">{employee.description}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="flex justify-start items-center gap-2">
|
|
<div className="w-3 h-3 bg-yellow-500 rounded-full"></div>
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Core Support</div>
|
|
</div>
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
{report.organizationalImpactSummary.coreSupport.map((employee, index) => (
|
|
<div key={index} className="self-stretch flex justify-between items-start">
|
|
<div className="w-48 text-[--Text-Gray-800] text-sm font-medium font-['Inter'] leading-tight">{employee.employeeName}</div>
|
|
<div className="flex-1 text-[--Text-Gray-800] text-sm font-normal font-['Inter'] leading-tight">{employee.description}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="flex justify-start items-center gap-2">
|
|
<div className="w-3 h-3 bg-green-500 rounded-full"></div>
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Low Criticality</div>
|
|
</div>
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
{report.organizationalImpactSummary.lowCriticality.map((employee, index) => (
|
|
<div key={index} className="self-stretch flex justify-between items-start">
|
|
<div className="w-48 text-[--Text-Gray-800] text-sm font-medium font-['Inter'] leading-tight">{employee.employeeName}</div>
|
|
<div className="flex-1 text-[--Text-Gray-800] text-sm font-normal font-['Inter'] leading-tight">{employee.description}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
**/}
|
|
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Organizational Impact Summary</div>
|
|
</div>
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="p-1 bg-[--Neutrals-NeutralSlate200] rounded-full outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate50] inline-flex justify-start items-center gap-1">
|
|
{/* Department Tabs */}
|
|
{report.organizationalImpactSummary && report.organizationalImpactSummary.map((dept, index) => (
|
|
<div
|
|
key={dept.category}
|
|
className={`px-3 py-1.5 rounded-full flex justify-center items-center gap-1 overflow-hidden cursor-pointer ${activeImpactSummary === dept.category
|
|
? 'bg-[--Neutrals-NeutralSlate0]'
|
|
: 'bg-[--Neutrals-NeutralSlate100] shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)]'
|
|
}`}
|
|
onClick={() => setActiveImpactSummary(dept.category)}
|
|
>
|
|
<div className="px-0.5 flex justify-center items-center">
|
|
<div className={`justify-start text-xs flex items-center font-medium font-['Inter'] leading-none ${activeImpactSummary === dept.category ? 'text-[--Text-Gray-950]' : 'text-zinc-600'
|
|
}`}>
|
|
<span className={`flex-shrink-0 w-2 h-2 rounded-full mr-2
|
|
${dept?.employees.length === 0 ? 'blinkLightEmpty bg-[--Neutrals-NeutralSlate100]' : ''}
|
|
${dept.category === "Mission Critical" ?
|
|
'bg-green-400 blinkLightGreen' :
|
|
dept.category === "Highly Valuable" ?
|
|
'bg-yellow-400 blinkLightYellow' :
|
|
dept.category === "Core Support" ?
|
|
'bg-blue-800 blinkLightBlue' :
|
|
dept.category === "Low Criticality" ?
|
|
'bg-red-600 blinkLightRed' :
|
|
''}
|
|
`}></span>{dept.category}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
{/* Content for the currently selected department */}
|
|
{report.organizationalImpactSummary && (() => {
|
|
const currentImpact = report.organizationalImpactSummary.find(dept => dept.category === activeImpactSummary);
|
|
if (!currentImpact) return null;
|
|
|
|
return (
|
|
<div className="self-stretch p-6 bg-[--Neutrals-NeutralSlate100] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-start gap-4">
|
|
{/* Department Overview Section */}
|
|
{
|
|
currentImpact && currentImpact?.employees.map((item) => (
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-4">
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-4">
|
|
<div className="self-stretch inline-flex justify-start items-center">
|
|
<div className="w-48 justify-start text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal w-fit">{item.employeeName} - {item.impact}</div>
|
|
<div className="flex-1 text-right justify-start text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">{item.suggestedPay}</div>
|
|
</div>
|
|
<div className="self-stretch justify-start text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">{item.description}</div>
|
|
</div>
|
|
<div className="self-stretch h-0 outline outline-1 outline-offset-[-0.50px] outline-[--Neutrals-NeutralSlate200]" />
|
|
</div>
|
|
))
|
|
}
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
</div>
|
|
|
|
|
|
{/* Grading Overview */}
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Grading Overview</div>
|
|
</div>
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-3">
|
|
<div className="p-1 bg-[--Neutrals-NeutralSlate200] rounded-full outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate50] inline-flex justify-start items-center gap-1">
|
|
{/* Department Tabs */}
|
|
{report.gradingBreakdown && report?.gradingBreakdown?.map(dept => (
|
|
<div
|
|
key={dept.departmentNameShort}
|
|
className={`px-3 py-1.5 rounded-full flex justify-center items-center gap-1 overflow-hidden cursor-pointer ${activeDepartmentTab === dept.departmentNameShort
|
|
? 'bg-[--Neutrals-NeutralSlate0]'
|
|
: 'bg-[--Neutrals-NeutralSlate100] shadow-[0px_1px_2px_0px_rgba(16,24,40,0.05)] shadow-[inset_0px_-2px_0px_0px_rgba(10,13,18,0.05)] shadow-[inset_0px_0px_0px_1px_rgba(10,13,18,0.18)]'
|
|
}`}
|
|
onClick={() => setActiveDepartmentTab(dept.departmentNameShort)}
|
|
>
|
|
<div className="px-0.5 flex justify-center items-center">
|
|
<div className={`justify-start text-xs font-medium font-['Inter'] leading-none ${activeDepartmentTab === dept.departmentNameShort ? 'text-[--Text-Gray-950]' : 'text-zinc-600'
|
|
}`}>
|
|
{dept.departmentNameShort}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
{/* Content for the currently selected department */}
|
|
{report.gradingBreakdown && (() => {
|
|
const currentDepartment = report?.gradingBreakdown?.find(dept => dept.departmentNameShort === activeDepartmentTab);
|
|
if (!currentDepartment) return null;
|
|
|
|
return (
|
|
<div className="self-stretch p-6 bg-[--Neutrals-NeutralSlate100] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-start gap-4">
|
|
{/* Department Overview Section */}
|
|
<div className="self-stretch p-5 bg-[--Neutrals-NeutralSlate100] rounded-2xl outline outline-1 outline-offset-[-1px] outline-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-start gap-3 overflow-hidden">
|
|
<div className="self-stretch inline-flex justify-between items-center">
|
|
<div className="w-48 justify-start text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Department Overview</div>
|
|
<div className="pl-3 pr-1 py-1 bg-green-500/10 rounded-2xl flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Gray-800] text-sm font-medium font-['Inter'] leading-tight">Overall Grade</div>
|
|
<div className="px-2 bg-green-500 rounded-2xl flex justify-start items-center">
|
|
<div className="text-center justify-start text-[--Text-Gray-0] text-sm font-medium font-['Inter'] leading-tight">A</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<svg width="752" height="2" viewBox="0 0 752 2" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M0 1H752" stroke="var(--Neutrals-NeutralSlate300)" />
|
|
</svg>
|
|
</div>
|
|
<div className="self-stretch justify-start text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">
|
|
Overall company performance shows strong collaboration and delivery with opportunities for process improvement.
|
|
</div>
|
|
</div>
|
|
|
|
{/* Employee Radar Charts Section */}
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-4">
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal">Team Performance Analysis</div>
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 w-full">
|
|
{currentDepartment.teamScores?.map((teamScore, index) => {
|
|
const radarData = [
|
|
{ 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 (
|
|
<div key={index} className="p-4 bg-[--Neutrals-NeutralSlate50] rounded-lg shadow-sm border border-[--Neutrals-NeutralSlate200] flex flex-col justify-start items-center gap-3">
|
|
<div className="flex flex-col items-center gap-1">
|
|
<h3 className="text-base font-semibold text-[--Text-Gray-800]">{teamScore.employeeName}</h3>
|
|
<div className={`px-2 py-1 rounded-full text-xs font-medium ${teamScore.grade.startsWith('A') ? 'bg-green-100 text-green-800' :
|
|
teamScore.grade.startsWith('B') ? 'bg-blue-100 text-blue-800' :
|
|
teamScore.grade.startsWith('C') ? 'bg-yellow-100 text-yellow-800' :
|
|
'bg-red-100 text-red-800'
|
|
}`}>
|
|
Grade: {teamScore.grade}
|
|
</div>
|
|
</div>
|
|
<div className="w-full h-64">
|
|
<RadarPerformanceChart
|
|
data={radarData}
|
|
height={240}
|
|
color={
|
|
teamScore.grade.startsWith('A') ? 'var(--Brand-Orange)' :
|
|
teamScore.grade.startsWith('B') ? 'var(--color-green)' :
|
|
teamScore.grade.startsWith('C') ? 'var(--color-yellow)' :
|
|
'var(--color-red)'
|
|
}
|
|
/>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})()}
|
|
</div>
|
|
</div>
|
|
</div >
|
|
</>
|
|
);
|
|
};
|
|
|
|
// Component for displaying employee report content
|
|
const EmployeeReportContent: React.FC<{
|
|
report: EmployeeReport;
|
|
employeeName: string;
|
|
employeeEmail: string;
|
|
onGenerateReport?: () => void;
|
|
isGenerating?: boolean;
|
|
hasSubmission?: boolean;
|
|
showGenerateButton?: boolean;
|
|
employees?: Employee[];
|
|
org?: any;
|
|
onChatWithAI?: () => void;
|
|
}> = ({ report, employeeName, onGenerateReport, isGenerating = false, hasSubmission = false, showGenerateButton = false, employees, org, onChatWithAI }) => {
|
|
return (
|
|
<>
|
|
{/* Header */}
|
|
<div className="self-stretch px-5 py-3 inline-flex justify-start items-center gap-2.5">
|
|
<div className="flex-1 justify-start text-[--Text-Gray-800] text-base font-medium font-['Inter'] leading-normal">
|
|
{employeeName}'s Report
|
|
</div>
|
|
<div className="flex justify-start items-center gap-3">
|
|
{/* Generate Report Button - only show when needed */}
|
|
{showGenerateButton && hasSubmission && onGenerateReport && (
|
|
<button
|
|
onClick={onGenerateReport}
|
|
disabled={isGenerating}
|
|
className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
|
>
|
|
<div className="relative">
|
|
{isGenerating ? (
|
|
<div className="w-4 h-4 animate-spin rounded-full border-2 border-white border-t-transparent" />
|
|
) : (
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M8 3V13M3 8H13" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
)}
|
|
</div>
|
|
<div className="px-1 flex justify-center items-center">
|
|
<div className="justify-center text-white text-sm font-medium font-['Inter'] leading-tight">
|
|
{isGenerating ? 'Generating...' : 'Generate Report'}
|
|
</div>
|
|
</div>
|
|
</button>
|
|
)}
|
|
|
|
{/* Chat with AI Button - only show for actual reports */}
|
|
{!showGenerateButton && onChatWithAI && (
|
|
<button
|
|
onClick={onChatWithAI}
|
|
className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] flex justify-center items-center gap-1 overflow-hidden hover:bg-orange-600 transition-colors"
|
|
>
|
|
<div className="relative">
|
|
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M14.6667 7.33333C14.6667 10.647 11.9804 13.3333 8.66667 13.3333C7.95 13.3333 7.27333 13.18 6.66667 12.9067L2 14L3.09333 9.33333C2.82 8.72667 2.66667 8.05 2.66667 7.33333C2.66667 4.01967 5.353 1.33333 8.66667 1.33333C11.9804 1.33333 14.6667 4.01967 14.6667 7.33333Z" stroke="white" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"/>
|
|
<path d="M6 6.66667H6.00667M8.66667 6.66667H8.67333M11.3333 6.66667H11.34" stroke="white" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
|
</svg>
|
|
</div>
|
|
<div className="px-1 flex justify-center items-center">
|
|
<div className="justify-center text-white text-sm font-medium font-['Inter'] leading-tight">
|
|
Chat with AI
|
|
</div>
|
|
</div>
|
|
</button>
|
|
)}
|
|
|
|
{/* Download PDF Button - only show for actual reports */}
|
|
{!showGenerateButton && (
|
|
<FigmaPrimaryButton
|
|
containerExtra='h-9.5'
|
|
text="Download as PDF"
|
|
onClick={() => {
|
|
const employee = employees.find(emp => emp.email === employeeName);
|
|
if (employee) {
|
|
downloadEmployeeReportPDF(employee, report, org?.companyName || 'Company');
|
|
}
|
|
}}
|
|
size="tiny"
|
|
iconLeft={<DownloadIcon />}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="self-stretch flex flex-col justify-start items-start gap-4 px-5 pb-6 overflow-y-auto">
|
|
{/* Role & Responsibilities */}
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Dark-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Role & Responsibilities</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
|
|
<p className="text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">
|
|
{report.roleAndOutput?.responsibilities || 'No role information available.'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Self-Rated Output */}
|
|
{report.roleAndOutput && report.roleAndOutput?.selfRatedOutput && (
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Dark-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Self-Rated Output</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
|
|
<p className="text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">
|
|
{report.roleAndOutput.selfRatedOutput}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Insights */}
|
|
{report.insights && (
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Dark-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Insights & Traits</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
|
|
{report.insights.personalityTraits && (
|
|
<div className="self-stretch">
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal mb-2">Personality Traits</div>
|
|
<p className="text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">
|
|
{report.insights.personalityTraits}
|
|
</p>
|
|
</div>
|
|
)}
|
|
{report.insights.selfAwareness && (
|
|
<div className="self-stretch">
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal mb-2">Self Awareness</div>
|
|
<p className="text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">
|
|
{report.insights.selfAwareness}
|
|
</p>
|
|
</div>
|
|
)}
|
|
{report.insights.growthDesire && (
|
|
<div className="self-stretch">
|
|
<div className="text-[--Text-Gray-800] text-base font-semibold font-['Inter'] leading-normal mb-2">Growth Desire</div>
|
|
<p className="text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">
|
|
{report.insights.growthDesire}
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Strengths */}
|
|
{report.strengths && report.strengths.length > 0 && (
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Dark-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Strengths</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
|
|
{report.strengths.map((strength, index) => (
|
|
<div key={index} className="self-stretch flex justify-start items-start gap-3">
|
|
<div className="w-6 h-6 relative rounded-[999px] overflow-hidden">
|
|
<div className="w-6 h-6 left-0 top-0 absolute bg-Other-Green rounded-full" />
|
|
<div className="left-[5px] top-[5px] absolute">
|
|
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<path d="M11.6666 3.5L5.24998 9.91667L2.33331 7" stroke="var(--Other-Green)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 justify-start text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">{strength}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Recommendations */}
|
|
{report.recommendations && report.recommendations.length > 0 && (
|
|
<div className="w-full p-3 bg-[--Neutrals-NeutralSlate100] rounded-[20px] shadow-[0px_1px_2px_0px_rgba(0,0,0,0.02)] flex flex-col justify-center items-start gap-1">
|
|
<div className="self-stretch px-3 py-2 inline-flex justify-start items-center gap-2">
|
|
<div className="justify-start text-[--Text-Dark-950] text-xl font-medium font-['Neue_Montreal'] leading-normal">Recommendations</div>
|
|
</div>
|
|
<div className="self-stretch p-6 bg-Light-Grays-l-gray08 rounded-2xl outline outline-1 outline-offset-[-1px] outline-Text-Gray-200 flex flex-col justify-start items-start gap-4">
|
|
{report.recommendations.map((recommendation, index) => (
|
|
<div key={index} className="self-stretch text-[--Text-Gray-800] text-base font-normal font-['Inter'] leading-normal">
|
|
<MarkdownRenderer content={recommendation} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default Reports;
|