@burdenoff/microfe-crewfoundry 2026.704.1 → 2026.704.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/crewfoundry/components/forms/FormModal.js.map +1 -1
- package/dist/crewfoundry/pages/AdminAdminPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsDirectoryPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsFilesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsTeamsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyCompanyDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyCompanyProfilePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardEnhancedDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardExecutiveDashboardPage.js.map +1 -1
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.js +1 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsCreateProjectPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardExecutiveDashboardPage.js","names":[],"sources":["../../../src/crewfoundry/pages/DashboardExecutiveDashboardPage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n ResponsiveTable,\n type ResponsiveTableColumn,\n Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { Building2, Percent, Target } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useCrewFoundryStore, type Department } from '../store/crewfoundryStore';\n\nexport function DashboardExecutiveDashboardPage() {\n const { employees, departments, projects, jobPostings } = useCrewFoundryStore();\n\n const riskIndicators = useMemo(\n () => [\n {\n metric: 'Flight Risk Employees',\n value: employees.filter((employee) => employee.performanceScore < 3.6).length,\n severity: 'warning' as const,\n detail: `${Math.round((employees.filter((employee) => employee.performanceScore < 3.6).length / Math.max(employees.length, 1)) * 100)}% of workforce`,\n },\n {\n metric: 'Employees On Leave',\n value: employees.filter((employee) => employee.status === 'on_leave').length,\n severity: 'error' as const,\n detail: 'Coverage follow-ups required',\n },\n {\n metric: 'Unfilled Lead Roles',\n value: jobPostings.filter(\n (posting) => posting.status === 'active' && posting.experienceLevel === 'lead'\n ).length,\n severity: 'warning' as const,\n detail: 'Open lead-level positions',\n },\n {\n metric: 'Contract Employees',\n value: employees.filter((employee) => employee.type === 'contract').length,\n severity: 'info' as const,\n detail: 'Contract renewals in focus',\n },\n ],\n [employees, jobPostings]\n );\n\n const totalSalaryBudget = employees.reduce((sum, e) => sum + e.salary, 0);\n const totalDeptBudget = departments.reduce((sum, d) => sum + d.budget, 0);\n const totalProjectBudget = projects.reduce((sum, p) => sum + p.budget, 0);\n const totalProjectSpent = projects.reduce((sum, p) => sum + p.spent, 0);\n const budgetUtilization =\n totalProjectBudget > 0 ? Math.round((totalProjectSpent / totalProjectBudget) * 100) : 0;\n\n const terminatedCount = employees.filter((e) => e.status === 'terminated').length;\n const retentionRate =\n employees.length > 0 ? Math.round((1 - terminatedCount / employees.length) * 100) : 0;\n\n const totalHeadcount = departments.reduce((sum, d) => sum + d.employeeCount, 0);\n const avgCostPerEmployee = totalHeadcount > 0 ? totalDeptBudget / totalHeadcount : 0;\n\n const departmentColumns: ResponsiveTableColumn<Department>[] = [\n {\n key: 'name',\n header: 'Department',\n priority: 'primary',\n cell: (dept) => <span className=\"font-medium\">{dept.name}</span>,\n },\n {\n key: 'head',\n header: 'Head',\n priority: 'secondary',\n cell: (dept) => <span className=\"text-text-secondary\">{dept.headName}</span>,\n },\n {\n key: 'headcount',\n header: 'Headcount',\n priority: 'secondary',\n cell: (dept) => <span className=\"md:block md:text-right\">{dept.employeeCount}</span>,\n },\n {\n key: 'budget',\n header: 'Budget',\n priority: 'secondary',\n cell: (dept) => (\n <span className=\"md:block md:text-right\">${(dept.budget / 1000000).toFixed(1)}M</span>\n ),\n },\n {\n key: 'costPerEmployee',\n header: 'Cost/Employee',\n priority: 'primary',\n cell: (dept) => {\n const costPerEmp = dept.employeeCount > 0 ? Math.round(dept.budget / dept.employeeCount) : 0;\n return <span className=\"md:block md:text-right\">${(costPerEmp / 1000).toFixed(0)}K</span>;\n },\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Executive Dashboard</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Strategic workforce insights for leadership</p>\n <PagePurpose className=\"mt-1\">The executive snapshot — top-line workforce, financial, and hiring KPIs in one view so leaders can make decisions fast.</PagePurpose>\n </div>\n <Badge variant=\"outline\">Q1 2026</Badge>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Workforce Cost</CardTitle>\n <Building2 className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalSalaryBudget / 1000000).toFixed(1)}M\n </div>\n <p className=\"text-xs text-text-secondary\">Annual salary expenditure</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Budget Utilization</CardTitle>\n <Percent className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{budgetUtilization}%</div>\n <p className=\"text-xs text-text-secondary\">\n ${(totalProjectSpent / 1000000).toFixed(1)}M of $\n {(totalProjectBudget / 1000000).toFixed(1)}M project budget\n </p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Retention Rate</CardTitle>\n <Target className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{retentionRate}%</div>\n <p className=\"text-xs text-text-secondary\">\n {terminatedCount} of {employees.length} employees no longer active\n </p>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Risk Indicators</CardTitle>\n <CardDescription>Items requiring executive attention</CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-3 md:grid-cols-2\">\n {riskIndicators.map((risk) => (\n <div key={risk.metric} className=\"flex items-center justify-between rounded border p-3\">\n <div className=\"flex items-center gap-3\">\n <div\n className={`h-3 w-3 rounded-full ${\n risk.severity === 'error'\n ? 'bg-status-error-text'\n : risk.severity === 'warning'\n ? 'bg-status-warning-text'\n : 'bg-status-info-text'\n }`}\n />\n <div>\n <p className=\"text-sm font-medium\">{risk.metric}</p>\n <p className=\"text-xs text-text-secondary\">{risk.detail}</p>\n </div>\n </div>\n <span className=\"text-lg font-bold\">{risk.value}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Department Budget Status</CardTitle>\n <CardDescription>Annual budget allocation and headcount</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n columns={departmentColumns}\n data={departments}\n rowKey={(dept) => dept.id}\n />\n <Separator className=\"my-3\" />\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-sm font-bold\">\n <span>Total</span>\n <span>{totalHeadcount} employees</span>\n <span>${(totalDeptBudget / 1000000).toFixed(1)}M budget</span>\n <span>${(avgCostPerEmployee / 1000).toFixed(0)}K / employee</span>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardExecutiveDashboardPage.js","names":[],"sources":["../../../src/crewfoundry/pages/DashboardExecutiveDashboardPage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n ResponsiveTable,\n type ResponsiveTableColumn,\n Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { Building2, Percent, Target } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useCrewFoundryStore, type Department } from '../store/crewfoundryStore';\n\nexport function DashboardExecutiveDashboardPage() {\n const { employees, departments, projects, jobPostings } = useCrewFoundryStore();\n\n const riskIndicators = useMemo(\n () => [\n {\n metric: 'Flight Risk Employees',\n value: employees.filter((employee) => employee.performanceScore < 3.6).length,\n severity: 'warning' as const,\n detail: `${Math.round((employees.filter((employee) => employee.performanceScore < 3.6).length / Math.max(employees.length, 1)) * 100)}% of workforce`,\n },\n {\n metric: 'Employees On Leave',\n value: employees.filter((employee) => employee.status === 'on_leave').length,\n severity: 'error' as const,\n detail: 'Coverage follow-ups required',\n },\n {\n metric: 'Unfilled Lead Roles',\n value: jobPostings.filter(\n (posting) => posting.status === 'active' && posting.experienceLevel === 'lead'\n ).length,\n severity: 'warning' as const,\n detail: 'Open lead-level positions',\n },\n {\n metric: 'Contract Employees',\n value: employees.filter((employee) => employee.type === 'contract').length,\n severity: 'info' as const,\n detail: 'Contract renewals in focus',\n },\n ],\n [employees, jobPostings]\n );\n\n const totalSalaryBudget = employees.reduce((sum, e) => sum + e.salary, 0);\n const totalDeptBudget = departments.reduce((sum, d) => sum + d.budget, 0);\n const totalProjectBudget = projects.reduce((sum, p) => sum + p.budget, 0);\n const totalProjectSpent = projects.reduce((sum, p) => sum + p.spent, 0);\n const budgetUtilization =\n totalProjectBudget > 0 ? Math.round((totalProjectSpent / totalProjectBudget) * 100) : 0;\n\n const terminatedCount = employees.filter((e) => e.status === 'terminated').length;\n const retentionRate =\n employees.length > 0 ? Math.round((1 - terminatedCount / employees.length) * 100) : 0;\n\n const totalHeadcount = departments.reduce((sum, d) => sum + d.employeeCount, 0);\n const avgCostPerEmployee = totalHeadcount > 0 ? totalDeptBudget / totalHeadcount : 0;\n\n const departmentColumns: ResponsiveTableColumn<Department>[] = [\n {\n key: 'name',\n header: 'Department',\n priority: 'primary',\n cell: (dept) => <span className=\"font-medium\">{dept.name}</span>,\n },\n {\n key: 'head',\n header: 'Head',\n priority: 'secondary',\n cell: (dept) => <span className=\"text-text-secondary\">{dept.headName}</span>,\n },\n {\n key: 'headcount',\n header: 'Headcount',\n priority: 'secondary',\n cell: (dept) => <span className=\"md:block md:text-right\">{dept.employeeCount}</span>,\n },\n {\n key: 'budget',\n header: 'Budget',\n priority: 'secondary',\n cell: (dept) => (\n <span className=\"md:block md:text-right\">${(dept.budget / 1000000).toFixed(1)}M</span>\n ),\n },\n {\n key: 'costPerEmployee',\n header: 'Cost/Employee',\n priority: 'primary',\n cell: (dept) => {\n const costPerEmp =\n dept.employeeCount > 0 ? Math.round(dept.budget / dept.employeeCount) : 0;\n return <span className=\"md:block md:text-right\">${(costPerEmp / 1000).toFixed(0)}K</span>;\n },\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Executive Dashboard</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Strategic workforce insights for leadership</p>\n <PagePurpose className=\"mt-1\">\n The executive snapshot — top-line workforce, financial, and hiring KPIs in one view so\n leaders can make decisions fast.\n </PagePurpose>\n </div>\n <Badge variant=\"outline\">Q1 2026</Badge>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Workforce Cost</CardTitle>\n <Building2 className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalSalaryBudget / 1000000).toFixed(1)}M\n </div>\n <p className=\"text-xs text-text-secondary\">Annual salary expenditure</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Budget Utilization</CardTitle>\n <Percent className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{budgetUtilization}%</div>\n <p className=\"text-xs text-text-secondary\">\n ${(totalProjectSpent / 1000000).toFixed(1)}M of $\n {(totalProjectBudget / 1000000).toFixed(1)}M project budget\n </p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Retention Rate</CardTitle>\n <Target className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{retentionRate}%</div>\n <p className=\"text-xs text-text-secondary\">\n {terminatedCount} of {employees.length} employees no longer active\n </p>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Risk Indicators</CardTitle>\n <CardDescription>Items requiring executive attention</CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-3 md:grid-cols-2\">\n {riskIndicators.map((risk) => (\n <div key={risk.metric} className=\"flex items-center justify-between rounded border p-3\">\n <div className=\"flex items-center gap-3\">\n <div\n className={`h-3 w-3 rounded-full ${\n risk.severity === 'error'\n ? 'bg-status-error-text'\n : risk.severity === 'warning'\n ? 'bg-status-warning-text'\n : 'bg-status-info-text'\n }`}\n />\n <div>\n <p className=\"text-sm font-medium\">{risk.metric}</p>\n <p className=\"text-xs text-text-secondary\">{risk.detail}</p>\n </div>\n </div>\n <span className=\"text-lg font-bold\">{risk.value}</span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Department Budget Status</CardTitle>\n <CardDescription>Annual budget allocation and headcount</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n columns={departmentColumns}\n data={departments}\n rowKey={(dept) => dept.id}\n />\n <Separator className=\"my-3\" />\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-sm font-bold\">\n <span>Total</span>\n <span>{totalHeadcount} employees</span>\n <span>${(totalDeptBudget / 1000000).toFixed(1)}M budget</span>\n <span>${(avgCostPerEmployee / 1000).toFixed(0)}K / employee</span>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HelpApiDocumentationPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HelpApiDocumentationPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n Check,\n ChevronRight,\n Code2,\n Copy,\n Database,\n ListTree,\n Lock,\n Network,\n Search,\n} from 'lucide-react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\n// The CrewFoundry API is served by wspace-crewfoundry-svc as an Apollo\n// Federation v2 subgraph, reached through the workspaces public GraphQL\n// gateway. There is no separate REST surface — this reference is derived\n// directly from the published subgraph schema.\nconst ENDPOINT = 'https://graphqlworkspaces.burdenoff.com/workspaces/graphql';\n\n// The 17 domain entities exposed by the subgraph, each with create / update /\n// remove mutations. Names and input types mirror the published schema exactly.\nconst ENTITIES: { name: string; domain: string }[] = [\n { name: 'Announcement', domain: 'Communications' },\n { name: 'Candidate', domain: 'Hiring' },\n { name: 'Client', domain: 'Finance' },\n { name: 'Course', domain: 'Learning' },\n { name: 'Department', domain: 'HR' },\n { name: 'Employee', domain: 'HR' },\n { name: 'Interview', domain: 'Hiring' },\n { name: 'Invoice', domain: 'Finance' },\n { name: 'JobPosting', domain: 'Hiring' },\n { name: 'LearningPath', domain: 'Learning' },\n { name: 'LeaveRequest', domain: 'HR' },\n { name: 'MarketplaceService', domain: 'Marketplace' },\n { name: 'PayrollRecord', domain: 'Finance' },\n { name: 'Project', domain: 'Projects' },\n { name: 'Team', domain: 'Teams' },\n { name: 'TimeEntry', domain: 'HR' },\n { name: 'Vendor', domain: 'Finance' },\n];\n\ntype OperationKind = 'Query' | 'Mutation';\n\ntype ApiOperation = {\n kind: OperationKind;\n name: string;\n signature: string;\n description: string;\n};\n\ntype ApiGroup = {\n id: string;\n name: string;\n description: string;\n operations: ApiOperation[];\n};\n\nconst kindColors: Record<OperationKind, string> = {\n Query: 'bg-status-success-bg text-status-success-text',\n Mutation: 'bg-status-info-bg text-status-info-text',\n};\n\nconst humanize = (value: string): string =>\n value.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase();\n\nconst copyToClipboard = async (text: string): Promise<boolean> => {\n try {\n if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n return true;\n }\n } catch {\n // Fall through to the legacy path for insecure / restricted contexts.\n }\n try {\n if (typeof document === 'undefined') {\n return false;\n }\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n document.body.appendChild(textarea);\n textarea.focus();\n textarea.select();\n const succeeded = document.execCommand('copy');\n document.body.removeChild(textarea);\n return succeeded;\n } catch {\n return false;\n }\n};\n\nexport function HelpApiDocumentationPagePage() {\n const { employees, jobPostings, candidates, payrollRecords, leaveRequests } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [search, setSearch] = useState('');\n const [copied, setCopied] = useState<string | null>(null);\n\n const groups = useMemo<ApiGroup[]>(() => {\n const createOps: ApiOperation[] = ENTITIES.map((entity) => ({\n kind: 'Mutation',\n name: `createCrew${entity.name}`,\n signature: `createCrew${entity.name}(input: CreateCrew${entity.name}Input!): Crew${entity.name}!`,\n description: `Create a ${humanize(entity.name)} record (${entity.domain}).`,\n }));\n const updateOps: ApiOperation[] = ENTITIES.map((entity) => ({\n kind: 'Mutation',\n name: `updateCrew${entity.name}`,\n signature: `updateCrew${entity.name}(id: ID!, input: UpdateCrew${entity.name}Input!): Crew${entity.name}!`,\n description: `Update an existing ${humanize(entity.name)} record (${entity.domain}).`,\n }));\n const removeOps: ApiOperation[] = ENTITIES.map((entity) => ({\n kind: 'Mutation',\n name: `removeCrew${entity.name}`,\n signature: `removeCrew${entity.name}(id: ID!): Boolean!`,\n description: `Delete a ${humanize(entity.name)} record (${entity.domain}).`,\n }));\n\n return [\n {\n id: 'queries',\n name: 'Queries',\n description: 'Read the workspace dataset',\n operations: [\n {\n kind: 'Query',\n name: 'crewfoundryBootstrap',\n signature: 'crewfoundryBootstrap: CrewfoundryBootstrap!',\n description: `Load the full workspace dataset in one call — ${employees.length} employees, ${jobPostings.length} job postings, ${candidates.length} candidates, ${payrollRecords.length} payroll records, ${leaveRequests.length} leave requests, and every other CrewFoundry collection.`,\n },\n ],\n },\n {\n id: 'create',\n name: 'Create mutations',\n description: 'Add new records',\n operations: createOps,\n },\n {\n id: 'update',\n name: 'Update mutations',\n description: 'Modify existing records',\n operations: updateOps,\n },\n {\n id: 'delete',\n name: 'Delete mutations',\n description: 'Remove records',\n operations: removeOps,\n },\n {\n id: 'workflow',\n name: 'Workflow mutations',\n description: 'Enrollment & notifications',\n operations: [\n {\n kind: 'Mutation',\n name: 'enrollCrewCourse',\n signature: 'enrollCrewCourse(id: ID!): CrewCourse!',\n description: 'Enroll the current member into a learning course.',\n },\n {\n kind: 'Mutation',\n name: 'markCrewNotificationRead',\n signature: 'markCrewNotificationRead(id: ID!): Boolean!',\n description: 'Mark a single notification as read.',\n },\n {\n kind: 'Mutation',\n name: 'markAllCrewNotificationsRead',\n signature: 'markAllCrewNotificationsRead: Boolean!',\n description: 'Mark every notification as read.',\n },\n ],\n },\n ];\n }, [\n candidates.length,\n employees.length,\n jobPostings.length,\n leaveRequests.length,\n payrollRecords.length,\n ]);\n\n const totalOperations = useMemo(\n () => groups.reduce((sum, group) => sum + group.operations.length, 0),\n [groups]\n );\n\n const filteredGroups = useMemo(() => {\n const term = search.trim().toLowerCase();\n if (!term) {\n return groups;\n }\n return groups\n .map((group) => ({\n ...group,\n operations: group.operations.filter(\n (op) =>\n op.name.toLowerCase().includes(term) ||\n op.signature.toLowerCase().includes(term) ||\n op.description.toLowerCase().includes(term)\n ),\n }))\n .filter((group) => group.operations.length > 0);\n }, [groups, search]);\n\n const handleCopy = async (value: string) => {\n const ok = await copyToClipboard(value);\n if (ok) {\n setCopied(value);\n showToast('Copied to clipboard', 'success');\n window.setTimeout(() => setCopied((current) => (current === value ? null : current)), 1500);\n } else {\n showToast('Unable to copy to clipboard', 'error');\n }\n };\n\n const scrollToGroup = (id: string) => {\n const target = typeof document !== 'undefined' ? document.getElementById(`group-${id}`) : null;\n target?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n };\n\n const statTiles = [\n { icon: Network, label: 'Protocol', value: 'GraphQL', color: 'text-status-info-text' },\n {\n icon: Code2,\n label: 'Composition',\n value: 'Apollo Federation v2',\n color: 'text-action-primary-bg',\n },\n { icon: Lock, label: 'Auth', value: 'Bearer token', color: 'text-status-success-text' },\n {\n icon: ListTree,\n label: 'Operations',\n value: String(totalOperations),\n color: 'text-status-warning-text',\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-3 md:flex-row md:items-start md:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>API Documentation</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n CrewFoundry GraphQL API reference for integrations\n </p>\n <PagePurpose className=\"mt-1\">Browse the CrewFoundry API reference — use it to find operations, understand request formats, and integrate CrewFoundry with your own tools.</PagePurpose>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\" className=\"max-w-full truncate font-mono text-[10px]\">\n {ENDPOINT}\n </Badge>\n <Button\n size=\"sm\"\n variant=\"outline\"\n aria-label=\"Copy the GraphQL endpoint URL\"\n onClick={() => void handleCopy(ENDPOINT)}\n >\n {copied === ENDPOINT ? (\n <Check className=\"mr-1 h-3 w-3 text-status-success-text\" />\n ) : (\n <Copy className=\"mr-1 h-3 w-3\" />\n )}\n Copy endpoint\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-4\">\n {statTiles.map((item) => (\n <Card key={item.label}>\n <CardContent className=\"flex items-center gap-3 pt-6\">\n <item.icon className={`h-5 w-5 ${item.color}`} />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">{item.label}</p>\n <p className=\"truncate text-sm font-medium\">{item.value}</p>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-4\">\n <div className=\"space-y-4\">\n <EmphasisPanel>\n <div className=\"mb-2 text-sm font-semibold\">Jump to</div>\n <div className=\"space-y-2\">\n {groups.map((group) => (\n <button\n key={group.id}\n type=\"button\"\n aria-label={`Jump to ${group.name}`}\n onClick={() => scrollToGroup(group.id)}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-bg-sunken\"\n >\n <ListTree className=\"h-3 w-3 text-text-secondary\" />\n <span>{group.name}</span>\n <Badge variant=\"outline\" className=\"ml-auto text-[10px]\">\n {group.operations.length}\n </Badge>\n <ChevronRight className=\"h-3 w-3 text-text-secondary\" />\n </button>\n ))}\n </div>\n </EmphasisPanel>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-sm\">Authentication</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <div className=\"rounded-md bg-bg-sunken p-3\">\n <code className=\"font-mono text-xs\">Authorization: Bearer {'<token>'}</code>\n </div>\n <p className=\"text-xs text-text-secondary\">\n Requests are authenticated at the gateway edge. Send your session bearer token on\n every request; the gateway resolves the actor and enforces workspace-scoped RBAC\n before the subgraph runs.\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-4 lg:col-span-3\">\n <div className=\"relative max-w-md\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search operations...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n />\n </div>\n\n {filteredGroups.map((group) => (\n <Card key={group.id} id={`group-${group.id}`} className=\"scroll-mt-6\">\n <CardHeader>\n <div className=\"flex items-center gap-2\">\n <Database className=\"h-4 w-4 text-text-secondary\" />\n <CardTitle className=\"text-base\">{group.name}</CardTitle>\n </div>\n <CardDescription>\n {group.description} · {group.operations.length} operations\n </CardDescription>\n </CardHeader>\n <CardContent className=\"p-0\">\n {group.operations.map((op, index) => (\n <div\n key={op.name}\n className={`flex items-center gap-3 px-6 py-3 hover:bg-bg-sunken/50 ${index < group.operations.length - 1 ? 'border-b' : ''}`}\n >\n <Badge\n className={`${kindColors[op.kind]} min-w-[64px] justify-center border-0 font-mono text-[10px]`}\n >\n {op.kind}\n </Badge>\n <div className=\"min-w-0 flex-1\">\n <code className=\"block truncate font-mono text-sm\">{op.signature}</code>\n <span className=\"text-xs text-text-secondary\">{op.description}</span>\n </div>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 w-6 shrink-0 p-0\"\n aria-label={`Copy ${op.name} operation name`}\n onClick={() => void handleCopy(op.name)}\n >\n {copied === op.name ? (\n <Check className=\"h-3 w-3 text-status-success-text\" />\n ) : (\n <Copy className=\"h-3 w-3 text-text-secondary\" />\n )}\n </Button>\n </div>\n ))}\n </CardContent>\n </Card>\n ))}\n\n {filteredGroups.length === 0 && (\n <div className=\"py-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No operations match\"\n description=\"Try a different search term to find the operation you need.\"\n />\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HelpApiDocumentationPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HelpApiDocumentationPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n Check,\n ChevronRight,\n Code2,\n Copy,\n Database,\n ListTree,\n Lock,\n Network,\n Search,\n} from 'lucide-react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\n// The CrewFoundry API is served by wspace-crewfoundry-svc as an Apollo\n// Federation v2 subgraph, reached through the workspaces public GraphQL\n// gateway. There is no separate REST surface — this reference is derived\n// directly from the published subgraph schema.\nconst ENDPOINT = 'https://graphqlworkspaces.burdenoff.com/workspaces/graphql';\n\n// The 17 domain entities exposed by the subgraph, each with create / update /\n// remove mutations. Names and input types mirror the published schema exactly.\nconst ENTITIES: { name: string; domain: string }[] = [\n { name: 'Announcement', domain: 'Communications' },\n { name: 'Candidate', domain: 'Hiring' },\n { name: 'Client', domain: 'Finance' },\n { name: 'Course', domain: 'Learning' },\n { name: 'Department', domain: 'HR' },\n { name: 'Employee', domain: 'HR' },\n { name: 'Interview', domain: 'Hiring' },\n { name: 'Invoice', domain: 'Finance' },\n { name: 'JobPosting', domain: 'Hiring' },\n { name: 'LearningPath', domain: 'Learning' },\n { name: 'LeaveRequest', domain: 'HR' },\n { name: 'MarketplaceService', domain: 'Marketplace' },\n { name: 'PayrollRecord', domain: 'Finance' },\n { name: 'Project', domain: 'Projects' },\n { name: 'Team', domain: 'Teams' },\n { name: 'TimeEntry', domain: 'HR' },\n { name: 'Vendor', domain: 'Finance' },\n];\n\ntype OperationKind = 'Query' | 'Mutation';\n\ntype ApiOperation = {\n kind: OperationKind;\n name: string;\n signature: string;\n description: string;\n};\n\ntype ApiGroup = {\n id: string;\n name: string;\n description: string;\n operations: ApiOperation[];\n};\n\nconst kindColors: Record<OperationKind, string> = {\n Query: 'bg-status-success-bg text-status-success-text',\n Mutation: 'bg-status-info-bg text-status-info-text',\n};\n\nconst humanize = (value: string): string => value.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase();\n\nconst copyToClipboard = async (text: string): Promise<boolean> => {\n try {\n if (typeof navigator !== 'undefined' && navigator.clipboard?.writeText) {\n await navigator.clipboard.writeText(text);\n return true;\n }\n } catch {\n // Fall through to the legacy path for insecure / restricted contexts.\n }\n try {\n if (typeof document === 'undefined') {\n return false;\n }\n const textarea = document.createElement('textarea');\n textarea.value = text;\n textarea.style.position = 'fixed';\n textarea.style.opacity = '0';\n document.body.appendChild(textarea);\n textarea.focus();\n textarea.select();\n const succeeded = document.execCommand('copy');\n document.body.removeChild(textarea);\n return succeeded;\n } catch {\n return false;\n }\n};\n\nexport function HelpApiDocumentationPagePage() {\n const { employees, jobPostings, candidates, payrollRecords, leaveRequests } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const [search, setSearch] = useState('');\n const [copied, setCopied] = useState<string | null>(null);\n\n const groups = useMemo<ApiGroup[]>(() => {\n const createOps: ApiOperation[] = ENTITIES.map((entity) => ({\n kind: 'Mutation',\n name: `createCrew${entity.name}`,\n signature: `createCrew${entity.name}(input: CreateCrew${entity.name}Input!): Crew${entity.name}!`,\n description: `Create a ${humanize(entity.name)} record (${entity.domain}).`,\n }));\n const updateOps: ApiOperation[] = ENTITIES.map((entity) => ({\n kind: 'Mutation',\n name: `updateCrew${entity.name}`,\n signature: `updateCrew${entity.name}(id: ID!, input: UpdateCrew${entity.name}Input!): Crew${entity.name}!`,\n description: `Update an existing ${humanize(entity.name)} record (${entity.domain}).`,\n }));\n const removeOps: ApiOperation[] = ENTITIES.map((entity) => ({\n kind: 'Mutation',\n name: `removeCrew${entity.name}`,\n signature: `removeCrew${entity.name}(id: ID!): Boolean!`,\n description: `Delete a ${humanize(entity.name)} record (${entity.domain}).`,\n }));\n\n return [\n {\n id: 'queries',\n name: 'Queries',\n description: 'Read the workspace dataset',\n operations: [\n {\n kind: 'Query',\n name: 'crewfoundryBootstrap',\n signature: 'crewfoundryBootstrap: CrewfoundryBootstrap!',\n description: `Load the full workspace dataset in one call — ${employees.length} employees, ${jobPostings.length} job postings, ${candidates.length} candidates, ${payrollRecords.length} payroll records, ${leaveRequests.length} leave requests, and every other CrewFoundry collection.`,\n },\n ],\n },\n {\n id: 'create',\n name: 'Create mutations',\n description: 'Add new records',\n operations: createOps,\n },\n {\n id: 'update',\n name: 'Update mutations',\n description: 'Modify existing records',\n operations: updateOps,\n },\n {\n id: 'delete',\n name: 'Delete mutations',\n description: 'Remove records',\n operations: removeOps,\n },\n {\n id: 'workflow',\n name: 'Workflow mutations',\n description: 'Enrollment & notifications',\n operations: [\n {\n kind: 'Mutation',\n name: 'enrollCrewCourse',\n signature: 'enrollCrewCourse(id: ID!): CrewCourse!',\n description: 'Enroll the current member into a learning course.',\n },\n {\n kind: 'Mutation',\n name: 'markCrewNotificationRead',\n signature: 'markCrewNotificationRead(id: ID!): Boolean!',\n description: 'Mark a single notification as read.',\n },\n {\n kind: 'Mutation',\n name: 'markAllCrewNotificationsRead',\n signature: 'markAllCrewNotificationsRead: Boolean!',\n description: 'Mark every notification as read.',\n },\n ],\n },\n ];\n }, [\n candidates.length,\n employees.length,\n jobPostings.length,\n leaveRequests.length,\n payrollRecords.length,\n ]);\n\n const totalOperations = useMemo(\n () => groups.reduce((sum, group) => sum + group.operations.length, 0),\n [groups]\n );\n\n const filteredGroups = useMemo(() => {\n const term = search.trim().toLowerCase();\n if (!term) {\n return groups;\n }\n return groups\n .map((group) => ({\n ...group,\n operations: group.operations.filter(\n (op) =>\n op.name.toLowerCase().includes(term) ||\n op.signature.toLowerCase().includes(term) ||\n op.description.toLowerCase().includes(term)\n ),\n }))\n .filter((group) => group.operations.length > 0);\n }, [groups, search]);\n\n const handleCopy = async (value: string) => {\n const ok = await copyToClipboard(value);\n if (ok) {\n setCopied(value);\n showToast('Copied to clipboard', 'success');\n window.setTimeout(() => setCopied((current) => (current === value ? null : current)), 1500);\n } else {\n showToast('Unable to copy to clipboard', 'error');\n }\n };\n\n const scrollToGroup = (id: string) => {\n const target = typeof document !== 'undefined' ? document.getElementById(`group-${id}`) : null;\n target?.scrollIntoView({ behavior: 'smooth', block: 'start' });\n };\n\n const statTiles = [\n { icon: Network, label: 'Protocol', value: 'GraphQL', color: 'text-status-info-text' },\n {\n icon: Code2,\n label: 'Composition',\n value: 'Apollo Federation v2',\n color: 'text-action-primary-bg',\n },\n { icon: Lock, label: 'Auth', value: 'Bearer token', color: 'text-status-success-text' },\n {\n icon: ListTree,\n label: 'Operations',\n value: String(totalOperations),\n color: 'text-status-warning-text',\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-3 md:flex-row md:items-start md:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>API Documentation</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n CrewFoundry GraphQL API reference for integrations\n </p>\n <PagePurpose className=\"mt-1\">\n Browse the CrewFoundry API reference — use it to find operations, understand request\n formats, and integrate CrewFoundry with your own tools.\n </PagePurpose>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\" className=\"max-w-full truncate font-mono text-[10px]\">\n {ENDPOINT}\n </Badge>\n <Button\n size=\"sm\"\n variant=\"outline\"\n aria-label=\"Copy the GraphQL endpoint URL\"\n onClick={() => void handleCopy(ENDPOINT)}\n >\n {copied === ENDPOINT ? (\n <Check className=\"mr-1 h-3 w-3 text-status-success-text\" />\n ) : (\n <Copy className=\"mr-1 h-3 w-3\" />\n )}\n Copy endpoint\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-4\">\n {statTiles.map((item) => (\n <Card key={item.label}>\n <CardContent className=\"flex items-center gap-3 pt-6\">\n <item.icon className={`h-5 w-5 ${item.color}`} />\n <div className=\"min-w-0\">\n <p className=\"text-xs text-text-secondary\">{item.label}</p>\n <p className=\"truncate text-sm font-medium\">{item.value}</p>\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n\n <div className=\"grid gap-6 lg:grid-cols-4\">\n <div className=\"space-y-4\">\n <EmphasisPanel>\n <div className=\"mb-2 text-sm font-semibold\">Jump to</div>\n <div className=\"space-y-2\">\n {groups.map((group) => (\n <button\n key={group.id}\n type=\"button\"\n aria-label={`Jump to ${group.name}`}\n onClick={() => scrollToGroup(group.id)}\n className=\"flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-sm transition-colors hover:bg-bg-sunken\"\n >\n <ListTree className=\"h-3 w-3 text-text-secondary\" />\n <span>{group.name}</span>\n <Badge variant=\"outline\" className=\"ml-auto text-[10px]\">\n {group.operations.length}\n </Badge>\n <ChevronRight className=\"h-3 w-3 text-text-secondary\" />\n </button>\n ))}\n </div>\n </EmphasisPanel>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"text-sm\">Authentication</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <div className=\"rounded-md bg-bg-sunken p-3\">\n <code className=\"font-mono text-xs\">Authorization: Bearer {'<token>'}</code>\n </div>\n <p className=\"text-xs text-text-secondary\">\n Requests are authenticated at the gateway edge. Send your session bearer token on\n every request; the gateway resolves the actor and enforces workspace-scoped RBAC\n before the subgraph runs.\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"space-y-4 lg:col-span-3\">\n <div className=\"relative max-w-md\">\n <Search className=\"absolute top-1/2 left-3 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search operations...\"\n className=\"pl-9\"\n value={search}\n onChange={(event) => setSearch(event.target.value)}\n />\n </div>\n\n {filteredGroups.map((group) => (\n <Card key={group.id} id={`group-${group.id}`} className=\"scroll-mt-6\">\n <CardHeader>\n <div className=\"flex items-center gap-2\">\n <Database className=\"h-4 w-4 text-text-secondary\" />\n <CardTitle className=\"text-base\">{group.name}</CardTitle>\n </div>\n <CardDescription>\n {group.description} · {group.operations.length} operations\n </CardDescription>\n </CardHeader>\n <CardContent className=\"p-0\">\n {group.operations.map((op, index) => (\n <div\n key={op.name}\n className={`flex items-center gap-3 px-6 py-3 hover:bg-bg-sunken/50 ${index < group.operations.length - 1 ? 'border-b' : ''}`}\n >\n <Badge\n className={`${kindColors[op.kind]} min-w-[64px] justify-center border-0 font-mono text-[10px]`}\n >\n {op.kind}\n </Badge>\n <div className=\"min-w-0 flex-1\">\n <code className=\"block truncate font-mono text-sm\">{op.signature}</code>\n <span className=\"text-xs text-text-secondary\">{op.description}</span>\n </div>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 w-6 shrink-0 p-0\"\n aria-label={`Copy ${op.name} operation name`}\n onClick={() => void handleCopy(op.name)}\n >\n {copied === op.name ? (\n <Check className=\"h-3 w-3 text-status-success-text\" />\n ) : (\n <Copy className=\"h-3 w-3 text-text-secondary\" />\n )}\n </Button>\n </div>\n ))}\n </CardContent>\n </Card>\n ))}\n\n {filteredGroups.length === 0 && (\n <div className=\"py-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No operations match\"\n description=\"Try a different search term to find the operation you need.\"\n />\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringApplicationsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringApplicationsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Clock, FileText, Star, UserCheck, Users } from 'lucide-react';\nimport { useCrewFoundryStore, type Candidate } from '../store/crewfoundryStore';\n\nconst CANDIDATE_COLUMNS: ResponsiveTableColumn<Candidate>[] = [\n {\n key: 'candidate',\n header: 'Candidate',\n priority: 'primary',\n cell: (c) => (\n <div>\n <p className=\"font-medium\">\n {c.firstName} {c.lastName}\n </p>\n <p className=\"text-xs text-text-secondary\">{c.email}</p>\n </div>\n ),\n },\n {\n key: 'position',\n header: 'Position',\n priority: 'secondary',\n cell: (c) => <span className=\"text-text-secondary\">{c.position}</span>,\n },\n {\n key: 'company',\n header: 'Company',\n priority: 'secondary',\n cell: (c) => <span className=\"text-text-secondary\">{c.currentCompany}</span>,\n },\n {\n key: 'experience',\n header: 'Experience',\n priority: 'hidden-on-mobile',\n cell: (c) => <span>{c.experience} yrs</span>,\n },\n {\n key: 'rating',\n header: 'Rating',\n priority: 'hidden-on-mobile',\n cell: (c) => (\n <span className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n {c.rating}\n </span>\n ),\n },\n {\n key: 'skills',\n header: 'Skills',\n priority: 'secondary',\n cell: (c) => (\n <div className=\"flex flex-wrap gap-1\">\n {c.skills.slice(0, 2).map((s) => (\n <Badge key={s} variant=\"outline\" className=\"text-[9px]\">\n {s}\n </Badge>\n ))}\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (c) => (\n <Badge\n variant={\n c.status === 'hired'\n ? 'default'\n : c.status === 'offer'\n ? 'default'\n : c.status === 'rejected'\n ? 'destructive'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {c.status}\n </Badge>\n ),\n },\n];\n\nexport function HiringApplicationsPagePage() {\n const { candidates, jobPostings } = useCrewFoundryStore();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n const newApps = candidates.filter((c) => c.status === 'new');\n const screeningApps = candidates.filter((c) => c.status === 'screening');\n const interviewApps = candidates.filter((c) => c.status === 'interview');\n const hiredCount = candidates.filter((c) => c.status === 'hired').length;\n const totalApps = jobPostings.reduce((s, j) => s + j.applicants, 0);\n const conversionRate = totalApps > 0 ? (hiredCount / totalApps) * 100 : 0;\n const maxApplicants = Math.max(1, ...jobPostings.map((j) => j.applicants));\n const rankedPositions = [...jobPostings].sort((a, b) => b.applicants - a.applicants);\n const pagedCandidates = candidates.slice(offset, offset + pageSize);\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>Applications</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {totalApps} total applications across all positions\n </p>\n <PagePurpose className=\"mt-1\">Review and triage incoming job applications — move candidates through your pipeline and decide who to interview next.</PagePurpose>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">New</CardTitle>\n <FileText className=\"h-4 w-4 text-status-info-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{newApps.length}</div>\n <p className=\"text-xs text-text-secondary\">Unreviewed applications</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Screening</CardTitle>\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{screeningApps.length}</div>\n <p className=\"text-xs text-text-secondary\">Under review</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Interview Stage</CardTitle>\n <Users className=\"h-4 w-4 text-action-primary-bg\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{interviewApps.length}</div>\n <p className=\"text-xs text-text-secondary\">Moving forward</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Conversion Rate</CardTitle>\n <UserCheck className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{conversionRate.toFixed(1)}%</div>\n <p className=\"text-xs text-text-secondary\">Hired vs. total applications</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Applications by Position</CardTitle>\n <CardDescription>Applicant count per open role</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {rankedPositions.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No positions yet\"\n description=\"Create a job posting to start collecting applications.\"\n />\n ) : (\n rankedPositions.map((job) => (\n <div key={job.id} className=\"flex items-center gap-3\">\n <span className=\"w-56 text-sm font-medium truncate\">{job.title}</span>\n <div className=\"flex-1 h-4 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-4 bg-action-primary-bg rounded\"\n style={{ width: `${Math.round((job.applicants / maxApplicants) * 100)}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-10 text-right\">{job.applicants}</span>\n <Badge\n variant={job.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {job.status}\n </Badge>\n </div>\n ))\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>All Candidates</CardTitle>\n <CardDescription>Complete candidate pipeline</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n data={pagedCandidates}\n rowKey={(c) => c.id}\n columns={CANDIDATE_COLUMNS}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No candidates yet\"\n description=\"Candidates you add to your pipeline will appear here.\"\n />\n }\n />\n {candidates.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={candidates.length}\n onPageChange={setPage}\n />\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HiringApplicationsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringApplicationsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Clock, FileText, Star, UserCheck, Users } from 'lucide-react';\nimport { useCrewFoundryStore, type Candidate } from '../store/crewfoundryStore';\n\nconst CANDIDATE_COLUMNS: ResponsiveTableColumn<Candidate>[] = [\n {\n key: 'candidate',\n header: 'Candidate',\n priority: 'primary',\n cell: (c) => (\n <div>\n <p className=\"font-medium\">\n {c.firstName} {c.lastName}\n </p>\n <p className=\"text-xs text-text-secondary\">{c.email}</p>\n </div>\n ),\n },\n {\n key: 'position',\n header: 'Position',\n priority: 'secondary',\n cell: (c) => <span className=\"text-text-secondary\">{c.position}</span>,\n },\n {\n key: 'company',\n header: 'Company',\n priority: 'secondary',\n cell: (c) => <span className=\"text-text-secondary\">{c.currentCompany}</span>,\n },\n {\n key: 'experience',\n header: 'Experience',\n priority: 'hidden-on-mobile',\n cell: (c) => <span>{c.experience} yrs</span>,\n },\n {\n key: 'rating',\n header: 'Rating',\n priority: 'hidden-on-mobile',\n cell: (c) => (\n <span className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n {c.rating}\n </span>\n ),\n },\n {\n key: 'skills',\n header: 'Skills',\n priority: 'secondary',\n cell: (c) => (\n <div className=\"flex flex-wrap gap-1\">\n {c.skills.slice(0, 2).map((s) => (\n <Badge key={s} variant=\"outline\" className=\"text-[9px]\">\n {s}\n </Badge>\n ))}\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (c) => (\n <Badge\n variant={\n c.status === 'hired'\n ? 'default'\n : c.status === 'offer'\n ? 'default'\n : c.status === 'rejected'\n ? 'destructive'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {c.status}\n </Badge>\n ),\n },\n];\n\nexport function HiringApplicationsPagePage() {\n const { candidates, jobPostings } = useCrewFoundryStore();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n const newApps = candidates.filter((c) => c.status === 'new');\n const screeningApps = candidates.filter((c) => c.status === 'screening');\n const interviewApps = candidates.filter((c) => c.status === 'interview');\n const hiredCount = candidates.filter((c) => c.status === 'hired').length;\n const totalApps = jobPostings.reduce((s, j) => s + j.applicants, 0);\n const conversionRate = totalApps > 0 ? (hiredCount / totalApps) * 100 : 0;\n const maxApplicants = Math.max(1, ...jobPostings.map((j) => j.applicants));\n const rankedPositions = [...jobPostings].sort((a, b) => b.applicants - a.applicants);\n const pagedCandidates = candidates.slice(offset, offset + pageSize);\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>Applications</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {totalApps} total applications across all positions\n </p>\n <PagePurpose className=\"mt-1\">\n Review and triage incoming job applications — move candidates through your pipeline and\n decide who to interview next.\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">New</CardTitle>\n <FileText className=\"h-4 w-4 text-status-info-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{newApps.length}</div>\n <p className=\"text-xs text-text-secondary\">Unreviewed applications</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Screening</CardTitle>\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{screeningApps.length}</div>\n <p className=\"text-xs text-text-secondary\">Under review</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Interview Stage</CardTitle>\n <Users className=\"h-4 w-4 text-action-primary-bg\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{interviewApps.length}</div>\n <p className=\"text-xs text-text-secondary\">Moving forward</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Conversion Rate</CardTitle>\n <UserCheck className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{conversionRate.toFixed(1)}%</div>\n <p className=\"text-xs text-text-secondary\">Hired vs. total applications</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Applications by Position</CardTitle>\n <CardDescription>Applicant count per open role</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {rankedPositions.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No positions yet\"\n description=\"Create a job posting to start collecting applications.\"\n />\n ) : (\n rankedPositions.map((job) => (\n <div key={job.id} className=\"flex items-center gap-3\">\n <span className=\"w-56 text-sm font-medium truncate\">{job.title}</span>\n <div className=\"flex-1 h-4 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-4 bg-action-primary-bg rounded\"\n style={{ width: `${Math.round((job.applicants / maxApplicants) * 100)}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-10 text-right\">{job.applicants}</span>\n <Badge\n variant={job.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {job.status}\n </Badge>\n </div>\n ))\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>All Candidates</CardTitle>\n <CardDescription>Complete candidate pipeline</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable\n data={pagedCandidates}\n rowKey={(c) => c.id}\n columns={CANDIDATE_COLUMNS}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No candidates yet\"\n description=\"Candidates you add to your pipeline will appear here.\"\n />\n }\n />\n {candidates.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={candidates.length}\n onPageChange={setPage}\n />\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringCandidatesPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringCandidatesPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Button,\n CardContent,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Pencil, Plus, Search, Star, Trash2 } from 'lucide-react';\nimport { useCrewFoundryStore, type Candidate } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getTags, getText, newClientId } from '../components/forms/formValues';\n\nconst CANDIDATE_STATUS_OPTIONS: FieldConfig['options'] = [\n { label: 'New', value: 'new' },\n { label: 'Screening', value: 'screening' },\n { label: 'Interview', value: 'interview' },\n { label: 'Assessment', value: 'assessment' },\n { label: 'Offer', value: 'offer' },\n { label: 'Hired', value: 'hired' },\n { label: 'Rejected', value: 'rejected' },\n];\n\nconst CANDIDATE_FIELDS: FieldConfig[] = [\n { name: 'firstName', label: 'First Name', type: 'text', required: true },\n { name: 'lastName', label: 'Last Name', type: 'text', required: true },\n { name: 'email', label: 'Email', type: 'email', required: true },\n { name: 'phone', label: 'Phone', type: 'text', required: true },\n { name: 'position', label: 'Position', type: 'text', required: true },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: CANDIDATE_STATUS_OPTIONS,\n },\n { name: 'source', label: 'Source', type: 'text', required: true, placeholder: 'e.g. LinkedIn' },\n { name: 'experience', label: 'Experience (years)', type: 'number', required: true },\n { name: 'currentCompany', label: 'Current Company', type: 'text' },\n { name: 'rating', label: 'Rating (out of 5)', type: 'number' },\n { name: 'appliedDate', label: 'Applied Date', type: 'date' },\n { name: 'resumeUrl', label: 'Resume URL', type: 'text' },\n { name: 'skills', label: 'Skills', type: 'tags', colSpan: 2 },\n { name: 'notes', label: 'Notes', type: 'textarea', colSpan: 2 },\n];\n\nconst candidateToValues = (candidate: Candidate): FormValues => ({\n firstName: candidate.firstName,\n lastName: candidate.lastName,\n email: candidate.email,\n phone: candidate.phone,\n position: candidate.position,\n status: candidate.status,\n source: candidate.source,\n experience: candidate.experience,\n currentCompany: candidate.currentCompany,\n rating: candidate.rating,\n appliedDate: candidate.appliedDate,\n resumeUrl: candidate.resumeUrl ?? '',\n skills: candidate.skills,\n notes: candidate.notes,\n});\n\nconst valuesToCandidateFields = (values: FormValues): Omit<Candidate, 'id'> => {\n const appliedDate = getText(values, 'appliedDate');\n return {\n firstName: getText(values, 'firstName'),\n lastName: getText(values, 'lastName'),\n email: getText(values, 'email'),\n phone: getText(values, 'phone'),\n position: getText(values, 'position'),\n status: values.status as Candidate['status'],\n source: getText(values, 'source'),\n experience: getNumber(values, 'experience'),\n currentCompany: getText(values, 'currentCompany'),\n rating: getNumber(values, 'rating'),\n appliedDate: appliedDate.trim() !== '' ? appliedDate : new Date().toISOString().slice(0, 10),\n resumeUrl: getText(values, 'resumeUrl'),\n skills: getTags(values, 'skills'),\n notes: getText(values, 'notes'),\n };\n};\n\nconst CANDIDATE_COLUMNS: ResponsiveTableColumn<Candidate>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (candidate) => (\n <div>\n <p className=\"font-medium\">\n {candidate.firstName} {candidate.lastName}\n </p>\n <p className=\"text-xs text-text-secondary\">{candidate.email}</p>\n </div>\n ),\n },\n {\n key: 'position',\n header: 'Position',\n priority: 'secondary',\n cell: (candidate) => <span className=\"text-text-secondary\">{candidate.position}</span>,\n },\n {\n key: 'source',\n header: 'Source',\n priority: 'secondary',\n cell: (candidate) => (\n <Badge variant=\"outline\" className=\"w-fit h-fit text-[10px]\">\n {candidate.source}\n </Badge>\n ),\n },\n {\n key: 'experience',\n header: 'Experience',\n priority: 'secondary',\n cell: (candidate) => <span>{candidate.experience} yrs</span>,\n },\n {\n key: 'rating',\n header: 'Rating',\n priority: 'secondary',\n cell: (candidate) => (\n <span className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n {candidate.rating}\n </span>\n ),\n },\n {\n key: 'applied',\n header: 'Applied',\n priority: 'hidden-on-mobile',\n cell: (candidate) => (\n <span className=\"text-text-secondary text-xs\">{candidate.appliedDate}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (candidate) => (\n <Badge\n variant={\n candidate.status === 'hired'\n ? 'default'\n : candidate.status === 'offer'\n ? 'default'\n : candidate.status === 'rejected'\n ? 'destructive'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {candidate.status}\n </Badge>\n ),\n },\n];\n\nexport function HiringCandidatesPagePage() {\n const { showToast } = useGlobalUi();\n const { candidates, addCandidate, updateCandidate, removeCandidate } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const [filterStatus, setFilterStatus] = useState<string>('all');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<Candidate | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<Candidate | null>(null);\n\n const handleCreate = async (values: FormValues) => {\n await addCandidate({ id: newClientId(), ...valuesToCandidateFields(values) });\n showToast('Candidate added', 'success');\n };\n\n const handleEdit = async (values: FormValues) => {\n if (!editTarget) {\n return;\n }\n await updateCandidate(editTarget.id, valuesToCandidateFields(values));\n showToast('Candidate updated', 'success');\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) {\n return;\n }\n await removeCandidate(deleteTarget.id);\n showToast('Candidate removed', 'success');\n };\n\n const statuses = [...new Set(candidates.map((c) => c.status))];\n const filtered = candidates.filter((c) => {\n const matchSearch = `${c.firstName} ${c.lastName} ${c.position} ${c.email}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchStatus = filterStatus === 'all' || c.status === filterStatus;\n return matchSearch && matchStatus;\n });\n const paged = filtered.slice(offset, offset + pageSize);\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>Candidates</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{candidates.length} candidates in pipeline</p>\n <PagePurpose className=\"mt-1\">Your candidate pool — search, filter, and evaluate the people you're considering so you can fill open roles with the right talent.</PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => setCreateOpen(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Candidate\n </Button>\n </div>\n\n <div className=\"flex gap-3 flex-wrap\">\n <div className=\"relative flex-1 max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\" />\n <Input\n placeholder=\"Search candidates...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n <div className=\"flex gap-1\">\n <Button\n variant={filterStatus === 'all' ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n setFilterStatus('all');\n setPage(1);\n }}\n >\n All\n </Button>\n {statuses.map((status) => (\n <Button\n key={status}\n variant={filterStatus === status ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n setFilterStatus(status);\n setPage(1);\n }}\n >\n {status}\n </Button>\n ))}\n </div>\n </div>\n\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"p-4\">\n <ResponsiveTable\n data={paged}\n rowKey={(candidate) => candidate.id}\n columns={CANDIDATE_COLUMNS}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No candidates match\"\n description=\"Try a different search term or status filter.\"\n />\n }\n rowActions={(candidate) => (\n <div className=\"flex items-center justify-end gap-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Edit ${candidate.firstName} ${candidate.lastName}`}\n onClick={() => setEditTarget(candidate)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Delete ${candidate.firstName} ${candidate.lastName}`}\n onClick={() => setDeleteTarget(candidate)}\n >\n <Trash2 className=\"h-3.5 w-3.5 text-status-error-fg\" />\n </Button>\n </div>\n )}\n />\n {filtered.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n </div>\n )}\n </CardContent>\n </GlassCard>\n\n <FormModal\n open={createOpen}\n onOpenChange={setCreateOpen}\n title=\"Add Candidate\"\n description=\"Add someone to your hiring pipeline.\"\n fields={CANDIDATE_FIELDS}\n submitLabel=\"Add candidate\"\n onSubmit={handleCreate}\n />\n <FormModal\n open={editTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setEditTarget(null);\n }\n }}\n title=\"Edit Candidate\"\n description={editTarget ? `${editTarget.firstName} ${editTarget.lastName}` : undefined}\n fields={CANDIDATE_FIELDS}\n initialValues={editTarget ? candidateToValues(editTarget) : undefined}\n submitLabel=\"Save changes\"\n onSubmit={handleEdit}\n />\n <ConfirmDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n title=\"Remove candidate\"\n description={\n deleteTarget\n ? `Remove ${deleteTarget.firstName} ${deleteTarget.lastName} from the pipeline?`\n : undefined\n }\n confirmLabel=\"Remove\"\n destructive\n onConfirm={handleDelete}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HiringCandidatesPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringCandidatesPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Badge,\n Button,\n CardContent,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Pencil, Plus, Search, Star, Trash2 } from 'lucide-react';\nimport { useCrewFoundryStore, type Candidate } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getTags, getText, newClientId } from '../components/forms/formValues';\n\nconst CANDIDATE_STATUS_OPTIONS: FieldConfig['options'] = [\n { label: 'New', value: 'new' },\n { label: 'Screening', value: 'screening' },\n { label: 'Interview', value: 'interview' },\n { label: 'Assessment', value: 'assessment' },\n { label: 'Offer', value: 'offer' },\n { label: 'Hired', value: 'hired' },\n { label: 'Rejected', value: 'rejected' },\n];\n\nconst CANDIDATE_FIELDS: FieldConfig[] = [\n { name: 'firstName', label: 'First Name', type: 'text', required: true },\n { name: 'lastName', label: 'Last Name', type: 'text', required: true },\n { name: 'email', label: 'Email', type: 'email', required: true },\n { name: 'phone', label: 'Phone', type: 'text', required: true },\n { name: 'position', label: 'Position', type: 'text', required: true },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: CANDIDATE_STATUS_OPTIONS,\n },\n { name: 'source', label: 'Source', type: 'text', required: true, placeholder: 'e.g. LinkedIn' },\n { name: 'experience', label: 'Experience (years)', type: 'number', required: true },\n { name: 'currentCompany', label: 'Current Company', type: 'text' },\n { name: 'rating', label: 'Rating (out of 5)', type: 'number' },\n { name: 'appliedDate', label: 'Applied Date', type: 'date' },\n { name: 'resumeUrl', label: 'Resume URL', type: 'text' },\n { name: 'skills', label: 'Skills', type: 'tags', colSpan: 2 },\n { name: 'notes', label: 'Notes', type: 'textarea', colSpan: 2 },\n];\n\nconst candidateToValues = (candidate: Candidate): FormValues => ({\n firstName: candidate.firstName,\n lastName: candidate.lastName,\n email: candidate.email,\n phone: candidate.phone,\n position: candidate.position,\n status: candidate.status,\n source: candidate.source,\n experience: candidate.experience,\n currentCompany: candidate.currentCompany,\n rating: candidate.rating,\n appliedDate: candidate.appliedDate,\n resumeUrl: candidate.resumeUrl ?? '',\n skills: candidate.skills,\n notes: candidate.notes,\n});\n\nconst valuesToCandidateFields = (values: FormValues): Omit<Candidate, 'id'> => {\n const appliedDate = getText(values, 'appliedDate');\n return {\n firstName: getText(values, 'firstName'),\n lastName: getText(values, 'lastName'),\n email: getText(values, 'email'),\n phone: getText(values, 'phone'),\n position: getText(values, 'position'),\n status: values.status as Candidate['status'],\n source: getText(values, 'source'),\n experience: getNumber(values, 'experience'),\n currentCompany: getText(values, 'currentCompany'),\n rating: getNumber(values, 'rating'),\n appliedDate: appliedDate.trim() !== '' ? appliedDate : new Date().toISOString().slice(0, 10),\n resumeUrl: getText(values, 'resumeUrl'),\n skills: getTags(values, 'skills'),\n notes: getText(values, 'notes'),\n };\n};\n\nconst CANDIDATE_COLUMNS: ResponsiveTableColumn<Candidate>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (candidate) => (\n <div>\n <p className=\"font-medium\">\n {candidate.firstName} {candidate.lastName}\n </p>\n <p className=\"text-xs text-text-secondary\">{candidate.email}</p>\n </div>\n ),\n },\n {\n key: 'position',\n header: 'Position',\n priority: 'secondary',\n cell: (candidate) => <span className=\"text-text-secondary\">{candidate.position}</span>,\n },\n {\n key: 'source',\n header: 'Source',\n priority: 'secondary',\n cell: (candidate) => (\n <Badge variant=\"outline\" className=\"w-fit h-fit text-[10px]\">\n {candidate.source}\n </Badge>\n ),\n },\n {\n key: 'experience',\n header: 'Experience',\n priority: 'secondary',\n cell: (candidate) => <span>{candidate.experience} yrs</span>,\n },\n {\n key: 'rating',\n header: 'Rating',\n priority: 'secondary',\n cell: (candidate) => (\n <span className=\"flex items-center gap-1\">\n <Star className=\"h-3 w-3 text-status-warning-text\" />\n {candidate.rating}\n </span>\n ),\n },\n {\n key: 'applied',\n header: 'Applied',\n priority: 'hidden-on-mobile',\n cell: (candidate) => (\n <span className=\"text-text-secondary text-xs\">{candidate.appliedDate}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (candidate) => (\n <Badge\n variant={\n candidate.status === 'hired'\n ? 'default'\n : candidate.status === 'offer'\n ? 'default'\n : candidate.status === 'rejected'\n ? 'destructive'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {candidate.status}\n </Badge>\n ),\n },\n];\n\nexport function HiringCandidatesPagePage() {\n const { showToast } = useGlobalUi();\n const { candidates, addCandidate, updateCandidate, removeCandidate } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const [filterStatus, setFilterStatus] = useState<string>('all');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n const [createOpen, setCreateOpen] = useState(false);\n const [editTarget, setEditTarget] = useState<Candidate | null>(null);\n const [deleteTarget, setDeleteTarget] = useState<Candidate | null>(null);\n\n const handleCreate = async (values: FormValues) => {\n await addCandidate({ id: newClientId(), ...valuesToCandidateFields(values) });\n showToast('Candidate added', 'success');\n };\n\n const handleEdit = async (values: FormValues) => {\n if (!editTarget) {\n return;\n }\n await updateCandidate(editTarget.id, valuesToCandidateFields(values));\n showToast('Candidate updated', 'success');\n };\n\n const handleDelete = async () => {\n if (!deleteTarget) {\n return;\n }\n await removeCandidate(deleteTarget.id);\n showToast('Candidate removed', 'success');\n };\n\n const statuses = [...new Set(candidates.map((c) => c.status))];\n const filtered = candidates.filter((c) => {\n const matchSearch = `${c.firstName} ${c.lastName} ${c.position} ${c.email}`\n .toLowerCase()\n .includes(search.toLowerCase());\n const matchStatus = filterStatus === 'all' || c.status === filterStatus;\n return matchSearch && matchStatus;\n });\n const paged = filtered.slice(offset, offset + pageSize);\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>Candidates</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{candidates.length} candidates in pipeline</p>\n <PagePurpose className=\"mt-1\">\n Your candidate pool — search, filter, and evaluate the people you're considering so\n you can fill open roles with the right talent.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => setCreateOpen(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Candidate\n </Button>\n </div>\n\n <div className=\"flex gap-3 flex-wrap\">\n <div className=\"relative flex-1 max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\" />\n <Input\n placeholder=\"Search candidates...\"\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n <div className=\"flex gap-1\">\n <Button\n variant={filterStatus === 'all' ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n setFilterStatus('all');\n setPage(1);\n }}\n >\n All\n </Button>\n {statuses.map((status) => (\n <Button\n key={status}\n variant={filterStatus === status ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n setFilterStatus(status);\n setPage(1);\n }}\n >\n {status}\n </Button>\n ))}\n </div>\n </div>\n\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"p-4\">\n <ResponsiveTable\n data={paged}\n rowKey={(candidate) => candidate.id}\n columns={CANDIDATE_COLUMNS}\n emptyState={\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No candidates match\"\n description=\"Try a different search term or status filter.\"\n />\n }\n rowActions={(candidate) => (\n <div className=\"flex items-center justify-end gap-1\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Edit ${candidate.firstName} ${candidate.lastName}`}\n onClick={() => setEditTarget(candidate)}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Delete ${candidate.firstName} ${candidate.lastName}`}\n onClick={() => setDeleteTarget(candidate)}\n >\n <Trash2 className=\"h-3.5 w-3.5 text-status-error-fg\" />\n </Button>\n </div>\n )}\n />\n {filtered.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n </div>\n )}\n </CardContent>\n </GlassCard>\n\n <FormModal\n open={createOpen}\n onOpenChange={setCreateOpen}\n title=\"Add Candidate\"\n description=\"Add someone to your hiring pipeline.\"\n fields={CANDIDATE_FIELDS}\n submitLabel=\"Add candidate\"\n onSubmit={handleCreate}\n />\n <FormModal\n open={editTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setEditTarget(null);\n }\n }}\n title=\"Edit Candidate\"\n description={editTarget ? `${editTarget.firstName} ${editTarget.lastName}` : undefined}\n fields={CANDIDATE_FIELDS}\n initialValues={editTarget ? candidateToValues(editTarget) : undefined}\n submitLabel=\"Save changes\"\n onSubmit={handleEdit}\n />\n <ConfirmDialog\n open={deleteTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleteTarget(null);\n }\n }}\n title=\"Remove candidate\"\n description={\n deleteTarget\n ? `Remove ${deleteTarget.firstName} ${deleteTarget.lastName} from the pipeline?`\n : undefined\n }\n confirmLabel=\"Remove\"\n destructive\n onConfirm={handleDelete}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAgCA,IAAM,IAAkC;CACtC;EAAE,MAAM;EAAa,OAAO;EAAc,MAAM;EAAQ,UAAU;EAAM;CACxE;EAAE,MAAM;EAAY,OAAO;EAAa,MAAM;EAAQ,UAAU;EAAM;CACtE;EAAE,MAAM;EAAS,OAAO;EAAS,MAAM;EAAS,UAAU;EAAM;CAChE;EAAE,MAAM;EAAS,OAAO;EAAS,MAAM;EAAQ,UAAU;EAAM;CAC/D;EAAE,MAAM;EAAY,OAAO;EAAY,MAAM;EAAQ,UAAU;EAAM;CACrE;EACE,MAAM;EACN,OAAO;EACP,MAAM;EACN,UAAU;EACV,SArBqD;GACvD;IAAE,OAAO;IAAO,OAAO;IAAO;GAC9B;IAAE,OAAO;IAAa,OAAO;IAAa;GAC1C;IAAE,OAAO;IAAa,OAAO;IAAa;GAC1C;IAAE,OAAO;IAAc,OAAO;IAAc;GAC5C;IAAE,OAAO;IAAS,OAAO;IAAS;GAClC;IAAE,OAAO;IAAS,OAAO;IAAS;GAClC;IAAE,OAAO;IAAY,OAAO;IAAY;GACzC;EAcE;CACD;EAAE,MAAM;EAAU,OAAO;EAAU,MAAM;EAAQ,UAAU;EAAM,aAAa;EAAiB;CAC/F;EAAE,MAAM;EAAc,OAAO;EAAsB,MAAM;EAAU,UAAU;EAAM;CACnF;EAAE,MAAM;EAAkB,OAAO;EAAmB,MAAM;EAAQ;CAClE;EAAE,MAAM;EAAU,OAAO;EAAqB,MAAM;EAAU;CAC9D;EAAE,MAAM;EAAe,OAAO;EAAgB,MAAM;EAAQ;CAC5D;EAAE,MAAM;EAAa,OAAO;EAAc,MAAM;EAAQ;CACxD;EAAE,MAAM;EAAU,OAAO;EAAU,MAAM;EAAQ,SAAS;EAAG;CAC7D;EAAE,MAAM;EAAS,OAAO;EAAS,MAAM;EAAY,SAAS;EAAG;CAChE,EAEK,KAAqB,OAAsC;CAC/D,WAAW,EAAU;CACrB,UAAU,EAAU;CACpB,OAAO,EAAU;CACjB,OAAO,EAAU;CACjB,UAAU,EAAU;CACpB,QAAQ,EAAU;CAClB,QAAQ,EAAU;CAClB,YAAY,EAAU;CACtB,gBAAgB,EAAU;CAC1B,QAAQ,EAAU;CAClB,aAAa,EAAU;CACvB,WAAW,EAAU,aAAa;CAClC,QAAQ,EAAU;CAClB,OAAO,EAAU;CAClB,GAEK,KAA2B,MAA8C;CAC7E,IAAM,IAAc,EAAQ,GAAQ,cAAc;AAClD,QAAO;EACL,WAAW,EAAQ,GAAQ,YAAY;EACvC,UAAU,EAAQ,GAAQ,WAAW;EACrC,OAAO,EAAQ,GAAQ,QAAQ;EAC/B,OAAO,EAAQ,GAAQ,QAAQ;EAC/B,UAAU,EAAQ,GAAQ,WAAW;EACrC,QAAQ,EAAO;EACf,QAAQ,EAAQ,GAAQ,SAAS;EACjC,YAAY,EAAU,GAAQ,aAAa;EAC3C,gBAAgB,EAAQ,GAAQ,iBAAiB;EACjD,QAAQ,EAAU,GAAQ,SAAS;EACnC,aAAa,EAAY,MAAM,KAAK,sBAAmB,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG,GAAnD;EACzC,WAAW,EAAQ,GAAQ,YAAY;EACvC,QAAQ,EAAQ,GAAQ,SAAS;EACjC,OAAO,EAAQ,GAAQ,QAAQ;EAChC;GAGG,IAAwD;CAC5D;EACE,KAAK;EACL,QAAQ;EACR,UAAU;EACV,OAAO,MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;GAAG,WAAU;aAAb;IACG,EAAU;IAAU;IAAE,EAAU;IAC/B;MACJ,kBAAC,KAAD;GAAG,WAAU;aAA+B,EAAU;GAAU,CAAA,CAC5D,EAAA,CAAA;EAET;CACD;EACE,KAAK;EACL,QAAQ;EACR,UAAU;EACV,OAAO,MAAc,kBAAC,QAAD;GAAM,WAAU;aAAuB,EAAU;GAAgB,CAAA;EACvF;CACD;EACE,KAAK;EACL,QAAQ;EACR,UAAU;EACV,OAAO,MACL,kBAAC,GAAD;GAAO,SAAQ;GAAU,WAAU;aAChC,EAAU;GACL,CAAA;EAEX;CACD;EACE,KAAK;EACL,QAAQ;EACR,UAAU;EACV,OAAO,MAAc,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAU,YAAW,OAAW,EAAA,CAAA;EAC7D;CACD;EACE,KAAK;EACL,QAAQ;EACR,UAAU;EACV,OAAO,MACL,kBAAC,QAAD;GAAM,WAAU;aAAhB,CACE,kBAAC,GAAD,EAAM,WAAU,oCAAqC,CAAA,EACpD,EAAU,OACN;;EAEV;CACD;EACE,KAAK;EACL,QAAQ;EACR,UAAU;EACV,OAAO,MACL,kBAAC,QAAD;GAAM,WAAU;aAA+B,EAAU;GAAmB,CAAA;EAE/E;CACD;EACE,KAAK;EACL,QAAQ;EACR,UAAU;EACV,OAAO,MACL,kBAAC,GAAD;GACE,SACE,EAAU,WAAW,WAEjB,EAAU,WAAW,UADrB,YAGE,EAAU,WAAW,aACnB,gBACA;GAEV,WAAU;aAET,EAAU;GACL,CAAA;EAEX;CACF;AAED,SAAgB,IAA2B;CACzC,IAAM,EAAE,iBAAc,GAAa,EAC7B,EAAE,eAAY,iBAAc,oBAAiB,uBAAoB,GAAqB,EACtF,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAc,KAAmB,EAAiB,MAAM,EACzD,EAAE,SAAM,aAAU,WAAQ,eAAY,EAAc,EAAE,iBAAiB,IAAI,CAAC,EAC5E,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAY,KAAiB,EAA2B,KAAK,EAC9D,CAAC,GAAc,KAAmB,EAA2B,KAAK,EAElE,IAAe,OAAO,MAAuB;AAEjD,EADA,MAAM,EAAa;GAAE,IAAI,GAAa;GAAE,GAAG,EAAwB,EAAO;GAAE,CAAC,EAC7E,EAAU,mBAAmB,UAAU;IAGnC,IAAa,OAAO,MAAuB;AAC1C,QAGL,MAAM,EAAgB,EAAW,IAAI,EAAwB,EAAO,CAAC,EACrE,EAAU,qBAAqB,UAAU;IAGrC,IAAe,YAAY;AAC1B,QAGL,MAAM,EAAgB,EAAa,GAAG,EACtC,EAAU,qBAAqB,UAAU;IAGrC,IAAW,CAAC,GAAG,IAAI,IAAI,EAAW,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC,EACxD,IAAW,EAAW,QAAQ,MAAM;EACxC,IAAM,IAAc,GAAG,EAAE,UAAU,GAAG,EAAE,SAAS,GAAG,EAAE,SAAS,GAAG,EAAE,QACjE,aAAa,CACb,SAAS,EAAO,aAAa,CAAC,EAC3B,IAAc,MAAiB,SAAS,EAAE,WAAW;AAC3D,SAAO,KAAe;GACtB,EACI,IAAQ,EAAS,MAAM,GAAQ,IAAS,EAAS;AAEvD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAc,cAAyB,CAAA;MACpC,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAAb,CAA4C,EAAW,QAAO,0BAA2B;;KACzF,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAGhB,CAAA;KACV,EAAA,CAAA,EACN,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAc,GAAK;eAApD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,gBAE1B;OACL;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;MACE,aAAY;MACZ,WAAU;MACV,OAAO;MACP,WAAW,MAAM;AAEf,OADA,EAAU,EAAE,OAAO,MAAM,EACzB,EAAQ,EAAE;;MAEZ,CAAA,CACE;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MACE,SAAS,MAAiB,QAAQ,YAAY;MAC9C,MAAK;MACL,eAAe;AAEb,OADA,EAAgB,MAAM,EACtB,EAAQ,EAAE;;gBAEb;MAEQ,CAAA,EACR,EAAS,KAAK,MACb,kBAAC,GAAD;MAEE,SAAS,MAAiB,IAAS,YAAY;MAC/C,MAAK;MACL,eAAe;AAEb,OADA,EAAgB,EAAO,EACvB,EAAQ,EAAE;;gBAGX;MACM,EATF,EASE,CACT,CACE;OACF;;GAEN,kBAAC,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;cAC1C,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACE,kBAAC,GAAD;MACE,MAAM;MACN,SAAS,MAAc,EAAU;MACjC,SAAS;MACT,YACE,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;OACZ,CAAA;MAEJ,aAAa,MACX,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,SAAQ;QACR,MAAK;QACL,cAAY,QAAQ,EAAU,UAAU,GAAG,EAAU;QACrD,eAAe,EAAc,EAAU;kBAEvC,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA;QAC3B,CAAA,EACT,kBAAC,GAAD;QACE,SAAQ;QACR,MAAK;QACL,cAAY,UAAU,EAAU,UAAU,GAAG,EAAU;QACvD,eAAe,EAAgB,EAAU;kBAEzC,kBAAC,GAAD,EAAQ,WAAU,oCAAqC,CAAA;QAChD,CAAA,CACL;;MAER,CAAA,EACD,EAAS,SAAS,KACjB,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OACQ;OACI;OACV,YAAY,EAAS;OACrB,cAAc;OACd,CAAA;MACE,CAAA,CAEI;;IACJ,CAAA;GAEZ,kBAAC,GAAD;IACE,MAAM;IACN,cAAc;IACd,OAAM;IACN,aAAY;IACZ,QAAQ;IACR,aAAY;IACZ,UAAU;IACV,CAAA;GACF,kBAAC,GAAD;IACE,MAAM,MAAe;IACrB,eAAe,MAAS;AACtB,KAAK,KACH,EAAc,KAAK;;IAGvB,OAAM;IACN,aAAa,IAAa,GAAG,EAAW,UAAU,GAAG,EAAW,aAAa,KAAA;IAC7E,QAAQ;IACR,eAAe,IAAa,EAAkB,EAAW,GAAG,KAAA;IAC5D,aAAY;IACZ,UAAU;IACV,CAAA;GACF,kBAAC,GAAD;IACE,MAAM,MAAiB;IACvB,eAAe,MAAS;AACtB,KAAK,KACH,EAAgB,KAAK;;IAGzB,OAAM;IACN,aACE,IACI,UAAU,EAAa,UAAU,GAAG,EAAa,SAAS,uBAC1D,KAAA;IAEN,cAAa;IACb,aAAA;IACA,WAAW;IACX,CAAA;GACE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringCreateJobPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringCreateJobPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n Input,\n PagePurpose,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Briefcase, Plus, Save, X } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\nimport { newClientId } from '../components/forms/formValues';\n\n// Native <select> styled with the same semantic tokens as fe-libs Input.\nconst SELECT_CLASS =\n 'flex h-10 w-full rounded-input border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary transition-colors duration-200 focus-visible:border-border-focus focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50';\n\nconst JOB_TYPE_OPTIONS: { label: string; value: JobPosting['type'] }[] = [\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\nconst EXPERIENCE_OPTIONS: { label: string; value: JobPosting['experienceLevel'] }[] = [\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\nexport function HiringCreateJobPagePage() {\n const navigate = useNavigate();\n const { showToast } = useGlobalUi();\n const addJobPosting = useCrewFoundryStore((state) => state.addJobPosting);\n\n const [title, setTitle] = useState('');\n const [department, setDepartment] = useState('');\n const [location, setLocation] = useState('');\n const [description, setDescription] = useState('');\n const [type, setType] = useState<JobPosting['type']>('full_time');\n const [experienceLevel, setExperienceLevel] = useState<JobPosting['experienceLevel']>('mid');\n const [salaryMin, setSalaryMin] = useState('');\n const [salaryMax, setSalaryMax] = useState('');\n const [deadline, setDeadline] = useState('');\n const [requirements, setRequirements] = useState<string[]>(['']);\n const [benefits, setBenefits] = useState<string[]>(['']);\n const [submitting, setSubmitting] = useState(false);\n\n const addRequirement = () => setRequirements([...requirements, '']);\n const addBenefit = () => setBenefits([...benefits, '']);\n const updateRequirement = (idx: number, value: string) => {\n const updated = [...requirements];\n updated[idx] = value;\n setRequirements(updated);\n };\n const updateBenefit = (idx: number, value: string) => {\n const updated = [...benefits];\n updated[idx] = value;\n setBenefits(updated);\n };\n const removeRequirement = (idx: number) =>\n setRequirements(requirements.filter((_, i) => i !== idx));\n const removeBenefit = (idx: number) => setBenefits(benefits.filter((_, i) => i !== idx));\n\n const submit = async (status: JobPosting['status']) => {\n if (submitting) {\n return;\n }\n if (!title.trim()) {\n showToast('Job Title is required', 'warning');\n return;\n }\n if (!department.trim()) {\n showToast('Department is required', 'warning');\n return;\n }\n const today = new Date().toISOString().slice(0, 10);\n const job: JobPosting = {\n id: newClientId(),\n title: title.trim(),\n department: department.trim(),\n location: location.trim(),\n type,\n experienceLevel,\n salaryMin: salaryMin.trim() === '' ? 0 : Number(salaryMin),\n salaryMax: salaryMax.trim() === '' ? 0 : Number(salaryMax),\n status,\n applicants: 0,\n postedDate: today,\n deadline: deadline.trim() === '' ? today : deadline,\n description: description.trim(),\n requirements: requirements.map((item) => item.trim()).filter(Boolean),\n benefits: benefits.map((item) => item.trim()).filter(Boolean),\n };\n\n setSubmitting(true);\n try {\n await addJobPosting(job);\n showToast(status === 'draft' ? 'Draft saved' : 'Job published', 'success');\n navigate('../jobs-page');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to save job posting', 'error');\n } finally {\n setSubmitting(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-3xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Create Job Posting</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Fill in the details for a new position</p>\n <PagePurpose className=\"mt-1\">Create a new job posting — define the role, requirements, and compensation here to start attracting and collecting applicants.</PagePurpose>\n </div>\n </div>\n\n <EmphasisPanel>\n <div className=\"mb-4 flex items-center gap-2 text-lg font-semibold\">\n <Briefcase className=\"h-5 w-5\" />\n Job Details\n </div>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-title\">\n Job Title\n </label>\n <Input\n id=\"job-title\"\n placeholder=\"e.g. Senior Frontend Developer\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-department\">\n Department\n </label>\n <Input\n id=\"job-department\"\n placeholder=\"e.g. Engineering\"\n value={department}\n onChange={(e) => setDepartment(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-location\">\n Location\n </label>\n <Input\n id=\"job-location\"\n placeholder=\"e.g. San Francisco, CA\"\n value={location}\n onChange={(e) => setLocation(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-type\">\n Employment Type\n </label>\n <select\n id=\"job-type\"\n className={SELECT_CLASS}\n value={type}\n onChange={(e) => setType(e.target.value as JobPosting['type'])}\n >\n {JOB_TYPE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-experience\">\n Experience Level\n </label>\n <select\n id=\"job-experience\"\n className={SELECT_CLASS}\n value={experienceLevel}\n onChange={(e) =>\n setExperienceLevel(e.target.value as JobPosting['experienceLevel'])\n }\n >\n {EXPERIENCE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-salary-min\">\n Salary Min ($)\n </label>\n <Input\n id=\"job-salary-min\"\n type=\"number\"\n placeholder=\"e.g. 120000\"\n value={salaryMin}\n onChange={(e) => setSalaryMin(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-salary-max\">\n Salary Max ($)\n </label>\n <Input\n id=\"job-salary-max\"\n type=\"number\"\n placeholder=\"e.g. 180000\"\n value={salaryMax}\n onChange={(e) => setSalaryMax(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-deadline\">\n Application Deadline\n </label>\n <Input\n id=\"job-deadline\"\n type=\"date\"\n value={deadline}\n onChange={(e) => setDeadline(e.target.value)}\n />\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-description\">\n Description\n </label>\n <Textarea\n id=\"job-description\"\n placeholder=\"Describe the role, responsibilities, and ideal candidate...\"\n rows={4}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n />\n </div>\n </div>\n </EmphasisPanel>\n\n <Card>\n <CardHeader>\n <CardTitle>Requirements</CardTitle>\n <CardDescription>List the key requirements for this role</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {requirements.map((req, idx) => (\n <div key={idx} className=\"flex gap-2\">\n <Input\n placeholder={`Requirement ${idx + 1}`}\n value={req}\n onChange={(e) => updateRequirement(idx, e.target.value)}\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n aria-label={`Remove requirement ${idx + 1}`}\n onClick={() => removeRequirement(idx)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n <Button variant=\"outline\" size=\"sm\" onClick={addRequirement}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Requirement\n </Button>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Benefits</CardTitle>\n <CardDescription>What does this position offer?</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {benefits.map((ben, idx) => (\n <div key={idx} className=\"flex gap-2\">\n <Input\n placeholder={`Benefit ${idx + 1}`}\n value={ben}\n onChange={(e) => updateBenefit(idx, e.target.value)}\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n aria-label={`Remove benefit ${idx + 1}`}\n onClick={() => removeBenefit(idx)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n <Button variant=\"outline\" size=\"sm\" onClick={addBenefit}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Benefit\n </Button>\n </CardContent>\n </Card>\n\n <div className=\"flex justify-end gap-2\">\n <Button variant=\"outline\" disabled={submitting} onClick={() => submit('draft')}>\n Save as Draft\n </Button>\n <Button disabled={submitting} onClick={() => submit('active')}>\n <Save className=\"h-4 w-4 mr-1\" />\n {submitting ? 'Saving…' : 'Publish Job'}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HiringCreateJobPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringCreateJobPagePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport {\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n EmphasisPanel,\n GradientText,\n Input,\n PagePurpose,\n Textarea,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Briefcase, Plus, Save, X } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\nimport { newClientId } from '../components/forms/formValues';\n\n// Native <select> styled with the same semantic tokens as fe-libs Input.\nconst SELECT_CLASS =\n 'flex h-10 w-full rounded-input border border-border-default bg-bg-surface px-3 py-2 text-sm text-text-primary transition-colors duration-200 focus-visible:border-border-focus focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50';\n\nconst JOB_TYPE_OPTIONS: { label: string; value: JobPosting['type'] }[] = [\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\nconst EXPERIENCE_OPTIONS: { label: string; value: JobPosting['experienceLevel'] }[] = [\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\nexport function HiringCreateJobPagePage() {\n const navigate = useNavigate();\n const { showToast } = useGlobalUi();\n const addJobPosting = useCrewFoundryStore((state) => state.addJobPosting);\n\n const [title, setTitle] = useState('');\n const [department, setDepartment] = useState('');\n const [location, setLocation] = useState('');\n const [description, setDescription] = useState('');\n const [type, setType] = useState<JobPosting['type']>('full_time');\n const [experienceLevel, setExperienceLevel] = useState<JobPosting['experienceLevel']>('mid');\n const [salaryMin, setSalaryMin] = useState('');\n const [salaryMax, setSalaryMax] = useState('');\n const [deadline, setDeadline] = useState('');\n const [requirements, setRequirements] = useState<string[]>(['']);\n const [benefits, setBenefits] = useState<string[]>(['']);\n const [submitting, setSubmitting] = useState(false);\n\n const addRequirement = () => setRequirements([...requirements, '']);\n const addBenefit = () => setBenefits([...benefits, '']);\n const updateRequirement = (idx: number, value: string) => {\n const updated = [...requirements];\n updated[idx] = value;\n setRequirements(updated);\n };\n const updateBenefit = (idx: number, value: string) => {\n const updated = [...benefits];\n updated[idx] = value;\n setBenefits(updated);\n };\n const removeRequirement = (idx: number) =>\n setRequirements(requirements.filter((_, i) => i !== idx));\n const removeBenefit = (idx: number) => setBenefits(benefits.filter((_, i) => i !== idx));\n\n const submit = async (status: JobPosting['status']) => {\n if (submitting) {\n return;\n }\n if (!title.trim()) {\n showToast('Job Title is required', 'warning');\n return;\n }\n if (!department.trim()) {\n showToast('Department is required', 'warning');\n return;\n }\n const today = new Date().toISOString().slice(0, 10);\n const job: JobPosting = {\n id: newClientId(),\n title: title.trim(),\n department: department.trim(),\n location: location.trim(),\n type,\n experienceLevel,\n salaryMin: salaryMin.trim() === '' ? 0 : Number(salaryMin),\n salaryMax: salaryMax.trim() === '' ? 0 : Number(salaryMax),\n status,\n applicants: 0,\n postedDate: today,\n deadline: deadline.trim() === '' ? today : deadline,\n description: description.trim(),\n requirements: requirements.map((item) => item.trim()).filter(Boolean),\n benefits: benefits.map((item) => item.trim()).filter(Boolean),\n };\n\n setSubmitting(true);\n try {\n await addJobPosting(job);\n showToast(status === 'draft' ? 'Draft saved' : 'Job published', 'success');\n navigate('../jobs-page');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to save job posting', 'error');\n } finally {\n setSubmitting(false);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6 max-w-3xl\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Create Job Posting</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Fill in the details for a new position</p>\n <PagePurpose className=\"mt-1\">\n Create a new job posting — define the role, requirements, and compensation here to start\n attracting and collecting applicants.\n </PagePurpose>\n </div>\n </div>\n\n <EmphasisPanel>\n <div className=\"mb-4 flex items-center gap-2 text-lg font-semibold\">\n <Briefcase className=\"h-5 w-5\" />\n Job Details\n </div>\n <div className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-title\">\n Job Title\n </label>\n <Input\n id=\"job-title\"\n placeholder=\"e.g. Senior Frontend Developer\"\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n />\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-department\">\n Department\n </label>\n <Input\n id=\"job-department\"\n placeholder=\"e.g. Engineering\"\n value={department}\n onChange={(e) => setDepartment(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-location\">\n Location\n </label>\n <Input\n id=\"job-location\"\n placeholder=\"e.g. San Francisco, CA\"\n value={location}\n onChange={(e) => setLocation(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-type\">\n Employment Type\n </label>\n <select\n id=\"job-type\"\n className={SELECT_CLASS}\n value={type}\n onChange={(e) => setType(e.target.value as JobPosting['type'])}\n >\n {JOB_TYPE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-experience\">\n Experience Level\n </label>\n <select\n id=\"job-experience\"\n className={SELECT_CLASS}\n value={experienceLevel}\n onChange={(e) =>\n setExperienceLevel(e.target.value as JobPosting['experienceLevel'])\n }\n >\n {EXPERIENCE_OPTIONS.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-salary-min\">\n Salary Min ($)\n </label>\n <Input\n id=\"job-salary-min\"\n type=\"number\"\n placeholder=\"e.g. 120000\"\n value={salaryMin}\n onChange={(e) => setSalaryMin(e.target.value)}\n />\n </div>\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-salary-max\">\n Salary Max ($)\n </label>\n <Input\n id=\"job-salary-max\"\n type=\"number\"\n placeholder=\"e.g. 180000\"\n value={salaryMax}\n onChange={(e) => setSalaryMax(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-deadline\">\n Application Deadline\n </label>\n <Input\n id=\"job-deadline\"\n type=\"date\"\n value={deadline}\n onChange={(e) => setDeadline(e.target.value)}\n />\n </div>\n\n <div className=\"space-y-2\">\n <label className=\"text-sm font-medium\" htmlFor=\"job-description\">\n Description\n </label>\n <Textarea\n id=\"job-description\"\n placeholder=\"Describe the role, responsibilities, and ideal candidate...\"\n rows={4}\n value={description}\n onChange={(e) => setDescription(e.target.value)}\n />\n </div>\n </div>\n </EmphasisPanel>\n\n <Card>\n <CardHeader>\n <CardTitle>Requirements</CardTitle>\n <CardDescription>List the key requirements for this role</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {requirements.map((req, idx) => (\n <div key={idx} className=\"flex gap-2\">\n <Input\n placeholder={`Requirement ${idx + 1}`}\n value={req}\n onChange={(e) => updateRequirement(idx, e.target.value)}\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n aria-label={`Remove requirement ${idx + 1}`}\n onClick={() => removeRequirement(idx)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n <Button variant=\"outline\" size=\"sm\" onClick={addRequirement}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Requirement\n </Button>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Benefits</CardTitle>\n <CardDescription>What does this position offer?</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {benefits.map((ben, idx) => (\n <div key={idx} className=\"flex gap-2\">\n <Input\n placeholder={`Benefit ${idx + 1}`}\n value={ben}\n onChange={(e) => updateBenefit(idx, e.target.value)}\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n aria-label={`Remove benefit ${idx + 1}`}\n onClick={() => removeBenefit(idx)}\n >\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n ))}\n <Button variant=\"outline\" size=\"sm\" onClick={addBenefit}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Benefit\n </Button>\n </CardContent>\n </Card>\n\n <div className=\"flex justify-end gap-2\">\n <Button variant=\"outline\" disabled={submitting} onClick={() => submit('draft')}>\n Save as Draft\n </Button>\n <Button disabled={submitting} onClick={() => submit('active')}>\n <Save className=\"h-4 w-4 mr-1\" />\n {submitting ? 'Saving…' : 'Publish Job'}\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringInterviewsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringInterviewsPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calendar, Clock, Plus, Star, Video } from 'lucide-react';\nimport { useCrewFoundryStore, type Interview } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { getText, newClientId } from '../components/forms/formValues';\n\nconst INTERVIEW_TYPE_OPTIONS: FieldConfig['options'] = [\n { label: 'Phone', value: 'phone' },\n { label: 'Video', value: 'video' },\n { label: 'Onsite', value: 'onsite' },\n { label: 'Panel', value: 'panel' },\n { label: 'Technical', value: 'technical' },\n];\n\nconst INTERVIEW_STATUS_OPTIONS: FieldConfig['options'] = [\n { label: 'Scheduled', value: 'scheduled' },\n { label: 'Completed', value: 'completed' },\n { label: 'Cancelled', value: 'cancelled' },\n { label: 'No show', value: 'no_show' },\n];\n\nconst UPDATE_FIELDS: FieldConfig[] = [\n { name: 'status', label: 'Status', type: 'select', options: INTERVIEW_STATUS_OPTIONS, required: true },\n { name: 'rating', label: 'Rating (out of 5)', type: 'number' },\n { name: 'feedback', label: 'Feedback', type: 'textarea', colSpan: 2 },\n];\n\nexport function HiringInterviewsPagePage() {\n const { showToast } = useGlobalUi();\n const { interviews, candidates, addInterview, updateInterview } = useCrewFoundryStore();\n const [scheduleOpen, setScheduleOpen] = useState(false);\n const [updateTarget, setUpdateTarget] = useState<Interview | null>(null);\n\n const scheduled = interviews.filter((i) => i.status === 'scheduled');\n const completed = interviews.filter((i) => i.status === 'completed');\n\n // Interviews dated within the current calendar week (Mon–Sun).\n const thisWeekCount = useMemo(() => {\n const now = new Date();\n const startOfWeek = new Date(now);\n startOfWeek.setDate(now.getDate() - ((now.getDay() + 6) % 7));\n startOfWeek.setHours(0, 0, 0, 0);\n const endOfWeek = new Date(startOfWeek);\n endOfWeek.setDate(startOfWeek.getDate() + 7);\n return interviews.filter((i) => {\n const date = new Date(i.date);\n return !Number.isNaN(date.getTime()) && date >= startOfWeek && date < endOfWeek;\n }).length;\n }, [interviews]);\n\n const scheduleFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'candidateId',\n label: 'Candidate',\n type: 'select',\n required: true,\n placeholder: candidates.length ? 'Select a candidate…' : 'No candidates available',\n options: candidates.map((c) => ({\n label: `${c.firstName} ${c.lastName} — ${c.position}`,\n value: c.id,\n })),\n },\n { name: 'interviewerName', label: 'Interviewer', type: 'text', required: true },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'time', label: 'Time', type: 'text', required: true, placeholder: 'e.g. 10:30 AM' },\n { name: 'type', label: 'Type', type: 'select', required: true, options: INTERVIEW_TYPE_OPTIONS },\n ],\n [candidates]\n );\n\n const handleSchedule = async (values: FormValues) => {\n const candidate = candidates.find((c) => c.id === getText(values, 'candidateId'));\n if (!candidate) {\n throw new Error('Please select a candidate');\n }\n await addInterview({\n id: newClientId(),\n candidateId: candidate.id,\n candidateName: `${candidate.firstName} ${candidate.lastName}`,\n position: candidate.position,\n interviewerName: getText(values, 'interviewerName'),\n date: getText(values, 'date'),\n time: getText(values, 'time'),\n type: values.type as Interview['type'],\n status: 'scheduled',\n });\n showToast('Interview scheduled', 'success');\n };\n\n const handleUpdate = async (values: FormValues) => {\n if (!updateTarget) {\n return;\n }\n const ratingRaw = values.rating;\n const hasRating =\n typeof ratingRaw === 'number' ||\n (typeof ratingRaw === 'string' && ratingRaw.trim() !== '');\n await updateInterview(updateTarget.id, {\n status: values.status as Interview['status'],\n feedback: getText(values, 'feedback'),\n ...(hasRating ? { rating: Number(ratingRaw) } : {}),\n });\n showToast('Interview updated', 'success');\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Interviews</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {scheduled.length} upcoming · {completed.length} completed\n </p>\n <PagePurpose className=\"mt-1\">Schedule and track interviews across your hiring pipeline — keep everyone aligned on who's meeting which candidate and when.</PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => setScheduleOpen(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Schedule Interview\n </Button>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Scheduled</CardTitle>\n <Calendar className=\"h-4 w-4 text-status-info-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{scheduled.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Completed</CardTitle>\n <Clock className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{completed.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">This Week</CardTitle>\n <Video className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{thisWeekCount}</div>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Upcoming Interviews</CardTitle>\n <CardDescription>Scheduled interviews for the coming days</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {scheduled.map((interview) => (\n <div\n key={interview.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded border p-4\"\n >\n <div>\n <p className=\"text-sm font-medium\">{interview.candidateName}</p>\n <p className=\"text-xs text-text-secondary\">{interview.position}</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n Interviewer: {interview.interviewerName}\n </p>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">{interview.date}</p>\n <p className=\"text-xs text-text-secondary\">{interview.time}</p>\n </div>\n <Badge variant=\"outline\">{interview.type}</Badge>\n <Badge variant=\"secondary\">{interview.status}</Badge>\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setUpdateTarget(interview)}\n >\n Record outcome\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {completed.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Completed Interviews</CardTitle>\n <CardDescription>Recent interview feedback</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {completed.map((interview) => (\n <div\n key={interview.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded border p-4\"\n >\n <div>\n <p className=\"text-sm font-medium\">{interview.candidateName}</p>\n <p className=\"text-xs text-text-secondary\">{interview.position}</p>\n {interview.feedback && (\n <p className=\"text-xs text-text-secondary mt-1 italic\">\n "{interview.feedback}"\n </p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n {interview.rating && (\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-4 w-4 text-status-warning-text\" />\n <span className=\"text-sm font-medium\">{interview.rating}/5</span>\n </div>\n )}\n <Badge variant=\"default\">completed</Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n\n <FormModal\n open={scheduleOpen}\n onOpenChange={setScheduleOpen}\n title=\"Schedule Interview\"\n description=\"Set up a conversation with a candidate in your pipeline.\"\n fields={scheduleFields}\n submitLabel=\"Schedule\"\n onSubmit={handleSchedule}\n />\n <FormModal\n open={updateTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setUpdateTarget(null);\n }\n }}\n title=\"Record Interview Outcome\"\n description={updateTarget ? `${updateTarget.candidateName} — ${updateTarget.position}` : undefined}\n fields={UPDATE_FIELDS}\n initialValues={\n updateTarget\n ? {\n status: updateTarget.status,\n rating: updateTarget.rating ?? '',\n feedback: updateTarget.feedback ?? '',\n }\n : undefined\n }\n submitLabel=\"Save outcome\"\n onSubmit={handleUpdate}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAmBA,IAAM,IAAiD;CACrD;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAa,OAAO;EAAa;CAC3C,EASK,IAA+B;CACnC;EAAE,MAAM;EAAU,OAAO;EAAU,MAAM;EAAU,SARI;GACvD;IAAE,OAAO;IAAa,OAAO;IAAa;GAC1C;IAAE,OAAO;IAAa,OAAO;IAAa;GAC1C;IAAE,OAAO;IAAa,OAAO;IAAa;GAC1C;IAAE,OAAO;IAAW,OAAO;IAAW;GACvC;EAGuF,UAAU;EAAM;CACtG;EAAE,MAAM;EAAU,OAAO;EAAqB,MAAM;EAAU;CAC9D;EAAE,MAAM;EAAY,OAAO;EAAY,MAAM;EAAY,SAAS;EAAG;CACtE;AAED,SAAgB,IAA2B;CACzC,IAAM,EAAE,iBAAc,GAAa,EAC7B,EAAE,eAAY,eAAY,iBAAc,uBAAoB,GAAqB,EACjF,CAAC,GAAc,KAAmB,EAAS,GAAM,EACjD,CAAC,GAAc,KAAmB,EAA2B,KAAK,EAElE,IAAY,EAAW,QAAQ,MAAM,EAAE,WAAW,YAAY,EAC9D,IAAY,EAAW,QAAQ,MAAM,EAAE,WAAW,YAAY,EAG9D,IAAgB,QAAc;EAClC,IAAM,oBAAM,IAAI,MAAM,EAChB,IAAc,IAAI,KAAK,EAAI;AAEjC,EADA,EAAY,QAAQ,EAAI,SAAS,IAAK,EAAI,QAAQ,GAAG,KAAK,EAAG,EAC7D,EAAY,SAAS,GAAG,GAAG,GAAG,EAAE;EAChC,IAAM,IAAY,IAAI,KAAK,EAAY;AAEvC,SADA,EAAU,QAAQ,EAAY,SAAS,GAAG,EAAE,EACrC,EAAW,QAAQ,MAAM;GAC9B,IAAM,IAAO,IAAI,KAAK,EAAE,KAAK;AAC7B,UAAO,CAAC,OAAO,MAAM,EAAK,SAAS,CAAC,IAAI,KAAQ,KAAe,IAAO;IACtE,CAAC;IACF,CAAC,EAAW,CAAC,EAEV,IAAiB,QACf;EACJ;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,aAAa,EAAW,SAAS,wBAAwB;GACzD,SAAS,EAAW,KAAK,OAAO;IAC9B,OAAO,GAAG,EAAE,UAAU,GAAG,EAAE,SAAS,KAAK,EAAE;IAC3C,OAAO,EAAE;IACV,EAAE;GACJ;EACD;GAAE,MAAM;GAAmB,OAAO;GAAe,MAAM;GAAQ,UAAU;GAAM;EAC/E;GAAE,MAAM;GAAQ,OAAO;GAAQ,MAAM;GAAQ,UAAU;GAAM;EAC7D;GAAE,MAAM;GAAQ,OAAO;GAAQ,MAAM;GAAQ,UAAU;GAAM,aAAa;GAAiB;EAC3F;GAAE,MAAM;GAAQ,OAAO;GAAQ,MAAM;GAAU,UAAU;GAAM,SAAS;GAAwB;EACjG,EACD,CAAC,EAAW,CACb;AAqCD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAc,cAAyB,CAAA;MACpC,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAAb;OACG,EAAU;OAAO;OAAa,EAAU;OAAO;OAC9C;;KACJ,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAA+I,CAAA;KACzK,EAAA,CAAA,EACN,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAgB,GAAK;eAAtD,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,qBAE1B;OACL;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,MAAA;MAAK,WAAU;gBAA5C,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAqB,CAAA,EAChE,kBAAC,GAAD,EAAU,WAAU,iCAAkC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC,EAAU;OAAa,CAAA,EAC7D,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAqB,CAAA,EAChE,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC,EAAU;OAAa,CAAA,EAC7D,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAqB,CAAA,EAChE,kBAAC,GAAD,EAAO,WAAU,+BAAgC,CAAA,CACtC;UACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC;OAAoB,CAAA,EAC1D,CAAA,CACJ;;KACR;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,uBAA+B,CAAA,EAC1C,kBAAC,GAAD,EAAA,UAAiB,4CAA0D,CAAA,CAChE,EAAA,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cACpB,EAAU,KAAK,MACd,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD,EAAA,UAAA;MACE,kBAAC,KAAD;OAAG,WAAU;iBAAuB,EAAU;OAAkB,CAAA;MAChE,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAU;OAAa,CAAA;MACnE,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAAgD,iBAChC,EAAU,gBACtB;;MACA,EAAA,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAuB,EAAU;SAAS,CAAA,EACvD,kBAAC,KAAD;SAAG,WAAU;mBAA+B,EAAU;SAAS,CAAA,CAC3D;;OACN,kBAAC,GAAD;QAAO,SAAQ;kBAAW,EAAU;QAAa,CAAA;OACjD,kBAAC,GAAD;QAAO,SAAQ;kBAAa,EAAU;QAAe,CAAA;OACrD,kBAAC,GAAD;QACE,SAAQ;QACR,MAAK;QACL,eAAe,EAAgB,EAAU;kBAC1C;QAEQ,CAAA;OACL;QACF;OAzBC,EAAU,GAyBX,CACN;IACU,CAAA,CACT,EAAA,CAAA;GAEN,EAAU,SAAS,KAClB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,wBAAgC,CAAA,EAC3C,kBAAC,GAAD,EAAA,UAAiB,6BAA2C,CAAA,CACjD,EAAA,CAAA,EACb,kBAAC,GAAD;IAAa,WAAU;cACpB,EAAU,KAAK,MACd,kBAAC,OAAD;KAEE,WAAU;eAFZ,CAIE,kBAAC,OAAD,EAAA,UAAA;MACE,kBAAC,KAAD;OAAG,WAAU;iBAAuB,EAAU;OAAkB,CAAA;MAChE,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAU;OAAa,CAAA;MAClE,EAAU,YACT,kBAAC,KAAD;OAAG,WAAU;iBAAb;QAAuD;QAC9C,EAAU;QAAS;QACxB;;MAEF,EAAA,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,EAAU,UACT,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,oCAAqC,CAAA,EACrD,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAAuC,EAAU,QAAO,KAAS;UAC7D;UAER,kBAAC,GAAD;OAAO,SAAQ;iBAAU;OAAiB,CAAA,CACtC;QACF;OArBC,EAAU,GAqBX,CACN;IACU,CAAA,CACT,EAAA,CAAA;GAGT,kBAAC,GAAD;IACE,MAAM;IACN,cAAc;IACd,OAAM;IACN,aAAY;IACZ,QAAQ;IACR,aAAY;IACZ,UApKiB,OAAO,MAAuB;KACnD,IAAM,IAAY,EAAW,MAAM,MAAM,EAAE,OAAO,EAAQ,GAAQ,cAAc,CAAC;AACjF,SAAI,CAAC,EACH,OAAU,MAAM,4BAA4B;AAa9C,KAXA,MAAM,EAAa;MACjB,IAAI,GAAa;MACjB,aAAa,EAAU;MACvB,eAAe,GAAG,EAAU,UAAU,GAAG,EAAU;MACnD,UAAU,EAAU;MACpB,iBAAiB,EAAQ,GAAQ,kBAAkB;MACnD,MAAM,EAAQ,GAAQ,OAAO;MAC7B,MAAM,EAAQ,GAAQ,OAAO;MAC7B,MAAM,EAAO;MACb,QAAQ;MACT,CAAC,EACF,EAAU,uBAAuB,UAAU;;IAqJvC,CAAA;GACF,kBAAC,GAAD;IACE,MAAM,MAAiB;IACvB,eAAe,MAAS;AACtB,KAAK,KACH,EAAgB,KAAK;;IAGzB,OAAM;IACN,aAAa,IAAe,GAAG,EAAa,cAAc,KAAK,EAAa,aAAa,KAAA;IACzF,QAAQ;IACR,eACE,IACI;KACE,QAAQ,EAAa;KACrB,QAAQ,EAAa,UAAU;KAC/B,UAAU,EAAa,YAAY;KACpC,GACD,KAAA;IAEN,aAAY;IACZ,UAvKe,OAAO,MAAuB;AACjD,SAAI,CAAC,EACH;KAEF,IAAM,IAAY,EAAO,QACnB,IACJ,OAAO,KAAc,YACpB,OAAO,KAAc,YAAY,EAAU,MAAM,KAAK;AAMzD,KALA,MAAM,EAAgB,EAAa,IAAI;MACrC,QAAQ,EAAO;MACf,UAAU,EAAQ,GAAQ,WAAW;MACrC,GAAI,IAAY,EAAE,QAAQ,OAAO,EAAU,EAAE,GAAG,EAAE;MACnD,CAAC,EACF,EAAU,qBAAqB,UAAU;;IA2JrC,CAAA;GACE"}
|
|
1
|
+
{"version":3,"file":"HiringInterviewsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringInterviewsPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calendar, Clock, Plus, Star, Video } from 'lucide-react';\nimport { useCrewFoundryStore, type Interview } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { getText, newClientId } from '../components/forms/formValues';\n\nconst INTERVIEW_TYPE_OPTIONS: FieldConfig['options'] = [\n { label: 'Phone', value: 'phone' },\n { label: 'Video', value: 'video' },\n { label: 'Onsite', value: 'onsite' },\n { label: 'Panel', value: 'panel' },\n { label: 'Technical', value: 'technical' },\n];\n\nconst INTERVIEW_STATUS_OPTIONS: FieldConfig['options'] = [\n { label: 'Scheduled', value: 'scheduled' },\n { label: 'Completed', value: 'completed' },\n { label: 'Cancelled', value: 'cancelled' },\n { label: 'No show', value: 'no_show' },\n];\n\nconst UPDATE_FIELDS: FieldConfig[] = [\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: INTERVIEW_STATUS_OPTIONS,\n required: true,\n },\n { name: 'rating', label: 'Rating (out of 5)', type: 'number' },\n { name: 'feedback', label: 'Feedback', type: 'textarea', colSpan: 2 },\n];\n\nexport function HiringInterviewsPagePage() {\n const { showToast } = useGlobalUi();\n const { interviews, candidates, addInterview, updateInterview } = useCrewFoundryStore();\n const [scheduleOpen, setScheduleOpen] = useState(false);\n const [updateTarget, setUpdateTarget] = useState<Interview | null>(null);\n\n const scheduled = interviews.filter((i) => i.status === 'scheduled');\n const completed = interviews.filter((i) => i.status === 'completed');\n\n // Interviews dated within the current calendar week (Mon–Sun).\n const thisWeekCount = useMemo(() => {\n const now = new Date();\n const startOfWeek = new Date(now);\n startOfWeek.setDate(now.getDate() - ((now.getDay() + 6) % 7));\n startOfWeek.setHours(0, 0, 0, 0);\n const endOfWeek = new Date(startOfWeek);\n endOfWeek.setDate(startOfWeek.getDate() + 7);\n return interviews.filter((i) => {\n const date = new Date(i.date);\n return !Number.isNaN(date.getTime()) && date >= startOfWeek && date < endOfWeek;\n }).length;\n }, [interviews]);\n\n const scheduleFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'candidateId',\n label: 'Candidate',\n type: 'select',\n required: true,\n placeholder: candidates.length ? 'Select a candidate…' : 'No candidates available',\n options: candidates.map((c) => ({\n label: `${c.firstName} ${c.lastName} — ${c.position}`,\n value: c.id,\n })),\n },\n { name: 'interviewerName', label: 'Interviewer', type: 'text', required: true },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'time', label: 'Time', type: 'text', required: true, placeholder: 'e.g. 10:30 AM' },\n {\n name: 'type',\n label: 'Type',\n type: 'select',\n required: true,\n options: INTERVIEW_TYPE_OPTIONS,\n },\n ],\n [candidates]\n );\n\n const handleSchedule = async (values: FormValues) => {\n const candidate = candidates.find((c) => c.id === getText(values, 'candidateId'));\n if (!candidate) {\n throw new Error('Please select a candidate');\n }\n await addInterview({\n id: newClientId(),\n candidateId: candidate.id,\n candidateName: `${candidate.firstName} ${candidate.lastName}`,\n position: candidate.position,\n interviewerName: getText(values, 'interviewerName'),\n date: getText(values, 'date'),\n time: getText(values, 'time'),\n type: values.type as Interview['type'],\n status: 'scheduled',\n });\n showToast('Interview scheduled', 'success');\n };\n\n const handleUpdate = async (values: FormValues) => {\n if (!updateTarget) {\n return;\n }\n const ratingRaw = values.rating;\n const hasRating =\n typeof ratingRaw === 'number' || (typeof ratingRaw === 'string' && ratingRaw.trim() !== '');\n await updateInterview(updateTarget.id, {\n status: values.status as Interview['status'],\n feedback: getText(values, 'feedback'),\n ...(hasRating ? { rating: Number(ratingRaw) } : {}),\n });\n showToast('Interview updated', 'success');\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Interviews</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {scheduled.length} upcoming · {completed.length} completed\n </p>\n <PagePurpose className=\"mt-1\">\n Schedule and track interviews across your hiring pipeline — keep everyone aligned on\n who's meeting which candidate and when.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => setScheduleOpen(true)}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Schedule Interview\n </Button>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Scheduled</CardTitle>\n <Calendar className=\"h-4 w-4 text-status-info-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{scheduled.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Completed</CardTitle>\n <Clock className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{completed.length}</div>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">This Week</CardTitle>\n <Video className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{thisWeekCount}</div>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Upcoming Interviews</CardTitle>\n <CardDescription>Scheduled interviews for the coming days</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {scheduled.map((interview) => (\n <div\n key={interview.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded border p-4\"\n >\n <div>\n <p className=\"text-sm font-medium\">{interview.candidateName}</p>\n <p className=\"text-xs text-text-secondary\">{interview.position}</p>\n <p className=\"text-xs text-text-secondary mt-1\">\n Interviewer: {interview.interviewerName}\n </p>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">{interview.date}</p>\n <p className=\"text-xs text-text-secondary\">{interview.time}</p>\n </div>\n <Badge variant=\"outline\">{interview.type}</Badge>\n <Badge variant=\"secondary\">{interview.status}</Badge>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setUpdateTarget(interview)}>\n Record outcome\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n\n {completed.length > 0 && (\n <Card>\n <CardHeader>\n <CardTitle>Completed Interviews</CardTitle>\n <CardDescription>Recent interview feedback</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {completed.map((interview) => (\n <div\n key={interview.id}\n className=\"flex flex-wrap items-center justify-between gap-3 rounded border p-4\"\n >\n <div>\n <p className=\"text-sm font-medium\">{interview.candidateName}</p>\n <p className=\"text-xs text-text-secondary\">{interview.position}</p>\n {interview.feedback && (\n <p className=\"text-xs text-text-secondary mt-1 italic\">\n "{interview.feedback}"\n </p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n {interview.rating && (\n <div className=\"flex items-center gap-1\">\n <Star className=\"h-4 w-4 text-status-warning-text\" />\n <span className=\"text-sm font-medium\">{interview.rating}/5</span>\n </div>\n )}\n <Badge variant=\"default\">completed</Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n )}\n\n <FormModal\n open={scheduleOpen}\n onOpenChange={setScheduleOpen}\n title=\"Schedule Interview\"\n description=\"Set up a conversation with a candidate in your pipeline.\"\n fields={scheduleFields}\n submitLabel=\"Schedule\"\n onSubmit={handleSchedule}\n />\n <FormModal\n open={updateTarget !== null}\n onOpenChange={(open) => {\n if (!open) {\n setUpdateTarget(null);\n }\n }}\n title=\"Record Interview Outcome\"\n description={\n updateTarget ? `${updateTarget.candidateName} — ${updateTarget.position}` : undefined\n }\n fields={UPDATE_FIELDS}\n initialValues={\n updateTarget\n ? {\n status: updateTarget.status,\n rating: updateTarget.rating ?? '',\n feedback: updateTarget.feedback ?? '',\n }\n : undefined\n }\n submitLabel=\"Save outcome\"\n onSubmit={handleUpdate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -11,7 +11,7 @@ function w() {
|
|
|
11
11
|
let { t: w } = C(), T = s(), { jobPostings: E } = e(), [D, O] = t(""), { page: k, pageSize: A, offset: j, setPage: M } = S({ initialPageSize: 12 }), N = (e, t) => {
|
|
12
12
|
let n = w(e);
|
|
13
13
|
return n === e ? t : n;
|
|
14
|
-
}, P = E.filter((e) => `${e.title} ${e.department} ${e.location}`.toLowerCase().includes(D.toLowerCase())), F = P.slice(j, j + A), I = (e) => T(
|
|
14
|
+
}, P = E.filter((e) => `${e.title} ${e.department} ${e.location}`.toLowerCase().includes(D.toLowerCase())), F = P.slice(j, j + A), I = (e) => T(`../../hiring/job-details-page?id=${encodeURIComponent(e)}`), L = E.filter((e) => e.status === "active").length, R = E.reduce((e, t) => e + t.applicants, 0);
|
|
15
15
|
return /* @__PURE__ */ l("div", {
|
|
16
16
|
className: "space-y-6 p-6",
|
|
17
17
|
children: [
|