@burdenoff/microfe-crewfoundry 2026.704.6 → 2026.707.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 (150) hide show
  1. package/dist/crewfoundry/CrewFoundryRoutes.js +51 -43
  2. package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
  3. package/dist/crewfoundry/components/forms/formValues.js +5 -2
  4. package/dist/crewfoundry/components/forms/formValues.js.map +1 -1
  5. package/dist/crewfoundry/components/viz/AllocationBar.js +68 -0
  6. package/dist/crewfoundry/components/viz/AllocationBar.js.map +1 -0
  7. package/dist/crewfoundry/components/viz/AreaChart.js +102 -0
  8. package/dist/crewfoundry/components/viz/AreaChart.js.map +1 -0
  9. package/dist/crewfoundry/components/viz/AvatarStack.js +36 -0
  10. package/dist/crewfoundry/components/viz/AvatarStack.js.map +1 -0
  11. package/dist/crewfoundry/components/viz/BarChart.js +83 -0
  12. package/dist/crewfoundry/components/viz/BarChart.js.map +1 -0
  13. package/dist/crewfoundry/components/viz/CertificateCard.js +118 -0
  14. package/dist/crewfoundry/components/viz/CertificateCard.js.map +1 -0
  15. package/dist/crewfoundry/components/viz/DonutChart.js +109 -0
  16. package/dist/crewfoundry/components/viz/DonutChart.js.map +1 -0
  17. package/dist/crewfoundry/components/viz/HBarFunnel.js +54 -0
  18. package/dist/crewfoundry/components/viz/HBarFunnel.js.map +1 -0
  19. package/dist/crewfoundry/components/viz/Heatmap.js +91 -0
  20. package/dist/crewfoundry/components/viz/Heatmap.js.map +1 -0
  21. package/dist/crewfoundry/components/viz/KanbanBoard.js +50 -0
  22. package/dist/crewfoundry/components/viz/KanbanBoard.js.map +1 -0
  23. package/dist/crewfoundry/components/viz/LineChart.js +123 -0
  24. package/dist/crewfoundry/components/viz/LineChart.js.map +1 -0
  25. package/dist/crewfoundry/components/viz/OrgTree.js +83 -0
  26. package/dist/crewfoundry/components/viz/OrgTree.js.map +1 -0
  27. package/dist/crewfoundry/components/viz/PathSteps.js +68 -0
  28. package/dist/crewfoundry/components/viz/PathSteps.js.map +1 -0
  29. package/dist/crewfoundry/components/viz/ProgressRing.js +68 -0
  30. package/dist/crewfoundry/components/viz/ProgressRing.js.map +1 -0
  31. package/dist/crewfoundry/components/viz/RatingStars.js +51 -0
  32. package/dist/crewfoundry/components/viz/RatingStars.js.map +1 -0
  33. package/dist/crewfoundry/components/viz/SegmentedTabs.js +69 -0
  34. package/dist/crewfoundry/components/viz/SegmentedTabs.js.map +1 -0
  35. package/dist/crewfoundry/components/viz/SkillBar.js +3 -0
  36. package/dist/crewfoundry/components/viz/Sparkline.js +74 -0
  37. package/dist/crewfoundry/components/viz/Sparkline.js.map +1 -0
  38. package/dist/crewfoundry/components/viz/StatCard.js +60 -0
  39. package/dist/crewfoundry/components/viz/StatCard.js.map +1 -0
  40. package/dist/crewfoundry/components/viz/StepProgress.js +3 -0
  41. package/dist/crewfoundry/components/viz/Timeline.js +66 -0
  42. package/dist/crewfoundry/components/viz/Timeline.js.map +1 -0
  43. package/dist/crewfoundry/components/viz/index.js +23 -0
  44. package/dist/crewfoundry/components/viz/lib/Avatar.js +30 -0
  45. package/dist/crewfoundry/components/viz/lib/Avatar.js.map +1 -0
  46. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js +119 -0
  47. package/dist/crewfoundry/components/viz/lib/CartesianFrame.js.map +1 -0
  48. package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js +35 -0
  49. package/dist/crewfoundry/components/viz/lib/ResponsiveSvg.js.map +1 -0
  50. package/dist/crewfoundry/components/viz/lib/format.js +21 -0
  51. package/dist/crewfoundry/components/viz/lib/format.js.map +1 -0
  52. package/dist/crewfoundry/components/viz/lib/geometry.js +69 -0
  53. package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -0
  54. package/dist/crewfoundry/components/viz/lib/hooks.js +39 -0
  55. package/dist/crewfoundry/components/viz/lib/hooks.js.map +1 -0
  56. package/dist/crewfoundry/components/viz/lib/palette.js +27 -0
  57. package/dist/crewfoundry/components/viz/lib/palette.js.map +1 -0
  58. package/dist/crewfoundry/lib/analytics.js +323 -0
  59. package/dist/crewfoundry/lib/analytics.js.map +1 -0
  60. package/dist/crewfoundry/lib/companyFinance.js +16 -0
  61. package/dist/crewfoundry/lib/companyFinance.js.map +1 -0
  62. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js +226 -283
  63. package/dist/crewfoundry/pages/AnalyticsAnalyticsPagePage.js.map +1 -1
  64. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js +107 -120
  65. package/dist/crewfoundry/pages/AnalyticsEmployeeAnalyticsPagePage.js.map +1 -1
  66. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +122 -162
  67. package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
  68. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +121 -158
  69. package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
  70. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +316 -211
  71. package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
  72. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +75 -27
  73. package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
  74. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +167 -165
  75. package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
  76. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +168 -109
  77. package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
  78. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +401 -290
  79. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
  80. package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js +0 -41
  81. package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
  82. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js +84 -75
  83. package/dist/crewfoundry/pages/HiringApplicationsPagePage.js.map +1 -1
  84. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +368 -174
  85. package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
  86. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +236 -201
  87. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
  88. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +369 -196
  89. package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
  90. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +269 -270
  91. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
  92. package/dist/crewfoundry/pages/HiringJobsPagePage.js +210 -110
  93. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  94. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +216 -178
  95. package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
  96. package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js +385 -0
  97. package/dist/crewfoundry/pages/HrEmployeeProfilePagePage.js.map +1 -0
  98. package/dist/crewfoundry/pages/HrEmployeesPagePage.js +189 -130
  99. package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
  100. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +397 -241
  101. package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
  102. package/dist/crewfoundry/pages/HrPayrollPagePage.js +262 -147
  103. package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
  104. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +345 -167
  105. package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
  106. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +98 -24
  107. package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +1 -1
  108. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +1 -1
  109. package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
  110. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +289 -108
  111. package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +1 -1
  112. package/dist/crewfoundry/pages/LearningCoursesPagePage.js +221 -114
  113. package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
  114. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +134 -135
  115. package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
  116. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +148 -97
  117. package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
  118. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +264 -157
  119. package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
  120. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +85 -93
  121. package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +1 -1
  122. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +211 -122
  123. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  124. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +94 -54
  125. package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +1 -1
  126. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +289 -140
  127. package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
  128. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +169 -79
  129. package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
  130. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +296 -125
  131. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  132. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js +187 -0
  133. package/dist/crewfoundry/pages/ProjectsResourcingPagePage.js.map +1 -0
  134. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js +140 -78
  135. package/dist/crewfoundry/pages/TeamsTeamDetailPagePage.js.map +1 -1
  136. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js +93 -43
  137. package/dist/crewfoundry/pages/TeamsTeamDirectoryPagePage.js.map +1 -1
  138. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +250 -98
  139. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  140. package/dist/crewfoundry/store/crewfoundryStore.js +142 -184
  141. package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
  142. package/dist/crewfoundry/utils/cn.js +10 -0
  143. package/dist/crewfoundry/utils/cn.js.map +1 -0
  144. package/dist/crewfoundry/utils/courseProgress.js +46 -0
  145. package/dist/crewfoundry/utils/courseProgress.js.map +1 -0
  146. package/dist/node_modules/clsx/dist/clsx.js +18 -0
  147. package/dist/node_modules/clsx/dist/clsx.js.map +1 -0
  148. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1716 -0
  149. package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -0
  150. package/package.json +1 -1
