@burdenoff/microfe-crewfoundry 2026.1013.1 → 2026.1029.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.
- package/dist/crewfoundry/CrewFoundryRoot.d.ts.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoot.js +160 -256
- package/dist/crewfoundry/CrewFoundryRoot.js.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoutes.d.ts.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoutes.js +152 -192
- package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
- package/dist/crewfoundry/components/InlineError.d.ts +5 -0
- package/dist/crewfoundry/components/InlineError.d.ts.map +1 -0
- package/dist/crewfoundry/components/InlineError.js +17 -0
- package/dist/crewfoundry/components/InlineError.js.map +1 -0
- package/dist/crewfoundry/components/forms/FormModal.d.ts +25 -1
- package/dist/crewfoundry/components/forms/FormModal.d.ts.map +1 -1
- package/dist/crewfoundry/components/forms/FormModal.js +151 -136
- package/dist/crewfoundry/components/forms/FormModal.js.map +1 -1
- package/dist/crewfoundry/components/marketplace/BookServiceModal.d.ts +14 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.js +75 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.js.map +1 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.d.ts +24 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.js +47 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.js.map +1 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.d.ts +12 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.js +123 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.js.map +1 -0
- package/dist/crewfoundry/components/viz/CertificateCard.js +4 -118
- package/dist/crewfoundry/components/viz/PathSteps.js +3 -68
- package/dist/crewfoundry/components/viz/lib/geometry.js +5 -13
- package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -1
- package/dist/crewfoundry/lib/analytics.d.ts +1 -6
- package/dist/crewfoundry/lib/analytics.d.ts.map +1 -1
- package/dist/crewfoundry/lib/analytics.js +3 -18
- package/dist/crewfoundry/lib/analytics.js.map +1 -1
- package/dist/crewfoundry/lib/companyFinance.d.ts +3 -7
- package/dist/crewfoundry/lib/companyFinance.d.ts.map +1 -1
- package/dist/crewfoundry/lib/companyFinance.js +3 -8
- package/dist/crewfoundry/lib/companyFinance.js.map +1 -1
- package/dist/crewfoundry/lib/learning.d.ts +12 -0
- package/dist/crewfoundry/lib/learning.d.ts.map +1 -0
- package/dist/crewfoundry/lib/learning.js +26 -0
- package/dist/crewfoundry/lib/learning.js.map +1 -0
- package/dist/crewfoundry/lib/marketplace.d.ts +30 -0
- package/dist/crewfoundry/lib/marketplace.d.ts.map +1 -0
- package/dist/crewfoundry/lib/marketplace.js +91 -0
- package/dist/crewfoundry/lib/marketplace.js.map +1 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.d.ts +7 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.d.ts.map +1 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.js +25 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.js.map +1 -0
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +12 -25
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +49 -49
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +145 -157
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +14 -14
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +14 -14
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js +71 -33
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +260 -115
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +199 -166
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +118 -104
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +247 -173
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.d.ts +12 -0
- package/dist/crewfoundry/pages/LearningCoursesPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +307 -293
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.d.ts +2 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.d.ts.map +1 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.js +223 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.d.ts +4 -0
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.js +115 -160
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js +165 -75
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.d.ts +7 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.d.ts.map +1 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.js +175 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +182 -223
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js +168 -227
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +127 -290
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.d.ts +139 -79
- package/dist/crewfoundry/store/crewfoundryStore.d.ts.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js +323 -244
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/dist/crewfoundry/utils/dateUtils.d.ts +52 -0
- package/dist/crewfoundry/utils/dateUtils.d.ts.map +1 -0
- package/dist/crewfoundry/utils/dateUtils.js +31 -0
- package/dist/crewfoundry/utils/dateUtils.js.map +1 -0
- package/dist/generated/global-types.d.ts +5145 -1627
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +100373 -34602
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +0 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
- package/dist/crewfoundry/components/viz/CertificateCard.js.map +0 -1
- package/dist/crewfoundry/components/viz/PathSteps.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js +0 -53
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +0 -107
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js +0 -62
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js +0 -37
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +0 -111
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +0 -309
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.js +0 -200
- package/dist/crewfoundry/pages/LearningLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +0 -190
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +0 -230
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +0 -293
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js +0 -163
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js +0 -365
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +0 -224
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +0 -180
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js +0 -38
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js +0 -204
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js +0 -55
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +0 -335
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js +0 -160
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js +0 -80
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js +0 -191
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js.map +0 -1
- package/dist/crewfoundry/utils/courseProgress.d.ts +0 -33
- package/dist/crewfoundry/utils/courseProgress.d.ts.map +0 -1
- package/dist/crewfoundry/utils/courseProgress.js +0 -46
- package/dist/crewfoundry/utils/courseProgress.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringJobsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobsPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n Badge,\n Button,\n CardContent,\n CardHeader,\n CardTitle,\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 { Briefcase, DollarSign, MapPin, Plus, Search, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, SegmentedTabs, type SegmentedTab } from '../components/viz';\n\n/** Ordered status filter set (only rendered when a status has postings). */\nconst STATUS_ORDER: JobPosting['status'][] = ['active', 'draft', 'paused', 'closed', 'filled'];\n\n/** Relative \"Posted N days ago\" label from an ISO date-only string. */\nfunction relativePostedLabel(dateStr: string): string {\n const posted = new Date(dateStr);\n if (Number.isNaN(posted.getTime())) {\n return dateStr;\n }\n const days = Math.floor((Date.now() - posted.getTime()) / 86_400_000);\n if (days <= 0) {\n return 'Today';\n }\n if (days === 1) {\n return '1 day ago';\n }\n if (days < 7) {\n return `${days} days ago`;\n }\n if (days < 30) {\n const weeks = Math.floor(days / 7);\n return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;\n }\n const months = Math.floor(days / 30);\n return months === 1 ? '1 month ago' : `${months} months ago`;\n}\n\n/** A label→value row matching the mock's clean scannable card layout. */\nfunction InfoRow({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"text-text-secondary\">{label}</span>\n <span className=\"font-medium text-text-primary text-right\">{children}</span>\n </div>\n );\n}\n\nexport function HiringJobsPagePage() {\n const tr = useTr();\n const navigate = useNavigate();\n const { jobPostings, candidates } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<string>('all');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const filtered = useMemo(() => {\n const query = search.toLowerCase();\n return jobPostings.filter((j) => {\n const matchesSearch = `${j.title} ${j.department} ${j.location}`\n .toLowerCase()\n .includes(query);\n const matchesStatus = statusFilter === 'all' || j.status === statusFilter;\n return matchesSearch && matchesStatus;\n });\n }, [jobPostings, search, statusFilter]);\n\n const paged = filtered.slice(offset, offset + pageSize);\n const openJob = (id: string) =>\n navigate(`../hiring/job-details-page?id=${encodeURIComponent(id)}`);\n const activeJobs = jobPostings.filter((j) => j.status === 'active').length;\n const totalApplicants = jobPostings.reduce((s, j) => s + j.applicants, 0);\n\n // Live per-status counts drive the filter pills.\n const statusTabs = useMemo<SegmentedTab[]>(() => {\n const tabs: SegmentedTab[] = [{ id: 'all', label: 'All', count: jobPostings.length }];\n for (const status of STATUS_ORDER) {\n const count = jobPostings.filter((j) => j.status === status).length;\n if (count > 0 || status === 'active') {\n tabs.push({ id: status, label: formatEnumLabel(status), count });\n }\n }\n return tabs;\n }, [jobPostings]);\n\n // Real applicant faces per job, matched by the candidate's applied-for position.\n const facesByJob = useMemo(() => {\n const map = new Map<string, { name: string }[]>();\n for (const job of jobPostings) {\n const title = job.title.trim().toLowerCase();\n const people = candidates\n .filter((c) => c.position.trim().toLowerCase() === title)\n .map((c) => ({ name: `${c.firstName} ${c.lastName}`.trim() }));\n map.set(job.id, people);\n }\n return map;\n }, [jobPostings, candidates]);\n\n const setFilter = (id: string) => {\n setStatusFilter(id);\n setPage(1);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{tr('crewfoundry.hiringJobs.title', 'Job Postings')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeJobs} {tr('crewfoundry.hiringJobs.summary.activePositions', 'active positions')}{' '}\n · {totalApplicants}{' '}\n {tr('crewfoundry.hiringJobs.summary.totalApplicants', 'total applicants')}\n </p>\n <PagePurpose className=\"mt-1\">\n All your open and past job postings in one place — use it to monitor applicant volume\n and manage what roles you're actively hiring for.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => navigate('../hiring/create-job-page')}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {tr('crewfoundry.hiringJobs.actions.postNewJob', 'Post New Job')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"-mx-1 overflow-x-auto px-1\">\n <SegmentedTabs\n tabs={statusTabs}\n value={statusFilter}\n onChange={setFilter}\n aria-label=\"Filter job postings by status\"\n />\n </div>\n <div className=\"relative w-full 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={tr('crewfoundry.hiringJobs.searchPlaceholder', 'Search positions...')}\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n </div>\n\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job postings found\"\n description=\"Try adjusting your search or status filter, or create a new job posting.\"\n />\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {paged.map((job) => {\n const faces = facesByJob.get(job.id) ?? [];\n return (\n <GlassCard\n key={job.id}\n treatment=\"glass\"\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${job.title}`}\n className=\"flex cursor-pointer flex-col transition-shadow hover:shadow-elevation-2 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n onClick={() => openJob(job.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openJob(job.id);\n }\n }}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-lg leading-snug\">{job.title}</CardTitle>\n <Badge\n variant={\n job.status === 'active'\n ? 'default'\n : job.status === 'draft'\n ? 'secondary'\n : 'outline'\n }\n >\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.type)}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.experienceLevel)}\n </Badge>\n </div>\n </CardHeader>\n <CardContent className=\"flex flex-1 flex-col gap-2.5\">\n <InfoRow label={tr('crewfoundry.hiringJobs.card.department', 'Department')}>\n <span className=\"inline-flex items-center gap-1\">\n <Briefcase className=\"h-3 w-3 text-text-secondary\" />\n {job.department || '—'}\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.location', 'Location')}>\n <span className=\"inline-flex items-center gap-1\">\n <MapPin className=\"h-3 w-3 text-text-secondary\" />\n {job.location || '—'}\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.salary', 'Salary')}>\n <span className=\"inline-flex items-center gap-0.5 tabular-nums\">\n <DollarSign className=\"h-3 w-3 text-text-secondary\" />\n {(job.salaryMin / 1000).toFixed(0)}k – ${(job.salaryMax / 1000).toFixed(0)}k\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.applicantsLabel', 'Applicants')}>\n <span className=\"inline-flex items-center gap-2\">\n {faces.length > 0 && <AvatarStack people={faces} max={4} size={22} />}\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Users className=\"h-3 w-3 text-text-secondary\" />\n {job.applicants}\n </span>\n </span>\n </InfoRow>\n <div className=\"mt-auto pt-1\">\n <InfoRow label={tr('crewfoundry.hiringJobs.card.posted', 'Posted')}>\n <span className=\"text-text-secondary font-normal\">\n {relativePostedLabel(job.postedDate)}\n </span>\n </InfoRow>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\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":"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"}
|
|
1
|
+
{"version":3,"file":"HiringJobsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobsPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n Badge,\n Button,\n CardContent,\n CardHeader,\n CardTitle,\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 { Briefcase, DollarSign, MapPin, Plus, Search, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { localDayDiff, parseLocalDate } from '../utils/dateUtils';\nimport { AvatarStack, SegmentedTabs, type SegmentedTab } from '../components/viz';\n\n/** Ordered status filter set (only rendered when a status has postings). */\nconst STATUS_ORDER: JobPosting['status'][] = ['active', 'draft', 'paused', 'closed', 'filled'];\n\n/** Relative \"Posted N days ago\" label from an ISO date-only string. */\nfunction relativePostedLabel(dateStr: string): string {\n const posted = new Date(dateStr);\n if (Number.isNaN(posted.getTime())) {\n return dateStr;\n }\n const days = Math.floor((Date.now() - posted.getTime()) / 86_400_000);\n if (days <= 0) {\n return 'Today';\n }\n if (days === 1) {\n return '1 day ago';\n }\n if (days < 7) {\n return `${days} days ago`;\n }\n if (days < 30) {\n const weeks = Math.floor(days / 7);\n return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;\n }\n const months = Math.floor(days / 30);\n return months === 1 ? '1 month ago' : `${months} months ago`;\n}\n\n/**\n * Format a salary value. The previous `(value / 1000).toFixed(0)` truncated\n * any value under 1000 to 0, so an entry-level posting (Min 12, Max 13)\n * displayed as the misleading \"$0k – $0k\". Apply per-value so a range can\n * legitimately span the 1000 threshold — e.g. Min 100, Max 1000 renders\n * as \"$100 – $1k\" rather than grouping them as \"$0k – $1k\". The 0 case is\n * special-cased to \"$0\" so unset salaries don't read as \"—\" or \"0k\".\n */\nfunction formatSalary(value: number): string {\n if (value === 0) return '$0';\n if (value < 1000) return `$${value.toLocaleString()}`;\n return `$${Math.round(value / 1000)}k`;\n}\n\n/**\n * Deadline label from an ISO date-only string or null. Renders relative\n * phrasing when the deadline is upcoming (\"Closes in 3 days\", \"Closes\n * today\", \"Closed 2 days ago\") and falls back to the raw date when the\n * value can't be parsed. Returns \"No deadline\" for null.\n *\n * Compares against local calendar days (not raw timestamps) so a deadline\n * picked as \"today\" never reads as \"Closed yesterday\" for users in\n * negative-UTC offsets — `new Date('YYYY-MM-DD')` parses at UTC midnight\n * which would otherwise put it 17-20 hours in the past by local noon.\n */\nfunction formatDeadlineLabel(dateStr: string | null): string {\n if (!dateStr) {\n return 'No deadline';\n }\n const deadline = parseLocalDate(dateStr);\n if (!deadline) {\n return dateStr;\n }\n const days = localDayDiff(new Date(), deadline);\n if (days > 1) {\n return `Closes in ${days} days`;\n }\n if (days === 1) {\n return 'Closes tomorrow';\n }\n if (days === 0) {\n return 'Closes today';\n }\n if (days === -1) {\n return 'Closed yesterday';\n }\n return `Closed ${Math.abs(days)} days ago`;\n}\n\nfunction formatSalaryRange(min: number, max: number): string {\n return `${formatSalary(min)} – ${formatSalary(max)}`;\n}\n\n/** A label→value row matching the mock's clean scannable card layout. */\nfunction InfoRow({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"text-text-secondary\">{label}</span>\n <span className=\"font-medium text-text-primary text-right\">{children}</span>\n </div>\n );\n}\n\nexport function HiringJobsPagePage() {\n const tr = useTr();\n const navigate = useNavigate();\n const { jobPostings, candidates } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<string>('all');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const filtered = useMemo(() => {\n const query = search.toLowerCase();\n return jobPostings.filter((j) => {\n const matchesSearch = `${j.title} ${j.department} ${j.location}`\n .toLowerCase()\n .includes(query);\n const matchesStatus = statusFilter === 'all' || j.status === statusFilter;\n return matchesSearch && matchesStatus;\n });\n }, [jobPostings, search, statusFilter]);\n\n const paged = filtered.slice(offset, offset + pageSize);\n const openJob = (id: string) =>\n navigate(`../hiring/job-details-page?id=${encodeURIComponent(id)}`);\n const activeJobs = jobPostings.filter((j) => j.status === 'active').length;\n const totalApplicants = jobPostings.reduce((s, j) => s + j.applicants, 0);\n\n // Live per-status counts drive the filter pills.\n const statusTabs = useMemo<SegmentedTab[]>(() => {\n const tabs: SegmentedTab[] = [{ id: 'all', label: 'All', count: jobPostings.length }];\n for (const status of STATUS_ORDER) {\n const count = jobPostings.filter((j) => j.status === status).length;\n if (count > 0 || status === 'active') {\n tabs.push({ id: status, label: formatEnumLabel(status), count });\n }\n }\n return tabs;\n }, [jobPostings]);\n\n // Real applicant faces per job, matched by the candidate's applied-for position.\n const facesByJob = useMemo(() => {\n const map = new Map<string, { name: string }[]>();\n for (const job of jobPostings) {\n const title = job.title.trim().toLowerCase();\n const people = candidates\n .filter((c) => c.position.trim().toLowerCase() === title)\n .map((c) => ({ name: `${c.firstName} ${c.lastName}`.trim() }));\n map.set(job.id, people);\n }\n return map;\n }, [jobPostings, candidates]);\n\n const setFilter = (id: string) => {\n setStatusFilter(id);\n setPage(1);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{tr('crewfoundry.hiringJobs.title', 'Job Postings')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeJobs} {tr('crewfoundry.hiringJobs.summary.activePositions', 'active positions')}{' '}\n · {totalApplicants}{' '}\n {tr('crewfoundry.hiringJobs.summary.totalApplicants', 'total applicants')}\n </p>\n <PagePurpose className=\"mt-1\">\n All your open and past job postings in one place — use it to monitor applicant volume\n and manage what roles you're actively hiring for.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => navigate('../hiring/create-job-page')}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {tr('crewfoundry.hiringJobs.actions.postNewJob', 'Post New Job')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"-mx-1 overflow-x-auto px-1\">\n <SegmentedTabs\n tabs={statusTabs}\n value={statusFilter}\n onChange={setFilter}\n aria-label=\"Filter job postings by status\"\n />\n </div>\n <div className=\"relative w-full 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={tr('crewfoundry.hiringJobs.searchPlaceholder', 'Search positions...')}\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n </div>\n\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job postings found\"\n description=\"Try adjusting your search or status filter, or create a new job posting.\"\n />\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {paged.map((job) => {\n const faces = facesByJob.get(job.id) ?? [];\n return (\n <GlassCard\n key={job.id}\n treatment=\"glass\"\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${job.title}`}\n className=\"flex cursor-pointer flex-col transition-shadow hover:shadow-elevation-2 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n onClick={() => openJob(job.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openJob(job.id);\n }\n }}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-lg leading-snug\">{job.title}</CardTitle>\n <Badge\n variant={\n job.status === 'active'\n ? 'default'\n : job.status === 'draft'\n ? 'secondary'\n : 'outline'\n }\n >\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.type)}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.experienceLevel)}\n </Badge>\n </div>\n </CardHeader>\n <CardContent className=\"flex flex-1 flex-col gap-2.5\">\n <InfoRow label={tr('crewfoundry.hiringJobs.card.department', 'Department')}>\n <span className=\"inline-flex items-center gap-1\">\n <Briefcase className=\"h-3 w-3 text-text-secondary\" />\n {job.department || '—'}\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.location', 'Location')}>\n <span className=\"inline-flex items-center gap-1\">\n <MapPin className=\"h-3 w-3 text-text-secondary\" />\n {job.location || '—'}\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.salary', 'Salary')}>\n <span className=\"inline-flex items-center gap-0.5 tabular-nums\">\n <DollarSign className=\"h-3 w-3 text-text-secondary\" />\n {formatSalaryRange(job.salaryMin, job.salaryMax)}\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.applicantsLabel', 'Applicants')}>\n <span className=\"inline-flex items-center gap-2\">\n {faces.length > 0 && <AvatarStack people={faces} max={4} size={22} />}\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Users className=\"h-3 w-3 text-text-secondary\" />\n {job.applicants}\n </span>\n </span>\n </InfoRow>\n <div className=\"mt-auto flex flex-col gap-2.5\">\n <InfoRow label={tr('crewfoundry.hiringJobs.card.posted', 'Posted')}>\n <span className=\"text-text-secondary font-normal\">\n {relativePostedLabel(job.postedDate)}\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.deadline', 'Deadline')}>\n <span className=\"text-text-secondary font-normal\">\n {formatDeadlineLabel(job.deadline)}\n </span>\n </InfoRow>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\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":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningCourseDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/LearningCourseDetailsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LearningCourseDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/LearningCourseDetailsPagePage.tsx"],"names":[],"mappings":"AAuCA,wBAAgB,6BAA6B,gCAqT5C"}
|
|
@@ -1,200 +1,274 @@
|
|
|
1
1
|
import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
2
|
+
import { StatCard as t } from "../components/viz/StatCard.js";
|
|
3
|
+
import { AvatarStack as n } from "../components/viz/AvatarStack.js";
|
|
4
|
+
import "../components/viz/index.js";
|
|
5
|
+
import { FormModal as r, useFormModal as i } from "../components/forms/FormModal.js";
|
|
6
|
+
import { formatDate as a } from "../utils/dateUtils.js";
|
|
7
|
+
import { InlineError as o } from "../components/InlineError.js";
|
|
8
|
+
import { ENROLLMENT_STATUS_LABEL as s, ENROLLMENT_STATUS_OPTIONS as c, formatDuration as ee } from "../lib/learning.js";
|
|
9
|
+
import { useEnsureLoaded as te } from "../lib/useEnsureLoaded.js";
|
|
10
|
+
import { COURSE_FIELDS as ne, courseToValues as re, valuesToCourseInput as ie } from "./LearningCoursesPagePage.js";
|
|
11
|
+
import { useMemo as ae, useState as l } from "react";
|
|
12
|
+
import { ArrowLeft as oe, Clock as u, ExternalLink as d, Pencil as f, Trash2 as se, UserPlus as ce } from "lucide-react";
|
|
13
|
+
import { useNavigate as p, useSearchParams as m } from "react-router";
|
|
14
|
+
import { jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
15
|
+
import { Badge as _, Button as v, Card as y, CardContent as b, CardDescription as x, CardHeader as S, CardTitle as C, GradientText as le, IllustratedEmptyState as w, ResponsiveTable as T, Select as E, SelectContent as D, SelectItem as O, SelectTrigger as k, SelectValue as A, Spinner as j } from "@burdenoff/fe-libs/ui";
|
|
9
16
|
//#region src/crewfoundry/pages/LearningCourseDetailsPagePage.tsx
|
|
10
|
-
function
|
|
11
|
-
let
|
|
12
|
-
|
|
17
|
+
function M() {
|
|
18
|
+
let M = p(), [N] = m(), { trainingCourses: P, enrollments: F, employees: I, learningLoaded: L, fetchLearning: R, updateTrainingCourse: ue, enrollEmployee: z, updateEnrollmentStatus: B, removeEnrollment: V } = e(), H = te(L, R), U = i(), W = i(), [G, K] = l(), [de, q] = l(null), J = N.get("id"), Y = P.find((e) => e.id === J), X = ae(() => F.filter((e) => e.courseId === J), [F, J]), fe = new Set(X.map((e) => e.employeeId)), Z = I.filter((e) => e.status !== "terminated" && !fe.has(e.id)), Q = async (e) => {
|
|
19
|
+
q(null);
|
|
20
|
+
try {
|
|
21
|
+
await e();
|
|
22
|
+
} catch (e) {
|
|
23
|
+
q(e instanceof Error ? e.message : "Something went wrong");
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
if (!L && !H) return /* @__PURE__ */ h("div", {
|
|
13
27
|
className: "flex min-h-[60vh] items-center justify-center p-6",
|
|
14
|
-
children: /* @__PURE__ */
|
|
28
|
+
children: /* @__PURE__ */ h(j, { size: "lg" })
|
|
15
29
|
});
|
|
16
|
-
if (!
|
|
17
|
-
className: "p-6",
|
|
18
|
-
children: /* @__PURE__ */
|
|
30
|
+
if (!Y) return /* @__PURE__ */ g("div", {
|
|
31
|
+
className: "space-y-4 p-6",
|
|
32
|
+
children: [/* @__PURE__ */ h(o, { message: H }), /* @__PURE__ */ h(w, {
|
|
19
33
|
illustration: "empty-search",
|
|
20
34
|
title: "Course not found",
|
|
21
|
-
description: "This course
|
|
22
|
-
|
|
35
|
+
description: "This course was removed or the link is incomplete.",
|
|
36
|
+
action: /* @__PURE__ */ h(v, {
|
|
37
|
+
size: "sm",
|
|
38
|
+
onClick: () => M("../learning/courses-page"),
|
|
39
|
+
children: "Back to courses"
|
|
40
|
+
})
|
|
41
|
+
})]
|
|
23
42
|
});
|
|
24
|
-
let
|
|
25
|
-
return /* @__PURE__ */
|
|
26
|
-
className: "space-y-6 p-6
|
|
43
|
+
let $ = (e) => X.filter((t) => t.status === e).length;
|
|
44
|
+
return /* @__PURE__ */ g("div", {
|
|
45
|
+
className: "space-y-6 p-6",
|
|
27
46
|
children: [
|
|
28
|
-
/* @__PURE__ */
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
47
|
+
/* @__PURE__ */ g(v, {
|
|
48
|
+
variant: "ghost",
|
|
49
|
+
size: "sm",
|
|
50
|
+
onClick: () => M("../learning/courses-page"),
|
|
51
|
+
children: [/* @__PURE__ */ h(oe, { className: "mr-1 h-4 w-4" }), "Courses"]
|
|
52
|
+
}),
|
|
53
|
+
/* @__PURE__ */ g("div", {
|
|
54
|
+
className: "flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between",
|
|
55
|
+
children: [/* @__PURE__ */ g("div", {
|
|
56
|
+
className: "min-w-0",
|
|
57
|
+
children: [/* @__PURE__ */ g("div", {
|
|
58
|
+
className: "flex flex-wrap items-center gap-2",
|
|
59
|
+
children: [/* @__PURE__ */ h("h1", {
|
|
34
60
|
className: "text-2xl font-semibold",
|
|
35
|
-
children: /* @__PURE__ */
|
|
36
|
-
}), /* @__PURE__ */
|
|
37
|
-
variant:
|
|
38
|
-
|
|
61
|
+
children: /* @__PURE__ */ h(le, { children: Y.title })
|
|
62
|
+
}), /* @__PURE__ */ h(_, {
|
|
63
|
+
variant: Y.isActive ? "default" : "secondary",
|
|
64
|
+
className: "text-[10px]",
|
|
65
|
+
children: Y.isActive ? "Active" : "Archived"
|
|
39
66
|
})]
|
|
67
|
+
}), /* @__PURE__ */ g("p", {
|
|
68
|
+
className: "mt-1 flex items-center gap-1 text-sm text-text-secondary",
|
|
69
|
+
children: [/* @__PURE__ */ h(u, { className: "h-3.5 w-3.5" }), ee(Y.durationMinutes)]
|
|
70
|
+
})]
|
|
71
|
+
}), /* @__PURE__ */ g("div", {
|
|
72
|
+
className: "flex flex-wrap gap-2",
|
|
73
|
+
children: [
|
|
74
|
+
/* @__PURE__ */ h(v, {
|
|
75
|
+
size: "sm",
|
|
76
|
+
variant: "outline",
|
|
77
|
+
asChild: !0,
|
|
78
|
+
children: /* @__PURE__ */ g("a", {
|
|
79
|
+
href: Y.contentUrl,
|
|
80
|
+
target: "_blank",
|
|
81
|
+
rel: "noopener noreferrer",
|
|
82
|
+
children: [/* @__PURE__ */ h(d, { className: "mr-1 h-4 w-4" }), "Open content"]
|
|
83
|
+
})
|
|
84
|
+
}),
|
|
85
|
+
/* @__PURE__ */ g(v, {
|
|
86
|
+
size: "sm",
|
|
87
|
+
variant: "outline",
|
|
88
|
+
onClick: () => {
|
|
89
|
+
K(void 0), U.openModal();
|
|
90
|
+
},
|
|
91
|
+
children: [/* @__PURE__ */ h(f, { className: "mr-1 h-4 w-4" }), "Edit"]
|
|
92
|
+
}),
|
|
93
|
+
/* @__PURE__ */ g(v, {
|
|
94
|
+
size: "sm",
|
|
95
|
+
disabled: !Y.isActive || Z.length === 0,
|
|
96
|
+
title: Y.isActive ? Z.length === 0 ? "Every active employee is already enrolled" : void 0 : "Reactivate the course to enroll employees",
|
|
97
|
+
onClick: () => {
|
|
98
|
+
K(void 0), W.openModal();
|
|
99
|
+
},
|
|
100
|
+
children: [/* @__PURE__ */ h(ce, { className: "mr-1 h-4 w-4" }), "Enroll employee"]
|
|
101
|
+
})
|
|
102
|
+
]
|
|
103
|
+
})]
|
|
104
|
+
}),
|
|
105
|
+
/* @__PURE__ */ h(o, { message: H ?? de }),
|
|
106
|
+
/* @__PURE__ */ g(y, { children: [/* @__PURE__ */ h(S, { children: /* @__PURE__ */ h(C, {
|
|
107
|
+
className: "text-base",
|
|
108
|
+
children: "About this course"
|
|
109
|
+
}) }), /* @__PURE__ */ g(b, {
|
|
110
|
+
className: "space-y-3",
|
|
111
|
+
children: [/* @__PURE__ */ h("p", {
|
|
112
|
+
className: "whitespace-pre-line text-sm text-text-secondary",
|
|
113
|
+
children: Y.description
|
|
114
|
+
}), Y.requiredForRoles.length > 0 && /* @__PURE__ */ g("div", {
|
|
115
|
+
className: "flex flex-wrap items-center gap-1",
|
|
116
|
+
children: [/* @__PURE__ */ h("span", {
|
|
117
|
+
className: "mr-1 text-xs text-text-secondary",
|
|
118
|
+
children: "Required for"
|
|
119
|
+
}), Y.requiredForRoles.map((e) => /* @__PURE__ */ h(_, {
|
|
120
|
+
variant: "outline",
|
|
121
|
+
className: "text-[10px]",
|
|
122
|
+
children: e
|
|
123
|
+
}, e))]
|
|
124
|
+
})]
|
|
125
|
+
})] }),
|
|
126
|
+
/* @__PURE__ */ g("div", {
|
|
127
|
+
className: "grid gap-4 sm:grid-cols-2 xl:grid-cols-4",
|
|
128
|
+
children: [
|
|
129
|
+
/* @__PURE__ */ h(t, {
|
|
130
|
+
label: "Enrolled",
|
|
131
|
+
value: X.length
|
|
40
132
|
}),
|
|
41
|
-
/* @__PURE__ */
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"by ",
|
|
45
|
-
F.instructor,
|
|
46
|
-
" · ",
|
|
47
|
-
F.category
|
|
48
|
-
]
|
|
133
|
+
/* @__PURE__ */ h(t, {
|
|
134
|
+
label: s.not_started,
|
|
135
|
+
value: $("not_started")
|
|
49
136
|
}),
|
|
50
|
-
/* @__PURE__ */
|
|
51
|
-
|
|
52
|
-
|
|
137
|
+
/* @__PURE__ */ h(t, {
|
|
138
|
+
label: s.in_progress,
|
|
139
|
+
value: $("in_progress")
|
|
140
|
+
}),
|
|
141
|
+
/* @__PURE__ */ h(t, {
|
|
142
|
+
label: s.completed,
|
|
143
|
+
value: $("completed")
|
|
53
144
|
})
|
|
54
|
-
]
|
|
55
|
-
onClick: async () => {
|
|
56
|
-
N(!0);
|
|
57
|
-
try {
|
|
58
|
-
await k(F.id), j("Enrolled — your course is ready", "success"), E(`../learning/course-learning-page?id=${encodeURIComponent(F.id)}`);
|
|
59
|
-
} catch (e) {
|
|
60
|
-
j(e instanceof Error ? e.message : "Unable to enroll in course", "error");
|
|
61
|
-
} finally {
|
|
62
|
-
N(!1);
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
disabled: M,
|
|
66
|
-
children: [/* @__PURE__ */ d(o, { className: "h-4 w-4 mr-1" }), M ? "Enrolling…" : "Start Course"]
|
|
67
|
-
})]
|
|
145
|
+
]
|
|
68
146
|
}),
|
|
69
|
-
/* @__PURE__ */
|
|
70
|
-
|
|
71
|
-
|
|
147
|
+
/* @__PURE__ */ g(y, { children: [/* @__PURE__ */ g(S, { children: [/* @__PURE__ */ h(C, {
|
|
148
|
+
className: "text-base",
|
|
149
|
+
children: "Enrolled employees"
|
|
150
|
+
}), /* @__PURE__ */ h(x, { children: "Update each person’s progress as they start and finish the course." })] }), /* @__PURE__ */ h(b, { children: /* @__PURE__ */ h(T, {
|
|
151
|
+
data: X,
|
|
152
|
+
rowKey: (e) => e.id,
|
|
153
|
+
columns: [
|
|
154
|
+
{
|
|
155
|
+
key: "employee",
|
|
156
|
+
header: "Employee",
|
|
157
|
+
priority: "primary",
|
|
158
|
+
cell: (e) => /* @__PURE__ */ g("div", {
|
|
159
|
+
className: "flex items-center gap-2.5",
|
|
160
|
+
children: [/* @__PURE__ */ h(n, {
|
|
161
|
+
people: [{ name: e.employeeName }],
|
|
162
|
+
size: 28
|
|
163
|
+
}), /* @__PURE__ */ h("span", {
|
|
164
|
+
className: "font-medium",
|
|
165
|
+
children: e.employeeName
|
|
166
|
+
})]
|
|
167
|
+
})
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
key: "status",
|
|
171
|
+
header: "Status",
|
|
172
|
+
cell: (e) => /* @__PURE__ */ g(E, {
|
|
173
|
+
value: e.status,
|
|
174
|
+
onValueChange: (t) => void Q(() => B(e.id, t)),
|
|
175
|
+
children: [/* @__PURE__ */ h(k, {
|
|
176
|
+
className: "h-8 w-36",
|
|
177
|
+
"aria-label": `Status for ${e.employeeName}`,
|
|
178
|
+
children: /* @__PURE__ */ h(A, {})
|
|
179
|
+
}), /* @__PURE__ */ h(D, { children: c.map((e) => /* @__PURE__ */ h(O, {
|
|
180
|
+
value: e.value,
|
|
181
|
+
children: e.label
|
|
182
|
+
}, e.value)) })]
|
|
183
|
+
})
|
|
184
|
+
},
|
|
72
185
|
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
186
|
+
key: "started",
|
|
187
|
+
header: "Started",
|
|
188
|
+
cell: (e) => /* @__PURE__ */ h("span", {
|
|
189
|
+
className: "text-text-secondary",
|
|
190
|
+
children: e.startedAt ? a(e.startedAt) : "—"
|
|
191
|
+
})
|
|
78
192
|
},
|
|
79
193
|
{
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
194
|
+
key: "completed",
|
|
195
|
+
header: "Completed",
|
|
196
|
+
cell: (e) => /* @__PURE__ */ h("span", {
|
|
197
|
+
className: "text-text-secondary",
|
|
198
|
+
children: e.completedAt ? a(e.completedAt) : "—"
|
|
199
|
+
})
|
|
85
200
|
},
|
|
86
201
|
{
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
202
|
+
key: "actions",
|
|
203
|
+
header: "",
|
|
204
|
+
cellClassName: "md:text-right",
|
|
205
|
+
cell: (e) => /* @__PURE__ */ h(v, {
|
|
206
|
+
variant: "ghost",
|
|
207
|
+
size: "icon",
|
|
208
|
+
className: "h-8 w-8",
|
|
209
|
+
"aria-label": `Remove ${e.employeeName} from this course`,
|
|
210
|
+
onClick: () => void Q(() => V(e.id)),
|
|
211
|
+
children: /* @__PURE__ */ h(se, { className: "h-4 w-4" })
|
|
212
|
+
})
|
|
92
213
|
}
|
|
93
|
-
]
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
children: [
|
|
98
|
-
/* @__PURE__ */ d(h, { children: /* @__PURE__ */ f(g, {
|
|
99
|
-
className: "p-4 text-center",
|
|
100
|
-
children: [
|
|
101
|
-
/* @__PURE__ */ d(i, { className: "h-5 w-5 mx-auto text-text-secondary mb-1" }),
|
|
102
|
-
/* @__PURE__ */ d("p", {
|
|
103
|
-
className: "text-lg font-bold",
|
|
104
|
-
children: F.duration
|
|
105
|
-
}),
|
|
106
|
-
/* @__PURE__ */ d("p", {
|
|
107
|
-
className: "text-xs text-text-secondary",
|
|
108
|
-
children: "Duration"
|
|
109
|
-
})
|
|
110
|
-
]
|
|
111
|
-
}) }),
|
|
112
|
-
/* @__PURE__ */ d(h, { children: /* @__PURE__ */ f(g, {
|
|
113
|
-
className: "p-4 text-center",
|
|
114
|
-
children: [
|
|
115
|
-
/* @__PURE__ */ d(r, { className: "h-5 w-5 mx-auto text-text-secondary mb-1" }),
|
|
116
|
-
/* @__PURE__ */ d("p", {
|
|
117
|
-
className: "text-lg font-bold",
|
|
118
|
-
children: F.modules
|
|
119
|
-
}),
|
|
120
|
-
/* @__PURE__ */ d("p", {
|
|
121
|
-
className: "text-xs text-text-secondary",
|
|
122
|
-
children: "Modules"
|
|
123
|
-
})
|
|
124
|
-
]
|
|
125
|
-
}) }),
|
|
126
|
-
/* @__PURE__ */ d(h, { children: /* @__PURE__ */ f(g, {
|
|
127
|
-
className: "p-4 text-center",
|
|
128
|
-
children: [
|
|
129
|
-
/* @__PURE__ */ d(c, { className: "h-5 w-5 mx-auto text-text-secondary mb-1" }),
|
|
130
|
-
/* @__PURE__ */ d("p", {
|
|
131
|
-
className: "text-lg font-bold",
|
|
132
|
-
children: F.enrolled
|
|
133
|
-
}),
|
|
134
|
-
/* @__PURE__ */ d("p", {
|
|
135
|
-
className: "text-xs text-text-secondary",
|
|
136
|
-
children: "Enrolled"
|
|
137
|
-
})
|
|
138
|
-
]
|
|
139
|
-
}) }),
|
|
140
|
-
/* @__PURE__ */ d(h, { children: /* @__PURE__ */ f(g, {
|
|
141
|
-
className: "p-4 text-center",
|
|
142
|
-
children: [
|
|
143
|
-
/* @__PURE__ */ d(s, { className: "h-5 w-5 mx-auto text-status-warning-text mb-1" }),
|
|
144
|
-
/* @__PURE__ */ f("p", {
|
|
145
|
-
className: "text-lg font-bold",
|
|
146
|
-
children: [F.rating, "/5"]
|
|
147
|
-
}),
|
|
148
|
-
/* @__PURE__ */ d("p", {
|
|
149
|
-
className: "text-xs text-text-secondary",
|
|
150
|
-
children: "Rating"
|
|
151
|
-
})
|
|
152
|
-
]
|
|
153
|
-
}) })
|
|
154
|
-
]
|
|
155
|
-
}),
|
|
156
|
-
/* @__PURE__ */ f(y, { children: [
|
|
157
|
-
/* @__PURE__ */ d("div", {
|
|
158
|
-
className: "mb-2 text-lg font-semibold",
|
|
159
|
-
children: "About this Course"
|
|
160
|
-
}),
|
|
161
|
-
/* @__PURE__ */ d("p", {
|
|
162
|
-
className: "text-sm text-text-secondary",
|
|
163
|
-
children: F.description
|
|
164
|
-
}),
|
|
165
|
-
/* @__PURE__ */ d("div", {
|
|
166
|
-
className: "flex flex-wrap gap-1 mt-3",
|
|
167
|
-
children: F.skills.map((e) => /* @__PURE__ */ d(p, {
|
|
168
|
-
variant: "outline",
|
|
169
|
-
children: e
|
|
170
|
-
}, e))
|
|
214
|
+
],
|
|
215
|
+
emptyState: /* @__PURE__ */ h("p", {
|
|
216
|
+
className: "py-6 text-center text-sm text-text-secondary",
|
|
217
|
+
children: "No one is enrolled yet."
|
|
171
218
|
})
|
|
172
|
-
] }),
|
|
173
|
-
/* @__PURE__ */
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
219
|
+
}) })] }),
|
|
220
|
+
/* @__PURE__ */ h(r, {
|
|
221
|
+
open: U.open,
|
|
222
|
+
onOpenChange: U.onOpenChange,
|
|
223
|
+
title: "Edit course",
|
|
224
|
+
fields: ne,
|
|
225
|
+
initialValues: re(Y),
|
|
226
|
+
submitLabel: "Save changes",
|
|
227
|
+
errorMessage: G,
|
|
228
|
+
onValidationError: K,
|
|
229
|
+
onSubmit: async (e) => {
|
|
230
|
+
K(void 0);
|
|
231
|
+
try {
|
|
232
|
+
await ue(Y.id, ie(e));
|
|
233
|
+
} catch (e) {
|
|
234
|
+
throw K(e instanceof Error ? e.message : "Unable to save course"), e;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}),
|
|
238
|
+
/* @__PURE__ */ h(r, {
|
|
239
|
+
open: W.open,
|
|
240
|
+
onOpenChange: W.onOpenChange,
|
|
241
|
+
title: "Enroll employee",
|
|
242
|
+
description: `Enroll someone in “${Y.title}”. They start as Not started.`,
|
|
243
|
+
fields: [{
|
|
244
|
+
name: "employeeId",
|
|
245
|
+
label: "Employee",
|
|
246
|
+
type: "select",
|
|
247
|
+
required: !0,
|
|
248
|
+
colSpan: 2,
|
|
249
|
+
options: Z.map((e) => ({
|
|
250
|
+
value: e.id,
|
|
251
|
+
label: `${e.firstName} ${e.lastName} · ${e.position}`
|
|
252
|
+
}))
|
|
253
|
+
}],
|
|
254
|
+
submitLabel: "Enroll",
|
|
255
|
+
errorMessage: G,
|
|
256
|
+
onValidationError: K,
|
|
257
|
+
onSubmit: async (e) => {
|
|
258
|
+
K(void 0);
|
|
259
|
+
let t = I.find((t) => t.id === e.employeeId);
|
|
260
|
+
try {
|
|
261
|
+
if (!t) throw Error("Choose an employee to enroll");
|
|
262
|
+
await z(Y.id, t);
|
|
263
|
+
} catch (e) {
|
|
264
|
+
throw K(e instanceof Error ? e.message : "Unable to enroll employee"), e;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
})
|
|
194
268
|
]
|
|
195
269
|
});
|
|
196
270
|
}
|
|
197
271
|
//#endregion
|
|
198
|
-
export {
|
|
272
|
+
export { M as LearningCourseDetailsPagePage };
|
|
199
273
|
|
|
200
274
|
//# sourceMappingURL=LearningCourseDetailsPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningCourseDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCourseDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { BookOpen, Clock, GraduationCap, Play, Star, Users } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router';\nimport { useState } from 'react';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nexport function LearningCourseDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { courses, enrollCourse, initialized } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [enrolling, setEnrolling] = useState(false);\n const courseId = searchParams.get('id');\n const course = courseId ? courses.find((c) => c.id === courseId) : courses[0];\n if (!initialized && !course) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!course)\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course is no longer available.\"\n />\n </div>\n );\n const completionRate =\n course.enrolled > 0 ? Math.round((course.completed / course.enrolled) * 100) : 0;\n\n const handleStart = async () => {\n setEnrolling(true);\n try {\n await enrollCourse(course.id);\n showToast('Enrolled — your course is ready', 'success');\n navigate(`../learning/course-learning-page?id=${encodeURIComponent(course.id)}`);\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to enroll in course', 'error');\n } finally {\n setEnrolling(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-4xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"flex items-center gap-2 mb-1\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{course.title}</GradientText>\n </h1>\n <Badge\n variant={\n course.level === 'beginner'\n ? 'secondary'\n : course.level === 'intermediate'\n ? 'default'\n : 'destructive'\n }\n >\n {formatEnumLabel(course.level)}\n </Badge>\n </div>\n <p className=\"text-sm text-text-secondary\">\n by {course.instructor} · {course.category}\n </p>\n <PagePurpose className=\"mt-2\">\n Everything you need to decide on and start this course — what it covers, who it's\n for, and how others have done — so you can enroll with confidence and build the right\n skills.\n </PagePurpose>\n </div>\n <Button onClick={handleStart} disabled={enrolling}>\n <Play className=\"h-4 w-4 mr-1\" />\n {enrolling ? 'Enrolling…' : 'Start Course'}\n </Button>\n </div>\n\n <NextSteps\n storageKey=\"crewfoundry-course-details\"\n steps={[\n {\n id: 'start',\n label: 'Start the first module',\n description: 'Begin learning and track your progress as you go.',\n icon: <Play className=\"h-4 w-4\" />,\n onClick: () =>\n navigate(`../learning/course-learning-page?id=${encodeURIComponent(course.id)}`),\n },\n {\n id: 'my-learning',\n label: 'See all your learning',\n description: 'Review everything you are currently enrolled in.',\n icon: <GraduationCap className=\"h-4 w-4\" />,\n onClick: () => navigate('../learning/my-learning-page'),\n },\n {\n id: 'browse',\n label: 'Browse more courses',\n description: 'Find related training to round out your skills.',\n icon: <BookOpen className=\"h-4 w-4\" />,\n onClick: () => navigate('../learning/courses-page'),\n },\n ]}\n />\n\n <div className=\"grid gap-4 md:grid-cols-4\">\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Clock className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{course.duration}</p>\n <p className=\"text-xs text-text-secondary\">Duration</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <BookOpen className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{course.modules}</p>\n <p className=\"text-xs text-text-secondary\">Modules</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Users className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{course.enrolled}</p>\n <p className=\"text-xs text-text-secondary\">Enrolled</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Star className=\"h-5 w-5 mx-auto text-status-warning-text mb-1\" />\n <p className=\"text-lg font-bold\">{course.rating}/5</p>\n <p className=\"text-xs text-text-secondary\">Rating</p>\n </CardContent>\n </Card>\n </div>\n <EmphasisPanel>\n <div className=\"mb-2 text-lg font-semibold\">About this Course</div>\n <p className=\"text-sm text-text-secondary\">{course.description}</p>\n <div className=\"flex flex-wrap gap-1 mt-3\">\n {course.skills.map((s) => (\n <Badge key={s} variant=\"outline\">\n {s}\n </Badge>\n ))}\n </div>\n </EmphasisPanel>\n <Card>\n <CardHeader>\n <CardTitle>Completion Statistics</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2\">\n <div className=\"flex justify-between text-sm\">\n <span>\n {course.completed} of {course.enrolled} students completed\n </span>\n <span className=\"font-medium\">{completionRate}%</span>\n </div>\n <div className=\"h-3 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-3 bg-status-success-text rounded\"\n style={{ width: `${completionRate}%` }}\n />\n </div>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"LearningCourseDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningCourseDetailsPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GradientText,\n IllustratedEmptyState,\n ResponsiveTable,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Spinner,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { ArrowLeft, Clock, ExternalLink, Pencil, Trash2, UserPlus } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router';\nimport { FormModal, useFormModal, type FormValues } from '../components/forms/FormModal';\nimport { InlineError } from '../components/InlineError';\nimport { AvatarStack, StatCard } from '../components/viz';\nimport {\n useCrewFoundryStore,\n type CrewEnrollment,\n type CrewEnrollmentStatus,\n} from '../store/crewfoundryStore';\nimport {\n ENROLLMENT_STATUS_LABEL,\n ENROLLMENT_STATUS_OPTIONS,\n formatDuration,\n} from '../lib/learning';\nimport { useEnsureLoaded } from '../lib/useEnsureLoaded';\nimport { COURSE_FIELDS, courseToValues, valuesToCourseInput } from './LearningCoursesPagePage';\nimport { formatDate } from '../utils/dateUtils';\n\nexport function LearningCourseDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const {\n trainingCourses,\n enrollments,\n employees,\n learningLoaded,\n fetchLearning,\n updateTrainingCourse,\n enrollEmployee,\n updateEnrollmentStatus,\n removeEnrollment,\n } = useCrewFoundryStore();\n const loadError = useEnsureLoaded(learningLoaded, fetchLearning);\n const editModal = useFormModal();\n const enrollModal = useFormModal();\n const [formError, setFormError] = useState<string | undefined>();\n const [actionError, setActionError] = useState<string | null>(null);\n\n const courseId = searchParams.get('id');\n const course = trainingCourses.find((item) => item.id === courseId);\n const roster = useMemo(\n () => enrollments.filter((row) => row.courseId === courseId),\n [enrollments, courseId]\n );\n const enrolledIds = new Set(roster.map((row) => row.employeeId));\n const enrollable = employees.filter(\n (employee) => employee.status !== 'terminated' && !enrolledIds.has(employee.id)\n );\n\n const runAction = async (action: () => Promise<void>) => {\n setActionError(null);\n try {\n await action();\n } catch (error) {\n setActionError(error instanceof Error ? error.message : 'Something went wrong');\n }\n };\n\n if (!learningLoaded && !loadError) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!course) {\n return (\n <div className=\"space-y-4 p-6\">\n <InlineError message={loadError} />\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"Course not found\"\n description=\"This course was removed or the link is incomplete.\"\n action={\n <Button size=\"sm\" onClick={() => navigate('../learning/courses-page')}>\n Back to courses\n </Button>\n }\n />\n </div>\n );\n }\n\n const countBy = (status: CrewEnrollmentStatus) =>\n roster.filter((row) => row.status === status).length;\n\n const handleEdit = async (values: FormValues) => {\n setFormError(undefined);\n try {\n await updateTrainingCourse(course.id, valuesToCourseInput(values));\n } catch (error) {\n setFormError(error instanceof Error ? error.message : 'Unable to save course');\n throw error;\n }\n };\n\n const handleEnroll = async (values: FormValues) => {\n setFormError(undefined);\n const employee = employees.find((item) => item.id === values.employeeId);\n try {\n if (!employee) throw new Error('Choose an employee to enroll');\n await enrollEmployee(course.id, employee);\n } catch (error) {\n setFormError(error instanceof Error ? error.message : 'Unable to enroll employee');\n throw error;\n }\n };\n\n const columns: ResponsiveTableColumn<CrewEnrollment>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (row) => (\n <div className=\"flex items-center gap-2.5\">\n <AvatarStack people={[{ name: row.employeeName }]} size={28} />\n <span className=\"font-medium\">{row.employeeName}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (row) => (\n <Select\n value={row.status}\n onValueChange={(value) =>\n void runAction(() => updateEnrollmentStatus(row.id, value as CrewEnrollmentStatus))\n }\n >\n <SelectTrigger className=\"h-8 w-36\" aria-label={`Status for ${row.employeeName}`}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {ENROLLMENT_STATUS_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ),\n },\n {\n key: 'started',\n header: 'Started',\n cell: (row) => (\n <span className=\"text-text-secondary\">\n {row.startedAt ? formatDate(row.startedAt) : '—'}\n </span>\n ),\n },\n {\n key: 'completed',\n header: 'Completed',\n cell: (row) => (\n <span className=\"text-text-secondary\">\n {row.completedAt ? formatDate(row.completedAt) : '—'}\n </span>\n ),\n },\n {\n key: 'actions',\n header: '',\n cellClassName: 'md:text-right',\n cell: (row) => (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8\"\n aria-label={`Remove ${row.employeeName} from this course`}\n onClick={() => void runAction(() => removeEnrollment(row.id))}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n ),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <Button variant=\"ghost\" size=\"sm\" onClick={() => navigate('../learning/courses-page')}>\n <ArrowLeft className=\"mr-1 h-4 w-4\" />\n Courses\n </Button>\n\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{course.title}</GradientText>\n </h1>\n <Badge variant={course.isActive ? 'default' : 'secondary'} className=\"text-[10px]\">\n {course.isActive ? 'Active' : 'Archived'}\n </Badge>\n </div>\n <p className=\"mt-1 flex items-center gap-1 text-sm text-text-secondary\">\n <Clock className=\"h-3.5 w-3.5\" />\n {formatDuration(course.durationMinutes)}\n </p>\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button size=\"sm\" variant=\"outline\" asChild>\n <a href={course.contentUrl} target=\"_blank\" rel=\"noopener noreferrer\">\n <ExternalLink className=\"mr-1 h-4 w-4\" />\n Open content\n </a>\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onClick={() => {\n setFormError(undefined);\n editModal.openModal();\n }}\n >\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <Button\n size=\"sm\"\n disabled={!course.isActive || enrollable.length === 0}\n title={\n !course.isActive\n ? 'Reactivate the course to enroll employees'\n : enrollable.length === 0\n ? 'Every active employee is already enrolled'\n : undefined\n }\n onClick={() => {\n setFormError(undefined);\n enrollModal.openModal();\n }}\n >\n <UserPlus className=\"mr-1 h-4 w-4\" />\n Enroll employee\n </Button>\n </div>\n </div>\n\n <InlineError message={loadError ?? actionError} />\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">About this course</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"whitespace-pre-line text-sm text-text-secondary\">{course.description}</p>\n {course.requiredForRoles.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1\">\n <span className=\"mr-1 text-xs text-text-secondary\">Required for</span>\n {course.requiredForRoles.map((role) => (\n <Badge key={role} variant=\"outline\" className=\"text-[10px]\">\n {role}\n </Badge>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n\n <div className=\"grid gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <StatCard label=\"Enrolled\" value={roster.length} />\n <StatCard label={ENROLLMENT_STATUS_LABEL.not_started} value={countBy('not_started')} />\n <StatCard label={ENROLLMENT_STATUS_LABEL.in_progress} value={countBy('in_progress')} />\n <StatCard label={ENROLLMENT_STATUS_LABEL.completed} value={countBy('completed')} />\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-base\">Enrolled employees</CardTitle>\n <CardDescription>\n Update each person’s progress as they start and finish the course.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable<CrewEnrollment>\n data={roster}\n rowKey={(row) => row.id}\n columns={columns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No one is enrolled yet.\n </p>\n }\n />\n </CardContent>\n </Card>\n\n <FormModal\n open={editModal.open}\n onOpenChange={editModal.onOpenChange}\n title=\"Edit course\"\n fields={COURSE_FIELDS}\n initialValues={courseToValues(course)}\n submitLabel=\"Save changes\"\n errorMessage={formError}\n onValidationError={setFormError}\n onSubmit={handleEdit}\n />\n\n <FormModal\n open={enrollModal.open}\n onOpenChange={enrollModal.onOpenChange}\n title=\"Enroll employee\"\n description={`Enroll someone in “${course.title}”. They start as Not started.`}\n fields={[\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n colSpan: 2,\n options: enrollable.map((employee) => ({\n value: employee.id,\n label: `${employee.firstName} ${employee.lastName} · ${employee.position}`,\n })),\n },\n ]}\n submitLabel=\"Enroll\"\n errorMessage={formError}\n onValidationError={setFormError}\n onSubmit={handleEnroll}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,14 @@
|
|
|
1
|
+
import { FieldConfig, FormValues } from '../components/forms/FormModal';
|
|
2
|
+
import { CrewTrainingCourse, CrewTrainingCourseInput } from '../store/crewfoundryStore';
|
|
3
|
+
|
|
4
|
+
export declare const COURSE_FIELDS: FieldConfig[];
|
|
5
|
+
export declare const courseToValues: (course: CrewTrainingCourse) => FormValues;
|
|
6
|
+
/** Validate + coerce form values into the course mutation input. Throws a user-facing message. */
|
|
7
|
+
export declare const valuesToCourseInput: (values: FormValues) => CrewTrainingCourseInput;
|
|
8
|
+
export declare const LEARNING_TABS: {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
path: string;
|
|
12
|
+
}[];
|
|
1
13
|
export declare function LearningCoursesPagePage(): import("react").JSX.Element;
|
|
2
14
|
//# sourceMappingURL=LearningCoursesPagePage.d.ts.map
|