@burdenoff/microfe-crewfoundry 2026.704.6 → 2026.707.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/crewfoundry/CrewFoundryRoutes.js +51 -43
  2. package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
  3. package/dist/crewfoundry/components/forms/formValues.js +5 -2
  4. package/dist/crewfoundry/components/forms/formValues.js.map +1 -1
  5. package/dist/crewfoundry/components/viz/AllocationBar.js +68 -0
  6. package/dist/crewfoundry/components/viz/AllocationBar.js.map +1 -0
  7. package/dist/crewfoundry/components/viz/AreaChart.js +102 -0
  8. package/dist/crewfoundry/components/viz/AreaChart.js.map +1 -0
  9. package/dist/crewfoundry/components/viz/AvatarStack.js +36 -0
  10. package/dist/crewfoundry/components/viz/AvatarStack.js.map +1 -0
  11. package/dist/crewfoundry/components/viz/BarChart.js +83 -0
  12. package/dist/crewfoundry/components/viz/BarChart.js.map +1 -0
  13. package/dist/crewfoundry/components/viz/CertificateCard.js +118 -0
  14. package/dist/crewfoundry/components/viz/CertificateCard.js.map +1 -0
  15. package/dist/crewfoundry/components/viz/DonutChart.js +109 -0
  16. package/dist/crewfoundry/components/viz/DonutChart.js.map +1 -0
  17. package/dist/crewfoundry/components/viz/HBarFunnel.js +54 -0
  18. package/dist/crewfoundry/components/viz/HBarFunnel.js.map +1 -0
  19. package/dist/crewfoundry/components/viz/Heatmap.js +91 -0
  20. package/dist/crewfoundry/components/viz/Heatmap.js.map +1 -0
  21. package/dist/crewfoundry/components/viz/KanbanBoard.js +50 -0
  22. package/dist/crewfoundry/components/viz/KanbanBoard.js.map +1 -0
  23. package/dist/crewfoundry/components/viz/LineChart.js +123 -0
  24. package/dist/crewfoundry/components/viz/LineChart.js.map +1 -0
  25. package/dist/crewfoundry/components/viz/OrgTree.js +83 -0
  26. package/dist/crewfoundry/components/viz/OrgTree.js.map +1 -0
  27. package/dist/crewfoundry/components/viz/PathSteps.js +68 -0
  28. package/dist/crewfoundry/components/viz/PathSteps.js.map +1 -0
  29. package/dist/crewfoundry/components/viz/ProgressRing.js +68 -0
  30. package/dist/crewfoundry/components/viz/ProgressRing.js.map +1 -0
  31. package/dist/crewfoundry/components/viz/RatingStars.js +51 -0
  32. package/dist/crewfoundry/components/viz/RatingStars.js.map +1 -0
  33. package/dist/crewfoundry/components/viz/SegmentedTabs.js +69 -0
  34. package/dist/crewfoundry/components/viz/SegmentedTabs.js.map +1 -0
  35. package/dist/crewfoundry/components/viz/SkillBar.js +3 -0
  36. package/dist/crewfoundry/components/viz/Sparkline.js +74 -0
  37. package/dist/crewfoundry/components/viz/Sparkline.js.map +1 -0
  38. package/dist/crewfoundry/components/viz/StatCard.js +60 -0
  39. package/dist/crewfoundry/components/viz/StatCard.js.map +1 -0
  40. package/dist/crewfoundry/components/viz/StepProgress.js +3 -0
  41. package/dist/crewfoundry/components/viz/Timeline.js +66 -0
  42. package/dist/crewfoundry/components/viz/Timeline.js.map +1 -0
  43. package/dist/crewfoundry/components/viz/index.js +23 -0
  44. package/dist/crewfoundry/components/viz/lib/Avatar.js +30 -0
  45. package/dist/crewfoundry/components/viz/lib/Avatar.js.map +1 -0
  46. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js +119 -0
  47. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js.map +1 -0
  48. package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js +35 -0
  49. package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js.map +1 -0
  50. package/dist/crewfoundry/components/viz/lib/format.js +21 -0
  51. package/dist/crewfoundry/components/viz/lib/format.js.map +1 -0
  52. package/dist/crewfoundry/components/viz/lib/geometry.js +69 -0
  53. package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -0
  54. package/dist/crewfoundry/components/viz/lib/hooks.js +39 -0
  55. package/dist/crewfoundry/components/viz/lib/hooks.js.map +1 -0
  56. package/dist/crewfoundry/components/viz/lib/palette.js +27 -0
  57. package/dist/crewfoundry/components/viz/lib/palette.js.map +1 -0
  58. package/dist/crewfoundry/lib/analytics.js +323 -0
  59. package/dist/crewfoundry/lib/analytics.js.map +1 -0
  60. package/dist/crewfoundry/lib/companyFinance.js +16 -0
  61. package/dist/crewfoundry/lib/companyFinance.js.map +1 -0
  62. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js +226 -283
  63. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  64. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js +107 -120
  65. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
  66. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +122 -162
  67. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
  68. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +121 -158
  69. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
  70. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +316 -211
  71. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
  72. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +75 -27
  73. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
  74. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +167 -165
  75. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  76. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +168 -109
  77. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
  78. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +401 -290
  79. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
  80. package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js +0 -41
  81. package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
  82. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js +84 -75
  83. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
  84. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +368 -174
  85. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  86. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +236 -201
  87. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
  88. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +369 -196
  89. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
  90. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +269 -270
  91. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
  92. package/dist/crewfoundry/pages/HiringJobsPagePage.js +210 -110
  93. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  94. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +216 -178
  95. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  96. package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js +385 -0
  97. package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js.map +1 -0
  98. package/dist/crewfoundry/pages/HrEmployeesPagePage.js +189 -130
  99. package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
  100. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +397 -241
  101. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  102. package/dist/crewfoundry/pages/HrPayrollPagePage.js +262 -147
  103. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  104. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +345 -167
  105. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  106. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +98 -24
  107. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
  108. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +1 -1
  109. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
  110. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +289 -108
  111. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
  112. package/dist/crewfoundry/pages/LearningCoursesPagePage.js +221 -114
  113. package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
  114. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +134 -135
  115. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
  116. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +148 -97
  117. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
  118. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +264 -157
  119. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
  120. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +85 -93
  121. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
  122. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +211 -122
  123. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  124. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +94 -54
  125. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
  126. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +289 -140
  127. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
  128. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +169 -79
  129. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
  130. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +296 -125
  131. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  132. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js +187 -0
  133. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -0
  134. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js +140 -78
  135. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
  136. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js +93 -43
  137. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
  138. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +250 -98
  139. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  140. package/dist/crewfoundry/store/crewfoundryStore.js +142 -184
  141. package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
  142. package/dist/crewfoundry/utils/cn.js +10 -0
  143. package/dist/crewfoundry/utils/cn.js.map +1 -0
  144. package/dist/crewfoundry/utils/courseProgress.js +46 -0
  145. package/dist/crewfoundry/utils/courseProgress.js.map +1 -0
  146. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  147. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  148. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  149. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  150. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { Activity, BarChart3, Calendar, PieChart, TrendingUp, Users } from 'lucide-react';\nimport {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n ResponsiveTable,\n type ResponsiveTableColumn,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\ntype MonthlyMetric = {\n month: string;\n headcount: number;\n hires: number;\n exits: number;\n};\n\ntype DeptRow = {\n name: string;\n count: number;\n budget: number;\n};\n\ntype FunnelStage = {\n stage: string;\n count: number;\n color: string;\n};\n\nexport function AnalyticsAnalyticsPagePage() {\n const { employees, departments, jobPostings, candidates, projects } = useCrewFoundryStore();\n\n const monthlyMetrics = useMemo<MonthlyMetric[]>(() => {\n if (employees.length === 0) {\n return [];\n }\n const monthKey = (value: string) => {\n const date = new Date(value);\n return `${date.getFullYear()}-${String(date.getMonth()).padStart(2, '0')}`;\n };\n const distinctMonths = Array.from(new Set(employees.map((item) => monthKey(item.startDate))))\n .sort()\n .slice(-6);\n return distinctMonths.map((key) => {\n const [year, month] = key.split('-').map(Number);\n const monthEnd = new Date(year, month + 1, 0).getTime();\n const label = new Date(year, month, 1).toLocaleString('en-US', {\n month: 'short',\n year: '2-digit',\n });\n // Real cumulative headcount as of month end, and real hires/exits in month.\n const headcount = employees.filter(\n (item) => new Date(item.startDate).getTime() <= monthEnd && item.status !== 'terminated'\n ).length;\n const hires = employees.filter((item) => monthKey(item.startDate) === key).length;\n const exits = employees.filter(\n (item) => item.status === 'terminated' && monthKey(item.startDate) === key\n ).length;\n return { month: label, headcount, hires, exits };\n });\n }, [employees]);\n\n const deptDistribution = departments.map((department) => ({\n name: department.name,\n count: department.employeeCount,\n budget: department.budget,\n }));\n\n const maxDeptCount = Math.max(1, ...deptDistribution.map((department) => department.count));\n const maxMetric = Math.max(1, ...monthlyMetrics.map((metric) => metric.headcount));\n\n const sortedDeptDistribution = [...deptDistribution].sort((a, b) => b.count - a.count);\n\n const deptColumns: ResponsiveTableColumn<DeptRow>[] = [\n {\n key: 'name',\n header: 'Department',\n priority: 'primary',\n cell: (department) => <span className=\"font-medium\">{department.name}</span>,\n },\n {\n key: 'count',\n header: 'Headcount',\n priority: 'secondary',\n cell: (department) => <span className=\"md:block md:text-right\">{department.count}</span>,\n },\n {\n key: 'budget',\n header: 'Budget',\n priority: 'secondary',\n cell: (department) => (\n <span className=\"md:block md:text-right\">${(department.budget / 1000000).toFixed(1)}M</span>\n ),\n },\n {\n key: 'distribution',\n header: 'Distribution',\n priority: 'primary',\n cell: (department) => (\n <div className=\"h-3 w-full min-w-24 overflow-hidden rounded bg-bg-sunken\">\n <div\n className=\"h-3 rounded bg-action-primary-bg/70\"\n style={{ width: `${Math.round((department.count / maxDeptCount) * 100)}%` }}\n />\n </div>\n ),\n },\n ];\n\n const hiringFunnel = useMemo<FunnelStage[]>(\n () => [\n {\n stage: 'Applications',\n count: jobPostings.reduce((sum, posting) => sum + posting.applicants, 0),\n color: 'bg-status-info-text',\n },\n {\n stage: 'Screening',\n count: candidates.filter((candidate) => candidate.status === 'screening').length,\n color: 'bg-status-info-text',\n },\n {\n stage: 'Interview',\n count: candidates.filter((candidate) => candidate.status === 'interview').length,\n color: 'bg-action-primary-bg',\n },\n {\n stage: 'Assessment',\n count: candidates.filter((candidate) => candidate.status === 'assessment').length,\n color: 'bg-action-primary-bg',\n },\n {\n stage: 'Offer',\n count: candidates.filter((candidate) => candidate.status === 'offer').length,\n color: 'bg-status-warning-text',\n },\n {\n stage: 'Hired',\n count: candidates.filter((candidate) => candidate.status === 'hired').length,\n color: 'bg-status-success-text',\n },\n ],\n [candidates, jobPostings]\n );\n\n const maxFunnel = Math.max(1, ...hiringFunnel.map((stage) => stage.count));\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Workforce Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Comprehensive analytics across all HR domains\n </p>\n <PagePurpose className=\"mt-1\">\n Your workforce analytics hub — track headcount, performance, and engagement trends here\n to spot what&apos;s working and where your teams need attention.\n </PagePurpose>\n </div>\n <Badge variant=\"outline\" className=\"flex items-center gap-1 self-start\">\n <Calendar className=\"h-3.5 w-3.5\" />\n Rolling 6 months\n </Badge>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Workforce</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{employees.length}</div>\n <div className=\"flex items-center text-xs text-status-success-text\">\n <TrendingUp className=\"mr-1 h-3 w-3\" />\n Live headcount data\n </div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Active Departments</CardTitle>\n <PieChart className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{departments.length}</div>\n <p className=\"text-xs text-text-secondary\">All departments tracked</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Active Projects</CardTitle>\n <Activity className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {projects.filter((project) => project.status === 'active').length}\n </div>\n <p className=\"text-xs text-text-secondary\">\n {projects.filter((project) => project.status === 'completed').length} completed\n </p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Hiring Pipeline</CardTitle>\n <BarChart3 className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{candidates.length}</div>\n <p className=\"text-xs text-text-secondary\">candidates in pipeline</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <Tabs defaultValue=\"overview\">\n <TabsList>\n <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n <TabsTrigger value=\"hiring\">Hiring Funnel</TabsTrigger>\n <TabsTrigger value=\"departments\">Departments</TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"overview\" className=\"space-y-4\">\n <Card>\n <CardHeader>\n <CardTitle>Workforce Growth Trend</CardTitle>\n <CardDescription>Monthly headcount and hiring activity</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {monthlyMetrics.map((metric) => (\n <div\n key={`${metric.month}-${metric.headcount}`}\n className=\"flex items-center gap-3\"\n >\n <span className=\"w-10 text-sm text-text-secondary\">{metric.month}</span>\n <div className=\"h-5 flex-1 overflow-hidden rounded bg-bg-sunken\">\n <div\n className=\"h-5 rounded bg-action-primary-bg\"\n style={{ width: `${Math.round((metric.headcount / maxMetric) * 100)}%` }}\n />\n </div>\n <span className=\"w-10 text-right text-sm font-medium\">{metric.headcount}</span>\n <div className=\"flex w-32 justify-end gap-1\">\n <Badge variant=\"default\" className=\"text-[10px]\">\n +{metric.hires} hired\n </Badge>\n {metric.exits > 0 && (\n <Badge variant=\"destructive\" className=\"text-[10px]\">\n -{metric.exits} exits\n </Badge>\n )}\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </TabsContent>\n\n <TabsContent value=\"hiring\" className=\"space-y-4\">\n <Card>\n <CardHeader>\n <CardTitle>Hiring Funnel</CardTitle>\n <CardDescription>Candidate pipeline stages</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n {hiringFunnel.map((stage) => (\n <div key={stage.stage} className=\"flex items-center gap-3\">\n <span className=\"w-28 text-sm font-medium\">{stage.stage}</span>\n <div className=\"h-6 flex-1 overflow-hidden rounded bg-bg-sunken\">\n <div\n className={`h-6 rounded ${stage.color}`}\n style={{\n width: `${Math.max(Math.round((stage.count / maxFunnel) * 100), 5)}%`,\n }}\n />\n </div>\n <span className=\"w-10 text-right text-sm font-bold\">{stage.count}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n </TabsContent>\n\n <TabsContent value=\"departments\" className=\"space-y-4\">\n <Card>\n <CardHeader>\n <CardTitle>Department Breakdown</CardTitle>\n <CardDescription>Headcount and budget by department</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n columns={deptColumns}\n data={sortedDeptDistribution}\n rowKey={(department) => department.name}\n />\n </CardContent>\n </Card>\n </TabsContent>\n </Tabs>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsAnalyticsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/AnalyticsAnalyticsPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { Activity, BarChart3, Download, PieChart, Users } from 'lucide-react';\nimport {\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\nimport {\n AreaChart,\n BarChart,\n DonutChart,\n HBarFunnel,\n LineChart,\n SegmentedTabs,\n StatCard,\n} from '../components/viz';\nimport type { LabeledValue, Segment } from '../components/viz';\nimport {\n attritionTrend,\n benchAndOverallocated,\n deptHeadcount,\n headcountByMonth,\n momDelta,\n performanceDistribution,\n timeToHire,\n utilizationByPersonWeek,\n} from '../lib/analytics';\n\nconst STAGE_ORDER = ['new', 'screening', 'interview', 'assessment', 'offer', 'hired'] as const;\nconst PERF_LABELS = ['Needs', 'Meets', 'Exceeds', 'Outstanding'];\n\nfunction csvEscape(value: string | number): string {\n return `\"${String(value).replace(/\"/g, '\"\"')}\"`;\n}\n\nexport function AnalyticsAnalyticsPagePage() {\n const { employees, departments, candidates, projects, timeEntries } = useCrewFoundryStore();\n const [periodMonths, setPeriodMonths] = useState<'6' | '12'>('12');\n\n // ── KPI strip ──────────────────────────────────────────────────────────────\n const headcountSeries = useMemo(() => headcountByMonth(employees), [employees]);\n const headcountDelta = momDelta(headcountSeries).deltaPercent;\n const activeProjects = projects.filter((project) => project.status === 'active').length;\n\n // ── Headcount by department ─────────────────────────────────────────────────\n const deptSeries = useMemo<LabeledValue[]>(\n () => deptHeadcount(departments).slice(0, 8),\n [departments]\n );\n\n // ── Hiring funnel conversion (candidates who reached ≥ each stage) ──────────\n const windowedCandidates = useMemo(() => {\n if (candidates.length === 0) {\n return candidates;\n }\n const times = candidates\n .map((candidate) => new Date(candidate.appliedDate).getTime())\n .filter((time) => !Number.isNaN(time));\n if (times.length === 0) {\n return candidates;\n }\n const anchor = Math.max(...times);\n const windowStart = anchor - Number(periodMonths) * 30 * 24 * 60 * 60 * 1000;\n const filtered = candidates.filter((candidate) => {\n const time = new Date(candidate.appliedDate).getTime();\n return Number.isNaN(time) || time >= windowStart;\n });\n return filtered.length > 0 ? filtered : candidates;\n }, [candidates, periodMonths]);\n\n const funnel = useMemo<LabeledValue[]>(() => {\n const rankOf = (status: string) => STAGE_ORDER.indexOf(status as (typeof STAGE_ORDER)[number]);\n const reached = (minRank: number) =>\n windowedCandidates.filter((candidate) => rankOf(candidate.status) >= minRank).length;\n return [\n { label: 'Applied', value: windowedCandidates.length },\n { label: 'Screened', value: reached(1) },\n { label: 'Interviewed', value: reached(2) },\n { label: 'Offer', value: reached(4) },\n { label: 'Hired', value: reached(5) },\n ];\n }, [windowedCandidates]);\n\n // ── Performance distribution histogram ──────────────────────────────────────\n const perfSeries = useMemo<Segment[]>(() => {\n const buckets = performanceDistribution(employees);\n return buckets.map((bucket, index) => ({ label: PERF_LABELS[index], value: bucket.value }));\n }, [employees]);\n\n // ── Attrition trend (monthly exit counts from real Employee.terminationDate) ─\n const attrition = useMemo(() => attritionTrend(employees), [employees]);\n\n // ── Utilization donut (real logged time ÷ 40h capacity) ─────────────────────\n const utilization = useMemo(() => {\n const matrix = utilizationByPersonWeek(timeEntries, employees);\n if (!matrix.hasData) {\n return null;\n }\n const kpis = benchAndOverallocated(matrix, employees);\n const utilized = Math.min(Math.max(kpis.avgUtilization, 0), 100);\n return { utilized, available: Math.max(0, 100 - utilized) };\n }, [timeEntries, employees]);\n\n const utilizationSegments: Segment[] | null = utilization\n ? [\n { label: 'Utilized', value: utilization.utilized },\n { label: 'Available', value: utilization.available, color: 'var(--color-chart-grid)' },\n ]\n : null;\n\n // ── Time-to-hire (avg applied→hired days from real Candidate.hiredDate) ─────\n const timeToHireResult = useMemo(() => timeToHire(candidates), [candidates]);\n\n const exportCsv = () => {\n const lines = ['Section,Label,Value'];\n for (const row of deptSeries) {\n lines.push(`Headcount by department,${csvEscape(row.label)},${row.value}`);\n }\n for (const stage of funnel) {\n lines.push(`Hiring funnel,${csvEscape(stage.label)},${stage.value}`);\n }\n for (const bucket of perfSeries) {\n lines.push(`Performance distribution,${csvEscape(bucket.label)},${bucket.value}`);\n }\n if (utilization) {\n lines.push(`Utilization,Utilized %,${utilization.utilized}`);\n lines.push(`Utilization,Available %,${utilization.available}`);\n }\n const blob = new Blob([lines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = 'workforce-analytics.csv';\n anchor.click();\n URL.revokeObjectURL(url);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Workforce Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Comprehensive analytics across all HR domains\n </p>\n <PagePurpose className=\"mt-1\">\n Your workforce analytics hub — track headcount, hiring conversion, performance, and\n utilization here to spot what&apos;s working and where your teams need attention.\n </PagePurpose>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <SegmentedTabs\n aria-label=\"Reporting period\"\n tabs={[\n { id: '6', label: 'Last 6 months' },\n { id: '12', label: 'Last 12 months' },\n ]}\n value={periodMonths}\n onChange={(id) => setPeriodMonths(id === '6' ? '6' : '12')}\n />\n <Button variant=\"outline\" size=\"sm\" onClick={exportCsv}>\n <Download className=\"mr-1 h-4 w-4\" />\n Export\n </Button>\n </div>\n </div>\n\n {/* KPI strip */}\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Workforce\"\n value={employees.length}\n delta={headcountDelta || undefined}\n deltaLabel=\"from last month\"\n icon={<Users className=\"h-4 w-4\" />}\n sparkline={headcountSeries.map((point) => point.value)}\n sparklineVariant=\"area\"\n />\n <StatCard\n label=\"Active Departments\"\n value={departments.length}\n icon={<PieChart className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Active Projects\"\n value={activeProjects}\n icon={<Activity className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Hiring Pipeline\"\n value={candidates.length}\n icon={<BarChart3 className=\"h-4 w-4\" />}\n />\n </div>\n\n {/* Six-card analytics grid */}\n <div className=\"grid gap-4 lg:grid-cols-3\">\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Headcount by department</CardTitle>\n </CardHeader>\n <CardContent>\n {deptSeries.length > 0 ? (\n <BarChart series={deptSeries} colorful height={230} title=\"Headcount by department\" />\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No departments yet\"\n description=\"Department headcount appears once departments exist.\"\n />\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Hiring funnel conversion</CardTitle>\n </CardHeader>\n <CardContent>\n {windowedCandidates.length > 0 ? (\n <HBarFunnel stages={funnel} valueFormatter={(value) => `${value}`} showConversion />\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No candidates yet\"\n description=\"The hiring funnel and stage-to-stage conversion appear once candidates apply.\"\n />\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Performance distribution</CardTitle>\n </CardHeader>\n <CardContent>\n {employees.length > 0 ? (\n <BarChart series={perfSeries} height={230} title=\"Performance score distribution\" />\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No employees yet\"\n description=\"Performance bands appear once employees have scores.\"\n />\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Attrition trend</CardTitle>\n </CardHeader>\n <CardContent>\n {attrition.length > 0 ? (\n <LineChart\n series={attrition}\n height={230}\n valueFormatter={(v) => `${v}`}\n title=\"Employee exits by month\"\n />\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No exits recorded\"\n description=\"Monthly attrition appears once employees have termination dates. Nothing is shown while no exits are on record — estimated exits are never charted.\"\n />\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Utilization</CardTitle>\n </CardHeader>\n <CardContent>\n {utilizationSegments ? (\n <DonutChart\n segments={utilizationSegments}\n centerLabel={`${utilization?.utilized ?? 0}%`}\n centerCaption=\"Utilized\"\n valueFormatter={(value) => `${value}%`}\n />\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No logged time yet\"\n description=\"Utilization is computed from real logged hours against a 40h weekly capacity.\"\n />\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Time-to-hire</CardTitle>\n </CardHeader>\n <CardContent>\n {timeToHireResult ? (\n <StatCard\n label=\"Average time-to-hire\"\n value={`${timeToHireResult.averageDays} days`}\n deltaLabel={`from ${timeToHireResult.sampleSize} hires`}\n />\n ) : (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No hires recorded yet\"\n description=\"Average time-to-hire appears once candidates carry a hired date. The elapsed applied-to-hired time is measured, never estimated.\"\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Headcount growth (real cumulative series) */}\n {headcountSeries.length > 1 && (\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Headcount growth</CardTitle>\n </CardHeader>\n <CardContent>\n <AreaChart series={headcountSeries} height={220} title=\"Cumulative headcount by month\" />\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,12 +1,14 @@
1
1
  import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
2
- import { useMemo as t } from "react";
3
- import { Award as n, BarChart3 as r, Clock as i, MapPin as a, Star as o, UserCheck as s } from "lucide-react";
4
- import { jsx as c, jsxs as l } from "react/jsx-runtime";
5
- import { Badge as u, Card as d, CardContent as f, CardDescription as p, CardHeader as m, CardTitle as h, GlassCard as g, GradientText as _, IllustratedEmptyState as v, PagePurpose as y, ResponsiveTable as b } from "@burdenoff/fe-libs/ui";
2
+ import { BarChart as t } from "../components/viz/BarChart.js";
3
+ import "../components/viz/index.js";
4
+ import { useMemo as n } from "react";
5
+ import { Award as r, BarChart3 as i, Clock as a, MapPin as o, Star as s, UserCheck as c } from "lucide-react";
6
+ import { jsx as l, jsxs as u } from "react/jsx-runtime";
7
+ import { Badge as d, Card as f, CardContent as p, CardDescription as m, CardHeader as h, CardTitle as g, GlassCard as _, GradientText as v, IllustratedEmptyState as y, PagePurpose as b, ResponsiveTable as x } from "@burdenoff/fe-libs/ui";
6
8
  //#region src/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.tsx
7
- function x() {
8
- let { employees: x, departments: S } = e(), C = t(() => {
9
- let e = Math.max(x.length, 1), t = (e) => Math.max(0, ((/* @__PURE__ */ new Date("2026-03-01")).getTime() - new Date(e).getTime()) / (1e3 * 60 * 60 * 24 * 365));
9
+ function S() {
10
+ let { employees: S, departments: C } = e(), w = n(() => {
11
+ let e = Math.max(S.length, 1), t = (e) => Math.max(0, ((/* @__PURE__ */ new Date("2026-03-01")).getTime() - new Date(e).getTime()) / (1e3 * 60 * 60 * 24 * 365));
10
12
  return [
11
13
  {
12
14
  range: "< 1 year",
@@ -34,7 +36,7 @@ function x() {
34
36
  max: Infinity
35
37
  }
36
38
  ].map((n) => {
37
- let r = x.filter((e) => {
39
+ let r = S.filter((e) => {
38
40
  let r = t(e.startDate);
39
41
  return r >= n.min && r < n.max;
40
42
  }).length;
@@ -44,30 +46,33 @@ function x() {
44
46
  percentage: Math.round(r / e * 100)
45
47
  };
46
48
  });
47
- }, [x]), { avgTenure: w, medianTenure: T } = t(() => {
48
- if (x.length === 0) return {
49
+ }, [S]), { avgTenure: T, medianTenure: E } = n(() => {
50
+ if (S.length === 0) return {
49
51
  avgTenure: 0,
50
52
  medianTenure: 0
51
53
  };
52
- let e = (e) => Math.max(0, ((/* @__PURE__ */ new Date("2026-03-01")).getTime() - new Date(e).getTime()) / (1e3 * 60 * 60 * 24 * 365)), t = x.map((t) => e(t.startDate)).sort((e, t) => e - t), n = t.reduce((e, t) => e + t, 0) / t.length, r = Math.floor(t.length / 2);
54
+ let e = (e) => Math.max(0, ((/* @__PURE__ */ new Date("2026-03-01")).getTime() - new Date(e).getTime()) / (1e3 * 60 * 60 * 24 * 365)), t = S.map((t) => e(t.startDate)).sort((e, t) => e - t), n = t.reduce((e, t) => e + t, 0) / t.length, r = Math.floor(t.length / 2);
53
55
  return {
54
56
  avgTenure: n,
55
57
  medianTenure: t.length % 2 == 1 ? t[r] : (t[r - 1] + t[r]) / 2
56
58
  };
57
- }, [x]), E = (x.reduce((e, t) => e + t.performanceScore, 0) / Math.max(x.length, 1)).toFixed(1), D = Math.round(x.reduce((e, t) => e + t.salary, 0) / Math.max(x.length, 1)), O = x.filter((e) => e.status === "active").length, k = x.filter((e) => e.status === "on_leave").length, A = S.map((e) => {
58
- let t = x.filter((t) => t.department === e.name), n = t.length > 0 ? (t.reduce((e, t) => e + t.performanceScore, 0) / t.length).toFixed(1) : "0", r = t.length > 0 ? Math.round(t.reduce((e, t) => e + t.salary, 0) / t.length) : 0;
59
+ }, [S]), D = w.map((e) => ({
60
+ label: e.range.replace(" years", "y").replace(" year", "y"),
61
+ value: e.count
62
+ })), O = (S.reduce((e, t) => e + t.performanceScore, 0) / Math.max(S.length, 1)).toFixed(1), k = Math.round(S.reduce((e, t) => e + t.salary, 0) / Math.max(S.length, 1)), A = S.filter((e) => e.status === "active").length, j = S.filter((e) => e.status === "on_leave").length, M = C.map((e) => {
63
+ let t = S.filter((t) => t.department === e.name), n = t.length > 0 ? (t.reduce((e, t) => e + t.performanceScore, 0) / t.length).toFixed(1) : "0", r = t.length > 0 ? Math.round(t.reduce((e, t) => e + t.salary, 0) / t.length) : 0;
59
64
  return {
60
65
  name: e.name,
61
66
  headcount: t.length,
62
67
  avgScore: n,
63
68
  avgSalary: r
64
69
  };
65
- }), j = [...A].sort((e, t) => Number(t.avgScore) - Number(e.avgScore)), M = j[0], N = x.filter((e) => e.performanceScore < 3.5).length, P = A.filter((e) => e.headcount > 0), F = [...P].map((e) => e.avgSalary).sort((e, t) => e - t), I = F.length > 0 ? F[Math.floor(F.length / 2)] : 0, L = P.filter((e) => e.avgSalary < I).length, R = [
70
+ }), N = [...M].sort((e, t) => Number(t.avgScore) - Number(e.avgScore)), P = N[0], F = S.filter((e) => e.performanceScore < 3.5).length, I = M.filter((e) => e.headcount > 0), L = [...I].map((e) => e.avgSalary).sort((e, t) => e - t), R = L.length > 0 ? L[Math.floor(L.length / 2)] : 0, z = I.filter((e) => e.avgSalary < R).length, B = [
66
71
  {
67
72
  key: "name",
68
73
  header: "Department",
69
74
  priority: "primary",
70
- cell: (e) => /* @__PURE__ */ c("span", {
75
+ cell: (e) => /* @__PURE__ */ l("span", {
71
76
  className: "font-medium",
72
77
  children: e.name
73
78
  })
@@ -76,7 +81,7 @@ function x() {
76
81
  key: "headcount",
77
82
  header: "Headcount",
78
83
  priority: "secondary",
79
- cell: (e) => /* @__PURE__ */ c("span", {
84
+ cell: (e) => /* @__PURE__ */ l("span", {
80
85
  className: "md:block md:text-right",
81
86
  children: e.headcount
82
87
  })
@@ -85,9 +90,9 @@ function x() {
85
90
  key: "avgScore",
86
91
  header: "Avg Score",
87
92
  priority: "primary",
88
- cell: (e) => /* @__PURE__ */ c("span", {
93
+ cell: (e) => /* @__PURE__ */ l("span", {
89
94
  className: "md:block md:text-right",
90
- children: /* @__PURE__ */ c(u, {
95
+ children: /* @__PURE__ */ l(d, {
91
96
  variant: Number(e.avgScore) >= 4.5 ? "default" : "secondary",
92
97
  children: e.avgScore
93
98
  })
@@ -97,223 +102,205 @@ function x() {
97
102
  key: "avgSalary",
98
103
  header: "Avg Salary",
99
104
  priority: "secondary",
100
- cell: (e) => /* @__PURE__ */ l("span", {
105
+ cell: (e) => /* @__PURE__ */ u("span", {
101
106
  className: "md:block md:text-right",
102
107
  children: ["$", e.avgSalary.toLocaleString()]
103
108
  })
104
109
  }
105
- ], z = x.reduce((e, t) => (e[t.location] = (e[t.location] || 0) + 1, e), {});
106
- return /* @__PURE__ */ l("div", {
110
+ ], V = S.reduce((e, t) => (e[t.location] = (e[t.location] || 0) + 1, e), {});
111
+ return /* @__PURE__ */ u("div", {
107
112
  className: "space-y-6 p-6",
108
113
  children: [
109
- /* @__PURE__ */ c("div", {
114
+ /* @__PURE__ */ l("div", {
110
115
  className: "flex items-center justify-between",
111
- children: /* @__PURE__ */ l("div", { children: [
112
- /* @__PURE__ */ c("h1", {
116
+ children: /* @__PURE__ */ u("div", { children: [
117
+ /* @__PURE__ */ l("h1", {
113
118
  className: "text-2xl font-semibold",
114
- children: /* @__PURE__ */ c(_, { children: "Employee Analytics" })
119
+ children: /* @__PURE__ */ l(v, { children: "Employee Analytics" })
115
120
  }),
116
- /* @__PURE__ */ c("p", {
121
+ /* @__PURE__ */ l("p", {
117
122
  className: "text-sm text-text-secondary",
118
123
  children: "Deep-dive into employee performance, engagement, and demographics"
119
124
  }),
120
- /* @__PURE__ */ c(y, {
125
+ /* @__PURE__ */ l(b, {
121
126
  className: "mt-1",
122
127
  children: "See how individual employees and teams are performing over time — use these insights for reviews, recognition, and targeted coaching."
123
128
  })
124
129
  ] })
125
130
  }),
126
- /* @__PURE__ */ l("div", {
131
+ /* @__PURE__ */ u("div", {
127
132
  className: "grid gap-4 md:grid-cols-2 lg:grid-cols-4",
128
133
  children: [
129
- /* @__PURE__ */ l(g, {
134
+ /* @__PURE__ */ u(_, {
130
135
  treatment: "glass",
131
136
  glow: !0,
132
137
  className: "border-border-seam",
133
- children: [/* @__PURE__ */ l(m, {
138
+ children: [/* @__PURE__ */ u(h, {
134
139
  className: "flex flex-row items-center justify-between space-y-0 pb-2",
135
- children: [/* @__PURE__ */ c(h, {
140
+ children: [/* @__PURE__ */ l(g, {
136
141
  className: "text-sm font-medium",
137
142
  children: "Active Employees"
138
- }), /* @__PURE__ */ c(s, { className: "h-4 w-4 text-text-secondary" })]
139
- }), /* @__PURE__ */ l(f, { children: [/* @__PURE__ */ c("div", {
143
+ }), /* @__PURE__ */ l(c, { className: "h-4 w-4 text-text-secondary" })]
144
+ }), /* @__PURE__ */ u(p, { children: [/* @__PURE__ */ l("div", {
140
145
  className: "text-2xl font-bold tabular-nums",
141
- children: O
142
- }), /* @__PURE__ */ l("p", {
146
+ children: A
147
+ }), /* @__PURE__ */ u("p", {
143
148
  className: "text-xs text-text-secondary",
144
- children: [k, " currently on leave"]
149
+ children: [j, " currently on leave"]
145
150
  })] })]
146
151
  }),
147
- /* @__PURE__ */ l(g, {
152
+ /* @__PURE__ */ u(_, {
148
153
  treatment: "metric",
149
154
  className: "border-border-seam",
150
- children: [/* @__PURE__ */ l(m, {
155
+ children: [/* @__PURE__ */ u(h, {
151
156
  className: "flex flex-row items-center justify-between space-y-0 pb-2",
152
- children: [/* @__PURE__ */ c(h, {
157
+ children: [/* @__PURE__ */ l(g, {
153
158
  className: "text-sm font-medium",
154
159
  children: "Avg Performance"
155
- }), /* @__PURE__ */ c(o, { className: "h-4 w-4 text-text-secondary" })]
156
- }), /* @__PURE__ */ l(f, { children: [/* @__PURE__ */ l("div", {
160
+ }), /* @__PURE__ */ l(s, { className: "h-4 w-4 text-text-secondary" })]
161
+ }), /* @__PURE__ */ u(p, { children: [/* @__PURE__ */ u("div", {
157
162
  className: "text-2xl font-bold tabular-nums",
158
- children: [E, "/5.0"]
159
- }), /* @__PURE__ */ l("p", {
163
+ children: [O, "/5.0"]
164
+ }), /* @__PURE__ */ u("p", {
160
165
  className: "text-xs text-text-secondary",
161
166
  children: [
162
167
  "Across ",
163
- x.length,
168
+ S.length,
164
169
  " employees"
165
170
  ]
166
171
  })] })]
167
172
  }),
168
- /* @__PURE__ */ l(g, {
173
+ /* @__PURE__ */ u(_, {
169
174
  treatment: "metric",
170
175
  className: "border-border-seam",
171
- children: [/* @__PURE__ */ l(m, {
176
+ children: [/* @__PURE__ */ u(h, {
172
177
  className: "flex flex-row items-center justify-between space-y-0 pb-2",
173
- children: [/* @__PURE__ */ c(h, {
178
+ children: [/* @__PURE__ */ l(g, {
174
179
  className: "text-sm font-medium",
175
180
  children: "Avg Compensation"
176
- }), /* @__PURE__ */ c(r, { className: "h-4 w-4 text-text-secondary" })]
177
- }), /* @__PURE__ */ l(f, { children: [/* @__PURE__ */ l("div", {
181
+ }), /* @__PURE__ */ l(i, { className: "h-4 w-4 text-text-secondary" })]
182
+ }), /* @__PURE__ */ u(p, { children: [/* @__PURE__ */ u("div", {
178
183
  className: "text-2xl font-bold tabular-nums",
179
- children: ["$", D.toLocaleString()]
180
- }), /* @__PURE__ */ c("p", {
184
+ children: ["$", k.toLocaleString()]
185
+ }), /* @__PURE__ */ l("p", {
181
186
  className: "text-xs text-text-secondary",
182
187
  children: "Across all departments"
183
188
  })] })]
184
189
  }),
185
- /* @__PURE__ */ l(g, {
190
+ /* @__PURE__ */ u(_, {
186
191
  treatment: "metric",
187
192
  className: "border-border-seam",
188
- children: [/* @__PURE__ */ l(m, {
193
+ children: [/* @__PURE__ */ u(h, {
189
194
  className: "flex flex-row items-center justify-between space-y-0 pb-2",
190
- children: [/* @__PURE__ */ c(h, {
195
+ children: [/* @__PURE__ */ l(g, {
191
196
  className: "text-sm font-medium",
192
197
  children: "Avg Tenure"
193
- }), /* @__PURE__ */ c(i, { className: "h-4 w-4 text-text-secondary" })]
194
- }), /* @__PURE__ */ l(f, { children: [/* @__PURE__ */ l("div", {
198
+ }), /* @__PURE__ */ l(a, { className: "h-4 w-4 text-text-secondary" })]
199
+ }), /* @__PURE__ */ u(p, { children: [/* @__PURE__ */ u("div", {
195
200
  className: "text-2xl font-bold tabular-nums",
196
- children: [w.toFixed(1), " yrs"]
197
- }), /* @__PURE__ */ l("p", {
201
+ children: [T.toFixed(1), " yrs"]
202
+ }), /* @__PURE__ */ u("p", {
198
203
  className: "text-xs text-text-secondary",
199
204
  children: [
200
205
  "Median: ",
201
- T.toFixed(1),
206
+ E.toFixed(1),
202
207
  " years"
203
208
  ]
204
209
  })] })]
205
210
  })
206
211
  ]
207
212
  }),
208
- /* @__PURE__ */ l("div", {
213
+ /* @__PURE__ */ u("div", {
209
214
  className: "grid gap-4 md:grid-cols-2",
210
- children: [/* @__PURE__ */ l(d, { children: [/* @__PURE__ */ l(m, { children: [/* @__PURE__ */ c(h, { children: "Department Performance" }), /* @__PURE__ */ c(p, { children: "Average performance score and compensation by department" })] }), /* @__PURE__ */ c(f, { children: /* @__PURE__ */ c(b, {
211
- columns: R,
212
- data: j,
215
+ children: [/* @__PURE__ */ u(f, { children: [/* @__PURE__ */ u(h, { children: [/* @__PURE__ */ l(g, { children: "Department Performance" }), /* @__PURE__ */ l(m, { children: "Average performance score and compensation by department" })] }), /* @__PURE__ */ l(p, { children: /* @__PURE__ */ l(x, {
216
+ columns: B,
217
+ data: N,
213
218
  rowKey: (e) => e.name
214
- }) })] }), /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ l(m, { children: [/* @__PURE__ */ c(h, { children: "Engagement Drivers" }), /* @__PURE__ */ c(p, { children: "Employee satisfaction by category" })] }), /* @__PURE__ */ c(f, { children: /* @__PURE__ */ c(v, {
219
+ }) })] }), /* @__PURE__ */ u(f, { children: [/* @__PURE__ */ u(h, { children: [/* @__PURE__ */ l(g, { children: "Engagement Drivers" }), /* @__PURE__ */ l(m, { children: "Employee satisfaction by category" })] }), /* @__PURE__ */ l(p, { children: /* @__PURE__ */ l(y, {
215
220
  illustration: "empty-data",
216
221
  title: "Engagement surveys coming soon",
217
222
  description: "Satisfaction and engagement drivers will appear here once the employee survey integration is connected — we don't show estimated scores."
218
223
  }) })] })]
219
224
  }),
220
- /* @__PURE__ */ l("div", {
225
+ /* @__PURE__ */ u("div", {
221
226
  className: "grid gap-4 md:grid-cols-2",
222
- children: [/* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(m, { children: /* @__PURE__ */ l(h, {
227
+ children: [/* @__PURE__ */ u(f, { children: [/* @__PURE__ */ l(h, { children: /* @__PURE__ */ u(g, {
223
228
  className: "flex items-center gap-2",
224
- children: [/* @__PURE__ */ c(i, { className: "h-4 w-4" }), "Tenure Distribution"]
225
- }) }), /* @__PURE__ */ c(f, {
226
- className: "space-y-3",
227
- children: C.map((e) => /* @__PURE__ */ l("div", {
228
- className: "flex items-center gap-3",
229
- children: [
230
- /* @__PURE__ */ c("span", {
231
- className: "w-20 text-sm text-text-secondary",
232
- children: e.range
233
- }),
234
- /* @__PURE__ */ c("div", {
235
- className: "flex-1 h-4 bg-bg-sunken rounded overflow-hidden",
236
- children: /* @__PURE__ */ c("div", {
237
- className: "h-4 bg-action-primary-bg/70 rounded",
238
- style: { width: `${e.percentage}%` }
239
- })
240
- }),
241
- /* @__PURE__ */ c("span", {
242
- className: "text-sm font-medium w-10 text-right",
243
- children: e.count
244
- }),
245
- /* @__PURE__ */ l("span", {
246
- className: "text-xs text-text-secondary w-10 text-right",
247
- children: [e.percentage, "%"]
248
- })
249
- ]
250
- }, e.range))
251
- })] }), /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(m, { children: /* @__PURE__ */ l(h, {
229
+ children: [/* @__PURE__ */ l(a, { className: "h-4 w-4" }), "Tenure Distribution"]
230
+ }) }), /* @__PURE__ */ l(p, { children: S.length > 0 ? /* @__PURE__ */ l(t, {
231
+ series: D,
232
+ colorful: !0,
233
+ height: 220,
234
+ title: "Employee tenure distribution"
235
+ }) : /* @__PURE__ */ l("p", {
236
+ className: "py-6 text-center text-sm text-text-secondary",
237
+ children: "No employees yet."
238
+ }) })] }), /* @__PURE__ */ u(f, { children: [/* @__PURE__ */ l(h, { children: /* @__PURE__ */ u(g, {
252
239
  className: "flex items-center gap-2",
253
- children: [/* @__PURE__ */ c(a, { className: "h-4 w-4" }), "Location Distribution"]
254
- }) }), /* @__PURE__ */ c(f, {
240
+ children: [/* @__PURE__ */ l(o, { className: "h-4 w-4" }), "Location Distribution"]
241
+ }) }), /* @__PURE__ */ l(p, {
255
242
  className: "space-y-3",
256
- children: Object.entries(z).sort(([, e], [, t]) => t - e).map(([e, t]) => /* @__PURE__ */ l("div", {
243
+ children: Object.entries(V).sort(([, e], [, t]) => t - e).map(([e, t]) => /* @__PURE__ */ u("div", {
257
244
  className: "flex items-center justify-between py-1",
258
- children: [/* @__PURE__ */ c("span", {
245
+ children: [/* @__PURE__ */ l("span", {
259
246
  className: "text-sm",
260
247
  children: e
261
- }), /* @__PURE__ */ l("div", {
248
+ }), /* @__PURE__ */ u("div", {
262
249
  className: "flex items-center gap-2",
263
- children: [/* @__PURE__ */ c("span", {
250
+ children: [/* @__PURE__ */ l("span", {
264
251
  className: "text-sm font-medium",
265
252
  children: t
266
- }), /* @__PURE__ */ l(u, {
253
+ }), /* @__PURE__ */ u(d, {
267
254
  variant: "outline",
268
255
  className: "text-[10px]",
269
- children: [Math.round(t / Math.max(x.length, 1) * 100), "%"]
256
+ children: [Math.round(t / Math.max(S.length, 1) * 100), "%"]
270
257
  })]
271
258
  })]
272
259
  }, e))
273
260
  })] })]
274
261
  }),
275
- /* @__PURE__ */ l(d, { children: [/* @__PURE__ */ c(m, { children: /* @__PURE__ */ l(h, {
262
+ /* @__PURE__ */ u(f, { children: [/* @__PURE__ */ l(h, { children: /* @__PURE__ */ u(g, {
276
263
  className: "flex items-center gap-2",
277
- children: [/* @__PURE__ */ c(n, { className: "h-4 w-4 text-status-warning-text" }), "Key Insights"]
278
- }) }), /* @__PURE__ */ c(f, { children: /* @__PURE__ */ l("div", {
264
+ children: [/* @__PURE__ */ l(r, { className: "h-4 w-4 text-status-warning-text" }), "Key Insights"]
265
+ }) }), /* @__PURE__ */ l(p, { children: /* @__PURE__ */ u("div", {
279
266
  className: "grid gap-3 md:grid-cols-3",
280
267
  children: [
281
- /* @__PURE__ */ l("div", {
268
+ /* @__PURE__ */ u("div", {
282
269
  className: "rounded border p-3",
283
- children: [/* @__PURE__ */ c("p", {
270
+ children: [/* @__PURE__ */ l("p", {
284
271
  className: "text-sm font-medium",
285
272
  children: "Highest Performance"
286
- }), /* @__PURE__ */ c("p", {
273
+ }), /* @__PURE__ */ l("p", {
287
274
  className: "text-xs text-text-secondary mt-1",
288
- children: M ? `${M.name} leads with an average score of ${M.avgScore}` : "No department performance data yet"
275
+ children: P ? `${P.name} leads with an average score of ${P.avgScore}` : "No department performance data yet"
289
276
  })]
290
277
  }),
291
- /* @__PURE__ */ l("div", {
278
+ /* @__PURE__ */ u("div", {
292
279
  className: "rounded border p-3",
293
- children: [/* @__PURE__ */ c("p", {
280
+ children: [/* @__PURE__ */ l("p", {
294
281
  className: "text-sm font-medium",
295
282
  children: "Retention Risk"
296
- }), /* @__PURE__ */ l("p", {
283
+ }), /* @__PURE__ */ u("p", {
297
284
  className: "text-xs text-text-secondary mt-1",
298
285
  children: [
299
- N,
286
+ F,
300
287
  " employee",
301
- N === 1 ? "" : "s",
288
+ F === 1 ? "" : "s",
302
289
  " below a 3.5 performance score — a potential flight risk"
303
290
  ]
304
291
  })]
305
292
  }),
306
- /* @__PURE__ */ l("div", {
293
+ /* @__PURE__ */ u("div", {
307
294
  className: "rounded border p-3",
308
- children: [/* @__PURE__ */ c("p", {
295
+ children: [/* @__PURE__ */ l("p", {
309
296
  className: "text-sm font-medium",
310
297
  children: "Compensation Gap"
311
- }), /* @__PURE__ */ l("p", {
298
+ }), /* @__PURE__ */ u("p", {
312
299
  className: "text-xs text-text-secondary mt-1",
313
300
  children: [
314
- L,
301
+ z,
315
302
  " department",
316
- L === 1 ? "" : "s",
303
+ z === 1 ? "" : "s",
317
304
  " below the median department salary"
318
305
  ]
319
306
  })]
@@ -324,6 +311,6 @@ function x() {
324
311
  });
325
312
  }
326
313
  //#endregion
327
- export { x as AnalyticsEmployeeAnalyticsPagePage };
314
+ export { S as AnalyticsEmployeeAnalyticsPagePage };
328
315
 
329
316
  //# sourceMappingURL=AnalyticsEmployeeAnalyticsPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsEmployeeAnalyticsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { Award, BarChart3, Clock, MapPin, Star, UserCheck } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\ntype DeptPerformanceRow = {\n name: string;\n headcount: number;\n avgScore: string;\n avgSalary: number;\n};\n\nexport function AnalyticsEmployeeAnalyticsPagePage() {\n const { employees, departments } = useCrewFoundryStore();\n\n const tenureDistribution = useMemo(() => {\n const total = Math.max(employees.length, 1);\n const getYears = (startDate: string) =>\n Math.max(\n 0,\n (new Date('2026-03-01').getTime() - new Date(startDate).getTime()) /\n (1000 * 60 * 60 * 24 * 365)\n );\n const buckets = [\n { range: '< 1 year', min: 0, max: 1 },\n { range: '1-2 years', min: 1, max: 2 },\n { range: '2-3 years', min: 2, max: 3 },\n { range: '3-4 years', min: 3, max: 4 },\n { range: '4+ years', min: 4, max: Infinity },\n ];\n return buckets.map((bucket) => {\n const count = employees.filter((employee) => {\n const years = getYears(employee.startDate);\n return years >= bucket.min && years < bucket.max;\n }).length;\n return { range: bucket.range, count, percentage: Math.round((count / total) * 100) };\n });\n }, [employees]);\n\n // Real average and median tenure derived from employee start dates.\n const { avgTenure, medianTenure } = useMemo(() => {\n if (employees.length === 0) {\n return { avgTenure: 0, medianTenure: 0 };\n }\n const getYears = (startDate: string) =>\n Math.max(\n 0,\n (new Date('2026-03-01').getTime() - new Date(startDate).getTime()) /\n (1000 * 60 * 60 * 24 * 365)\n );\n const years = employees.map((employee) => getYears(employee.startDate)).sort((a, b) => a - b);\n const avg = years.reduce((sum, value) => sum + value, 0) / years.length;\n const mid = Math.floor(years.length / 2);\n const median = years.length % 2 === 1 ? years[mid] : (years[mid - 1] + years[mid]) / 2;\n return { avgTenure: avg, medianTenure: median };\n }, [employees]);\n\n const avgPerformance = (\n employees.reduce((sum, e) => sum + e.performanceScore, 0) / Math.max(employees.length, 1)\n ).toFixed(1);\n const avgSalary = Math.round(\n employees.reduce((sum, e) => sum + e.salary, 0) / Math.max(employees.length, 1)\n );\n const activeCount = employees.filter((e) => e.status === 'active').length;\n const onLeaveCount = employees.filter((e) => e.status === 'on_leave').length;\n\n const deptPerformance: DeptPerformanceRow[] = departments.map((dept) => {\n const deptEmps = employees.filter((e) => e.department === dept.name);\n const avgScore =\n deptEmps.length > 0\n ? (deptEmps.reduce((s, e) => s + e.performanceScore, 0) / deptEmps.length).toFixed(1)\n : '0';\n const avgSal =\n deptEmps.length > 0\n ? Math.round(deptEmps.reduce((s, e) => s + e.salary, 0) / deptEmps.length)\n : 0;\n return { name: dept.name, headcount: deptEmps.length, avgScore, avgSalary: avgSal };\n });\n\n const sortedDeptPerformance = [...deptPerformance].sort(\n (a, b) => Number(b.avgScore) - Number(a.avgScore)\n );\n\n // Real key-insight figures.\n const topDept = sortedDeptPerformance[0];\n const flightRiskCount = employees.filter((e) => e.performanceScore < 3.5).length;\n const deptsWithStaff = deptPerformance.filter((d) => d.headcount > 0);\n const sortedSalaries = [...deptsWithStaff].map((d) => d.avgSalary).sort((a, b) => a - b);\n const medianDeptSalary =\n sortedSalaries.length > 0 ? sortedSalaries[Math.floor(sortedSalaries.length / 2)] : 0;\n const belowMedianDeptCount = deptsWithStaff.filter((d) => d.avgSalary < medianDeptSalary).length;\n\n const deptPerformanceColumns: ResponsiveTableColumn<DeptPerformanceRow>[] = [\n {\n key: 'name',\n header: 'Department',\n priority: 'primary',\n cell: (dept) => <span className=\"font-medium\">{dept.name}</span>,\n },\n {\n key: 'headcount',\n header: 'Headcount',\n priority: 'secondary',\n cell: (dept) => <span className=\"md:block md:text-right\">{dept.headcount}</span>,\n },\n {\n key: 'avgScore',\n header: 'Avg Score',\n priority: 'primary',\n cell: (dept) => (\n <span className=\"md:block md:text-right\">\n <Badge variant={Number(dept.avgScore) >= 4.5 ? 'default' : 'secondary'}>\n {dept.avgScore}\n </Badge>\n </span>\n ),\n },\n {\n key: 'avgSalary',\n header: 'Avg Salary',\n priority: 'secondary',\n cell: (dept) => (\n <span className=\"md:block md:text-right\">${dept.avgSalary.toLocaleString()}</span>\n ),\n },\n ];\n\n const locationBreakdown = employees.reduce<Record<string, number>>((acc, e) => {\n acc[e.location] = (acc[e.location] || 0) + 1;\n return acc;\n }, {});\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Employee Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Deep-dive into employee performance, engagement, and demographics\n </p>\n <PagePurpose className=\"mt-1\">\n See how individual employees and teams are performing over time — use these insights for\n reviews, recognition, and targeted coaching.\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Active Employees</CardTitle>\n <UserCheck className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{activeCount}</div>\n <p className=\"text-xs text-text-secondary\">{onLeaveCount} currently on leave</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Avg Performance</CardTitle>\n <Star className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgPerformance}/5.0</div>\n <p className=\"text-xs text-text-secondary\">Across {employees.length} employees</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Avg Compensation</CardTitle>\n <BarChart3 className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">${avgSalary.toLocaleString()}</div>\n <p className=\"text-xs text-text-secondary\">Across all departments</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Avg Tenure</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgTenure.toFixed(1)} yrs</div>\n <p className=\"text-xs text-text-secondary\">Median: {medianTenure.toFixed(1)} years</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Department Performance</CardTitle>\n <CardDescription>\n Average performance score and compensation by department\n </CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n columns={deptPerformanceColumns}\n data={sortedDeptPerformance}\n rowKey={(dept) => dept.name}\n />\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Engagement Drivers</CardTitle>\n <CardDescription>Employee satisfaction by category</CardDescription>\n </CardHeader>\n <CardContent>\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Engagement surveys coming soon\"\n description=\"Satisfaction and engagement drivers will appear here once the employee survey integration is connected — we don't show estimated scores.\"\n />\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4\" />\n Tenure Distribution\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {tenureDistribution.map((t) => (\n <div key={t.range} className=\"flex items-center gap-3\">\n <span className=\"w-20 text-sm text-text-secondary\">{t.range}</span>\n <div className=\"flex-1 h-4 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-4 bg-action-primary-bg/70 rounded\"\n style={{ width: `${t.percentage}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-10 text-right\">{t.count}</span>\n <span className=\"text-xs text-text-secondary w-10 text-right\">{t.percentage}%</span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <MapPin className=\"h-4 w-4\" />\n Location Distribution\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(locationBreakdown)\n .sort(([, a], [, b]) => b - a)\n .map(([loc, count]) => (\n <div key={loc} className=\"flex items-center justify-between py-1\">\n <span className=\"text-sm\">{loc}</span>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium\">{count}</span>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {Math.round((count / Math.max(employees.length, 1)) * 100)}%\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Award className=\"h-4 w-4 text-status-warning-text\" />\n Key Insights\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <div className=\"rounded border p-3\">\n <p className=\"text-sm font-medium\">Highest Performance</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {topDept\n ? `${topDept.name} leads with an average score of ${topDept.avgScore}`\n : 'No department performance data yet'}\n </p>\n </div>\n <div className=\"rounded border p-3\">\n <p className=\"text-sm font-medium\">Retention Risk</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {flightRiskCount} employee{flightRiskCount === 1 ? '' : 's'} below a 3.5 performance\n score — a potential flight risk\n </p>\n </div>\n <div className=\"rounded border p-3\">\n <p className=\"text-sm font-medium\">Compensation Gap</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {belowMedianDeptCount} department{belowMedianDeptCount === 1 ? '' : 's'} below the\n median department salary\n </p>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsEmployeeAnalyticsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { Award, BarChart3, Clock, MapPin, Star, UserCheck } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\nimport { BarChart } from '../components/viz';\nimport type { Segment } from '../components/viz';\n\ntype DeptPerformanceRow = {\n name: string;\n headcount: number;\n avgScore: string;\n avgSalary: number;\n};\n\nexport function AnalyticsEmployeeAnalyticsPagePage() {\n const { employees, departments } = useCrewFoundryStore();\n\n const tenureDistribution = useMemo(() => {\n const total = Math.max(employees.length, 1);\n const getYears = (startDate: string) =>\n Math.max(\n 0,\n (new Date('2026-03-01').getTime() - new Date(startDate).getTime()) /\n (1000 * 60 * 60 * 24 * 365)\n );\n const buckets = [\n { range: '< 1 year', min: 0, max: 1 },\n { range: '1-2 years', min: 1, max: 2 },\n { range: '2-3 years', min: 2, max: 3 },\n { range: '3-4 years', min: 3, max: 4 },\n { range: '4+ years', min: 4, max: Infinity },\n ];\n return buckets.map((bucket) => {\n const count = employees.filter((employee) => {\n const years = getYears(employee.startDate);\n return years >= bucket.min && years < bucket.max;\n }).length;\n return { range: bucket.range, count, percentage: Math.round((count / total) * 100) };\n });\n }, [employees]);\n\n // Real average and median tenure derived from employee start dates.\n const { avgTenure, medianTenure } = useMemo(() => {\n if (employees.length === 0) {\n return { avgTenure: 0, medianTenure: 0 };\n }\n const getYears = (startDate: string) =>\n Math.max(\n 0,\n (new Date('2026-03-01').getTime() - new Date(startDate).getTime()) /\n (1000 * 60 * 60 * 24 * 365)\n );\n const years = employees.map((employee) => getYears(employee.startDate)).sort((a, b) => a - b);\n const avg = years.reduce((sum, value) => sum + value, 0) / years.length;\n const mid = Math.floor(years.length / 2);\n const median = years.length % 2 === 1 ? years[mid] : (years[mid - 1] + years[mid]) / 2;\n return { avgTenure: avg, medianTenure: median };\n }, [employees]);\n\n const tenureSeries: Segment[] = tenureDistribution.map((bucket) => ({\n label: bucket.range.replace(' years', 'y').replace(' year', 'y'),\n value: bucket.count,\n }));\n\n const avgPerformance = (\n employees.reduce((sum, e) => sum + e.performanceScore, 0) / Math.max(employees.length, 1)\n ).toFixed(1);\n const avgSalary = Math.round(\n employees.reduce((sum, e) => sum + e.salary, 0) / Math.max(employees.length, 1)\n );\n const activeCount = employees.filter((e) => e.status === 'active').length;\n const onLeaveCount = employees.filter((e) => e.status === 'on_leave').length;\n\n const deptPerformance: DeptPerformanceRow[] = departments.map((dept) => {\n const deptEmps = employees.filter((e) => e.department === dept.name);\n const avgScore =\n deptEmps.length > 0\n ? (deptEmps.reduce((s, e) => s + e.performanceScore, 0) / deptEmps.length).toFixed(1)\n : '0';\n const avgSal =\n deptEmps.length > 0\n ? Math.round(deptEmps.reduce((s, e) => s + e.salary, 0) / deptEmps.length)\n : 0;\n return { name: dept.name, headcount: deptEmps.length, avgScore, avgSalary: avgSal };\n });\n\n const sortedDeptPerformance = [...deptPerformance].sort(\n (a, b) => Number(b.avgScore) - Number(a.avgScore)\n );\n\n // Real key-insight figures.\n const topDept = sortedDeptPerformance[0];\n const flightRiskCount = employees.filter((e) => e.performanceScore < 3.5).length;\n const deptsWithStaff = deptPerformance.filter((d) => d.headcount > 0);\n const sortedSalaries = [...deptsWithStaff].map((d) => d.avgSalary).sort((a, b) => a - b);\n const medianDeptSalary =\n sortedSalaries.length > 0 ? sortedSalaries[Math.floor(sortedSalaries.length / 2)] : 0;\n const belowMedianDeptCount = deptsWithStaff.filter((d) => d.avgSalary < medianDeptSalary).length;\n\n const deptPerformanceColumns: ResponsiveTableColumn<DeptPerformanceRow>[] = [\n {\n key: 'name',\n header: 'Department',\n priority: 'primary',\n cell: (dept) => <span className=\"font-medium\">{dept.name}</span>,\n },\n {\n key: 'headcount',\n header: 'Headcount',\n priority: 'secondary',\n cell: (dept) => <span className=\"md:block md:text-right\">{dept.headcount}</span>,\n },\n {\n key: 'avgScore',\n header: 'Avg Score',\n priority: 'primary',\n cell: (dept) => (\n <span className=\"md:block md:text-right\">\n <Badge variant={Number(dept.avgScore) >= 4.5 ? 'default' : 'secondary'}>\n {dept.avgScore}\n </Badge>\n </span>\n ),\n },\n {\n key: 'avgSalary',\n header: 'Avg Salary',\n priority: 'secondary',\n cell: (dept) => (\n <span className=\"md:block md:text-right\">${dept.avgSalary.toLocaleString()}</span>\n ),\n },\n ];\n\n const locationBreakdown = employees.reduce<Record<string, number>>((acc, e) => {\n acc[e.location] = (acc[e.location] || 0) + 1;\n return acc;\n }, {});\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Employee Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Deep-dive into employee performance, engagement, and demographics\n </p>\n <PagePurpose className=\"mt-1\">\n See how individual employees and teams are performing over time — use these insights for\n reviews, recognition, and targeted coaching.\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Active Employees</CardTitle>\n <UserCheck className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{activeCount}</div>\n <p className=\"text-xs text-text-secondary\">{onLeaveCount} currently on leave</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Avg Performance</CardTitle>\n <Star className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgPerformance}/5.0</div>\n <p className=\"text-xs text-text-secondary\">Across {employees.length} employees</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Avg Compensation</CardTitle>\n <BarChart3 className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">${avgSalary.toLocaleString()}</div>\n <p className=\"text-xs text-text-secondary\">Across all departments</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Avg Tenure</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgTenure.toFixed(1)} yrs</div>\n <p className=\"text-xs text-text-secondary\">Median: {medianTenure.toFixed(1)} years</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Department Performance</CardTitle>\n <CardDescription>\n Average performance score and compensation by department\n </CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n columns={deptPerformanceColumns}\n data={sortedDeptPerformance}\n rowKey={(dept) => dept.name}\n />\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Engagement Drivers</CardTitle>\n <CardDescription>Employee satisfaction by category</CardDescription>\n </CardHeader>\n <CardContent>\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"Engagement surveys coming soon\"\n description=\"Satisfaction and engagement drivers will appear here once the employee survey integration is connected — we don't show estimated scores.\"\n />\n </CardContent>\n </Card>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Clock className=\"h-4 w-4\" />\n Tenure Distribution\n </CardTitle>\n </CardHeader>\n <CardContent>\n {employees.length > 0 ? (\n <BarChart series={tenureSeries} colorful height={220} title=\"Employee tenure distribution\" />\n ) : (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No employees yet.\n </p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <MapPin className=\"h-4 w-4\" />\n Location Distribution\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(locationBreakdown)\n .sort(([, a], [, b]) => b - a)\n .map(([loc, count]) => (\n <div key={loc} className=\"flex items-center justify-between py-1\">\n <span className=\"text-sm\">{loc}</span>\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm font-medium\">{count}</span>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {Math.round((count / Math.max(employees.length, 1)) * 100)}%\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Award className=\"h-4 w-4 text-status-warning-text\" />\n Key Insights\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"grid gap-3 md:grid-cols-3\">\n <div className=\"rounded border p-3\">\n <p className=\"text-sm font-medium\">Highest Performance</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {topDept\n ? `${topDept.name} leads with an average score of ${topDept.avgScore}`\n : 'No department performance data yet'}\n </p>\n </div>\n <div className=\"rounded border p-3\">\n <p className=\"text-sm font-medium\">Retention Risk</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {flightRiskCount} employee{flightRiskCount === 1 ? '' : 's'} below a 3.5 performance\n score — a potential flight risk\n </p>\n </div>\n <div className=\"rounded border p-3\">\n <p className=\"text-sm font-medium\">Compensation Gap</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {belowMedianDeptCount} department{belowMedianDeptCount === 1 ? '' : 's'} below the\n median department salary\n </p>\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}