@burdenoff/microfe-crewfoundry 2026.827.1 → 2026.830.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 (29) 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/HiringJobsPagePage.d.ts.map +1 -1
  14. package/dist/crewfoundry/pages/HiringJobsPagePage.js +75 -78
  15. package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
  16. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.d.ts.map +1 -1
  17. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +101 -104
  18. package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
  19. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.d.ts.map +1 -1
  20. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +95 -98
  21. package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
  22. package/dist/crewfoundry/pages/TeamsTeamsPagePage.d.ts.map +1 -1
  23. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +101 -104
  24. package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
  25. package/dist/shared/hooks/useTr.d.ts +17 -0
  26. package/dist/shared/hooks/useTr.d.ts.map +1 -0
  27. package/dist/shared/hooks/useTr.js +14 -0
  28. package/dist/shared/hooks/useTr.js.map +1 -0
  29. package/package.json +1 -1
@@ -1,16 +1,16 @@
1
1
  import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
2
2
  import { formatEnumLabel as t } from "../utils/formatEnumLabel.js";
3
- import { getInitials as n } from "../components/viz/lib/format.js";
4
- import { AvatarStack as r } from "../components/viz/AvatarStack.js";
3
+ import { useTr as n } from "../../shared/hooks/useTr.js";
4
+ import { getInitials as r } from "../components/viz/lib/format.js";
5
+ import { AvatarStack as i } from "../components/viz/AvatarStack.js";
5
6
  import "../components/viz/index.js";
6
- import { FormModal as i, useFormModal as a } from "../components/forms/FormModal.js";
7
- import { useMemo as o, useState as s } from "react";
8
- import { ArrowRight as c, FolderKanban as l, Plus as u, Users as d } from "lucide-react";
9
- import { useNavigate as f } from "react-router";
10
- import { jsx as p, jsxs as m } from "react/jsx-runtime";
11
- import { Badge as h, Button as g, CardContent as _, CardDescription as v, CardHeader as y, CardTitle as b, GlassCard as x, GradientText as S, IllustratedEmptyState as C, PagePurpose as w, Pagination as T } from "@burdenoff/fe-libs/ui";
12
- import { usePagination as E } from "@burdenoff/fe-libs/shared/hooks";
13
- import { useI18n as D } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
7
+ import { FormModal as a, useFormModal as o } from "../components/forms/FormModal.js";
8
+ import { useMemo as s, useState as c } from "react";
9
+ import { ArrowRight as l, FolderKanban as u, Plus as d, Users as f } from "lucide-react";
10
+ import { useNavigate as p } from "react-router";
11
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
12
+ import { Badge as g, Button as _, CardContent as v, CardDescription as y, CardHeader as b, CardTitle as x, GlassCard as S, GradientText as C, IllustratedEmptyState as w, PagePurpose as T, Pagination as E } from "@burdenoff/fe-libs/ui";
13
+ import { usePagination as D } from "@burdenoff/fe-libs/shared/hooks";
14
14
  //#region src/crewfoundry/pages/TeamsTeamsPagePage.tsx
