fix most of the listed bugs

This commit is contained in:
Ra
2025-08-26 11:23:27 -07:00
parent 772d1d4c10
commit ad15aaa35e
22 changed files with 3493 additions and 2375 deletions

View File

@@ -8,11 +8,11 @@ import RadarPerformanceChart from '../components/charts/RadarPerformanceChart';
const Reports: React.FC = () => {
const location = useLocation();
const { employees, reports, user, isOwner, getFullCompanyReportHistory, generateEmployeeReport, generateCompanyReport, orgId } = useOrg();
const { employees, reports, submissions, user, isOwner, getFullCompanyReportHistory, generateEmployeeReport, generateCompanyReport, orgId } = useOrg();
const [companyReport, setCompanyReport] = useState<CompanyReport | null>(null);
const [selectedReport, setSelectedReport] = useState<{ report: CompanyReport | EmployeeReport; type: 'company' | 'employee'; employeeName?: string } | null>(null);
const [searchQuery, setSearchQuery] = useState('');
const [generatingReports, setGeneratingReports] = useState<Set<string>>(new Set());
const [generatingEmployeeReport, setGeneratingEmployeeReport] = useState<string | null>(null);
const [generatingCompanyReport, setGeneratingCompanyReport] = useState(false);
const currentUserIsOwner = isOwner(user?.uid || '');
@@ -20,6 +20,29 @@ const Reports: React.FC = () => {
// Get selected employee ID from navigation state (from Submissions page)
const selectedEmployeeId = location.state?.selectedEmployeeId;
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 () => {
@@ -31,6 +54,9 @@ const Reports: React.FC = () => {
// Auto-select company report by default
setSelectedReport({ report: history[0], type: 'company' });
} else {
// FIXED: No automatic generation - only load existing reports
// Use sample data when no real reports exist
console.log('No company reports found, using sample data. Click "Refresh Report" to generate a new one.');
setCompanyReport(SAMPLE_COMPANY_REPORT);
setSelectedReport({ report: SAMPLE_COMPANY_REPORT, type: 'company' });
}
@@ -42,9 +68,9 @@ const Reports: React.FC = () => {
}
};
loadCompanyReport();
}, [currentUserIsOwner, getFullCompanyReportHistory]);
}, [currentUserIsOwner, getFullCompanyReportHistory]); // FIXED: Removed generateCompanyReport and submissions dependencies
const handleEmployeeSelect = useCallback((employee: Employee) => {
const handleEmployeeSelect = useCallback(async (employee: Employee) => {
const employeeReport = reports[employee.id];
if (employeeReport) {
setSelectedReport({
@@ -52,8 +78,52 @@ const Reports: React.FC = () => {
type: 'employee',
employeeName: employee.name
});
} 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
});
} 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
});
}
}
}, [reports]);
}, [reports, submissions]); // FIXED: Removed generateEmployeeReport and generatingReports dependencies
// Handle navigation from Submissions page
useEffect(() => {
@@ -81,11 +151,15 @@ const Reports: React.FC = () => {
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);
}
@@ -139,23 +213,42 @@ const Reports: React.FC = () => {
)}
{/* Employee Items */}
{visibleEmployees.map((employee) => (
<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?.employeeName === employee.name ? 'bg-[--Neutrals-NeutralSlate100]' : ''
}`}
onClick={() => handleEmployeeSelect(employee)}
>
<div className="w-7 h-7 p-1 bg-[--Neutrals-NeutralSlate100] rounded-[666.67px] flex justify-center items-center">
<div className="text-center justify-start text-[--Neutrals-NeutralSlate500] text-xs font-medium font-['Inter'] leading-none">
{employee.initials}
{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?.employeeName === employee.name ? 'bg-[--Neutrals-NeutralSlate100]' : ''
}`}
onClick={() => handleEmployeeSelect(employee)}
>
<div className="w-7 h-7 p-1 bg-[--Neutrals-NeutralSlate100] rounded-[666.67px] flex justify-center items-center relative">
<div className="text-center justify-start text-[--Neutrals-NeutralSlate500] 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-[--Neutrals-NeutralSlate800] 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 className="flex-1 justify-start text-[--Neutrals-NeutralSlate800] text-sm font-normal font-['Inter'] leading-tight">
{employee.name}
</div>
</div>
))}
);
})}
</div>
</div>
</div>
@@ -170,10 +263,23 @@ const Reports: React.FC = () => {
isGenerating={generatingCompanyReport}
/>
) : (
<EmployeeReportContent
report={selectedReport.report as EmployeeReport}
employeeName={selectedReport.employeeName!}
/>
(() => {
const employeeReport = selectedReport.report as EmployeeReport;
const employeeId = employeeReport.employeeId;
return (
<EmployeeReportContent
report={employeeReport}
employeeName={selectedReport.employeeName!}
onGenerateReport={() => {
const employee = employees.find(emp => emp.name === selectedReport.employeeName);
if (employee) handleGenerateEmployeeReport(employee);
}}
isGenerating={generatingEmployeeReport === employeeId}
hasSubmission={!!submissions[employeeId]}
showGenerateButton={!reports[employeeId] && !!submissions[employeeId]}
/>
);
})()
)
) : (
<div className="flex-1 flex items-center justify-center">
@@ -214,14 +320,37 @@ const CompanyReportContent: React.FC<{
<div className="flex-1 justify-start text-[--Neutrals-NeutralSlate800] text-base font-medium font-['Inter'] leading-normal">
Company Report
</div>
<div className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden">
<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 14H2M12 7.33333L8 11.3333M8 11.3333L4 7.33333M8 11.3333V2" stroke="var(--Neutrals-NeutralSlate0)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-[--Neutrals-NeutralSlate0] text-sm font-medium font-['Inter'] leading-tight">Download as PDF</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-[--Neutrals-NeutralSlate800] text-sm font-medium font-['Inter'] leading-tight">
{isGenerating ? 'Generating...' : 'Refresh Report'}
</div>
</div>
</button>
<div className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden">
<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 14H2M12 7.33333L8 11.3333M8 11.3333L4 7.33333M8 11.3333V2" stroke="var(--Neutrals-NeutralSlate0)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
<div className="px-1 flex justify-center items-center">
<div className="justify-center text-[--Neutrals-NeutralSlate0] text-sm font-medium font-['Inter'] leading-tight">Download as PDF</div>
</div>
</div>
</div>
</div>
@@ -484,7 +613,7 @@ const CompanyReportContent: React.FC<{
<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.map((dept, index) => (
{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
@@ -514,8 +643,8 @@ const CompanyReportContent: React.FC<{
))}
</div>
{/* Content for the currently selected department */}
{(() => {
const currentImpact = report?.organizationalImpactSummary.find(dept => dept.category === activeImpactSummary);
{report.organizationalImpactSummary && (() => {
const currentImpact = report.organizationalImpactSummary.find(dept => dept.category === activeImpactSummary);
if (!currentImpact) return null;
return (
@@ -550,7 +679,7 @@ const CompanyReportContent: React.FC<{
<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?.map(dept => (
{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
@@ -569,7 +698,7 @@ const CompanyReportContent: React.FC<{
))}
</div>
{/* Content for the currently selected department */}
{(() => {
{report.gradingBreakdown && (() => {
const currentDepartment = report?.gradingBreakdown?.find(dept => dept.departmentNameShort === activeDepartmentTab);
if (!currentDepartment) return null;
@@ -652,23 +781,56 @@ const CompanyReportContent: React.FC<{
const EmployeeReportContent: React.FC<{
report: EmployeeReport;
employeeName: string;
}> = ({ report, employeeName }) => {
onGenerateReport?: () => void;
isGenerating?: boolean;
hasSubmission?: boolean;
showGenerateButton?: boolean;
}> = ({ report, employeeName, onGenerateReport, isGenerating = false, hasSubmission = false, showGenerateButton = false }) => {
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-[--Neutrals-NeutralSlate800] text-base font-medium font-['Inter'] leading-normal">
{employeeName}'s Answers
{employeeName}'s Report
</div>
<div className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden">
<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 14H2M12 7.33333L8 11.3333M8 11.3333L4 7.33333M8 11.3333V2" stroke="var(--white, 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">Download as PDF</div>
</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>
)}
{/* Download PDF Button - only show for actual reports */}
{!showGenerateButton && (
<div className="px-3 py-2.5 bg-[--Brand-Orange] rounded-[999px] outline outline-2 outline-offset-[-2px] outline-blue-400 flex justify-center items-center gap-1 overflow-hidden">
<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 14H2M12 7.33333L8 11.3333M8 11.3333L4 7.33333M8 11.3333V2" stroke="var(--white, 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">Download as PDF</div>
</div>
</div>
)}
</div>
</div>
@@ -687,7 +849,7 @@ const EmployeeReportContent: React.FC<{
</div>
{/* Self-Rated Output */}
{report.roleAndOutput?.selfRatedOutput && (
{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 overflow-hidden">
<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>
@@ -748,7 +910,7 @@ const EmployeeReportContent: React.FC<{
<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(--Neutrals-NeutralSlate0, #FDFDFD)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
<path d="M11.6666 3.5L5.24998 9.91667L2.33331 7" stroke="var(--Other-Green)" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</div>
</div>