@burdenoff/microfe-crewfoundry 2026.707.1 → 2026.710.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) 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/index.js +1 -0
  14. package/dist/crewfoundry/lib/analytics.js.map +1 -1
  15. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  16. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
  17. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
  18. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
  19. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +2 -1
  20. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
  21. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  22. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +4 -4
  23. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
  24. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
  25. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +3 -3
  26. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  27. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
  28. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
  29. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  30. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +8 -4
  31. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  32. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +4 -4
  33. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  34. package/dist/crewfoundry/pages/HrPayrollPagePage.js +3 -3
  35. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  36. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  37. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
  38. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
  39. package/dist/crewfoundry/pages/LearningCoursesPagePage.js +3 -3
  40. package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
  41. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
  42. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
  43. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  44. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
  45. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
  46. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  47. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -1
  48. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
  49. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  50. package/dist/crewfoundry/utils/courseProgress.js.map +1 -1
  51. package/dist/index.js +2 -1
  52. package/dist/services/assistantApi.js +273 -0
  53. package/dist/services/assistantApi.js.map +1 -0
  54. package/dist/services/createSandboxAssistantTransport.js +120 -0
  55. package/dist/services/createSandboxAssistantTransport.js.map +1 -0
  56. package/dist/utils/pageContext.js +52 -0
  57. package/dist/utils/pageContext.js.map +1 -0
  58. package/package.json +6 -2