@@ -1,151 +1,322 @@
1
1
  import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
2
2
  import { formatEnumLabel as t } from "../utils/formatEnumLabel.js";
3
- import { Plus as n } from "lucide-react";
4
- import { useNavigate as r } from "react-router-dom";
5
- import { jsx as i, jsxs as a } from "react/jsx-runtime";
6
- import { Badge as o, Button as s, CardContent as c, CardDescription as l, CardHeader as u, CardTitle as d, GlassCard as f, GradientText as p, IllustratedEmptyState as m, PagePurpose as h, Pagination as g } from "@burdenoff/fe-libs/ui";
7
- import { usePagination as _ } from "@burdenoff/fe-libs/shared/hooks";
8
- import { useI18n as v } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
3
+ import { AllocationBar as n } from "../components/viz/AllocationBar.js";
4
+ import { AvatarStack as r } from "../components/viz/AvatarStack.js";
5
+ import { SegmentedTabs as i } from "../components/viz/SegmentedTabs.js";
6
+ import "../components/viz/index.js";
7
+ import { allocationByPerson as a } from "../lib/analytics.js";
8
+ import { useMemo as o, useState as s } from "react";
9
+ import { ArrowRight as c, Gauge as l, LayoutGrid as u, Plus as d, Table2 as f } from "lucide-react";
10
+ import { useNavigate as p } from "react-router-dom";
11
+ import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
12
+ import { Badge as _, Button as v, Card as y, CardContent as b, CardDescription as x, CardHeader as S, CardTitle as C, GlassCard as w, GradientText as T, IllustratedEmptyState as E, PagePurpose as D, Pagination as O, ResponsiveTable as k } from "@burdenoff/fe-libs/ui";
13
+ import { usePagination as A } from "@burdenoff/fe-libs/shared/hooks";
14
+ import { useI18n as j } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
9
15
  //#region src/crewfoundry/pages/ProjectsProjectsPagePage.tsx
