@burdenoff/microfe-crewfoundry 2026.703.2 → 2026.704.2

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 (130) hide show
  1. package/dist/crewfoundry/components/forms/FormModal.js.map +1 -1
  2. package/dist/crewfoundry/pages/AdminAdminPagePage.js +117 -164
  3. package/dist/crewfoundry/pages/AdminAdminPagePage.js.map +1 -1
  4. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js +99 -82
  5. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  6. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js +160 -192
  7. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
  8. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +238 -231
  9. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
  10. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +196 -166
  11. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
  12. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
  13. package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js +45 -147
  14. package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js.map +1 -1
  15. package/dist/crewfoundry/pages/CommunicationsDirectoryPagePage.js +20 -11
  16. package/dist/crewfoundry/pages/CommunicationsDirectoryPagePage.js.map +1 -1
  17. package/dist/crewfoundry/pages/CommunicationsFilesPagePage.js +45 -152
  18. package/dist/crewfoundry/pages/CommunicationsFilesPagePage.js.map +1 -1
  19. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +50 -138
  20. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
  21. package/dist/crewfoundry/pages/CommunicationsTeamsPagePage.js +149 -67
  22. package/dist/crewfoundry/pages/CommunicationsTeamsPagePage.js.map +1 -1
  23. package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js +50 -201
  24. package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js.map +1 -1
  25. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +205 -136
  26. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  27. package/dist/crewfoundry/pages/CompanyCompanyDashboardPagePage.js +25 -31
  28. package/dist/crewfoundry/pages/CompanyCompanyDashboardPagePage.js.map +1 -1
  29. package/dist/crewfoundry/pages/CompanyCompanyProfilePagePage.js +118 -104
  30. package/dist/crewfoundry/pages/CompanyCompanyProfilePagePage.js.map +1 -1
  31. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +117 -58
  32. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
  33. package/dist/crewfoundry/pages/DashboardAnalyticsPagePage.js +90 -94
  34. package/dist/crewfoundry/pages/DashboardAnalyticsPagePage.js.map +1 -1
  35. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +210 -191
  36. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
  37. package/dist/crewfoundry/pages/DashboardEnhancedDashboardPagePage.js +213 -180
  38. package/dist/crewfoundry/pages/DashboardEnhancedDashboardPagePage.js.map +1 -1
  39. package/dist/crewfoundry/pages/DashboardExecutiveDashboardPage.js +133 -264
  40. package/dist/crewfoundry/pages/DashboardExecutiveDashboardPage.js.map +1 -1
  41. package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js +279 -212
  42. package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
  43. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js +131 -105
  44. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
  45. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +167 -148
  46. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  47. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +3 -3
  48. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
  49. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +13 -5
  50. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
  51. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +151 -152
  52. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
  53. package/dist/crewfoundry/pages/HiringJobsPagePage.js +38 -21
  54. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  55. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +3 -3
  56. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  57. package/dist/crewfoundry/pages/HrEmployeesPagePage.js +134 -127
  58. package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
  59. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +163 -132
  60. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  61. package/dist/crewfoundry/pages/HrPayrollPagePage.js +202 -160
  62. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  63. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +104 -91
  64. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  65. package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js +29 -147
  66. package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js.map +1 -1
  67. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +22 -106
  68. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
  69. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +74 -74
  70. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
  71. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +101 -97
  72. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
  73. package/dist/crewfoundry/pages/LearningCoursesPagePage.js +64 -47
  74. package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
  75. package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.js +111 -121
  76. package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.js.map +1 -1
  77. package/dist/crewfoundry/pages/LearningLearningPagePage.js +131 -110
  78. package/dist/crewfoundry/pages/LearningLearningPagePage.js.map +1 -1
  79. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +65 -64
  80. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
  81. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +58 -43
  82. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
  83. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +66 -82
  84. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
  85. package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js +38 -88
  86. package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js.map +1 -1
  87. package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js +59 -65
  88. package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js.map +1 -1
  89. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js +209 -323
  90. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +1 -1
  91. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +175 -220
  92. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
  93. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +74 -59
  94. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  95. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +96 -80
  96. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
  97. package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js +28 -100
  98. package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js.map +1 -1
  99. package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js +27 -20
  100. package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js.map +1 -1
  101. package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js +73 -73
  102. package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +1 -1
  103. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +62 -56
  104. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
  105. package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js +40 -138
  106. package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js.map +1 -1
  107. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +126 -93
  108. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
  109. package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js +99 -77
  110. package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +1 -1
  111. package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js.map +1 -1
  112. package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js +73 -74
  113. package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js.map +1 -1
  114. package/dist/crewfoundry/pages/ProjectsCreateProjectPagePage.js +1 -1
  115. package/dist/crewfoundry/pages/ProjectsCreateProjectPagePage.js.map +1 -1
  116. package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js +122 -122
  117. package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js.map +1 -1
  118. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +130 -113
  119. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  120. package/dist/crewfoundry/pages/TeamsTeamAnalyticsPagePage.js +68 -59
  121. package/dist/crewfoundry/pages/TeamsTeamAnalyticsPagePage.js.map +1 -1
  122. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js +73 -62
  123. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
  124. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js +82 -58
  125. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
  126. package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js +153 -153
  127. package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js.map +1 -1
  128. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +63 -48
  129. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  130. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsHiringAnalyticsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { Briefcase, Clock, Target, TrendingUp, Users } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function AnalyticsHiringAnalyticsPagePage() {\n const { jobPostings, candidates, interviews } = useCrewFoundryStore();\n\n const hiringMetrics = useMemo(() => {\n const months = ['Sep', 'Oct', 'Nov', 'Dec', 'Jan', 'Feb'];\n return months.map((month, index) => {\n const monthInterviews = interviews.filter(\n (interview) =>\n new Date(interview.date).toLocaleString('en-US', { month: 'short' }) === month\n ).length;\n const monthCandidates = candidates.filter(\n (candidate) =>\n new Date(candidate.appliedDate).toLocaleString('en-US', { month: 'short' }) === month\n );\n const offers = monthCandidates.filter((candidate) => candidate.status === 'offer').length;\n const hires = monthCandidates.filter((candidate) => candidate.status === 'hired').length;\n const applications =\n monthCandidates.length +\n jobPostings.reduce((sum, posting) => sum + posting.applicants, 0) / 6;\n return {\n month,\n applications: Math.round(applications - index * 3),\n interviews: monthInterviews,\n offers,\n hires,\n };\n });\n }, [candidates, interviews, jobPostings]);\n\n const sourceEffectiveness = useMemo(() => {\n const grouped = candidates.reduce<Record<string, { applications: number; hires: number }>>(\n (acc, candidate) => {\n const source = candidate.source || 'Unknown';\n const value = acc[source] || { applications: 0, hires: 0 };\n value.applications += 1;\n if (candidate.status === 'hired') {\n value.hires += 1;\n }\n acc[source] = value;\n return acc;\n },\n {}\n );\n\n return Object.entries(grouped).map(([source, value]) => ({\n source,\n applications: value.applications,\n hires: value.hires,\n conversionRate: Number(((value.hires / Math.max(value.applications, 1)) * 100).toFixed(1)),\n costPerHire: Math.round(\n jobPostings.reduce((sum, posting) => sum + posting.salaryMin * 0.02, 0) /\n Math.max(value.hires, 1)\n ),\n }));\n }, [candidates, jobPostings]);\n\n const activeJobs = jobPostings.filter((j) => j.status === 'active');\n const totalApplicants = jobPostings.reduce((s, j) => s + j.applicants, 0);\n const scheduledInterviews = interviews.filter((i) => i.status === 'scheduled').length;\n const completedInterviews = interviews.filter((i) => i.status === 'completed').length;\n const offeredCandidates = candidates.filter((c) => c.status === 'offer').length;\n const hiredCandidates = candidates.filter((c) => c.status === 'hired').length;\n\n const maxApplications = Math.max(1, ...hiringMetrics.map((m) => m.applications));\n\n const statusPipeline = [\n {\n status: 'New',\n count: candidates.filter((c) => c.status === 'new').length,\n color: 'bg-text-secondary',\n },\n {\n status: 'Screening',\n count: candidates.filter((c) => c.status === 'screening').length,\n color: 'bg-status-info-text',\n },\n {\n status: 'Interview',\n count: candidates.filter((c) => c.status === 'interview').length,\n color: 'bg-status-info-text',\n },\n {\n status: 'Assessment',\n count: candidates.filter((c) => c.status === 'assessment').length,\n color: 'bg-action-primary-bg',\n },\n { status: 'Offer', count: offeredCandidates, color: 'bg-status-warning-text' },\n { status: 'Hired', count: hiredCandidates, color: 'bg-status-success-text' },\n {\n status: 'Rejected',\n count: candidates.filter((c) => c.status === 'rejected').length,\n color: 'bg-status-error-text',\n },\n ];\n const totalPipeline = statusPipeline.reduce((s, p) => s + p.count, 0);\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>Hiring Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Recruitment performance and pipeline analysis\n </p>\n <PagePurpose className=\"mt-1\">Measure how your hiring funnel performs — applications, time-to-hire, and source quality — to make recruiting faster and more effective.</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 Postings</CardTitle>\n <Briefcase className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{activeJobs.length}</div>\n <p className=\"text-xs text-text-secondary\">{totalApplicants} total applicants</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\">Time to Fill</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">28 days</div>\n <div className=\"flex items-center text-xs text-status-success-text\">\n <TrendingUp className=\"h-3 w-3 mr-1\" />\n -3 days from last quarter\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\">Interview to Offer</CardTitle>\n <Target className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">32%</div>\n <p className=\"text-xs text-text-secondary\">\n {scheduledInterviews} scheduled, {completedInterviews} 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\">Offer Acceptance</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">87%</div>\n <p className=\"text-xs text-text-secondary\">{hiredCandidates} hired this quarter</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-7\">\n <Card className=\"col-span-4\">\n <CardHeader>\n <CardTitle>Monthly Hiring Activity</CardTitle>\n <CardDescription>Applications, interviews, and hires over time</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {hiringMetrics.map((m) => (\n <div key={m.month} className=\"flex items-center gap-3\">\n <span className=\"w-10 text-sm text-text-secondary\">{m.month}</span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-5 bg-action-primary-bg rounded\"\n style={{\n width: `${Math.round((m.applications / (maxApplications + 20)) * 100)}%`,\n }}\n />\n </div>\n <div className=\"flex gap-1 w-48 justify-end\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {m.applications} apps\n </Badge>\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {m.interviews} int\n </Badge>\n <Badge variant=\"default\" className=\"text-[10px]\">\n {m.hires} hired\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card className=\"col-span-3\">\n <CardHeader>\n <CardTitle>Pipeline Status</CardTitle>\n <CardDescription>Current candidate distribution</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {statusPipeline.map((stage) => (\n <div key={stage.status} className=\"flex items-center gap-3\">\n <div className={`h-3 w-3 rounded-full ${stage.color}`} />\n <span className=\"w-20 text-sm\">{stage.status}</span>\n <div className=\"flex-1 h-3 bg-bg-sunken rounded overflow-hidden\">\n <div\n className={`h-3 rounded ${stage.color}`}\n style={{\n width: `${totalPipeline > 0 ? Math.max(Math.round((stage.count / totalPipeline) * 100), 3) : 3}%`,\n }}\n />\n </div>\n <span className=\"text-sm font-medium w-6 text-right\">{stage.count}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Source Effectiveness</CardTitle>\n <CardDescription>Recruitment channel performance comparison</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-5 gap-2 text-xs font-medium text-text-secondary border-b pb-2 mb-2\">\n <span>Source</span>\n <span className=\"text-right\">Applications</span>\n <span className=\"text-right\">Hires</span>\n <span className=\"text-right\">Conversion</span>\n <span className=\"text-right\">Cost/Hire</span>\n </div>\n {sourceEffectiveness\n .sort((a, b) => b.conversionRate - a.conversionRate)\n .map((src) => (\n <div\n key={src.source}\n className=\"grid grid-cols-5 gap-2 py-2 border-b last:border-0 text-sm\"\n >\n <span className=\"font-medium\">{src.source}</span>\n <span className=\"text-right\">{src.applications}</span>\n <span className=\"text-right\">{src.hires}</span>\n <span className=\"text-right\">\n <Badge\n variant={src.conversionRate >= 5 ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {src.conversionRate}%\n </Badge>\n </span>\n <span className=\"text-right\">${src.costPerHire.toLocaleString()}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Open Positions Detail</CardTitle>\n <CardDescription>Active job postings and applicant counts</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-6 gap-2 text-xs font-medium text-text-secondary border-b pb-2 mb-2\">\n <span>Position</span>\n <span>Department</span>\n <span>Level</span>\n <span className=\"text-right\">Applicants</span>\n <span className=\"text-right\">Salary Range</span>\n <span className=\"text-right\">Status</span>\n </div>\n {jobPostings.map((job) => (\n <div\n key={job.id}\n className=\"grid grid-cols-6 gap-2 py-2 border-b last:border-0 text-sm\"\n >\n <span className=\"font-medium truncate\">{job.title}</span>\n <span className=\"text-text-secondary\">{job.department}</span>\n <Badge variant=\"outline\" className=\"w-fit text-[10px]\">\n {job.experienceLevel}\n </Badge>\n <span className=\"text-right\">{job.applicants}</span>\n <span className=\"text-right text-text-secondary\">\n ${(job.salaryMin / 1000).toFixed(0)}K-${(job.salaryMax / 1000).toFixed(0)}K\n </span>\n <div className=\"text-right\">\n <Badge\n variant={job.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {job.status}\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsHiringAnalyticsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Briefcase, Clock, Target, Users } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\n\ntype SourceRow = {\n source: string;\n applications: number;\n hires: number;\n conversionRate: number;\n};\n\nconst monthKeyOf = (value: string) => {\n const date = new Date(value);\n return `${date.getFullYear()}-${String(date.getMonth()).padStart(2, '0')}`;\n};\n\n// Stable \"as of\" anchor for this dataset (avoids impure Date.now() in render).\nconst AS_OF = new Date('2026-03-01').getTime();\n\nexport function AnalyticsHiringAnalyticsPagePage() {\n const { jobPostings, candidates, interviews } = useCrewFoundryStore();\n\n // Real monthly activity derived from candidate/interview dates.\n const hiringMetrics = useMemo(() => {\n if (candidates.length === 0 && interviews.length === 0) {\n return [];\n }\n const keys = Array.from(\n new Set([\n ...candidates.map((candidate) => monthKeyOf(candidate.appliedDate)),\n ...interviews.map((interview) => monthKeyOf(interview.date)),\n ])\n )\n .sort()\n .slice(-6);\n return keys.map((key) => {\n const [year, month] = key.split('-').map(Number);\n const label = new Date(year, month, 1).toLocaleString('en-US', {\n month: 'short',\n year: '2-digit',\n });\n const monthCandidates = candidates.filter(\n (candidate) => monthKeyOf(candidate.appliedDate) === key\n );\n return {\n month: label,\n applications: monthCandidates.length,\n interviews: interviews.filter((interview) => monthKeyOf(interview.date) === key).length,\n hires: monthCandidates.filter((candidate) => candidate.status === 'hired').length,\n };\n });\n }, [candidates, interviews]);\n\n const sourceEffectiveness = useMemo<SourceRow[]>(() => {\n const grouped = candidates.reduce<Record<string, { applications: number; hires: number }>>(\n (acc, candidate) => {\n const source = candidate.source || 'Unknown';\n const value = acc[source] || { applications: 0, hires: 0 };\n value.applications += 1;\n if (candidate.status === 'hired') {\n value.hires += 1;\n }\n acc[source] = value;\n return acc;\n },\n {}\n );\n\n return Object.entries(grouped)\n .map(([source, value]) => ({\n source,\n applications: value.applications,\n hires: value.hires,\n conversionRate: Number(((value.hires / Math.max(value.applications, 1)) * 100).toFixed(1)),\n }))\n .sort((a, b) => b.conversionRate - a.conversionRate);\n }, [candidates]);\n\n // Real average number of days active postings have been open.\n const avgDaysOpen = useMemo(() => {\n const activePostings = jobPostings.filter((posting) => posting.status === 'active');\n if (activePostings.length === 0) {\n return 0;\n }\n const totalDays = activePostings.reduce((sum, posting) => {\n const posted = new Date(posting.postedDate).getTime();\n if (Number.isNaN(posted)) {\n return sum;\n }\n return sum + Math.max(0, (AS_OF - posted) / (1000 * 60 * 60 * 24));\n }, 0);\n return Math.round(totalDays / activePostings.length);\n }, [jobPostings]);\n\n const activeJobs = jobPostings.filter((j) => j.status === 'active');\n const totalApplicants = jobPostings.reduce((s, j) => s + j.applicants, 0);\n const scheduledInterviews = interviews.filter((i) => i.status === 'scheduled').length;\n const completedInterviews = interviews.filter((i) => i.status === 'completed').length;\n const offeredCandidates = candidates.filter((c) => c.status === 'offer').length;\n const hiredCandidates = candidates.filter((c) => c.status === 'hired').length;\n\n const interviewedOrBeyond = candidates.filter((c) =>\n ['interview', 'assessment', 'offer', 'hired'].includes(c.status)\n ).length;\n const offerOrBeyond = candidates.filter((c) => ['offer', 'hired'].includes(c.status)).length;\n const interviewToOffer = Math.round((offerOrBeyond / Math.max(interviewedOrBeyond, 1)) * 100);\n const offerAcceptance = Math.round(\n (hiredCandidates / Math.max(offeredCandidates + hiredCandidates, 1)) * 100\n );\n\n const maxApplications = Math.max(1, ...hiringMetrics.map((m) => m.applications));\n\n const statusPipeline = [\n {\n status: 'New',\n count: candidates.filter((c) => c.status === 'new').length,\n color: 'bg-text-secondary',\n },\n {\n status: 'Screening',\n count: candidates.filter((c) => c.status === 'screening').length,\n color: 'bg-status-info-text',\n },\n {\n status: 'Interview',\n count: candidates.filter((c) => c.status === 'interview').length,\n color: 'bg-status-info-text',\n },\n {\n status: 'Assessment',\n count: candidates.filter((c) => c.status === 'assessment').length,\n color: 'bg-action-primary-bg',\n },\n { status: 'Offer', count: offeredCandidates, color: 'bg-status-warning-text' },\n { status: 'Hired', count: hiredCandidates, color: 'bg-status-success-text' },\n {\n status: 'Rejected',\n count: candidates.filter((c) => c.status === 'rejected').length,\n color: 'bg-status-error-text',\n },\n ];\n const totalPipeline = statusPipeline.reduce((s, p) => s + p.count, 0);\n\n // Open Positions = active postings only, paginated to match the card title.\n const openPositions = [...activeJobs].sort((a, b) => b.applicants - a.applicants);\n const {\n page: jobPage,\n pageSize: jobPageSize,\n offset: jobOffset,\n setPage: setJobPage,\n } = usePagination({ initialPageSize: 8 });\n const jobPageItems = openPositions.slice(jobOffset, jobOffset + jobPageSize);\n\n const sourceColumns: ResponsiveTableColumn<SourceRow>[] = [\n {\n key: 'source',\n header: 'Source',\n priority: 'primary',\n cell: (src) => <span className=\"font-medium\">{src.source}</span>,\n },\n {\n key: 'applications',\n header: 'Applications',\n priority: 'secondary',\n cell: (src) => <span className=\"md:block md:text-right\">{src.applications}</span>,\n },\n {\n key: 'hires',\n header: 'Hires',\n priority: 'secondary',\n cell: (src) => <span className=\"md:block md:text-right\">{src.hires}</span>,\n },\n {\n key: 'conversion',\n header: 'Conversion',\n priority: 'primary',\n cell: (src) => (\n <span className=\"md:block md:text-right\">\n <Badge\n variant={src.conversionRate >= 5 ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {src.conversionRate}%\n </Badge>\n </span>\n ),\n },\n ];\n\n const jobColumns: ResponsiveTableColumn<JobPosting>[] = [\n {\n key: 'title',\n header: 'Position',\n priority: 'primary',\n cell: (job) => <span className=\"font-medium\">{job.title}</span>,\n },\n {\n key: 'department',\n header: 'Department',\n priority: 'secondary',\n cell: (job) => <span className=\"text-text-secondary\">{job.department}</span>,\n },\n {\n key: 'level',\n header: 'Level',\n priority: 'hidden-on-mobile',\n cell: (job) => (\n <Badge variant=\"outline\" className=\"w-fit text-[10px]\">\n {job.experienceLevel}\n </Badge>\n ),\n },\n {\n key: 'applicants',\n header: 'Applicants',\n priority: 'secondary',\n cell: (job) => <span className=\"md:block md:text-right\">{job.applicants}</span>,\n },\n {\n key: 'salary',\n header: 'Salary Range',\n priority: 'secondary',\n cell: (job) => (\n <span className=\"md:block md:text-right text-text-secondary\">\n ${(job.salaryMin / 1000).toFixed(0)}K-${(job.salaryMax / 1000).toFixed(0)}K\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'primary',\n cell: (job) => (\n <Badge variant=\"default\" className=\"text-[10px]\">\n {job.status}\n </Badge>\n ),\n },\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>Hiring Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Recruitment performance and pipeline analysis\n </p>\n <PagePurpose className=\"mt-1\">\n Measure how your hiring funnel performs — applications, time-to-hire, and source quality\n — to make recruiting faster and more effective.\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 Postings</CardTitle>\n <Briefcase className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{activeJobs.length}</div>\n <p className=\"text-xs text-text-secondary\">{totalApplicants} total applicants</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 Days Open</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{avgDaysOpen} days</div>\n <p className=\"text-xs text-text-secondary\">Across active postings</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\">Interview to Offer</CardTitle>\n <Target className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{interviewToOffer}%</div>\n <p className=\"text-xs text-text-secondary\">\n {scheduledInterviews} scheduled, {completedInterviews} 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\">Offer Acceptance</CardTitle>\n <Users className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{offerAcceptance}%</div>\n <p className=\"text-xs text-text-secondary\">{hiredCandidates} hired to date</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-7\">\n <Card className=\"col-span-4\">\n <CardHeader>\n <CardTitle>Monthly Hiring Activity</CardTitle>\n <CardDescription>Applications, interviews, and hires over time</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {hiringMetrics.length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No hiring activity recorded yet.\n </p>\n )}\n {hiringMetrics.map((m) => (\n <div key={m.month} className=\"flex items-center gap-3\">\n <span className=\"w-12 text-sm text-text-secondary\">{m.month}</span>\n <div className=\"flex-1 h-5 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-5 bg-action-primary-bg rounded\"\n style={{\n width: `${Math.round((m.applications / maxApplications) * 100)}%`,\n }}\n />\n </div>\n <div className=\"flex gap-1 w-48 justify-end\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {m.applications} apps\n </Badge>\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {m.interviews} int\n </Badge>\n <Badge variant=\"default\" className=\"text-[10px]\">\n {m.hires} hired\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card className=\"col-span-3\">\n <CardHeader>\n <CardTitle>Pipeline Status</CardTitle>\n <CardDescription>Current candidate distribution</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {statusPipeline.map((stage) => (\n <div key={stage.status} className=\"flex items-center gap-3\">\n <div className={`h-3 w-3 rounded-full ${stage.color}`} />\n <span className=\"w-20 text-sm\">{stage.status}</span>\n <div className=\"flex-1 h-3 bg-bg-sunken rounded overflow-hidden\">\n <div\n className={`h-3 rounded ${stage.color}`}\n style={{\n width: `${totalPipeline > 0 ? Math.max(Math.round((stage.count / totalPipeline) * 100), 3) : 3}%`,\n }}\n />\n </div>\n <span className=\"text-sm font-medium w-6 text-right\">{stage.count}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Source Effectiveness</CardTitle>\n <CardDescription>Recruitment channel performance comparison</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n columns={sourceColumns}\n data={sourceEffectiveness}\n rowKey={(src) => src.source}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No candidate source data yet.\n </p>\n }\n />\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Open Positions Detail</CardTitle>\n <CardDescription>Active job postings and applicant counts</CardDescription>\n </CardHeader>\n <CardContent>\n {openPositions.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No open positions\"\n description=\"Active job postings will appear here once you publish a role.\"\n />\n ) : (\n <>\n <ResponsiveTable columns={jobColumns} data={jobPageItems} rowKey={(job) => job.id} />\n {openPositions.length > jobPageSize && (\n <Pagination\n page={jobPage}\n pageSize={jobPageSize}\n totalItems={openPositions.length}\n onPageChange={setJobPage}\n className=\"mt-3\"\n />\n )}\n </>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"CommunicationsAnnouncementsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsAnnouncementsPagePage.tsx"],"sourcesContent":["import {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Eye, Pencil, Pin, Plus, Trash2 } from 'lucide-react';\nimport { useState } from 'react';\nimport { useCrewFoundryStore, type Announcement } from '../store/crewfoundryStore';\nimport { FormModal, useFormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\n\nconst CATEGORY_OPTIONS = [\n { label: 'General', value: 'general' },\n { label: 'HR', value: 'hr' },\n { label: 'IT', value: 'it' },\n { label: 'Emergency', value: 'emergency' },\n { label: 'Event', value: 'event' },\n];\n\nconst PRIORITY_OPTIONS = [\n { label: 'Low', value: 'low' },\n { label: 'Medium', value: 'medium' },\n { label: 'High', value: 'high' },\n { label: 'Urgent', value: 'urgent' },\n];\n\nconst CREATE_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Title', type: 'text', required: true, colSpan: 2 },\n { name: 'content', label: 'Content', type: 'textarea', required: true, colSpan: 2 },\n { name: 'author', label: 'Author', type: 'text', required: true },\n { name: 'authorRole', label: 'Author Role', type: 'text', required: true },\n {\n name: 'category',\n label: 'Category',\n type: 'select',\n required: true,\n options: CATEGORY_OPTIONS,\n placeholder: 'Select a category',\n },\n {\n name: 'priority',\n label: 'Priority',\n type: 'select',\n required: true,\n options: PRIORITY_OPTIONS,\n placeholder: 'Select a priority',\n },\n { name: 'tags', label: 'Tags', type: 'tags', placeholder: 'Add a tag and press Enter' },\n { name: 'isPinned', label: 'Pin to top', type: 'switch' },\n];\n\n// Author/role are immutable after creation (they are not part of the update\n// input), so the edit form omits them.\nconst EDIT_FIELDS: FieldConfig[] = CREATE_FIELDS.filter(\n (field) => field.name !== 'author' && field.name !== 'authorRole'\n);\n\nexport function CommunicationsAnnouncementsPagePage() {\n const { announcements, addAnnouncement, updateAnnouncement, removeAnnouncement } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const form = useFormModal<Announcement>();\n const [deleteTarget, setDeleteTarget] = useState<Announcement | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n const editing = form.context;\n const pinned = announcements.filter((a) => a.isPinned);\n const regular = announcements.filter((a) => !a.isPinned);\n\n const handleSubmit = async (values: FormValues) => {\n const tags = Array.isArray(values.tags) ? values.tags : [];\n if (editing) {\n await updateAnnouncement(editing.id, {\n title: String(values.title),\n content: String(values.content),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n tags,\n });\n showToast('Announcement updated', 'success');\n return;\n }\n await addAnnouncement({\n id: crypto.randomUUID(),\n title: String(values.title),\n content: String(values.content),\n author: String(values.author),\n authorRole: String(values.authorRole),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n createdAt: new Date().toISOString().slice(0, 10),\n viewCount: 0,\n tags,\n });\n showToast('Announcement published', 'success');\n };\n\n const confirmDelete = async () => {\n if (!deleteTarget) {\n return;\n }\n setDeleting(true);\n try {\n await removeAnnouncement(deleteTarget.id);\n showToast('Announcement deleted', 'success');\n setDeleteTarget(null);\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to delete announcement', 'error');\n } finally {\n setDeleting(false);\n }\n };\n\n const initialValues: FormValues | undefined = editing\n ? {\n title: editing.title,\n content: editing.content,\n category: editing.category,\n priority: editing.priority,\n tags: editing.tags,\n isPinned: editing.isPinned,\n }\n : undefined;\n\n const renderActions = (announcement: Announcement) => (\n <div className=\"flex gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n aria-label={`Edit announcement ${announcement.title}`}\n onClick={() => form.openModal(announcement)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7 text-status-error-text\"\n aria-label={`Delete announcement ${announcement.title}`}\n onClick={() => setDeleteTarget(announcement)}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\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>Announcements</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {announcements.length} company announcements\n </p>\n <PagePurpose className=\"mt-1\">Broadcast company-wide news and updates so everyone stays informed — post announcements here when you have something the whole team needs to see.</PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Announcement\n </Button>\n </div>\n {pinned.length > 0 && (\n <div className=\"space-y-3\">\n <h2 className=\"text-sm font-medium flex items-center gap-1\">\n <Pin className=\"h-4 w-4\" />\n Pinned\n </h2>\n {pinned.map((ann) => (\n <GlassCard key={ann.id} treatment=\"glass\" glow>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{ann.title}</CardTitle>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-1\">\n <Badge\n variant={\n ann.priority === 'urgent'\n ? 'destructive'\n : ann.priority === 'high'\n ? 'default'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {ann.priority}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {ann.category}\n </Badge>\n </div>\n {renderActions(ann)}\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary\">{ann.content}</p>\n <div className=\"flex items-center justify-between mt-3 text-xs text-text-secondary\">\n <span>\n {ann.author} · {ann.authorRole}\n </span>\n <div className=\"flex items-center gap-3\">\n <span className=\"flex items-center gap-1\">\n <Eye className=\"h-3 w-3\" />\n {ann.viewCount}\n </span>\n <span>{ann.createdAt}</span>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n ))}\n </div>\n )}\n {announcements.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"No announcements yet\"\n description=\"Post an announcement to share news with the whole team.\"\n />\n )}\n <div className=\"space-y-3\">\n {regular.map((ann) => (\n <Card key={ann.id}>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{ann.title}</CardTitle>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-1\">\n <Badge\n variant={\n ann.priority === 'urgent'\n ? 'destructive'\n : ann.priority === 'high'\n ? 'default'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {ann.priority}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {ann.category}\n </Badge>\n </div>\n {renderActions(ann)}\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary\">{ann.content}</p>\n <div className=\"flex items-center justify-between mt-3 text-xs text-text-secondary\">\n <span>\n {ann.author} · {ann.authorRole}\n </span>\n <div className=\"flex items-center gap-3\">\n <span className=\"flex items-center gap-1\">\n <Eye className=\"h-3 w-3\" />\n {ann.viewCount}\n </span>\n <span>{ann.createdAt}</span>\n </div>\n </div>\n <div className=\"flex gap-1 mt-2\">\n {ann.tags.map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[9px]\">\n {t}\n </Badge>\n ))}\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit Announcement' : 'New Announcement'}\n description={\n editing\n ? 'Update this announcement for the whole team.'\n : 'Share news and updates across the company.'\n }\n fields={editing ? EDIT_FIELDS : CREATE_FIELDS}\n initialValues={initialValues}\n submitLabel={editing ? 'Save changes' : 'Publish'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete announcement?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `\"${deleteTarget.title}\" will be permanently removed. This cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void confirmDelete();\n }}\n disabled={deleting}\n >\n {deleting ? 'Deleting…' : 'Delete'}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CommunicationsAnnouncementsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsAnnouncementsPagePage.tsx"],"sourcesContent":["import {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Eye, Pencil, Pin, Plus, Trash2 } from 'lucide-react';\nimport { useState } from 'react';\nimport { useCrewFoundryStore, type Announcement } from '../store/crewfoundryStore';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\n\nconst CATEGORY_OPTIONS = [\n { label: 'General', value: 'general' },\n { label: 'HR', value: 'hr' },\n { label: 'IT', value: 'it' },\n { label: 'Emergency', value: 'emergency' },\n { label: 'Event', value: 'event' },\n];\n\nconst PRIORITY_OPTIONS = [\n { label: 'Low', value: 'low' },\n { label: 'Medium', value: 'medium' },\n { label: 'High', value: 'high' },\n { label: 'Urgent', value: 'urgent' },\n];\n\nconst CREATE_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Title', type: 'text', required: true, colSpan: 2 },\n { name: 'content', label: 'Content', type: 'textarea', required: true, colSpan: 2 },\n { name: 'author', label: 'Author', type: 'text', required: true },\n { name: 'authorRole', label: 'Author Role', type: 'text', required: true },\n {\n name: 'category',\n label: 'Category',\n type: 'select',\n required: true,\n options: CATEGORY_OPTIONS,\n placeholder: 'Select a category',\n },\n {\n name: 'priority',\n label: 'Priority',\n type: 'select',\n required: true,\n options: PRIORITY_OPTIONS,\n placeholder: 'Select a priority',\n },\n { name: 'tags', label: 'Tags', type: 'tags', placeholder: 'Add a tag and press Enter' },\n { name: 'isPinned', label: 'Pin to top', type: 'switch' },\n];\n\n// Author/role are immutable after creation (they are not part of the update\n// input), so the edit form omits them.\nconst EDIT_FIELDS: FieldConfig[] = CREATE_FIELDS.filter(\n (field) => field.name !== 'author' && field.name !== 'authorRole'\n);\n\nexport function CommunicationsAnnouncementsPagePage() {\n const { announcements, addAnnouncement, updateAnnouncement, removeAnnouncement } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const form = useFormModal<Announcement>();\n const [deleteTarget, setDeleteTarget] = useState<Announcement | null>(null);\n const [deleting, setDeleting] = useState(false);\n\n const editing = form.context;\n const pinned = announcements.filter((a) => a.isPinned);\n const regular = announcements.filter((a) => !a.isPinned);\n\n const handleSubmit = async (values: FormValues) => {\n const tags = Array.isArray(values.tags) ? values.tags : [];\n if (editing) {\n await updateAnnouncement(editing.id, {\n title: String(values.title),\n content: String(values.content),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n tags,\n });\n showToast('Announcement updated', 'success');\n return;\n }\n await addAnnouncement({\n id: crypto.randomUUID(),\n title: String(values.title),\n content: String(values.content),\n author: String(values.author),\n authorRole: String(values.authorRole),\n category: String(values.category) as Announcement['category'],\n priority: String(values.priority) as Announcement['priority'],\n isPinned: Boolean(values.isPinned),\n createdAt: new Date().toISOString().slice(0, 10),\n viewCount: 0,\n tags,\n });\n showToast('Announcement published', 'success');\n };\n\n const confirmDelete = async () => {\n if (!deleteTarget) {\n return;\n }\n setDeleting(true);\n try {\n await removeAnnouncement(deleteTarget.id);\n showToast('Announcement deleted', 'success');\n setDeleteTarget(null);\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to delete announcement', 'error');\n } finally {\n setDeleting(false);\n }\n };\n\n const initialValues: FormValues | undefined = editing\n ? {\n title: editing.title,\n content: editing.content,\n category: editing.category,\n priority: editing.priority,\n tags: editing.tags,\n isPinned: editing.isPinned,\n }\n : undefined;\n\n const renderActions = (announcement: Announcement) => (\n <div className=\"flex gap-1\">\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7\"\n aria-label={`Edit announcement ${announcement.title}`}\n onClick={() => form.openModal(announcement)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-7 w-7 text-status-error-text\"\n aria-label={`Delete announcement ${announcement.title}`}\n onClick={() => setDeleteTarget(announcement)}\n >\n <Trash2 className=\"h-3.5 w-3.5\" />\n </Button>\n </div>\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>Announcements</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {announcements.length} company announcements\n </p>\n <PagePurpose className=\"mt-1\">\n Broadcast company-wide news and updates so everyone stays informed — post announcements\n here when you have something the whole team needs to see.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n New Announcement\n </Button>\n </div>\n {pinned.length > 0 && (\n <div className=\"space-y-3\">\n <h2 className=\"text-sm font-medium flex items-center gap-1\">\n <Pin className=\"h-4 w-4\" />\n Pinned\n </h2>\n {pinned.map((ann) => (\n <GlassCard key={ann.id} treatment=\"glass\" glow>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{ann.title}</CardTitle>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-1\">\n <Badge\n variant={\n ann.priority === 'urgent'\n ? 'destructive'\n : ann.priority === 'high'\n ? 'default'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {ann.priority}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {ann.category}\n </Badge>\n </div>\n {renderActions(ann)}\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary\">{ann.content}</p>\n <div className=\"flex items-center justify-between mt-3 text-xs text-text-secondary\">\n <span>\n {ann.author} · {ann.authorRole}\n </span>\n <div className=\"flex items-center gap-3\">\n <span className=\"flex items-center gap-1\">\n <Eye className=\"h-3 w-3\" />\n {ann.viewCount}\n </span>\n <span>{ann.createdAt}</span>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n ))}\n </div>\n )}\n {announcements.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"No announcements yet\"\n description=\"Post an announcement to share news with the whole team.\"\n />\n )}\n <div className=\"space-y-3\">\n {regular.map((ann) => (\n <Card key={ann.id}>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{ann.title}</CardTitle>\n <div className=\"flex items-center gap-2\">\n <div className=\"flex gap-1\">\n <Badge\n variant={\n ann.priority === 'urgent'\n ? 'destructive'\n : ann.priority === 'high'\n ? 'default'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {ann.priority}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {ann.category}\n </Badge>\n </div>\n {renderActions(ann)}\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary\">{ann.content}</p>\n <div className=\"flex items-center justify-between mt-3 text-xs text-text-secondary\">\n <span>\n {ann.author} · {ann.authorRole}\n </span>\n <div className=\"flex items-center gap-3\">\n <span className=\"flex items-center gap-1\">\n <Eye className=\"h-3 w-3\" />\n {ann.viewCount}\n </span>\n <span>{ann.createdAt}</span>\n </div>\n </div>\n <div className=\"flex gap-1 mt-2\">\n {ann.tags.map((t) => (\n <Badge key={t} variant=\"outline\" className=\"text-[9px]\">\n {t}\n </Badge>\n ))}\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit Announcement' : 'New Announcement'}\n description={\n editing\n ? 'Update this announcement for the whole team.'\n : 'Share news and updates across the company.'\n }\n fields={editing ? EDIT_FIELDS : CREATE_FIELDS}\n initialValues={initialValues}\n submitLabel={editing ? 'Save changes' : 'Publish'}\n onSubmit={handleSubmit}\n />\n\n <AlertDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete announcement?</AlertDialogTitle>\n <AlertDialogDescription>\n {deleteTarget\n ? `\"${deleteTarget.title}\" will be permanently removed. This cannot be undone.`\n : ''}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>\n <AlertDialogAction\n onClick={(event) => {\n event.preventDefault();\n void confirmDelete();\n }}\n disabled={deleting}\n >\n {deleting ? 'Deleting…' : 'Delete'}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,155 +1,53 @@
1
- import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
2
- import { useMemo as t } from "react";
3
- import { Clock as n, Phone as r, PhoneIncoming as i, PhoneMissed as a, PhoneOutgoing as o } from "lucide-react";
4
- import { jsx as s, jsxs as c } from "react/jsx-runtime";
5
- import { Badge as l, Button as u, Card as d, CardContent as f, CardHeader as p, CardTitle as m, GlassCard as h, GradientText as g, PagePurpose as _ } from "@burdenoff/fe-libs/ui";
6
- import { useGlobalUi as v } from "@burdenoff/fe-libs/shared/providers/shell";
1
+ import { Phone as e } from "lucide-react";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ import { Badge as r, Button as i, Card as a, CardContent as o, CardHeader as s, CardTitle as c, GradientText as l, IllustratedEmptyState as u, PagePurpose as d } from "@burdenoff/fe-libs/ui";
4
+ import { useGlobalUi as f } from "@burdenoff/fe-libs/shared/providers/shell";
7
5
  //#region src/crewfoundry/pages/CommunicationsCallsPagePage.tsx
8
- var y = (e) => {
9
- let t = Math.max(e, 0), n = Math.floor(t / 60), r = t % 60;
10
- return n > 0 ? `${String(n).padStart(2, "0")}:${String(r).padStart(2, "0")}` : `00:${String(r).padStart(2, "0")}`;
11
- }, b = ({ type: e }) => e === "incoming" ? /* @__PURE__ */ s(i, { className: "h-4 w-4 text-status-success-text" }) : e === "outgoing" ? /* @__PURE__ */ s(o, { className: "h-4 w-4 text-status-info-text" }) : /* @__PURE__ */ s(a, { className: "h-4 w-4 text-status-error-text" });
12
- function x() {
13
- let { showToast: i } = v(), { employees: o, teams: x } = e(), S = () => i("Voice calling is on the CrewFoundry roadmap — coming soon.", "info"), C = t(() => o.slice(0, 10).map((e, t) => {
14
- let n = t % 5 == 0 ? "missed" : t % 2 == 0 ? "outgoing" : "incoming", r = x[t % Math.max(x.length, 1)], i = Math.round((e.performanceScore + t) * 3);
15
- return {
16
- id: `call-${e.id}`,
17
- name: `${e.firstName} ${e.lastName}`.trim(),
18
- type: n,
19
- duration: n === "missed" ? "-" : y(i),
20
- time: new Date(e.startDate).toLocaleDateString(),
21
- topic: r ? `${r.name} check-in` : `${e.department} operations update`
22
- };
23
- }), [o, x]), w = C.filter((e) => e.duration !== "-"), T = w.length ? Math.round(w.map((e) => {
24
- let [t, n] = e.duration.split(":").map(Number);
25
- return t * 60 + n;
26
- }).reduce((e, t) => e + t, 0) / w.length) : 0;
27
- return /* @__PURE__ */ c("div", {
6
+ function p() {
7
+ let { showToast: p } = f();
8
+ return /* @__PURE__ */ n("div", {
28
9
  className: "space-y-6 p-6",
29
- children: [
30
- /* @__PURE__ */ c("div", {
31
- className: "flex items-center justify-between",
32
- children: [/* @__PURE__ */ c("div", { children: [
33
- /* @__PURE__ */ s("h1", {
34
- className: "text-2xl font-semibold",
35
- children: /* @__PURE__ */ s(g, { children: "Calls" })
36
- }),
37
- /* @__PURE__ */ s("p", {
38
- className: "text-sm text-text-secondary",
39
- children: "Voice call history and quick dial"
40
- }),
41
- /* @__PURE__ */ s(_, {
42
- className: "mt-1",
43
- children: "Review your call history and start new voice calls with colleagues — a quick way to reach teammates without leaving CrewFoundry."
44
- })
45
- ] }), /* @__PURE__ */ c("div", {
46
- className: "flex items-center gap-2",
47
- children: [/* @__PURE__ */ s(l, {
48
- variant: "secondary",
49
- className: "text-[10px]",
50
- children: "Coming soon"
51
- }), /* @__PURE__ */ c(u, {
52
- size: "sm",
53
- onClick: S,
54
- children: [/* @__PURE__ */ s(r, { className: "mr-1 h-4 w-4" }), "New Call"]
55
- })]
10
+ children: [/* @__PURE__ */ n("div", {
11
+ className: "flex items-center justify-between",
12
+ children: [/* @__PURE__ */ n("div", { children: [
13
+ /* @__PURE__ */ t("h1", {
14
+ className: "text-2xl font-semibold",
15
+ children: /* @__PURE__ */ t(l, { children: "Calls" })
16
+ }),
17
+ /* @__PURE__ */ t("p", {
18
+ className: "text-sm text-text-secondary",
19
+ children: "Voice call history and quick dial"
20
+ }),
21
+ /* @__PURE__ */ t(d, {
22
+ className: "mt-1",
23
+ children: "Review your call history and start new voice calls with colleagues — a quick way to reach teammates without leaving CrewFoundry."
24
+ })
25
+ ] }), /* @__PURE__ */ n("div", {
26
+ className: "flex items-center gap-2",
27
+ children: [/* @__PURE__ */ t(r, {
28
+ variant: "secondary",
29
+ className: "text-[10px]",
30
+ children: "Coming soon"
31
+ }), /* @__PURE__ */ n(i, {
32
+ size: "sm",
33
+ onClick: () => p("Voice calling is on the CrewFoundry roadmap — coming soon.", "info"),
34
+ children: [/* @__PURE__ */ t(e, { className: "mr-1 h-4 w-4" }), "New Call"]
56
35
  })]
57
- }),
58
- /* @__PURE__ */ c("div", {
59
- className: "grid gap-4 md:grid-cols-3",
60
- children: [
61
- /* @__PURE__ */ c(h, {
62
- treatment: "glass",
63
- glow: !0,
64
- className: "border-border-seam",
65
- children: [/* @__PURE__ */ c(p, {
66
- className: "flex flex-row items-center justify-between space-y-0 pb-2",
67
- children: [/* @__PURE__ */ s(m, {
68
- className: "text-sm font-medium",
69
- children: "Total Calls"
70
- }), /* @__PURE__ */ s(r, { className: "h-4 w-4 text-text-secondary" })]
71
- }), /* @__PURE__ */ c(f, { children: [/* @__PURE__ */ s("div", {
72
- className: "text-2xl font-bold tabular-nums",
73
- children: C.length
74
- }), /* @__PURE__ */ s("p", {
75
- className: "text-xs text-text-secondary",
76
- children: "Live call activity"
77
- })] })]
78
- }),
79
- /* @__PURE__ */ c(h, {
80
- treatment: "metric",
81
- className: "border-border-seam",
82
- children: [/* @__PURE__ */ c(p, {
83
- className: "flex flex-row items-center justify-between space-y-0 pb-2",
84
- children: [/* @__PURE__ */ s(m, {
85
- className: "text-sm font-medium",
86
- children: "Missed"
87
- }), /* @__PURE__ */ s(a, { className: "h-4 w-4 text-status-error-text" })]
88
- }), /* @__PURE__ */ s(f, { children: /* @__PURE__ */ s("div", {
89
- className: "text-2xl font-bold tabular-nums",
90
- children: C.filter((e) => e.type === "missed").length
91
- }) })]
92
- }),
93
- /* @__PURE__ */ c(h, {
94
- treatment: "metric",
95
- className: "border-border-seam",
96
- children: [/* @__PURE__ */ c(p, {
97
- className: "flex flex-row items-center justify-between space-y-0 pb-2",
98
- children: [/* @__PURE__ */ s(m, {
99
- className: "text-sm font-medium",
100
- children: "Avg Duration"
101
- }), /* @__PURE__ */ s(n, { className: "h-4 w-4 text-text-secondary" })]
102
- }), /* @__PURE__ */ c(f, { children: [/* @__PURE__ */ s("div", {
103
- className: "text-2xl font-bold tabular-nums",
104
- children: y(T)
105
- }), /* @__PURE__ */ s("p", {
106
- className: "text-xs text-text-secondary",
107
- children: "minutes"
108
- })] })]
109
- })
110
- ]
111
- }),
112
- /* @__PURE__ */ c(d, { children: [/* @__PURE__ */ s(p, { children: /* @__PURE__ */ s(m, { children: "Call History" }) }), /* @__PURE__ */ s(f, {
113
- className: "space-y-3",
114
- children: C.map((e) => /* @__PURE__ */ c("div", {
115
- className: "flex items-center justify-between rounded border p-3",
116
- children: [/* @__PURE__ */ c("div", {
117
- className: "flex items-center gap-3",
118
- children: [/* @__PURE__ */ s(b, { type: e.type }), /* @__PURE__ */ c("div", { children: [/* @__PURE__ */ s("p", {
119
- className: "text-sm font-medium",
120
- children: e.name
121
- }), /* @__PURE__ */ s("p", {
122
- className: "text-xs text-text-secondary",
123
- children: e.topic
124
- })] })]
125
- }), /* @__PURE__ */ c("div", {
126
- className: "flex items-center gap-3",
127
- children: [
128
- /* @__PURE__ */ s("span", {
129
- className: "text-xs text-text-secondary",
130
- children: e.time
131
- }),
132
- /* @__PURE__ */ s(l, {
133
- variant: e.type === "missed" ? "destructive" : "outline",
134
- className: "text-[10px]",
135
- children: e.duration === "-" ? "missed" : e.duration
136
- }),
137
- /* @__PURE__ */ s(u, {
138
- variant: "outline",
139
- size: "sm",
140
- className: "h-7",
141
- "aria-label": `Call ${e.name} (coming soon)`,
142
- onClick: S,
143
- children: /* @__PURE__ */ s(r, { className: "h-3 w-3" })
144
- })
145
- ]
146
- })]
147
- }, e.id))
148
- })] })
149
- ]
36
+ })]
37
+ }), /* @__PURE__ */ n(a, { children: [/* @__PURE__ */ t(s, { children: /* @__PURE__ */ t(c, { children: "Call History" }) }), /* @__PURE__ */ t(o, {
38
+ className: "p-0",
39
+ children: /* @__PURE__ */ t("div", {
40
+ className: "p-6",
41
+ children: /* @__PURE__ */ t(u, {
42
+ illustration: "empty-notifications",
43
+ title: "Voice calling is coming soon",
44
+ description: "In-app voice calls are on the CrewFoundry roadmap. Once the telephony backend ships, your call history and quick dial will appear here."
45
+ })
46
+ })
47
+ })] })]
150
48
  });
151
49
  }
152
50
  //#endregion
153
- export { x as CommunicationsCallsPagePage };
51
+ export { p as CommunicationsCallsPagePage };
154
52
 
155
53
  //# sourceMappingURL=CommunicationsCallsPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommunicationsCallsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsCallsPagePage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { Clock, Phone, PhoneIncoming, PhoneMissed, PhoneOutgoing } from 'lucide-react';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\ntype CallType = 'incoming' | 'outgoing' | 'missed';\n\ntype CallRecord = {\n id: string;\n name: string;\n type: CallType;\n duration: string;\n time: string;\n topic: string;\n};\n\nconst formatDuration = (minutes: number): string => {\n const safeMinutes = Math.max(minutes, 0);\n const hours = Math.floor(safeMinutes / 60);\n const mins = safeMinutes % 60;\n if (hours > 0) {\n return `${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}`;\n }\n return `00:${String(mins).padStart(2, '0')}`;\n};\n\nconst CallIcon = ({ type }: { type: CallType }) => {\n if (type === 'incoming') return <PhoneIncoming className=\"h-4 w-4 text-status-success-text\" />;\n if (type === 'outgoing') return <PhoneOutgoing className=\"h-4 w-4 text-status-info-text\" />;\n return <PhoneMissed className=\"h-4 w-4 text-status-error-text\" />;\n};\n\nexport function CommunicationsCallsPagePage() {\n const { showToast } = useGlobalUi();\n const { employees, teams } = useCrewFoundryStore();\n\n // Voice calling is a planned surface — no telephony backend exists yet, so the\n // dial controls honestly signal their roadmap status.\n const notifyComingSoon = () =>\n showToast('Voice calling is on the CrewFoundry roadmap — coming soon.', 'info');\n\n const callHistory = useMemo<CallRecord[]>(() => {\n const seed = employees.slice(0, 10);\n return seed.map((employee, index) => {\n const callType: CallType =\n index % 5 === 0 ? 'missed' : index % 2 === 0 ? 'outgoing' : 'incoming';\n const topicSource = teams[index % Math.max(teams.length, 1)];\n const durationMinutes = Math.round((employee.performanceScore + index) * 3);\n return {\n id: `call-${employee.id}`,\n name: `${employee.firstName} ${employee.lastName}`.trim(),\n type: callType,\n duration: callType === 'missed' ? '-' : formatDuration(durationMinutes),\n time: new Date(employee.startDate).toLocaleDateString(),\n topic: topicSource\n ? `${topicSource.name} check-in`\n : `${employee.department} operations update`,\n };\n });\n }, [employees, teams]);\n\n const connectedCalls = callHistory.filter((call) => call.duration !== '-');\n const averageMinutes = connectedCalls.length\n ? Math.round(\n connectedCalls\n .map((call) => {\n const [hours, minutes] = call.duration.split(':').map(Number);\n return hours * 60 + minutes;\n })\n .reduce((total, value) => total + value, 0) / connectedCalls.length\n )\n : 0;\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>Calls</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Voice call history and quick dial</p>\n <PagePurpose className=\"mt-1\">Review your call history and start new voice calls with colleagues — a quick way to reach teammates without leaving CrewFoundry.</PagePurpose>\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n Coming soon\n </Badge>\n <Button size=\"sm\" onClick={notifyComingSoon}>\n <Phone className=\"mr-1 h-4 w-4\" />\n New Call\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\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 Calls</CardTitle>\n <Phone className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{callHistory.length}</div>\n <p className=\"text-xs text-text-secondary\">Live call activity</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\">Missed</CardTitle>\n <PhoneMissed className=\"h-4 w-4 text-status-error-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {callHistory.filter((call) => call.type === 'missed').length}\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\">Avg Duration</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{formatDuration(averageMinutes)}</div>\n <p className=\"text-xs text-text-secondary\">minutes</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Call History</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {callHistory.map((call) => (\n <div key={call.id} className=\"flex items-center justify-between rounded border p-3\">\n <div className=\"flex items-center gap-3\">\n <CallIcon type={call.type} />\n <div>\n <p className=\"text-sm font-medium\">{call.name}</p>\n <p className=\"text-xs text-text-secondary\">{call.topic}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-3\">\n <span className=\"text-xs text-text-secondary\">{call.time}</span>\n <Badge\n variant={call.type === 'missed' ? 'destructive' : 'outline'}\n className=\"text-[10px]\"\n >\n {call.duration === '-' ? 'missed' : call.duration}\n </Badge>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-7\"\n aria-label={`Call ${call.name} (coming soon)`}\n onClick={notifyComingSoon}\n >\n <Phone className=\"h-3 w-3\" />\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CommunicationsCallsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsCallsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { Phone } from 'lucide-react';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\n\nexport function CommunicationsCallsPagePage() {\n const { showToast } = useGlobalUi();\n\n // Voice calling is a planned surface — no telephony backend exists yet, so the\n // dial controls honestly signal their roadmap status. Call history is not\n // fabricated in the meantime.\n const notifyComingSoon = () =>\n showToast('Voice calling is on the CrewFoundry roadmap — coming soon.', 'info');\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>Calls</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Voice call history and quick dial</p>\n <PagePurpose className=\"mt-1\">\n Review your call history and start new voice calls with colleagues — a quick way to\n reach teammates without leaving CrewFoundry.\n </PagePurpose>\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n Coming soon\n </Badge>\n <Button size=\"sm\" onClick={notifyComingSoon}>\n <Phone className=\"mr-1 h-4 w-4\" />\n New Call\n </Button>\n </div>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Call History</CardTitle>\n </CardHeader>\n <CardContent className=\"p-0\">\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title=\"Voice calling is coming soon\"\n description=\"In-app voice calls are on the CrewFoundry roadmap. Once the telephony backend ships, your call history and quick dial will appear here.\"\n />\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":";;;;;AAcA,SAAgB,IAA8B;CAC5C,IAAM,EAAE,iBAAc,GAAa;AAQnC,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAc,SAAoB,CAAA;KAC/B,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAA8B;KAAqC,CAAA;IAChF,kBAAC,GAAD;KAAa,WAAU;eAAO;KAGhB,CAAA;IACV,EAAA,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAO,SAAQ;KAAY,WAAU;eAAc;KAE3C,CAAA,EACR,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAnBxB,EAAU,8DAA8D,OAAO;eAmBzE,CACE,kBAAC,GAAD,EAAO,WAAU,gBAAiB,CAAA,EAAA,WAE3B;OACL;MACF;MAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UAAW,gBAAwB,CAAA,EACxB,CAAA,EACb,kBAAC,GAAD;GAAa,WAAU;aACrB,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,cAAa;KACb,OAAM;KACN,aAAY;KACZ,CAAA;IACE,CAAA;GACM,CAAA,CACT,EAAA,CAAA,CACH"}
@@ -2,10 +2,11 @@ import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
2
2
  import { useState as t } from "react";
3
3
  import { Mail as n, MapPin as r, Phone as i, Search as a } from "lucide-react";
4
4
  import { jsx as o, jsxs as s } from "react/jsx-runtime";
5
- import { Badge as c, CardContent as l, GlassCard as u, GradientText as d, IllustratedEmptyState as f, Input as p, PagePurpose as m } from "@burdenoff/fe-libs/ui";
5
+ import { Badge as c, CardContent as l, GlassCard as u, GradientText as d, IllustratedEmptyState as f, Input as p, PagePurpose as m, Pagination as h } from "@burdenoff/fe-libs/ui";
6
+ import { usePagination as g } from "@burdenoff/fe-libs/shared/hooks";
6
7
  //#region src/crewfoundry/pages/CommunicationsDirectoryPagePage.tsx
7
- function h() {
8
- let { employees: h } = e(), [g, _] = t(""), v = h.filter((e) => `${e.firstName} ${e.lastName} ${e.department} ${e.position} ${e.email}`.toLowerCase().includes(g.toLowerCase()));
8
+ function _() {
9
+ let { employees: _ } = e(), [v, y] = t(""), { page: b, pageSize: x, setPage: S } = g({ initialPageSize: 12 }), C = _.filter((e) => `${e.firstName} ${e.lastName} ${e.department} ${e.position} ${e.email}`.toLowerCase().includes(v.toLowerCase())), w = C.slice((b - 1) * x, (b - 1) * x + x);
9
10
  return /* @__PURE__ */ s("div", {
10
11
  className: "space-y-6 p-6",
11
12
  children: [
@@ -16,7 +17,7 @@ function h() {
16
17
  }),
17
18
  /* @__PURE__ */ s("p", {
18
19
  className: "text-sm text-text-secondary",
19
- children: [h.length, " team members"]
20
+ children: [_.length, " team members"]
20
21
  }),
21
22
  /* @__PURE__ */ o(m, {
22
23
  className: "mt-1",
@@ -28,22 +29,24 @@ function h() {
28
29
  children: [/* @__PURE__ */ o(a, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary" }), /* @__PURE__ */ o(p, {
29
30
  placeholder: "Search by name, role, department...",
30
31
  className: "pl-9",
31
- value: g,
32
- onChange: (e) => _(e.target.value)
32
+ value: v,
33
+ onChange: (e) => {
34
+ y(e.target.value), S(1);
35
+ }
33
36
  })]
34
37
  }),
35
38
  /* @__PURE__ */ o("div", {
36
39
  className: "grid gap-3 md:grid-cols-2 lg:grid-cols-3",
37
- children: v.length === 0 ? /* @__PURE__ */ o("div", {
40
+ children: C.length === 0 ? /* @__PURE__ */ o("div", {
38
41
  className: "col-span-full",
39
42
  children: /* @__PURE__ */ o(f, {
40
43
  illustration: "empty-people",
41
44
  title: "No people found",
42
45
  description: "Try a different search term to find a colleague."
43
46
  })
44
- }) : v.map((e, t) => /* @__PURE__ */ o(u, {
45
- treatment: t === 0 ? "glass" : "plain",
46
- glow: t === 0,
47
+ }) : w.map((e, t) => /* @__PURE__ */ o(u, {
48
+ treatment: t === 0 && b === 1 ? "glass" : "plain",
49
+ glow: t === 0 && b === 1,
47
50
  children: /* @__PURE__ */ o(l, {
48
51
  className: "p-4",
49
52
  children: /* @__PURE__ */ s("div", {
@@ -101,11 +104,17 @@ function h() {
101
104
  })
102
105
  })
103
106
  }, e.id))
107
+ }),
108
+ C.length > x && /* @__PURE__ */ o(h, {
109
+ page: b,
110
+ pageSize: x,
111
+ totalItems: C.length,
112
+ onPageChange: S
104
113
  })
105
114
  ]
106
115
  });
107
116
  }
108
117
  //#endregion
109
- export { h as CommunicationsDirectoryPagePage };
118
+ export { _ as CommunicationsDirectoryPagePage };
110
119
 
111
120
  //# sourceMappingURL=CommunicationsDirectoryPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CommunicationsDirectoryPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsDirectoryPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n CardContent,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { Mail, MapPin, Phone, Search } from 'lucide-react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function CommunicationsDirectoryPagePage() {\n const { employees } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n\n const filtered = employees.filter((e) =>\n `${e.firstName} ${e.lastName} ${e.department} ${e.position} ${e.email}`\n .toLowerCase()\n .includes(search.toLowerCase())\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Company Directory</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{employees.length} team members</p>\n <PagePurpose className=\"mt-1\">Find anyone in the company by name, role, or department — use it to discover who to contact and how to reach them.</PagePurpose>\n </div>\n <div className=\"relative max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\" />\n <Input\n placeholder=\"Search by name, role, department...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2 lg:grid-cols-3\">\n {filtered.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState illustration=\"empty-people\" title=\"No people found\" description=\"Try a different search term to find a colleague.\" />\n </div>\n ) : (\n filtered.map((emp, _idx) => (\n <GlassCard key={emp.id} treatment={_idx === 0 ? 'glass' : 'plain'} glow={_idx === 0}>\n <CardContent className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"h-10 w-10 rounded-full bg-action-primary-bg/10 flex items-center justify-center text-sm font-bold flex-shrink-0\">\n {emp.firstName[0]}\n {emp.lastName[0]}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium\">\n {emp.firstName} {emp.lastName}\n </p>\n <p className=\"text-xs text-text-secondary\">{emp.position}</p>\n <Badge variant=\"outline\" className=\"mt-1 text-[10px]\">\n {emp.department}\n </Badge>\n <div className=\"mt-2 space-y-1\">\n <p className=\"text-xs text-text-secondary flex items-center gap-1\">\n <Mail className=\"h-3 w-3\" />\n {emp.email}\n </p>\n <p className=\"text-xs text-text-secondary flex items-center gap-1\">\n <Phone className=\"h-3 w-3\" />\n {emp.phone}\n </p>\n <p className=\"text-xs text-text-secondary flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" />\n {emp.location}\n </p>\n </div>\n </div>\n <Badge\n variant={emp.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {emp.status.replace('_', ' ')}\n </Badge>\n </div>\n </CardContent>\n </GlassCard>\n )))}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;AAaA,SAAgB,IAAkC;CAChD,IAAM,EAAE,iBAAc,GAAqB,EACrC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAElC,IAAW,EAAU,QAAQ,MACjC,GAAG,EAAE,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,WAAW,GAAG,EAAE,SAAS,GAAG,EAAE,QAC7D,aAAa,CACb,SAAS,EAAO,aAAa,CAAC,CAClC;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAc,qBAAgC,CAAA;KAC3C,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAAb,CAA4C,EAAU,QAAO,gBAAiB;;IAC9E,kBAAC,GAAD;KAAa,WAAU;eAAO;KAAgI,CAAA;IAC1J,EAAA,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;KACE,aAAY;KACZ,WAAU;KACV,OAAO;KACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;KAC1C,CAAA,CACE;;GACN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,WAAW,IACnB,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAuB,cAAa;MAAe,OAAM;MAAkB,aAAY;MAAqD,CAAA;KACxI,CAAA,GAEN,EAAS,KAAK,GAAK,MACnB,kBAAC,GAAD;KAAwB,WAAW,MAAS,IAAI,UAAU;KAAS,MAAM,MAAS;eAChF,kBAAC,GAAD;MAAa,WAAU;gBACrB,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACG,EAAI,UAAU,IACd,EAAI,SAAS,GACV;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAI;YAAU;YAAE,EAAI;YACnB;;UACJ,kBAAC,KAAD;WAAG,WAAU;qBAA+B,EAAI;WAAa,CAAA;UAC7D,kBAAC,GAAD;WAAO,SAAQ;WAAU,WAAU;qBAChC,EAAI;WACC,CAAA;UACR,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,KAAD;aAAG,WAAU;uBAAb,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC3B,EAAI,MACH;;YACJ,kBAAC,KAAD;aAAG,WAAU;uBAAb,CACE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,EAC5B,EAAI,MACH;;YACJ,kBAAC,KAAD;aAAG,WAAU;uBAAb,CACE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC7B,EAAI,SACH;;YACA;;UACF;;QACN,kBAAC,GAAD;SACE,SAAS,EAAI,WAAW,WAAW,YAAY;SAC/C,WAAU;mBAET,EAAI,OAAO,QAAQ,KAAK,IAAI;SACvB,CAAA;QACJ;;MACM,CAAA;KACJ,EAtCI,EAAI,GAsCR,CACZ;IACE,CAAA;GACF"}
1
+ {"version":3,"file":"CommunicationsDirectoryPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/CommunicationsDirectoryPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n CardContent,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Mail, MapPin, Phone, Search } from 'lucide-react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function CommunicationsDirectoryPagePage() {\n const { employees } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const { page, pageSize, setPage } = usePagination({ initialPageSize: 12 });\n\n const filtered = employees.filter((e) =>\n `${e.firstName} ${e.lastName} ${e.department} ${e.position} ${e.email}`\n .toLowerCase()\n .includes(search.toLowerCase())\n );\n\n const pageEmployees = filtered.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Company Directory</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{employees.length} team members</p>\n <PagePurpose className=\"mt-1\">\n Find anyone in the company by name, role, or department — use it to discover who to\n contact and how to reach them.\n </PagePurpose>\n </div>\n <div className=\"relative max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\" />\n <Input\n placeholder=\"Search by name, role, department...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n <div className=\"grid gap-3 md:grid-cols-2 lg:grid-cols-3\">\n {filtered.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No people found\"\n description=\"Try a different search term to find a colleague.\"\n />\n </div>\n ) : (\n pageEmployees.map((emp, _idx) => (\n <GlassCard\n key={emp.id}\n treatment={_idx === 0 && page === 1 ? 'glass' : 'plain'}\n glow={_idx === 0 && page === 1}\n >\n <CardContent className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"h-10 w-10 rounded-full bg-action-primary-bg/10 flex items-center justify-center text-sm font-bold flex-shrink-0\">\n {emp.firstName[0]}\n {emp.lastName[0]}\n </div>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium\">\n {emp.firstName} {emp.lastName}\n </p>\n <p className=\"text-xs text-text-secondary\">{emp.position}</p>\n <Badge variant=\"outline\" className=\"mt-1 text-[10px]\">\n {emp.department}\n </Badge>\n <div className=\"mt-2 space-y-1\">\n <p className=\"text-xs text-text-secondary flex items-center gap-1\">\n <Mail className=\"h-3 w-3\" />\n {emp.email}\n </p>\n <p className=\"text-xs text-text-secondary flex items-center gap-1\">\n <Phone className=\"h-3 w-3\" />\n {emp.phone}\n </p>\n <p className=\"text-xs text-text-secondary flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" />\n {emp.location}\n </p>\n </div>\n </div>\n <Badge\n variant={emp.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {emp.status.replace('_', ' ')}\n </Badge>\n </div>\n </CardContent>\n </GlassCard>\n ))\n )}\n </div>\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAeA,SAAgB,IAAkC;CAChD,IAAM,EAAE,iBAAc,GAAqB,EACrC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,EAAE,SAAM,aAAU,eAAY,EAAc,EAAE,iBAAiB,IAAI,CAAC,EAEpE,IAAW,EAAU,QAAQ,MACjC,GAAG,EAAE,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,WAAW,GAAG,EAAE,SAAS,GAAG,EAAE,QAC7D,aAAa,CACb,SAAS,EAAO,aAAa,CAAC,CAClC,EAEK,IAAgB,EAAS,OAAO,IAAO,KAAK,IAAW,IAAO,KAAK,IAAW,EAAS;AAE7F,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,MAAD;KAAI,WAAU;eACZ,kBAAC,GAAD,EAAA,UAAc,qBAAgC,CAAA;KAC3C,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eAAb,CAA4C,EAAU,QAAO,gBAAiB;;IAC9E,kBAAC,GAAD;KAAa,WAAU;eAAO;KAGhB,CAAA;IACV,EAAA,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;KACE,aAAY;KACZ,WAAU;KACV,OAAO;KACP,WAAW,MAAM;AAEf,MADA,EAAU,EAAE,OAAO,MAAM,EACzB,EAAQ,EAAE;;KAEZ,CAAA,CACE;;GACN,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAS,WAAW,IACnB,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MACE,cAAa;MACb,OAAM;MACN,aAAY;MACZ,CAAA;KACE,CAAA,GAEN,EAAc,KAAK,GAAK,MACtB,kBAAC,GAAD;KAEE,WAAW,MAAS,KAAK,MAAS,IAAI,UAAU;KAChD,MAAM,MAAS,KAAK,MAAS;eAE7B,kBAAC,GAAD;MAAa,WAAU;gBACrB,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACG,EAAI,UAAU,IACd,EAAI,SAAS,GACV;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAI;YAAU;YAAE,EAAI;YACnB;;UACJ,kBAAC,KAAD;WAAG,WAAU;qBAA+B,EAAI;WAAa,CAAA;UAC7D,kBAAC,GAAD;WAAO,SAAQ;WAAU,WAAU;qBAChC,EAAI;WACC,CAAA;UACR,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,KAAD;aAAG,WAAU;uBAAb,CACE,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,EAC3B,EAAI,MACH;;YACJ,kBAAC,KAAD;aAAG,WAAU;uBAAb,CACE,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA,EAC5B,EAAI,MACH;;YACJ,kBAAC,KAAD;aAAG,WAAU;uBAAb,CACE,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA,EAC7B,EAAI,SACH;;YACA;;UACF;;QACN,kBAAC,GAAD;SACE,SAAS,EAAI,WAAW,WAAW,YAAY;SAC/C,WAAU;mBAET,EAAI,OAAO,QAAQ,KAAK,IAAI;SACvB,CAAA;QACJ;;MACM,CAAA;KACJ,EAzCL,EAAI,GAyCC,CACZ;IAEA,CAAA;GACL,EAAS,SAAS,KACjB,kBAAC,GAAD;IACQ;IACI;IACV,YAAY,EAAS;IACrB,cAAc;IACd,CAAA;GAEA"}