@@ -1 +1 @@
1
- {"version":3,"file":"HrDepartmentsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrDepartmentsPagePage.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { Building2, DollarSign, MapPin, Network, Pencil, Plus, Trash2, Users } from 'lucide-react';\nimport {\n useCrewFoundryStore,\n type Department,\n type Employee,\n} from '../store/crewfoundryStore';\nimport { AvatarStack, OrgTree, StatCard, type OrgNode } from '../components/viz';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nconst fullName = (employee: Employee): string => `${employee.firstName} ${employee.lastName}`.trim();\n\nconst DEPARTMENT_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Department name', type: 'text', required: true, colSpan: 2 },\n { name: 'headName', label: 'Department head', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n { name: 'budget', label: 'Annual budget', type: 'number', required: true },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Restructuring', value: 'restructuring' },\n ],\n },\n];\n\nconst toFormValues = (department: Department): FormValues => ({\n name: department.name,\n headName: department.headName,\n location: department.location,\n budget: department.budget,\n status: department.status,\n});\n\nexport function HrDepartmentsPagePage() {\n const { departments, employees, teams, addDepartment, updateDepartment, removeDepartment } =\n useCrewFoundryStore();\n const form = useFormModal<Department>();\n const [deleting, setDeleting] = useState<Department | null>(null);\n\n const totalBudget = departments.reduce((s, d) => s + d.budget, 0);\n const totalHeadcount = departments.reduce((s, d) => s + d.employeeCount, 0);\n\n const editing = form.context;\n\n // Resolve a department's head to a real employee (by id first, then by name) so\n // we can show a real avatar rather than a text-only name.\n const headOf = useCallback(\n (department: Department): Employee | undefined =>\n employees.find((e) => e.id === department.headId) ||\n employees.find((e) => fullName(e) === department.headName),\n [employees]\n );\n\n // Org structure: a real hierarchy derived from the roster + departments + teams.\n // Root = a top-of-org employee (CEO / founder / chief with no manager) when one\n // exists, else a generic company node. Each department hangs off the root with\n // its head; each department's children are its real teams (or a sample of its\n // real employees when the department has no teams).\n const orgRoot = useMemo<OrgNode | null>(() => {\n if (departments.length === 0) {\n return null;\n }\n const topLeader = employees.find(\n (e) => !e.managerId && /chief|ceo|founder|president|head of company/i.test(e.position)\n );\n\n const deptNodes: OrgNode[] = departments.map((department) => {\n const head = headOf(department);\n const deptTeams = teams.filter((team) => team.department === department.name);\n let children: OrgNode[];\n if (deptTeams.length > 0) {\n children = deptTeams.slice(0, 4).map((team) => ({\n id: team.id,\n name: team.name,\n role: `${team.memberCount} member${team.memberCount === 1 ? '' : 's'}`,\n }));\n } else {\n children = employees\n .filter((e) => e.department === department.name && e.id !== head?.id)\n .slice(0, 3)\n .map((e) => ({ id: e.id, name: fullName(e), role: e.position, avatarUrl: e.avatar }));\n }\n return {\n id: department.id,\n name: department.name,\n role: department.headName ? `${department.headName}, Head` : 'Department',\n avatarUrl: head?.avatar,\n children,\n };\n });\n\n return {\n id: topLeader?.id ?? 'org-root',\n name: topLeader ? fullName(topLeader) : 'Organization',\n role: topLeader?.position ?? 'Company',\n avatarUrl: topLeader?.avatar,\n children: deptNodes,\n };\n }, [departments, employees, teams, headOf]);\n\n const handleSubmit = async (values: FormValues) => {\n const payload: Department = {\n id: editing?.id ?? newClientId(),\n name: getText(values, 'name'),\n headId: editing?.headId ?? '',\n headName: getText(values, 'headName'),\n employeeCount: editing?.employeeCount ?? 0,\n budget: getNumber(values, 'budget'),\n location: getText(values, 'location'),\n status: getText(values, 'status') as Department['status'],\n };\n if (editing) {\n await updateDepartment(editing.id, payload);\n } else {\n await addDepartment(payload);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Departments</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Organizational structure and department management\n </p>\n <PagePurpose className=\"mt-1\">\n Organize your company into departments — set up structure, assign heads, and see how\n headcount is distributed across the org.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Department\n </Button>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <StatCard\n label=\"Total Departments\"\n value={departments.length}\n icon={<Building2 className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Total Headcount\"\n value={totalHeadcount}\n icon={<Users className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Total Budget\"\n value={`$${(totalBudget / 1000000).toFixed(1)}M`}\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {departments.map((dept) => {\n const deptEmps = employees.filter((e) => e.department === dept.name);\n const headcount = dept.employeeCount || deptEmps.length;\n const avgSalary =\n deptEmps.length > 0\n ? Math.round(deptEmps.reduce((s, e) => s + e.salary, 0) / deptEmps.length)\n : 0;\n const avgPerfValue =\n deptEmps.length > 0\n ? deptEmps.reduce((s, e) => s + e.performanceScore, 0) / deptEmps.length\n : 0;\n // Progress bar = real average performance for the department, on a 0–100\n // scale (score out of 5). A clearly-labeled, computed metric — no fabrication.\n const perfPct = Math.round((avgPerfValue / 5) * 100);\n const head = headOf(dept);\n const perfTone =\n perfPct >= 80\n ? 'bg-status-success-text'\n : perfPct >= 60\n ? 'bg-status-warning-text'\n : 'bg-status-error-text';\n\n return (\n <GlassCard key={dept.id} treatment=\"glass\" className=\"border-border-seam\">\n <CardHeader>\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <CardTitle className=\"truncate text-lg\">{dept.name}</CardTitle>\n <div className=\"mt-2 flex items-center gap-2\">\n <AvatarStack\n people={[{ name: dept.headName || 'Unassigned', avatarUrl: head?.avatar }]}\n size={28}\n />\n <span className=\"truncate text-sm text-text-secondary\">\n <span className=\"font-medium text-text-primary\">{dept.headName}</span>\n {dept.headName ? ', Head' : 'No head assigned'}\n </span>\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n <Badge variant={dept.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(dept.status)}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Edit ${dept.name}`}\n onClick={() => form.openModal(dept)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Delete ${dept.name}`}\n onClick={() => setDeleting(dept)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" />\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex items-end justify-between gap-3\">\n <div>\n <p className=\"text-3xl font-bold leading-none tabular-nums\">{headcount}</p>\n <p className=\"mt-1 text-xs text-text-secondary\">Employees</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-lg font-semibold tabular-nums\">\n ${(dept.budget / 1000000).toFixed(1)}M\n </p>\n <p className=\"text-xs text-text-secondary\">Annual budget</p>\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Avg performance</span>\n <span className=\"font-medium text-text-primary tabular-nums\">{perfPct}%</span>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div className={`h-2 rounded-full ${perfTone}`} style={{ width: `${perfPct}%` }} />\n </div>\n </div>\n\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" />\n {dept.location}\n </span>\n <span>\n Cost/Head:{' '}\n {headcount > 0\n ? `$${Math.round(dept.budget / headcount / 1000)}K`\n : '—'}\n </span>\n </div>\n {avgSalary > 0 && (\n <p className=\"text-xs text-text-secondary\">\n Avg salary <span className=\"font-medium text-text-primary\">${avgSalary.toLocaleString()}</span>\n </p>\n )}\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {orgRoot && (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <Network className=\"h-4 w-4 text-text-secondary\" />\n Org structure\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"overflow-x-auto pb-2\">\n <div className=\"min-w-fit\">\n <OrgTree root={orgRoot} />\n </div>\n </div>\n </CardContent>\n </Card>\n )}\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit department' : 'Add department'}\n description={\n editing\n ? 'Update this department’s details.'\n : 'Create a new department in your organization.'\n }\n fields={DEPARTMENT_FIELDS}\n initialValues={editing ? toFormValues(editing) : undefined}\n submitLabel={editing ? 'Save changes' : 'Add department'}\n onSubmit={handleSubmit}\n />\n\n <ConfirmDialog\n open={deleting !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleting(null);\n }\n }}\n title=\"Delete department?\"\n description={\n deleting ? `${deleting.name} will be removed. This cannot be undone.` : undefined\n }\n confirmLabel=\"Delete\"\n destructive\n onConfirm={async () => {\n if (deleting) {\n await removeDepartment(deleting.id);\n setDeleting(null);\n }\n }}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HrDepartmentsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrDepartmentsPagePage.tsx"],"sourcesContent":["import { useCallback, useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { Building2, DollarSign, MapPin, Network, Pencil, Plus, Trash2, Users } from 'lucide-react';\nimport { useCrewFoundryStore, type Department, type Employee } from '../store/crewfoundryStore';\nimport { AvatarStack, OrgTree, StatCard, type OrgNode } from '../components/viz';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nconst fullName = (employee: Employee): string =>\n `${employee.firstName} ${employee.lastName}`.trim();\n\nconst DEPARTMENT_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Department name', type: 'text', required: true, colSpan: 2 },\n { name: 'headName', label: 'Department head', type: 'text', required: true },\n { name: 'location', label: 'Location', type: 'text', required: true },\n { name: 'budget', label: 'Annual budget', type: 'number', required: true },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n required: true,\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Restructuring', value: 'restructuring' },\n ],\n },\n];\n\nconst toFormValues = (department: Department): FormValues => ({\n name: department.name,\n headName: department.headName,\n location: department.location,\n budget: department.budget,\n status: department.status,\n});\n\nexport function HrDepartmentsPagePage() {\n const { departments, employees, teams, addDepartment, updateDepartment, removeDepartment } =\n useCrewFoundryStore();\n const form = useFormModal<Department>();\n const [deleting, setDeleting] = useState<Department | null>(null);\n\n const totalBudget = departments.reduce((s, d) => s + d.budget, 0);\n const totalHeadcount = departments.reduce((s, d) => s + d.employeeCount, 0);\n\n const editing = form.context;\n\n // Resolve a department's head to a real employee (by id first, then by name) so\n // we can show a real avatar rather than a text-only name.\n const headOf = useCallback(\n (department: Department): Employee | undefined =>\n employees.find((e) => e.id === department.headId) ||\n employees.find((e) => fullName(e) === department.headName),\n [employees]\n );\n\n // Org structure: a real hierarchy derived from the roster + departments + teams.\n // Root = a top-of-org employee (CEO / founder / chief with no manager) when one\n // exists, else a generic company node. Each department hangs off the root with\n // its head; each department's children are its real teams (or a sample of its\n // real employees when the department has no teams).\n const orgRoot = useMemo<OrgNode | null>(() => {\n if (departments.length === 0) {\n return null;\n }\n const topLeader = employees.find(\n (e) => !e.managerId && /chief|ceo|founder|president|head of company/i.test(e.position)\n );\n\n const deptNodes: OrgNode[] = departments.map((department) => {\n const head = headOf(department);\n const deptTeams = teams.filter((team) => team.department === department.name);\n let children: OrgNode[];\n if (deptTeams.length > 0) {\n children = deptTeams.slice(0, 4).map((team) => ({\n id: team.id,\n name: team.name,\n role: `${team.memberCount} member${team.memberCount === 1 ? '' : 's'}`,\n }));\n } else {\n children = employees\n .filter((e) => e.department === department.name && e.id !== head?.id)\n .slice(0, 3)\n .map((e) => ({ id: e.id, name: fullName(e), role: e.position, avatarUrl: e.avatar }));\n }\n return {\n id: department.id,\n name: department.name,\n role: department.headName ? `${department.headName}, Head` : 'Department',\n avatarUrl: head?.avatar,\n children,\n };\n });\n\n return {\n id: topLeader?.id ?? 'org-root',\n name: topLeader ? fullName(topLeader) : 'Organization',\n role: topLeader?.position ?? 'Company',\n avatarUrl: topLeader?.avatar,\n children: deptNodes,\n };\n }, [departments, employees, teams, headOf]);\n\n const handleSubmit = async (values: FormValues) => {\n const payload: Department = {\n id: editing?.id ?? newClientId(),\n name: getText(values, 'name'),\n headId: editing?.headId ?? '',\n headName: getText(values, 'headName'),\n employeeCount: editing?.employeeCount ?? 0,\n budget: getNumber(values, 'budget'),\n location: getText(values, 'location'),\n status: getText(values, 'status') as Department['status'],\n };\n if (editing) {\n await updateDepartment(editing.id, payload);\n } else {\n await addDepartment(payload);\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Departments</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Organizational structure and department management\n </p>\n <PagePurpose className=\"mt-1\">\n Organize your company into departments — set up structure, assign heads, and see how\n headcount is distributed across the org.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Department\n </Button>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\n <StatCard\n label=\"Total Departments\"\n value={departments.length}\n icon={<Building2 className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Total Headcount\"\n value={totalHeadcount}\n icon={<Users className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Total Budget\"\n value={`$${(totalBudget / 1000000).toFixed(1)}M`}\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {departments.map((dept) => {\n const deptEmps = employees.filter((e) => e.department === dept.name);\n const headcount = dept.employeeCount || deptEmps.length;\n const avgSalary =\n deptEmps.length > 0\n ? Math.round(deptEmps.reduce((s, e) => s + e.salary, 0) / deptEmps.length)\n : 0;\n const avgPerfValue =\n deptEmps.length > 0\n ? deptEmps.reduce((s, e) => s + e.performanceScore, 0) / deptEmps.length\n : 0;\n // Progress bar = real average performance for the department, on a 0–100\n // scale (score out of 5). A clearly-labeled, computed metric — no fabrication.\n const perfPct = Math.round((avgPerfValue / 5) * 100);\n const head = headOf(dept);\n const perfTone =\n perfPct >= 80\n ? 'bg-status-success-text'\n : perfPct >= 60\n ? 'bg-status-warning-text'\n : 'bg-status-error-text';\n\n return (\n <GlassCard key={dept.id} treatment=\"glass\" className=\"border-border-seam\">\n <CardHeader>\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"min-w-0\">\n <CardTitle className=\"truncate text-lg\">{dept.name}</CardTitle>\n <div className=\"mt-2 flex items-center gap-2\">\n <AvatarStack\n people={[{ name: dept.headName || 'Unassigned', avatarUrl: head?.avatar }]}\n size={28}\n />\n <span className=\"truncate text-sm text-text-secondary\">\n <span className=\"font-medium text-text-primary\">{dept.headName}</span>\n {dept.headName ? ', Head' : 'No head assigned'}\n </span>\n </div>\n </div>\n <div className=\"flex items-center gap-1\">\n <Badge variant={dept.status === 'active' ? 'default' : 'secondary'}>\n {formatEnumLabel(dept.status)}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Edit ${dept.name}`}\n onClick={() => form.openModal(dept)}\n >\n <Pencil className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Delete ${dept.name}`}\n onClick={() => setDeleting(dept)}\n >\n <Trash2 className=\"h-4 w-4 text-status-error-text\" />\n </Button>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"flex items-end justify-between gap-3\">\n <div>\n <p className=\"text-3xl font-bold leading-none tabular-nums\">{headcount}</p>\n <p className=\"mt-1 text-xs text-text-secondary\">Employees</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-lg font-semibold tabular-nums\">\n ${(dept.budget / 1000000).toFixed(1)}M\n </p>\n <p className=\"text-xs text-text-secondary\">Annual budget</p>\n </div>\n </div>\n\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>Avg performance</span>\n <span className=\"font-medium text-text-primary tabular-nums\">{perfPct}%</span>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={`h-2 rounded-full ${perfTone}`}\n style={{ width: `${perfPct}%` }}\n />\n </div>\n </div>\n\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <MapPin className=\"h-3 w-3\" />\n {dept.location}\n </span>\n <span>\n Cost/Head:{' '}\n {headcount > 0 ? `$${Math.round(dept.budget / headcount / 1000)}K` : '—'}\n </span>\n </div>\n {avgSalary > 0 && (\n <p className=\"text-xs text-text-secondary\">\n Avg salary{' '}\n <span className=\"font-medium text-text-primary\">\n ${avgSalary.toLocaleString()}\n </span>\n </p>\n )}\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {orgRoot && (\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <Network className=\"h-4 w-4 text-text-secondary\" />\n Org structure\n </CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"overflow-x-auto pb-2\">\n <div className=\"min-w-fit\">\n <OrgTree root={orgRoot} />\n </div>\n </div>\n </CardContent>\n </Card>\n )}\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title={editing ? 'Edit department' : 'Add department'}\n description={\n editing\n ? 'Update this department’s details.'\n : 'Create a new department in your organization.'\n }\n fields={DEPARTMENT_FIELDS}\n initialValues={editing ? toFormValues(editing) : undefined}\n submitLabel={editing ? 'Save changes' : 'Add department'}\n onSubmit={handleSubmit}\n />\n\n <ConfirmDialog\n open={deleting !== null}\n onOpenChange={(open) => {\n if (!open) {\n setDeleting(null);\n }\n }}\n title=\"Delete department?\"\n description={\n deleting ? `${deleting.name} will be removed. This cannot be undone.` : undefined\n }\n confirmLabel=\"Delete\"\n destructive\n onConfirm={async () => {\n if (deleting) {\n await removeDepartment(deleting.id);\n setDeleting(null);\n }\n }}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -10,8 +10,8 @@ import { useMemo as u, useState as d } from "react";
10
10
  import { Calendar as f, CheckCircle as p, ChevronLeft as m, ChevronRight as h, Clock as g, XCircle as _ } from "lucide-react";
11
11
  import { jsx as v, jsxs as y } from "react/jsx-runtime";
12
12
  import { Badge as b, Button as x, Card as S, CardContent as C, CardDescription as w, CardHeader as T, CardTitle as E, GradientText as D, PagePurpose as O, Pagination as ee, ResponsiveTable as te } from "@burdenoff/fe-libs/ui";
13
- import { usePagination as k } from "@burdenoff/fe-libs/shared/hooks";
14
- import { useAuth as A, useGlobalUi as j } from "@burdenoff/fe-libs/shared/providers/shell";
13
+ import { useAuth as k, useGlobalUi as A } from "@burdenoff/fe-libs/shared/providers/shell";
14
+ import { usePagination as j } from "@burdenoff/fe-libs/shared/hooks";
15
15
  //#region src/crewfoundry/pages/HrLeaveManagementPagePage.tsx
16
16
  var ne = [
17
17
  {
@@ -182,7 +182,7 @@ function z({ leaveRequests: e, monthCursor: t, onPrev: n, onNext: r }) {
182
182
  })] });
183
183
  }