10
- function y() {
11
- let { t: y } = v(), b = r(), { projects: x } = e(), S = x.filter((e) => e.status === "active"), { page: C, pageSize: w, setPage: T } = _({ initialPageSize: 12 }), E = (e, t) => {
12
- let n = y(e);
16
+ var M = 6;
17
+ function N(e) {
18
+ return e === "active" ? "default" : e === "completed" ? "secondary" : "outline";
19
+ }
20
+ function P(e) {
21
+ return e === "critical" ? "destructive" : e === "high" ? "default" : "secondary";
22
+ }
23
+ function F() {
24
+ let { t: F } = j(), I = p(), { projects: L, teams: R, employees: z, timeEntries: B } = e(), V = L.filter((e) => e.status === "active"), { page: H, pageSize: U, setPage: W } = A({ initialPageSize: 12 }), [G, K] = s("board"), q = (e, t) => {
25
+ let n = F(e);
13
26
  return n === e ? t : n;
14
- }, D = x.slice((C - 1) * w, (C - 1) * w + w), O = (e) => b(`../projects/project-details-page?id=${encodeURIComponent(e.id)}`);
15
- return /* @__PURE__ */ a("div", {
27
+ }, J = o(() => new Map(z.map((e) => [e.id, e])), [z]), Y = o(() => new Map(R.map((e) => [e.id, e])), [R]), X = (e) => {
28
+ let t = Y.get(e.teamId);
29
+ return t ? t.members.map((e) => J.get(e)).filter((e) => e !== void 0).map((e) => ({
30
+ name: `${e.firstName} ${e.lastName}`.trim(),
31
+ avatarUrl: e.avatar,
32
+ role: e.position
33
+ })) : [];
34
+ }, Z = o(() => a(B, z).slice(0, M), [B, z]), Q = L.slice((H - 1) * U, (H - 1) * U + U), $ = (e) => I(`../projects/project-details-page?id=${encodeURIComponent(e.id)}`), ee = [
35
+ {
36
+ key: "name",
37
+ header: q("crewfoundry.projects.table.project", "Project"),
38
+ priority: "primary",
39
+ cell: (e) => /* @__PURE__ */ h("span", {
40
+ className: "font-medium text-text-primary",
41
+ children: e.name
42
+ })
43
+ },
44
+ {
45
+ key: "status",
46
+ header: q("crewfoundry.projects.table.status", "Status"),
47
+ priority: "primary",
48
+ cell: (e) => /* @__PURE__ */ h(_, {
49
+ variant: N(e.status),
50
+ className: "text-[10px]",
51
+ children: t(e.status)
52
+ })
53
+ },
54
+ {
55
+ key: "progress",
56
+ header: q("crewfoundry.projects.table.progress", "Progress"),
57
+ priority: "secondary",
58
+ cell: (e) => /* @__PURE__ */ g("div", {
59
+ className: "flex items-center gap-2 md:min-w-[160px]",
60
+ children: [/* @__PURE__ */ h("div", {
61
+ className: "h-1.5 flex-1 overflow-hidden rounded-full bg-bg-sunken",
62
+ children: /* @__PURE__ */ h("div", {
63
+ className: "h-1.5 rounded-full bg-action-primary-bg",
64
+ style: { width: `${e.progress}%` }
65
+ })
66
+ }), /* @__PURE__ */ g("span", {
67
+ className: "w-9 shrink-0 text-right text-xs tabular-nums text-text-secondary",
68
+ children: [e.progress, "%"]
69
+ })]
70
+ })
71
+ },
72
+ {
73
+ key: "team",
74
+ header: q("crewfoundry.projects.table.team", "Team"),
75
+ priority: "secondary",
76
+ cell: (e) => {
77
+ let t = X(e);
78
+ return t.length > 0 ? /* @__PURE__ */ h(r, {
79
+ people: t,
80
+ max: 4,
81
+ size: 26
82
+ }) : /* @__PURE__ */ h("span", {
83
+ className: "text-xs text-text-secondary",
84
+ children: e.teamName || "—"
85
+ });
86
+ }
87
+ },
88
+ {
89
+ key: "timeline",
90
+ header: q("crewfoundry.projects.table.timeline", "Timeline"),
91
+ priority: "secondary",
92
+ cell: (e) => /* @__PURE__ */ g("span", {
93
+ className: "whitespace-nowrap text-xs text-text-secondary",
94
+ children: [
95
+ e.startDate,
96
+ " — ",
97
+ e.endDate
98
+ ]
99
+ })
100
+ },
101
+ {
102
+ key: "budget",
103
+ header: q("crewfoundry.projects.table.budget", "Budget"),
104
+ priority: "primary",
105
+ cell: (e) => /* @__PURE__ */ g("span", {
106
+ className: "tabular-nums md:text-right md:block",
107
+ children: [
108
+ "$",
109
+ (e.budget / 1e3).toFixed(0),
110
+ "K"
111
+ ]
112
+ })
113
+ }
114
+ ];
115
+ return /* @__PURE__ */ g("div", {
16
116
  className: "space-y-6 p-6",
17
117
  children: [
18
- /* @__PURE__ */ a("div", {
19
- className: "flex items-center justify-between",
20
- children: [/* @__PURE__ */ a("div", { children: [
21
- /* @__PURE__ */ i("h1", {
118
+ /* @__PURE__ */ g("div", {
119
+ className: "flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between",
120
+ children: [/* @__PURE__ */ g("div", { children: [
121
+ /* @__PURE__ */ h("h1", {
22
122
  className: "text-2xl font-semibold",
23
- children: /* @__PURE__ */ i(p, { children: E("crewfoundry.projects.title", "Projects") })
123
+ children: /* @__PURE__ */ h(T, { children: q("crewfoundry.projects.title", "Projects") })
24
124
  }),
25
- /* @__PURE__ */ a("p", {
125
+ /* @__PURE__ */ g("p", {
26
126
  className: "text-sm text-text-secondary",
27
127
  children: [
28
- S.length,
128
+ V.length,
29
129
  " ",
30
- E("crewfoundry.projects.summary.activeProjects", "active projects")
130
+ q("crewfoundry.projects.summary.activeProjects", "active projects")
31
131
  ]
32
132
  }),
33
- /* @__PURE__ */ i(h, {
133
+ /* @__PURE__ */ h(D, {
34
134
  className: "mt-1",
35
135
  children: "All your projects in one view — track status, progress, and budgets so nothing stalls and everything ships on time."
36
136
  })
37
- ] }), /* @__PURE__ */ a(s, {
38
- size: "sm",
39
- onClick: () => b("../projects/create-project-page"),
40
- children: [/* @__PURE__ */ i(n, { className: "h-4 w-4 mr-1" }), E("crewfoundry.projects.actions.newProject", "New Project")]
41
- })]
42
- }),
43
- /* @__PURE__ */ i("div", {
44
- className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
45
- children: x.length === 0 ? /* @__PURE__ */ i("div", {
46
- className: "col-span-full",
47
- children: /* @__PURE__ */ i(m, {
48
- illustration: "empty-data",
49
- title: "No projects yet",
50
- description: "Create your first project to start tracking work."
51
- })
52
- }) : D.map((e, n) => /* @__PURE__ */ a(f, {
53
- treatment: n === 0 && C === 1 ? "glass" : "plain",
54
- glow: n === 0 && C === 1,
55
- role: "button",
56
- tabIndex: 0,
57
- "aria-label": `Open ${e.name} project detail`,
58
- onClick: () => O(e),
59
- onKeyDown: (t) => {
60
- (t.key === "Enter" || t.key === " ") && (t.preventDefault(), O(e));
61
- },
62
- className: "hover:shadow-md transition-shadow cursor-pointer focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]",
63
- children: [/* @__PURE__ */ a(u, { children: [/* @__PURE__ */ a("div", {
64
- className: "flex items-center justify-between",
65
- children: [/* @__PURE__ */ i(d, {
66
- className: "text-base",
67
- children: e.name
68
- }), /* @__PURE__ */ i(o, {
69
- variant: e.status === "active" ? "default" : e.status === "completed" ? "secondary" : "outline",
70
- className: "text-[10px]",
71
- children: t(e.status)
72
- })]
73
- }), /* @__PURE__ */ i(l, { children: e.description })] }), /* @__PURE__ */ a(c, { children: [
74
- /* @__PURE__ */ a("div", {
75
- className: "space-y-2 mb-3",
76
- children: [/* @__PURE__ */ a("div", {
77
- className: "flex justify-between text-xs",
78
- children: [/* @__PURE__ */ a("span", { children: [
79
- e.completedTasks,
80
- "/",
81
- e.tasks,
82
- " ",
83
- E("crewfoundry.projects.card.tasks", "tasks")
84
- ] }), /* @__PURE__ */ a("span", { children: [e.progress, "%"] })]
85
- }), /* @__PURE__ */ i("div", {
86
- className: "h-2 bg-bg-sunken rounded overflow-hidden",
87
- children: /* @__PURE__ */ i("div", {
88
- className: "h-2 bg-action-primary-bg rounded",
89
- style: { width: `${e.progress}%` }
90
- })
91
- })]
137
+ ] }), /* @__PURE__ */ g("div", {
138
+ className: "flex flex-wrap items-center gap-2",
139
+ children: [
140
+ /* @__PURE__ */ h(i, {
141
+ "aria-label": "Projects view",
142
+ value: G,
143
+ onChange: (e) => K(e),
144
+ tabs: [{
145
+ id: "board",
146
+ label: "Board",
147
+ icon: /* @__PURE__ */ h(u, { className: "h-4 w-4" })
148
+ }, {
149
+ id: "table",
150
+ label: "Table",
151
+ icon: /* @__PURE__ */ h(f, { className: "h-4 w-4" })
152
+ }]
92
153
  }),
93
- /* @__PURE__ */ a("div", {
94
- className: "grid grid-cols-2 gap-2 text-xs text-text-secondary",
95
- children: [
96
- /* @__PURE__ */ a("span", { children: [
97
- E("crewfoundry.projects.card.team", "Team"),
98
- ": ",
99
- e.teamName
100
- ] }),
101
- /* @__PURE__ */ a("span", { children: [
102
- E("crewfoundry.projects.card.manager", "Manager"),
103
- ": ",
104
- e.managerName
105
- ] }),
106
- /* @__PURE__ */ a("span", { children: [
107
- E("crewfoundry.projects.card.budget", "Budget"),
108
- ": $",
109
- (e.budget / 1e3).toFixed(0),
110
- "K"
111
- ] }),
112
- /* @__PURE__ */ a("span", { children: [
113
- E("crewfoundry.projects.card.spent", "Spent"),
114
- ": $",
115
- (e.spent / 1e3).toFixed(0),
116
- "K"
117
- ] })
118
- ]
154
+ /* @__PURE__ */ g(v, {
155
+ variant: "outline",
156
+ size: "sm",
157
+ onClick: () => I("../projects/resourcing-page"),
158
+ children: [/* @__PURE__ */ h(l, { className: "h-4 w-4 mr-1" }), q("crewfoundry.projects.actions.resourcing", "Resourcing & Utilization")]
119
159
  }),
120
- /* @__PURE__ */ a("div", {
121
- className: "flex gap-1 mt-2",
122
- children: [/* @__PURE__ */ i(o, {
123
- variant: e.priority === "critical" ? "destructive" : e.priority === "high" ? "default" : "secondary",
124
- className: "text-[10px]",
125
- children: t(e.priority)
126
- }), /* @__PURE__ */ a(o, {
127
- variant: "outline",
128
- className: "text-[10px]",
129
- children: [
130
- e.startDate,
131
- " — ",
132
- e.endDate
133
- ]
134
- })]
160
+ /* @__PURE__ */ g(v, {
161
+ size: "sm",
162
+ onClick: () => I("../projects/create-project-page"),
163
+ children: [/* @__PURE__ */ h(d, { className: "h-4 w-4 mr-1" }), q("crewfoundry.projects.actions.newProject", "New Project")]
135
164
  })
136
- ] })]
137
- }, e.id))
165
+ ]
166
+ })]
138
167
  }),
139
- x.length > w && /* @__PURE__ */ i(g, {
140
- page: C,
141
- pageSize: w,
142
- totalItems: x.length,
143
- onPageChange: T
144
- })
168
+ L.length === 0 ? /* @__PURE__ */ h(E, {
169
+ illustration: "empty-data",
170
+ title: "No projects yet",
171
+ description: "Create your first project to start tracking work."
172
+ }) : G === "table" ? /* @__PURE__ */ h(w, {
173
+ treatment: "glass",
174
+ glow: !0,
175
+ className: "border-border-seam",
176
+ children: /* @__PURE__ */ g(b, {
177
+ className: "p-4",
178
+ children: [/* @__PURE__ */ h(k, {
179
+ columns: ee,
180
+ data: Q,
181
+ rowKey: (e) => e.id,
182
+ onRowClick: $
183
+ }), L.length > U && /* @__PURE__ */ h("div", {
184
+ className: "mt-4",
185
+ children: /* @__PURE__ */ h(O, {
186
+ page: H,
187
+ pageSize: U,
188
+ totalItems: L.length,
189
+ onPageChange: W
190
+ })
191
+ })]
192
+ })
193
+ }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h("div", {
194
+ className: "grid gap-4 md:grid-cols-2 lg:grid-cols-3",
195
+ children: Q.map((e) => {
196
+ let n = X(e);
197
+ return /* @__PURE__ */ g(w, {
198
+ role: "button",
199
+ tabIndex: 0,
200
+ "aria-label": `Open ${e.name} project detail`,
201
+ onClick: () => $(e),
202
+ onKeyDown: (t) => {
203
+ (t.key === "Enter" || t.key === " ") && (t.preventDefault(), $(e));
204
+ },
205
+ className: "cursor-pointer transition-shadow hover:shadow-md focus-visible:shadow-[var(--shadow-focus)] focus-visible:outline-none",
206
+ children: [/* @__PURE__ */ g(S, {
207
+ className: "pb-3",
208
+ children: [/* @__PURE__ */ g("div", {
209
+ className: "flex items-center justify-between",
210
+ children: [/* @__PURE__ */ h(C, {
211
+ className: "text-base",
212
+ children: e.name
213
+ }), /* @__PURE__ */ h(_, {
214
+ variant: N(e.status),
215
+ className: "text-[10px]",
216
+ children: t(e.status)
217
+ })]
218
+ }), /* @__PURE__ */ h(x, { children: e.description })]
219
+ }), /* @__PURE__ */ g(b, { children: [
220
+ /* @__PURE__ */ g("div", {
221
+ className: "space-y-2 mb-3",
222
+ children: [/* @__PURE__ */ g("div", {
223
+ className: "flex justify-between text-xs",
224
+ children: [/* @__PURE__ */ g("span", { children: [
225
+ e.completedTasks,
226
+ "/",
227
+ e.tasks,
228
+ " ",
229
+ q("crewfoundry.projects.card.tasks", "tasks")
230
+ ] }), /* @__PURE__ */ g("span", {
231
+ className: "tabular-nums",
232
+ children: [e.progress, "%"]
233
+ })]
234
+ }), /* @__PURE__ */ h("div", {
235
+ className: "h-2 overflow-hidden rounded bg-bg-sunken",
236
+ children: /* @__PURE__ */ h("div", {
237
+ className: "h-2 rounded bg-action-primary-bg",
238
+ style: { width: `${e.progress}%` }
239
+ })
240
+ })]
241
+ }),
242
+ /* @__PURE__ */ g("div", {
243
+ className: "mb-3 flex items-center justify-between gap-2",
244
+ children: [n.length > 0 ? /* @__PURE__ */ h(r, {
245
+ people: n,
246
+ max: 4,
247
+ size: 26
248
+ }) : /* @__PURE__ */ g("span", {
249
+ className: "text-xs text-text-secondary",
250
+ children: [
251
+ q("crewfoundry.projects.card.team", "Team"),
252
+ ": ",
253
+ e.teamName
254
+ ]
255
+ }), /* @__PURE__ */ g("span", {
256
+ className: "shrink-0 text-xs tabular-nums text-text-secondary",
257
+ children: [
258
+ "$",
259
+ (e.spent / 1e3).toFixed(0),
260
+ "K / $",
261
+ (e.budget / 1e3).toFixed(0),
262
+ "K"
263
+ ]
264
+ })]
265
+ }),
266
+ /* @__PURE__ */ g("div", {
267
+ className: "flex flex-wrap gap-1",
268
+ children: [/* @__PURE__ */ h(_, {
269
+ variant: P(e.priority),
270
+ className: "text-[10px]",
271
+ children: t(e.priority)
272
+ }), /* @__PURE__ */ g(_, {
273
+ variant: "outline",
274
+ className: "text-[10px]",
275
+ children: [
276
+ e.startDate,
277
+ " — ",
278
+ e.endDate
279
+ ]
280
+ })]
281
+ })
282
+ ] })]
283
+ }, e.id);
284
+ })
285
+ }), L.length > U && /* @__PURE__ */ h(O, {
286
+ page: H,
287
+ pageSize: U,
288
+ totalItems: L.length,
289
+ onPageChange: W
290
+ })] }),
291
+ Z.length > 0 && /* @__PURE__ */ g(y, { children: [/* @__PURE__ */ h(S, { children: /* @__PURE__ */ g("div", {
292
+ className: "flex flex-wrap items-center justify-between gap-2",
293
+ children: [/* @__PURE__ */ g("div", { children: [/* @__PURE__ */ h(C, {
294
+ className: "text-base",
295
+ children: q("crewfoundry.projects.resourcing.title", "Resourcing")
296
+ }), /* @__PURE__ */ g(x, { children: [
297
+ q("crewfoundry.projects.resourcing.subtitle", "People allocation for the latest week with logged time"),
298
+ " ",
299
+ "(",
300
+ Z[0].weekLabel,
301
+ ")"
302
+ ] })] }), /* @__PURE__ */ g(v, {
303
+ variant: "ghost",
304
+ size: "sm",
305
+ onClick: () => I("../projects/resourcing-page"),
306
+ children: [q("crewfoundry.projects.resourcing.viewAll", "View full resourcing"), /* @__PURE__ */ h(c, { className: "ml-1 h-4 w-4" })]
307
+ })]
308
+ }) }), /* @__PURE__ */ h(b, {
309
+ className: "space-y-5",
310
+ children: Z.map((e) => /* @__PURE__ */ h(n, {
311
+ segments: e.segments,
312
+ label: e.name,
313
+ rightLabel: `${e.utilization}% utilized`
314
+ }, e.employeeId))
315
+ })] })
145
316
  ]
146
317
  });
147
318
  }
148
319
  //#endregion
149
- export { y as ProjectsProjectsPagePage };
320
+ export { F as ProjectsProjectsPagePage };
150
321
 
151
322
  //# sourceMappingURL=ProjectsProjectsPagePage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ProjectsProjectsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/ProjectsProjectsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Plus } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useCrewFoundryStore, type Project } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\n\nexport function ProjectsProjectsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { projects } = useCrewFoundryStore();\n const activeProjects = projects.filter((p) => p.status === 'active');\n const { page, pageSize, setPage } = usePagination({ initialPageSize: 12 });\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\n const pageProjects = projects.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);\n\n const openProject = (project: Project) =>\n navigate(`../projects/project-details-page?id=${encodeURIComponent(project.id)}`);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{t('crewfoundry.projects.title', 'Projects')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeProjects.length}{' '}\n {t('crewfoundry.projects.summary.activeProjects', 'active projects')}\n </p>\n <PagePurpose className=\"mt-1\">\n All your projects in one view — track status, progress, and budgets so nothing stalls\n and everything ships on time.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => navigate('../projects/create-project-page')}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('crewfoundry.projects.actions.newProject', 'New Project')}\n </Button>\n </div>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {projects.length === 0 ? (\n <div className=\"col-span-full\">\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No projects yet\"\n description=\"Create your first project to start tracking work.\"\n />\n </div>\n ) : (\n pageProjects.map((project, _idx) => (\n <GlassCard\n key={project.id}\n treatment={_idx === 0 && page === 1 ? 'glass' : 'plain'}\n glow={_idx === 0 && page === 1}\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${project.name} project detail`}\n onClick={() => openProject(project)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openProject(project);\n }\n }}\n className=\"hover:shadow-md transition-shadow cursor-pointer focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n >\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{project.name}</CardTitle>\n <Badge\n variant={\n project.status === 'active'\n ? 'default'\n : project.status === 'completed'\n ? 'secondary'\n : 'outline'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(project.status)}\n </Badge>\n </div>\n <CardDescription>{project.description}</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2 mb-3\">\n <div className=\"flex justify-between text-xs\">\n <span>\n {project.completedTasks}/{project.tasks}{' '}\n {t('crewfoundry.projects.card.tasks', 'tasks')}\n </span>\n <span>{project.progress}%</span>\n </div>\n <div className=\"h-2 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-2 bg-action-primary-bg rounded\"\n style={{ width: `${project.progress}%` }}\n />\n </div>\n </div>\n <div className=\"grid grid-cols-2 gap-2 text-xs text-text-secondary\">\n <span>\n {t('crewfoundry.projects.card.team', 'Team')}: {project.teamName}\n </span>\n <span>\n {t('crewfoundry.projects.card.manager', 'Manager')}: {project.managerName}\n </span>\n <span>\n {t('crewfoundry.projects.card.budget', 'Budget')}: $\n {(project.budget / 1000).toFixed(0)}K\n </span>\n <span>\n {t('crewfoundry.projects.card.spent', 'Spent')}: $\n {(project.spent / 1000).toFixed(0)}K\n </span>\n </div>\n <div className=\"flex gap-1 mt-2\">\n <Badge\n variant={\n project.priority === 'critical'\n ? 'destructive'\n : project.priority === 'high'\n ? 'default'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {formatEnumLabel(project.priority)}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {project.startDate} — {project.endDate}\n </Badge>\n </div>\n </CardContent>\n </GlassCard>\n ))\n )}\n </div>\n\n {projects.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={projects.length}\n onPageChange={setPage}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProjectsProjectsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/ProjectsProjectsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { ArrowRight, Gauge, LayoutGrid, Plus, Table2 } from 'lucide-react';\nimport { useNavigate } from 'react-router-dom';\nimport { useMemo, useState } from 'react';\nimport {\n useCrewFoundryStore,\n type Employee,\n type Project,\n} from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AllocationBar, AvatarStack, SegmentedTabs, type Person } from '../components/viz';\nimport { allocationByPerson } from '../lib/analytics';\n\nconst MAX_ALLOCATION_ROWS = 6;\n\nfunction statusVariant(status: Project['status']): 'default' | 'secondary' | 'outline' {\n if (status === 'active') {\n return 'default';\n }\n if (status === 'completed') {\n return 'secondary';\n }\n return 'outline';\n}\n\nfunction priorityVariant(\n priority: Project['priority']\n): 'destructive' | 'default' | 'secondary' {\n if (priority === 'critical') {\n return 'destructive';\n }\n if (priority === 'high') {\n return 'default';\n }\n return 'secondary';\n}\n\nexport function ProjectsProjectsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { projects, teams, employees, timeEntries } = useCrewFoundryStore();\n const activeProjects = projects.filter((p) => p.status === 'active');\n const { page, pageSize, setPage } = usePagination({ initialPageSize: 12 });\n const [view, setView] = useState<'board' | 'table'>('board');\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\n const employeeById = useMemo(\n () => new Map(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n const teamById = useMemo(() => new Map(teams.map((team) => [team.id, team])), [teams]);\n\n const teamPeople = (project: Project): Person[] => {\n const team = teamById.get(project.teamId);\n if (!team) {\n return [];\n }\n return team.members\n .map((memberId) => employeeById.get(memberId))\n .filter((employee): employee is Employee => employee !== undefined)\n .map((employee) => ({\n name: `${employee.firstName} ${employee.lastName}`.trim(),\n avatarUrl: employee.avatar,\n role: employee.position,\n }));\n };\n\n const allocation = useMemo(\n () => allocationByPerson(timeEntries, employees).slice(0, MAX_ALLOCATION_ROWS),\n [timeEntries, employees]\n );\n\n const pageProjects = projects.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);\n\n const openProject = (project: Project) =>\n navigate(`../projects/project-details-page?id=${encodeURIComponent(project.id)}`);\n\n const columns: ResponsiveTableColumn<Project>[] = [\n {\n key: 'name',\n header: t('crewfoundry.projects.table.project', 'Project'),\n priority: 'primary',\n cell: (project) => (\n <span className=\"font-medium text-text-primary\">{project.name}</span>\n ),\n },\n {\n key: 'status',\n header: t('crewfoundry.projects.table.status', 'Status'),\n priority: 'primary',\n cell: (project) => (\n <Badge variant={statusVariant(project.status)} className=\"text-[10px]\">\n {formatEnumLabel(project.status)}\n </Badge>\n ),\n },\n {\n key: 'progress',\n header: t('crewfoundry.projects.table.progress', 'Progress'),\n priority: 'secondary',\n cell: (project) => (\n <div className=\"flex items-center gap-2 md:min-w-[160px]\">\n <div className=\"h-1.5 flex-1 overflow-hidden rounded-full bg-bg-sunken\">\n <div\n className=\"h-1.5 rounded-full bg-action-primary-bg\"\n style={{ width: `${project.progress}%` }}\n />\n </div>\n <span className=\"w-9 shrink-0 text-right text-xs tabular-nums text-text-secondary\">\n {project.progress}%\n </span>\n </div>\n ),\n },\n {\n key: 'team',\n header: t('crewfoundry.projects.table.team', 'Team'),\n priority: 'secondary',\n cell: (project) => {\n const people = teamPeople(project);\n return people.length > 0 ? (\n <AvatarStack people={people} max={4} size={26} />\n ) : (\n <span className=\"text-xs text-text-secondary\">{project.teamName || '—'}</span>\n );\n },\n },\n {\n key: 'timeline',\n header: t('crewfoundry.projects.table.timeline', 'Timeline'),\n priority: 'secondary',\n cell: (project) => (\n <span className=\"whitespace-nowrap text-xs text-text-secondary\">\n {project.startDate} — {project.endDate}\n </span>\n ),\n },\n {\n key: 'budget',\n header: t('crewfoundry.projects.table.budget', 'Budget'),\n priority: 'primary',\n cell: (project) => (\n <span className=\"tabular-nums md:text-right md:block\">\n ${(project.budget / 1000).toFixed(0)}K\n </span>\n ),\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{t('crewfoundry.projects.title', 'Projects')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeProjects.length}{' '}\n {t('crewfoundry.projects.summary.activeProjects', 'active projects')}\n </p>\n <PagePurpose className=\"mt-1\">\n All your projects in one view — track status, progress, and budgets so nothing stalls\n and everything ships on time.\n </PagePurpose>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <SegmentedTabs\n aria-label=\"Projects view\"\n value={view}\n onChange={(id) => setView(id as 'board' | 'table')}\n tabs={[\n { id: 'board', label: 'Board', icon: <LayoutGrid className=\"h-4 w-4\" /> },\n { id: 'table', label: 'Table', icon: <Table2 className=\"h-4 w-4\" /> },\n ]}\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => navigate('../projects/resourcing-page')}\n >\n <Gauge className=\"h-4 w-4 mr-1\" />\n {t('crewfoundry.projects.actions.resourcing', 'Resourcing & Utilization')}\n </Button>\n <Button size=\"sm\" onClick={() => navigate('../projects/create-project-page')}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('crewfoundry.projects.actions.newProject', 'New Project')}\n </Button>\n </div>\n </div>\n\n {projects.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No projects yet\"\n description=\"Create your first project to start tracking work.\"\n />\n ) : view === 'table' ? (\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardContent className=\"p-4\">\n <ResponsiveTable\n columns={columns}\n data={pageProjects}\n rowKey={(project) => project.id}\n onRowClick={openProject}\n />\n {projects.length > pageSize && (\n <div className=\"mt-4\">\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={projects.length}\n onPageChange={setPage}\n />\n </div>\n )}\n </CardContent>\n </GlassCard>\n ) : (\n <>\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {pageProjects.map((project) => {\n const people = teamPeople(project);\n return (\n <GlassCard\n key={project.id}\n role=\"button\"\n tabIndex={0}\n aria-label={`Open ${project.name} project detail`}\n onClick={() => openProject(project)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openProject(project);\n }\n }}\n className=\"cursor-pointer transition-shadow hover:shadow-md focus-visible:shadow-[var(--shadow-focus)] focus-visible:outline-none\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"text-base\">{project.name}</CardTitle>\n <Badge variant={statusVariant(project.status)} className=\"text-[10px]\">\n {formatEnumLabel(project.status)}\n </Badge>\n </div>\n <CardDescription>{project.description}</CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"space-y-2 mb-3\">\n <div className=\"flex justify-between text-xs\">\n <span>\n {project.completedTasks}/{project.tasks}{' '}\n {t('crewfoundry.projects.card.tasks', 'tasks')}\n </span>\n <span className=\"tabular-nums\">{project.progress}%</span>\n </div>\n <div className=\"h-2 overflow-hidden rounded bg-bg-sunken\">\n <div\n className=\"h-2 rounded bg-action-primary-bg\"\n style={{ width: `${project.progress}%` }}\n />\n </div>\n </div>\n <div className=\"mb-3 flex items-center justify-between gap-2\">\n {people.length > 0 ? (\n <AvatarStack people={people} max={4} size={26} />\n ) : (\n <span className=\"text-xs text-text-secondary\">\n {t('crewfoundry.projects.card.team', 'Team')}: {project.teamName}\n </span>\n )}\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n ${(project.spent / 1000).toFixed(0)}K / $\n {(project.budget / 1000).toFixed(0)}K\n </span>\n </div>\n <div className=\"flex flex-wrap gap-1\">\n <Badge variant={priorityVariant(project.priority)} className=\"text-[10px]\">\n {formatEnumLabel(project.priority)}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {project.startDate} — {project.endDate}\n </Badge>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {projects.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={projects.length}\n onPageChange={setPage}\n />\n )}\n </>\n )}\n\n {/* Resourcing — People allocation this week */}\n {allocation.length > 0 && (\n <Card>\n <CardHeader>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div>\n <CardTitle className=\"text-base\">\n {t('crewfoundry.projects.resourcing.title', 'Resourcing')}\n </CardTitle>\n <CardDescription>\n {t(\n 'crewfoundry.projects.resourcing.subtitle',\n 'People allocation for the latest week with logged time'\n )}{' '}\n ({allocation[0].weekLabel})\n </CardDescription>\n </div>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => navigate('../projects/resourcing-page')}\n >\n {t('crewfoundry.projects.resourcing.viewAll', 'View full resourcing')}\n <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {allocation.map((row) => (\n <AllocationBar\n key={row.employeeId}\n segments={row.segments}\n label={row.name}\n rightLabel={`${row.utilization}% utilized`}\n />\n ))}\n </CardContent>\n </Card>\n )}\n </div>\n );\n}\n"],"mappings":"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"}