@burdenoff/microfe-crewfoundry 2026.704.7 → 2026.707.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/crewfoundry/components/viz/BarChart.js.map +1 -1
  2. package/dist/crewfoundry/components/viz/CertificateCard.js.map +1 -1
  3. package/dist/crewfoundry/components/viz/DonutChart.js.map +1 -1
  4. package/dist/crewfoundry/components/viz/Heatmap.js.map +1 -1
  5. package/dist/crewfoundry/components/viz/LineChart.js.map +1 -1
  6. package/dist/crewfoundry/components/viz/PathSteps.js.map +1 -1
  7. package/dist/crewfoundry/components/viz/RatingStars.js.map +1 -1
  8. package/dist/crewfoundry/components/viz/SegmentedTabs.js.map +1 -1
  9. package/dist/crewfoundry/components/viz/StatCard.js.map +1 -1
  10. package/dist/crewfoundry/components/viz/lib/Avatar.js.map +1 -1
  11. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js.map +1 -1
  12. package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -1
  13. package/dist/crewfoundry/lib/analytics.js.map +1 -1
  14. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  15. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
  16. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
  17. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
  18. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +2 -1
  19. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
  20. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  21. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
  22. package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js +0 -41
  23. package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
  24. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
  25. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  26. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
  27. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
  28. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  29. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +8 -4
  30. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  31. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  32. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  33. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  34. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
  35. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
  36. package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
  37. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
  38. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
  39. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  40. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
  41. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
  42. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  43. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -1
  44. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
  45. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  46. package/dist/crewfoundry/store/crewfoundryStore.js +142 -184
  47. package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
  48. package/dist/crewfoundry/utils/courseProgress.js.map +1 -1
  49. package/package.json +1 -1