184
184
  function B() {
185
- let { leaveRequests: m, employees: h, addLeaveRequest: N, updateLeaveRequest: F } = e(), { showToast: I } = j(), { currentUser: L } = A(), B = o(), [V, H] = d("requests"), U = u(() => new Map(h.map((e) => [e.id, e])), [h]), [W, G] = d(() => {
185
+ let { leaveRequests: m, employees: h, addLeaveRequest: N, updateLeaveRequest: F } = e(), { showToast: I } = A(), { currentUser: L } = k(), B = o(), [V, H] = d("requests"), U = u(() => new Map(h.map((e) => [e.id, e])), [h]), [W, G] = d(() => {
186
186
  let e = m.filter((e) => e.status === "approved" || e.status === "pending").map((e) => R(e.startDate)).sort(), t = e[e.length - 1];
187
187
  if (t) {
188
188
  let [e, n] = t.split("-").map(Number);
@@ -254,7 +254,7 @@ function B() {
254
254
  }, [m]), oe = u(() => {
255
255
  let e = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
256
256
  return m.filter((t) => t.status === "approved" && t.startDate <= e && t.endDate >= e).length;
257
- }, [m]), Z = u(() => [...m].sort((e, t) => t.startDate.localeCompare(e.startDate)), [m]), Q = k({ initialPageSize: 10 }), se = Z.slice(Q.offset, Q.offset + Q.pageSize), ce = [
257
+ }, [m]), Z = u(() => [...m].sort((e, t) => t.startDate.localeCompare(e.startDate)), [m]), Q = j({ initialPageSize: 10 }), se = Z.slice(Q.offset, Q.offset + Q.pageSize), ce = [
258
258
  {
259
259
  key: "employee",
260
260
  header: "Employee",
@@ -1 +1 @@
1
- {"version":3,"file":"HrLeaveManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrLeaveManagementPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GradientText,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useAuth, useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calendar, CheckCircle, ChevronLeft, ChevronRight, Clock, XCircle } from 'lucide-react';\nimport { useCrewFoundryStore, type Employee, type LeaveRequest } from '../store/crewfoundryStore';\nimport { AvatarStack, SegmentedTabs, StatCard } from '../components/viz';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\ntype LeaveBalance = {\n type: string;\n total: number;\n used: number;\n remaining: number;\n};\n\nconst LEAVE_TYPE_OPTIONS = [\n { label: 'Annual', value: 'annual' },\n { label: 'Sick', value: 'sick' },\n { label: 'Personal', value: 'personal' },\n { label: 'Maternity', value: 'maternity' },\n { label: 'Paternity', value: 'paternity' },\n { label: 'Unpaid', value: 'unpaid' },\n];\n\nconst toLabel = (value: string): string =>\n value\n .split('_')\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ');\n\n// Tokenized color config per leave type (dot + faint cell tint). No raw colors.\nconst LEAVE_COLORS: Record<string, { label: string; dot: string; cell: string }> = {\n annual: { label: 'Annual', dot: 'var(--color-status-success-text)', cell: 'bg-status-success-bg' },\n sick: { label: 'Sick', dot: 'var(--color-status-warning-text)', cell: 'bg-status-warning-bg' },\n personal: { label: 'Personal', dot: 'var(--color-status-info-text)', cell: 'bg-status-info-bg' },\n maternity: { label: 'Maternity', dot: 'var(--color-accent-purple)', cell: 'bg-bg-sunken' },\n paternity: { label: 'Paternity', dot: 'var(--color-accent-teal)', cell: 'bg-bg-sunken' },\n unpaid: { label: 'Unpaid', dot: 'var(--color-text-muted)', cell: 'bg-bg-sunken' },\n};\nconst leaveColor = (type: string) =>\n LEAVE_COLORS[type] ?? { label: toLabel(type), dot: 'var(--color-accent-blue)', cell: 'bg-bg-sunken' };\n\nconst MONTH_NAMES = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\nconst WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];\n\nconst isoLocal = (date: Date): string =>\n `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(\n date.getDate()\n ).padStart(2, '0')}`;\n\nconst dayOf = (value: string): string => value.slice(0, 10);\n\ninterface LeaveCalendarProps {\n leaveRequests: LeaveRequest[];\n monthCursor: Date;\n onPrev: () => void;\n onNext: () => void;\n}\n\n/** Month grid plotting approved + pending leave, color-coded by type. Real data only. */\nfunction LeaveCalendar({ leaveRequests, monthCursor, onPrev, onNext }: LeaveCalendarProps) {\n const year = monthCursor.getFullYear();\n const month = monthCursor.getMonth();\n const todayStr = isoLocal(new Date());\n\n // Leaves that count on the calendar: scheduled (approved) + requested (pending).\n const visibleLeaves = useMemo(\n () => leaveRequests.filter((l) => l.status === 'approved' || l.status === 'pending'),\n [leaveRequests]\n );\n\n const gridStart = new Date(year, month, 1);\n gridStart.setDate(1 - gridStart.getDay());\n\n const cells = Array.from({ length: 42 }, (_, i) => {\n const date = new Date(gridStart);\n date.setDate(gridStart.getDate() + i);\n const dayStr = isoLocal(date);\n const types = new Set<string>();\n for (const leave of visibleLeaves) {\n if (dayOf(leave.startDate) <= dayStr && dayStr <= dayOf(leave.endDate)) {\n types.add(leave.type);\n }\n }\n return { date, dayStr, inMonth: date.getMonth() === month, types: [...types] };\n });\n\n const typesThisMonth = useMemo(() => {\n const set = new Set<string>();\n for (const cell of cells) {\n if (cell.inMonth) {\n cell.types.forEach((t) => set.add(t));\n }\n }\n return [...set];\n }, [cells]);\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"text-base\">\n {MONTH_NAMES[month]} {year}\n </CardTitle>\n <div className=\"flex gap-1\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label=\"Previous month\"\n onClick={onPrev}\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label=\"Next month\"\n onClick={onNext}\n >\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"grid grid-cols-7 gap-1 text-center text-xs font-medium text-text-secondary\">\n {WEEKDAYS.map((weekday) => (\n <div key={weekday}>{weekday}</div>\n ))}\n </div>\n <div className=\"grid grid-cols-7 gap-1\">\n {cells.map((cell) => {\n const primary = cell.types[0];\n const cellTint = cell.inMonth && primary ? leaveColor(primary).cell : '';\n const isToday = cell.dayStr === todayStr;\n return (\n <div\n key={cell.dayStr}\n className={`relative flex aspect-square flex-col items-center justify-center rounded-md text-xs ${cellTint} ${\n cell.inMonth ? 'text-text-primary' : 'text-text-muted'\n } ${isToday ? 'ring-2 ring-action-primary-bg' : ''}`}\n title={\n cell.types.length > 0\n ? `${cell.dayStr}: ${cell.types.map((t) => leaveColor(t).label).join(', ')}`\n : cell.dayStr\n }\n >\n <span className={cell.types.length > 0 ? 'font-semibold' : ''}>\n {cell.date.getDate()}\n </span>\n {cell.types.length > 0 && (\n <span className=\"mt-0.5 flex gap-0.5\">\n {cell.types.slice(0, 3).map((type) => (\n <span\n key={type}\n className=\"h-1 w-1 rounded-full\"\n style={{ backgroundColor: leaveColor(type).dot }}\n />\n ))}\n </span>\n )}\n </div>\n );\n })}\n </div>\n {typesThisMonth.length > 0 && (\n <div className=\"flex flex-wrap gap-3 pt-1 text-xs text-text-secondary\">\n {typesThisMonth.map((type) => (\n <span key={type} className=\"flex items-center gap-1.5\">\n <span\n className=\"h-2.5 w-2.5 rounded-full\"\n style={{ backgroundColor: leaveColor(type).dot }}\n />\n {leaveColor(type).label}\n </span>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n\nexport function HrLeaveManagementPagePage() {\n const { leaveRequests, employees, addLeaveRequest, updateLeaveRequest } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const { currentUser } = useAuth();\n const form = useFormModal<void>();\n const [activeTab, setActiveTab] = useState('requests');\n\n const employeeById = useMemo(\n () => new Map<string, Employee>(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n // Calendar cursor defaults to the month of the most recent scheduled leave so the\n // widget opens on data, falling back to the current month when there is none.\n const [monthCursor, setMonthCursor] = useState<Date>(() => {\n const relevant = leaveRequests\n .filter((l) => l.status === 'approved' || l.status === 'pending')\n .map((l) => dayOf(l.startDate))\n .sort();\n const latest = relevant[relevant.length - 1];\n if (latest) {\n const [y, m] = latest.split('-').map(Number);\n return new Date(y, (m || 1) - 1, 1);\n }\n return new Date();\n });\n const shiftMonth = (delta: number) =>\n setMonthCursor((prev) => new Date(prev.getFullYear(), prev.getMonth() + delta, 1));\n\n const approverName =\n currentUser?.name ||\n [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ') ||\n currentUser?.email ||\n 'Manager';\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const leaveFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n {\n name: 'type',\n label: 'Leave type',\n type: 'select',\n required: true,\n options: LEAVE_TYPE_OPTIONS,\n },\n { name: 'days', label: 'Number of days', type: 'number', required: true },\n { name: 'startDate', label: 'Start date', type: 'date', required: true },\n { name: 'endDate', label: 'End date', type: 'date', required: true },\n { name: 'reason', label: 'Reason', type: 'textarea', required: true, colSpan: 2 },\n ],\n [employeeOptions]\n );\n\n const pendingCount = leaveRequests.filter((leave) => leave.status === 'pending').length;\n const approvedCount = leaveRequests.filter((leave) => leave.status === 'approved').length;\n const totalDays = leaveRequests\n .filter((leave) => leave.status === 'approved')\n .reduce((sum, leave) => sum + leave.days, 0);\n\n const leaveBalances = useMemo<LeaveBalance[]>(() => {\n // Default annual entitlement per leave type (company policy defaults).\n // \"Used\" below is derived from real approved leave requests in the store.\n const entitlementByType: Record<string, number> = {\n annual: 20,\n sick: 12,\n personal: 5,\n maternity: 90,\n paternity: 30,\n unpaid: 15,\n };\n\n const usedByType = leaveRequests\n .filter((leave) => leave.status === 'approved')\n .reduce<Record<string, number>>((acc, leave) => {\n acc[leave.type] = (acc[leave.type] || 0) + leave.days;\n return acc;\n }, {});\n\n return Object.entries(entitlementByType).map(([type, total]) => {\n const used = usedByType[type] || 0;\n return {\n type: toLabel(type),\n total,\n used,\n remaining: Math.max(total - used, 0),\n };\n });\n }, [leaveRequests]);\n\n const onLeaveToday = useMemo(() => {\n const today = new Date().toISOString().split('T')[0];\n return leaveRequests.filter(\n (leave) => leave.status === 'approved' && leave.startDate <= today && leave.endDate >= today\n ).length;\n }, [leaveRequests]);\n\n const sortedRequests = useMemo(\n () => [...leaveRequests].sort((a, b) => b.startDate.localeCompare(a.startDate)),\n [leaveRequests]\n );\n\n const allPg = usePagination({ initialPageSize: 10 });\n const requestPageItems = sortedRequests.slice(allPg.offset, allPg.offset + allPg.pageSize);\n\n const requestColumns: ResponsiveTableColumn<LeaveRequest>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (leave) => (\n <div className=\"flex items-center gap-2.5\">\n <AvatarStack\n people={[\n { name: leave.employeeName, avatarUrl: employeeById.get(leave.employeeId)?.avatar },\n ]}\n size={32}\n />\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{leave.employeeName}</p>\n <p className=\"truncate text-xs text-text-secondary\">{leave.department}</p>\n </div>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n cell: (leave) => (\n <span className=\"flex items-center gap-1.5\">\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: leaveColor(leave.type).dot }}\n />\n <span className=\"text-xs\">{formatEnumLabel(leave.type)}</span>\n </span>\n ),\n },\n {\n key: 'period',\n header: 'Dates',\n cell: (leave) => (\n <span className=\"text-xs\">\n {leave.startDate} — {leave.endDate}\n </span>\n ),\n },\n {\n key: 'days',\n header: 'Days',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (leave) => <span className=\"tabular-nums\">{leave.days}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n cell: (leave) =>\n leave.status === 'pending' ? (\n <div className=\"flex items-center gap-1.5\">\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n onClick={() => handleDecision(leave, 'approved')}\n >\n Approve\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n onClick={() => handleDecision(leave, 'rejected')}\n >\n Reject\n </Button>\n </div>\n ) : (\n <Badge\n variant={leave.status === 'approved' ? 'default' : 'destructive'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(leave.status)}\n </Badge>\n ),\n },\n ];\n\n const handleSubmit = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: LeaveRequest = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n department: employee?.department ?? '',\n type: getText(values, 'type') as LeaveRequest['type'],\n startDate: getText(values, 'startDate'),\n endDate: getText(values, 'endDate'),\n days: getNumber(values, 'days'),\n status: 'pending',\n reason: getText(values, 'reason'),\n };\n await addLeaveRequest(payload);\n };\n\n const handleDecision = async (leave: LeaveRequest, status: 'approved' | 'rejected') => {\n try {\n await updateLeaveRequest(leave.id, { status, approverName });\n showToast(`Leave ${status} for ${leave.employeeName}`, 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update leave request', 'error');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Leave Management</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Track and manage employee leave requests</p>\n <PagePurpose className=\"mt-1\">\n Handle time-off in one place — review and approve leave requests and keep an eye on team\n availability and balances.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Calendar className=\"mr-1 h-4 w-4\" />\n Request Leave\n </Button>\n </div>\n\n <SegmentedTabs\n aria-label=\"Leave views\"\n variant=\"underline\"\n value={activeTab}\n onChange={setActiveTab}\n tabs={[\n { id: 'requests', label: 'Requests' },\n { id: 'balances', label: 'Balances' },\n { id: 'calendar', label: 'Calendar' },\n ]}\n />\n\n {activeTab === 'requests' && (\n <div className=\"space-y-6\">\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <StatCard label=\"Pending\" value={pendingCount} icon={<Clock className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Approved\"\n value={approvedCount}\n icon={<CheckCircle className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Days Used\" value={totalDays} icon={<Calendar className=\"h-4 w-4\" />} />\n <StatCard\n label=\"On Leave Today\"\n value={onLeaveToday}\n icon={<XCircle className=\"h-4 w-4\" />}\n />\n </div>\n\n <div className=\"grid gap-6 xl:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]\">\n <Card>\n <CardHeader>\n <CardTitle>Leave Requests</CardTitle>\n <CardDescription>\n Approve or reject pending requests inline; full request history below.\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <ResponsiveTable<LeaveRequest>\n data={requestPageItems}\n rowKey={(leave) => leave.id}\n columns={requestColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No leave requests yet.\n </p>\n }\n />\n {sortedRequests.length > allPg.pageSize && (\n <Pagination\n page={allPg.page}\n pageSize={allPg.pageSize}\n totalItems={sortedRequests.length}\n onPageChange={allPg.setPage}\n />\n )}\n </CardContent>\n </Card>\n\n <LeaveCalendar\n leaveRequests={leaveRequests}\n monthCursor={monthCursor}\n onPrev={() => shiftMonth(-1)}\n onNext={() => shiftMonth(1)}\n />\n </div>\n </div>\n )}\n\n {activeTab === 'balances' && (\n <Card>\n <CardHeader>\n <CardTitle>Leave Balances</CardTitle>\n <CardDescription>\n Company-wide leave allocation (default policy) vs. approved days used\n </CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-x-8 gap-y-5 md:grid-cols-2\">\n {leaveBalances.map((balance) => {\n const usedPct = Math.round((balance.used / Math.max(balance.total, 1)) * 100);\n return (\n <div key={balance.type} className=\"space-y-1.5\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"font-medium\">{formatEnumLabel(balance.type)}</span>\n <span className=\"text-text-secondary\">\n {balance.used}/{balance.total} used · {balance.remaining} left\n </span>\n </div>\n <div className=\"h-2.5 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={`h-2.5 rounded-full ${\n usedPct > 80\n ? 'bg-status-error-text'\n : usedPct > 50\n ? 'bg-status-warning-text'\n : 'bg-status-success-text'\n }`}\n style={{ width: `${usedPct}%` }}\n />\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n )}\n\n {activeTab === 'calendar' && (\n <div className=\"grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.6fr)]\">\n <LeaveCalendar\n leaveRequests={leaveRequests}\n monthCursor={monthCursor}\n onPrev={() => shiftMonth(-1)}\n onNext={() => shiftMonth(1)}\n />\n <Card>\n <CardHeader>\n <CardTitle>Scheduled &amp; requested leave</CardTitle>\n <CardDescription>Everyone who is out, most recent first</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {sortedRequests\n .filter((l) => l.status === 'approved' || l.status === 'pending')\n .slice(0, 8)\n .map((leave) => (\n <div\n key={leave.id}\n className=\"flex items-center justify-between gap-3 rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"flex items-center gap-2.5\">\n <AvatarStack\n people={[\n {\n name: leave.employeeName,\n avatarUrl: employeeById.get(leave.employeeId)?.avatar,\n },\n ]}\n size={32}\n />\n <div>\n <p className=\"text-sm font-medium\">{leave.employeeName}</p>\n <p className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: leaveColor(leave.type).dot }}\n />\n {formatEnumLabel(leave.type)} · {leave.days} days\n </p>\n </div>\n </div>\n <div className=\"text-right text-xs text-text-secondary\">\n <p>\n {leave.startDate} — {leave.endDate}\n </p>\n <Badge\n variant={leave.status === 'approved' ? 'default' : 'secondary'}\n className=\"mt-1 text-[10px]\"\n >\n {formatEnumLabel(leave.status)}\n </Badge>\n </div>\n </div>\n ))}\n {sortedRequests.filter((l) => l.status === 'approved' || l.status === 'pending')\n .length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No scheduled or requested leave.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n )}\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title=\"Request leave\"\n description=\"Submit a new time-off request for review.\"\n fields={leaveFields}\n submitLabel=\"Submit request\"\n onSubmit={handleSubmit}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HrLeaveManagementPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrLeaveManagementPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GradientText,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { useAuth, useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calendar, CheckCircle, ChevronLeft, ChevronRight, Clock, XCircle } from 'lucide-react';\nimport { useCrewFoundryStore, type Employee, type LeaveRequest } from '../store/crewfoundryStore';\nimport { AvatarStack, SegmentedTabs, StatCard } from '../components/viz';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\ntype LeaveBalance = {\n type: string;\n total: number;\n used: number;\n remaining: number;\n};\n\nconst LEAVE_TYPE_OPTIONS = [\n { label: 'Annual', value: 'annual' },\n { label: 'Sick', value: 'sick' },\n { label: 'Personal', value: 'personal' },\n { label: 'Maternity', value: 'maternity' },\n { label: 'Paternity', value: 'paternity' },\n { label: 'Unpaid', value: 'unpaid' },\n];\n\nconst toLabel = (value: string): string =>\n value\n .split('_')\n .map((part) => part[0]?.toUpperCase() + part.slice(1))\n .join(' ');\n\n// Tokenized color config per leave type (dot + faint cell tint). No raw colors.\nconst LEAVE_COLORS: Record<string, { label: string; dot: string; cell: string }> = {\n annual: {\n label: 'Annual',\n dot: 'var(--color-status-success-text)',\n cell: 'bg-status-success-bg',\n },\n sick: { label: 'Sick', dot: 'var(--color-status-warning-text)', cell: 'bg-status-warning-bg' },\n personal: { label: 'Personal', dot: 'var(--color-status-info-text)', cell: 'bg-status-info-bg' },\n maternity: { label: 'Maternity', dot: 'var(--color-accent-purple)', cell: 'bg-bg-sunken' },\n paternity: { label: 'Paternity', dot: 'var(--color-accent-teal)', cell: 'bg-bg-sunken' },\n unpaid: { label: 'Unpaid', dot: 'var(--color-text-muted)', cell: 'bg-bg-sunken' },\n};\nconst leaveColor = (type: string) =>\n LEAVE_COLORS[type] ?? {\n label: toLabel(type),\n dot: 'var(--color-accent-blue)',\n cell: 'bg-bg-sunken',\n };\n\nconst MONTH_NAMES = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\nconst WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];\n\nconst isoLocal = (date: Date): string =>\n `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(\n date.getDate()\n ).padStart(2, '0')}`;\n\nconst dayOf = (value: string): string => value.slice(0, 10);\n\ninterface LeaveCalendarProps {\n leaveRequests: LeaveRequest[];\n monthCursor: Date;\n onPrev: () => void;\n onNext: () => void;\n}\n\n/** Month grid plotting approved + pending leave, color-coded by type. Real data only. */\nfunction LeaveCalendar({ leaveRequests, monthCursor, onPrev, onNext }: LeaveCalendarProps) {\n const year = monthCursor.getFullYear();\n const month = monthCursor.getMonth();\n const todayStr = isoLocal(new Date());\n\n // Leaves that count on the calendar: scheduled (approved) + requested (pending).\n const visibleLeaves = useMemo(\n () => leaveRequests.filter((l) => l.status === 'approved' || l.status === 'pending'),\n [leaveRequests]\n );\n\n const gridStart = new Date(year, month, 1);\n gridStart.setDate(1 - gridStart.getDay());\n\n const cells = Array.from({ length: 42 }, (_, i) => {\n const date = new Date(gridStart);\n date.setDate(gridStart.getDate() + i);\n const dayStr = isoLocal(date);\n const types = new Set<string>();\n for (const leave of visibleLeaves) {\n if (dayOf(leave.startDate) <= dayStr && dayStr <= dayOf(leave.endDate)) {\n types.add(leave.type);\n }\n }\n return { date, dayStr, inMonth: date.getMonth() === month, types: [...types] };\n });\n\n const typesThisMonth = useMemo(() => {\n const set = new Set<string>();\n for (const cell of cells) {\n if (cell.inMonth) {\n cell.types.forEach((t) => set.add(t));\n }\n }\n return [...set];\n }, [cells]);\n\n return (\n <Card>\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"text-base\">\n {MONTH_NAMES[month]} {year}\n </CardTitle>\n <div className=\"flex gap-1\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label=\"Previous month\"\n onClick={onPrev}\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label=\"Next month\"\n onClick={onNext}\n >\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"grid grid-cols-7 gap-1 text-center text-xs font-medium text-text-secondary\">\n {WEEKDAYS.map((weekday) => (\n <div key={weekday}>{weekday}</div>\n ))}\n </div>\n <div className=\"grid grid-cols-7 gap-1\">\n {cells.map((cell) => {\n const primary = cell.types[0];\n const cellTint = cell.inMonth && primary ? leaveColor(primary).cell : '';\n const isToday = cell.dayStr === todayStr;\n return (\n <div\n key={cell.dayStr}\n className={`relative flex aspect-square flex-col items-center justify-center rounded-md text-xs ${cellTint} ${\n cell.inMonth ? 'text-text-primary' : 'text-text-muted'\n } ${isToday ? 'ring-2 ring-action-primary-bg' : ''}`}\n title={\n cell.types.length > 0\n ? `${cell.dayStr}: ${cell.types.map((t) => leaveColor(t).label).join(', ')}`\n : cell.dayStr\n }\n >\n <span className={cell.types.length > 0 ? 'font-semibold' : ''}>\n {cell.date.getDate()}\n </span>\n {cell.types.length > 0 && (\n <span className=\"mt-0.5 flex gap-0.5\">\n {cell.types.slice(0, 3).map((type) => (\n <span\n key={type}\n className=\"h-1 w-1 rounded-full\"\n style={{ backgroundColor: leaveColor(type).dot }}\n />\n ))}\n </span>\n )}\n </div>\n );\n })}\n </div>\n {typesThisMonth.length > 0 && (\n <div className=\"flex flex-wrap gap-3 pt-1 text-xs text-text-secondary\">\n {typesThisMonth.map((type) => (\n <span key={type} className=\"flex items-center gap-1.5\">\n <span\n className=\"h-2.5 w-2.5 rounded-full\"\n style={{ backgroundColor: leaveColor(type).dot }}\n />\n {leaveColor(type).label}\n </span>\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n\nexport function HrLeaveManagementPagePage() {\n const { leaveRequests, employees, addLeaveRequest, updateLeaveRequest } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const { currentUser } = useAuth();\n const form = useFormModal<void>();\n const [activeTab, setActiveTab] = useState('requests');\n\n const employeeById = useMemo(\n () => new Map<string, Employee>(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n // Calendar cursor defaults to the month of the most recent scheduled leave so the\n // widget opens on data, falling back to the current month when there is none.\n const [monthCursor, setMonthCursor] = useState<Date>(() => {\n const relevant = leaveRequests\n .filter((l) => l.status === 'approved' || l.status === 'pending')\n .map((l) => dayOf(l.startDate))\n .sort();\n const latest = relevant[relevant.length - 1];\n if (latest) {\n const [y, m] = latest.split('-').map(Number);\n return new Date(y, (m || 1) - 1, 1);\n }\n return new Date();\n });\n const shiftMonth = (delta: number) =>\n setMonthCursor((prev) => new Date(prev.getFullYear(), prev.getMonth() + delta, 1));\n\n const approverName =\n currentUser?.name ||\n [currentUser?.firstName, currentUser?.lastName].filter(Boolean).join(' ') ||\n currentUser?.email ||\n 'Manager';\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const leaveFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n {\n name: 'type',\n label: 'Leave type',\n type: 'select',\n required: true,\n options: LEAVE_TYPE_OPTIONS,\n },\n { name: 'days', label: 'Number of days', type: 'number', required: true },\n { name: 'startDate', label: 'Start date', type: 'date', required: true },\n { name: 'endDate', label: 'End date', type: 'date', required: true },\n { name: 'reason', label: 'Reason', type: 'textarea', required: true, colSpan: 2 },\n ],\n [employeeOptions]\n );\n\n const pendingCount = leaveRequests.filter((leave) => leave.status === 'pending').length;\n const approvedCount = leaveRequests.filter((leave) => leave.status === 'approved').length;\n const totalDays = leaveRequests\n .filter((leave) => leave.status === 'approved')\n .reduce((sum, leave) => sum + leave.days, 0);\n\n const leaveBalances = useMemo<LeaveBalance[]>(() => {\n // Default annual entitlement per leave type (company policy defaults).\n // \"Used\" below is derived from real approved leave requests in the store.\n const entitlementByType: Record<string, number> = {\n annual: 20,\n sick: 12,\n personal: 5,\n maternity: 90,\n paternity: 30,\n unpaid: 15,\n };\n\n const usedByType = leaveRequests\n .filter((leave) => leave.status === 'approved')\n .reduce<Record<string, number>>((acc, leave) => {\n acc[leave.type] = (acc[leave.type] || 0) + leave.days;\n return acc;\n }, {});\n\n return Object.entries(entitlementByType).map(([type, total]) => {\n const used = usedByType[type] || 0;\n return {\n type: toLabel(type),\n total,\n used,\n remaining: Math.max(total - used, 0),\n };\n });\n }, [leaveRequests]);\n\n const onLeaveToday = useMemo(() => {\n const today = new Date().toISOString().split('T')[0];\n return leaveRequests.filter(\n (leave) => leave.status === 'approved' && leave.startDate <= today && leave.endDate >= today\n ).length;\n }, [leaveRequests]);\n\n const sortedRequests = useMemo(\n () => [...leaveRequests].sort((a, b) => b.startDate.localeCompare(a.startDate)),\n [leaveRequests]\n );\n\n const allPg = usePagination({ initialPageSize: 10 });\n const requestPageItems = sortedRequests.slice(allPg.offset, allPg.offset + allPg.pageSize);\n\n const requestColumns: ResponsiveTableColumn<LeaveRequest>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (leave) => (\n <div className=\"flex items-center gap-2.5\">\n <AvatarStack\n people={[\n { name: leave.employeeName, avatarUrl: employeeById.get(leave.employeeId)?.avatar },\n ]}\n size={32}\n />\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{leave.employeeName}</p>\n <p className=\"truncate text-xs text-text-secondary\">{leave.department}</p>\n </div>\n </div>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n cell: (leave) => (\n <span className=\"flex items-center gap-1.5\">\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: leaveColor(leave.type).dot }}\n />\n <span className=\"text-xs\">{formatEnumLabel(leave.type)}</span>\n </span>\n ),\n },\n {\n key: 'period',\n header: 'Dates',\n cell: (leave) => (\n <span className=\"text-xs\">\n {leave.startDate} — {leave.endDate}\n </span>\n ),\n },\n {\n key: 'days',\n header: 'Days',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (leave) => <span className=\"tabular-nums\">{leave.days}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n cell: (leave) =>\n leave.status === 'pending' ? (\n <div className=\"flex items-center gap-1.5\">\n <Button\n size=\"sm\"\n className=\"h-7 text-xs\"\n onClick={() => handleDecision(leave, 'approved')}\n >\n Approve\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n className=\"h-7 text-xs\"\n onClick={() => handleDecision(leave, 'rejected')}\n >\n Reject\n </Button>\n </div>\n ) : (\n <Badge\n variant={leave.status === 'approved' ? 'default' : 'destructive'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(leave.status)}\n </Badge>\n ),\n },\n ];\n\n const handleSubmit = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: LeaveRequest = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n department: employee?.department ?? '',\n type: getText(values, 'type') as LeaveRequest['type'],\n startDate: getText(values, 'startDate'),\n endDate: getText(values, 'endDate'),\n days: getNumber(values, 'days'),\n status: 'pending',\n reason: getText(values, 'reason'),\n };\n await addLeaveRequest(payload);\n };\n\n const handleDecision = async (leave: LeaveRequest, status: 'approved' | 'rejected') => {\n try {\n await updateLeaveRequest(leave.id, { status, approverName });\n showToast(`Leave ${status} for ${leave.employeeName}`, 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to update leave request', 'error');\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Leave Management</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Track and manage employee leave requests</p>\n <PagePurpose className=\"mt-1\">\n Handle time-off in one place — review and approve leave requests and keep an eye on team\n availability and balances.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => form.openModal()}>\n <Calendar className=\"mr-1 h-4 w-4\" />\n Request Leave\n </Button>\n </div>\n\n <SegmentedTabs\n aria-label=\"Leave views\"\n variant=\"underline\"\n value={activeTab}\n onChange={setActiveTab}\n tabs={[\n { id: 'requests', label: 'Requests' },\n { id: 'balances', label: 'Balances' },\n { id: 'calendar', label: 'Calendar' },\n ]}\n />\n\n {activeTab === 'requests' && (\n <div className=\"space-y-6\">\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <StatCard label=\"Pending\" value={pendingCount} icon={<Clock className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Approved\"\n value={approvedCount}\n icon={<CheckCircle className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Days Used\" value={totalDays} icon={<Calendar className=\"h-4 w-4\" />} />\n <StatCard\n label=\"On Leave Today\"\n value={onLeaveToday}\n icon={<XCircle className=\"h-4 w-4\" />}\n />\n </div>\n\n <div className=\"grid gap-6 xl:grid-cols-[minmax(0,2fr)_minmax(0,1fr)]\">\n <Card>\n <CardHeader>\n <CardTitle>Leave Requests</CardTitle>\n <CardDescription>\n Approve or reject pending requests inline; full request history below.\n </CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <ResponsiveTable<LeaveRequest>\n data={requestPageItems}\n rowKey={(leave) => leave.id}\n columns={requestColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No leave requests yet.\n </p>\n }\n />\n {sortedRequests.length > allPg.pageSize && (\n <Pagination\n page={allPg.page}\n pageSize={allPg.pageSize}\n totalItems={sortedRequests.length}\n onPageChange={allPg.setPage}\n />\n )}\n </CardContent>\n </Card>\n\n <LeaveCalendar\n leaveRequests={leaveRequests}\n monthCursor={monthCursor}\n onPrev={() => shiftMonth(-1)}\n onNext={() => shiftMonth(1)}\n />\n </div>\n </div>\n )}\n\n {activeTab === 'balances' && (\n <Card>\n <CardHeader>\n <CardTitle>Leave Balances</CardTitle>\n <CardDescription>\n Company-wide leave allocation (default policy) vs. approved days used\n </CardDescription>\n </CardHeader>\n <CardContent className=\"grid gap-x-8 gap-y-5 md:grid-cols-2\">\n {leaveBalances.map((balance) => {\n const usedPct = Math.round((balance.used / Math.max(balance.total, 1)) * 100);\n return (\n <div key={balance.type} className=\"space-y-1.5\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"font-medium\">{formatEnumLabel(balance.type)}</span>\n <span className=\"text-text-secondary\">\n {balance.used}/{balance.total} used · {balance.remaining} left\n </span>\n </div>\n <div className=\"h-2.5 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className={`h-2.5 rounded-full ${\n usedPct > 80\n ? 'bg-status-error-text'\n : usedPct > 50\n ? 'bg-status-warning-text'\n : 'bg-status-success-text'\n }`}\n style={{ width: `${usedPct}%` }}\n />\n </div>\n </div>\n );\n })}\n </CardContent>\n </Card>\n )}\n\n {activeTab === 'calendar' && (\n <div className=\"grid gap-6 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.6fr)]\">\n <LeaveCalendar\n leaveRequests={leaveRequests}\n monthCursor={monthCursor}\n onPrev={() => shiftMonth(-1)}\n onNext={() => shiftMonth(1)}\n />\n <Card>\n <CardHeader>\n <CardTitle>Scheduled &amp; requested leave</CardTitle>\n <CardDescription>Everyone who is out, most recent first</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {sortedRequests\n .filter((l) => l.status === 'approved' || l.status === 'pending')\n .slice(0, 8)\n .map((leave) => (\n <div\n key={leave.id}\n className=\"flex items-center justify-between gap-3 rounded-lg border border-border-subtle p-3\"\n >\n <div className=\"flex items-center gap-2.5\">\n <AvatarStack\n people={[\n {\n name: leave.employeeName,\n avatarUrl: employeeById.get(leave.employeeId)?.avatar,\n },\n ]}\n size={32}\n />\n <div>\n <p className=\"text-sm font-medium\">{leave.employeeName}</p>\n <p className=\"flex items-center gap-1.5 text-xs text-text-secondary\">\n <span\n className=\"h-2 w-2 rounded-full\"\n style={{ backgroundColor: leaveColor(leave.type).dot }}\n />\n {formatEnumLabel(leave.type)} · {leave.days} days\n </p>\n </div>\n </div>\n <div className=\"text-right text-xs text-text-secondary\">\n <p>\n {leave.startDate} — {leave.endDate}\n </p>\n <Badge\n variant={leave.status === 'approved' ? 'default' : 'secondary'}\n className=\"mt-1 text-[10px]\"\n >\n {formatEnumLabel(leave.status)}\n </Badge>\n </div>\n </div>\n ))}\n {sortedRequests.filter((l) => l.status === 'approved' || l.status === 'pending')\n .length === 0 && (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No scheduled or requested leave.\n </p>\n )}\n </CardContent>\n </Card>\n </div>\n )}\n\n <FormModal\n open={form.open}\n onOpenChange={form.onOpenChange}\n title=\"Request leave\"\n description=\"Submit a new time-off request for review.\"\n fields={leaveFields}\n submitLabel=\"Submit request\"\n onSubmit={handleSubmit}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -10,8 +10,8 @@ import { useMemo as u, useState as d } from "react";
10
10
  import { CalendarClock as ee, CheckCircle as f, DollarSign as te, Download as ne, Play as re, Plus as p, Users as m } from "lucide-react";
11
11
  import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
12
12
  import { Badge as v, Button as y, Card as b, CardContent as x, CardDescription as ie, CardHeader as S, CardTitle as C, GlassCard as w, GradientText as ae, PagePurpose as oe, Pagination as T, ResponsiveTable as E, Separator as se } from "@burdenoff/fe-libs/ui";
13
- import { usePagination as ce } from "@burdenoff/fe-libs/shared/hooks";
14
- import { useGlobalUi as le } from "@burdenoff/fe-libs/shared/providers/shell";
13
+ import { useGlobalUi as ce } from "@burdenoff/fe-libs/shared/providers/shell";
14
+ import { usePagination as le } from "@burdenoff/fe-libs/shared/hooks";
15
15
  //#region src/crewfoundry/pages/HrPayrollPagePage.tsx
16
16
  var ue = [
17
17
  {
@@ -67,7 +67,7 @@ var ue = [
67
67
  return `${O[a]} 1, ${o}`;
68
68
  };
69
69
  function k() {
70
- let { payrollRecords: D, employees: O, addPayrollRecord: k, updatePayrollRecord: A } = e(), { showToast: j } = le(), M = u(() => [...new Set(D.map((e) => e.period))], [D]), [N, P] = d(""), F = a(), [I, L] = d(!1), [R, z] = d(!1), { page: B, pageSize: V, offset: H, setPage: U } = ce({ initialPageSize: 10 }), W = N || M[0] || "", fe = u(() => new Map(O.map((e) => [e.id, e])), [O]), G = u(() => O.map((e) => ({
70
+ let { payrollRecords: D, employees: O, addPayrollRecord: k, updatePayrollRecord: A } = e(), { showToast: j } = ce(), M = u(() => [...new Set(D.map((e) => e.period))], [D]), [N, P] = d(""), F = a(), [I, L] = d(!1), [R, z] = d(!1), { page: B, pageSize: V, offset: H, setPage: U } = le({ initialPageSize: 10 }), W = N || M[0] || "", fe = u(() => new Map(O.map((e) => [e.id, e])), [O]), G = u(() => O.map((e) => ({
71
71
  label: `${e.firstName} ${e.lastName}`,
72
72
  value: e.id
73
73
  })), [O]), pe = u(() => [
@@ -1 +1 @@
1
- {"version":3,"file":"HrPayrollPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrPayrollPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n Separator,\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 { CalendarClock, CheckCircle, DollarSign, Download, Play, Plus, Users } from 'lucide-react';\nimport { useCrewFoundryStore, type Employee, type PayrollRecord } from '../store/crewfoundryStore';\nimport { AvatarStack, StatCard } from '../components/viz';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nconst STATUS_OPTIONS = [\n { label: 'Draft', value: 'draft' },\n { label: 'Processing', value: 'processing' },\n { label: 'Approved', value: 'approved' },\n { label: 'Paid', value: 'paid' },\n { label: 'Failed', value: 'failed' },\n];\n\nconst MONTH_NAMES = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\nconst MONTH_ABBR = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\n// Derive the next pay-run label from a \"Month YYYY\" period; \"—\" when unparseable.\nconst nextRunLabel = (period: string): string => {\n const [monthName, yearStr] = period.split(' ');\n const monthIndex = MONTH_NAMES.findIndex((m) => m.toLowerCase() === monthName?.toLowerCase());\n const year = Number(yearStr);\n if (monthIndex === -1 || Number.isNaN(year)) {\n return '—';\n }\n const nextMonth = (monthIndex + 1) % 12;\n const nextYear = monthIndex === 11 ? year + 1 : year;\n return `${MONTH_ABBR[nextMonth]} 1, ${nextYear}`;\n};\n\nexport function HrPayrollPagePage() {\n const { payrollRecords, employees, addPayrollRecord, updatePayrollRecord } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const periods = useMemo(\n () => [...new Set(payrollRecords.map((r) => r.period))],\n [payrollRecords]\n );\n const [selectedPeriod, setSelectedPeriod] = useState('');\n const createForm = useFormModal<void>();\n const [runOpen, setRunOpen] = useState(false);\n const [running, setRunning] = useState(false);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 10 });\n\n // Default the active period to the first real one once data loads.\n const activePeriod = selectedPeriod || periods[0] || '';\n\n const employeeById = useMemo(\n () => new Map<string, Employee>(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const payrollFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n {\n name: 'period',\n label: 'Pay period',\n type: 'text',\n required: true,\n placeholder: 'e.g. February 2026',\n },\n {\n name: 'paymentMethod',\n label: 'Payment method',\n type: 'text',\n required: true,\n placeholder: 'e.g. Bank Transfer',\n },\n { name: 'grossPay', label: 'Gross pay', type: 'number', required: true },\n { name: 'deductions', label: 'Deductions', type: 'number', required: true },\n { name: 'netPay', label: 'Net pay (auto if blank)', type: 'number' },\n { name: 'payDate', label: 'Pay date', type: 'date' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n ],\n [employeeOptions]\n );\n\n const periodRecords = payrollRecords.filter((r) => r.period === activePeriod);\n const totalGross = periodRecords.reduce((s, r) => s + r.grossPay, 0);\n const totalNet = periodRecords.reduce((s, r) => s + r.netPay, 0);\n const totalDeductions = periodRecords.reduce((s, r) => s + r.deductions, 0);\n const paidCount = periodRecords.filter((r) => r.status === 'paid').length;\n const pendingCount = periodRecords.length - paidCount;\n const runPct =\n periodRecords.length > 0 ? Math.round((paidCount / periodRecords.length) * 100) : 0;\n const runComplete = periodRecords.length > 0 && pendingCount === 0;\n const recordPageItems = periodRecords.slice(offset, offset + pageSize);\n\n const handlePeriodChange = (period: string) => {\n setSelectedPeriod(period);\n setPage(1);\n };\n\n const handleRunPayroll = async () => {\n const toAdvance = periodRecords.filter((r) => r.status !== 'paid');\n setRunning(true);\n let done = 0;\n try {\n // Sequential to avoid bursting the gateway (no PgBouncer in front of the svc).\n for (const record of toAdvance) {\n await updatePayrollRecord(record.id, {\n status: 'paid',\n payDate: new Date().toISOString().slice(0, 10),\n });\n done += 1;\n }\n showToast(`Pay run complete — ${done} record${done === 1 ? '' : 's'} paid`, 'success');\n } catch (error) {\n showToast(\n error instanceof Error ? error.message : 'Payroll run failed partway through',\n 'error'\n );\n } finally {\n setRunning(false);\n setRunOpen(false);\n }\n };\n\n const handleExportCsv = () => {\n if (periodRecords.length === 0) {\n showToast('No records to export for this period', 'info');\n return;\n }\n const header = ['Employee', 'Department', 'Period', 'Gross', 'Deductions', 'Net', 'Status'];\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const rows = periodRecords.map((r) =>\n [\n r.employeeName,\n r.department,\n r.period,\n String(r.grossPay),\n String(r.deductions),\n String(r.netPay),\n r.status,\n ]\n .map(escape)\n .join(',')\n );\n const csv = [header.map(escape).join(','), ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `payroll-${activePeriod.replace(/\\s+/g, '-').toLowerCase()}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n showToast('Payroll CSV exported', 'success');\n };\n\n const recordColumns: ResponsiveTableColumn<PayrollRecord>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (record) => (\n <div className=\"flex items-center gap-2.5\">\n <AvatarStack\n people={[\n { name: record.employeeName, avatarUrl: employeeById.get(record.employeeId)?.avatar },\n ]}\n size={32}\n />\n <span className=\"font-medium\">{record.employeeName}</span>\n </div>\n ),\n },\n {\n key: 'department',\n header: 'Department',\n cell: (record) => <span className=\"text-text-secondary\">{record.department}</span>,\n },\n {\n key: 'gross',\n header: 'Gross Pay',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => <span>${record.grossPay.toLocaleString()}</span>,\n },\n {\n key: 'deductions',\n header: 'Deductions',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => (\n <span className=\"text-status-error-text\">-${record.deductions.toLocaleString()}</span>\n ),\n },\n {\n key: 'net',\n header: 'Net Pay',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => <span className=\"font-medium\">${record.netPay.toLocaleString()}</span>,\n },\n {\n key: 'method',\n header: 'Payment Method',\n cell: (record) => <span className=\"text-text-secondary\">{record.paymentMethod}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n cell: (record) => (\n <div className=\"flex items-center gap-2\">\n <Badge\n variant={\n record.status === 'paid'\n ? 'default'\n : record.status === 'processing'\n ? 'secondary'\n : 'outline'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(record.status)}\n </Badge>\n {record.status !== 'paid' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 px-2 text-[10px]\"\n onClick={() => handleMarkPaid(record)}\n >\n Mark paid\n </Button>\n )}\n </div>\n ),\n },\n ];\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const grossPay = getNumber(values, 'grossPay');\n const deductions = getNumber(values, 'deductions');\n const netPayInput = getNumber(values, 'netPay');\n const payload: PayrollRecord = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n department: employee?.department ?? '',\n period: getText(values, 'period'),\n grossPay,\n deductions,\n netPay: netPayInput > 0 ? netPayInput : Math.max(grossPay - deductions, 0),\n status: (getText(values, 'status') as PayrollRecord['status']) || 'draft',\n payDate: getText(values, 'payDate'),\n paymentMethod: getText(values, 'paymentMethod'),\n };\n await addPayrollRecord(payload);\n };\n\n const handleMarkPaid = async (record: PayrollRecord) => {\n try {\n await updatePayrollRecord(record.id, {\n status: 'paid',\n payDate: new Date().toISOString().slice(0, 10),\n });\n } catch (error) {\n showToast(\n error instanceof Error ? error.message : 'Unable to update payroll record',\n 'error'\n );\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Payroll</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activePeriod ? `${activePeriod} pay run` : 'Process and manage employee payroll'}\n </p>\n <PagePurpose className=\"mt-1\">\n Run and review payroll — track salaries, deductions, and pay runs so your team gets paid\n accurately and on time.\n </PagePurpose>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleExportCsv}>\n <Download className=\"h-4 w-4 mr-1\" />\n Export\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => createForm.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Record\n </Button>\n <Button\n size=\"sm\"\n onClick={() => setRunOpen(true)}\n disabled={runComplete || periodRecords.length === 0}\n title={runComplete ? 'All records in this period are paid' : 'Advance this period to paid'}\n >\n <Play className=\"h-4 w-4 mr-1\" />\n Run Payroll\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Payroll\"\n value={`$${totalGross.toLocaleString()}`}\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Employees Paid\"\n value={paidCount}\n icon={<CheckCircle className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Pending\" value={pendingCount} icon={<Users className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Next Run\"\n value={activePeriod ? nextRunLabel(activePeriod) : '—'}\n icon={<CalendarClock className=\"h-4 w-4\" />}\n />\n </div>\n\n {periodRecords.length > 0 && (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"text-base\">Current pay run</CardTitle>\n <Badge\n variant={runComplete ? 'default' : 'secondary'}\n className=\"flex items-center gap-1\"\n >\n <CheckCircle className=\"h-3 w-3\" />\n {runComplete ? 'Completed' : 'Processing'}\n </Badge>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid gap-4 sm:grid-cols-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Period</p>\n <p className=\"text-sm font-semibold\">{activePeriod}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Gross Total</p>\n <p className=\"text-sm font-semibold tabular-nums\">${totalGross.toLocaleString()}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Deductions</p>\n <p className=\"text-sm font-semibold tabular-nums text-status-error-text\">\n -${totalDeductions.toLocaleString()}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Net Total</p>\n <p className=\"text-sm font-semibold tabular-nums\">${totalNet.toLocaleString()}</p>\n </div>\n </div>\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {paidCount} of {periodRecords.length} paid\n </span>\n <span className=\"font-medium text-text-primary tabular-nums\">{runPct}%</span>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-status-success-text transition-all duration-500\"\n style={{ width: `${runPct}%` }}\n />\n </div>\n </div>\n </CardContent>\n </GlassCard>\n )}\n\n {periods.length > 1 && (\n <div className=\"flex flex-wrap gap-2\">\n {periods.map((period) => (\n <Button\n key={period}\n variant={activePeriod === period ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => handlePeriodChange(period)}\n >\n {period}\n </Button>\n ))}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Payroll Records</CardTitle>\n <CardDescription>Individual employee payroll details for {activePeriod}</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <ResponsiveTable<PayrollRecord>\n data={recordPageItems}\n rowKey={(record) => record.id}\n columns={recordColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No payroll records for {activePeriod || 'this period'}.\n </p>\n }\n />\n {periodRecords.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={periodRecords.length}\n onPageChange={setPage}\n />\n )}\n {periodRecords.length > 0 && (\n <>\n <Separator />\n <div className=\"flex flex-wrap items-center justify-between gap-4 text-sm font-semibold\">\n <span>Total for {activePeriod}</span>\n <div className=\"flex flex-wrap gap-x-6 gap-y-1\">\n <span>\n <span className=\"text-text-secondary\">Gross </span>$\n {totalGross.toLocaleString()}\n </span>\n <span className=\"text-status-error-text\">\n <span className=\"text-text-secondary\">Deductions </span>-$\n {totalDeductions.toLocaleString()}\n </span>\n <span>\n <span className=\"text-text-secondary\">Net </span>${totalNet.toLocaleString()}\n </span>\n </div>\n </div>\n </>\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Add payroll record\"\n description=\"Create a payroll entry for a team member.\"\n fields={payrollFields}\n submitLabel=\"Add record\"\n onSubmit={handleCreate}\n />\n\n <ConfirmDialog\n open={runOpen}\n onOpenChange={(open) => {\n if (!open && !running) {\n setRunOpen(false);\n }\n }}\n title={`Run payroll for ${activePeriod}?`}\n description={`This marks the ${pendingCount} remaining record${\n pendingCount === 1 ? '' : 's'\n } in ${activePeriod} as paid. This cannot be undone.`}\n confirmLabel={running ? 'Processing…' : 'Run payroll'}\n onConfirm={handleRunPayroll}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HrPayrollPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrPayrollPagePage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n Separator,\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 { CalendarClock, CheckCircle, DollarSign, Download, Play, Plus, Users } from 'lucide-react';\nimport { useCrewFoundryStore, type Employee, type PayrollRecord } from '../store/crewfoundryStore';\nimport { AvatarStack, StatCard } from '../components/viz';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { ConfirmDialog } from '../components/forms/ConfirmDialog';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nconst STATUS_OPTIONS = [\n { label: 'Draft', value: 'draft' },\n { label: 'Processing', value: 'processing' },\n { label: 'Approved', value: 'approved' },\n { label: 'Paid', value: 'paid' },\n { label: 'Failed', value: 'failed' },\n];\n\nconst MONTH_NAMES = [\n 'January',\n 'February',\n 'March',\n 'April',\n 'May',\n 'June',\n 'July',\n 'August',\n 'September',\n 'October',\n 'November',\n 'December',\n];\nconst MONTH_ABBR = [\n 'Jan',\n 'Feb',\n 'Mar',\n 'Apr',\n 'May',\n 'Jun',\n 'Jul',\n 'Aug',\n 'Sep',\n 'Oct',\n 'Nov',\n 'Dec',\n];\n\n// Derive the next pay-run label from a \"Month YYYY\" period; \"—\" when unparseable.\nconst nextRunLabel = (period: string): string => {\n const [monthName, yearStr] = period.split(' ');\n const monthIndex = MONTH_NAMES.findIndex((m) => m.toLowerCase() === monthName?.toLowerCase());\n const year = Number(yearStr);\n if (monthIndex === -1 || Number.isNaN(year)) {\n return '—';\n }\n const nextMonth = (monthIndex + 1) % 12;\n const nextYear = monthIndex === 11 ? year + 1 : year;\n return `${MONTH_ABBR[nextMonth]} 1, ${nextYear}`;\n};\n\nexport function HrPayrollPagePage() {\n const { payrollRecords, employees, addPayrollRecord, updatePayrollRecord } =\n useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n const periods = useMemo(\n () => [...new Set(payrollRecords.map((r) => r.period))],\n [payrollRecords]\n );\n const [selectedPeriod, setSelectedPeriod] = useState('');\n const createForm = useFormModal<void>();\n const [runOpen, setRunOpen] = useState(false);\n const [running, setRunning] = useState(false);\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 10 });\n\n // Default the active period to the first real one once data loads.\n const activePeriod = selectedPeriod || periods[0] || '';\n\n const employeeById = useMemo(\n () => new Map<string, Employee>(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const payrollFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n {\n name: 'period',\n label: 'Pay period',\n type: 'text',\n required: true,\n placeholder: 'e.g. February 2026',\n },\n {\n name: 'paymentMethod',\n label: 'Payment method',\n type: 'text',\n required: true,\n placeholder: 'e.g. Bank Transfer',\n },\n { name: 'grossPay', label: 'Gross pay', type: 'number', required: true },\n { name: 'deductions', label: 'Deductions', type: 'number', required: true },\n { name: 'netPay', label: 'Net pay (auto if blank)', type: 'number' },\n { name: 'payDate', label: 'Pay date', type: 'date' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n ],\n [employeeOptions]\n );\n\n const periodRecords = payrollRecords.filter((r) => r.period === activePeriod);\n const totalGross = periodRecords.reduce((s, r) => s + r.grossPay, 0);\n const totalNet = periodRecords.reduce((s, r) => s + r.netPay, 0);\n const totalDeductions = periodRecords.reduce((s, r) => s + r.deductions, 0);\n const paidCount = periodRecords.filter((r) => r.status === 'paid').length;\n const pendingCount = periodRecords.length - paidCount;\n const runPct =\n periodRecords.length > 0 ? Math.round((paidCount / periodRecords.length) * 100) : 0;\n const runComplete = periodRecords.length > 0 && pendingCount === 0;\n const recordPageItems = periodRecords.slice(offset, offset + pageSize);\n\n const handlePeriodChange = (period: string) => {\n setSelectedPeriod(period);\n setPage(1);\n };\n\n const handleRunPayroll = async () => {\n const toAdvance = periodRecords.filter((r) => r.status !== 'paid');\n setRunning(true);\n let done = 0;\n try {\n // Sequential to avoid bursting the gateway (no PgBouncer in front of the svc).\n for (const record of toAdvance) {\n await updatePayrollRecord(record.id, {\n status: 'paid',\n payDate: new Date().toISOString().slice(0, 10),\n });\n done += 1;\n }\n showToast(`Pay run complete — ${done} record${done === 1 ? '' : 's'} paid`, 'success');\n } catch (error) {\n showToast(\n error instanceof Error ? error.message : 'Payroll run failed partway through',\n 'error'\n );\n } finally {\n setRunning(false);\n setRunOpen(false);\n }\n };\n\n const handleExportCsv = () => {\n if (periodRecords.length === 0) {\n showToast('No records to export for this period', 'info');\n return;\n }\n const header = ['Employee', 'Department', 'Period', 'Gross', 'Deductions', 'Net', 'Status'];\n const escape = (value: string) => `\"${value.replace(/\"/g, '\"\"')}\"`;\n const rows = periodRecords.map((r) =>\n [\n r.employeeName,\n r.department,\n r.period,\n String(r.grossPay),\n String(r.deductions),\n String(r.netPay),\n r.status,\n ]\n .map(escape)\n .join(',')\n );\n const csv = [header.map(escape).join(','), ...rows].join('\\n');\n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n link.href = url;\n link.download = `payroll-${activePeriod.replace(/\\s+/g, '-').toLowerCase()}.csv`;\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n showToast('Payroll CSV exported', 'success');\n };\n\n const recordColumns: ResponsiveTableColumn<PayrollRecord>[] = [\n {\n key: 'employee',\n header: 'Employee',\n priority: 'primary',\n cell: (record) => (\n <div className=\"flex items-center gap-2.5\">\n <AvatarStack\n people={[\n { name: record.employeeName, avatarUrl: employeeById.get(record.employeeId)?.avatar },\n ]}\n size={32}\n />\n <span className=\"font-medium\">{record.employeeName}</span>\n </div>\n ),\n },\n {\n key: 'department',\n header: 'Department',\n cell: (record) => <span className=\"text-text-secondary\">{record.department}</span>,\n },\n {\n key: 'gross',\n header: 'Gross Pay',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => <span>${record.grossPay.toLocaleString()}</span>,\n },\n {\n key: 'deductions',\n header: 'Deductions',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => (\n <span className=\"text-status-error-text\">-${record.deductions.toLocaleString()}</span>\n ),\n },\n {\n key: 'net',\n header: 'Net Pay',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (record) => <span className=\"font-medium\">${record.netPay.toLocaleString()}</span>,\n },\n {\n key: 'method',\n header: 'Payment Method',\n cell: (record) => <span className=\"text-text-secondary\">{record.paymentMethod}</span>,\n },\n {\n key: 'status',\n header: 'Status',\n cell: (record) => (\n <div className=\"flex items-center gap-2\">\n <Badge\n variant={\n record.status === 'paid'\n ? 'default'\n : record.status === 'processing'\n ? 'secondary'\n : 'outline'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(record.status)}\n </Badge>\n {record.status !== 'paid' && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-6 px-2 text-[10px]\"\n onClick={() => handleMarkPaid(record)}\n >\n Mark paid\n </Button>\n )}\n </div>\n ),\n },\n ];\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const grossPay = getNumber(values, 'grossPay');\n const deductions = getNumber(values, 'deductions');\n const netPayInput = getNumber(values, 'netPay');\n const payload: PayrollRecord = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n department: employee?.department ?? '',\n period: getText(values, 'period'),\n grossPay,\n deductions,\n netPay: netPayInput > 0 ? netPayInput : Math.max(grossPay - deductions, 0),\n status: (getText(values, 'status') as PayrollRecord['status']) || 'draft',\n payDate: getText(values, 'payDate'),\n paymentMethod: getText(values, 'paymentMethod'),\n };\n await addPayrollRecord(payload);\n };\n\n const handleMarkPaid = async (record: PayrollRecord) => {\n try {\n await updatePayrollRecord(record.id, {\n status: 'paid',\n payDate: new Date().toISOString().slice(0, 10),\n });\n } catch (error) {\n showToast(\n error instanceof Error ? error.message : 'Unable to update payroll record',\n 'error'\n );\n }\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Payroll</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activePeriod ? `${activePeriod} pay run` : 'Process and manage employee payroll'}\n </p>\n <PagePurpose className=\"mt-1\">\n Run and review payroll — track salaries, deductions, and pay runs so your team gets paid\n accurately and on time.\n </PagePurpose>\n </div>\n <div className=\"flex gap-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={handleExportCsv}>\n <Download className=\"h-4 w-4 mr-1\" />\n Export\n </Button>\n <Button variant=\"outline\" size=\"sm\" onClick={() => createForm.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n Add Record\n </Button>\n <Button\n size=\"sm\"\n onClick={() => setRunOpen(true)}\n disabled={runComplete || periodRecords.length === 0}\n title={\n runComplete ? 'All records in this period are paid' : 'Advance this period to paid'\n }\n >\n <Play className=\"h-4 w-4 mr-1\" />\n Run Payroll\n </Button>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <StatCard\n label=\"Total Payroll\"\n value={`$${totalGross.toLocaleString()}`}\n icon={<DollarSign className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Employees Paid\"\n value={paidCount}\n icon={<CheckCircle className=\"h-4 w-4\" />}\n />\n <StatCard label=\"Pending\" value={pendingCount} icon={<Users className=\"h-4 w-4\" />} />\n <StatCard\n label=\"Next Run\"\n value={activePeriod ? nextRunLabel(activePeriod) : '—'}\n icon={<CalendarClock className=\"h-4 w-4\" />}\n />\n </div>\n\n {periodRecords.length > 0 && (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0\">\n <CardTitle className=\"text-base\">Current pay run</CardTitle>\n <Badge\n variant={runComplete ? 'default' : 'secondary'}\n className=\"flex items-center gap-1\"\n >\n <CheckCircle className=\"h-3 w-3\" />\n {runComplete ? 'Completed' : 'Processing'}\n </Badge>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid gap-4 sm:grid-cols-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Period</p>\n <p className=\"text-sm font-semibold\">{activePeriod}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Gross Total</p>\n <p className=\"text-sm font-semibold tabular-nums\">${totalGross.toLocaleString()}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Deductions</p>\n <p className=\"text-sm font-semibold tabular-nums text-status-error-text\">\n -${totalDeductions.toLocaleString()}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Net Total</p>\n <p className=\"text-sm font-semibold tabular-nums\">${totalNet.toLocaleString()}</p>\n </div>\n </div>\n <div className=\"space-y-1\">\n <div className=\"flex items-center justify-between text-xs text-text-secondary\">\n <span>\n {paidCount} of {periodRecords.length} paid\n </span>\n <span className=\"font-medium text-text-primary tabular-nums\">{runPct}%</span>\n </div>\n <div className=\"h-2 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-2 rounded-full bg-status-success-text transition-all duration-500\"\n style={{ width: `${runPct}%` }}\n />\n </div>\n </div>\n </CardContent>\n </GlassCard>\n )}\n\n {periods.length > 1 && (\n <div className=\"flex flex-wrap gap-2\">\n {periods.map((period) => (\n <Button\n key={period}\n variant={activePeriod === period ? 'default' : 'outline'}\n size=\"sm\"\n onClick={() => handlePeriodChange(period)}\n >\n {period}\n </Button>\n ))}\n </div>\n )}\n\n <Card>\n <CardHeader>\n <CardTitle>Payroll Records</CardTitle>\n <CardDescription>Individual employee payroll details for {activePeriod}</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <ResponsiveTable<PayrollRecord>\n data={recordPageItems}\n rowKey={(record) => record.id}\n columns={recordColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No payroll records for {activePeriod || 'this period'}.\n </p>\n }\n />\n {periodRecords.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={periodRecords.length}\n onPageChange={setPage}\n />\n )}\n {periodRecords.length > 0 && (\n <>\n <Separator />\n <div className=\"flex flex-wrap items-center justify-between gap-4 text-sm font-semibold\">\n <span>Total for {activePeriod}</span>\n <div className=\"flex flex-wrap gap-x-6 gap-y-1\">\n <span>\n <span className=\"text-text-secondary\">Gross </span>$\n {totalGross.toLocaleString()}\n </span>\n <span className=\"text-status-error-text\">\n <span className=\"text-text-secondary\">Deductions </span>-$\n {totalDeductions.toLocaleString()}\n </span>\n <span>\n <span className=\"text-text-secondary\">Net </span>${totalNet.toLocaleString()}\n </span>\n </div>\n </div>\n </>\n )}\n </CardContent>\n </Card>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Add payroll record\"\n description=\"Create a payroll entry for a team member.\"\n fields={payrollFields}\n submitLabel=\"Add record\"\n onSubmit={handleCreate}\n />\n\n <ConfirmDialog\n open={runOpen}\n onOpenChange={(open) => {\n if (!open && !running) {\n setRunOpen(false);\n }\n }}\n title={`Run payroll for ${activePeriod}?`}\n description={`This marks the ${pendingCount} remaining record${\n pendingCount === 1 ? '' : 's'\n } in ${activePeriod} as paid. This cannot be undone.`}\n confirmLabel={running ? 'Processing…' : 'Run payroll'}\n onConfirm={handleRunPayroll}\n />\n </div>\n );\n}\n"],"mappings":"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"}