@burdenoff/microfe-crewfoundry 2026.723.2 → 2026.731.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.
- package/dist/crewfoundry/components/payroll/GlobalPayrollSection.d.ts +2 -0
- package/dist/crewfoundry/components/payroll/GlobalPayrollSection.d.ts.map +1 -0
- package/dist/crewfoundry/components/payroll/GlobalPayrollSection.js +369 -0
- package/dist/crewfoundry/components/payroll/GlobalPayrollSection.js.map +1 -0
- package/dist/crewfoundry/components/viz/SkillBar.js +43 -3
- package/dist/crewfoundry/components/viz/SkillBar.js.map +1 -0
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +219 -122
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrPayrollPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HrPayrollPagePage.js +136 -134
- package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.d.ts +87 -0
- package/dist/crewfoundry/store/crewfoundryStore.d.ts.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js +126 -49
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalPayrollSection.d.ts","sourceRoot":"","sources":["../../../../src/crewfoundry/components/payroll/GlobalPayrollSection.tsx"],"names":[],"mappings":"AAwEA,wBAAgB,oBAAoB,gCA8YnC"}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
import { useCrewFoundryStore as e } from "../../store/crewfoundryStore.js";
|
|
2
|
+
import { useMemo as t, useState as n } from "react";
|
|
3
|
+
import { Calculator as r, CheckCircle2 as i, FileText as a, Play as o, Plus as s } from "lucide-react";
|
|
4
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
5
|
+
import { Badge as u, Button as d, Card as f, CardContent as p, CardDescription as ee, CardHeader as te, CardTitle as ne, Checkbox as re, Input as ie, Label as m, ResponsiveDialog as h, ResponsiveDialogContent as g, ResponsiveDialogDescription as _, ResponsiveDialogFooter as v, ResponsiveDialogHeader as y, ResponsiveDialogTitle as b, ResponsiveTable as x, Select as ae, SelectContent as oe, SelectItem as S, SelectTrigger as se, SelectValue as ce, Separator as le } from "@burdenoff/fe-libs/ui";
|
|
6
|
+
import { useGlobalUi as ue } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
7
|
+
//#region src/crewfoundry/components/payroll/GlobalPayrollSection.tsx
|
|
8
|
+
var C = {
|
|
9
|
+
US: "United States (federal, single filer)",
|
|
10
|
+
IN: "India (new tax regime)",
|
|
11
|
+
GB: "United Kingdom (PAYE)"
|
|
12
|
+
}, de = {
|
|
13
|
+
DRAFT: "outline",
|
|
14
|
+
CALCULATED: "secondary",
|
|
15
|
+
APPROVED: "default",
|
|
16
|
+
CLOSED: "default"
|
|
17
|
+
}, w = (e, t) => {
|
|
18
|
+
try {
|
|
19
|
+
return new Intl.NumberFormat("en-US", {
|
|
20
|
+
style: "currency",
|
|
21
|
+
currency: t,
|
|
22
|
+
maximumFractionDigits: 0
|
|
23
|
+
}).format(e);
|
|
24
|
+
} catch {
|
|
25
|
+
return `${t} ${e.toLocaleString()}`;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
function T() {
|
|
29
|
+
let { employees: T, payrollRuns: E, payrollRunsLoaded: fe, payrollRunsLoading: D, payslipsByRun: O, payslipsLoading: k, fetchPayrollRuns: A, createPayrollRun: j, calculatePayrollRun: M, approvePayrollRun: N, fetchPayslipsForRun: P } = e(), { showToast: F } = ue(), [I, L] = n(!1), [R, z] = n("US"), [B, V] = n(""), [H, U] = n(/* @__PURE__ */ new Set()), [W, G] = n(!1), [K, q] = n(null), [J, Y] = n(null), [pe, me] = n(!1);
|
|
30
|
+
!fe && !D && !pe && (me(!0), A().catch(() => {}));
|
|
31
|
+
let X = t(() => T.filter((e) => e.status === "active"), [T]), he = [
|
|
32
|
+
{
|
|
33
|
+
key: "period",
|
|
34
|
+
header: "Period",
|
|
35
|
+
priority: "primary",
|
|
36
|
+
cell: (e) => /* @__PURE__ */ l("div", { children: [/* @__PURE__ */ c("div", {
|
|
37
|
+
className: "font-medium",
|
|
38
|
+
children: e.period
|
|
39
|
+
}), /* @__PURE__ */ c("div", {
|
|
40
|
+
className: "text-xs text-text-secondary",
|
|
41
|
+
children: C[e.country]
|
|
42
|
+
})] })
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
key: "status",
|
|
46
|
+
header: "Status",
|
|
47
|
+
cell: (e) => /* @__PURE__ */ c(u, {
|
|
48
|
+
variant: de[e.status],
|
|
49
|
+
children: e.status
|
|
50
|
+
})
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
key: "employees",
|
|
54
|
+
header: "Employees",
|
|
55
|
+
headerClassName: "text-right",
|
|
56
|
+
cellClassName: "md:text-right",
|
|
57
|
+
cell: (e) => /* @__PURE__ */ c("span", { children: e.employeeCount })
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
key: "gross",
|
|
61
|
+
header: "Total Gross",
|
|
62
|
+
headerClassName: "text-right",
|
|
63
|
+
cellClassName: "md:text-right",
|
|
64
|
+
cell: (e) => /* @__PURE__ */ c("span", { children: e.totalGrossPay > 0 ? w(e.totalGrossPay, e.currency) : "—" })
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
key: "net",
|
|
68
|
+
header: "Total Net",
|
|
69
|
+
headerClassName: "text-right",
|
|
70
|
+
cellClassName: "md:text-right",
|
|
71
|
+
cell: (e) => /* @__PURE__ */ c("span", { children: e.totalNetPay > 0 ? w(e.totalNetPay, e.currency) : "—" })
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
key: "actions",
|
|
75
|
+
header: "",
|
|
76
|
+
cell: (e) => /* @__PURE__ */ l("div", {
|
|
77
|
+
className: "flex flex-wrap justify-end gap-2",
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ l(d, {
|
|
80
|
+
variant: "outline",
|
|
81
|
+
size: "sm",
|
|
82
|
+
onClick: () => {
|
|
83
|
+
Y(e.id), P(e.id);
|
|
84
|
+
},
|
|
85
|
+
children: [/* @__PURE__ */ c(a, { className: "mr-1.5 size-3.5" }), "Payslips"]
|
|
86
|
+
}),
|
|
87
|
+
(e.status === "DRAFT" || e.status === "CALCULATED") && /* @__PURE__ */ l(d, {
|
|
88
|
+
variant: "outline",
|
|
89
|
+
size: "sm",
|
|
90
|
+
disabled: K === e.id,
|
|
91
|
+
onClick: async () => {
|
|
92
|
+
q(e.id);
|
|
93
|
+
try {
|
|
94
|
+
await M(e.id), F("Payroll run calculated", "success");
|
|
95
|
+
} catch (e) {
|
|
96
|
+
F(e instanceof Error ? e.message : "Calculation failed", "error");
|
|
97
|
+
} finally {
|
|
98
|
+
q(null);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
children: [/* @__PURE__ */ c(r, { className: "mr-1.5 size-3.5" }), "Calculate"]
|
|
102
|
+
}),
|
|
103
|
+
e.status === "CALCULATED" && /* @__PURE__ */ l(d, {
|
|
104
|
+
size: "sm",
|
|
105
|
+
disabled: K === e.id,
|
|
106
|
+
onClick: async () => {
|
|
107
|
+
q(e.id);
|
|
108
|
+
try {
|
|
109
|
+
await N(e.id), F("Payroll run approved", "success");
|
|
110
|
+
} catch (e) {
|
|
111
|
+
F(e instanceof Error ? e.message : "Approval failed", "error");
|
|
112
|
+
} finally {
|
|
113
|
+
q(null);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
children: [/* @__PURE__ */ c(i, { className: "mr-1.5 size-3.5" }), "Approve"]
|
|
117
|
+
})
|
|
118
|
+
]
|
|
119
|
+
})
|
|
120
|
+
}
|
|
121
|
+
], Z = (e) => {
|
|
122
|
+
U((t) => {
|
|
123
|
+
let n = new Set(t);
|
|
124
|
+
return n.has(e) ? n.delete(e) : n.add(e), n;
|
|
125
|
+
});
|
|
126
|
+
}, ge = () => {
|
|
127
|
+
z("US"), V(""), U(/* @__PURE__ */ new Set());
|
|
128
|
+
}, _e = async () => {
|
|
129
|
+
if (!B.trim()) {
|
|
130
|
+
F("Enter a pay period (e.g. 2026-07)", "error");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (H.size === 0) {
|
|
134
|
+
F("Select at least one employee", "error");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
G(!0);
|
|
138
|
+
try {
|
|
139
|
+
let e = X.filter((e) => H.has(e.id));
|
|
140
|
+
await j({
|
|
141
|
+
country: R,
|
|
142
|
+
period: B.trim(),
|
|
143
|
+
employees: e.map((e) => ({
|
|
144
|
+
employeeId: e.id,
|
|
145
|
+
employeeName: `${e.firstName} ${e.lastName}`,
|
|
146
|
+
annualGrossSalary: e.salary
|
|
147
|
+
}))
|
|
148
|
+
}), F("Payroll run created — click Calculate to run the engine", "success"), L(!1), ge();
|
|
149
|
+
} catch (e) {
|
|
150
|
+
F(e instanceof Error ? e.message : "Unable to create payroll run", "error");
|
|
151
|
+
} finally {
|
|
152
|
+
G(!1);
|
|
153
|
+
}
|
|
154
|
+
}, Q = E.find((e) => e.id === J) ?? null, $ = J ? O[J] ?? [] : [];
|
|
155
|
+
return /* @__PURE__ */ l(f, { children: [
|
|
156
|
+
/* @__PURE__ */ c(te, { children: /* @__PURE__ */ l("div", {
|
|
157
|
+
className: "flex flex-wrap items-start justify-between gap-3",
|
|
158
|
+
children: [/* @__PURE__ */ l("div", { children: [/* @__PURE__ */ l("div", {
|
|
159
|
+
className: "flex items-center gap-2",
|
|
160
|
+
children: [/* @__PURE__ */ c(ne, { children: "Global Payroll" }), /* @__PURE__ */ c(u, {
|
|
161
|
+
variant: "outline",
|
|
162
|
+
children: "Beta — 3 countries"
|
|
163
|
+
})]
|
|
164
|
+
}), /* @__PURE__ */ c(ee, { children: "Native gross-to-net calculation engine for US, India, and UK reference tax rules. Not a licensed EOR — no statutory filing, remittance, or payment execution." })] }), /* @__PURE__ */ l(d, {
|
|
165
|
+
size: "sm",
|
|
166
|
+
onClick: () => L(!0),
|
|
167
|
+
children: [/* @__PURE__ */ c(s, { className: "mr-1.5 size-3.5" }), "New Payroll Run"]
|
|
168
|
+
})]
|
|
169
|
+
}) }),
|
|
170
|
+
/* @__PURE__ */ c(p, { children: /* @__PURE__ */ c(x, {
|
|
171
|
+
data: E,
|
|
172
|
+
rowKey: (e) => e.id,
|
|
173
|
+
columns: he,
|
|
174
|
+
emptyState: /* @__PURE__ */ c("p", {
|
|
175
|
+
className: "py-6 text-center text-sm text-text-secondary",
|
|
176
|
+
children: D ? "Loading payroll runs…" : "No payroll runs yet. Create one for a supported country to calculate gross-to-net pay."
|
|
177
|
+
})
|
|
178
|
+
}) }),
|
|
179
|
+
/* @__PURE__ */ c(h, {
|
|
180
|
+
open: I,
|
|
181
|
+
onOpenChange: L,
|
|
182
|
+
children: /* @__PURE__ */ l(g, {
|
|
183
|
+
className: "max-h-[85vh] overflow-y-auto sm:max-w-[560px]",
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ l(y, { children: [/* @__PURE__ */ c(b, { children: "New payroll run" }), /* @__PURE__ */ c(_, { children: "Select the reference country, pay period, and employees to include. Employee salary is treated as their reference annual gross for the calculation." })] }),
|
|
186
|
+
/* @__PURE__ */ l("div", {
|
|
187
|
+
className: "space-y-4",
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ l("div", {
|
|
190
|
+
className: "grid grid-cols-1 gap-4 md:grid-cols-2",
|
|
191
|
+
children: [/* @__PURE__ */ l("div", {
|
|
192
|
+
className: "flex flex-col gap-1.5",
|
|
193
|
+
children: [/* @__PURE__ */ c(m, {
|
|
194
|
+
htmlFor: "payroll-run-country",
|
|
195
|
+
children: "Country"
|
|
196
|
+
}), /* @__PURE__ */ l(ae, {
|
|
197
|
+
value: R,
|
|
198
|
+
onValueChange: (e) => z(e),
|
|
199
|
+
children: [/* @__PURE__ */ c(se, {
|
|
200
|
+
id: "payroll-run-country",
|
|
201
|
+
children: /* @__PURE__ */ c(ce, {})
|
|
202
|
+
}), /* @__PURE__ */ l(oe, { children: [
|
|
203
|
+
/* @__PURE__ */ c(S, {
|
|
204
|
+
value: "US",
|
|
205
|
+
children: C.US
|
|
206
|
+
}),
|
|
207
|
+
/* @__PURE__ */ c(S, {
|
|
208
|
+
value: "IN",
|
|
209
|
+
children: C.IN
|
|
210
|
+
}),
|
|
211
|
+
/* @__PURE__ */ c(S, {
|
|
212
|
+
value: "GB",
|
|
213
|
+
children: C.GB
|
|
214
|
+
})
|
|
215
|
+
] })]
|
|
216
|
+
})]
|
|
217
|
+
}), /* @__PURE__ */ l("div", {
|
|
218
|
+
className: "flex flex-col gap-1.5",
|
|
219
|
+
children: [/* @__PURE__ */ c(m, {
|
|
220
|
+
htmlFor: "payroll-run-period",
|
|
221
|
+
children: "Pay period"
|
|
222
|
+
}), /* @__PURE__ */ c(ie, {
|
|
223
|
+
id: "payroll-run-period",
|
|
224
|
+
placeholder: "e.g. 2026-07",
|
|
225
|
+
value: B,
|
|
226
|
+
onChange: (e) => V(e.target.value)
|
|
227
|
+
})]
|
|
228
|
+
})]
|
|
229
|
+
}),
|
|
230
|
+
/* @__PURE__ */ c(le, {}),
|
|
231
|
+
/* @__PURE__ */ l("div", {
|
|
232
|
+
className: "space-y-1.5",
|
|
233
|
+
children: [/* @__PURE__ */ l(m, { children: [
|
|
234
|
+
"Employees (",
|
|
235
|
+
H.size,
|
|
236
|
+
" selected)"
|
|
237
|
+
] }), /* @__PURE__ */ c("div", {
|
|
238
|
+
className: "max-h-64 space-y-1 overflow-y-auto rounded-input border border-border-default p-2",
|
|
239
|
+
children: X.length === 0 ? /* @__PURE__ */ c("p", {
|
|
240
|
+
className: "p-2 text-sm text-text-secondary",
|
|
241
|
+
children: "No active employees to add."
|
|
242
|
+
}) : X.map((e) => /* @__PURE__ */ l("label", {
|
|
243
|
+
className: "flex cursor-pointer items-center justify-between gap-3 rounded-input px-2 py-1.5 hover:bg-bg-subtle",
|
|
244
|
+
children: [/* @__PURE__ */ l("span", {
|
|
245
|
+
className: "flex items-center gap-2 text-sm",
|
|
246
|
+
children: [
|
|
247
|
+
/* @__PURE__ */ c(re, {
|
|
248
|
+
checked: H.has(e.id),
|
|
249
|
+
onCheckedChange: () => Z(e.id)
|
|
250
|
+
}),
|
|
251
|
+
e.firstName,
|
|
252
|
+
" ",
|
|
253
|
+
e.lastName,
|
|
254
|
+
/* @__PURE__ */ l("span", {
|
|
255
|
+
className: "text-text-secondary",
|
|
256
|
+
children: ["— ", e.position]
|
|
257
|
+
})
|
|
258
|
+
]
|
|
259
|
+
}), /* @__PURE__ */ l("span", {
|
|
260
|
+
className: "text-xs text-text-secondary",
|
|
261
|
+
children: [
|
|
262
|
+
"$",
|
|
263
|
+
e.salary.toLocaleString(),
|
|
264
|
+
"/yr"
|
|
265
|
+
]
|
|
266
|
+
})]
|
|
267
|
+
}, e.id))
|
|
268
|
+
})]
|
|
269
|
+
})
|
|
270
|
+
]
|
|
271
|
+
}),
|
|
272
|
+
/* @__PURE__ */ l(v, { children: [/* @__PURE__ */ c(d, {
|
|
273
|
+
variant: "outline",
|
|
274
|
+
onClick: () => L(!1),
|
|
275
|
+
disabled: W,
|
|
276
|
+
children: "Cancel"
|
|
277
|
+
}), /* @__PURE__ */ c(d, {
|
|
278
|
+
onClick: _e,
|
|
279
|
+
disabled: W,
|
|
280
|
+
children: W ? "Creating…" : "Create run"
|
|
281
|
+
})] })
|
|
282
|
+
]
|
|
283
|
+
})
|
|
284
|
+
}),
|
|
285
|
+
/* @__PURE__ */ c(h, {
|
|
286
|
+
open: J !== null,
|
|
287
|
+
onOpenChange: (e) => !e && Y(null),
|
|
288
|
+
children: /* @__PURE__ */ l(g, {
|
|
289
|
+
className: "max-h-[85vh] overflow-y-auto sm:max-w-[720px]",
|
|
290
|
+
children: [
|
|
291
|
+
/* @__PURE__ */ l(y, { children: [/* @__PURE__ */ l(b, { children: [
|
|
292
|
+
"Payslips —",
|
|
293
|
+
" ",
|
|
294
|
+
Q ? `${Q.period} (${C[Q.country]})` : ""
|
|
295
|
+
] }), /* @__PURE__ */ c(_, { children: "Bracket-by-bracket breakdown from the native calculation engine — not a certified payslip document." })] }),
|
|
296
|
+
/* @__PURE__ */ c("div", {
|
|
297
|
+
className: "space-y-4",
|
|
298
|
+
children: k && $.length === 0 ? /* @__PURE__ */ c("p", {
|
|
299
|
+
className: "py-6 text-center text-sm text-text-secondary",
|
|
300
|
+
children: "Loading payslips…"
|
|
301
|
+
}) : $.length === 0 ? /* @__PURE__ */ c("p", {
|
|
302
|
+
className: "py-6 text-center text-sm text-text-secondary",
|
|
303
|
+
children: "No payslips yet — click Calculate on this run first."
|
|
304
|
+
}) : $.map((e) => /* @__PURE__ */ l("div", {
|
|
305
|
+
className: "rounded-lg border border-border-default p-4",
|
|
306
|
+
children: [
|
|
307
|
+
/* @__PURE__ */ l("div", {
|
|
308
|
+
className: "flex flex-wrap items-center justify-between gap-2",
|
|
309
|
+
children: [/* @__PURE__ */ c("span", {
|
|
310
|
+
className: "font-medium",
|
|
311
|
+
children: e.employeeName
|
|
312
|
+
}), /* @__PURE__ */ l("span", {
|
|
313
|
+
className: "text-sm text-text-secondary",
|
|
314
|
+
children: [
|
|
315
|
+
"Gross ",
|
|
316
|
+
w(e.grossPay, e.currency),
|
|
317
|
+
" · Net",
|
|
318
|
+
" ",
|
|
319
|
+
w(e.netPay, e.currency)
|
|
320
|
+
]
|
|
321
|
+
})]
|
|
322
|
+
}),
|
|
323
|
+
/* @__PURE__ */ l("div", {
|
|
324
|
+
className: "mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-sm md:grid-cols-4",
|
|
325
|
+
children: [
|
|
326
|
+
/* @__PURE__ */ l("span", { children: [/* @__PURE__ */ c("span", {
|
|
327
|
+
className: "text-text-secondary",
|
|
328
|
+
children: "Taxable "
|
|
329
|
+
}), w(e.taxableIncome, e.currency)] }),
|
|
330
|
+
/* @__PURE__ */ l("span", { children: [/* @__PURE__ */ c("span", {
|
|
331
|
+
className: "text-text-secondary",
|
|
332
|
+
children: "Income tax "
|
|
333
|
+
}), w(e.incomeTax, e.currency)] }),
|
|
334
|
+
/* @__PURE__ */ l("span", { children: [/* @__PURE__ */ c("span", {
|
|
335
|
+
className: "text-text-secondary",
|
|
336
|
+
children: "Employee contrib. "
|
|
337
|
+
}), w(e.employeeSocialContrib, e.currency)] }),
|
|
338
|
+
/* @__PURE__ */ l("span", { children: [/* @__PURE__ */ c("span", {
|
|
339
|
+
className: "text-text-secondary",
|
|
340
|
+
children: "Other deductions "
|
|
341
|
+
}), w(e.otherDeductions, e.currency)] })
|
|
342
|
+
]
|
|
343
|
+
}),
|
|
344
|
+
e.assumptions.length > 0 && /* @__PURE__ */ c("ul", {
|
|
345
|
+
className: "mt-3 list-disc space-y-0.5 pl-4 text-xs text-text-secondary",
|
|
346
|
+
children: e.assumptions.map((e) => /* @__PURE__ */ c("li", { children: e }, e))
|
|
347
|
+
})
|
|
348
|
+
]
|
|
349
|
+
}, e.id))
|
|
350
|
+
}),
|
|
351
|
+
/* @__PURE__ */ l(v, { children: [/* @__PURE__ */ c(d, {
|
|
352
|
+
variant: "outline",
|
|
353
|
+
onClick: () => Y(null),
|
|
354
|
+
children: "Close"
|
|
355
|
+
}), Q && (Q.status === "DRAFT" || Q.status === "CALCULATED") && /* @__PURE__ */ l(d, {
|
|
356
|
+
onClick: async () => {
|
|
357
|
+
await M(Q.id), await P(Q.id);
|
|
358
|
+
},
|
|
359
|
+
children: [/* @__PURE__ */ c(o, { className: "mr-1.5 size-3.5" }), Q.status === "CALCULATED" ? "Recalculate" : "Calculate"]
|
|
360
|
+
})] })
|
|
361
|
+
]
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
] });
|
|
365
|
+
}
|
|
366
|
+
//#endregion
|
|
367
|
+
export { T as GlobalPayrollSection };
|
|
368
|
+
|
|
369
|
+
//# sourceMappingURL=GlobalPayrollSection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GlobalPayrollSection.js","names":[],"sources":["../../../../src/crewfoundry/components/payroll/GlobalPayrollSection.tsx"],"sourcesContent":["/**\n * Global Payroll (Beta) — BOFF-4193.\n *\n * Native gross-to-net calculation core for 3 reference countries (US\n * federal, India new-regime, UK PAYE). This is deliberately NOT a licensed\n * EOR/statutory-filing platform — see\n * wspace-crewfoundry-svc/src/modules/workforce/services/payroll/README.md\n * for the full scope note (no filing/remittance, no payment-rail\n * integration, 3-country coverage only). The badges/copy in this component\n * must stay honest about that boundary.\n */\nimport { useMemo, useState } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Checkbox,\n Input,\n Label,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveTable,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Separator,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { useGlobalUi } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { Calculator, CheckCircle2, FileText, Play, Plus } from 'lucide-react';\nimport {\n useCrewFoundryStore,\n type CrewPayrollRun,\n type CrewPayslip,\n} from '../../store/crewfoundryStore';\n\nconst COUNTRY_LABEL: Record<CrewPayrollRun['country'], string> = {\n US: 'United States (federal, single filer)',\n IN: 'India (new tax regime)',\n GB: 'United Kingdom (PAYE)',\n};\n\nconst STATUS_BADGE_VARIANT: Record<CrewPayrollRun['status'], 'default' | 'secondary' | 'outline'> =\n {\n DRAFT: 'outline',\n CALCULATED: 'secondary',\n APPROVED: 'default',\n CLOSED: 'default',\n };\n\nconst formatMoney = (amount: number, currency: string) => {\n try {\n return new Intl.NumberFormat('en-US', {\n style: 'currency',\n currency,\n maximumFractionDigits: 0,\n }).format(amount);\n } catch {\n return `${currency} ${amount.toLocaleString()}`;\n }\n};\n\nexport function GlobalPayrollSection() {\n const {\n employees,\n payrollRuns,\n payrollRunsLoaded,\n payrollRunsLoading,\n payslipsByRun,\n payslipsLoading,\n fetchPayrollRuns,\n createPayrollRun,\n calculatePayrollRun,\n approvePayrollRun,\n fetchPayslipsForRun,\n } = useCrewFoundryStore();\n const { showToast } = useGlobalUi();\n\n const [createOpen, setCreateOpen] = useState(false);\n const [country, setCountry] = useState<CrewPayrollRun['country']>('US');\n const [period, setPeriod] = useState('');\n const [selectedEmployeeIds, setSelectedEmployeeIds] = useState<Set<string>>(new Set());\n const [submitting, setSubmitting] = useState(false);\n const [busyRunId, setBusyRunId] = useState<string | null>(null);\n const [viewingRunId, setViewingRunId] = useState<string | null>(null);\n\n // Lazy-load on first interaction rather than on every page mount — payroll\n // runs are gated by the `crewfoundry-payroll` RBAC resource, so a caller\n // without that grant should never see a failed background fetch.\n const [triedLoad, setTriedLoad] = useState(false);\n if (!payrollRunsLoaded && !payrollRunsLoading && !triedLoad) {\n setTriedLoad(true);\n void fetchPayrollRuns().catch(() => {\n // Best-effort — a caller without payroll access simply sees an empty\n // section rather than an error banner.\n });\n }\n\n const activeEmployees = useMemo(\n () => employees.filter((e) => e.status === 'active'),\n [employees]\n );\n\n const runColumns: ResponsiveTableColumn<CrewPayrollRun>[] = [\n {\n key: 'period',\n header: 'Period',\n priority: 'primary',\n cell: (run) => (\n <div>\n <div className=\"font-medium\">{run.period}</div>\n <div className=\"text-xs text-text-secondary\">{COUNTRY_LABEL[run.country]}</div>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cell: (run) => <Badge variant={STATUS_BADGE_VARIANT[run.status]}>{run.status}</Badge>,\n },\n {\n key: 'employees',\n header: 'Employees',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (run) => <span>{run.employeeCount}</span>,\n },\n {\n key: 'gross',\n header: 'Total Gross',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (run) => (\n <span>{run.totalGrossPay > 0 ? formatMoney(run.totalGrossPay, run.currency) : '—'}</span>\n ),\n },\n {\n key: 'net',\n header: 'Total Net',\n headerClassName: 'text-right',\n cellClassName: 'md:text-right',\n cell: (run) => (\n <span>{run.totalNetPay > 0 ? formatMoney(run.totalNetPay, run.currency) : '—'}</span>\n ),\n },\n {\n key: 'actions',\n header: '',\n cell: (run) => (\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n setViewingRunId(run.id);\n void fetchPayslipsForRun(run.id);\n }}\n >\n <FileText className=\"mr-1.5 size-3.5\" />\n Payslips\n </Button>\n {(run.status === 'DRAFT' || run.status === 'CALCULATED') && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n disabled={busyRunId === run.id}\n onClick={async () => {\n setBusyRunId(run.id);\n try {\n await calculatePayrollRun(run.id);\n showToast('Payroll run calculated', 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Calculation failed', 'error');\n } finally {\n setBusyRunId(null);\n }\n }}\n >\n <Calculator className=\"mr-1.5 size-3.5\" />\n Calculate\n </Button>\n )}\n {run.status === 'CALCULATED' && (\n <Button\n size=\"sm\"\n disabled={busyRunId === run.id}\n onClick={async () => {\n setBusyRunId(run.id);\n try {\n await approvePayrollRun(run.id);\n showToast('Payroll run approved', 'success');\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Approval failed', 'error');\n } finally {\n setBusyRunId(null);\n }\n }}\n >\n <CheckCircle2 className=\"mr-1.5 size-3.5\" />\n Approve\n </Button>\n )}\n </div>\n ),\n },\n ];\n\n const toggleEmployee = (id: string) => {\n setSelectedEmployeeIds((prev) => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n return next;\n });\n };\n\n const resetCreateForm = () => {\n setCountry('US');\n setPeriod('');\n setSelectedEmployeeIds(new Set());\n };\n\n const handleCreate = async () => {\n if (!period.trim()) {\n showToast('Enter a pay period (e.g. 2026-07)', 'error');\n return;\n }\n if (selectedEmployeeIds.size === 0) {\n showToast('Select at least one employee', 'error');\n return;\n }\n setSubmitting(true);\n try {\n const selectedEmployees = activeEmployees.filter((e) => selectedEmployeeIds.has(e.id));\n await createPayrollRun({\n country,\n period: period.trim(),\n employees: selectedEmployees.map((e) => ({\n employeeId: e.id,\n employeeName: `${e.firstName} ${e.lastName}`,\n // Employee.salary is treated as the reference ANNUAL gross salary\n // for this calculation — see the payroll engine README for the\n // per-country simplifying assumptions applied on top of it.\n annualGrossSalary: e.salary,\n })),\n });\n showToast('Payroll run created — click Calculate to run the engine', 'success');\n setCreateOpen(false);\n resetCreateForm();\n } catch (error) {\n showToast(error instanceof Error ? error.message : 'Unable to create payroll run', 'error');\n } finally {\n setSubmitting(false);\n }\n };\n\n const viewingRun = payrollRuns.find((r) => r.id === viewingRunId) ?? null;\n const viewingPayslips: CrewPayslip[] = viewingRunId ? (payslipsByRun[viewingRunId] ?? []) : [];\n\n return (\n <Card>\n <CardHeader>\n <div className=\"flex flex-wrap items-start justify-between gap-3\">\n <div>\n <div className=\"flex items-center gap-2\">\n <CardTitle>Global Payroll</CardTitle>\n <Badge variant=\"outline\">Beta — 3 countries</Badge>\n </div>\n <CardDescription>\n Native gross-to-net calculation engine for US, India, and UK reference tax rules. Not\n a licensed EOR — no statutory filing, remittance, or payment execution.\n </CardDescription>\n </div>\n <Button size=\"sm\" onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-1.5 size-3.5\" />\n New Payroll Run\n </Button>\n </div>\n </CardHeader>\n <CardContent>\n <ResponsiveTable<CrewPayrollRun>\n data={payrollRuns}\n rowKey={(run) => run.id}\n columns={runColumns}\n emptyState={\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n {payrollRunsLoading\n ? 'Loading payroll runs…'\n : 'No payroll runs yet. Create one for a supported country to calculate gross-to-net pay.'}\n </p>\n }\n />\n </CardContent>\n\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-[560px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New payroll run</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Select the reference country, pay period, and employees to include. Employee salary is\n treated as their reference annual gross for the calculation.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"payroll-run-country\">Country</Label>\n <Select\n value={country}\n onValueChange={(v) => setCountry(v as CrewPayrollRun['country'])}\n >\n <SelectTrigger id=\"payroll-run-country\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"US\">{COUNTRY_LABEL.US}</SelectItem>\n <SelectItem value=\"IN\">{COUNTRY_LABEL.IN}</SelectItem>\n <SelectItem value=\"GB\">{COUNTRY_LABEL.GB}</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"payroll-run-period\">Pay period</Label>\n <Input\n id=\"payroll-run-period\"\n placeholder=\"e.g. 2026-07\"\n value={period}\n onChange={(e) => setPeriod(e.target.value)}\n />\n </div>\n </div>\n\n <Separator />\n\n <div className=\"space-y-1.5\">\n <Label>Employees ({selectedEmployeeIds.size} selected)</Label>\n <div className=\"max-h-64 space-y-1 overflow-y-auto rounded-input border border-border-default p-2\">\n {activeEmployees.length === 0 ? (\n <p className=\"p-2 text-sm text-text-secondary\">No active employees to add.</p>\n ) : (\n activeEmployees.map((employee) => (\n <label\n key={employee.id}\n className=\"flex cursor-pointer items-center justify-between gap-3 rounded-input px-2 py-1.5 hover:bg-bg-subtle\"\n >\n <span className=\"flex items-center gap-2 text-sm\">\n <Checkbox\n checked={selectedEmployeeIds.has(employee.id)}\n onCheckedChange={() => toggleEmployee(employee.id)}\n />\n {employee.firstName} {employee.lastName}\n <span className=\"text-text-secondary\">— {employee.position}</span>\n </span>\n <span className=\"text-xs text-text-secondary\">\n ${employee.salary.toLocaleString()}/yr\n </span>\n </label>\n ))\n )}\n </div>\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setCreateOpen(false)} disabled={submitting}>\n Cancel\n </Button>\n <Button onClick={handleCreate} disabled={submitting}>\n {submitting ? 'Creating…' : 'Create run'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n\n <ResponsiveDialog\n open={viewingRunId !== null}\n onOpenChange={(open) => !open && setViewingRunId(null)}\n >\n <ResponsiveDialogContent className=\"max-h-[85vh] overflow-y-auto sm:max-w-[720px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n Payslips —{' '}\n {viewingRun ? `${viewingRun.period} (${COUNTRY_LABEL[viewingRun.country]})` : ''}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Bracket-by-bracket breakdown from the native calculation engine — not a certified\n payslip document.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <div className=\"space-y-4\">\n {payslipsLoading && viewingPayslips.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">Loading payslips…</p>\n ) : viewingPayslips.length === 0 ? (\n <p className=\"py-6 text-center text-sm text-text-secondary\">\n No payslips yet — click Calculate on this run first.\n </p>\n ) : (\n viewingPayslips.map((slip) => (\n <div key={slip.id} className=\"rounded-lg border border-border-default p-4\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <span className=\"font-medium\">{slip.employeeName}</span>\n <span className=\"text-sm text-text-secondary\">\n Gross {formatMoney(slip.grossPay, slip.currency)} · Net{' '}\n {formatMoney(slip.netPay, slip.currency)}\n </span>\n </div>\n <div className=\"mt-2 grid grid-cols-2 gap-x-4 gap-y-1 text-sm md:grid-cols-4\">\n <span>\n <span className=\"text-text-secondary\">Taxable </span>\n {formatMoney(slip.taxableIncome, slip.currency)}\n </span>\n <span>\n <span className=\"text-text-secondary\">Income tax </span>\n {formatMoney(slip.incomeTax, slip.currency)}\n </span>\n <span>\n <span className=\"text-text-secondary\">Employee contrib. </span>\n {formatMoney(slip.employeeSocialContrib, slip.currency)}\n </span>\n <span>\n <span className=\"text-text-secondary\">Other deductions </span>\n {formatMoney(slip.otherDeductions, slip.currency)}\n </span>\n </div>\n {slip.assumptions.length > 0 && (\n <ul className=\"mt-3 list-disc space-y-0.5 pl-4 text-xs text-text-secondary\">\n {slip.assumptions.map((assumption) => (\n <li key={assumption}>{assumption}</li>\n ))}\n </ul>\n )}\n </div>\n ))\n )}\n </div>\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setViewingRunId(null)}>\n Close\n </Button>\n {viewingRun &&\n (viewingRun.status === 'DRAFT' || viewingRun.status === 'CALCULATED') && (\n <Button\n onClick={async () => {\n await calculatePayrollRun(viewingRun.id);\n await fetchPayslipsForRun(viewingRun.id);\n }}\n >\n <Play className=\"mr-1.5 size-3.5\" />\n {viewingRun.status === 'CALCULATED' ? 'Recalculate' : 'Calculate'}\n </Button>\n )}\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </Card>\n );\n}\n"],"mappings":";;;;;;;AA8CA,IAAM,IAA2D;CAC/D,IAAI;CACJ,IAAI;CACJ,IAAI;CACL,EAEK,KACJ;CACE,OAAO;CACP,YAAY;CACZ,UAAU;CACV,QAAQ;CACT,EAEG,KAAe,GAAgB,MAAqB;AACxD,KAAI;AACF,SAAO,IAAI,KAAK,aAAa,SAAS;GACpC,OAAO;GACP;GACA,uBAAuB;GACxB,CAAC,CAAC,OAAO,EAAO;SACX;AACN,SAAO,GAAG,EAAS,GAAG,EAAO,gBAAgB;;;AAIjD,SAAgB,IAAuB;CACrC,IAAM,EACJ,cACA,gBACA,uBACA,uBACA,kBACA,oBACA,qBACA,qBACA,wBACA,sBACA,2BACE,GAAqB,EACnB,EAAE,iBAAc,IAAa,EAE7B,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAS,KAAc,EAAoC,KAAK,EACjE,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAqB,KAA0B,kBAAsB,IAAI,KAAK,CAAC,EAChF,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAW,KAAgB,EAAwB,KAAK,EACzD,CAAC,GAAc,KAAmB,EAAwB,KAAK,EAK/D,CAAC,IAAW,MAAgB,EAAS,GAAM;AACjD,CAAI,CAAC,MAAqB,CAAC,KAAsB,CAAC,OAChD,GAAa,GAAK,EACb,GAAkB,CAAC,YAAY,GAGlC;CAGJ,IAAM,IAAkB,QAChB,EAAU,QAAQ,MAAM,EAAE,WAAW,SAAS,EACpD,CAAC,EAAU,CACZ,EAEK,KAAsD;EAC1D;GACE,KAAK;GACL,QAAQ;GACR,UAAU;GACV,OAAO,MACL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;cAAe,EAAI;IAAa,CAAA,EAC/C,kBAAC,OAAD;IAAK,WAAU;cAA+B,EAAc,EAAI;IAAe,CAAA,CAC3E,EAAA,CAAA;GAET;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO,MAAQ,kBAAC,GAAD;IAAO,SAAS,GAAqB,EAAI;cAAU,EAAI;IAAe,CAAA;GACtF;EACD;GACE,KAAK;GACL,QAAQ;GACR,iBAAiB;GACjB,eAAe;GACf,OAAO,MAAQ,kBAAC,QAAD,EAAA,UAAO,EAAI,eAAqB,CAAA;GAChD;EACD;GACE,KAAK;GACL,QAAQ;GACR,iBAAiB;GACjB,eAAe;GACf,OAAO,MACL,kBAAC,QAAD,EAAA,UAAO,EAAI,gBAAgB,IAAI,EAAY,EAAI,eAAe,EAAI,SAAS,GAAG,KAAW,CAAA;GAE5F;EACD;GACE,KAAK;GACL,QAAQ;GACR,iBAAiB;GACjB,eAAe;GACf,OAAO,MACL,kBAAC,QAAD,EAAA,UAAO,EAAI,cAAc,IAAI,EAAY,EAAI,aAAa,EAAI,SAAS,GAAG,KAAW,CAAA;GAExF;EACD;GACE,KAAK;GACL,QAAQ;GACR,OAAO,MACL,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MACE,SAAQ;MACR,MAAK;MACL,eAAe;AAER,OADL,EAAgB,EAAI,GAAG,EAClB,EAAoB,EAAI,GAAG;;gBALpC,CAQE,kBAAC,GAAD,EAAU,WAAU,mBAAoB,CAAA,EAAA,WAEjC;;MACP,EAAI,WAAW,WAAW,EAAI,WAAW,iBACzC,kBAAC,GAAD;MACE,SAAQ;MACR,MAAK;MACL,UAAU,MAAc,EAAI;MAC5B,SAAS,YAAY;AACnB,SAAa,EAAI,GAAG;AACpB,WAAI;AAEF,QADA,MAAM,EAAoB,EAAI,GAAG,EACjC,EAAU,0BAA0B,UAAU;gBACvC,GAAO;AACd,UAAU,aAAiB,QAAQ,EAAM,UAAU,sBAAsB,QAAQ;iBACzE;AACR,UAAa,KAAK;;;gBAZxB,CAgBE,kBAAC,GAAD,EAAY,WAAU,mBAAoB,CAAA,EAAA,YAEnC;;KAEV,EAAI,WAAW,gBACd,kBAAC,GAAD;MACE,MAAK;MACL,UAAU,MAAc,EAAI;MAC5B,SAAS,YAAY;AACnB,SAAa,EAAI,GAAG;AACpB,WAAI;AAEF,QADA,MAAM,EAAkB,EAAI,GAAG,EAC/B,EAAU,wBAAwB,UAAU;gBACrC,GAAO;AACd,UAAU,aAAiB,QAAQ,EAAM,UAAU,mBAAmB,QAAQ;iBACtE;AACR,UAAa,KAAK;;;gBAXxB,CAeE,kBAAC,GAAD,EAAc,WAAU,mBAAoB,CAAA,EAAA,UAErC;;KAEP;;GAET;EACF,EAEK,KAAkB,MAAe;AACrC,KAAwB,MAAS;GAC/B,IAAM,IAAO,IAAI,IAAI,EAAK;AAM1B,UALI,EAAK,IAAI,EAAG,GACd,EAAK,OAAO,EAAG,GAEf,EAAK,IAAI,EAAG,EAEP;IACP;IAGE,WAAwB;AAG5B,EAFA,EAAW,KAAK,EAChB,EAAU,GAAG,EACb,kBAAuB,IAAI,KAAK,CAAC;IAG7B,KAAe,YAAY;AAC/B,MAAI,CAAC,EAAO,MAAM,EAAE;AAClB,KAAU,qCAAqC,QAAQ;AACvD;;AAEF,MAAI,EAAoB,SAAS,GAAG;AAClC,KAAU,gCAAgC,QAAQ;AAClD;;AAEF,IAAc,GAAK;AACnB,MAAI;GACF,IAAM,IAAoB,EAAgB,QAAQ,MAAM,EAAoB,IAAI,EAAE,GAAG,CAAC;AAetF,GAdA,MAAM,EAAiB;IACrB;IACA,QAAQ,EAAO,MAAM;IACrB,WAAW,EAAkB,KAAK,OAAO;KACvC,YAAY,EAAE;KACd,cAAc,GAAG,EAAE,UAAU,GAAG,EAAE;KAIlC,mBAAmB,EAAE;KACtB,EAAE;IACJ,CAAC,EACF,EAAU,2DAA2D,UAAU,EAC/E,EAAc,GAAM,EACpB,IAAiB;WACV,GAAO;AACd,KAAU,aAAiB,QAAQ,EAAM,UAAU,gCAAgC,QAAQ;YACnF;AACR,KAAc,GAAM;;IAIlB,IAAa,EAAY,MAAM,MAAM,EAAE,OAAO,EAAa,IAAI,MAC/D,IAAiC,IAAgB,EAAc,MAAiB,EAAE,GAAI,EAAE;AAE9F,QACE,kBAAC,GAAD,EAAA,UAAA;EACE,kBAAC,IAAD,EAAA,UACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,IAAD,EAAA,UAAW,kBAA0B,CAAA,EACrC,kBAAC,GAAD;KAAO,SAAQ;eAAU;KAA0B,CAAA,CAC/C;OACN,kBAAC,IAAD,EAAA,UAAiB,iKAGC,CAAA,CACd,EAAA,CAAA,EACN,kBAAC,GAAD;IAAQ,MAAK;IAAK,eAAe,EAAc,GAAK;cAApD,CACE,kBAAC,GAAD,EAAM,WAAU,mBAAoB,CAAA,EAAA,kBAE7B;MACL;MACK,CAAA;EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GACE,MAAM;GACN,SAAS,MAAQ,EAAI;GACrB,SAAS;GACT,YACE,kBAAC,KAAD;IAAG,WAAU;cACV,IACG,0BACA;IACF,CAAA;GAEN,CAAA,EACU,CAAA;EAEd,kBAAC,GAAD;GAAkB,MAAM;GAAY,cAAc;aAChD,kBAAC,GAAD;IAAyB,WAAU;cAAnC;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,mBAAuC,CAAA,EAC9D,kBAAC,GAAD,EAAA,UAA6B,uJAGC,CAAA,CACP,EAAA,CAAA;KAEzB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAsB;UAAe,CAAA,EACpD,kBAAC,IAAD;UACE,OAAO;UACP,gBAAgB,MAAM,EAAW,EAA+B;oBAFlE,CAIE,kBAAC,IAAD;WAAe,IAAG;qBAChB,kBAAC,IAAD,EAAe,CAAA;WACD,CAAA,EAChB,kBAAC,IAAD,EAAA,UAAA;WACE,kBAAC,GAAD;YAAY,OAAM;sBAAM,EAAc;YAAgB,CAAA;WACtD,kBAAC,GAAD;YAAY,OAAM;sBAAM,EAAc;YAAgB,CAAA;WACtD,kBAAC,GAAD;YAAY,OAAM;sBAAM,EAAc;YAAgB,CAAA;WACxC,EAAA,CAAA,CACT;YACL;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAqB;UAAkB,CAAA,EACtD,kBAAC,IAAD;UACE,IAAG;UACH,aAAY;UACZ,OAAO;UACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;UAC1C,CAAA,CACE;WACF;;OAEN,kBAAC,IAAD,EAAa,CAAA;OAEb,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAA,UAAA;SAAO;SAAY,EAAoB;SAAK;SAAkB,EAAA,CAAA,EAC9D,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAgB,WAAW,IAC1B,kBAAC,KAAD;UAAG,WAAU;oBAAkC;UAA+B,CAAA,GAE9E,EAAgB,KAAK,MACnB,kBAAC,SAAD;UAEE,WAAU;oBAFZ,CAIE,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YACE,kBAAC,IAAD;aACE,SAAS,EAAoB,IAAI,EAAS,GAAG;aAC7C,uBAAuB,EAAe,EAAS,GAAG;aAClD,CAAA;YACD,EAAS;YAAU;YAAE,EAAS;YAC/B,kBAAC,QAAD;aAAM,WAAU;uBAAhB,CAAsC,MAAG,EAAS,SAAgB;;YAC7D;cACP,kBAAC,QAAD;WAAM,WAAU;qBAAhB;YAA8C;YAC1C,EAAS,OAAO,gBAAgB;YAAC;YAC9B;aACD;YAdD,EAAS,GAcR,CACR;SAEA,CAAA,CACF;;OACF;;KAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,eAAe,EAAc,GAAM;MAAE,UAAU;gBAAY;MAE5E,CAAA,EACT,kBAAC,GAAD;MAAQ,SAAS;MAAc,UAAU;gBACtC,IAAa,cAAc;MACrB,CAAA,CACc,EAAA,CAAA;KACD;;GACT,CAAA;EAEnB,kBAAC,GAAD;GACE,MAAM,MAAiB;GACvB,eAAe,MAAS,CAAC,KAAQ,EAAgB,KAAK;aAEtD,kBAAC,GAAD;IAAyB,WAAU;cAAnC;KACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA;MAAuB;MACV;MACV,IAAa,GAAG,EAAW,OAAO,IAAI,EAAc,EAAW,SAAS,KAAK;MACxD,EAAA,CAAA,EACxB,kBAAC,GAAD,EAAA,UAA6B,uGAGC,CAAA,CACP,EAAA,CAAA;KACzB,kBAAC,OAAD;MAAK,WAAU;gBACZ,KAAmB,EAAgB,WAAW,IAC7C,kBAAC,KAAD;OAAG,WAAU;iBAA+C;OAAqB,CAAA,GAC/E,EAAgB,WAAW,IAC7B,kBAAC,KAAD;OAAG,WAAU;iBAA+C;OAExD,CAAA,GAEJ,EAAgB,KAAK,MACnB,kBAAC,OAAD;OAAmB,WAAU;iBAA7B;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAAe,EAAK;UAAoB,CAAA,EACxD,kBAAC,QAAD;UAAM,WAAU;oBAAhB;WAA8C;WACrC,EAAY,EAAK,UAAU,EAAK,SAAS;WAAC;WAAO;WACvD,EAAY,EAAK,QAAQ,EAAK,SAAS;WACnC;YACH;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAsB;WAAe,CAAA,EACpD,EAAY,EAAK,eAAe,EAAK,SAAS,CAC1C,EAAA,CAAA;UACP,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAsB;WAAkB,CAAA,EACvD,EAAY,EAAK,WAAW,EAAK,SAAS,CACtC,EAAA,CAAA;UACP,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAsB;WAAyB,CAAA,EAC9D,EAAY,EAAK,uBAAuB,EAAK,SAAS,CAClD,EAAA,CAAA;UACP,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,QAAD;WAAM,WAAU;qBAAsB;WAAwB,CAAA,EAC7D,EAAY,EAAK,iBAAiB,EAAK,SAAS,CAC5C,EAAA,CAAA;UACH;;QACL,EAAK,YAAY,SAAS,KACzB,kBAAC,MAAD;SAAI,WAAU;mBACX,EAAK,YAAY,KAAK,MACrB,kBAAC,MAAD,EAAA,UAAsB,GAAgB,EAA7B,EAA6B,CACtC;SACC,CAAA;QAEH;SAjCI,EAAK,GAiCT,CACN;MAEA,CAAA;KACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,eAAe,EAAgB,KAAK;gBAAE;MAEvD,CAAA,EACR,MACE,EAAW,WAAW,WAAW,EAAW,WAAW,iBACtD,kBAAC,GAAD;MACE,SAAS,YAAY;AAEnB,OADA,MAAM,EAAoB,EAAW,GAAG,EACxC,MAAM,EAAoB,EAAW,GAAG;;gBAH5C,CAME,kBAAC,GAAD,EAAM,WAAU,mBAAoB,CAAA,EACnC,EAAW,WAAW,eAAe,gBAAgB,YAC/C;QAEU,EAAA,CAAA;KACD;;GACT,CAAA;EACd,EAAA,CAAA"}
|
|
@@ -1,3 +1,43 @@
|
|
|
1
|
-
import "./lib/palette.js";
|
|
2
|
-
import "
|
|
3
|
-
import "
|
|
1
|
+
import { ACCENT as e } from "./lib/palette.js";
|
|
2
|
+
import { cn as t } from "../../utils/cn.js";
|
|
3
|
+
import { useMounted as n, usePrefersReducedMotion as r } from "./lib/hooks.js";
|
|
4
|
+
import { clamp as i } from "./lib/geometry.js";
|
|
5
|
+
import { toPercent as a } from "./lib/format.js";
|
|
6
|
+
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
7
|
+
//#region src/crewfoundry/components/viz/SkillBar.tsx
|
|
8
|
+
function c({ label: c, value: l, max: u = 100, levelLabel: d, showPercent: f = !0, color: p = e, height: m = 8, className: h }) {
|
|
9
|
+
let g = n(), _ = r(), v = i(a(l, u, 0), 0, 100), y = d ?? (f ? `${v}%` : null);
|
|
10
|
+
return /* @__PURE__ */ s("div", {
|
|
11
|
+
className: t("flex flex-col gap-1.5", h),
|
|
12
|
+
children: [/* @__PURE__ */ s("div", {
|
|
13
|
+
className: "flex items-baseline justify-between gap-3 text-sm",
|
|
14
|
+
children: [/* @__PURE__ */ o("span", {
|
|
15
|
+
className: "font-medium text-text-primary",
|
|
16
|
+
children: c
|
|
17
|
+
}), y === null ? null : /* @__PURE__ */ o("span", {
|
|
18
|
+
className: "tabular-nums text-text-secondary",
|
|
19
|
+
children: y
|
|
20
|
+
})]
|
|
21
|
+
}), /* @__PURE__ */ o("div", {
|
|
22
|
+
className: "w-full overflow-hidden rounded-full bg-bg-sunken",
|
|
23
|
+
style: { height: m },
|
|
24
|
+
role: "progressbar",
|
|
25
|
+
"aria-valuenow": v,
|
|
26
|
+
"aria-valuemin": 0,
|
|
27
|
+
"aria-valuemax": 100,
|
|
28
|
+
"aria-label": `${c} proficiency`,
|
|
29
|
+
children: /* @__PURE__ */ o("div", {
|
|
30
|
+
className: "h-full rounded-full",
|
|
31
|
+
style: {
|
|
32
|
+
width: g || _ ? `${v}%` : "0%",
|
|
33
|
+
backgroundColor: p,
|
|
34
|
+
transition: _ ? void 0 : "width 700ms cubic-bezier(0.22,1,0.36,1)"
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
})]
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
41
|
+
export { c as SkillBar };
|
|
42
|
+
|
|
43
|
+
//# sourceMappingURL=SkillBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SkillBar.js","names":[],"sources":["../../../../src/crewfoundry/components/viz/SkillBar.tsx"],"sourcesContent":["import { cn } from '../../utils/cn';\nimport { ACCENT } from './lib/palette';\nimport { clamp } from './lib/geometry';\nimport { toPercent } from './lib/format';\nimport { useMounted, usePrefersReducedMotion } from './lib/hooks';\n\nexport interface SkillBarProps {\n label: string;\n value: number;\n max?: number;\n /** Qualitative level shown on the right, e.g. \"Advanced\". */\n levelLabel?: string;\n /** Show the numeric percentage on the right (ignored when levelLabel is set). */\n showPercent?: boolean;\n /** Tokenized fill color; defaults to the product accent. */\n color?: string;\n /** Bar height in pixels. */\n height?: number;\n className?: string;\n}\n\n/** A labeled proficiency bar (skill name + level bar). */\nexport function SkillBar({\n label,\n value,\n max = 100,\n levelLabel,\n showPercent = true,\n color = ACCENT,\n height = 8,\n className,\n}: SkillBarProps) {\n const mounted = useMounted();\n const reduced = usePrefersReducedMotion();\n const pct = clamp(toPercent(value, max, 0), 0, 100);\n const right = levelLabel ?? (showPercent ? `${pct}%` : null);\n\n return (\n <div className={cn('flex flex-col gap-1.5', className)}>\n <div className=\"flex items-baseline justify-between gap-3 text-sm\">\n <span className=\"font-medium text-text-primary\">{label}</span>\n {right !== null ? <span className=\"tabular-nums text-text-secondary\">{right}</span> : null}\n </div>\n <div\n className=\"w-full overflow-hidden rounded-full bg-bg-sunken\"\n style={{ height }}\n role=\"progressbar\"\n aria-valuenow={pct}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`${label} proficiency`}\n >\n <div\n className=\"h-full rounded-full\"\n style={{\n width: mounted || reduced ? `${pct}%` : '0%',\n backgroundColor: color,\n transition: reduced ? undefined : 'width 700ms cubic-bezier(0.22,1,0.36,1)',\n }}\n />\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,EAAS,EACvB,UACA,UACA,SAAM,KACN,eACA,iBAAc,IACd,WAAQ,GACR,YAAS,GACT,gBACgB;CAChB,IAAM,IAAU,GAAY,EACtB,IAAU,GAAyB,EACnC,IAAM,EAAM,EAAU,GAAO,GAAK,EAAE,EAAE,GAAG,IAAI,EAC7C,IAAQ,MAAe,IAAc,GAAG,EAAI,KAAK;AAEvD,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,yBAAyB,EAAU;YAAtD,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAAiC;IAAa,CAAA,EAC7D,MAAU,OAA2E,OAApE,kBAAC,QAAD;IAAM,WAAU;cAAoC;IAAa,CAAA,CAC/E;MACN,kBAAC,OAAD;GACE,WAAU;GACV,OAAO,EAAE,WAAQ;GACjB,MAAK;GACL,iBAAe;GACf,iBAAe;GACf,iBAAe;GACf,cAAY,GAAG,EAAM;aAErB,kBAAC,OAAD;IACE,WAAU;IACV,OAAO;KACL,OAAO,KAAW,IAAU,GAAG,EAAI,KAAK;KACxC,iBAAiB;KACjB,YAAY,IAAU,KAAA,IAAY;KACnC;IACD,CAAA;GACE,CAAA,CACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HiringJobDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HiringJobDetailsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/HiringJobDetailsPagePage.tsx"],"names":[],"mappings":"AAkHA,wBAAgB,wBAAwB,gCAsbvC"}
|