15
15
  var O = [
16
16
  {
@@ -50,261 +50,258 @@ var O = [
50
50
  colSpan: 2
51
51
  }
52
52
  ];
53
- function k({ name: e, src: t, size: r = 32 }) {
54
- return t ? /* @__PURE__ */ p("img", {
53
+ function k({ name: e, src: t, size: n = 32 }) {
54
+ return t ? /* @__PURE__ */ m("img", {
55
55
  src: t,
56
56
  alt: e,
57
- width: r,
58
- height: r,
57
+ width: n,
58
+ height: n,
59
59
  loading: "lazy",
60
60
  className: "shrink-0 rounded-full object-cover",
61
61
  style: {
62
- width: r,
63
- height: r
62
+ width: n,
63
+ height: n
64
64
  }
65
- }) : /* @__PURE__ */ p("span", {
65
+ }) : /* @__PURE__ */ m("span", {
66
66
  "aria-hidden": "true",
67
67
  className: "inline-flex shrink-0 items-center justify-center rounded-full bg-bg-elevated font-medium text-text-secondary",
68
68
  style: {
69
- width: r,
70
- height: r,
71
- fontSize: Math.round(r * .36)
69
+ width: n,
70
+ height: n,
71
+ fontSize: Math.round(n * .36)
72
72
  },
73
- children: n(e)
73
+ children: r(e)
74
74
  });
75
75
  }
76
76
  function A(e) {
77
77
  return `${e.firstName} ${e.lastName}`.trim();
78
78
  }
79
79
  function j() {
80
- let { t: n } = D(), j = f(), { teams: M, employees: N, projects: P } = e(), F = e((e) => e.addTeam), I = a(), { page: L, pageSize: R, setPage: z } = E({ initialPageSize: 12 }), [B, V] = s(null), H = (e, t) => {
81
- let r = n(e);
82
- return r === e ? t : r;
83
- }, U = o(() => new Map(N.map((e) => [e.id, e])), [N]), W = o(() => {
80
+ let r = n(), j = p(), { teams: M, employees: N, projects: P } = e(), F = e((e) => e.addTeam), I = o(), { page: L, pageSize: R, setPage: z } = D({ initialPageSize: 12 }), [B, V] = c(null), H = s(() => new Map(N.map((e) => [e.id, e])), [N]), U = s(() => {
84
81
  let e = /* @__PURE__ */ new Map();
85
82
  for (let t of P) t.status === "active" && e.set(t.teamId, (e.get(t.teamId) ?? 0) + 1);
86
83
  return e;
87
- }, [P]), G = (e) => e.members.map((e) => U.get(e)).filter((e) => e !== void 0), K = (e) => G(e).map((e) => ({
84
+ }, [P]), W = (e) => e.members.map((e) => H.get(e)).filter((e) => e !== void 0), G = (e) => W(e).map((e) => ({
88
85
  name: A(e),
89
86
  avatarUrl: e.avatar,
90
87
  role: e.position
91
- })), q = M.slice((L - 1) * R, (L - 1) * R + R), J = M.find((e) => e.id === B) ?? q[0] ?? M[0] ?? null, Y = J ? G(J) : [], X = J ? P.filter((e) => e.teamId === J.id && e.status === "active") : [], Z = (e) => j(`../teams/team-detail-page?id=${encodeURIComponent(e.id)}`);
92
- return /* @__PURE__ */ m("div", {
88
+ })), K = M.slice((L - 1) * R, (L - 1) * R + R), q = M.find((e) => e.id === B) ?? K[0] ?? M[0] ?? null, J = q ? W(q) : [], Y = q ? P.filter((e) => e.teamId === q.id && e.status === "active") : [], X = (e) => j(`../teams/team-detail-page?id=${encodeURIComponent(e.id)}`);
89
+ return /* @__PURE__ */ h("div", {
93
90
  className: "space-y-6 p-6",
94
91
  children: [
95
- /* @__PURE__ */ m("div", {
92
+ /* @__PURE__ */ h("div", {
96
93
  className: "flex items-start justify-between gap-4",
97
- children: [/* @__PURE__ */ m("div", { children: [
98
- /* @__PURE__ */ p("h1", {
94
+ children: [/* @__PURE__ */ h("div", { children: [
95
+ /* @__PURE__ */ m("h1", {
99
96
  className: "text-2xl font-semibold",
100
- children: /* @__PURE__ */ p(S, { children: H("crewfoundry.teams.title", "Teams") })
97
+ children: /* @__PURE__ */ m(C, { children: r("crewfoundry.teams.title", "Teams") })
101
98
  }),
102
- /* @__PURE__ */ m("p", {
99
+ /* @__PURE__ */ h("p", {
103
100
  className: "text-sm text-text-secondary",
104
101
  children: [
105
102
  M.length,
106
103
  " ",
107
- H("crewfoundry.teams.summary.count", "teams across the organization")
104
+ r("crewfoundry.teams.summary.count", "teams across the organization")
108
105
  ]
109
106
  }),
110
- /* @__PURE__ */ p(w, {
107
+ /* @__PURE__ */ m(T, {
111
108
  className: "mt-1",
112
109
  children: "Manage your teams — create groups, assign members, and organize people around the work they do together."
113
110
  })
114
- ] }), /* @__PURE__ */ m(g, {
111
+ ] }), /* @__PURE__ */ h(_, {
115
112
  size: "sm",
116
113
  onClick: () => I.openModal(),
117
- children: [/* @__PURE__ */ p(u, { className: "h-4 w-4 mr-1" }), H("crewfoundry.teams.actions.createTeam", "Create Team")]
114
+ children: [/* @__PURE__ */ m(d, { className: "h-4 w-4 mr-1" }), r("crewfoundry.teams.actions.createTeam", "Create Team")]
118
115
  })]
119
116
  }),
120
- M.length === 0 ? /* @__PURE__ */ p(C, {
117
+ M.length === 0 ? /* @__PURE__ */ m(w, {
121
118
  illustration: "empty-people",
122
119
  title: "No teams yet",
123
120
  description: "Create a team to organize people around their work."
124
- }) : /* @__PURE__ */ m("div", {
121
+ }) : /* @__PURE__ */ h("div", {
125
122
  className: "grid gap-6 lg:grid-cols-3",
126
- children: [/* @__PURE__ */ m("div", {
123
+ children: [/* @__PURE__ */ h("div", {
127
124
  className: "space-y-4 lg:col-span-2",
128
- children: [/* @__PURE__ */ p("div", {
125
+ children: [/* @__PURE__ */ m("div", {
129
126
  className: "grid gap-4 sm:grid-cols-2",
130
- children: q.map((e) => {
131
- let n = e.leadId ? U.get(e.leadId) : void 0, i = W.get(e.id) ?? 0, a = J?.id === e.id;
132
- return /* @__PURE__ */ m(x, {
133
- treatment: a ? "glass" : "plain",
134
- glow: a,
127
+ children: K.map((e) => {
128
+ let n = e.leadId ? H.get(e.leadId) : void 0, a = U.get(e.id) ?? 0, o = q?.id === e.id;
129
+ return /* @__PURE__ */ h(S, {
130
+ treatment: o ? "glass" : "plain",
131
+ glow: o,
135
132
  role: "button",
136
133
  tabIndex: 0,
137
- "aria-pressed": a,
134
+ "aria-pressed": o,
138
135
  "aria-label": `Select ${e.name} team`,
139
136
  onClick: () => V(e.id),
140
137
  onKeyDown: (t) => {
141
138
  (t.key === "Enter" || t.key === " ") && (t.preventDefault(), V(e.id));
142
139
  },
143
140
  className: "cursor-pointer transition-shadow hover:shadow-md focus-visible:shadow-[var(--shadow-focus)] focus-visible:outline-none",
144
- children: [/* @__PURE__ */ m(y, {
141
+ children: [/* @__PURE__ */ h(b, {
145
142
  className: "pb-3",
146
- children: [/* @__PURE__ */ m("div", {
143
+ children: [/* @__PURE__ */ h("div", {
147
144
  className: "flex items-start justify-between gap-2",
148
- children: [/* @__PURE__ */ p(b, {
145
+ children: [/* @__PURE__ */ m(x, {
149
146
  className: "text-base",
150
147
  children: e.name
151
- }), /* @__PURE__ */ p(h, {
148
+ }), /* @__PURE__ */ m(g, {
152
149
  variant: e.status === "active" ? "default" : "secondary",
153
150
  className: "text-[10px]",
154
151
  children: t(e.status)
155
152
  })]
156
- }), /* @__PURE__ */ m("div", {
153
+ }), /* @__PURE__ */ h("div", {
157
154
  className: "flex items-center gap-2 text-xs text-text-secondary",
158
155
  children: [
159
- /* @__PURE__ */ p("span", { children: H("crewfoundry.teams.card.ledBy", "Led by") }),
160
- /* @__PURE__ */ p(k, {
156
+ /* @__PURE__ */ m("span", { children: r("crewfoundry.teams.card.ledBy", "Led by") }),
157
+ /* @__PURE__ */ m(k, {
161
158
  name: n ? A(n) : e.leadName,
162
159
  src: n?.avatar,
163
160
  size: 20
164
161
  }),
165
- /* @__PURE__ */ p("span", {
162
+ /* @__PURE__ */ m("span", {
166
163
  className: "font-medium text-text-primary",
167
164
  children: n ? A(n) : e.leadName || "—"
168
165
  })
169
166
  ]
170
167
  })]
171
- }), /* @__PURE__ */ m(_, {
168
+ }), /* @__PURE__ */ h(v, {
172
169
  className: "space-y-3",
173
- children: [/* @__PURE__ */ p(r, {
174
- people: K(e),
170
+ children: [/* @__PURE__ */ m(i, {
171
+ people: G(e),
175
172
  max: 5,
176
173
  size: 30
177
- }), /* @__PURE__ */ m("div", {
174
+ }), /* @__PURE__ */ h("div", {
178
175
  className: "flex items-center gap-4 text-sm text-text-secondary",
179
- children: [/* @__PURE__ */ m("span", {
176
+ children: [/* @__PURE__ */ h("span", {
180
177
  className: "flex items-center gap-1",
181
178
  children: [
182
- /* @__PURE__ */ p(d, { className: "h-3.5 w-3.5" }),
179
+ /* @__PURE__ */ m(f, { className: "h-3.5 w-3.5" }),
183
180
  e.memberCount,
184
181
  " ",
185
- H("crewfoundry.teams.card.members", "Members")
182
+ r("crewfoundry.teams.card.members", "Members")
186
183
  ]
187
- }), /* @__PURE__ */ m("span", {
184
+ }), /* @__PURE__ */ h("span", {
188
185
  className: "flex items-center gap-1",
189
186
  children: [
190
- /* @__PURE__ */ p(l, { className: "h-3.5 w-3.5" }),
191
- i,
187
+ /* @__PURE__ */ m(u, { className: "h-3.5 w-3.5" }),
188
+ a,
192
189
  " ",
193
- H("crewfoundry.teams.card.activeProjects", "Active Projects")
190
+ r("crewfoundry.teams.card.activeProjects", "Active Projects")
194
191
  ]
195
192
  })]
196
193
  })]
197
194
  })]
198
195
  }, e.id);
199
196
  })
200
- }), M.length > R && /* @__PURE__ */ p(T, {
197
+ }), M.length > R && /* @__PURE__ */ m(E, {
201
198
  page: L,
202
199
  pageSize: R,
203
200
  totalItems: M.length,
204
201
  onPageChange: z
205
202
  })]
206
- }), /* @__PURE__ */ p("div", {
203
+ }), /* @__PURE__ */ m("div", {
207
204
  className: "lg:col-span-1",
208
- children: J ? /* @__PURE__ */ m(x, {
205
+ children: q ? /* @__PURE__ */ h(S, {
209
206
  treatment: "glass",
210
207
  className: "border-border-seam lg:sticky lg:top-6",
211
208
  "aria-live": "polite",
212
- children: [/* @__PURE__ */ m(y, {
209
+ children: [/* @__PURE__ */ h(b, {
213
210
  className: "pb-3",
214
- children: [/* @__PURE__ */ m("div", {
211
+ children: [/* @__PURE__ */ h("div", {
215
212
  className: "flex items-start justify-between gap-2",
216
- children: [/* @__PURE__ */ p(b, {
213
+ children: [/* @__PURE__ */ m(x, {
217
214
  className: "text-lg",
218
- children: J.name
219
- }), /* @__PURE__ */ p(h, {
220
- variant: J.status === "active" ? "default" : "secondary",
215
+ children: q.name
216
+ }), /* @__PURE__ */ m(g, {
217
+ variant: q.status === "active" ? "default" : "secondary",
221
218
  className: "text-[10px]",
222
- children: t(J.status)
219
+ children: t(q.status)
223
220
  })]
224
- }), /* @__PURE__ */ p(v, { children: J.description })]
225
- }), /* @__PURE__ */ m(_, {
221
+ }), /* @__PURE__ */ m(y, { children: q.description })]
222
+ }), /* @__PURE__ */ h(v, {
226
223
  className: "space-y-5",
227
224
  children: [
228
- /* @__PURE__ */ m("section", { children: [/* @__PURE__ */ m("h3", {
225
+ /* @__PURE__ */ h("section", { children: [/* @__PURE__ */ h("h3", {
229
226
  className: "mb-2 text-sm font-semibold",
230
227
  children: [
231
- H("crewfoundry.teams.panel.members", "Members"),
228
+ r("crewfoundry.teams.panel.members", "Members"),
232
229
  " (",
233
- Y.length,
230
+ J.length,
234
231
  ")"
235
232
  ]
236
- }), Y.length === 0 ? /* @__PURE__ */ p("p", {
233
+ }), J.length === 0 ? /* @__PURE__ */ m("p", {
237
234
  className: "text-xs text-text-secondary",
238
235
  children: "No members have been added to this team yet."
239
- }) : /* @__PURE__ */ p("ul", {
236
+ }) : /* @__PURE__ */ m("ul", {
240
237
  className: "space-y-2 max-h-72 overflow-y-auto pr-1",
241
- children: Y.map((e) => /* @__PURE__ */ m("li", {
238
+ children: J.map((e) => /* @__PURE__ */ h("li", {
242
239
  className: "flex items-center gap-2.5",
243
- children: [/* @__PURE__ */ p(k, {
240
+ children: [/* @__PURE__ */ m(k, {
244
241
  name: A(e),
245
242
  src: e.avatar,
246
243
  size: 32
247
- }), /* @__PURE__ */ m("div", {
244
+ }), /* @__PURE__ */ h("div", {
248
245
  className: "min-w-0",
249
- children: [/* @__PURE__ */ m("p", {
246
+ children: [/* @__PURE__ */ h("p", {
250
247
  className: "truncate text-sm font-medium text-text-primary",
251
- children: [A(e), e.id === J.leadId ? /* @__PURE__ */ p("span", {
248
+ children: [A(e), e.id === q.leadId ? /* @__PURE__ */ m("span", {
252
249
  className: "ml-1.5 text-[10px] font-medium text-action-primary-text",
253
250
  children: "· Lead"
254
251
  }) : null]
255
- }), /* @__PURE__ */ p("p", {
252
+ }), /* @__PURE__ */ m("p", {
256
253
  className: "truncate text-xs text-text-secondary",
257
254
  children: e.position
258
255
  })]
259
256
  })]
260
257
  }, e.id))
261
258
  })] }),
262
- /* @__PURE__ */ m("section", { children: [/* @__PURE__ */ m("h3", {
259
+ /* @__PURE__ */ h("section", { children: [/* @__PURE__ */ h("h3", {
263
260
  className: "mb-2 text-sm font-semibold",
264
261
  children: [
265
- H("crewfoundry.teams.panel.activeProjects", "Active Projects"),
262
+ r("crewfoundry.teams.panel.activeProjects", "Active Projects"),
266
263
  " (",
267
- X.length,
264
+ Y.length,
268
265
  ")"
269
266
  ]
270
- }), X.length === 0 ? /* @__PURE__ */ p("p", {
267
+ }), Y.length === 0 ? /* @__PURE__ */ m("p", {
271
268
  className: "text-xs text-text-secondary",
272
269
  children: "No active projects for this team right now."
273
- }) : /* @__PURE__ */ p("ul", {
270
+ }) : /* @__PURE__ */ m("ul", {
274
271
  className: "space-y-3",
275
- children: X.map((e) => /* @__PURE__ */ m("li", { children: [/* @__PURE__ */ m("div", {
272
+ children: Y.map((e) => /* @__PURE__ */ h("li", { children: [/* @__PURE__ */ h("div", {
276
273
  className: "mb-1 flex items-center justify-between gap-2 text-xs",
277
- children: [/* @__PURE__ */ p("span", {
274
+ children: [/* @__PURE__ */ m("span", {
278
275
  className: "truncate font-medium text-text-primary",
279
276
  children: e.name
280
- }), /* @__PURE__ */ m("span", {
277
+ }), /* @__PURE__ */ h("span", {
281
278
  className: "shrink-0 tabular-nums text-text-secondary",
282
279
  children: [e.progress, "%"]
283
280
  })]
284
- }), /* @__PURE__ */ p("div", {
281
+ }), /* @__PURE__ */ m("div", {
285
282
  className: "h-1.5 overflow-hidden rounded-full bg-bg-sunken",
286
- children: /* @__PURE__ */ p("div", {
283
+ children: /* @__PURE__ */ m("div", {
287
284
  className: "h-1.5 rounded-full bg-action-primary-bg",
288
285
  style: { width: `${e.progress}%` }
289
286
  })
290
287
  })] }, e.id))
291
288
  })] }),
292
- /* @__PURE__ */ m(g, {
289
+ /* @__PURE__ */ h(_, {
293
290
  variant: "outline",
294
291
  size: "sm",
295
292
  className: "w-full",
296
- onClick: () => Z(J),
297
- children: [H("crewfoundry.teams.panel.openDetail", "Open full team detail"), /* @__PURE__ */ p(c, { className: "ml-1 h-4 w-4" })]
293
+ onClick: () => X(q),
294
+ children: [r("crewfoundry.teams.panel.openDetail", "Open full team detail"), /* @__PURE__ */ m(l, { className: "ml-1 h-4 w-4" })]
298
295
  })
299
296
  ]
300
297
  })]
301
298
  }) : null
302
299
  })]
303
300
  }),
304
- /* @__PURE__ */ p(i, {
301
+ /* @__PURE__ */ m(a, {
305
302
  open: I.open,
306
303
  onOpenChange: I.onOpenChange,
307
- title: H("crewfoundry.teams.actions.createTeam", "Create Team"),
304
+ title: r("crewfoundry.teams.actions.createTeam", "Create Team"),
308
305
  description: "Organize people around the work they do together.",
309
306
  fields: O,
310
307
  submitLabel: "Create Team",
@@ -1 +1 @@
1
- {"version":3,"file":"TeamsTeamsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/TeamsTeamsPagePage.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 { ArrowRight, FolderKanban, Plus, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useMemo, useState } from 'react';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Employee, type Team } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, getInitials, type Person } from '../components/viz';\n\nconst TEAM_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Team Name', type: 'text', required: true },\n { name: 'department', label: 'Department', type: 'text', placeholder: 'e.g. Engineering' },\n { name: 'leadName', label: 'Team Lead', type: 'text', placeholder: 'Lead full name' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Forming', value: 'forming' },\n ],\n },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n];\n\nfunction InitialsAvatar({ name, src, size = 32 }: { name: string; src?: string; size?: number }) {\n if (src) {\n return (\n <img\n src={src}\n alt={name}\n width={size}\n height={size}\n loading=\"lazy\"\n className=\"shrink-0 rounded-full object-cover\"\n style={{ width: size, height: size }}\n />\n );\n }\n return (\n <span\n aria-hidden=\"true\"\n className=\"inline-flex shrink-0 items-center justify-center rounded-full bg-bg-elevated font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n >\n {getInitials(name)}\n </span>\n );\n}\n\nfunction employeeName(employee: Employee): string {\n return `${employee.firstName} ${employee.lastName}`.trim();\n}\n\nexport function TeamsTeamsPagePage() {\n const { t: translate } = useI18n();\n const navigate = useNavigate();\n const { teams, employees, projects } = useCrewFoundryStore();\n const addTeam = useCrewFoundryStore((state) => state.addTeam);\n const createModal = useFormModal();\n const { page, pageSize, setPage } = usePagination({ initialPageSize: 12 });\n const [selectedId, setSelectedId] = useState<string | null>(null);\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\n const activeProjectsByTeam = useMemo(() => {\n const map = new Map<string, number>();\n for (const project of projects) {\n if (project.status === 'active') {\n map.set(project.teamId, (map.get(project.teamId) ?? 0) + 1);\n }\n }\n return map;\n }, [projects]);\n\n const membersOf = (team: Team): Employee[] =>\n team.members\n .map((memberId) => employeeById.get(memberId))\n .filter((employee): employee is Employee => employee !== undefined);\n\n const peopleOf = (team: Team): Person[] =>\n membersOf(team).map((employee) => ({\n name: employeeName(employee),\n avatarUrl: employee.avatar,\n role: employee.position,\n }));\n\n const pageTeams = teams.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);\n const selectedTeam =\n teams.find((team) => team.id === selectedId) ?? pageTeams[0] ?? teams[0] ?? null;\n const selectedMembers = selectedTeam ? membersOf(selectedTeam) : [];\n const selectedActiveProjects = selectedTeam\n ? projects.filter(\n (project) => project.teamId === selectedTeam.id && project.status === 'active'\n )\n : [];\n\n const openTeam = (team: Team) =>\n navigate(`../teams/team-detail-page?id=${encodeURIComponent(team.id)}`);\n\n const handleCreate = async (values: FormValues) => {\n // CreateCrewTeamInput accepts only this field subset; the store spreads the\n // object straight into `input`, so we send exactly the accepted shape and\n // let the server response replace the optimistic row.\n const payload: Partial<Team> = {\n name: String(values.name ?? '').trim(),\n description: String(values.description ?? '').trim(),\n department: String(values.department ?? '').trim() || 'operations',\n leadName: String(values.leadName ?? '').trim(),\n status: (String(values.status ?? 'forming') as Team['status']) || 'forming',\n members: [],\n };\n await addTeam(payload as Team);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{t('crewfoundry.teams.title', 'Teams')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {teams.length} {t('crewfoundry.teams.summary.count', 'teams across the organization')}\n </p>\n <PagePurpose className=\"mt-1\">\n Manage your teams — create groups, assign members, and organize people around the work\n they do together.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => createModal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {t('crewfoundry.teams.actions.createTeam', 'Create Team')}\n </Button>\n </div>\n\n {teams.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No teams yet\"\n description=\"Create a team to organize people around their work.\"\n />\n ) : (\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Team cards */}\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {pageTeams.map((team) => {\n const lead = team.leadId ? employeeById.get(team.leadId) : undefined;\n const activeCount = activeProjectsByTeam.get(team.id) ?? 0;\n const selected = selectedTeam?.id === team.id;\n return (\n <GlassCard\n key={team.id}\n treatment={selected ? 'glass' : 'plain'}\n glow={selected}\n role=\"button\"\n tabIndex={0}\n aria-pressed={selected}\n aria-label={`Select ${team.name} team`}\n onClick={() => setSelectedId(team.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setSelectedId(team.id);\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-start justify-between gap-2\">\n <CardTitle className=\"text-base\">{team.name}</CardTitle>\n <Badge\n variant={team.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(team.status)}\n </Badge>\n </div>\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span>{t('crewfoundry.teams.card.ledBy', 'Led by')}</span>\n <InitialsAvatar\n name={lead ? employeeName(lead) : team.leadName}\n src={lead?.avatar}\n size={20}\n />\n <span className=\"font-medium text-text-primary\">\n {lead ? employeeName(lead) : team.leadName || '—'}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <AvatarStack people={peopleOf(team)} max={5} size={30} />\n <div className=\"flex items-center gap-4 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" />\n {team.memberCount} {t('crewfoundry.teams.card.members', 'Members')}\n </span>\n <span className=\"flex items-center gap-1\">\n <FolderKanban className=\"h-3.5 w-3.5\" />\n {activeCount}{' '}\n {t('crewfoundry.teams.card.activeProjects', 'Active Projects')}\n </span>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {teams.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={teams.length}\n onPageChange={setPage}\n />\n )}\n </div>\n\n {/* Selected-team detail panel */}\n <div className=\"lg:col-span-1\">\n {selectedTeam ? (\n <GlassCard\n treatment=\"glass\"\n className=\"border-border-seam lg:sticky lg:top-6\"\n aria-live=\"polite\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-lg\">{selectedTeam.name}</CardTitle>\n <Badge\n variant={selectedTeam.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(selectedTeam.status)}\n </Badge>\n </div>\n <CardDescription>{selectedTeam.description}</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n <section>\n <h3 className=\"mb-2 text-sm font-semibold\">\n {t('crewfoundry.teams.panel.members', 'Members')} ({selectedMembers.length})\n </h3>\n {selectedMembers.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No members have been added to this team yet.\n </p>\n ) : (\n <ul className=\"space-y-2 max-h-72 overflow-y-auto pr-1\">\n {selectedMembers.map((member) => (\n <li key={member.id} className=\"flex items-center gap-2.5\">\n <InitialsAvatar\n name={employeeName(member)}\n src={member.avatar}\n size={32}\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {employeeName(member)}\n {member.id === selectedTeam.leadId ? (\n <span className=\"ml-1.5 text-[10px] font-medium text-action-primary-text\">\n · Lead\n </span>\n ) : null}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {member.position}\n </p>\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n\n <section>\n <h3 className=\"mb-2 text-sm font-semibold\">\n {t('crewfoundry.teams.panel.activeProjects', 'Active Projects')} (\n {selectedActiveProjects.length})\n </h3>\n {selectedActiveProjects.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No active projects for this team right now.\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {selectedActiveProjects.map((project) => (\n <li key={project.id}>\n <div className=\"mb-1 flex items-center justify-between gap-2 text-xs\">\n <span className=\"truncate font-medium text-text-primary\">\n {project.name}\n </span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {project.progress}%\n </span>\n </div>\n <div className=\"h-1.5 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 </li>\n ))}\n </ul>\n )}\n </section>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => openTeam(selectedTeam)}\n >\n {t('crewfoundry.teams.panel.openDetail', 'Open full team detail')}\n <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </CardContent>\n </GlassCard>\n ) : null}\n </div>\n </div>\n )}\n\n <FormModal\n open={createModal.open}\n onOpenChange={createModal.onOpenChange}\n title={t('crewfoundry.teams.actions.createTeam', 'Create Team')}\n description=\"Organize people around the work they do together.\"\n fields={TEAM_FIELDS}\n submitLabel=\"Create Team\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TeamsTeamsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/TeamsTeamsPagePage.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 { useTr } from '../../shared/hooks/useTr';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { ArrowRight, FolderKanban, Plus, Users } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { useMemo, useState } from 'react';\nimport {\n FormModal,\n useFormModal,\n type FieldConfig,\n type FormValues,\n} from '../components/forms/FormModal';\nimport { useCrewFoundryStore, type Employee, type Team } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { AvatarStack, getInitials, type Person } from '../components/viz';\n\nconst TEAM_FIELDS: FieldConfig[] = [\n { name: 'name', label: 'Team Name', type: 'text', required: true },\n { name: 'department', label: 'Department', type: 'text', placeholder: 'e.g. Engineering' },\n { name: 'leadName', label: 'Team Lead', type: 'text', placeholder: 'Lead full name' },\n {\n name: 'status',\n label: 'Status',\n type: 'select',\n options: [\n { label: 'Active', value: 'active' },\n { label: 'Forming', value: 'forming' },\n ],\n },\n { name: 'description', label: 'Description', type: 'textarea', colSpan: 2 },\n];\n\nfunction InitialsAvatar({ name, src, size = 32 }: { name: string; src?: string; size?: number }) {\n if (src) {\n return (\n <img\n src={src}\n alt={name}\n width={size}\n height={size}\n loading=\"lazy\"\n className=\"shrink-0 rounded-full object-cover\"\n style={{ width: size, height: size }}\n />\n );\n }\n return (\n <span\n aria-hidden=\"true\"\n className=\"inline-flex shrink-0 items-center justify-center rounded-full bg-bg-elevated font-medium text-text-secondary\"\n style={{ width: size, height: size, fontSize: Math.round(size * 0.36) }}\n >\n {getInitials(name)}\n </span>\n );\n}\n\nfunction employeeName(employee: Employee): string {\n return `${employee.firstName} ${employee.lastName}`.trim();\n}\n\nexport function TeamsTeamsPagePage() {\n const tr = useTr();\n const navigate = useNavigate();\n const { teams, employees, projects } = useCrewFoundryStore();\n const addTeam = useCrewFoundryStore((state) => state.addTeam);\n const createModal = useFormModal();\n const { page, pageSize, setPage } = usePagination({ initialPageSize: 12 });\n const [selectedId, setSelectedId] = useState<string | null>(null);\n\n const employeeById = useMemo(\n () => new Map(employees.map((employee) => [employee.id, employee])),\n [employees]\n );\n\n const activeProjectsByTeam = useMemo(() => {\n const map = new Map<string, number>();\n for (const project of projects) {\n if (project.status === 'active') {\n map.set(project.teamId, (map.get(project.teamId) ?? 0) + 1);\n }\n }\n return map;\n }, [projects]);\n\n const membersOf = (team: Team): Employee[] =>\n team.members\n .map((memberId) => employeeById.get(memberId))\n .filter((employee): employee is Employee => employee !== undefined);\n\n const peopleOf = (team: Team): Person[] =>\n membersOf(team).map((employee) => ({\n name: employeeName(employee),\n avatarUrl: employee.avatar,\n role: employee.position,\n }));\n\n const pageTeams = teams.slice((page - 1) * pageSize, (page - 1) * pageSize + pageSize);\n const selectedTeam =\n teams.find((team) => team.id === selectedId) ?? pageTeams[0] ?? teams[0] ?? null;\n const selectedMembers = selectedTeam ? membersOf(selectedTeam) : [];\n const selectedActiveProjects = selectedTeam\n ? projects.filter(\n (project) => project.teamId === selectedTeam.id && project.status === 'active'\n )\n : [];\n\n const openTeam = (team: Team) =>\n navigate(`../teams/team-detail-page?id=${encodeURIComponent(team.id)}`);\n\n const handleCreate = async (values: FormValues) => {\n // CreateCrewTeamInput accepts only this field subset; the store spreads the\n // object straight into `input`, so we send exactly the accepted shape and\n // let the server response replace the optimistic row.\n const payload: Partial<Team> = {\n name: String(values.name ?? '').trim(),\n description: String(values.description ?? '').trim(),\n department: String(values.department ?? '').trim() || 'operations',\n leadName: String(values.leadName ?? '').trim(),\n status: (String(values.status ?? 'forming') as Team['status']) || 'forming',\n members: [],\n };\n await addTeam(payload as Team);\n };\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-start justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>{tr('crewfoundry.teams.title', 'Teams')}</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n {teams.length} {tr('crewfoundry.teams.summary.count', 'teams across the organization')}\n </p>\n <PagePurpose className=\"mt-1\">\n Manage your teams — create groups, assign members, and organize people around the work\n they do together.\n </PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => createModal.openModal()}>\n <Plus className=\"h-4 w-4 mr-1\" />\n {tr('crewfoundry.teams.actions.createTeam', 'Create Team')}\n </Button>\n </div>\n\n {teams.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title=\"No teams yet\"\n description=\"Create a team to organize people around their work.\"\n />\n ) : (\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Team cards */}\n <div className=\"space-y-4 lg:col-span-2\">\n <div className=\"grid gap-4 sm:grid-cols-2\">\n {pageTeams.map((team) => {\n const lead = team.leadId ? employeeById.get(team.leadId) : undefined;\n const activeCount = activeProjectsByTeam.get(team.id) ?? 0;\n const selected = selectedTeam?.id === team.id;\n return (\n <GlassCard\n key={team.id}\n treatment={selected ? 'glass' : 'plain'}\n glow={selected}\n role=\"button\"\n tabIndex={0}\n aria-pressed={selected}\n aria-label={`Select ${team.name} team`}\n onClick={() => setSelectedId(team.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n setSelectedId(team.id);\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-start justify-between gap-2\">\n <CardTitle className=\"text-base\">{team.name}</CardTitle>\n <Badge\n variant={team.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(team.status)}\n </Badge>\n </div>\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span>{tr('crewfoundry.teams.card.ledBy', 'Led by')}</span>\n <InitialsAvatar\n name={lead ? employeeName(lead) : team.leadName}\n src={lead?.avatar}\n size={20}\n />\n <span className=\"font-medium text-text-primary\">\n {lead ? employeeName(lead) : team.leadName || '—'}\n </span>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <AvatarStack people={peopleOf(team)} max={5} size={30} />\n <div className=\"flex items-center gap-4 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1\">\n <Users className=\"h-3.5 w-3.5\" />\n {team.memberCount} {tr('crewfoundry.teams.card.members', 'Members')}\n </span>\n <span className=\"flex items-center gap-1\">\n <FolderKanban className=\"h-3.5 w-3.5\" />\n {activeCount}{' '}\n {tr('crewfoundry.teams.card.activeProjects', 'Active Projects')}\n </span>\n </div>\n </CardContent>\n </GlassCard>\n );\n })}\n </div>\n\n {teams.length > pageSize && (\n <Pagination\n page={page}\n pageSize={pageSize}\n totalItems={teams.length}\n onPageChange={setPage}\n />\n )}\n </div>\n\n {/* Selected-team detail panel */}\n <div className=\"lg:col-span-1\">\n {selectedTeam ? (\n <GlassCard\n treatment=\"glass\"\n className=\"border-border-seam lg:sticky lg:top-6\"\n aria-live=\"polite\"\n >\n <CardHeader className=\"pb-3\">\n <div className=\"flex items-start justify-between gap-2\">\n <CardTitle className=\"text-lg\">{selectedTeam.name}</CardTitle>\n <Badge\n variant={selectedTeam.status === 'active' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(selectedTeam.status)}\n </Badge>\n </div>\n <CardDescription>{selectedTeam.description}</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n <section>\n <h3 className=\"mb-2 text-sm font-semibold\">\n {tr('crewfoundry.teams.panel.members', 'Members')} ({selectedMembers.length})\n </h3>\n {selectedMembers.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No members have been added to this team yet.\n </p>\n ) : (\n <ul className=\"space-y-2 max-h-72 overflow-y-auto pr-1\">\n {selectedMembers.map((member) => (\n <li key={member.id} className=\"flex items-center gap-2.5\">\n <InitialsAvatar\n name={employeeName(member)}\n src={member.avatar}\n size={32}\n />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {employeeName(member)}\n {member.id === selectedTeam.leadId ? (\n <span className=\"ml-1.5 text-[10px] font-medium text-action-primary-text\">\n · Lead\n </span>\n ) : null}\n </p>\n <p className=\"truncate text-xs text-text-secondary\">\n {member.position}\n </p>\n </div>\n </li>\n ))}\n </ul>\n )}\n </section>\n\n <section>\n <h3 className=\"mb-2 text-sm font-semibold\">\n {tr('crewfoundry.teams.panel.activeProjects', 'Active Projects')} (\n {selectedActiveProjects.length})\n </h3>\n {selectedActiveProjects.length === 0 ? (\n <p className=\"text-xs text-text-secondary\">\n No active projects for this team right now.\n </p>\n ) : (\n <ul className=\"space-y-3\">\n {selectedActiveProjects.map((project) => (\n <li key={project.id}>\n <div className=\"mb-1 flex items-center justify-between gap-2 text-xs\">\n <span className=\"truncate font-medium text-text-primary\">\n {project.name}\n </span>\n <span className=\"shrink-0 tabular-nums text-text-secondary\">\n {project.progress}%\n </span>\n </div>\n <div className=\"h-1.5 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 </li>\n ))}\n </ul>\n )}\n </section>\n\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"w-full\"\n onClick={() => openTeam(selectedTeam)}\n >\n {tr('crewfoundry.teams.panel.openDetail', 'Open full team detail')}\n <ArrowRight className=\"ml-1 h-4 w-4\" />\n </Button>\n </CardContent>\n </GlassCard>\n ) : null}\n </div>\n </div>\n )}\n\n <FormModal\n open={createModal.open}\n onOpenChange={createModal.onOpenChange}\n title={tr('crewfoundry.teams.actions.createTeam', 'Create Team')}\n description=\"Organize people around the work they do together.\"\n fields={TEAM_FIELDS}\n submitLabel=\"Create Team\"\n onSubmit={handleCreate}\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * useTr — translation hook with fallback semantics.
3
+ *
4
+ * Wraps `useI18n().t()` so that when a key has no translation (i.e. the
5
+ * provider returns the key itself), the caller's English fallback is used
6
+ * instead. This keeps consumer call sites consistent and avoids leaking
7
+ * untranslated keys into the UI.
8
+ *
9
+ * Supports optional interpolation params using `{{key}}` placeholders.
10
+ *
11
+ * @example
12
+ * const tr = useTr();
13
+ * return <h1>{tr('crewfoundry.dashboard.title', 'Dashboard')}</h1>;
14
+ * return <p>{tr('crewfoundry.delete.confirm', 'Delete {{count}} items?', { count: 5 })}</p>;
15
+ */
16
+ export declare function useTr(): (key: string, fallback: string, params?: Record<string, string | number>) => string;
17
+ //# sourceMappingURL=useTr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.d.ts","sourceRoot":"","sources":["../../../src/shared/hooks/useTr.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,KAAK,IAAI,CACvB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,KACrC,MAAM,CAcV"}
@@ -0,0 +1,14 @@
1
+ import { useCallback as e } from "react";
2
+ import { useI18n as t } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
3
+ //#region src/shared/hooks/useTr.ts
4
+ function n() {
5
+ let { t: n } = t();
6
+ return e((e, t, r) => {
7
+ let i = n(e), a = i === e ? t : i;
8
+ return r ? Object.entries(r).reduce((e, [t, n]) => e.replace(RegExp(`\\{\\{${t}\\}\\}`, "g"), String(n)), a) : a;
9
+ }, [n]);
10
+ }
11
+ //#endregion
12
+ export { n as useTr };
13
+
14
+ //# sourceMappingURL=useTr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useTr.js","names":[],"sources":["../../../src/shared/hooks/useTr.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * useTr — translation hook with fallback semantics.\n *\n * Wraps `useI18n().t()` so that when a key has no translation (i.e. the\n * provider returns the key itself), the caller's English fallback is used\n * instead. This keeps consumer call sites consistent and avoids leaking\n * untranslated keys into the UI.\n *\n * Supports optional interpolation params using `{{key}}` placeholders.\n *\n * @example\n * const tr = useTr();\n * return <h1>{tr('crewfoundry.dashboard.title', 'Dashboard')}</h1>;\n * return <p>{tr('crewfoundry.delete.confirm', 'Delete {{count}} items?', { count: 5 })}</p>;\n */\nexport function useTr(): (\n key: string,\n fallback: string,\n params?: Record<string, string | number>\n) => string {\n const { t } = useI18n();\n return useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key);\n const raw = translated === key ? fallback : translated;\n if (!params) return raw;\n return Object.entries(params).reduce(\n (str, [k, v]) => str.replace(new RegExp(`\\\\{\\\\{${k}\\\\}\\\\}`, 'g'), String(v)),\n raw\n );\n },\n [t]\n );\n}\n"],"mappings":";;;AAkBA,SAAgB,IAIJ;CACV,IAAM,EAAE,SAAM,GAAS;AACvB,QAAO,GACJ,GAAa,GAAkB,MAAqD;EACnF,IAAM,IAAa,EAAE,EAAI,EACnB,IAAM,MAAe,IAAM,IAAW;AAE5C,SADK,IACE,OAAO,QAAQ,EAAO,CAAC,QAC3B,GAAK,CAAC,GAAG,OAAO,EAAI,QAAY,OAAO,SAAS,EAAE,SAAS,IAAI,EAAE,OAAO,EAAE,CAAC,EAC5E,EACD,GAJmB;IAMtB,CAAC,EAAE,CACJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-crewfoundry",
3
- "version": "2026.827.1",
3
+ "version": "2026.830.1",
4
4
  "description": "Burdenoff CrewFoundry Microfrontend",
5
5
  "type": "module",
6
6
  "files": [