@burdenoff/microfe-crewfoundry 2026.827.1 → 2026.924.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 (35) hide show
  1. package/dist/crewfoundry/CrewFoundryRoot.d.ts.map +1 -1
  2. package/dist/crewfoundry/CrewFoundryRoot.js +190 -185
  3. package/dist/crewfoundry/CrewFoundryRoot.js.map +1 -1
  4. package/dist/crewfoundry/components/forms/ConfirmDialog.d.ts.map +1 -1
  5. package/dist/crewfoundry/components/forms/ConfirmDialog.js +25 -24
  6. package/dist/crewfoundry/components/forms/ConfirmDialog.js.map +1 -1
  7. package/dist/crewfoundry/components/forms/FormModal.d.ts.map +1 -1
  8. package/dist/crewfoundry/components/forms/FormModal.js +103 -101
  9. package/dist/crewfoundry/components/forms/FormModal.js.map +1 -1
  10. package/dist/crewfoundry/pages/DashboardDashboardPagePage.d.ts.map +1 -1
  11. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +207 -210
  12. package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
  13. package/dist/crewfoundry/pages/HiringCreateJobPagePage.d.ts.map +1 -1
  14. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +0 -4
  15. package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
  16. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.d.ts.map +1 -1
  17. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +0 -4
  18. package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
  19. package/dist/crewfoundry/pages/HiringJobsPagePage.d.ts.map +1 -1
  20. package/dist/crewfoundry/pages/HiringJobsPagePage.js +75 -78
  21. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  22. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.d.ts.map +1 -1
  23. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +101 -104
  24. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  25. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  26. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +95 -98
  27. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  28. package/dist/crewfoundry/pages/TeamsTeamsPagePage.d.ts.map +1 -1
  29. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +101 -104
  30. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  31. package/dist/shared/hooks/useTr.d.ts +17 -0
  32. package/dist/shared/hooks/useTr.d.ts.map +1 -0
  33. package/dist/shared/hooks/useTr.js +14 -0
  34. package/dist/shared/hooks/useTr.js.map +1 -0
  35. package/package.json +1 -1
@@ -1,15 +1,15 @@
1
1
  import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
2
2
  import { formatEnumLabel as t } from "../utils/formatEnumLabel.js";
3
- import { AvatarStack as n } from "../components/viz/AvatarStack.js";
4
- import { SegmentedTabs as r } from "../components/viz/SegmentedTabs.js";
3
+ import { useTr as n } from "../../shared/hooks/useTr.js";
4
+ import { AvatarStack as r } from "../components/viz/AvatarStack.js";
5
+ import { SegmentedTabs as i } from "../components/viz/SegmentedTabs.js";
5
6
  import "../components/viz/index.js";
6
- import { useMemo as i, useState as a } from "react";
7
- import { Briefcase as o, DollarSign as s, MapPin as c, Plus as l, Search as u, Users as d } from "lucide-react";
8
- import { useNavigate as f } from "react-router";
9
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
10
- import { Badge as h, Button as g, CardContent as _, CardHeader as v, CardTitle as y, GlassCard as b, GradientText as x, IllustratedEmptyState as S, Input as C, PagePurpose as w, Pagination as T } from "@burdenoff/fe-libs/ui";
11
- import { usePagination as E } from "@burdenoff/fe-libs/shared/hooks";
12
- import { useI18n as D } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
+ import { useMemo as a, useState as o } from "react";
8
+ import { Briefcase as s, DollarSign as c, MapPin as l, Plus as u, Search as d, Users as f } from "lucide-react";
9
+ import { useNavigate as p } from "react-router";
10
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
11
+ import { Badge as g, Button as _, CardContent as v, CardHeader as y, CardTitle as b, GlassCard as x, GradientText as S, IllustratedEmptyState as C, Input as w, PagePurpose as T, Pagination as E } from "@burdenoff/fe-libs/ui";
12
+ import { usePagination as D } from "@burdenoff/fe-libs/shared/hooks";
13
13
  //#region src/crewfoundry/pages/HiringJobsPagePage.tsx