@@ -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 => 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":";;;;;;;AAiCA,IAAM,IAAW,8DAIX,IAA+C;CACnD;EAAE,MAAM;EAAgB,QAAQ;EAAkB;CAClD;EAAE,MAAM;EAAa,QAAQ;EAAU;CACvC;EAAE,MAAM;EAAU,QAAQ;EAAW;CACrC;EAAE,MAAM;EAAU,QAAQ;EAAY;CACtC;EAAE,MAAM;EAAc,QAAQ;EAAM;CACpC;EAAE,MAAM;EAAY,QAAQ;EAAM;CAClC;EAAE,MAAM;EAAa,QAAQ;EAAU;CACvC;EAAE,MAAM;EAAW,QAAQ;EAAW;CACtC;EAAE,MAAM;EAAc,QAAQ;EAAU;CACxC;EAAE,MAAM;EAAgB,QAAQ;EAAY;CAC5C;EAAE,MAAM;EAAgB,QAAQ;EAAM;CACtC;EAAE,MAAM;EAAsB,QAAQ;EAAe;CACrD;EAAE,MAAM;EAAiB,QAAQ;EAAW;CAC5C;EAAE,MAAM;EAAW,QAAQ;EAAY;CACvC;EAAE,MAAM;EAAQ,QAAQ;EAAS;CACjC;EAAE,MAAM;EAAa,QAAQ;EAAM;CACnC;EAAE,MAAM;EAAU,QAAQ;EAAW;CACtC,EAkBK,IAA4C;CAChD,OAAO;CACP,UAAU;CACX,EAEK,KAAY,MAA0B,EAAM,QAAQ,mBAAmB,QAAQ,CAAC,aAAa,EAE7F,IAAkB,OAAO,MAAmC;AAChE,KAAI;AACF,MAAI,OAAO,YAAc,OAAe,UAAU,WAAW,UAE3D,QADA,MAAM,UAAU,UAAU,UAAU,EAAK,EAClC;SAEH;AAGR,KAAI;AACF,MAAI,OAAO,WAAa,IACtB,QAAO;EAET,IAAM,IAAW,SAAS,cAAc,WAAW;AAMnD,EALA,EAAS,QAAQ,GACjB,EAAS,MAAM,WAAW,SAC1B,EAAS,MAAM,UAAU,KACzB,SAAS,KAAK,YAAY,EAAS,EACnC,EAAS,OAAO,EAChB,EAAS,QAAQ;EACjB,IAAM,IAAY,SAAS,YAAY,OAAO;AAE9C,SADA,SAAS,KAAK,YAAY,EAAS,EAC5B;SACD;AACN,SAAO;;;AAIX,SAAgB,IAA+B;CAC7C,IAAM,EAAE,cAAW,gBAAa,eAAY,mBAAgB,qBAC1D,GAAqB,EACjB,EAAE,iBAAc,GAAa,EAC7B,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAQ,KAAa,EAAwB,KAAK,EAEnD,IAAS,QAA0B;EACvC,IAAM,IAA4B,EAAS,KAAK,OAAY;GAC1D,MAAM;GACN,MAAM,aAAa,EAAO;GAC1B,WAAW,aAAa,EAAO,KAAK,oBAAoB,EAAO,KAAK,eAAe,EAAO,KAAK;GAC/F,aAAa,YAAY,EAAS,EAAO,KAAK,CAAC,WAAW,EAAO,OAAO;GACzE,EAAE,EACG,IAA4B,EAAS,KAAK,OAAY;GAC1D,MAAM;GACN,MAAM,aAAa,EAAO;GAC1B,WAAW,aAAa,EAAO,KAAK,6BAA6B,EAAO,KAAK,eAAe,EAAO,KAAK;GACxG,aAAa,sBAAsB,EAAS,EAAO,KAAK,CAAC,WAAW,EAAO,OAAO;GACnF,EAAE,EACG,IAA4B,EAAS,KAAK,OAAY;GAC1D,MAAM;GACN,MAAM,aAAa,EAAO;GAC1B,WAAW,aAAa,EAAO,KAAK;GACpC,aAAa,YAAY,EAAS,EAAO,KAAK,CAAC,WAAW,EAAO,OAAO;GACzE,EAAE;AAEH,SAAO;GACL;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY,CACV;KACE,MAAM;KACN,MAAM;KACN,WAAW;KACX,aAAa,iDAAiD,EAAU,OAAO,cAAc,EAAY,OAAO,iBAAiB,EAAW,OAAO,eAAe,EAAe,OAAO,oBAAoB,EAAc,OAAO;KAClO,CACF;IACF;GACD;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY;KACV;MACE,MAAM;MACN,MAAM;MACN,WAAW;MACX,aAAa;MACd;KACD;MACE,MAAM;MACN,MAAM;MACN,WAAW;MACX,aAAa;MACd;KACD;MACE,MAAM;MACN,MAAM;MACN,WAAW;MACX,aAAa;MACd;KACF;IACF;GACF;IACA;EACD,EAAW;EACX,EAAU;EACV,EAAY;EACZ,EAAc;EACd,EAAe;EAChB,CAAC,EAEI,IAAkB,QAChB,EAAO,QAAQ,GAAK,MAAU,IAAM,EAAM,WAAW,QAAQ,EAAE,EACrE,CAAC,EAAO,CACT,EAEK,IAAiB,QAAc;EACnC,IAAM,IAAO,EAAO,MAAM,CAAC,aAAa;AAIxC,SAHK,IAGE,EACJ,KAAK,OAAW;GACf,GAAG;GACH,YAAY,EAAM,WAAW,QAC1B,MACC,EAAG,KAAK,aAAa,CAAC,SAAS,EAAK,IACpC,EAAG,UAAU,aAAa,CAAC,SAAS,EAAK,IACzC,EAAG,YAAY,aAAa,CAAC,SAAS,EAAK,CAC9C;GACF,EAAE,CACF,QAAQ,MAAU,EAAM,WAAW,SAAS,EAAE,GAZxC;IAaR,CAAC,GAAQ,EAAO,CAAC,EAEd,IAAa,OAAO,MAAkB;AAE1C,EADW,MAAM,EAAgB,EAAM,IAErC,EAAU,EAAM,EAChB,EAAU,uBAAuB,UAAU,EAC3C,OAAO,iBAAiB,GAAW,MAAa,MAAY,IAAQ,OAAO,EAAS,EAAE,KAAK,IAE3F,EAAU,+BAA+B,QAAQ;IAI/C,KAAiB,MAAe;AAEpC,GADe,OAAO,WAAa,MAAc,SAAS,eAAe,SAAS,IAAK,GAAG,OAClF,eAAe;GAAE,UAAU;GAAU,OAAO;GAAS,CAAC;;AAoBhE,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,qBAAgC,CAAA;MAC3C,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAEvC,CAAA;KACJ,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAGhB,CAAA;KACV,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,SAAQ;MAAY,WAAU;gBAClC;MACK,CAAA,EACR,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,cAAW;MACX,eAAe,KAAK,EAAW,EAAS;gBAJ1C,CAMG,MAAW,IACV,kBAAC,GAAD,EAAO,WAAU,yCAA0C,CAAA,GAE3D,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EACjC,gBAEK;QACL;OACF;;GAEN,kBAAC,OAAD;IAAK,WAAU;cApDD;KAChB;MAAE,MAAM;MAAS,OAAO;MAAY,OAAO;MAAW,OAAO;MAAyB;KACtF;MACE,MAAM;MACN,OAAO;MACP,OAAO;MACP,OAAO;MACR;KACD;MAAE,MAAM;MAAM,OAAO;MAAQ,OAAO;MAAgB,OAAO;MAA4B;KACvF;MACE,MAAM;MACN,OAAO;MACP,OAAO,OAAO,EAAgB;MAC9B,OAAO;MACR;KACF,CAsCgB,KAAK,MACd,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACE,kBAAC,EAAK,MAAN,EAAW,WAAW,WAAW,EAAK,SAAW,CAAA,EACjD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAK;OAAU,CAAA,EAC3D,kBAAC,KAAD;OAAG,WAAU;iBAAgC,EAAK;OAAU,CAAA,CACxD;QACM;QACT,EARI,EAAK,MAQT,CACP;IACE,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBAA6B;MAAa,CAAA,EACzD,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAO,KAAK,MACX,kBAAC,UAAD;OAEE,MAAK;OACL,cAAY,WAAW,EAAM;OAC7B,eAAe,EAAc,EAAM,GAAG;OACtC,WAAU;iBALZ;QAOE,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA;QACpD,kBAAC,QAAD,EAAA,UAAO,EAAM,MAAY,CAAA;QACzB,kBAAC,GAAD;SAAO,SAAQ;SAAU,WAAU;mBAChC,EAAM,WAAW;SACZ,CAAA;QACR,kBAAC,GAAD,EAAc,WAAU,+BAAgC,CAAA;QACjD;SAZF,EAAM,GAYJ,CACT;MACE,CAAA,CACQ,EAAA,CAAA,EAEhB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAW,WAAU;gBAAU;MAA0B,CAAA,EAC9C,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAAoC,0BAAuB,UAAiB;;OACxE,CAAA,EACN,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAIvC,CAAA,CACQ;QACT,EAAA,CAAA,CACH;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;QACE,aAAY;QACZ,WAAU;QACV,OAAO;QACP,WAAW,MAAU,EAAU,EAAM,OAAO,MAAM;QAClD,CAAA,CACE;;MAEL,EAAe,KAAK,MACnB,kBAAC,GAAD;OAAqB,IAAI,SAAS,EAAM;OAAM,WAAU;iBAAxD,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA,EACpD,kBAAC,GAAD;SAAW,WAAU;mBAAa,EAAM;SAAiB,CAAA,CACrD;WACN,kBAAC,GAAD,EAAA,UAAA;QACG,EAAM;QAAY;QAAI,EAAM,WAAW;QAAO;QAC/B,EAAA,CAAA,CACP,EAAA,CAAA,EACb,kBAAC,GAAD;QAAa,WAAU;kBACpB,EAAM,WAAW,KAAK,GAAI,MACzB,kBAAC,OAAD;SAEE,WAAW,2DAA2D,IAAQ,EAAM,WAAW,SAAS,IAAI,aAAa;mBAF3H;UAIE,kBAAC,GAAD;WACE,WAAW,GAAG,EAAW,EAAG,MAAM;qBAEjC,EAAG;WACE,CAAA;UACR,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YAAM,WAAU;sBAAoC,EAAG;YAAiB,CAAA,EACxE,kBAAC,QAAD;YAAM,WAAU;sBAA+B,EAAG;YAAmB,CAAA,CACjE;;UACN,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,WAAU;WACV,cAAY,QAAQ,EAAG,KAAK;WAC5B,eAAe,KAAK,EAAW,EAAG,KAAK;qBAEtC,MAAW,EAAG,OACb,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,GAEtD,kBAAC,GAAD,EAAM,WAAU,+BAAgC,CAAA;WAE3C,CAAA;UACL;WAzBC,EAAG,KAyBJ,CACN;QACU,CAAA,CACT;SAzCI,EAAM,GAyCV,CACP;MAED,EAAe,WAAW,KACzB,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QACE,cAAa;QACb,OAAM;QACN,aAAY;QACZ,CAAA;OACE,CAAA;MAEJ;OACF;;GACF"}
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 13 CrewFoundry-owned domain entities exposed by the subgraph, each with\n// create / update / remove mutations. Marketplace, billing/invoices,\n// notifications, and learning are intentionally served by shared owners.\nconst ENTITIES: { name: string; domain: string }[] = [\n { name: 'Announcement', domain: 'Communications' },\n { name: 'Candidate', domain: 'Hiring' },\n { name: 'Client', domain: 'Finance' },\n { name: 'Department', domain: 'HR' },\n { name: 'Employee', domain: 'HR' },\n { name: 'Interview', domain: 'Hiring' },\n { name: 'JobPosting', domain: 'Hiring' },\n { name: 'LeaveRequest', domain: 'HR' },\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 }, [\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":";;;;;;;AAiCA,IAAM,IAAW,8DAKX,IAA+C;CACnD;EAAE,MAAM;EAAgB,QAAQ;EAAkB;CAClD;EAAE,MAAM;EAAa,QAAQ;EAAU;CACvC;EAAE,MAAM;EAAU,QAAQ;EAAW;CACrC;EAAE,MAAM;EAAc,QAAQ;EAAM;CACpC;EAAE,MAAM;EAAY,QAAQ;EAAM;CAClC;EAAE,MAAM;EAAa,QAAQ;EAAU;CACvC;EAAE,MAAM;EAAc,QAAQ;EAAU;CACxC;EAAE,MAAM;EAAgB,QAAQ;EAAM;CACtC;EAAE,MAAM;EAAiB,QAAQ;EAAW;CAC5C;EAAE,MAAM;EAAW,QAAQ;EAAY;CACvC;EAAE,MAAM;EAAQ,QAAQ;EAAS;CACjC;EAAE,MAAM;EAAa,QAAQ;EAAM;CACnC;EAAE,MAAM;EAAU,QAAQ;EAAW;CACtC,EAkBK,IAA4C;CAChD,OAAO;CACP,UAAU;CACX,EAEK,KAAY,MAA0B,EAAM,QAAQ,mBAAmB,QAAQ,CAAC,aAAa,EAE7F,IAAkB,OAAO,MAAmC;AAChE,KAAI;AACF,MAAI,OAAO,YAAc,OAAe,UAAU,WAAW,UAE3D,QADA,MAAM,UAAU,UAAU,UAAU,EAAK,EAClC;SAEH;AAGR,KAAI;AACF,MAAI,OAAO,WAAa,IACtB,QAAO;EAET,IAAM,IAAW,SAAS,cAAc,WAAW;AAMnD,EALA,EAAS,QAAQ,GACjB,EAAS,MAAM,WAAW,SAC1B,EAAS,MAAM,UAAU,KACzB,SAAS,KAAK,YAAY,EAAS,EACnC,EAAS,OAAO,EAChB,EAAS,QAAQ;EACjB,IAAM,IAAY,SAAS,YAAY,OAAO;AAE9C,SADA,SAAS,KAAK,YAAY,EAAS,EAC5B;SACD;AACN,SAAO;;;AAIX,SAAgB,IAA+B;CAC7C,IAAM,EAAE,cAAW,gBAAa,eAAY,mBAAgB,qBAC1D,GAAqB,EACjB,EAAE,iBAAc,GAAa,EAC7B,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAQ,KAAa,EAAwB,KAAK,EAEnD,IAAS,QAA0B;EACvC,IAAM,IAA4B,EAAS,KAAK,OAAY;GAC1D,MAAM;GACN,MAAM,aAAa,EAAO;GAC1B,WAAW,aAAa,EAAO,KAAK,oBAAoB,EAAO,KAAK,eAAe,EAAO,KAAK;GAC/F,aAAa,YAAY,EAAS,EAAO,KAAK,CAAC,WAAW,EAAO,OAAO;GACzE,EAAE,EACG,IAA4B,EAAS,KAAK,OAAY;GAC1D,MAAM;GACN,MAAM,aAAa,EAAO;GAC1B,WAAW,aAAa,EAAO,KAAK,6BAA6B,EAAO,KAAK,eAAe,EAAO,KAAK;GACxG,aAAa,sBAAsB,EAAS,EAAO,KAAK,CAAC,WAAW,EAAO,OAAO;GACnF,EAAE,EACG,IAA4B,EAAS,KAAK,OAAY;GAC1D,MAAM;GACN,MAAM,aAAa,EAAO;GAC1B,WAAW,aAAa,EAAO,KAAK;GACpC,aAAa,YAAY,EAAS,EAAO,KAAK,CAAC,WAAW,EAAO,OAAO;GACzE,EAAE;AAEH,SAAO;GACL;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY,CACV;KACE,MAAM;KACN,MAAM;KACN,WAAW;KACX,aAAa,iDAAiD,EAAU,OAAO,cAAc,EAAY,OAAO,iBAAiB,EAAW,OAAO,eAAe,EAAe,OAAO,oBAAoB,EAAc,OAAO;KAClO,CACF;IACF;GACD;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY;IACb;GACD;IACE,IAAI;IACJ,MAAM;IACN,aAAa;IACb,YAAY;IACb;GACF;IACA;EACD,EAAW;EACX,EAAU;EACV,EAAY;EACZ,EAAc;EACd,EAAe;EAChB,CAAC,EAEI,IAAkB,QAChB,EAAO,QAAQ,GAAK,MAAU,IAAM,EAAM,WAAW,QAAQ,EAAE,EACrE,CAAC,EAAO,CACT,EAEK,IAAiB,QAAc;EACnC,IAAM,IAAO,EAAO,MAAM,CAAC,aAAa;AAIxC,SAHK,IAGE,EACJ,KAAK,OAAW;GACf,GAAG;GACH,YAAY,EAAM,WAAW,QAC1B,MACC,EAAG,KAAK,aAAa,CAAC,SAAS,EAAK,IACpC,EAAG,UAAU,aAAa,CAAC,SAAS,EAAK,IACzC,EAAG,YAAY,aAAa,CAAC,SAAS,EAAK,CAC9C;GACF,EAAE,CACF,QAAQ,MAAU,EAAM,WAAW,SAAS,EAAE,GAZxC;IAaR,CAAC,GAAQ,EAAO,CAAC,EAEd,IAAa,OAAO,MAAkB;AAE1C,EADW,MAAM,EAAgB,EAAM,IAErC,EAAU,EAAM,EAChB,EAAU,uBAAuB,UAAU,EAC3C,OAAO,iBAAiB,GAAW,MAAa,MAAY,IAAQ,OAAO,EAAS,EAAE,KAAK,IAE3F,EAAU,+BAA+B,QAAQ;IAI/C,KAAiB,MAAe;AAEpC,GADe,OAAO,WAAa,MAAc,SAAS,eAAe,SAAS,IAAK,GAAG,OAClF,eAAe;GAAE,UAAU;GAAU,OAAO;GAAS,CAAC;;AAoBhE,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,qBAAgC,CAAA;MAC3C,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAEvC,CAAA;KACJ,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAGhB,CAAA;KACV,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,SAAQ;MAAY,WAAU;gBAClC;MACK,CAAA,EACR,kBAAC,GAAD;MACE,MAAK;MACL,SAAQ;MACR,cAAW;MACX,eAAe,KAAK,EAAW,EAAS;gBAJ1C,CAMG,MAAW,IACV,kBAAC,GAAD,EAAO,WAAU,yCAA0C,CAAA,GAE3D,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EACjC,gBAEK;QACL;OACF;;GAEN,kBAAC,OAAD;IAAK,WAAU;cApDD;KAChB;MAAE,MAAM;MAAS,OAAO;MAAY,OAAO;MAAW,OAAO;MAAyB;KACtF;MACE,MAAM;MACN,OAAO;MACP,OAAO;MACP,OAAO;MACR;KACD;MAAE,MAAM;MAAM,OAAO;MAAQ,OAAO;MAAgB,OAAO;MAA4B;KACvF;MACE,MAAM;MACN,OAAO;MACP,OAAO,OAAO,EAAgB;MAC9B,OAAO;MACR;KACF,CAsCgB,KAAK,MACd,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;KAAa,WAAU;eAAvB,CACE,kBAAC,EAAK,MAAN,EAAW,WAAW,WAAW,EAAK,SAAW,CAAA,EACjD,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAK;OAAU,CAAA,EAC3D,kBAAC,KAAD;OAAG,WAAU;iBAAgC,EAAK;OAAU,CAAA,CACxD;QACM;QACT,EARI,EAAK,MAQT,CACP;IACE,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBAA6B;MAAa,CAAA,EACzD,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAO,KAAK,MACX,kBAAC,UAAD;OAEE,MAAK;OACL,cAAY,WAAW,EAAM;OAC7B,eAAe,EAAc,EAAM,GAAG;OACtC,WAAU;iBALZ;QAOE,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA;QACpD,kBAAC,QAAD,EAAA,UAAO,EAAM,MAAY,CAAA;QACzB,kBAAC,GAAD;SAAO,SAAQ;SAAU,WAAU;mBAChC,EAAM,WAAW;SACZ,CAAA;QACR,kBAAC,GAAD,EAAc,WAAU,+BAAgC,CAAA;QACjD;SAZF,EAAM,GAYJ,CACT;MACE,CAAA,CACQ,EAAA,CAAA,EAEhB,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;MAAW,WAAU;gBAAU;MAA0B,CAAA,EAC9C,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAAoC,0BAAuB,UAAiB;;OACxE,CAAA,EACN,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAIvC,CAAA,CACQ;QACT,EAAA,CAAA,CACH;QAEN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,wEAAyE,CAAA,EAC3F,kBAAC,GAAD;QACE,aAAY;QACZ,WAAU;QACV,OAAO;QACP,WAAW,MAAU,EAAU,EAAM,OAAO,MAAM;QAClD,CAAA,CACE;;MAEL,EAAe,KAAK,MACnB,kBAAC,GAAD;OAAqB,IAAI,SAAS,EAAM;OAAM,WAAU;iBAAxD,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA,EACpD,kBAAC,GAAD;SAAW,WAAU;mBAAa,EAAM;SAAiB,CAAA,CACrD;WACN,kBAAC,GAAD,EAAA,UAAA;QACG,EAAM;QAAY;QAAI,EAAM,WAAW;QAAO;QAC/B,EAAA,CAAA,CACP,EAAA,CAAA,EACb,kBAAC,GAAD;QAAa,WAAU;kBACpB,EAAM,WAAW,KAAK,GAAI,MACzB,kBAAC,OAAD;SAEE,WAAW,2DAA2D,IAAQ,EAAM,WAAW,SAAS,IAAI,aAAa;mBAF3H;UAIE,kBAAC,GAAD;WACE,WAAW,GAAG,EAAW,EAAG,MAAM;qBAEjC,EAAG;WACE,CAAA;UACR,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,QAAD;YAAM,WAAU;sBAAoC,EAAG;YAAiB,CAAA,EACxE,kBAAC,QAAD;YAAM,WAAU;sBAA+B,EAAG;YAAmB,CAAA,CACjE;;UACN,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,WAAU;WACV,cAAY,QAAQ,EAAG,KAAK;WAC5B,eAAe,KAAK,EAAW,EAAG,KAAK;qBAEtC,MAAW,EAAG,OACb,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,GAEtD,kBAAC,GAAD,EAAM,WAAU,+BAAgC,CAAA;WAE3C,CAAA;UACL;WAzBC,EAAG,KAyBJ,CACN;QACU,CAAA,CACT;SAzCI,EAAM,GAyCV,CACP;MAED,EAAe,WAAW,KACzB,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QACE,cAAa;QACb,OAAM;QACN,aAAY;QACZ,CAAA;OACE,CAAA;MAEJ;OACF;;GACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"HiringApplicationsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringApplicationsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\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, X } from 'lucide-react';\nimport { useSearchParams } from 'react-router-dom';\nimport { useCrewFoundryStore, type Candidate } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\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 {formatEnumLabel(c.status)}\n </Badge>\n ),\n },\n];\n\nexport function HiringApplicationsPagePage() {\n const { candidates, jobPostings } = useCrewFoundryStore();\n const [searchParams, setSearchParams] = useSearchParams();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n // Optional role scope from the Job Detail \"View Applications\" deep-link.\n const positionFilter = searchParams.get('position');\n const positionKey = positionFilter?.trim().toLowerCase() ?? null;\n const scoped = positionKey\n ? candidates.filter((c) => c.position.trim().toLowerCase() === positionKey)\n : candidates;\n\n const clearPositionFilter = () => {\n const next = new URLSearchParams(searchParams);\n next.delete('position');\n setSearchParams(next, { replace: true });\n setPage(1);\n };\n\n const newApps = scoped.filter((c) => c.status === 'new');\n const screeningApps = scoped.filter((c) => c.status === 'screening');\n const interviewApps = scoped.filter((c) => c.status === 'interview');\n const hiredCount = scoped.filter((c) => c.status === 'hired').length;\n const totalApps = positionKey\n ? scoped.length\n : 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 = scoped.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 {positionFilter\n ? `${scoped.length} applications for ${positionFilter}`\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 {positionFilter && (\n <Button variant=\"outline\" size=\"sm\" onClick={clearPositionFilter}>\n <X className=\"mr-1 h-4 w-4\" />\n {positionFilter}\n </Button>\n )}\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 {formatEnumLabel(job.status)}\n </Badge>\n </div>\n ))\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>{positionFilter ? `Applicants — ${positionFilter}` : 'All Candidates'}</CardTitle>\n <CardDescription>\n {positionFilter ? 'Candidates who applied to this role' : 'Complete candidate pipeline'}\n </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 {scoped.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={scoped.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 Button,\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, X } from 'lucide-react';\nimport { useSearchParams } from 'react-router-dom';\nimport { useCrewFoundryStore, type Candidate } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\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 {formatEnumLabel(c.status)}\n </Badge>\n ),\n },\n];\n\nexport function HiringApplicationsPagePage() {\n const { candidates, jobPostings } = useCrewFoundryStore();\n const [searchParams, setSearchParams] = useSearchParams();\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 25 });\n\n // Optional role scope from the Job Detail \"View Applications\" deep-link.\n const positionFilter = searchParams.get('position');\n const positionKey = positionFilter?.trim().toLowerCase() ?? null;\n const scoped = positionKey\n ? candidates.filter((c) => c.position.trim().toLowerCase() === positionKey)\n : candidates;\n\n const clearPositionFilter = () => {\n const next = new URLSearchParams(searchParams);\n next.delete('position');\n setSearchParams(next, { replace: true });\n setPage(1);\n };\n\n const newApps = scoped.filter((c) => c.status === 'new');\n const screeningApps = scoped.filter((c) => c.status === 'screening');\n const interviewApps = scoped.filter((c) => c.status === 'interview');\n const hiredCount = scoped.filter((c) => c.status === 'hired').length;\n const totalApps = positionKey ? scoped.length : 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 = scoped.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 {positionFilter\n ? `${scoped.length} applications for ${positionFilter}`\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 {positionFilter && (\n <Button variant=\"outline\" size=\"sm\" onClick={clearPositionFilter}>\n <X className=\"mr-1 h-4 w-4\" />\n {positionFilter}\n </Button>\n )}\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 {formatEnumLabel(job.status)}\n </Badge>\n </div>\n ))\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>\n {positionFilter ? `Applicants — ${positionFilter}` : 'All Candidates'}\n </CardTitle>\n <CardDescription>\n {positionFilter ? 'Candidates who applied to this role' : 'Complete candidate pipeline'}\n </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 {scoped.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={scoped.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 { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n CardContent,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\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 { ArrowRightLeft, LayoutGrid, Pencil, Plus, Search, Table2, 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';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { KanbanBoard, RatingStars, getInitials, type KanbanColumnData } from '../components/viz';\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\n// Ordered pipeline stages (mock: New → Screening → Interview → Assessment → Offer → Hired),\n// with Rejected kept as a terminal column so rejected candidates stay visible.\nconst PIPELINE_STAGES: { key: Candidate['status']; label: string; accent: string }[] = [\n { key: 'new', label: 'New', accent: 'var(--color-status-info-text)' },\n { key: 'screening', label: 'Screening', accent: 'var(--color-accent-blue)' },\n { key: 'interview', label: 'Interview', accent: 'var(--color-accent-purple)' },\n { key: 'assessment', label: 'Assessment', accent: 'var(--color-status-warning-text)' },\n { key: 'offer', label: 'Offer', accent: 'var(--color-accent-teal)' },\n { key: 'hired', label: 'Hired', accent: 'var(--color-status-success-text)' },\n { key: 'rejected', label: 'Rejected', accent: 'var(--color-status-error-text)' },\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\n/** Initials avatar (CrewCandidate has no photo field — initials fallback). */\nfunction InitialsAvatar({ name, size = 36 }: { name: string; size?: number }) {\n return (\n <span\n className=\"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\nconst CANDIDATE_COLUMNS: ResponsiveTableColumn<Candidate>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (candidate) => (\n <div className=\"flex items-center gap-2\">\n <InitialsAvatar name={`${candidate.firstName} ${candidate.lastName}`} size={30} />\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 </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) => <RatingStars value={candidate.rating} size={13} />,\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' || candidate.status === 'offer'\n ? 'default'\n : candidate.status === 'rejected'\n ? 'destructive'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(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 [view, setView] = useState<'board' | 'table'>('board');\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 handleMove = async (candidate: Candidate, status: Candidate['status']) => {\n if (candidate.status === status) {\n return;\n }\n try {\n await updateCandidate(candidate.id, { status });\n showToast(\n `${candidate.firstName} ${candidate.lastName} moved to ${formatEnumLabel(status)}`,\n 'success'\n );\n } catch {\n showToast('Could not move candidate', 'error');\n }\n };\n\n const searchLower = search.toLowerCase();\n const searched = useMemo(\n () =>\n candidates.filter((c) =>\n `${c.firstName} ${c.lastName} ${c.position} ${c.email}`.toLowerCase().includes(searchLower)\n ),\n [candidates, searchLower]\n );\n\n const tableFiltered = searched.filter(\n (c) => filterStatus === 'all' || c.status === filterStatus\n );\n const paged = tableFiltered.slice(offset, offset + pageSize);\n\n const columns: KanbanColumnData[] = PIPELINE_STAGES.map((stage) => {\n const stageCandidates = searched\n .filter((c) => c.status === stage.key)\n .sort((a, b) => b.rating - a.rating);\n return {\n id: stage.key,\n title: stage.label,\n accent: stage.accent,\n items: stageCandidates.map((candidate) => (\n <CandidateCard\n key={candidate.id}\n candidate={candidate}\n onMove={handleMove}\n onEdit={() => setEditTarget(candidate)}\n onDelete={() => setDeleteTarget(candidate)}\n />\n )),\n };\n });\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Candidate Pipeline</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{candidates.length} candidates in pipeline</p>\n <PagePurpose className=\"mt-1\">\n Track every candidate as they move through your hiring stages — drag work through New,\n Screening, Interview, Assessment, Offer, and Hired to keep the pipeline flowing.\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 flex-wrap items-center gap-3\">\n <div className=\"relative flex-1 min-w-[12rem] 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=\"inline-flex items-center gap-1 rounded-full border border-border-subtle bg-bg-sunken p-1\">\n <Button\n variant={view === 'board' ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"rounded-full\"\n onClick={() => setView('board')}\n >\n <LayoutGrid className=\"h-4 w-4 mr-1\" />\n Board\n </Button>\n <Button\n variant={view === 'table' ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"rounded-full\"\n onClick={() => setView('table')}\n >\n <Table2 className=\"h-4 w-4 mr-1\" />\n Table\n </Button>\n </div>\n </div>\n\n {candidates.length === 0 ? (\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <CardContent className=\"p-4\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No candidates yet\"\n description=\"Add your first candidate to start building the pipeline.\"\n />\n </CardContent>\n </GlassCard>\n ) : view === 'board' ? (\n <KanbanBoard columns={columns} emptyColumnHint=\"No candidates in this stage\" />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"p-4\">\n <div className=\"mb-3 flex flex-wrap 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 {PIPELINE_STAGES.map((stage) => (\n <Button\n key={stage.key}\n variant={filterStatus === stage.key ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n setFilterStatus(stage.key);\n setPage(1);\n }}\n >\n {stage.label}\n </Button>\n ))}\n </div>\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 {tableFiltered.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={tableFiltered.length}\n onPageChange={setPage}\n />\n </div>\n )}\n </CardContent>\n </GlassCard>\n )}\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\ninterface CandidateCardProps {\n candidate: Candidate;\n onMove: (candidate: Candidate, status: Candidate['status']) => void;\n onEdit: () => void;\n onDelete: () => void;\n}\n\nfunction CandidateCard({ candidate, onMove, onEdit, onDelete }: CandidateCardProps) {\n const fullName = `${candidate.firstName} ${candidate.lastName}`;\n return (\n <div className=\"group rounded-[var(--radius-input)] border border-border-subtle bg-bg-surface p-3 shadow-elevation-1 transition-shadow hover:shadow-elevation-2\">\n <div className=\"flex items-start gap-2.5\">\n <InitialsAvatar name={fullName} size={36} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{fullName}</p>\n <p className=\"truncate text-xs text-text-secondary\">{candidate.position}</p>\n </div>\n </div>\n <div className=\"mt-2 flex items-center justify-between gap-2\">\n <RatingStars value={candidate.rating} size={13} />\n <Badge variant=\"outline\" className=\"shrink-0 text-[9px]\">\n {candidate.source}\n </Badge>\n </div>\n {candidate.skills.length > 0 && (\n <div className=\"mt-2 flex flex-wrap gap-1\">\n {candidate.skills.slice(0, 3).map((skill) => (\n <Badge key={skill} variant=\"secondary\" className=\"text-[9px]\">\n {skill}\n </Badge>\n ))}\n </div>\n )}\n <div className=\"mt-2.5 flex items-center justify-between gap-1 border-t border-border-subtle pt-2\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"sm\" className=\"h-7 px-2 text-xs\">\n <ArrowRightLeft className=\"mr-1 h-3.5 w-3.5\" />\n Move\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuLabel>Move to stage</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {PIPELINE_STAGES.filter((stage) => stage.key !== candidate.status).map((stage) => (\n <DropdownMenuItem key={stage.key} onSelect={() => onMove(candidate, stage.key)}>\n <span\n aria-hidden=\"true\"\n className=\"mr-2 inline-block h-2 w-2 rounded-full\"\n style={{ backgroundColor: stage.accent }}\n />\n {stage.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n <div className=\"flex items-center gap-0.5\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 w-7 p-0\"\n aria-label={`Edit ${fullName}`}\n onClick={onEdit}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 w-7 p-0\"\n aria-label={`Delete ${fullName}`}\n onClick={onDelete}\n >\n <Trash2 className=\"h-3.5 w-3.5 text-status-error-fg\" />\n </Button>\n </div>\n </div>\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 { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n CardContent,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\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 { ArrowRightLeft, LayoutGrid, Pencil, Plus, Search, Table2, 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';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { KanbanBoard, RatingStars, getInitials, type KanbanColumnData } from '../components/viz';\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\n// Ordered pipeline stages (mock: New → Screening → Interview → Assessment → Offer → Hired),\n// with Rejected kept as a terminal column so rejected candidates stay visible.\nconst PIPELINE_STAGES: { key: Candidate['status']; label: string; accent: string }[] = [\n { key: 'new', label: 'New', accent: 'var(--color-status-info-text)' },\n { key: 'screening', label: 'Screening', accent: 'var(--color-accent-blue)' },\n { key: 'interview', label: 'Interview', accent: 'var(--color-accent-purple)' },\n { key: 'assessment', label: 'Assessment', accent: 'var(--color-status-warning-text)' },\n { key: 'offer', label: 'Offer', accent: 'var(--color-accent-teal)' },\n { key: 'hired', label: 'Hired', accent: 'var(--color-status-success-text)' },\n { key: 'rejected', label: 'Rejected', accent: 'var(--color-status-error-text)' },\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\n/** Initials avatar (CrewCandidate has no photo field — initials fallback). */\nfunction InitialsAvatar({ name, size = 36 }: { name: string; size?: number }) {\n return (\n <span\n className=\"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\nconst CANDIDATE_COLUMNS: ResponsiveTableColumn<Candidate>[] = [\n {\n key: 'name',\n header: 'Name',\n priority: 'primary',\n cell: (candidate) => (\n <div className=\"flex items-center gap-2\">\n <InitialsAvatar name={`${candidate.firstName} ${candidate.lastName}`} size={30} />\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 </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) => <RatingStars value={candidate.rating} size={13} />,\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' || candidate.status === 'offer'\n ? 'default'\n : candidate.status === 'rejected'\n ? 'destructive'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(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 [view, setView] = useState<'board' | 'table'>('board');\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 handleMove = async (candidate: Candidate, status: Candidate['status']) => {\n if (candidate.status === status) {\n return;\n }\n try {\n await updateCandidate(candidate.id, { status });\n showToast(\n `${candidate.firstName} ${candidate.lastName} moved to ${formatEnumLabel(status)}`,\n 'success'\n );\n } catch {\n showToast('Could not move candidate', 'error');\n }\n };\n\n const searchLower = search.toLowerCase();\n const searched = useMemo(\n () =>\n candidates.filter((c) =>\n `${c.firstName} ${c.lastName} ${c.position} ${c.email}`.toLowerCase().includes(searchLower)\n ),\n [candidates, searchLower]\n );\n\n const tableFiltered = searched.filter((c) => filterStatus === 'all' || c.status === filterStatus);\n const paged = tableFiltered.slice(offset, offset + pageSize);\n\n const columns: KanbanColumnData[] = PIPELINE_STAGES.map((stage) => {\n const stageCandidates = searched\n .filter((c) => c.status === stage.key)\n .sort((a, b) => b.rating - a.rating);\n return {\n id: stage.key,\n title: stage.label,\n accent: stage.accent,\n items: stageCandidates.map((candidate) => (\n <CandidateCard\n key={candidate.id}\n candidate={candidate}\n onMove={handleMove}\n onEdit={() => setEditTarget(candidate)}\n onDelete={() => setDeleteTarget(candidate)}\n />\n )),\n };\n });\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Candidate Pipeline</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">{candidates.length} candidates in pipeline</p>\n <PagePurpose className=\"mt-1\">\n Track every candidate as they move through your hiring stages — drag work through New,\n Screening, Interview, Assessment, Offer, and Hired to keep the pipeline flowing.\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 flex-wrap items-center gap-3\">\n <div className=\"relative flex-1 min-w-[12rem] 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=\"inline-flex items-center gap-1 rounded-full border border-border-subtle bg-bg-sunken p-1\">\n <Button\n variant={view === 'board' ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"rounded-full\"\n onClick={() => setView('board')}\n >\n <LayoutGrid className=\"h-4 w-4 mr-1\" />\n Board\n </Button>\n <Button\n variant={view === 'table' ? 'default' : 'ghost'}\n size=\"sm\"\n className=\"rounded-full\"\n onClick={() => setView('table')}\n >\n <Table2 className=\"h-4 w-4 mr-1\" />\n Table\n </Button>\n </div>\n </div>\n\n {candidates.length === 0 ? (\n <GlassCard treatment=\"glass\" className=\"border-border-seam\">\n <CardContent className=\"p-4\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No candidates yet\"\n description=\"Add your first candidate to start building the pipeline.\"\n />\n </CardContent>\n </GlassCard>\n ) : view === 'board' ? (\n <KanbanBoard columns={columns} emptyColumnHint=\"No candidates in this stage\" />\n ) : (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"p-4\">\n <div className=\"mb-3 flex flex-wrap 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 {PIPELINE_STAGES.map((stage) => (\n <Button\n key={stage.key}\n variant={filterStatus === stage.key ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => {\n setFilterStatus(stage.key);\n setPage(1);\n }}\n >\n {stage.label}\n </Button>\n ))}\n </div>\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 {tableFiltered.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={tableFiltered.length}\n onPageChange={setPage}\n />\n </div>\n )}\n </CardContent>\n </GlassCard>\n )}\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\ninterface CandidateCardProps {\n candidate: Candidate;\n onMove: (candidate: Candidate, status: Candidate['status']) => void;\n onEdit: () => void;\n onDelete: () => void;\n}\n\nfunction CandidateCard({ candidate, onMove, onEdit, onDelete }: CandidateCardProps) {\n const fullName = `${candidate.firstName} ${candidate.lastName}`;\n return (\n <div className=\"group rounded-[var(--radius-input)] border border-border-subtle bg-bg-surface p-3 shadow-elevation-1 transition-shadow hover:shadow-elevation-2\">\n <div className=\"flex items-start gap-2.5\">\n <InitialsAvatar name={fullName} size={36} />\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{fullName}</p>\n <p className=\"truncate text-xs text-text-secondary\">{candidate.position}</p>\n </div>\n </div>\n <div className=\"mt-2 flex items-center justify-between gap-2\">\n <RatingStars value={candidate.rating} size={13} />\n <Badge variant=\"outline\" className=\"shrink-0 text-[9px]\">\n {candidate.source}\n </Badge>\n </div>\n {candidate.skills.length > 0 && (\n <div className=\"mt-2 flex flex-wrap gap-1\">\n {candidate.skills.slice(0, 3).map((skill) => (\n <Badge key={skill} variant=\"secondary\" className=\"text-[9px]\">\n {skill}\n </Badge>\n ))}\n </div>\n )}\n <div className=\"mt-2.5 flex items-center justify-between gap-1 border-t border-border-subtle pt-2\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"sm\" className=\"h-7 px-2 text-xs\">\n <ArrowRightLeft className=\"mr-1 h-3.5 w-3.5\" />\n Move\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\">\n <DropdownMenuLabel>Move to stage</DropdownMenuLabel>\n <DropdownMenuSeparator />\n {PIPELINE_STAGES.filter((stage) => stage.key !== candidate.status).map((stage) => (\n <DropdownMenuItem key={stage.key} onSelect={() => onMove(candidate, stage.key)}>\n <span\n aria-hidden=\"true\"\n className=\"mr-2 inline-block h-2 w-2 rounded-full\"\n style={{ backgroundColor: stage.accent }}\n />\n {stage.label}\n </DropdownMenuItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n <div className=\"flex items-center gap-0.5\">\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 w-7 p-0\"\n aria-label={`Edit ${fullName}`}\n onClick={onEdit}\n >\n <Pencil className=\"h-3.5 w-3.5\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-7 w-7 p-0\"\n aria-label={`Delete ${fullName}`}\n onClick={onDelete}\n >\n <Trash2 className=\"h-3.5 w-3.5 text-status-error-fg\" />\n </Button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"HiringCreateJobPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringCreateJobPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\nimport {\n Button,\n GlassCard,\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, ChevronRight, 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 FIELD_LABEL_CLASS = 'text-sm font-medium text-text-primary';\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\nfunction FieldLabel({ htmlFor, children }: { htmlFor: string; children: ReactNode }) {\n return (\n <label className={FIELD_LABEL_CLASS} htmlFor={htmlFor}>\n {children}\n </label>\n );\n}\n\nexport function HiringCreateJobPagePage() {\n const navigate = useNavigate();\n const { showToast } = useGlobalUi();\n const addJobPosting = useCrewFoundryStore((state) => state.addJobPosting);\n const departments = useCrewFoundryStore((state) => state.departments);\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 departmentNames = useMemo(\n () => Array.from(new Set(departments.map((d) => d.name).filter(Boolean))).sort(),\n [departments]\n );\n\n const addRequirement = () => setRequirements((prev) => [...prev, '']);\n const addBenefit = () => setBenefits((prev) => [...prev, '']);\n const updateRequirement = (idx: number, value: string) =>\n setRequirements((prev) => prev.map((item, i) => (i === idx ? value : item)));\n const updateBenefit = (idx: number, value: string) =>\n setBenefits((prev) => prev.map((item, i) => (i === idx ? value : item)));\n const removeRequirement = (idx: number) =>\n setRequirements((prev) => prev.filter((_, i) => i !== idx));\n const removeBenefit = (idx: number) => setBenefits((prev) => prev.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('../hiring/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 const renderChipRows = (\n items: string[],\n update: (idx: number, value: string) => void,\n remove: (idx: number) => void,\n add: () => void,\n kind: 'Requirement' | 'Benefit',\n placeholder: string\n ) => (\n <div className=\"space-y-2\">\n {items.map((value, idx) => (\n <div key={idx} className=\"relative\">\n <Input\n className=\"pr-9\"\n placeholder={placeholder}\n value={value}\n onChange={(e) => update(idx, e.target.value)}\n />\n <button\n type=\"button\"\n aria-label={`Remove ${kind.toLowerCase()} ${idx + 1}`}\n onClick={() => remove(idx)}\n className=\"absolute right-2.5 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ))}\n <Button\n variant=\"link\"\n size=\"sm\"\n className=\"h-auto p-0\"\n onClick={add}\n type=\"button\"\n >\n <Plus className=\"h-4 w-4 mr-1\" />\n Add {kind}\n </Button>\n </div>\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"space-y-2\">\n <nav\n aria-label=\"Breadcrumb\"\n className=\"flex items-center gap-1 text-sm text-text-secondary\"\n >\n <button\n type=\"button\"\n onClick={() => navigate('../hiring/jobs-page')}\n className=\"rounded-sm transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:text-text-primary\"\n >\n Hiring\n </button>\n <ChevronRight className=\"h-3.5 w-3.5\" />\n <span className=\"text-text-primary\">Post New Job</span>\n </nav>\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Create Job Posting</GradientText>\n </h1>\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 <GlassCard treatment=\"glass\" className=\"max-w-5xl p-6\">\n <div className=\"mb-6 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Briefcase className=\"h-5 w-5\" />\n Job details\n </div>\n\n <div className=\"space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-title\">Job Title</FieldLabel>\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 <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-type\">Employment Type</FieldLabel>\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>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-department\">Department</FieldLabel>\n <Input\n id=\"job-department\"\n list=\"crewfoundry-department-options\"\n placeholder=\"e.g. Engineering\"\n value={department}\n onChange={(e) => setDepartment(e.target.value)}\n />\n <datalist id=\"crewfoundry-department-options\">\n {departmentNames.map((name) => (\n <option key={name} value={name} />\n ))}\n </datalist>\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-experience\">Experience Level</FieldLabel>\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 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-location\">Location</FieldLabel>\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 className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-salary-min\">Salary Min</FieldLabel>\n <div className=\"relative\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-secondary\">\n $\n </span>\n <Input\n id=\"job-salary-min\"\n type=\"number\"\n className=\"pl-7\"\n placeholder=\"120,000\"\n value={salaryMin}\n onChange={(e) => setSalaryMin(e.target.value)}\n />\n </div>\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-salary-max\">Salary Max</FieldLabel>\n <div className=\"relative\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-secondary\">\n $\n </span>\n <Input\n id=\"job-salary-max\"\n type=\"number\"\n className=\"pl-7\"\n placeholder=\"180,000\"\n value={salaryMax}\n onChange={(e) => setSalaryMax(e.target.value)}\n />\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-deadline\">Application Deadline</FieldLabel>\n <Input\n id=\"job-deadline\"\n type=\"date\"\n value={deadline}\n onChange={(e) => setDeadline(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-description\">Description</FieldLabel>\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\n <div className=\"grid grid-cols-1 gap-6 border-t border-border-subtle pt-5 md:grid-cols-2\">\n <div className=\"space-y-3\">\n <div>\n <div className={FIELD_LABEL_CLASS}>Requirements</div>\n <p className=\"text-xs text-text-secondary\">List the key requirements for this role</p>\n </div>\n {renderChipRows(\n requirements,\n updateRequirement,\n removeRequirement,\n addRequirement,\n 'Requirement',\n 'e.g. 5+ years with React'\n )}\n </div>\n <div className=\"space-y-3\">\n <div>\n <div className={FIELD_LABEL_CLASS}>Benefits</div>\n <p className=\"text-xs text-text-secondary\">What does this position offer?</p>\n </div>\n {renderChipRows(\n benefits,\n updateBenefit,\n removeBenefit,\n addBenefit,\n 'Benefit',\n 'e.g. Flexible remote work'\n )}\n </div>\n </div>\n\n <div className=\"flex flex-col-reverse justify-end gap-2 border-t border-border-subtle pt-5 sm:flex-row\">\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 </GlassCard>\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 { useMemo, useState, type ReactNode } from 'react';\nimport {\n Button,\n GlassCard,\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, ChevronRight, 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 FIELD_LABEL_CLASS = 'text-sm font-medium text-text-primary';\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\nfunction FieldLabel({ htmlFor, children }: { htmlFor: string; children: ReactNode }) {\n return (\n <label className={FIELD_LABEL_CLASS} htmlFor={htmlFor}>\n {children}\n </label>\n );\n}\n\nexport function HiringCreateJobPagePage() {\n const navigate = useNavigate();\n const { showToast } = useGlobalUi();\n const addJobPosting = useCrewFoundryStore((state) => state.addJobPosting);\n const departments = useCrewFoundryStore((state) => state.departments);\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 departmentNames = useMemo(\n () => Array.from(new Set(departments.map((d) => d.name).filter(Boolean))).sort(),\n [departments]\n );\n\n const addRequirement = () => setRequirements((prev) => [...prev, '']);\n const addBenefit = () => setBenefits((prev) => [...prev, '']);\n const updateRequirement = (idx: number, value: string) =>\n setRequirements((prev) => prev.map((item, i) => (i === idx ? value : item)));\n const updateBenefit = (idx: number, value: string) =>\n setBenefits((prev) => prev.map((item, i) => (i === idx ? value : item)));\n const removeRequirement = (idx: number) =>\n setRequirements((prev) => prev.filter((_, i) => i !== idx));\n const removeBenefit = (idx: number) => setBenefits((prev) => prev.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('../hiring/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 const renderChipRows = (\n items: string[],\n update: (idx: number, value: string) => void,\n remove: (idx: number) => void,\n add: () => void,\n kind: 'Requirement' | 'Benefit',\n placeholder: string\n ) => (\n <div className=\"space-y-2\">\n {items.map((value, idx) => (\n <div key={idx} className=\"relative\">\n <Input\n className=\"pr-9\"\n placeholder={placeholder}\n value={value}\n onChange={(e) => update(idx, e.target.value)}\n />\n <button\n type=\"button\"\n aria-label={`Remove ${kind.toLowerCase()} ${idx + 1}`}\n onClick={() => remove(idx)}\n className=\"absolute right-2.5 top-1/2 -translate-y-1/2 rounded-sm p-0.5 text-text-secondary transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:text-text-primary\"\n >\n <X className=\"h-4 w-4\" />\n </button>\n </div>\n ))}\n <Button variant=\"link\" size=\"sm\" className=\"h-auto p-0\" onClick={add} type=\"button\">\n <Plus className=\"h-4 w-4 mr-1\" />\n Add {kind}\n </Button>\n </div>\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"space-y-2\">\n <nav\n aria-label=\"Breadcrumb\"\n className=\"flex items-center gap-1 text-sm text-text-secondary\"\n >\n <button\n type=\"button\"\n onClick={() => navigate('../hiring/jobs-page')}\n className=\"rounded-sm transition-colors hover:text-text-primary focus-visible:outline-none focus-visible:text-text-primary\"\n >\n Hiring\n </button>\n <ChevronRight className=\"h-3.5 w-3.5\" />\n <span className=\"text-text-primary\">Post New Job</span>\n </nav>\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Create Job Posting</GradientText>\n </h1>\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 <GlassCard treatment=\"glass\" className=\"max-w-5xl p-6\">\n <div className=\"mb-6 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Briefcase className=\"h-5 w-5\" />\n Job details\n </div>\n\n <div className=\"space-y-5\">\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-title\">Job Title</FieldLabel>\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 <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-type\">Employment Type</FieldLabel>\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>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-department\">Department</FieldLabel>\n <Input\n id=\"job-department\"\n list=\"crewfoundry-department-options\"\n placeholder=\"e.g. Engineering\"\n value={department}\n onChange={(e) => setDepartment(e.target.value)}\n />\n <datalist id=\"crewfoundry-department-options\">\n {departmentNames.map((name) => (\n <option key={name} value={name} />\n ))}\n </datalist>\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-experience\">Experience Level</FieldLabel>\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 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-location\">Location</FieldLabel>\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 className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-salary-min\">Salary Min</FieldLabel>\n <div className=\"relative\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-secondary\">\n $\n </span>\n <Input\n id=\"job-salary-min\"\n type=\"number\"\n className=\"pl-7\"\n placeholder=\"120,000\"\n value={salaryMin}\n onChange={(e) => setSalaryMin(e.target.value)}\n />\n </div>\n </div>\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-salary-max\">Salary Max</FieldLabel>\n <div className=\"relative\">\n <span className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-sm text-text-secondary\">\n $\n </span>\n <Input\n id=\"job-salary-max\"\n type=\"number\"\n className=\"pl-7\"\n placeholder=\"180,000\"\n value={salaryMax}\n onChange={(e) => setSalaryMax(e.target.value)}\n />\n </div>\n </div>\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-5 md:grid-cols-2\">\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-deadline\">Application Deadline</FieldLabel>\n <Input\n id=\"job-deadline\"\n type=\"date\"\n value={deadline}\n onChange={(e) => setDeadline(e.target.value)}\n />\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <FieldLabel htmlFor=\"job-description\">Description</FieldLabel>\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\n <div className=\"grid grid-cols-1 gap-6 border-t border-border-subtle pt-5 md:grid-cols-2\">\n <div className=\"space-y-3\">\n <div>\n <div className={FIELD_LABEL_CLASS}>Requirements</div>\n <p className=\"text-xs text-text-secondary\">\n List the key requirements for this role\n </p>\n </div>\n {renderChipRows(\n requirements,\n updateRequirement,\n removeRequirement,\n addRequirement,\n 'Requirement',\n 'e.g. 5+ years with React'\n )}\n </div>\n <div className=\"space-y-3\">\n <div>\n <div className={FIELD_LABEL_CLASS}>Benefits</div>\n <p className=\"text-xs text-text-secondary\">What does this position offer?</p>\n </div>\n {renderChipRows(\n benefits,\n updateBenefit,\n removeBenefit,\n addBenefit,\n 'Benefit',\n 'e.g. Flexible remote work'\n )}\n </div>\n </div>\n\n <div className=\"flex flex-col-reverse justify-end gap-2 border-t border-border-subtle pt-5 sm:flex-row\">\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 </GlassCard>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"HiringJobDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { useState } from 'react';\nimport { Briefcase, Calendar, CheckCircle, MapPin, Pencil, Share2, Trash2, Users } from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore, type Candidate, type JobPosting } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getTags, getText } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { HBarFunnel, RatingStars, getInitials, type LabeledValue } from '../components/viz';\n\nconst JOB_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Job Title', type: 'text', required: true, colSpan: 2 },\n { name: 'department', label: 'Department', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n {\n name: 'type',\n label: 'Employment Type',\n type: 'select',\n options: [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n { label: 'Remote', value: 'remote' },\n ],\n },\n {\n name: 'experienceLevel',\n label: 'Experience Level',\n type: 'select',\n options: [\n { label: 'Entry', value: 'entry' },\n { label: 'Mid', value: 'mid' },\n { label: 'Senior', value: 'senior' },\n { label: 'Lead', value: 'lead' },\n { label: 'Executive', value: 'executive' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Active', value: 'active' },\n { label: 'Paused', value: 'paused' },\n { label: 'Closed', value: 'closed' },\n { label: 'Filled', value: 'filled' },\n ],\n },\n { name: 'salaryMin', label: 'Salary Min ($)', type: 'number' },\n { name: 'salaryMax', label: 'Salary Max ($)', type: 'number' },\n { name: 'deadline', label: 'Application Deadline', type: 'date' },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n { name: 'requirements', label: 'Requirements', type: 'tags' },\n { name: 'benefits', label: 'Benefits', type: 'tags' },\n];\n\nconst STAGE_BADGE: Record<Candidate['status'], 'default' | 'secondary' | 'destructive' | 'outline'> = {\n new: 'outline',\n screening: 'secondary',\n interview: 'secondary',\n assessment: 'secondary',\n offer: 'default',\n hired: 'default',\n rejected: 'destructive',\n};\n\nfunction InitialsAvatar({ name, size = 32 }: { name: string; size?: number }) {\n return (\n <span\n className=\"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\nexport function HiringJobDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { showToast } = useGlobalUi();\n const { jobPostings, candidates, updateJobPosting, removeJobPosting, initialized } =\n useCrewFoundryStore();\n const jobId = searchParams.get('id');\n const job = jobId ? jobPostings.find((j) => j.id === jobId) : jobPostings[0];\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n\n if (!initialized && !job) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!job) {\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job posting found\"\n description=\"This job posting is no longer available.\"\n />\n </div>\n );\n }\n\n // Applicants for this role. CrewCandidate has no jobId link, so match on the\n // (normalized) position title — the only real linkage the schema exposes.\n const titleKey = job.title.trim().toLowerCase();\n const jobCandidates = candidates.filter((c) => c.position.trim().toLowerCase() === titleKey);\n const countBy = (status: Candidate['status']) =>\n jobCandidates.filter((c) => c.status === status).length;\n\n const interviewCount = countBy('interview');\n\n // Real-data hiring funnel: \"Applied\" = everyone who applied to this role, then\n // the count currently at each downstream stage (offer folds in hired).\n const funnelStages: LabeledValue[] = [\n { label: 'Applied', value: jobCandidates.length },\n { label: 'Screening', value: countBy('screening') },\n { label: 'Interview', value: interviewCount },\n { label: 'Assessment', value: countBy('assessment') },\n { label: 'Offer', value: countBy('offer') + countBy('hired') },\n ];\n\n const editInitial: FormValues = {\n title: job.title,\n department: job.department,\n location: job.location,\n type: job.type,\n experienceLevel: job.experienceLevel,\n status: job.status,\n salaryMin: job.salaryMin,\n salaryMax: job.salaryMax,\n deadline: job.deadline,\n description: job.description,\n requirements: job.requirements,\n benefits: job.benefits,\n };\n\n const handleEditSubmit = async (values: FormValues) => {\n const updates: Partial<JobPosting> = {\n title: getText(values, 'title'),\n department: getText(values, 'department'),\n location: getText(values, 'location'),\n type: values.type as JobPosting['type'],\n experienceLevel: values.experienceLevel as JobPosting['experienceLevel'],\n status: values.status as JobPosting['status'],\n salaryMin: getNumber(values, 'salaryMin'),\n salaryMax: getNumber(values, 'salaryMax'),\n deadline: getText(values, 'deadline'),\n description: getText(values, 'description'),\n requirements: getTags(values, 'requirements'),\n benefits: getTags(values, 'benefits'),\n };\n await updateJobPosting(job.id, updates);\n showToast('Job posting updated', 'success');\n };\n\n const handleDelete = async () => {\n await removeJobPosting(job.id);\n showToast('Job posting deleted', 'success');\n navigate('../hiring/jobs-page');\n };\n\n const handleShare = async () => {\n try {\n await navigator.clipboard.writeText(window.location.href);\n showToast('Job link copied to clipboard', 'success');\n } catch {\n showToast('Could not copy the job link', 'error');\n }\n };\n\n const viewApplications = () =>\n navigate(`../hiring/applications-page?position=${encodeURIComponent(job.title)}`);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"flex flex-col gap-4 p-6 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{job.title}</GradientText>\n </h1>\n <Badge variant={job.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Briefcase className=\"h-3.5 w-3.5\" />\n {job.department}\n </span>\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3.5 w-3.5\" />\n {job.location}\n </span>\n <span>{formatEnumLabel(job.type)}</span>\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {jobCandidates.length} applicants\n </Badge>\n {interviewCount > 0 && (\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {interviewCount} in interview\n </Badge>\n )}\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Posted {job.postedDate}\n </span>\n </div>\n <PagePurpose className=\"mt-3\">\n Everything about this role in one place — review applicants, requirements, and\n compensation to decide who to interview and keep the posting current.\n </PagePurpose>\n </div>\n\n <div className=\"flex shrink-0 flex-col items-start gap-3 lg:items-end\">\n <div className=\"text-2xl font-bold tabular-nums text-text-primary\">\n ${(job.salaryMin / 1000).toFixed(0)}k – ${(job.salaryMax / 1000).toFixed(0)}k\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleShare}>\n <Share2 className=\"mr-1 h-4 w-4\" />\n Share\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditOpen(true)}>\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n aria-label=\"Delete job posting\"\n onClick={() => setDeleteOpen(true)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n <Button size=\"sm\" onClick={viewApplications}>\n <Users className=\"mr-1 h-4 w-4\" />\n View Applications\n </Button>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n\n {/* Two-column body */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Left: description / requirements / benefits */}\n <div className=\"space-y-6 lg:col-span-2\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm leading-relaxed text-text-secondary\">{job.description}</p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Requirements</CardTitle>\n </CardHeader>\n <CardContent>\n {job.requirements.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No requirements listed.</p>\n ) : (\n <ul className=\"space-y-2\">\n {job.requirements.map((req, idx) => (\n <li key={idx} className=\"flex items-start gap-2 text-sm\">\n <CheckCircle className=\"mt-0.5 h-4 w-4 flex-shrink-0 text-status-success-text\" />\n {req}\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Benefits</CardTitle>\n </CardHeader>\n <CardContent>\n {job.benefits.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No benefits listed.</p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {job.benefits.map((ben, idx) => (\n <Badge key={idx} variant=\"outline\" className=\"rounded-full px-3 py-1 text-xs\">\n {ben}\n </Badge>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Right rail: candidates + funnel */}\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Candidates</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No applicants matched to this role yet.</p>\n ) : (\n <ul className=\"max-h-96 space-y-1 overflow-y-auto pr-1\">\n {jobCandidates.map((candidate) => (\n <li\n key={candidate.id}\n className=\"flex items-center gap-2 rounded-[var(--radius-input)] px-2 py-2 hover:bg-bg-sunken\"\n >\n <InitialsAvatar\n name={`${candidate.firstName} ${candidate.lastName}`}\n size={30}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-text-primary\">\n {candidate.firstName} {candidate.lastName}\n </span>\n <Badge variant={STAGE_BADGE[candidate.status]} className=\"shrink-0 text-[9px]\">\n {formatEnumLabel(candidate.status)}\n </Badge>\n <RatingStars value={candidate.rating} size={12} className=\"shrink-0\" />\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Hiring funnel</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n The funnel appears once candidates apply to this role.\n </p>\n ) : (\n <HBarFunnel\n stages={funnelStages}\n valueFormatter={(value) => String(value)}\n showConversion\n />\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={editOpen}\n onOpenChange={setEditOpen}\n title=\"Edit Job Posting\"\n description=\"Update the role details, compensation, and status.\"\n fields={JOB_FIELDS}\n initialValues={editInitial}\n submitLabel=\"Save changes\"\n onSubmit={handleEditSubmit}\n />\n <ConfirmDialog\n open={deleteOpen}\n onOpenChange={setDeleteOpen}\n title=\"Delete job posting\"\n description={`Permanently remove “${job.title}”? This cannot be undone.`}\n confirmLabel=\"Delete\"\n destructive\n onConfirm={handleDelete}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HiringJobDetailsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Spinner,\n} from '@burdenoff/fe-libs/ui';\nimport { useState } from 'react';\nimport {\n Briefcase,\n Calendar,\n CheckCircle,\n MapPin,\n Pencil,\n Share2,\n Trash2,\n Users,\n} from 'lucide-react';\nimport { useNavigate, useSearchParams } from 'react-router-dom';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useCrewFoundryStore, type Candidate, type JobPosting } from '../store/crewfoundryStore';\nimport { FormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getTags, getText } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { HBarFunnel, RatingStars, getInitials, type LabeledValue } from '../components/viz';\n\nconst JOB_FIELDS: FieldConfig[] = [\n { name: 'title', label: 'Job Title', type: 'text', required: true, colSpan: 2 },\n { name: 'department', label: 'Department', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n {\n name: 'type',\n label: 'Employment Type',\n type: 'select',\n options: [\n { label: 'Full time', value: 'full_time' },\n { label: 'Part time', value: 'part_time' },\n { label: 'Contract', value: 'contract' },\n { label: 'Remote', value: 'remote' },\n ],\n },\n {\n name: 'experienceLevel',\n label: 'Experience Level',\n type: 'select',\n options: [\n { label: 'Entry', value: 'entry' },\n { label: 'Mid', value: 'mid' },\n { label: 'Senior', value: 'senior' },\n { label: 'Lead', value: 'lead' },\n { label: 'Executive', value: 'executive' },\n ],\n },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Draft', value: 'draft' },\n { label: 'Active', value: 'active' },\n { label: 'Paused', value: 'paused' },\n { label: 'Closed', value: 'closed' },\n { label: 'Filled', value: 'filled' },\n ],\n },\n { name: 'salaryMin', label: 'Salary Min ($)', type: 'number' },\n { name: 'salaryMax', label: 'Salary Max ($)', type: 'number' },\n { name: 'deadline', label: 'Application Deadline', type: 'date' },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n { name: 'requirements', label: 'Requirements', type: 'tags' },\n { name: 'benefits', label: 'Benefits', type: 'tags' },\n];\n\nconst STAGE_BADGE: Record<\n Candidate['status'],\n 'default' | 'secondary' | 'destructive' | 'outline'\n> = {\n new: 'outline',\n screening: 'secondary',\n interview: 'secondary',\n assessment: 'secondary',\n offer: 'default',\n hired: 'default',\n rejected: 'destructive',\n};\n\nfunction InitialsAvatar({ name, size = 32 }: { name: string; size?: number }) {\n return (\n <span\n className=\"inline-flex shrink-0 items-center justify-center overflow-hidden rounded-full border border-border-subtle bg-bg-sunken font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n aria-hidden=\"true\"\n >\n {getInitials(name)}\n </span>\n );\n}\n\nexport function HiringJobDetailsPagePage() {\n const navigate = useNavigate();\n const [searchParams] = useSearchParams();\n const { showToast } = useGlobalUi();\n const { jobPostings, candidates, updateJobPosting, removeJobPosting, initialized } =\n useCrewFoundryStore();\n const jobId = searchParams.get('id');\n const job = jobId ? jobPostings.find((j) => j.id === jobId) : jobPostings[0];\n const [editOpen, setEditOpen] = useState(false);\n const [deleteOpen, setDeleteOpen] = useState(false);\n\n if (!initialized && !job) {\n return (\n <div className=\"flex min-h-[60vh] items-center justify-center p-6\">\n <Spinner size=\"lg\" />\n </div>\n );\n }\n\n if (!job) {\n return (\n <div className=\"p-6\">\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job posting found\"\n description=\"This job posting is no longer available.\"\n />\n </div>\n );\n }\n\n // Applicants for this role. CrewCandidate has no jobId link, so match on the\n // (normalized) position title — the only real linkage the schema exposes.\n const titleKey = job.title.trim().toLowerCase();\n const jobCandidates = candidates.filter((c) => c.position.trim().toLowerCase() === titleKey);\n const countBy = (status: Candidate['status']) =>\n jobCandidates.filter((c) => c.status === status).length;\n\n const interviewCount = countBy('interview');\n\n // Real-data hiring funnel: \"Applied\" = everyone who applied to this role, then\n // the count currently at each downstream stage (offer folds in hired).\n const funnelStages: LabeledValue[] = [\n { label: 'Applied', value: jobCandidates.length },\n { label: 'Screening', value: countBy('screening') },\n { label: 'Interview', value: interviewCount },\n { label: 'Assessment', value: countBy('assessment') },\n { label: 'Offer', value: countBy('offer') + countBy('hired') },\n ];\n\n const editInitial: FormValues = {\n title: job.title,\n department: job.department,\n location: job.location,\n type: job.type,\n experienceLevel: job.experienceLevel,\n status: job.status,\n salaryMin: job.salaryMin,\n salaryMax: job.salaryMax,\n deadline: job.deadline,\n description: job.description,\n requirements: job.requirements,\n benefits: job.benefits,\n };\n\n const handleEditSubmit = async (values: FormValues) => {\n const updates: Partial<JobPosting> = {\n title: getText(values, 'title'),\n department: getText(values, 'department'),\n location: getText(values, 'location'),\n type: values.type as JobPosting['type'],\n experienceLevel: values.experienceLevel as JobPosting['experienceLevel'],\n status: values.status as JobPosting['status'],\n salaryMin: getNumber(values, 'salaryMin'),\n salaryMax: getNumber(values, 'salaryMax'),\n deadline: getText(values, 'deadline'),\n description: getText(values, 'description'),\n requirements: getTags(values, 'requirements'),\n benefits: getTags(values, 'benefits'),\n };\n await updateJobPosting(job.id, updates);\n showToast('Job posting updated', 'success');\n };\n\n const handleDelete = async () => {\n await removeJobPosting(job.id);\n showToast('Job posting deleted', 'success');\n navigate('../hiring/jobs-page');\n };\n\n const handleShare = async () => {\n try {\n await navigator.clipboard.writeText(window.location.href);\n showToast('Job link copied to clipboard', 'success');\n } catch {\n showToast('Could not copy the job link', 'error');\n }\n };\n\n const viewApplications = () =>\n navigate(`../hiring/applications-page?position=${encodeURIComponent(job.title)}`);\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Header */}\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"flex flex-col gap-4 p-6 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0\">\n <div className=\"mb-1 flex flex-wrap items-center gap-2\">\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{job.title}</GradientText>\n </h1>\n <Badge variant={job.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Briefcase className=\"h-3.5 w-3.5\" />\n {job.department}\n </span>\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3.5 w-3.5\" />\n {job.location}\n </span>\n <span>{formatEnumLabel(job.type)}</span>\n </div>\n <div className=\"mt-2 flex flex-wrap items-center gap-2 text-xs text-text-secondary\">\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {jobCandidates.length} applicants\n </Badge>\n {interviewCount > 0 && (\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {interviewCount} in interview\n </Badge>\n )}\n <span className=\"flex items-center gap-1\">\n <Calendar className=\"h-3 w-3\" />\n Posted {job.postedDate}\n </span>\n </div>\n <PagePurpose className=\"mt-3\">\n Everything about this role in one place — review applicants, requirements, and\n compensation to decide who to interview and keep the posting current.\n </PagePurpose>\n </div>\n\n <div className=\"flex shrink-0 flex-col items-start gap-3 lg:items-end\">\n <div className=\"text-2xl font-bold tabular-nums text-text-primary\">\n ${(job.salaryMin / 1000).toFixed(0)}k – ${(job.salaryMax / 1000).toFixed(0)}k\n </div>\n <div className=\"flex flex-wrap gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleShare}>\n <Share2 className=\"mr-1 h-4 w-4\" />\n Share\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditOpen(true)}>\n <Pencil className=\"mr-1 h-4 w-4\" />\n Edit\n </Button>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n aria-label=\"Delete job posting\"\n onClick={() => setDeleteOpen(true)}\n >\n <Trash2 className=\"h-4 w-4\" />\n </Button>\n <Button size=\"sm\" onClick={viewApplications}>\n <Users className=\"mr-1 h-4 w-4\" />\n View Applications\n </Button>\n </div>\n </div>\n </CardContent>\n </GlassCard>\n\n {/* Two-column body */}\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Left: description / requirements / benefits */}\n <div className=\"space-y-6 lg:col-span-2\">\n <Card>\n <CardHeader>\n <CardTitle>Description</CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-sm leading-relaxed text-text-secondary\">{job.description}</p>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Requirements</CardTitle>\n </CardHeader>\n <CardContent>\n {job.requirements.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No requirements listed.</p>\n ) : (\n <ul className=\"space-y-2\">\n {job.requirements.map((req, idx) => (\n <li key={idx} className=\"flex items-start gap-2 text-sm\">\n <CheckCircle className=\"mt-0.5 h-4 w-4 flex-shrink-0 text-status-success-text\" />\n {req}\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Benefits</CardTitle>\n </CardHeader>\n <CardContent>\n {job.benefits.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No benefits listed.</p>\n ) : (\n <div className=\"flex flex-wrap gap-2\">\n {job.benefits.map((ben, idx) => (\n <Badge key={idx} variant=\"outline\" className=\"rounded-full px-3 py-1 text-xs\">\n {ben}\n </Badge>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Right rail: candidates + funnel */}\n <div className=\"space-y-6\">\n <Card>\n <CardHeader>\n <CardTitle>Candidates</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">No applicants matched to this role yet.</p>\n ) : (\n <ul className=\"max-h-96 space-y-1 overflow-y-auto pr-1\">\n {jobCandidates.map((candidate) => (\n <li\n key={candidate.id}\n className=\"flex items-center gap-2 rounded-[var(--radius-input)] px-2 py-2 hover:bg-bg-sunken\"\n >\n <InitialsAvatar\n name={`${candidate.firstName} ${candidate.lastName}`}\n size={30}\n />\n <span className=\"min-w-0 flex-1 truncate text-sm font-medium text-text-primary\">\n {candidate.firstName} {candidate.lastName}\n </span>\n <Badge\n variant={STAGE_BADGE[candidate.status]}\n className=\"shrink-0 text-[9px]\"\n >\n {formatEnumLabel(candidate.status)}\n </Badge>\n <RatingStars value={candidate.rating} size={12} className=\"shrink-0\" />\n </li>\n ))}\n </ul>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Hiring funnel</CardTitle>\n </CardHeader>\n <CardContent>\n {jobCandidates.length === 0 ? (\n <p className=\"text-sm text-text-muted\">\n The funnel appears once candidates apply to this role.\n </p>\n ) : (\n <HBarFunnel\n stages={funnelStages}\n valueFormatter={(value) => String(value)}\n showConversion\n />\n )}\n </CardContent>\n </Card>\n </div>\n </div>\n\n <FormModal\n open={editOpen}\n onOpenChange={setEditOpen}\n title=\"Edit Job Posting\"\n description=\"Update the role details, compensation, and status.\"\n fields={JOB_FIELDS}\n initialValues={editInitial}\n submitLabel=\"Save changes\"\n onSubmit={handleEditSubmit}\n />\n <ConfirmDialog\n open={deleteOpen}\n onOpenChange={setDeleteOpen}\n title=\"Delete job posting\"\n description={`Permanently remove “${job.title}”? This cannot be undone.`}\n confirmLabel=\"Delete\"\n destructive\n onConfirm={handleDelete}\n />\n </div>\n );\n}\n"],"mappings":"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"}