@burdenoff/microfe-crewfoundry 2026.704.5 → 2026.704.7
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/CrewFoundryRoutes.js +51 -43
- package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
- package/dist/crewfoundry/components/forms/formValues.js +5 -2
- package/dist/crewfoundry/components/forms/formValues.js.map +1 -1
- package/dist/crewfoundry/components/viz/AllocationBar.js +68 -0
- package/dist/crewfoundry/components/viz/AllocationBar.js.map +1 -0
- package/dist/crewfoundry/components/viz/AreaChart.js +102 -0
- package/dist/crewfoundry/components/viz/AreaChart.js.map +1 -0
- package/dist/crewfoundry/components/viz/AvatarStack.js +36 -0
- package/dist/crewfoundry/components/viz/AvatarStack.js.map +1 -0
- package/dist/crewfoundry/components/viz/BarChart.js +83 -0
- package/dist/crewfoundry/components/viz/BarChart.js.map +1 -0
- package/dist/crewfoundry/components/viz/CertificateCard.js +118 -0
- package/dist/crewfoundry/components/viz/CertificateCard.js.map +1 -0
- package/dist/crewfoundry/components/viz/DonutChart.js +109 -0
- package/dist/crewfoundry/components/viz/DonutChart.js.map +1 -0
- package/dist/crewfoundry/components/viz/HBarFunnel.js +54 -0
- package/dist/crewfoundry/components/viz/HBarFunnel.js.map +1 -0
- package/dist/crewfoundry/components/viz/Heatmap.js +91 -0
- package/dist/crewfoundry/components/viz/Heatmap.js.map +1 -0
- package/dist/crewfoundry/components/viz/KanbanBoard.js +50 -0
- package/dist/crewfoundry/components/viz/KanbanBoard.js.map +1 -0
- package/dist/crewfoundry/components/viz/LineChart.js +123 -0
- package/dist/crewfoundry/components/viz/LineChart.js.map +1 -0
- package/dist/crewfoundry/components/viz/OrgTree.js +83 -0
- package/dist/crewfoundry/components/viz/OrgTree.js.map +1 -0
- package/dist/crewfoundry/components/viz/PathSteps.js +68 -0
- package/dist/crewfoundry/components/viz/PathSteps.js.map +1 -0
- package/dist/crewfoundry/components/viz/ProgressRing.js +68 -0
- package/dist/crewfoundry/components/viz/ProgressRing.js.map +1 -0
- package/dist/crewfoundry/components/viz/RatingStars.js +51 -0
- package/dist/crewfoundry/components/viz/RatingStars.js.map +1 -0
- package/dist/crewfoundry/components/viz/SegmentedTabs.js +69 -0
- package/dist/crewfoundry/components/viz/SegmentedTabs.js.map +1 -0
- package/dist/crewfoundry/components/viz/SkillBar.js +3 -0
- package/dist/crewfoundry/components/viz/Sparkline.js +74 -0
- package/dist/crewfoundry/components/viz/Sparkline.js.map +1 -0
- package/dist/crewfoundry/components/viz/StatCard.js +60 -0
- package/dist/crewfoundry/components/viz/StatCard.js.map +1 -0
- package/dist/crewfoundry/components/viz/StepProgress.js +3 -0
- package/dist/crewfoundry/components/viz/Timeline.js +66 -0
- package/dist/crewfoundry/components/viz/Timeline.js.map +1 -0
- package/dist/crewfoundry/components/viz/index.js +23 -0
- package/dist/crewfoundry/components/viz/lib/Avatar.js +30 -0
- package/dist/crewfoundry/components/viz/lib/Avatar.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/crewfoundry/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/format.js +21 -0
- package/dist/crewfoundry/components/viz/lib/format.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/geometry.js +69 -0
- package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/hooks.js +39 -0
- package/dist/crewfoundry/components/viz/lib/hooks.js.map +1 -0
- package/dist/crewfoundry/components/viz/lib/palette.js +27 -0
- package/dist/crewfoundry/components/viz/lib/palette.js.map +1 -0
- package/dist/crewfoundry/lib/analytics.js +323 -0
- package/dist/crewfoundry/lib/analytics.js.map +1 -0
- package/dist/crewfoundry/lib/companyFinance.js +16 -0
- package/dist/crewfoundry/lib/companyFinance.js.map +1 -0
- package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js +226 -283
- package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js +107 -120
- package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +122 -162
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +121 -158
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +316 -211
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +75 -27
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +167 -165
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +168 -109
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +401 -290
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringApplicationsPagePage.js +84 -75
- package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +368 -174
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +236 -201
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +369 -196
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +252 -249
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +210 -110
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +216 -178
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js +385 -0
- package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js.map +1 -0
- package/dist/crewfoundry/pages/HrEmployeesPagePage.js +189 -130
- package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +397 -241
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrPayrollPagePage.js +262 -147
- package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +345 -167
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +98 -24
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +35 -31
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +289 -108
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +221 -114
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +136 -133
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +148 -97
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +264 -157
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +85 -93
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +211 -122
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +94 -54
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js +28 -25
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +290 -138
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +169 -79
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js +65 -61
- package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +296 -125
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js +187 -0
- package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js +142 -76
- package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js +93 -43
- package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +250 -98
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/dist/crewfoundry/utils/cn.js +10 -0
- package/dist/crewfoundry/utils/cn.js.map +1 -0
- package/dist/crewfoundry/utils/courseProgress.js +46 -0
- package/dist/crewfoundry/utils/courseProgress.js.map +1 -0
- package/dist/node_modules/clsx/dist/clsx.js +18 -0
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringJobDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n NextSteps,\n PagePurpose,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { useState } from 'react';\nimport {\n Briefcase,\n Calendar,\n CheckCircle,\n DollarSign,\n MapPin,\n Pencil,\n Share2,\n Trash2,\n Users,\n} from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore, type Candidate, type JobPosting } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getTags, getText } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nconst JOB_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Job Title', type: 'text', required: true, colSpan: 2 },\n { name: 'department', label: 'Department', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n {\n name: 'type',\n label: 'Employment Type',\n type: 'select',\n options: [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n { label: 'Remote', value: 'remote' },\n ],\n },\n {\n name: 'experienceLevel',\n label: 'Experience Level',\n type: 'select',\n options: [\n { label: 'Entry', value: 'entry' },\n { label: 'Mid', value: 'mid' },\n { label: 'Senior', value: 'senior' },\n { label: 'Lead', value: 'lead' },\n { label: 'Executive', value: 'executive' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Active', value: 'active' },\n { label: 'Paused', value: 'paused' },\n { label: 'Closed', value: 'closed' },\n { label: 'Filled', value: 'filled' },\n ],\n },\n { name: 'salaryMin', label: 'Salary Min ($)', type: 'number' },\n { name: 'salaryMax', label: 'Salary Max ($)', type: 'number' },\n { name: 'deadline', label: 'Application Deadline', type: 'date' },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n { name: 'requirements', label: 'Requirements', type: 'tags' },\n { name: 'benefits', label: 'Benefits', type: 'tags' },\n];\n\nconst APPLICANT_COLUMNS: ResponsiveTableColumn<Candidate>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (c) => (\n <span className=\"font-medium\">\n {c.firstName} {c.lastName}\n </span>\n ),\n },\n {\n key: 'source',\n header: 'Source',\n priority: 'secondary',\n cell: (c) => <span className=\"text-text-secondary\">{c.source}</span>,\n },\n {\n key: 'experience',\n header: 'Experience',\n priority: 'secondary',\n cell: (c) => <span>{c.experience} yrs</span>,\n },\n {\n key: 'rating',\n header: 'Rating',\n priority: 'secondary',\n cell: (c) => <span>{c.rating}/5</span>,\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (c) => (\n <Badge variant={c.status === 'hired' ? 'default' : 'secondary'} className=\"text-[10px]\">\n {formatEnumLabel(c.status)}\n </Badge>\n ),\n },\n];\n\nexport function HiringJobDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { showToast } = useGlobalUi();\n const { jobPostings, candidates, updateJobPosting, removeJobPosting } = useCrewFoundryStore();\n const jobId = searchParams.get('id');\n const job = jobId ? jobPostings.find((j) => j.id === jobId) : jobPostings[0];\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n\n if (!job) {\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job posting found\"\n description=\"This job posting is no longer available.\"\n />\n </div>\n );\n }\n\n const jobCandidates = candidates.filter((c) => c.position === job.title);\n\n const editInitial: FormValues = {\n title: job.title,\n department: job.department,\n location: job.location,\n type: job.type,\n experienceLevel: job.experienceLevel,\n status: job.status,\n salaryMin: job.salaryMin,\n salaryMax: job.salaryMax,\n deadline: job.deadline,\n description: job.description,\n requirements: job.requirements,\n benefits: job.benefits,\n };\n\n const handleEditSubmit = async (values: FormValues) => {\n const updates: Partial<JobPosting> = {\n title: getText(values, 'title'),\n department: getText(values, 'department'),\n location: getText(values, 'location'),\n type: values.type as JobPosting['type'],\n experienceLevel: values.experienceLevel as JobPosting['experienceLevel'],\n status: values.status as JobPosting['status'],\n salaryMin: getNumber(values, 'salaryMin'),\n salaryMax: getNumber(values, 'salaryMax'),\n deadline: getText(values, 'deadline'),\n description: getText(values, 'description'),\n requirements: getTags(values, 'requirements'),\n benefits: getTags(values, 'benefits'),\n };\n await updateJobPosting(job.id, updates);\n showToast('Job posting updated', 'success');\n };\n\n const handleDelete = async () => {\n await removeJobPosting(job.id);\n showToast('Job posting deleted', 'success');\n navigate('../hiring/jobs-page');\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>{job.title}</GradientText>\n </h1>\n <Badge variant={job.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"flex items-center gap-4 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Briefcase className=\"h-3 w-3\" />\n {job.department}\n </span>\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" />\n {job.location}\n </span>\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Posted {job.postedDate}\n </span>\n </div>\n <PagePurpose className=\"mt-2\">\n Everything about this role in one place — review applicants, requirements, and\n compensation here to decide who to interview and keep the posting current.\n </PagePurpose>\n </div>\n <div className=\"flex gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => showToast('Sharing job postings is coming soon', 'info')}\n >\n <Share2 className=\"h-4 w-4 mr-1\" />\n Share\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditOpen(true)}>\n <Pencil className=\"h-4 w-4 mr-1\" />\n Edit Posting\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n aria-label=\"Delete job posting\"\n onClick={() => setDeleteOpen(true)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n\n <NextSteps\n storageKey=\"crewfoundry-job-details\"\n steps={[\n {\n id: 'applicants',\n label: 'Review applicants',\n description: 'Screen the candidates who applied for this role.',\n icon: <Users className=\"h-4 w-4\" />,\n onClick: () => navigate('../hiring/applications-page'),\n },\n {\n id: 'interviews',\n label: 'Schedule interviews',\n description: 'Set up conversations with promising candidates.',\n icon: <Calendar className=\"h-4 w-4\" />,\n onClick: () => navigate('../hiring/interviews-page'),\n },\n {\n id: 'candidates',\n label: 'Browse your talent pool',\n description: 'Source more candidates that fit this position.',\n icon: <Briefcase className=\"h-4 w-4\" />,\n onClick: () => navigate('../hiring/candidates-page'),\n },\n ]}\n />\n\n <div className=\"grid gap-4 md:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\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-2xl font-bold tabular-nums\">{job.applicants}</p>\n <p className=\"text-xs text-text-secondary\">Applicants</p>\n </CardContent>\n </GlassCard>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <DollarSign className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">\n ${(job.salaryMin / 1000).toFixed(0)}K-${(job.salaryMax / 1000).toFixed(0)}K\n </p>\n <p className=\"text-xs text-text-secondary\">Salary Range</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <Calendar className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{job.deadline}</p>\n <p className=\"text-xs text-text-secondary\">Deadline</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"p-4 text-center\">\n <CheckCircle className=\"h-5 w-5 mx-auto text-text-secondary mb-1\" />\n <p className=\"text-lg font-bold\">{formatEnumLabel(job.experienceLevel)}</p>\n <p className=\"text-xs text-text-secondary\">Level</p>\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm text-text-secondary\">{job.description}</p>\n </CardContent>\n </Card>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Requirements</CardTitle>\n </CardHeader>\n <CardContent>\n <ul className=\"space-y-2\">\n {job.requirements.map((req, idx) => (\n <li key={idx} className=\"flex items-start gap-2 text-sm\">\n <CheckCircle className=\"h-4 w-4 text-status-success-text mt-0.5 flex-shrink-0\" />\n {req}\n </li>\n ))}\n </ul>\n </CardContent>\n </Card>\n <Card>\n <CardHeader>\n <CardTitle>Benefits</CardTitle>\n </CardHeader>\n <CardContent>\n <ul className=\"space-y-2\">\n {job.benefits.map((ben, idx) => (\n <li key={idx} className=\"flex items-start gap-2 text-sm\">\n <CheckCircle className=\"h-4 w-4 text-status-info-text mt-0.5 flex-shrink-0\" />\n {ben}\n </li>\n ))}\n </ul>\n </CardContent>\n </Card>\n </div>\n\n {jobCandidates.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Applicants for this Role</CardTitle>\n <CardDescription>{jobCandidates.length} candidates</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n data={jobCandidates}\n rowKey={(c) => c.id}\n columns={APPLICANT_COLUMNS}\n />\n </CardContent>\n </Card>\n )}\n\n <FormModal\n open={editOpen}\n onOpenChange={setEditOpen}\n title=\"Edit Job Posting\"\n description=\"Update the role details, compensation, and status.\"\n fields={JOB_FIELDS}\n initialValues={editInitial}\n submitLabel=\"Save changes\"\n onSubmit={handleEditSubmit}\n />\n <ConfirmDialog\n open={deleteOpen}\n onOpenChange={setDeleteOpen}\n title=\"Delete job posting\"\n description={`Permanently remove “${job.title}”? This cannot be undone.`}\n confirmLabel=\"Delete\"\n destructive\n onConfirm={handleDelete}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HiringJobDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { useState } from 'react';\nimport { Briefcase, Calendar, CheckCircle, MapPin, Pencil, Share2, Trash2, Users } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore, type Candidate, type JobPosting } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getTags, getText } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { HBarFunnel, RatingStars, getInitials, type LabeledValue } from '../components/viz';\n\nconst JOB_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Job Title', type: 'text', required: true, colSpan: 2 },\n { name: 'department', label: 'Department', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n {\n name: 'type',\n label: 'Employment Type',\n type: 'select',\n options: [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n { label: 'Remote', value: 'remote' },\n ],\n },\n {\n name: 'experienceLevel',\n label: 'Experience Level',\n type: 'select',\n options: [\n { label: 'Entry', value: 'entry' },\n { label: 'Mid', value: 'mid' },\n { label: 'Senior', value: 'senior' },\n { label: 'Lead', value: 'lead' },\n { label: 'Executive', value: 'executive' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Active', value: 'active' },\n { label: 'Paused', value: 'paused' },\n { label: 'Closed', value: 'closed' },\n { label: 'Filled', value: 'filled' },\n ],\n },\n { name: 'salaryMin', label: 'Salary Min ($)', type: 'number' },\n { name: 'salaryMax', label: 'Salary Max ($)', type: 'number' },\n { name: 'deadline', label: 'Application Deadline', type: 'date' },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n { name: 'requirements', label: 'Requirements', type: 'tags' },\n { name: 'benefits', label: 'Benefits', type: 'tags' },\n];\n\nconst STAGE_BADGE: Record<Candidate['status'], 'default' | 'secondary' | 'destructive' | 'outline'> = {\n new: 'outline',\n screening: 'secondary',\n interview: 'secondary',\n assessment: 'secondary',\n offer: 'default',\n hired: 'default',\n rejected: 'destructive',\n};\n\nfunction InitialsAvatar({ name, size = 32 }: { name: string; size?: number }) {\n return (\n <span\n className=\"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\nexport function HiringJobDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { showToast } = useGlobalUi();\n const { jobPostings, candidates, updateJobPosting, removeJobPosting, initialized } =\n useCrewFoundryStore();\n const jobId = searchParams.get('id');\n const job = jobId ? jobPostings.find((j) => j.id === jobId) : jobPostings[0];\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n\n if (!initialized && !job) {\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 (!job) {\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job posting found\"\n description=\"This job posting is no longer available.\"\n />\n </div>\n );\n }\n\n // Applicants for this role. CrewCandidate has no jobId link, so match on the\n // (normalized) position title — the only real linkage the schema exposes.\n const titleKey = job.title.trim().toLowerCase();\n const jobCandidates = candidates.filter((c) => c.position.trim().toLowerCase() === titleKey);\n const countBy = (status: Candidate['status']) =>\n jobCandidates.filter((c) => c.status === status).length;\n\n const interviewCount = countBy('interview');\n\n // Real-data hiring funnel: \"Applied\" = everyone who applied to this role, then\n // the count currently at each downstream stage (offer folds in hired).\n const funnelStages: LabeledValue[] = [\n { label: 'Applied', value: jobCandidates.length },\n { label: 'Screening', value: countBy('screening') },\n { label: 'Interview', value: interviewCount },\n { label: 'Assessment', value: countBy('assessment') },\n { label: 'Offer', value: countBy('offer') + countBy('hired') },\n ];\n\n const editInitial: FormValues = {\n title: job.title,\n department: job.department,\n location: job.location,\n type: job.type,\n experienceLevel: job.experienceLevel,\n status: job.status,\n salaryMin: job.salaryMin,\n salaryMax: job.salaryMax,\n deadline: job.deadline,\n description: job.description,\n requirements: job.requirements,\n benefits: job.benefits,\n };\n\n const handleEditSubmit = async (values: FormValues) => {\n const updates: Partial<JobPosting> = {\n title: getText(values, 'title'),\n department: getText(values, 'department'),\n location: getText(values, 'location'),\n type: values.type as JobPosting['type'],\n experienceLevel: values.experienceLevel as JobPosting['experienceLevel'],\n status: values.status as JobPosting['status'],\n salaryMin: getNumber(values, 'salaryMin'),\n salaryMax: getNumber(values, 'salaryMax'),\n deadline: getText(values, 'deadline'),\n description: getText(values, 'description'),\n requirements: getTags(values, 'requirements'),\n benefits: getTags(values, 'benefits'),\n };\n await updateJobPosting(job.id, updates);\n showToast('Job posting updated', 'success');\n };\n\n const handleDelete = async () => {\n await removeJobPosting(job.id);\n showToast('Job posting deleted', 'success');\n navigate('../hiring/jobs-page');\n };\n\n const handleShare = async () => {\n try {\n await navigator.clipboard.writeText(window.location.href);\n showToast('Job link copied to clipboard', 'success');\n } catch {\n showToast('Could not copy the job link', 'error');\n }\n };\n\n const viewApplications = () =>\n navigate(`../hiring/applications-page?position=${encodeURIComponent(job.title)}`);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"flex flex-col gap-4 p-6 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{job.title}</GradientText>\n </h1>\n <Badge variant={job.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Briefcase className=\"h-3.5 w-3.5\" />\n {job.department}\n </span>\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3.5 w-3.5\" />\n {job.location}\n </span>\n <span>{formatEnumLabel(job.type)}</span>\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {jobCandidates.length} applicants\n </Badge>\n {interviewCount > 0 && (\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {interviewCount} in interview\n </Badge>\n )}\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Posted {job.postedDate}\n </span>\n </div>\n <PagePurpose className=\"mt-3\">\n Everything about this role in one place — review applicants, requirements, and\n compensation to decide who to interview and keep the posting current.\n </PagePurpose>\n </div>\n\n <div className=\"flex shrink-0 flex-col items-start gap-3 lg:items-end\">\n <div className=\"text-2xl font-bold tabular-nums text-text-primary\">\n ${(job.salaryMin / 1000).toFixed(0)}k – ${(job.salaryMax / 1000).toFixed(0)}k\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleShare}>\n <Share2 className=\"mr-1 h-4 w-4\" />\n Share\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditOpen(true)}>\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n aria-label=\"Delete job posting\"\n onClick={() => setDeleteOpen(true)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n <Button size=\"sm\" onClick={viewApplications}>\n <Users className=\"mr-1 h-4 w-4\" />\n View Applications\n </Button>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n\n {/* Two-column body */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Left: description / requirements / benefits */}\n <div className=\"space-y-6 lg:col-span-2\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm leading-relaxed text-text-secondary\">{job.description}</p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Requirements</CardTitle>\n </CardHeader>\n <CardContent>\n {job.requirements.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No requirements listed.</p>\n ) : (\n <ul className=\"space-y-2\">\n {job.requirements.map((req, idx) => (\n <li key={idx} className=\"flex items-start gap-2 text-sm\">\n <CheckCircle className=\"mt-0.5 h-4 w-4 flex-shrink-0 text-status-success-text\" />\n {req}\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Benefits</CardTitle>\n </CardHeader>\n <CardContent>\n {job.benefits.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No benefits listed.</p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {job.benefits.map((ben, idx) => (\n <Badge key={idx} variant=\"outline\" className=\"rounded-full px-3 py-1 text-xs\">\n {ben}\n </Badge>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Right rail: candidates + funnel */}\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Candidates</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No applicants matched to this role yet.</p>\n ) : (\n <ul className=\"max-h-96 space-y-1 overflow-y-auto pr-1\">\n {jobCandidates.map((candidate) => (\n <li\n key={candidate.id}\n className=\"flex items-center gap-2 rounded-[var(--radius-input)] px-2 py-2 hover:bg-bg-sunken\"\n >\n <InitialsAvatar\n name={`${candidate.firstName} ${candidate.lastName}`}\n size={30}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-text-primary\">\n {candidate.firstName} {candidate.lastName}\n </span>\n <Badge variant={STAGE_BADGE[candidate.status]} className=\"shrink-0 text-[9px]\">\n {formatEnumLabel(candidate.status)}\n </Badge>\n <RatingStars value={candidate.rating} size={12} className=\"shrink-0\" />\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Hiring funnel</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n The funnel appears once candidates apply to this role.\n </p>\n ) : (\n <HBarFunnel\n stages={funnelStages}\n valueFormatter={(value) => String(value)}\n showConversion\n />\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={editOpen}\n onOpenChange={setEditOpen}\n title=\"Edit Job Posting\"\n description=\"Update the role details, compensation, and status.\"\n fields={JOB_FIELDS}\n initialValues={editInitial}\n submitLabel=\"Save changes\"\n onSubmit={handleEditSubmit}\n />\n <ConfirmDialog\n open={deleteOpen}\n onOpenChange={setDeleteOpen}\n title=\"Delete job posting\"\n description={`Permanently remove “${job.title}”? This cannot be undone.`}\n confirmLabel=\"Delete\"\n destructive\n onConfirm={handleDelete}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,151 +1,251 @@
|
|
|
1
1
|
import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
|
|
2
2
|
import { formatEnumLabel as t } from "../utils/formatEnumLabel.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
3
|
+
import { AvatarStack as n } from "../components/viz/AvatarStack.js";
|
|
4
|
+
import { SegmentedTabs as r } from "../components/viz/SegmentedTabs.js";
|
|
5
|
+
import "../components/viz/index.js";
|
|
6
|
+
import { useMemo as i, useState as a } from "react";
|
|
7
|
+
import { Briefcase as o, DollarSign as s, MapPin as c, Plus as l, Search as u, Users as d } from "lucide-react";
|
|
8
|
+
import { useNavigate as f } from "react-router-dom";
|
|
9
|
+
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
10
|
+
import { Badge as h, Button as g, CardContent as _, CardHeader as v, CardTitle as y, GlassCard as b, GradientText as x, IllustratedEmptyState as S, Input as C, PagePurpose as w, Pagination as T } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
import { usePagination as E } from "@burdenoff/fe-libs/shared/hooks";
|
|
12
|
+
import { useI18n as D } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
13
|
//#region src/crewfoundry/pages/HiringJobsPagePage.tsx
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
var O = [
|
|
15
|
+
"active",
|
|
16
|
+
"draft",
|
|
17
|
+
"paused",
|
|
18
|
+
"closed",
|
|
19
|
+
"filled"
|
|
20
|
+
];
|
|
21
|
+
function k(e) {
|
|
22
|
+
let t = new Date(e);
|
|
23
|
+
if (Number.isNaN(t.getTime())) return e;
|
|
24
|
+
let n = Math.floor((Date.now() - t.getTime()) / 864e5);
|
|
25
|
+
if (n <= 0) return "Today";
|
|
26
|
+
if (n === 1) return "1 day ago";
|
|
27
|
+
if (n < 7) return `${n} days ago`;
|
|
28
|
+
if (n < 30) {
|
|
29
|
+
let e = Math.floor(n / 7);
|
|
30
|
+
return e === 1 ? "1 week ago" : `${e} weeks ago`;
|
|
31
|
+
}
|
|
32
|
+
let r = Math.floor(n / 30);
|
|
33
|
+
return r === 1 ? "1 month ago" : `${r} months ago`;
|
|
34
|
+
}
|
|
35
|
+
function A({ label: e, children: t }) {
|
|
36
|
+
return /* @__PURE__ */ m("div", {
|
|
37
|
+
className: "flex items-center justify-between gap-3 text-sm",
|
|
38
|
+
children: [/* @__PURE__ */ p("span", {
|
|
39
|
+
className: "text-text-secondary",
|
|
40
|
+
children: e
|
|
41
|
+
}), /* @__PURE__ */ p("span", {
|
|
42
|
+
className: "font-medium text-text-primary text-right",
|
|
43
|
+
children: t
|
|
44
|
+
})]
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
function j() {
|
|
48
|
+
let { t: j } = D(), M = f(), { jobPostings: N, candidates: P } = e(), [F, I] = a(""), [L, R] = a("all"), { page: z, pageSize: B, offset: V, setPage: H } = E({ initialPageSize: 12 }), U = (e, t) => {
|
|
49
|
+
let n = j(e);
|
|
14
50
|
return n === e ? t : n;
|
|
15
|
-
},
|
|
16
|
-
|
|
51
|
+
}, W = i(() => {
|
|
52
|
+
let e = F.toLowerCase();
|
|
53
|
+
return N.filter((t) => {
|
|
54
|
+
let n = `${t.title} ${t.department} ${t.location}`.toLowerCase().includes(e), r = L === "all" || t.status === L;
|
|
55
|
+
return n && r;
|
|
56
|
+
});
|
|
57
|
+
}, [
|
|
58
|
+
N,
|
|
59
|
+
F,
|
|
60
|
+
L
|
|
61
|
+
]), G = W.slice(V, V + B), K = (e) => M(`../hiring/job-details-page?id=${encodeURIComponent(e)}`), q = N.filter((e) => e.status === "active").length, J = N.reduce((e, t) => e + t.applicants, 0), Y = i(() => {
|
|
62
|
+
let e = [{
|
|
63
|
+
id: "all",
|
|
64
|
+
label: "All",
|
|
65
|
+
count: N.length
|
|
66
|
+
}];
|
|
67
|
+
for (let n of O) {
|
|
68
|
+
let r = N.filter((e) => e.status === n).length;
|
|
69
|
+
(r > 0 || n === "active") && e.push({
|
|
70
|
+
id: n,
|
|
71
|
+
label: t(n),
|
|
72
|
+
count: r
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return e;
|
|
76
|
+
}, [N]), X = i(() => {
|
|
77
|
+
let e = /* @__PURE__ */ new Map();
|
|
78
|
+
for (let t of N) {
|
|
79
|
+
let n = t.title.trim().toLowerCase(), r = P.filter((e) => e.position.trim().toLowerCase() === n).map((e) => ({ name: `${e.firstName} ${e.lastName}`.trim() }));
|
|
80
|
+
e.set(t.id, r);
|
|
81
|
+
}
|
|
82
|
+
return e;
|
|
83
|
+
}, [N, P]);
|
|
84
|
+
return /* @__PURE__ */ m("div", {
|
|
17
85
|
className: "space-y-6 p-6",
|
|
18
86
|
children: [
|
|
19
|
-
/* @__PURE__ */
|
|
20
|
-
className: "flex items-
|
|
21
|
-
children: [/* @__PURE__ */
|
|
22
|
-
/* @__PURE__ */
|
|
87
|
+
/* @__PURE__ */ m("div", {
|
|
88
|
+
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
89
|
+
children: [/* @__PURE__ */ m("div", { children: [
|
|
90
|
+
/* @__PURE__ */ p("h1", {
|
|
23
91
|
className: "text-2xl font-semibold",
|
|
24
|
-
children: /* @__PURE__ */
|
|
92
|
+
children: /* @__PURE__ */ p(x, { children: U("crewfoundry.hiringJobs.title", "Job Postings") })
|
|
25
93
|
}),
|
|
26
|
-
/* @__PURE__ */
|
|
94
|
+
/* @__PURE__ */ m("p", {
|
|
27
95
|
className: "text-sm text-text-secondary",
|
|
28
96
|
children: [
|
|
29
|
-
|
|
97
|
+
q,
|
|
30
98
|
" ",
|
|
31
|
-
|
|
99
|
+
U("crewfoundry.hiringJobs.summary.activePositions", "active positions"),
|
|
32
100
|
" ·",
|
|
33
101
|
" ",
|
|
34
|
-
|
|
102
|
+
J,
|
|
35
103
|
" ",
|
|
36
|
-
|
|
104
|
+
U("crewfoundry.hiringJobs.summary.totalApplicants", "total applicants")
|
|
37
105
|
]
|
|
38
106
|
}),
|
|
39
|
-
/* @__PURE__ */
|
|
107
|
+
/* @__PURE__ */ p(w, {
|
|
40
108
|
className: "mt-1",
|
|
41
109
|
children: "All your open and past job postings in one place — use it to monitor applicant volume and manage what roles you're actively hiring for."
|
|
42
110
|
})
|
|
43
|
-
] }), /* @__PURE__ */
|
|
111
|
+
] }), /* @__PURE__ */ m(g, {
|
|
44
112
|
size: "sm",
|
|
45
|
-
onClick: () =>
|
|
46
|
-
children: [/* @__PURE__ */ l
|
|
113
|
+
onClick: () => M("../hiring/create-job-page"),
|
|
114
|
+
children: [/* @__PURE__ */ p(l, { className: "h-4 w-4 mr-1" }), U("crewfoundry.hiringJobs.actions.postNewJob", "Post New Job")]
|
|
47
115
|
})]
|
|
48
116
|
}),
|
|
49
|
-
/* @__PURE__ */
|
|
50
|
-
className: "
|
|
51
|
-
children: [/* @__PURE__ */
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
117
|
+
/* @__PURE__ */ m("div", {
|
|
118
|
+
className: "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between",
|
|
119
|
+
children: [/* @__PURE__ */ p("div", {
|
|
120
|
+
className: "-mx-1 overflow-x-auto px-1",
|
|
121
|
+
children: /* @__PURE__ */ p(r, {
|
|
122
|
+
tabs: Y,
|
|
123
|
+
value: L,
|
|
124
|
+
onChange: (e) => {
|
|
125
|
+
R(e), H(1);
|
|
126
|
+
},
|
|
127
|
+
"aria-label": "Filter job postings by status"
|
|
128
|
+
})
|
|
129
|
+
}), /* @__PURE__ */ m("div", {
|
|
130
|
+
className: "relative w-full max-w-sm",
|
|
131
|
+
children: [/* @__PURE__ */ p(u, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary" }), /* @__PURE__ */ p(C, {
|
|
132
|
+
placeholder: U("crewfoundry.hiringJobs.searchPlaceholder", "Search positions..."),
|
|
133
|
+
className: "pl-9",
|
|
134
|
+
value: F,
|
|
135
|
+
onChange: (e) => {
|
|
136
|
+
I(e.target.value), H(1);
|
|
137
|
+
}
|
|
138
|
+
})]
|
|
58
139
|
})]
|
|
59
140
|
}),
|
|
60
|
-
|
|
141
|
+
W.length === 0 && /* @__PURE__ */ p(S, {
|
|
61
142
|
illustration: "empty-search",
|
|
62
143
|
title: "No job postings found",
|
|
63
|
-
description: "Try adjusting your search, or create a new job posting."
|
|
144
|
+
description: "Try adjusting your search or status filter, or create a new job posting."
|
|
64
145
|
}),
|
|
65
|
-
/* @__PURE__ */
|
|
66
|
-
className: "grid gap-4 md:grid-cols-2",
|
|
67
|
-
children:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(t
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
/* @__PURE__ */
|
|
91
|
-
className: "flex items-center gap-1 text-sm text-text-secondary",
|
|
92
|
-
children: [/* @__PURE__ */ l(r, { className: "h-3 w-3" }), e.department]
|
|
93
|
-
}),
|
|
94
|
-
/* @__PURE__ */ u("div", {
|
|
95
|
-
className: "flex items-center gap-1 text-sm text-text-secondary",
|
|
96
|
-
children: [/* @__PURE__ */ l(i, { className: "h-3 w-3" }), e.location]
|
|
97
|
-
}),
|
|
98
|
-
/* @__PURE__ */ u("div", {
|
|
99
|
-
className: "flex items-center gap-1 text-sm text-text-secondary",
|
|
100
|
-
children: [
|
|
101
|
-
/* @__PURE__ */ l(s, { className: "h-3 w-3" }),
|
|
102
|
-
e.applicants,
|
|
103
|
-
" ",
|
|
104
|
-
P("crewfoundry.hiringJobs.card.applicants", "applicants")
|
|
105
|
-
]
|
|
106
|
-
}),
|
|
107
|
-
/* @__PURE__ */ u("div", {
|
|
108
|
-
className: "text-sm text-text-secondary",
|
|
109
|
-
children: [
|
|
110
|
-
"$",
|
|
111
|
-
(e.salaryMin / 1e3).toFixed(0),
|
|
112
|
-
"K — $",
|
|
113
|
-
(e.salaryMax / 1e3).toFixed(0),
|
|
114
|
-
"K"
|
|
115
|
-
]
|
|
116
|
-
})
|
|
117
|
-
]
|
|
118
|
-
}), /* @__PURE__ */ u("div", {
|
|
119
|
-
className: "flex items-center justify-between text-xs text-text-secondary",
|
|
120
|
-
children: [/* @__PURE__ */ u("div", {
|
|
121
|
-
className: "flex gap-1",
|
|
122
|
-
children: [/* @__PURE__ */ l(d, {
|
|
146
|
+
/* @__PURE__ */ p("div", {
|
|
147
|
+
className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3",
|
|
148
|
+
children: G.map((e) => {
|
|
149
|
+
let r = X.get(e.id) ?? [];
|
|
150
|
+
return /* @__PURE__ */ m(b, {
|
|
151
|
+
treatment: "glass",
|
|
152
|
+
role: "button",
|
|
153
|
+
tabIndex: 0,
|
|
154
|
+
"aria-label": `View ${e.title}`,
|
|
155
|
+
className: "flex cursor-pointer flex-col transition-shadow hover:shadow-elevation-2 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]",
|
|
156
|
+
onClick: () => K(e.id),
|
|
157
|
+
onKeyDown: (t) => {
|
|
158
|
+
(t.key === "Enter" || t.key === " ") && (t.preventDefault(), K(e.id));
|
|
159
|
+
},
|
|
160
|
+
children: [/* @__PURE__ */ m(v, { children: [/* @__PURE__ */ m("div", {
|
|
161
|
+
className: "flex items-start justify-between gap-2",
|
|
162
|
+
children: [/* @__PURE__ */ p(y, {
|
|
163
|
+
className: "text-lg leading-snug",
|
|
164
|
+
children: e.title
|
|
165
|
+
}), /* @__PURE__ */ p(h, {
|
|
166
|
+
variant: e.status === "active" ? "default" : e.status === "draft" ? "secondary" : "outline",
|
|
167
|
+
children: t(e.status)
|
|
168
|
+
})]
|
|
169
|
+
}), /* @__PURE__ */ m("div", {
|
|
170
|
+
className: "mt-2 flex flex-wrap gap-1.5",
|
|
171
|
+
children: [/* @__PURE__ */ p(h, {
|
|
123
172
|
variant: "outline",
|
|
124
173
|
className: "text-[10px]",
|
|
125
|
-
children: e.type
|
|
126
|
-
}), /* @__PURE__ */
|
|
174
|
+
children: t(e.type)
|
|
175
|
+
}), /* @__PURE__ */ p(h, {
|
|
127
176
|
variant: "outline",
|
|
128
177
|
className: "text-[10px]",
|
|
129
178
|
children: t(e.experienceLevel)
|
|
130
179
|
})]
|
|
131
|
-
}), /* @__PURE__ */
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
180
|
+
})] }), /* @__PURE__ */ m(_, {
|
|
181
|
+
className: "flex flex-1 flex-col gap-2.5",
|
|
182
|
+
children: [
|
|
183
|
+
/* @__PURE__ */ p(A, {
|
|
184
|
+
label: U("crewfoundry.hiringJobs.card.department", "Department"),
|
|
185
|
+
children: /* @__PURE__ */ m("span", {
|
|
186
|
+
className: "inline-flex items-center gap-1",
|
|
187
|
+
children: [/* @__PURE__ */ p(o, { className: "h-3 w-3 text-text-secondary" }), e.department || "—"]
|
|
188
|
+
})
|
|
189
|
+
}),
|
|
190
|
+
/* @__PURE__ */ p(A, {
|
|
191
|
+
label: U("crewfoundry.hiringJobs.card.location", "Location"),
|
|
192
|
+
children: /* @__PURE__ */ m("span", {
|
|
193
|
+
className: "inline-flex items-center gap-1",
|
|
194
|
+
children: [/* @__PURE__ */ p(c, { className: "h-3 w-3 text-text-secondary" }), e.location || "—"]
|
|
195
|
+
})
|
|
196
|
+
}),
|
|
197
|
+
/* @__PURE__ */ p(A, {
|
|
198
|
+
label: U("crewfoundry.hiringJobs.card.salary", "Salary"),
|
|
199
|
+
children: /* @__PURE__ */ m("span", {
|
|
200
|
+
className: "inline-flex items-center gap-0.5 tabular-nums",
|
|
201
|
+
children: [
|
|
202
|
+
/* @__PURE__ */ p(s, { className: "h-3 w-3 text-text-secondary" }),
|
|
203
|
+
(e.salaryMin / 1e3).toFixed(0),
|
|
204
|
+
"k – $",
|
|
205
|
+
(e.salaryMax / 1e3).toFixed(0),
|
|
206
|
+
"k"
|
|
207
|
+
]
|
|
208
|
+
})
|
|
209
|
+
}),
|
|
210
|
+
/* @__PURE__ */ p(A, {
|
|
211
|
+
label: U("crewfoundry.hiringJobs.card.applicantsLabel", "Applicants"),
|
|
212
|
+
children: /* @__PURE__ */ m("span", {
|
|
213
|
+
className: "inline-flex items-center gap-2",
|
|
214
|
+
children: [r.length > 0 && /* @__PURE__ */ p(n, {
|
|
215
|
+
people: r,
|
|
216
|
+
max: 4,
|
|
217
|
+
size: 22
|
|
218
|
+
}), /* @__PURE__ */ m("span", {
|
|
219
|
+
className: "inline-flex items-center gap-1 tabular-nums",
|
|
220
|
+
children: [/* @__PURE__ */ p(d, { className: "h-3 w-3 text-text-secondary" }), e.applicants]
|
|
221
|
+
})]
|
|
222
|
+
})
|
|
223
|
+
}),
|
|
224
|
+
/* @__PURE__ */ p("div", {
|
|
225
|
+
className: "mt-auto pt-1",
|
|
226
|
+
children: /* @__PURE__ */ p(A, {
|
|
227
|
+
label: U("crewfoundry.hiringJobs.card.posted", "Posted"),
|
|
228
|
+
children: /* @__PURE__ */ p("span", {
|
|
229
|
+
className: "text-text-secondary font-normal",
|
|
230
|
+
children: k(e.postedDate)
|
|
231
|
+
})
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
]
|
|
235
|
+
})]
|
|
236
|
+
}, e.id);
|
|
237
|
+
})
|
|
138
238
|
}),
|
|
139
|
-
|
|
140
|
-
page:
|
|
141
|
-
pageSize:
|
|
142
|
-
totalItems:
|
|
143
|
-
onPageChange:
|
|
239
|
+
W.length > B && /* @__PURE__ */ p(T, {
|
|
240
|
+
page: z,
|
|
241
|
+
pageSize: B,
|
|
242
|
+
totalItems: W.length,
|
|
243
|
+
onPageChange: H
|
|
144
244
|
})
|
|
145
245
|
]
|
|
146
246
|
});
|
|
147
247
|
}
|
|
148
248
|
//#endregion
|
|
149
|
-
export {
|
|
249
|
+
export { j as HiringJobsPagePage };
|
|
150
250
|
|
|
151
251
|
//# sourceMappingURL=HiringJobsPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringJobsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobsPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Badge,\n Button,\n CardContent,\n CardDescription,\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, MapPin, Plus, Search, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nexport function HiringJobsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { jobPostings } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\n const filtered = jobPostings.filter((j) =>\n `${j.title} ${j.department} ${j.location}`.toLowerCase().includes(search.toLowerCase())\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 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>{t('crewfoundry.hiringJobs.title', 'Job Postings')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeJobs} {t('crewfoundry.hiringJobs.summary.activePositions', 'active positions')} ·{' '}\n {totalApplicants}{' '}\n {t('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 {t('crewfoundry.hiringJobs.actions.postNewJob', 'Post New Job')}\n </Button>\n </div>\n\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={t('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\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job postings found\"\n description=\"Try adjusting your search, or create a new job posting.\"\n />\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n {paged.map((job, _idx) => (\n <GlassCard\n key={job.id}\n treatment={_idx === 0 ? 'glass' : 'plain'}\n glow={_idx === 0}\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${job.title}`}\n className=\"cursor-pointer 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-center justify-between\">\n <CardTitle className=\"text-lg\">{job.title}</CardTitle>\n <Badge\n variant={\n job.status === 'active'\n ? 'default'\n : job.status === 'paused'\n ? 'secondary'\n : 'outline'\n }\n >\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <CardDescription>{job.description}</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-2 gap-3 mb-3\">\n <div className=\"flex items-center gap-1 text-sm text-text-secondary\">\n <Briefcase className=\"h-3 w-3\" />\n {job.department}\n </div>\n <div className=\"flex items-center gap-1 text-sm text-text-secondary\">\n <MapPin className=\"h-3 w-3\" />\n {job.location}\n </div>\n <div className=\"flex items-center gap-1 text-sm text-text-secondary\">\n <Users className=\"h-3 w-3\" />\n {job.applicants} {t('crewfoundry.hiringJobs.card.applicants', 'applicants')}\n </div>\n <div className=\"text-sm text-text-secondary\">\n ${(job.salaryMin / 1000).toFixed(0)}K — ${(job.salaryMax / 1000).toFixed(0)}K\n </div>\n </div>\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <div className=\"flex gap-1\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {job.type.replace('_', ' ')}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.experienceLevel)}\n </Badge>\n </div>\n <span>\n {t('crewfoundry.hiringJobs.card.deadline', 'Deadline')}: {job.deadline}\n </span>\n </div>\n </CardContent>\n </GlassCard>\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 { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\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-dom';\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'][] = [\n 'active',\n 'draft',\n 'paused',\n 'closed',\n 'filled',\n];\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 { t: translate } = useI18n();\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 t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\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>{t('crewfoundry.hiringJobs.title', 'Job Postings')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeJobs} {t('crewfoundry.hiringJobs.summary.activePositions', 'active positions')} ·{' '}\n {totalApplicants}{' '}\n {t('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 {t('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={t('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={t('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={t('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={t('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={t('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={t('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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"}
|