14
14
  var O = [
15
15
  "active",
@@ -33,22 +33,19 @@ function k(e) {
33
33
  return r === 1 ? "1 month ago" : `${r} months ago`;
34
34
  }
35
35
  function A({ label: e, children: t }) {
36
- return /* @__PURE__ */ m("div", {
36
+ return /* @__PURE__ */ h("div", {
37
37
  className: "flex items-center justify-between gap-3 text-sm",
38
- children: [/* @__PURE__ */ p("span", {
38
+ children: [/* @__PURE__ */ m("span", {
39
39
  className: "text-text-secondary",
40
40
  children: e
41
- }), /* @__PURE__ */ p("span", {
41
+ }), /* @__PURE__ */ m("span", {
42
42
  className: "font-medium text-text-primary text-right",
43
43
  children: t
44
44
  })]
45
45
  });
46
46
  }
47
47
  function j() {
48
- let { t: j } = D(), M = f(), { jobPostings: N, candidates: P } = e(), [F, I] = a(""), [L, R] = a("all"), { page: z, pageSize: B, offset: V, setPage: H } = E({ initialPageSize: 12 }), U = (e, t) => {
49
- let n = j(e);
50
- return n === e ? t : n;
51
- }, W = i(() => {
48
+ let j = n(), M = p(), { jobPostings: N, candidates: P } = e(), [F, I] = o(""), [L, R] = o("all"), { page: z, pageSize: B, offset: V, setPage: H } = D({ initialPageSize: 12 }), U = a(() => {
52
49
  let e = F.toLowerCase();
53
50
  return N.filter((t) => {
54
51
  let n = `${t.title} ${t.department} ${t.location}`.toLowerCase().includes(e), r = L === "all" || t.status === L;
@@ -58,7 +55,7 @@ function j() {
58
55
  N,
59
56
  F,
60
57
  L
61
- ]), G = W.slice(V, V + B), K = (e) => M(`../hiring/job-details-page?id=${encodeURIComponent(e)}`), q = N.filter((e) => e.status === "active").length, J = N.reduce((e, t) => e + t.applicants, 0), Y = i(() => {
58
+ ]), W = U.slice(V, V + B), G = (e) => M(`../hiring/job-details-page?id=${encodeURIComponent(e)}`), K = N.filter((e) => e.status === "active").length, q = N.reduce((e, t) => e + t.applicants, 0), J = a(() => {
62
59
  let e = [{
63
60
  id: "all",
64
61
  label: "All",
@@ -73,7 +70,7 @@ function j() {
73
70
  });
74
71
  }
75
72
  return e;
76
- }, [N]), X = i(() => {
73
+ }, [N]), Y = a(() => {
77
74
  let e = /* @__PURE__ */ new Map();
78
75
  for (let t of N) {
79
76
  let n = t.title.trim().toLowerCase(), r = P.filter((e) => e.position.trim().toLowerCase() === n).map((e) => ({ name: `${e.firstName} ${e.lastName}`.trim() }));
@@ -81,55 +78,55 @@ function j() {
81
78
  }
82
79
  return e;
83
80
  }, [N, P]);
84
- return /* @__PURE__ */ m("div", {
81
+ return /* @__PURE__ */ h("div", {
85
82
  className: "space-y-6 p-6",
86
83
  children: [
87
- /* @__PURE__ */ m("div", {
84
+ /* @__PURE__ */ h("div", {
88
85
  className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
89
- children: [/* @__PURE__ */ m("div", { children: [
90
- /* @__PURE__ */ p("h1", {
86
+ children: [/* @__PURE__ */ h("div", { children: [
87
+ /* @__PURE__ */ m("h1", {
91
88
  className: "text-2xl font-semibold",
92
- children: /* @__PURE__ */ p(x, { children: U("crewfoundry.hiringJobs.title", "Job Postings") })
89
+ children: /* @__PURE__ */ m(S, { children: j("crewfoundry.hiringJobs.title", "Job Postings") })
93
90
  }),
94
- /* @__PURE__ */ m("p", {
91
+ /* @__PURE__ */ h("p", {
95
92
  className: "text-sm text-text-secondary",
96
93
  children: [
97
- q,
94
+ K,
98
95
  " ",
99
- U("crewfoundry.hiringJobs.summary.activePositions", "active positions"),
100
- " ·",
96
+ j("crewfoundry.hiringJobs.summary.activePositions", "active positions"),
101
97
  " ",
102
- J,
98
+ "· ",
99
+ q,
103
100
  " ",
104
- U("crewfoundry.hiringJobs.summary.totalApplicants", "total applicants")
101
+ j("crewfoundry.hiringJobs.summary.totalApplicants", "total applicants")
105
102
  ]
106
103
  }),
107
- /* @__PURE__ */ p(w, {
104
+ /* @__PURE__ */ m(T, {
108
105
  className: "mt-1",
109
106
  children: "All your open and past job postings in one place — use it to monitor applicant volume and manage what roles you're actively hiring for."
110
107
  })
111
- ] }), /* @__PURE__ */ m(g, {
108
+ ] }), /* @__PURE__ */ h(_, {
112
109
  size: "sm",
113
110
  onClick: () => M("../hiring/create-job-page"),
114
- children: [/* @__PURE__ */ p(l, { className: "h-4 w-4 mr-1" }), U("crewfoundry.hiringJobs.actions.postNewJob", "Post New Job")]
111
+ children: [/* @__PURE__ */ m(u, { className: "h-4 w-4 mr-1" }), j("crewfoundry.hiringJobs.actions.postNewJob", "Post New Job")]
115
112
  })]
116
113
  }),
117
- /* @__PURE__ */ m("div", {
114
+ /* @__PURE__ */ h("div", {
118
115
  className: "flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between",
119
- children: [/* @__PURE__ */ p("div", {
116
+ children: [/* @__PURE__ */ m("div", {
120
117
  className: "-mx-1 overflow-x-auto px-1",
121
- children: /* @__PURE__ */ p(r, {
122
- tabs: Y,
118
+ children: /* @__PURE__ */ m(i, {
119
+ tabs: J,
123
120
  value: L,
124
121
  onChange: (e) => {
125
122
  R(e), H(1);
126
123
  },
127
124
  "aria-label": "Filter job postings by status"
128
125
  })
129
- }), /* @__PURE__ */ m("div", {
126
+ }), /* @__PURE__ */ h("div", {
130
127
  className: "relative w-full max-w-sm",
131
- children: [/* @__PURE__ */ p(u, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary" }), /* @__PURE__ */ p(C, {
132
- placeholder: U("crewfoundry.hiringJobs.searchPlaceholder", "Search positions..."),
128
+ children: [/* @__PURE__ */ m(d, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary" }), /* @__PURE__ */ m(w, {
129
+ placeholder: j("crewfoundry.hiringJobs.searchPlaceholder", "Search positions..."),
133
130
  className: "pl-9",
134
131
  value: F,
135
132
  onChange: (e) => {
@@ -138,68 +135,68 @@ function j() {
138
135
  })]
139
136
  })]
140
137
  }),
141
- W.length === 0 && /* @__PURE__ */ p(S, {
138
+ U.length === 0 && /* @__PURE__ */ m(C, {
142
139
  illustration: "empty-search",
143
140
  title: "No job postings found",
144
141
  description: "Try adjusting your search or status filter, or create a new job posting."
145
142
  }),
146
- /* @__PURE__ */ p("div", {
143
+ /* @__PURE__ */ m("div", {
147
144
  className: "grid gap-4 md:grid-cols-2 xl:grid-cols-3",
148
- children: G.map((e) => {
149
- let r = X.get(e.id) ?? [];
150
- return /* @__PURE__ */ m(b, {
145
+ children: W.map((e) => {
146
+ let n = Y.get(e.id) ?? [];
147
+ return /* @__PURE__ */ h(x, {
151
148
  treatment: "glass",
152
149
  role: "button",
153
150
  tabIndex: 0,
154
151
  "aria-label": `View ${e.title}`,
155
152
  className: "flex cursor-pointer flex-col transition-shadow hover:shadow-elevation-2 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]",
156
- onClick: () => K(e.id),
153
+ onClick: () => G(e.id),
157
154
  onKeyDown: (t) => {
158
- (t.key === "Enter" || t.key === " ") && (t.preventDefault(), K(e.id));
155
+ (t.key === "Enter" || t.key === " ") && (t.preventDefault(), G(e.id));
159
156
  },
160
- children: [/* @__PURE__ */ m(v, { children: [/* @__PURE__ */ m("div", {
157
+ children: [/* @__PURE__ */ h(y, { children: [/* @__PURE__ */ h("div", {
161
158
  className: "flex items-start justify-between gap-2",
162
- children: [/* @__PURE__ */ p(y, {
159
+ children: [/* @__PURE__ */ m(b, {
163
160
  className: "text-lg leading-snug",
164
161
  children: e.title
165
- }), /* @__PURE__ */ p(h, {
162
+ }), /* @__PURE__ */ m(g, {
166
163
  variant: e.status === "active" ? "default" : e.status === "draft" ? "secondary" : "outline",
167
164
  children: t(e.status)
168
165
  })]
169
- }), /* @__PURE__ */ m("div", {
166
+ }), /* @__PURE__ */ h("div", {
170
167
  className: "mt-2 flex flex-wrap gap-1.5",
171
- children: [/* @__PURE__ */ p(h, {
168
+ children: [/* @__PURE__ */ m(g, {
172
169
  variant: "outline",
173
170
  className: "text-[10px]",
174
171
  children: t(e.type)
175
- }), /* @__PURE__ */ p(h, {
172
+ }), /* @__PURE__ */ m(g, {
176
173
  variant: "outline",
177
174
  className: "text-[10px]",
178
175
  children: t(e.experienceLevel)
179
176
  })]
180
- })] }), /* @__PURE__ */ m(_, {
177
+ })] }), /* @__PURE__ */ h(v, {
181
178
  className: "flex flex-1 flex-col gap-2.5",
182
179
  children: [
183
- /* @__PURE__ */ p(A, {
184
- label: U("crewfoundry.hiringJobs.card.department", "Department"),
185
- children: /* @__PURE__ */ m("span", {
180
+ /* @__PURE__ */ m(A, {
181
+ label: j("crewfoundry.hiringJobs.card.department", "Department"),
182
+ children: /* @__PURE__ */ h("span", {
186
183
  className: "inline-flex items-center gap-1",
187
- children: [/* @__PURE__ */ p(o, { className: "h-3 w-3 text-text-secondary" }), e.department || "—"]
184
+ children: [/* @__PURE__ */ m(s, { className: "h-3 w-3 text-text-secondary" }), e.department || "—"]
188
185
  })
189
186
  }),
190
- /* @__PURE__ */ p(A, {
191
- label: U("crewfoundry.hiringJobs.card.location", "Location"),
192
- children: /* @__PURE__ */ m("span", {
187
+ /* @__PURE__ */ m(A, {
188
+ label: j("crewfoundry.hiringJobs.card.location", "Location"),
189
+ children: /* @__PURE__ */ h("span", {
193
190
  className: "inline-flex items-center gap-1",
194
- children: [/* @__PURE__ */ p(c, { className: "h-3 w-3 text-text-secondary" }), e.location || "—"]
191
+ children: [/* @__PURE__ */ m(l, { className: "h-3 w-3 text-text-secondary" }), e.location || "—"]
195
192
  })
196
193
  }),
197
- /* @__PURE__ */ p(A, {
198
- label: U("crewfoundry.hiringJobs.card.salary", "Salary"),
199
- children: /* @__PURE__ */ m("span", {
194
+ /* @__PURE__ */ m(A, {
195
+ label: j("crewfoundry.hiringJobs.card.salary", "Salary"),
196
+ children: /* @__PURE__ */ h("span", {
200
197
  className: "inline-flex items-center gap-0.5 tabular-nums",
201
198
  children: [
202
- /* @__PURE__ */ p(s, { className: "h-3 w-3 text-text-secondary" }),
199
+ /* @__PURE__ */ m(c, { className: "h-3 w-3 text-text-secondary" }),
203
200
  (e.salaryMin / 1e3).toFixed(0),
204
201
  "k – $",
205
202
  (e.salaryMax / 1e3).toFixed(0),
@@ -207,25 +204,25 @@ function j() {
207
204
  ]
208
205
  })
209
206
  }),
210
- /* @__PURE__ */ p(A, {
211
- label: U("crewfoundry.hiringJobs.card.applicantsLabel", "Applicants"),
212
- children: /* @__PURE__ */ m("span", {
207
+ /* @__PURE__ */ m(A, {
208
+ label: j("crewfoundry.hiringJobs.card.applicantsLabel", "Applicants"),
209
+ children: /* @__PURE__ */ h("span", {
213
210
  className: "inline-flex items-center gap-2",
214
- children: [r.length > 0 && /* @__PURE__ */ p(n, {
215
- people: r,
211
+ children: [n.length > 0 && /* @__PURE__ */ m(r, {
212
+ people: n,
216
213
  max: 4,
217
214
  size: 22
218
- }), /* @__PURE__ */ m("span", {
215
+ }), /* @__PURE__ */ h("span", {
219
216
  className: "inline-flex items-center gap-1 tabular-nums",
220
- children: [/* @__PURE__ */ p(d, { className: "h-3 w-3 text-text-secondary" }), e.applicants]
217
+ children: [/* @__PURE__ */ m(f, { className: "h-3 w-3 text-text-secondary" }), e.applicants]
221
218
  })]
222
219
  })
223
220
  }),
224
- /* @__PURE__ */ p("div", {
221
+ /* @__PURE__ */ m("div", {
225
222
  className: "mt-auto pt-1",
226
- children: /* @__PURE__ */ p(A, {
227
- label: U("crewfoundry.hiringJobs.card.posted", "Posted"),
228
- children: /* @__PURE__ */ p("span", {
223
+ children: /* @__PURE__ */ m(A, {
224
+ label: j("crewfoundry.hiringJobs.card.posted", "Posted"),
225
+ children: /* @__PURE__ */ m("span", {
229
226
  className: "text-text-secondary font-normal",
230
227
  children: k(e.postedDate)
231
228
  })
@@ -236,10 +233,10 @@ function j() {
236
233
  }, e.id);
237
234
  })
238
235
  }),
239
- W.length > B && /* @__PURE__ */ p(T, {
236
+ U.length > B && /* @__PURE__ */ m(E, {
240
237
  page: z,
241
238
  pageSize: B,
242
- totalItems: W.length,
239
+ totalItems: U.length,
243
240
  onPageChange: H
244
241
  })
245
242
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"HiringJobsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobsPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n Badge,\n Button,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Briefcase, DollarSign, MapPin, Plus, Search, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, SegmentedTabs, type SegmentedTab } from '../components/viz';\n\n/** Ordered status filter set (only rendered when a status has postings). */\nconst STATUS_ORDER: JobPosting['status'][] = ['active', 'draft', 'paused', 'closed', 'filled'];\n\n/** Relative \"Posted N days ago\" label from an ISO date-only string. */\nfunction relativePostedLabel(dateStr: string): string {\n const posted = new Date(dateStr);\n if (Number.isNaN(posted.getTime())) {\n return dateStr;\n }\n const days = Math.floor((Date.now() - posted.getTime()) / 86_400_000);\n if (days <= 0) {\n return 'Today';\n }\n if (days === 1) {\n return '1 day ago';\n }\n if (days < 7) {\n return `${days} days ago`;\n }\n if (days < 30) {\n const weeks = Math.floor(days / 7);\n return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;\n }\n const months = Math.floor(days / 30);\n return months === 1 ? '1 month ago' : `${months} months ago`;\n}\n\n/** A label→value row matching the mock's clean scannable card layout. */\nfunction InfoRow({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"text-text-secondary\">{label}</span>\n <span className=\"font-medium text-text-primary text-right\">{children}</span>\n </div>\n );\n}\n\nexport function HiringJobsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { jobPostings, candidates } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<string>('all');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const t = (key: string, fallback: string) => {\n const value = translate(key);\n return value === key ? fallback : value;\n };\n\n const filtered = useMemo(() => {\n const query = search.toLowerCase();\n return jobPostings.filter((j) => {\n const matchesSearch = `${j.title} ${j.department} ${j.location}`\n .toLowerCase()\n .includes(query);\n const matchesStatus = statusFilter === 'all' || j.status === statusFilter;\n return matchesSearch && matchesStatus;\n });\n }, [jobPostings, search, statusFilter]);\n\n const paged = filtered.slice(offset, offset + pageSize);\n const openJob = (id: string) =>\n navigate(`../hiring/job-details-page?id=${encodeURIComponent(id)}`);\n const activeJobs = jobPostings.filter((j) => j.status === 'active').length;\n const totalApplicants = jobPostings.reduce((s, j) => s + j.applicants, 0);\n\n // Live per-status counts drive the filter pills.\n const statusTabs = useMemo<SegmentedTab[]>(() => {\n const tabs: SegmentedTab[] = [{ id: 'all', label: 'All', count: jobPostings.length }];\n for (const status of STATUS_ORDER) {\n const count = jobPostings.filter((j) => j.status === status).length;\n if (count > 0 || status === 'active') {\n tabs.push({ id: status, label: formatEnumLabel(status), count });\n }\n }\n return tabs;\n }, [jobPostings]);\n\n // Real applicant faces per job, matched by the candidate's applied-for position.\n const facesByJob = useMemo(() => {\n const map = new Map<string, { name: string }[]>();\n for (const job of jobPostings) {\n const title = job.title.trim().toLowerCase();\n const people = candidates\n .filter((c) => c.position.trim().toLowerCase() === title)\n .map((c) => ({ name: `${c.firstName} ${c.lastName}`.trim() }));\n map.set(job.id, people);\n }\n return map;\n }, [jobPostings, candidates]);\n\n const setFilter = (id: string) => {\n setStatusFilter(id);\n setPage(1);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{t('crewfoundry.hiringJobs.title', 'Job Postings')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeJobs} {t('crewfoundry.hiringJobs.summary.activePositions', 'active positions')} ·{' '}\n {totalApplicants}{' '}\n {t('crewfoundry.hiringJobs.summary.totalApplicants', 'total applicants')}\n </p>\n <PagePurpose className=\"mt-1\">\n All your open and past job postings in one place — use it to monitor applicant volume\n and manage what roles you&apos;re actively hiring for.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => navigate('../hiring/create-job-page')}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('crewfoundry.hiringJobs.actions.postNewJob', 'Post New Job')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"-mx-1 overflow-x-auto px-1\">\n <SegmentedTabs\n tabs={statusTabs}\n value={statusFilter}\n onChange={setFilter}\n aria-label=\"Filter job postings by status\"\n />\n </div>\n <div className=\"relative w-full max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\" />\n <Input\n placeholder={t('crewfoundry.hiringJobs.searchPlaceholder', 'Search positions...')}\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n </div>\n\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job postings found\"\n description=\"Try adjusting your search or status filter, or create a new job posting.\"\n />\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {paged.map((job) => {\n const faces = facesByJob.get(job.id) ?? [];\n return (\n <GlassCard\n key={job.id}\n treatment=\"glass\"\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${job.title}`}\n className=\"flex cursor-pointer flex-col transition-shadow hover:shadow-elevation-2 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n onClick={() => openJob(job.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openJob(job.id);\n }\n }}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-lg leading-snug\">{job.title}</CardTitle>\n <Badge\n variant={\n job.status === 'active'\n ? 'default'\n : job.status === 'draft'\n ? 'secondary'\n : 'outline'\n }\n >\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.type)}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.experienceLevel)}\n </Badge>\n </div>\n </CardHeader>\n <CardContent className=\"flex flex-1 flex-col gap-2.5\">\n <InfoRow label={t('crewfoundry.hiringJobs.card.department', 'Department')}>\n <span className=\"inline-flex items-center gap-1\">\n <Briefcase className=\"h-3 w-3 text-text-secondary\" />\n {job.department || '—'}\n </span>\n </InfoRow>\n <InfoRow label={t('crewfoundry.hiringJobs.card.location', 'Location')}>\n <span className=\"inline-flex items-center gap-1\">\n <MapPin className=\"h-3 w-3 text-text-secondary\" />\n {job.location || '—'}\n </span>\n </InfoRow>\n <InfoRow label={t('crewfoundry.hiringJobs.card.salary', 'Salary')}>\n <span className=\"inline-flex items-center gap-0.5 tabular-nums\">\n <DollarSign className=\"h-3 w-3 text-text-secondary\" />\n {(job.salaryMin / 1000).toFixed(0)}k – ${(job.salaryMax / 1000).toFixed(0)}k\n </span>\n </InfoRow>\n <InfoRow label={t('crewfoundry.hiringJobs.card.applicantsLabel', 'Applicants')}>\n <span className=\"inline-flex items-center gap-2\">\n {faces.length > 0 && <AvatarStack people={faces} max={4} size={22} />}\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Users className=\"h-3 w-3 text-text-secondary\" />\n {job.applicants}\n </span>\n </span>\n </InfoRow>\n <div className=\"mt-auto pt-1\">\n <InfoRow label={t('crewfoundry.hiringJobs.card.posted', 'Posted')}>\n <span className=\"text-text-secondary font-normal\">\n {relativePostedLabel(job.postedDate)}\n </span>\n </InfoRow>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"HiringJobsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HiringJobsPagePage.tsx"],"sourcesContent":["import { useMemo, useState, type ReactNode } from 'react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n Badge,\n Button,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n Input,\n PagePurpose,\n Pagination,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { Briefcase, DollarSign, MapPin, Plus, Search, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useCrewFoundryStore, type JobPosting } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, SegmentedTabs, type SegmentedTab } from '../components/viz';\n\n/** Ordered status filter set (only rendered when a status has postings). */\nconst STATUS_ORDER: JobPosting['status'][] = ['active', 'draft', 'paused', 'closed', 'filled'];\n\n/** Relative \"Posted N days ago\" label from an ISO date-only string. */\nfunction relativePostedLabel(dateStr: string): string {\n const posted = new Date(dateStr);\n if (Number.isNaN(posted.getTime())) {\n return dateStr;\n }\n const days = Math.floor((Date.now() - posted.getTime()) / 86_400_000);\n if (days <= 0) {\n return 'Today';\n }\n if (days === 1) {\n return '1 day ago';\n }\n if (days < 7) {\n return `${days} days ago`;\n }\n if (days < 30) {\n const weeks = Math.floor(days / 7);\n return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;\n }\n const months = Math.floor(days / 30);\n return months === 1 ? '1 month ago' : `${months} months ago`;\n}\n\n/** A label→value row matching the mock's clean scannable card layout. */\nfunction InfoRow({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div className=\"flex items-center justify-between gap-3 text-sm\">\n <span className=\"text-text-secondary\">{label}</span>\n <span className=\"font-medium text-text-primary text-right\">{children}</span>\n </div>\n );\n}\n\nexport function HiringJobsPagePage() {\n const tr = useTr();\n const navigate = useNavigate();\n const { jobPostings, candidates } = useCrewFoundryStore();\n const [search, setSearch] = useState('');\n const [statusFilter, setStatusFilter] = useState<string>('all');\n const { page, pageSize, offset, setPage } = usePagination({ initialPageSize: 12 });\n const filtered = useMemo(() => {\n const query = search.toLowerCase();\n return jobPostings.filter((j) => {\n const matchesSearch = `${j.title} ${j.department} ${j.location}`\n .toLowerCase()\n .includes(query);\n const matchesStatus = statusFilter === 'all' || j.status === statusFilter;\n return matchesSearch && matchesStatus;\n });\n }, [jobPostings, search, statusFilter]);\n\n const paged = filtered.slice(offset, offset + pageSize);\n const openJob = (id: string) =>\n navigate(`../hiring/job-details-page?id=${encodeURIComponent(id)}`);\n const activeJobs = jobPostings.filter((j) => j.status === 'active').length;\n const totalApplicants = jobPostings.reduce((s, j) => s + j.applicants, 0);\n\n // Live per-status counts drive the filter pills.\n const statusTabs = useMemo<SegmentedTab[]>(() => {\n const tabs: SegmentedTab[] = [{ id: 'all', label: 'All', count: jobPostings.length }];\n for (const status of STATUS_ORDER) {\n const count = jobPostings.filter((j) => j.status === status).length;\n if (count > 0 || status === 'active') {\n tabs.push({ id: status, label: formatEnumLabel(status), count });\n }\n }\n return tabs;\n }, [jobPostings]);\n\n // Real applicant faces per job, matched by the candidate's applied-for position.\n const facesByJob = useMemo(() => {\n const map = new Map<string, { name: string }[]>();\n for (const job of jobPostings) {\n const title = job.title.trim().toLowerCase();\n const people = candidates\n .filter((c) => c.position.trim().toLowerCase() === title)\n .map((c) => ({ name: `${c.firstName} ${c.lastName}`.trim() }));\n map.set(job.id, people);\n }\n return map;\n }, [jobPostings, candidates]);\n\n const setFilter = (id: string) => {\n setStatusFilter(id);\n setPage(1);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{tr('crewfoundry.hiringJobs.title', 'Job Postings')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {activeJobs} {tr('crewfoundry.hiringJobs.summary.activePositions', 'active positions')}{' '}\n · {totalApplicants}{' '}\n {tr('crewfoundry.hiringJobs.summary.totalApplicants', 'total applicants')}\n </p>\n <PagePurpose className=\"mt-1\">\n All your open and past job postings in one place — use it to monitor applicant volume\n and manage what roles you&apos;re actively hiring for.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => navigate('../hiring/create-job-page')}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {tr('crewfoundry.hiringJobs.actions.postNewJob', 'Post New Job')}\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"-mx-1 overflow-x-auto px-1\">\n <SegmentedTabs\n tabs={statusTabs}\n value={statusFilter}\n onChange={setFilter}\n aria-label=\"Filter job postings by status\"\n />\n </div>\n <div className=\"relative w-full max-w-sm\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-text-secondary\" />\n <Input\n placeholder={tr('crewfoundry.hiringJobs.searchPlaceholder', 'Search positions...')}\n className=\"pl-9\"\n value={search}\n onChange={(e) => {\n setSearch(e.target.value);\n setPage(1);\n }}\n />\n </div>\n </div>\n\n {filtered.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title=\"No job postings found\"\n description=\"Try adjusting your search or status filter, or create a new job posting.\"\n />\n )}\n\n <div className=\"grid gap-4 md:grid-cols-2 xl:grid-cols-3\">\n {paged.map((job) => {\n const faces = facesByJob.get(job.id) ?? [];\n return (\n <GlassCard\n key={job.id}\n treatment=\"glass\"\n role=\"button\"\n tabIndex={0}\n aria-label={`View ${job.title}`}\n className=\"flex cursor-pointer flex-col transition-shadow hover:shadow-elevation-2 focus-visible:outline-none focus-visible:shadow-[var(--shadow-focus)]\"\n onClick={() => openJob(job.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n openJob(job.id);\n }\n }}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-lg leading-snug\">{job.title}</CardTitle>\n <Badge\n variant={\n job.status === 'active'\n ? 'default'\n : job.status === 'draft'\n ? 'secondary'\n : 'outline'\n }\n >\n {formatEnumLabel(job.status)}\n </Badge>\n </div>\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.type)}\n </Badge>\n <Badge variant=\"outline\" className=\"text-[10px]\">\n {formatEnumLabel(job.experienceLevel)}\n </Badge>\n </div>\n </CardHeader>\n <CardContent className=\"flex flex-1 flex-col gap-2.5\">\n <InfoRow label={tr('crewfoundry.hiringJobs.card.department', 'Department')}>\n <span className=\"inline-flex items-center gap-1\">\n <Briefcase className=\"h-3 w-3 text-text-secondary\" />\n {job.department || '—'}\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.location', 'Location')}>\n <span className=\"inline-flex items-center gap-1\">\n <MapPin className=\"h-3 w-3 text-text-secondary\" />\n {job.location || '—'}\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.salary', 'Salary')}>\n <span className=\"inline-flex items-center gap-0.5 tabular-nums\">\n <DollarSign className=\"h-3 w-3 text-text-secondary\" />\n {(job.salaryMin / 1000).toFixed(0)}k – ${(job.salaryMax / 1000).toFixed(0)}k\n </span>\n </InfoRow>\n <InfoRow label={tr('crewfoundry.hiringJobs.card.applicantsLabel', 'Applicants')}>\n <span className=\"inline-flex items-center gap-2\">\n {faces.length > 0 && <AvatarStack people={faces} max={4} size={22} />}\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Users className=\"h-3 w-3 text-text-secondary\" />\n {job.applicants}\n </span>\n </span>\n </InfoRow>\n <div className=\"mt-auto pt-1\">\n <InfoRow label={tr('crewfoundry.hiringJobs.card.posted', 'Posted')}>\n <span className=\"text-text-secondary font-normal\">\n {relativePostedLabel(job.postedDate)}\n </span>\n </InfoRow>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {filtered.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={filtered.length}\n onPageChange={setPage}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"MarketplaceMarketplacePagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/MarketplaceMarketplacePagePage.tsx"],"names":[],"mappings":"AAgDA,wBAAgB,8BAA8B,gCA0O7C"}
1
+ {"version":3,"file":"MarketplaceMarketplacePagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/MarketplaceMarketplacePagePage.tsx"],"names":[],"mappings":"AAgDA,wBAAgB,8BAA8B,gCAsO7C"}