@burdenoff/microfe-crewfoundry 2026.1013.1 → 2026.1029.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/CrewFoundryRoot.d.ts.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoot.js +160 -256
- package/dist/crewfoundry/CrewFoundryRoot.js.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoutes.d.ts.map +1 -1
- package/dist/crewfoundry/CrewFoundryRoutes.js +152 -192
- package/dist/crewfoundry/CrewFoundryRoutes.js.map +1 -1
- package/dist/crewfoundry/components/InlineError.d.ts +5 -0
- package/dist/crewfoundry/components/InlineError.d.ts.map +1 -0
- package/dist/crewfoundry/components/InlineError.js +17 -0
- package/dist/crewfoundry/components/InlineError.js.map +1 -0
- package/dist/crewfoundry/components/forms/FormModal.d.ts +25 -1
- package/dist/crewfoundry/components/forms/FormModal.d.ts.map +1 -1
- package/dist/crewfoundry/components/forms/FormModal.js +151 -136
- package/dist/crewfoundry/components/forms/FormModal.js.map +1 -1
- package/dist/crewfoundry/components/marketplace/BookServiceModal.d.ts +14 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.js +75 -0
- package/dist/crewfoundry/components/marketplace/BookServiceModal.js.map +1 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.d.ts +24 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.js +47 -0
- package/dist/crewfoundry/components/marketplace/MarketplaceTabs.js.map +1 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.d.ts +12 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.d.ts.map +1 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.js +123 -0
- package/dist/crewfoundry/components/marketplace/ServiceListingModal.js.map +1 -0
- package/dist/crewfoundry/components/viz/CertificateCard.js +4 -118
- package/dist/crewfoundry/components/viz/PathSteps.js +3 -68
- package/dist/crewfoundry/components/viz/lib/geometry.js +5 -13
- package/dist/crewfoundry/components/viz/lib/geometry.js.map +1 -1
- package/dist/crewfoundry/lib/analytics.d.ts +1 -6
- package/dist/crewfoundry/lib/analytics.d.ts.map +1 -1
- package/dist/crewfoundry/lib/analytics.js +3 -18
- package/dist/crewfoundry/lib/analytics.js.map +1 -1
- package/dist/crewfoundry/lib/companyFinance.d.ts +3 -7
- package/dist/crewfoundry/lib/companyFinance.d.ts.map +1 -1
- package/dist/crewfoundry/lib/companyFinance.js +3 -8
- package/dist/crewfoundry/lib/companyFinance.js.map +1 -1
- package/dist/crewfoundry/lib/learning.d.ts +12 -0
- package/dist/crewfoundry/lib/learning.d.ts.map +1 -0
- package/dist/crewfoundry/lib/learning.js +26 -0
- package/dist/crewfoundry/lib/learning.js.map +1 -0
- package/dist/crewfoundry/lib/marketplace.d.ts +30 -0
- package/dist/crewfoundry/lib/marketplace.d.ts.map +1 -0
- package/dist/crewfoundry/lib/marketplace.js +91 -0
- package/dist/crewfoundry/lib/marketplace.js.map +1 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.d.ts +7 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.d.ts.map +1 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.js +25 -0
- package/dist/crewfoundry/lib/useEnsureLoaded.js.map +1 -0
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js +12 -25
- package/dist/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js +49 -49
- package/dist/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +145 -157
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +14 -14
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +14 -14
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js +71 -33
- package/dist/crewfoundry/pages/HelpApiDocumentationPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +260 -115
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +199 -166
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +118 -104
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +247 -173
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.d.ts +12 -0
- package/dist/crewfoundry/pages/LearningCoursesPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +307 -293
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.d.ts +2 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.d.ts.map +1 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.js +223 -0
- package/dist/crewfoundry/pages/LearningEnrollmentsPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.d.ts +4 -0
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.js +115 -160
- package/dist/crewfoundry/pages/LearningLearningAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js +165 -75
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.d.ts +7 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.d.ts.map +1 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.js +175 -0
- package/dist/crewfoundry/pages/MarketplaceEscrowPagePage.js.map +1 -0
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js +182 -223
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js +168 -227
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.d.ts.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js +127 -290
- package/dist/crewfoundry/pages/MarketplaceServiceDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.d.ts +139 -79
- package/dist/crewfoundry/store/crewfoundryStore.d.ts.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js +323 -244
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/dist/crewfoundry/utils/dateUtils.d.ts +52 -0
- package/dist/crewfoundry/utils/dateUtils.d.ts.map +1 -0
- package/dist/crewfoundry/utils/dateUtils.js +31 -0
- package/dist/crewfoundry/utils/dateUtils.js.map +1 -0
- package/dist/generated/global-types.d.ts +5145 -1627
- package/dist/generated/global-types.d.ts.map +1 -1
- package/dist/generated/wspace-types.d.ts +100373 -34602
- package/dist/generated/wspace-types.d.ts.map +1 -1
- package/dist/utils/pageContext.d.ts.map +1 -1
- package/dist/utils/pageContext.js +0 -1
- package/dist/utils/pageContext.js.map +1 -1
- package/package.json +1 -1
- package/dist/crewfoundry/components/viz/CertificateCard.js.map +0 -1
- package/dist/crewfoundry/components/viz/PathSteps.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js +0 -53
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +0 -107
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js +0 -62
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js +0 -37
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js +0 -111
- package/dist/crewfoundry/pages/LearningCertificatesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js +0 -309
- package/dist/crewfoundry/pages/LearningCourseLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.js +0 -200
- package/dist/crewfoundry/pages/LearningLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +0 -190
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +0 -230
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +0 -293
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js +0 -163
- package/dist/crewfoundry/pages/MarketplaceEarningsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js +0 -365
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePageEnhancedPage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js +0 -224
- package/dist/crewfoundry/pages/MarketplaceFreelancerProfilePagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js +0 -180
- package/dist/crewfoundry/pages/MarketplaceMarketplaceServicesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js +0 -38
- package/dist/crewfoundry/pages/MarketplaceMessagingPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js +0 -204
- package/dist/crewfoundry/pages/MarketplaceProjectDetailsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js +0 -55
- package/dist/crewfoundry/pages/PaymentsEscrowPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +0 -335
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js +0 -160
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js +0 -80
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js.map +0 -1
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.d.ts +0 -2
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.d.ts.map +0 -1
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js +0 -191
- package/dist/crewfoundry/pages/PaymentsSecurityPagePage.js.map +0 -1
- package/dist/crewfoundry/utils/courseProgress.d.ts +0 -33
- package/dist/crewfoundry/utils/courseProgress.d.ts.map +0 -1
- package/dist/crewfoundry/utils/courseProgress.js +0 -46
- package/dist/crewfoundry/utils/courseProgress.js.map +0 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
//#region src/crewfoundry/lib/marketplace.ts
|
|
2
|
+
var e = {
|
|
3
|
+
requested: {
|
|
4
|
+
confirmed: ["provider"],
|
|
5
|
+
cancelled: ["provider", "client"]
|
|
6
|
+
},
|
|
7
|
+
confirmed: {
|
|
8
|
+
completed: ["provider"],
|
|
9
|
+
cancelled: ["provider", "client"]
|
|
10
|
+
},
|
|
11
|
+
completed: {},
|
|
12
|
+
cancelled: {}
|
|
13
|
+
};
|
|
14
|
+
function t(e, t) {
|
|
15
|
+
if (!t) return [];
|
|
16
|
+
let n = [];
|
|
17
|
+
return e.providerActorId === t && n.push("provider"), e.clientActorId === t && n.push("client"), n;
|
|
18
|
+
}
|
|
19
|
+
function n(n, r) {
|
|
20
|
+
let i = t(n, r), a = e[n.status] ?? {};
|
|
21
|
+
return Object.keys(a).filter((e) => (a[e] ?? []).some((e) => i.includes(e)));
|
|
22
|
+
}
|
|
23
|
+
var r = {
|
|
24
|
+
requested: "Requested",
|
|
25
|
+
confirmed: "Confirm",
|
|
26
|
+
completed: "Mark completed",
|
|
27
|
+
cancelled: "Cancel booking"
|
|
28
|
+
}, i = {
|
|
29
|
+
requested: "Requested",
|
|
30
|
+
confirmed: "Confirmed",
|
|
31
|
+
completed: "Completed",
|
|
32
|
+
cancelled: "Cancelled"
|
|
33
|
+
}, a = {
|
|
34
|
+
pending_hold: "Pending hold",
|
|
35
|
+
held: "Held",
|
|
36
|
+
released: "Released",
|
|
37
|
+
cancelled: "Cancelled"
|
|
38
|
+
}, o = (e) => e === "pending_hold" || e === "held", s = [
|
|
39
|
+
{
|
|
40
|
+
label: "Per hour",
|
|
41
|
+
value: "hour"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
label: "Per day",
|
|
45
|
+
value: "day"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
label: "Per week",
|
|
49
|
+
value: "week"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
label: "Per month",
|
|
53
|
+
value: "month"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
label: "Fixed price",
|
|
57
|
+
value: "fixed"
|
|
58
|
+
}
|
|
59
|
+
], c = [
|
|
60
|
+
"USD",
|
|
61
|
+
"EUR",
|
|
62
|
+
"GBP",
|
|
63
|
+
"INR"
|
|
64
|
+
].map((e) => ({
|
|
65
|
+
label: e,
|
|
66
|
+
value: e
|
|
67
|
+
}));
|
|
68
|
+
function l(e, t) {
|
|
69
|
+
try {
|
|
70
|
+
return new Intl.NumberFormat(void 0, {
|
|
71
|
+
style: "currency",
|
|
72
|
+
currency: t,
|
|
73
|
+
maximumFractionDigits: 2
|
|
74
|
+
}).format(e);
|
|
75
|
+
} catch {
|
|
76
|
+
return `${e.toLocaleString()} ${t}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function u(e) {
|
|
80
|
+
let t = l(e.rate, e.currency), n = e.rateUnit.trim().toLowerCase();
|
|
81
|
+
return n === "fixed" || n === "" ? t : `${t} / ${n}`;
|
|
82
|
+
}
|
|
83
|
+
function d(e) {
|
|
84
|
+
let t = /* @__PURE__ */ new Map();
|
|
85
|
+
for (let n of e) t.set(n.currency, (t.get(n.currency) ?? 0) + n.amount);
|
|
86
|
+
return t.size === 0 ? l(0, "USD") : [...t].map(([e, t]) => l(t, e)).join(" · ");
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { r as BOOKING_ACTION_LABEL, i as BOOKING_STATUS_LABEL, c as CURRENCY_OPTIONS, a as ESCROW_STATUS_LABEL, s as RATE_UNIT_OPTIONS, n as allowedBookingTransitions, l as formatMoney, u as formatRate, o as isEscrowOpen, d as sumByCurrency };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=marketplace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marketplace.js","names":[],"sources":["../../../src/crewfoundry/lib/marketplace.ts"],"sourcesContent":["/**\n * Pure helpers for the talent-marketplace pages (BOFF-3401). Mirrors the\n * booking workflow enforced by wspace-crewfoundry-svc's MarketplaceService so\n * the UI only offers transitions the service will accept; the service stays\n * the authority and rejects anything else.\n */\nimport type {\n CrewBooking,\n CrewBookingStatus,\n CrewEscrowStatus,\n CrewServiceListing,\n} from '../store/crewfoundryStore';\n\nexport type BookingRole = 'provider' | 'client';\n\n/** Legal next statuses per current status, and which role may move there. */\nconst BOOKING_TRANSITIONS: Record<\n CrewBookingStatus,\n Partial<Record<CrewBookingStatus, BookingRole[]>>\n> = {\n requested: { confirmed: ['provider'], cancelled: ['provider', 'client'] },\n confirmed: { completed: ['provider'], cancelled: ['provider', 'client'] },\n completed: {},\n cancelled: {},\n};\n\n/** Roles the actor holds on a booking (both when they booked their own service). */\nexport function bookingRoles(booking: CrewBooking, actorId: string | undefined): BookingRole[] {\n if (!actorId) return [];\n const roles: BookingRole[] = [];\n if (booking.providerActorId === actorId) roles.push('provider');\n if (booking.clientActorId === actorId) roles.push('client');\n return roles;\n}\n\n/** Next statuses this actor may move the booking to, in workflow order. */\nexport function allowedBookingTransitions(\n booking: CrewBooking,\n actorId: string | undefined\n): CrewBookingStatus[] {\n const roles = bookingRoles(booking, actorId);\n const next = BOOKING_TRANSITIONS[booking.status] ?? {};\n return (Object.keys(next) as CrewBookingStatus[]).filter((status) =>\n (next[status] ?? []).some((role) => roles.includes(role))\n );\n}\n\nexport const BOOKING_ACTION_LABEL: Record<CrewBookingStatus, string> = {\n requested: 'Requested',\n confirmed: 'Confirm',\n completed: 'Mark completed',\n cancelled: 'Cancel booking',\n};\n\nexport const BOOKING_STATUS_LABEL: Record<CrewBookingStatus, string> = {\n requested: 'Requested',\n confirmed: 'Confirmed',\n completed: 'Completed',\n cancelled: 'Cancelled',\n};\n\nexport const ESCROW_STATUS_LABEL: Record<CrewEscrowStatus, string> = {\n pending_hold: 'Pending hold',\n held: 'Held',\n released: 'Released',\n cancelled: 'Cancelled',\n};\n\n/** Escrow ledger rows that can still be released or cancelled. */\nexport const isEscrowOpen = (status: CrewEscrowStatus): boolean =>\n status === 'pending_hold' || status === 'held';\n\nexport const RATE_UNIT_OPTIONS = [\n { label: 'Per hour', value: 'hour' },\n { label: 'Per day', value: 'day' },\n { label: 'Per week', value: 'week' },\n { label: 'Per month', value: 'month' },\n { label: 'Fixed price', value: 'fixed' },\n];\n\nexport const CURRENCY_OPTIONS = ['USD', 'EUR', 'GBP', 'INR'].map((code) => ({\n label: code,\n value: code,\n}));\n\n/** Formats an amount in its ISO currency, falling back to \"<amount> <code>\". */\nexport function formatMoney(amount: number, currency: string): string {\n try {\n return new Intl.NumberFormat(undefined, {\n style: 'currency',\n currency,\n maximumFractionDigits: 2,\n }).format(amount);\n } catch {\n return `${amount.toLocaleString()} ${currency}`;\n }\n}\n\n/** \"$120 / hour\", or just \"$900\" for a fixed-price listing. */\nexport function formatRate(\n item: Pick<CrewServiceListing, 'rate' | 'rateUnit' | 'currency'>\n): string {\n const money = formatMoney(item.rate, item.currency);\n const unit = item.rateUnit.trim().toLowerCase();\n return unit === 'fixed' || unit === '' ? money : `${money} / ${unit}`;\n}\n\n/** Sum of amounts grouped by currency, e.g. \"$300 · €120\" (never mixes currencies). */\nexport function sumByCurrency(rows: Array<{ amount: number; currency: string }>): string {\n const totals = new Map<string, number>();\n for (const row of rows) {\n totals.set(row.currency, (totals.get(row.currency) ?? 0) + row.amount);\n }\n if (totals.size === 0) return formatMoney(0, 'USD');\n return [...totals].map(([currency, amount]) => formatMoney(amount, currency)).join(' · ');\n}\n"],"mappings":";AAgBA,IAAM,IAGF;CACF,WAAW;EAAE,WAAW,CAAC,WAAW;EAAE,WAAW,CAAC,YAAY,SAAS;EAAE;CACzE,WAAW;EAAE,WAAW,CAAC,WAAW;EAAE,WAAW,CAAC,YAAY,SAAS;EAAE;CACzE,WAAW,EAAE;CACb,WAAW,EAAE;CACd;AAGD,SAAgB,EAAa,GAAsB,GAA4C;AAC7F,KAAI,CAAC,EAAS,QAAO,EAAE;CACvB,IAAM,IAAuB,EAAE;AAG/B,QAFI,EAAQ,oBAAoB,KAAS,EAAM,KAAK,WAAW,EAC3D,EAAQ,kBAAkB,KAAS,EAAM,KAAK,SAAS,EACpD;;AAIT,SAAgB,EACd,GACA,GACqB;CACrB,IAAM,IAAQ,EAAa,GAAS,EAAQ,EACtC,IAAO,EAAoB,EAAQ,WAAW,EAAE;AACtD,QAAQ,OAAO,KAAK,EAAK,CAAyB,QAAQ,OACvD,EAAK,MAAW,EAAE,EAAE,MAAM,MAAS,EAAM,SAAS,EAAK,CAAC,CAC1D;;AAGH,IAAa,IAA0D;CACrE,WAAW;CACX,WAAW;CACX,WAAW;CACX,WAAW;CACZ,EAEY,IAA0D;CACrE,WAAW;CACX,WAAW;CACX,WAAW;CACX,WAAW;CACZ,EAEY,IAAwD;CACnE,cAAc;CACd,MAAM;CACN,UAAU;CACV,WAAW;CACZ,EAGY,KAAgB,MAC3B,MAAW,kBAAkB,MAAW,QAE7B,IAAoB;CAC/B;EAAE,OAAO;EAAY,OAAO;EAAQ;CACpC;EAAE,OAAO;EAAW,OAAO;EAAO;CAClC;EAAE,OAAO;EAAY,OAAO;EAAQ;CACpC;EAAE,OAAO;EAAa,OAAO;EAAS;CACtC;EAAE,OAAO;EAAe,OAAO;EAAS;CACzC,EAEY,IAAmB;CAAC;CAAO;CAAO;CAAO;CAAM,CAAC,KAAK,OAAU;CAC1E,OAAO;CACP,OAAO;CACR,EAAE;AAGH,SAAgB,EAAY,GAAgB,GAA0B;AACpE,KAAI;AACF,SAAO,IAAI,KAAK,aAAa,KAAA,GAAW;GACtC,OAAO;GACP;GACA,uBAAuB;GACxB,CAAC,CAAC,OAAO,EAAO;SACX;AACN,SAAO,GAAG,EAAO,gBAAgB,CAAC,GAAG;;;AAKzC,SAAgB,EACd,GACQ;CACR,IAAM,IAAQ,EAAY,EAAK,MAAM,EAAK,SAAS,EAC7C,IAAO,EAAK,SAAS,MAAM,CAAC,aAAa;AAC/C,QAAO,MAAS,WAAW,MAAS,KAAK,IAAQ,GAAG,EAAM,KAAK;;AAIjE,SAAgB,EAAc,GAA2D;CACvF,IAAM,oBAAS,IAAI,KAAqB;AACxC,MAAK,IAAM,KAAO,EAChB,GAAO,IAAI,EAAI,WAAW,EAAO,IAAI,EAAI,SAAS,IAAI,KAAK,EAAI,OAAO;AAGxE,QADI,EAAO,SAAS,IAAU,EAAY,GAAG,MAAM,GAC5C,CAAC,GAAG,EAAO,CAAC,KAAK,CAAC,GAAU,OAAY,EAAY,GAAQ,EAAS,CAAC,CAAC,KAAK,MAAM"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a lazy store fetch once the module has its workspace context
|
|
3
|
+
* (`initialized`) and the slice has not been loaded yet. Returns the load
|
|
4
|
+
* error, if any, so the page can render it instead of a silent empty state.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useEnsureLoaded(loaded: boolean, load: () => Promise<void>): string | null;
|
|
7
|
+
//# sourceMappingURL=useEnsureLoaded.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEnsureLoaded.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/lib/useEnsureLoaded.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAoBzF"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
|
|
2
|
+
import { useEffect as t, useState as n } from "react";
|
|
3
|
+
//#region src/crewfoundry/lib/useEnsureLoaded.ts
|
|
4
|
+
function r(r, i) {
|
|
5
|
+
let a = e((e) => e.initialized), [o, s] = n(null);
|
|
6
|
+
return t(() => {
|
|
7
|
+
if (!a || r) return;
|
|
8
|
+
let e = !1;
|
|
9
|
+
return i().then(() => {
|
|
10
|
+
e || s(null);
|
|
11
|
+
}).catch((t) => {
|
|
12
|
+
e || s(t instanceof Error ? t.message : "Unable to load data");
|
|
13
|
+
}), () => {
|
|
14
|
+
e = !0;
|
|
15
|
+
};
|
|
16
|
+
}, [
|
|
17
|
+
a,
|
|
18
|
+
r,
|
|
19
|
+
i
|
|
20
|
+
]), o;
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { r as useEnsureLoaded };
|
|
24
|
+
|
|
25
|
+
//# sourceMappingURL=useEnsureLoaded.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEnsureLoaded.js","names":[],"sources":["../../../src/crewfoundry/lib/useEnsureLoaded.ts"],"sourcesContent":["import { useEffect, useState } from 'react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\n/**\n * Run a lazy store fetch once the module has its workspace context\n * (`initialized`) and the slice has not been loaded yet. Returns the load\n * error, if any, so the page can render it instead of a silent empty state.\n */\nexport function useEnsureLoaded(loaded: boolean, load: () => Promise<void>): string | null {\n const initialized = useCrewFoundryStore((state) => state.initialized);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n if (!initialized || loaded) return;\n let cancelled = false;\n load()\n .then(() => {\n if (!cancelled) setError(null);\n })\n .catch((err: unknown) => {\n if (!cancelled) setError(err instanceof Error ? err.message : 'Unable to load data');\n });\n return () => {\n cancelled = true;\n };\n }, [initialized, loaded, load]);\n\n return error;\n}\n"],"mappings":";;;AAQA,SAAgB,EAAgB,GAAiB,GAA0C;CACzF,IAAM,IAAc,GAAqB,MAAU,EAAM,YAAY,EAC/D,CAAC,GAAO,KAAY,EAAwB,KAAK;AAiBvD,QAfA,QAAgB;AACd,MAAI,CAAC,KAAe,EAAQ;EAC5B,IAAI,IAAY;AAQhB,SAPA,GAAM,CACH,WAAW;AACV,GAAK,KAAW,EAAS,KAAK;IAC9B,CACD,OAAO,MAAiB;AACvB,GAAK,KAAW,EAAS,aAAe,QAAQ,EAAI,UAAU,sBAAsB;IACpF,QACS;AACX,OAAY;;IAEb;EAAC;EAAa;EAAQ;EAAK,CAAC,EAExB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsFinancialAnalyticsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,mCAAmC,
|
|
1
|
+
{"version":3,"file":"AnalyticsFinancialAnalyticsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.tsx"],"names":[],"mappings":"AA+BA,wBAAgB,mCAAmC,gCAoSlD"}
|
|
@@ -10,7 +10,7 @@ import { Badge as f, Card as p, CardContent as m, CardDescription as h, CardHead
|
|
|
10
10
|
import { usePagination as T } from "@burdenoff/fe-libs/shared/hooks";
|
|
11
11
|
//#region src/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.tsx
|
|
12
12
|
function E() {
|
|
13
|
-
let { employees: E, departments: D, payrollRecords: O,
|
|
13
|
+
let { employees: E, departments: D, payrollRecords: O, clients: k, projects: A } = e(), j = i(() => {
|
|
14
14
|
let e = O.reduce((e, t) => {
|
|
15
15
|
let n = new Date(t.payDate).toLocaleString("en-US", { month: "short" }), r = e[n] || {
|
|
16
16
|
gross: 0,
|
|
@@ -39,22 +39,13 @@ function E() {
|
|
|
39
39
|
category: "Project Spend",
|
|
40
40
|
amount: A.reduce((e, t) => e + t.spent, 0),
|
|
41
41
|
icon: c
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
category: "Collected Invoices",
|
|
45
|
-
amount: k.filter((e) => e.status === "paid").reduce((e, t) => e + t.amount, 0),
|
|
46
|
-
icon: o
|
|
47
42
|
}
|
|
48
43
|
], t = e.reduce((e, t) => e + t.amount, 0) || 1;
|
|
49
44
|
return e.map((e) => ({
|
|
50
45
|
...e,
|
|
51
46
|
percentage: Math.round(e.amount / t * 100)
|
|
52
47
|
}));
|
|
53
|
-
}, [
|
|
54
|
-
k,
|
|
55
|
-
O,
|
|
56
|
-
A
|
|
57
|
-
]), N = E.reduce((e, t) => e + t.salary, 0), P = Math.round(N / 12), F = k.reduce((e, t) => e + t.amount, 0), I = k.filter((e) => e.status === "paid").reduce((e, t) => e + t.amount, 0), L = A.reduce((e, t) => e + t.budget, 0), R = A.reduce((e, t) => e + t.spent, 0), z = j.map((e) => ({
|
|
48
|
+
}, [O, A]), N = E.reduce((e, t) => e + t.salary, 0), P = Math.round(N / 12), F = k.reduce((e, t) => e + (t.revenue || 0), 0), I = k.filter((e) => e.status === "active").length, L = A.reduce((e, t) => e + t.budget, 0), R = A.reduce((e, t) => e + t.spent, 0), z = j.map((e) => ({
|
|
58
49
|
label: e.month,
|
|
59
50
|
value: e.gross
|
|
60
51
|
})), B = j.map((e) => e.net), V = M.filter((e) => e.amount > 0).map((e) => ({
|
|
@@ -68,7 +59,7 @@ function E() {
|
|
|
68
59
|
budget: e.budget,
|
|
69
60
|
headcount: t.length
|
|
70
61
|
};
|
|
71
|
-
})].sort((e, t) => t.salary - e.salary), W = D.reduce((e, t) => e + t.budget, 0), G = [...k].filter((e) => e.
|
|
62
|
+
})].sort((e, t) => t.salary - e.salary), W = D.reduce((e, t) => e + t.budget, 0), G = [...k].filter((e) => (e.revenue || 0) > 0).sort((e, t) => t.revenue - e.revenue), { page: K, pageSize: q, offset: J, setPage: Y } = T({ initialPageSize: 6 }), X = G.slice(J, J + q);
|
|
72
63
|
return /* @__PURE__ */ d("div", {
|
|
73
64
|
className: "space-y-6 p-6",
|
|
74
65
|
children: [
|
|
@@ -142,7 +133,7 @@ function E() {
|
|
|
142
133
|
className: "flex flex-row items-center justify-between space-y-0 pb-2",
|
|
143
134
|
children: [/* @__PURE__ */ u(_, {
|
|
144
135
|
className: "text-sm font-medium",
|
|
145
|
-
children: "
|
|
136
|
+
children: "Client Revenue"
|
|
146
137
|
}), /* @__PURE__ */ u(o, { className: "h-4 w-4 text-text-secondary" })]
|
|
147
138
|
}), /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ d("div", {
|
|
148
139
|
className: "text-2xl font-bold tabular-nums",
|
|
@@ -153,11 +144,7 @@ function E() {
|
|
|
153
144
|
]
|
|
154
145
|
}), /* @__PURE__ */ d("p", {
|
|
155
146
|
className: "text-xs text-text-secondary",
|
|
156
|
-
children: [
|
|
157
|
-
"$",
|
|
158
|
-
(I / 1e3).toFixed(1),
|
|
159
|
-
"K collected"
|
|
160
|
-
]
|
|
147
|
+
children: [I, " active clients"]
|
|
161
148
|
})] })]
|
|
162
149
|
}),
|
|
163
150
|
/* @__PURE__ */ d(v, {
|
|
@@ -204,29 +191,29 @@ function E() {
|
|
|
204
191
|
}) : /* @__PURE__ */ u("p", {
|
|
205
192
|
className: "py-6 text-center text-sm text-text-secondary",
|
|
206
193
|
children: "No tracked spend yet."
|
|
207
|
-
}) })] }), /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ d(g, { children: [/* @__PURE__ */ u(_, { children: "
|
|
194
|
+
}) })] }), /* @__PURE__ */ d(p, { children: [/* @__PURE__ */ d(g, { children: [/* @__PURE__ */ u(_, { children: "Revenue by Client" }), /* @__PURE__ */ u(h, { children: "Revenue recorded against each client" })] }), /* @__PURE__ */ d(m, {
|
|
208
195
|
className: "space-y-3",
|
|
209
196
|
children: [
|
|
210
197
|
G.length === 0 && /* @__PURE__ */ u(b, {
|
|
211
198
|
illustration: "empty-data",
|
|
212
|
-
title: "No
|
|
213
|
-
description: "
|
|
199
|
+
title: "No client revenue yet",
|
|
200
|
+
description: "Record revenue on your clients under Company → Client Management."
|
|
214
201
|
}),
|
|
215
202
|
X.map((e) => /* @__PURE__ */ d("div", {
|
|
216
203
|
className: "flex items-center justify-between rounded border p-3",
|
|
217
204
|
children: [/* @__PURE__ */ d("div", { children: [/* @__PURE__ */ u("p", {
|
|
218
205
|
className: "text-sm font-medium",
|
|
219
|
-
children: e.
|
|
206
|
+
children: e.name
|
|
220
207
|
}), /* @__PURE__ */ u("p", {
|
|
221
208
|
className: "text-xs text-text-secondary",
|
|
222
|
-
children: e.
|
|
209
|
+
children: e.industry
|
|
223
210
|
})] }), /* @__PURE__ */ d("div", {
|
|
224
211
|
className: "text-right",
|
|
225
212
|
children: [/* @__PURE__ */ d("p", {
|
|
226
213
|
className: "text-sm font-medium",
|
|
227
|
-
children: ["$", e.
|
|
214
|
+
children: ["$", e.revenue.toLocaleString()]
|
|
228
215
|
}), /* @__PURE__ */ u(f, {
|
|
229
|
-
variant:
|
|
216
|
+
variant: "secondary",
|
|
230
217
|
className: "text-[10px]",
|
|
231
218
|
children: t(e.status)
|
|
232
219
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsFinancialAnalyticsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { DollarSign, FileText, Receipt, TrendingUp, Wallet } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { DonutChart, LineChart } from '../components/viz';\nimport type { LabeledValue, Segment } from '../components/viz';\n\ntype DeptCostRow = {\n name: string;\n salary: number;\n budget: number;\n headcount: number;\n};\n\nexport function AnalyticsFinancialAnalyticsPagePage() {\n const { employees, departments, payrollRecords, invoices, projects } = useCrewFoundryStore();\n\n const monthlyPayrollTrend = useMemo(() => {\n const byPeriod = payrollRecords.reduce<\n Record<string, { gross: number; net: number; deductions: number }>\n >((acc, record) => {\n const month = new Date(record.payDate).toLocaleString('en-US', { month: 'short' });\n const current = acc[month] || { gross: 0, net: 0, deductions: 0 };\n current.gross += record.grossPay;\n current.net += record.netPay;\n current.deductions += record.deductions;\n acc[month] = current;\n return acc;\n }, {});\n return Object.entries(byPeriod)\n .map(([month, value]) => ({ month, ...value }))\n .slice(-6);\n }, [payrollRecords]);\n\n // Real spend lines only (no invented budget coefficients).\n const spendByCategory = useMemo(() => {\n const items = [\n {\n category: 'Gross Payroll',\n amount: payrollRecords.reduce((sum, record) => sum + record.grossPay, 0),\n icon: Wallet,\n },\n {\n category: 'Deductions',\n amount: payrollRecords.reduce((sum, record) => sum + record.deductions, 0),\n icon: Receipt,\n },\n {\n category: 'Project Spend',\n amount: projects.reduce((sum, project) => sum + project.spent, 0),\n icon: TrendingUp,\n },\n {\n category: 'Collected Invoices',\n amount: invoices\n .filter((invoice) => invoice.status === 'paid')\n .reduce((sum, invoice) => sum + invoice.amount, 0),\n icon: FileText,\n },\n ];\n const total = items.reduce((sum, item) => sum + item.amount, 0) || 1;\n return items.map((item) => ({ ...item, percentage: Math.round((item.amount / total) * 100) }));\n }, [invoices, payrollRecords, projects]);\n\n const totalPayroll = employees.reduce((sum, e) => sum + e.salary, 0);\n const monthlyPayroll = Math.round(totalPayroll / 12);\n const totalInvoiced = invoices.reduce((sum, inv) => sum + inv.amount, 0);\n const paidInvoices = invoices\n .filter((i) => i.status === 'paid')\n .reduce((s, i) => s + i.amount, 0);\n const totalProjectBudget = projects.reduce((s, p) => s + p.budget, 0);\n const totalProjectSpent = projects.reduce((s, p) => s + p.spent, 0);\n\n const grossSeries: LabeledValue[] = monthlyPayrollTrend.map((m) => ({\n label: m.month,\n value: m.gross,\n }));\n const netCompare = monthlyPayrollTrend.map((m) => m.net);\n const spendSegments: Segment[] = spendByCategory\n .filter((category) => category.amount > 0)\n .map((category) => ({ label: category.category, value: category.amount }));\n const projectUtilization =\n totalProjectBudget > 0 ? Math.round((totalProjectSpent / totalProjectBudget) * 100) : 0;\n\n const deptCost: DeptCostRow[] = departments.map((d) => {\n const deptEmps = employees.filter((e) => e.department === d.name);\n const totalSal = deptEmps.reduce((s, e) => s + e.salary, 0);\n return { name: d.name, salary: totalSal, budget: d.budget, headcount: deptEmps.length };\n });\n const sortedDeptCost = [...deptCost].sort((a, b) => b.salary - a.salary);\n const totalDeptBudget = departments.reduce((s, d) => s + d.budget, 0);\n\n // Outstanding invoices = anything not fully paid, matching the card title.\n const outstandingInvoices = [...invoices]\n .filter((invoice) => invoice.status !== 'paid')\n .sort((a, b) => a.dueDate.localeCompare(b.dueDate));\n const {\n page: invoicePage,\n pageSize: invoicePageSize,\n offset: invoiceOffset,\n setPage: setInvoicePage,\n } = usePagination({ initialPageSize: 6 });\n const invoicePageItems = outstandingInvoices.slice(\n invoiceOffset,\n invoiceOffset + invoicePageSize\n );\n\n const deptCostColumns: ResponsiveTableColumn<DeptCostRow>[] = [\n {\n key: 'name',\n header: 'Department',\n priority: 'primary',\n cell: (d) => <span className=\"font-medium\">{d.name}</span>,\n },\n {\n key: 'headcount',\n header: 'Headcount',\n priority: 'secondary',\n cell: (d) => <span className=\"md:block md:text-right\">{d.headcount}</span>,\n },\n {\n key: 'salary',\n header: 'Total Salary',\n priority: 'secondary',\n cell: (d) => <span className=\"md:block md:text-right\">${(d.salary / 1000).toFixed(0)}K</span>,\n },\n {\n key: 'budget',\n header: 'Dept Budget',\n priority: 'secondary',\n cell: (d) => (\n <span className=\"md:block md:text-right\">${(d.budget / 1000000).toFixed(1)}M</span>\n ),\n },\n {\n key: 'ratio',\n header: 'Salary/Budget %',\n priority: 'primary',\n cell: (d) => {\n const ratio = d.budget > 0 ? Math.round((d.salary / d.budget) * 100) : 0;\n return (\n <span className=\"md:block md:text-right\">\n <Badge variant={ratio > 50 ? 'destructive' : 'default'} className=\"text-[10px]\">\n {ratio}%\n </Badge>\n </span>\n );\n },\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Financial Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Workforce financial metrics and cost analysis\n </p>\n <PagePurpose className=\"mt-1\">\n Understand the money side of your workforce — payroll, project budgets, and spend trends\n — so you can plan headcount and control costs with confidence.\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Monthly Payroll</CardTitle>\n <DollarSign className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(monthlyPayroll / 1000).toFixed(0)}K\n </div>\n <p className=\"text-xs text-text-secondary\">Annual salary ÷ 12 months</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Annual Salary Cost</CardTitle>\n <Wallet className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalPayroll / 1000000).toFixed(1)}M\n </div>\n <p className=\"text-xs text-text-secondary\">{employees.length} employees</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Total Invoiced</CardTitle>\n <FileText className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalInvoiced / 1000).toFixed(1)}K\n </div>\n <p className=\"text-xs text-text-secondary\">\n ${(paidInvoices / 1000).toFixed(1)}K collected\n </p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Project Budget</CardTitle>\n <TrendingUp className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalProjectBudget / 1000000).toFixed(1)}M\n </div>\n <p className=\"text-xs text-text-secondary\">{projectUtilization}% utilized</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Payroll Trend</CardTitle>\n <CardDescription>Monthly gross payroll over the last 6 months</CardDescription>\n </CardHeader>\n <CardContent>\n {grossSeries.length > 0 ? (\n <LineChart\n series={grossSeries}\n compare={netCompare}\n seriesLabel=\"Gross pay\"\n compareLabel=\"Net pay\"\n height={220}\n valueFormatter={(value) => `$${(value / 1000).toFixed(0)}K`}\n />\n ) : (\n <p className=\"py-6 text-center text-sm text-text-secondary\">No payroll records yet.</p>\n )}\n </CardContent>\n </Card>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Spend by Category</CardTitle>\n <CardDescription>Share of tracked workforce spend</CardDescription>\n </CardHeader>\n <CardContent>\n {spendSegments.length > 0 ? (\n <DonutChart\n segments={spendSegments}\n centerLabel={`$${(spendSegments.reduce((sum, s) => sum + s.value, 0) / 1000).toFixed(0)}K`}\n centerCaption=\"Total spend\"\n valueFormatter={(value) => `$${(value / 1000).toFixed(0)}K`}\n />\n ) : (\n <p className=\"py-6 text-center text-sm text-text-secondary\">No tracked spend yet.</p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Outstanding Invoices</CardTitle>\n <CardDescription>Unpaid invoices requiring attention</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {outstandingInvoices.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No outstanding invoices\"\n description=\"Every invoice has been paid. New unpaid invoices will appear here.\"\n />\n )}\n {invoicePageItems.map((inv) => (\n <div key={inv.id} className=\"flex items-center justify-between rounded border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{inv.number}</p>\n <p className=\"text-xs text-text-secondary\">{inv.clientName}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">${inv.amount.toLocaleString()}</p>\n <Badge\n variant={inv.status === 'overdue' ? 'destructive' : 'secondary'}\n className=\"text-[10px]\"\n >\n {formatEnumLabel(inv.status)}\n </Badge>\n </div>\n </div>\n ))}\n {outstandingInvoices.length > invoicePageSize && (\n <Pagination\n page={invoicePage}\n pageSize={invoicePageSize}\n totalItems={outstandingInvoices.length}\n onPageChange={setInvoicePage}\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Department Cost Analysis</CardTitle>\n <CardDescription>Total salary cost and budget allocation by department</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable columns={deptCostColumns} data={sortedDeptCost} rowKey={(d) => d.name} />\n <Separator className=\"my-3\" />\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-sm font-bold\">\n <span>Total</span>\n <span>{employees.length} employees</span>\n <span>${(totalPayroll / 1000000).toFixed(1)}M salary</span>\n <span>${(totalDeptBudget / 1000000).toFixed(1)}M budget</span>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AA+BA,SAAgB,IAAsC;CACpD,IAAM,EAAE,cAAW,gBAAa,mBAAgB,aAAU,gBAAa,GAAqB,EAEtF,IAAsB,QAAc;EACxC,IAAM,IAAW,EAAe,QAE7B,GAAK,MAAW;GACjB,IAAM,IAAQ,IAAI,KAAK,EAAO,QAAQ,CAAC,eAAe,SAAS,EAAE,OAAO,SAAS,CAAC,EAC5E,IAAU,EAAI,MAAU;IAAE,OAAO;IAAG,KAAK;IAAG,YAAY;IAAG;AAKjE,UAJA,EAAQ,SAAS,EAAO,UACxB,EAAQ,OAAO,EAAO,QACtB,EAAQ,cAAc,EAAO,YAC7B,EAAI,KAAS,GACN;KACN,EAAE,CAAC;AACN,SAAO,OAAO,QAAQ,EAAS,CAC5B,KAAK,CAAC,GAAO,QAAY;GAAE;GAAO,GAAG;GAAO,EAAE,CAC9C,MAAM,GAAG;IACX,CAAC,EAAe,CAAC,EAGd,IAAkB,QAAc;EACpC,IAAM,IAAQ;GACZ;IACE,UAAU;IACV,QAAQ,EAAe,QAAQ,GAAK,MAAW,IAAM,EAAO,UAAU,EAAE;IACxE,MAAM;IACP;GACD;IACE,UAAU;IACV,QAAQ,EAAe,QAAQ,GAAK,MAAW,IAAM,EAAO,YAAY,EAAE;IAC1E,MAAM;IACP;GACD;IACE,UAAU;IACV,QAAQ,EAAS,QAAQ,GAAK,MAAY,IAAM,EAAQ,OAAO,EAAE;IACjE,MAAM;IACP;GACD;IACE,UAAU;IACV,QAAQ,EACL,QAAQ,MAAY,EAAQ,WAAW,OAAO,CAC9C,QAAQ,GAAK,MAAY,IAAM,EAAQ,QAAQ,EAAE;IACpD,MAAM;IACP;GACF,EACK,IAAQ,EAAM,QAAQ,GAAK,MAAS,IAAM,EAAK,QAAQ,EAAE,IAAI;AACnE,SAAO,EAAM,KAAK,OAAU;GAAE,GAAG;GAAM,YAAY,KAAK,MAAO,EAAK,SAAS,IAAS,IAAI;GAAE,EAAE;IAC7F;EAAC;EAAU;EAAgB;EAAS,CAAC,EAElC,IAAe,EAAU,QAAQ,GAAK,MAAM,IAAM,EAAE,QAAQ,EAAE,EAC9D,IAAiB,KAAK,MAAM,IAAe,GAAG,EAC9C,IAAgB,EAAS,QAAQ,GAAK,MAAQ,IAAM,EAAI,QAAQ,EAAE,EAClE,IAAe,EAClB,QAAQ,MAAM,EAAE,WAAW,OAAO,CAClC,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,EAAE,EAC9B,IAAqB,EAAS,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,EAAE,EAC/D,IAAoB,EAAS,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,EAAE,EAE7D,IAA8B,EAAoB,KAAK,OAAO;EAClE,OAAO,EAAE;EACT,OAAO,EAAE;EACV,EAAE,EACG,IAAa,EAAoB,KAAK,MAAM,EAAE,IAAI,EAClD,IAA2B,EAC9B,QAAQ,MAAa,EAAS,SAAS,EAAE,CACzC,KAAK,OAAc;EAAE,OAAO,EAAS;EAAU,OAAO,EAAS;EAAQ,EAAE,EACtE,IACJ,IAAqB,IAAI,KAAK,MAAO,IAAoB,IAAsB,IAAI,GAAG,GAOlF,IAAiB,CAAC,GALQ,EAAY,KAAK,MAAM;EACrD,IAAM,IAAW,EAAU,QAAQ,MAAM,EAAE,eAAe,EAAE,KAAK,EAC3D,IAAW,EAAS,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,EAAE;AAC3D,SAAO;GAAE,MAAM,EAAE;GAAM,QAAQ;GAAU,QAAQ,EAAE;GAAQ,WAAW,EAAS;GAAQ;GACvF,CACkC,CAAC,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,OAAO,EAClE,IAAkB,EAAY,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,EAAE,EAG/D,IAAsB,CAAC,GAAG,EAAS,CACtC,QAAQ,MAAY,EAAQ,WAAW,OAAO,CAC9C,MAAM,GAAG,MAAM,EAAE,QAAQ,cAAc,EAAE,QAAQ,CAAC,EAC/C,EACJ,MAAM,GACN,UAAU,GACV,QAAQ,GACR,SAAS,MACP,EAAc,EAAE,iBAAiB,GAAG,CAAC,EACnC,IAAmB,EAAoB,MAC3C,GACA,IAAgB,EACjB;AA8CD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAc,uBAAkC,CAAA;MAC7C,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAEvC,CAAA;KACJ,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAGhB,CAAA;KACV,EAAA,CAAA;IACF,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,MAAA;MAAK,WAAU;gBAA5C,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA2B,CAAA,EACtE,kBAAC,GAAD,EAAY,WAAU,+BAAgC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiD;SAC5C,IAAiB,KAAM,QAAQ,EAAE;QAAC;QACjC;UACN,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAA6B,CAAA,CAC5D,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA8B,CAAA,EACzE,kBAAC,GAAD,EAAQ,WAAU,+BAAgC,CAAA,CACvC;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiD;SAC5C,IAAe,KAAS,QAAQ,EAAE;QAAC;QAClC;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAA4C,EAAU,QAAO,aAAc;SAC/D,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA0B,CAAA,EACrE,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA,CACzC;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiD;SAC5C,IAAgB,KAAM,QAAQ,EAAE;QAAC;QAChC;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb;QAA2C;SACtC,IAAe,KAAM,QAAQ,EAAE;QAAC;QACjC;SACQ,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA0B,CAAA,EACrE,kBAAC,GAAD,EAAY,WAAU,+BAAgC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiD;SAC5C,IAAqB,KAAS,QAAQ,EAAE;QAAC;QACxC;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAA4C,GAAmB,aAAc;SACjE,EAAA,CAAA,CACJ;;KACR;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,iBAAyB,CAAA,EACpC,kBAAC,GAAD,EAAA,UAAiB,gDAA8D,CAAA,CACpE,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EAAY,SAAS,IACpB,kBAAC,GAAD;IACE,QAAQ;IACR,SAAS;IACT,aAAY;IACZ,cAAa;IACb,QAAQ;IACR,iBAAiB,MAAU,KAAK,IAAQ,KAAM,QAAQ,EAAE,CAAC;IACzD,CAAA,GAEF,kBAAC,KAAD;IAAG,WAAU;cAA+C;IAA2B,CAAA,EAE7E,CAAA,CACT,EAAA,CAAA;GAEP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,qBAA6B,CAAA,EACxC,kBAAC,GAAD,EAAA,UAAiB,oCAAkD,CAAA,CACxD,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EAAc,SAAS,IACtB,kBAAC,GAAD;KACE,UAAU;KACV,aAAa,KAAK,EAAc,QAAQ,GAAK,MAAM,IAAM,EAAE,OAAO,EAAE,GAAG,KAAM,QAAQ,EAAE,CAAC;KACxF,eAAc;KACd,iBAAiB,MAAU,KAAK,IAAQ,KAAM,QAAQ,EAAE,CAAC;KACzD,CAAA,GAEF,kBAAC,KAAD;KAAG,WAAU;eAA+C;KAAyB,CAAA,EAE3E,CAAA,CACT,EAAA,CAAA,EAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,wBAAgC,CAAA,EAC3C,kBAAC,GAAD,EAAA,UAAiB,uCAAqD,CAAA,CAC3D,EAAA,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eAAvB;MACG,EAAoB,WAAW,KAC9B,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;OACZ,CAAA;MAEH,EAAiB,KAAK,MACrB,kBAAC,OAAD;OAAkB,WAAU;iBAA5B,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAuB,EAAI;QAAW,CAAA,EACnD,kBAAC,KAAD;QAAG,WAAU;kBAA+B,EAAI;QAAe,CAAA,CAC3D,EAAA,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAAmC,KAAE,EAAI,OAAO,gBAAgB,CAAK;YACrE,kBAAC,GAAD;SACE,SAAS,EAAI,WAAW,YAAY,gBAAgB;SACpD,WAAU;mBAET,EAAgB,EAAI,OAAO;SACtB,CAAA,CACJ;UACF;SAdI,EAAI,GAcR,CACN;MACD,EAAoB,SAAS,KAC5B,kBAAC,GAAD;OACE,MAAM;OACN,UAAU;OACV,YAAY,EAAoB;OAChC,cAAc;OACd,CAAA;MAEQ;OACT,EAAA,CAAA,CACH;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,4BAAoC,CAAA,EAC/C,kBAAC,GAAD,EAAA,UAAiB,yDAAuE,CAAA,CAC7E,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KAAiB,SA3MqC;MAC5D;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAE;QAAY,CAAA;OAC3D;MACD;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAA0B,EAAE;QAAiB,CAAA;OAC3E;MACD;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SAAyC;UAAG,EAAE,SAAS,KAAM,QAAQ,EAAE;SAAC;SAAQ;;OAC9F;MACD;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MACL,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SAAyC;UAAG,EAAE,SAAS,KAAS,QAAQ,EAAE;SAAC;SAAQ;;OAEtF;MACD;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MAAM;QACX,IAAM,IAAQ,EAAE,SAAS,IAAI,KAAK,MAAO,EAAE,SAAS,EAAE,SAAU,IAAI,GAAG;AACvE,eACE,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,GAAD;UAAO,SAAS,IAAQ,KAAK,gBAAgB;UAAW,WAAU;oBAAlE,CACG,GAAM,IACD;;SACH,CAAA;;OAGZ;MACF;KAiKkD,MAAM;KAAgB,SAAS,MAAM,EAAE;KAAQ,CAAA;IAC1F,kBAAC,GAAD,EAAW,WAAU,QAAS,CAAA;IAC9B,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,QAAD,EAAA,UAAM,SAAY,CAAA;MAClB,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAU,QAAO,aAAiB,EAAA,CAAA;MACzC,kBAAC,QAAD,EAAA,UAAA;OAAM;QAAG,IAAe,KAAS,QAAQ,EAAE;OAAC;OAAe,EAAA,CAAA;MAC3D,kBAAC,QAAD,EAAA,UAAA;OAAM;QAAG,IAAkB,KAAS,QAAQ,EAAE;OAAC;OAAe,EAAA,CAAA;MAC1D;;IACM,EAAA,CAAA,CACT,EAAA,CAAA;GACH"}
|
|
1
|
+
{"version":3,"file":"AnalyticsFinancialAnalyticsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/AnalyticsFinancialAnalyticsPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n IllustratedEmptyState,\n PagePurpose,\n Pagination,\n ResponsiveTable,\n type ResponsiveTableColumn,\n Separator,\n} from '@burdenoff/fe-libs/ui';\nimport { usePagination } from '@burdenoff/fe-libs/shared/hooks';\nimport { DollarSign, FileText, Receipt, TrendingUp, Wallet } from 'lucide-react';\nimport { useMemo } from 'react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\nimport { formatEnumLabel } from '../utils/formatEnumLabel';\nimport { DonutChart, LineChart } from '../components/viz';\nimport type { LabeledValue, Segment } from '../components/viz';\n\ntype DeptCostRow = {\n name: string;\n salary: number;\n budget: number;\n headcount: number;\n};\n\nexport function AnalyticsFinancialAnalyticsPagePage() {\n const { employees, departments, payrollRecords, clients, projects } = useCrewFoundryStore();\n\n const monthlyPayrollTrend = useMemo(() => {\n const byPeriod = payrollRecords.reduce<\n Record<string, { gross: number; net: number; deductions: number }>\n >((acc, record) => {\n const month = new Date(record.payDate).toLocaleString('en-US', { month: 'short' });\n const current = acc[month] || { gross: 0, net: 0, deductions: 0 };\n current.gross += record.grossPay;\n current.net += record.netPay;\n current.deductions += record.deductions;\n acc[month] = current;\n return acc;\n }, {});\n return Object.entries(byPeriod)\n .map(([month, value]) => ({ month, ...value }))\n .slice(-6);\n }, [payrollRecords]);\n\n // Real spend lines only (no invented budget coefficients).\n const spendByCategory = useMemo(() => {\n const items = [\n {\n category: 'Gross Payroll',\n amount: payrollRecords.reduce((sum, record) => sum + record.grossPay, 0),\n icon: Wallet,\n },\n {\n category: 'Deductions',\n amount: payrollRecords.reduce((sum, record) => sum + record.deductions, 0),\n icon: Receipt,\n },\n {\n category: 'Project Spend',\n amount: projects.reduce((sum, project) => sum + project.spent, 0),\n icon: TrendingUp,\n },\n ];\n const total = items.reduce((sum, item) => sum + item.amount, 0) || 1;\n return items.map((item) => ({ ...item, percentage: Math.round((item.amount / total) * 100) }));\n }, [payrollRecords, projects]);\n\n const totalPayroll = employees.reduce((sum, e) => sum + e.salary, 0);\n const monthlyPayroll = Math.round(totalPayroll / 12);\n // Client revenue is recorded on each CrewClient (there is no invoicing in\n // CrewFoundry; billing for the workspace itself lives in the Billing app).\n const totalClientRevenue = clients.reduce((sum, client) => sum + (client.revenue || 0), 0);\n const activeClientCount = clients.filter((client) => client.status === 'active').length;\n const totalProjectBudget = projects.reduce((s, p) => s + p.budget, 0);\n const totalProjectSpent = projects.reduce((s, p) => s + p.spent, 0);\n\n const grossSeries: LabeledValue[] = monthlyPayrollTrend.map((m) => ({\n label: m.month,\n value: m.gross,\n }));\n const netCompare = monthlyPayrollTrend.map((m) => m.net);\n const spendSegments: Segment[] = spendByCategory\n .filter((category) => category.amount > 0)\n .map((category) => ({ label: category.category, value: category.amount }));\n const projectUtilization =\n totalProjectBudget > 0 ? Math.round((totalProjectSpent / totalProjectBudget) * 100) : 0;\n\n const deptCost: DeptCostRow[] = departments.map((d) => {\n const deptEmps = employees.filter((e) => e.department === d.name);\n const totalSal = deptEmps.reduce((s, e) => s + e.salary, 0);\n return { name: d.name, salary: totalSal, budget: d.budget, headcount: deptEmps.length };\n });\n const sortedDeptCost = [...deptCost].sort((a, b) => b.salary - a.salary);\n const totalDeptBudget = departments.reduce((s, d) => s + d.budget, 0);\n\n const clientsByRevenue = [...clients]\n .filter((client) => (client.revenue || 0) > 0)\n .sort((a, b) => b.revenue - a.revenue);\n const {\n page: clientPage,\n pageSize: clientPageSize,\n offset: clientOffset,\n setPage: setClientPage,\n } = usePagination({ initialPageSize: 6 });\n const clientPageItems = clientsByRevenue.slice(clientOffset, clientOffset + clientPageSize);\n\n const deptCostColumns: ResponsiveTableColumn<DeptCostRow>[] = [\n {\n key: 'name',\n header: 'Department',\n priority: 'primary',\n cell: (d) => <span className=\"font-medium\">{d.name}</span>,\n },\n {\n key: 'headcount',\n header: 'Headcount',\n priority: 'secondary',\n cell: (d) => <span className=\"md:block md:text-right\">{d.headcount}</span>,\n },\n {\n key: 'salary',\n header: 'Total Salary',\n priority: 'secondary',\n cell: (d) => <span className=\"md:block md:text-right\">${(d.salary / 1000).toFixed(0)}K</span>,\n },\n {\n key: 'budget',\n header: 'Dept Budget',\n priority: 'secondary',\n cell: (d) => (\n <span className=\"md:block md:text-right\">${(d.budget / 1000000).toFixed(1)}M</span>\n ),\n },\n {\n key: 'ratio',\n header: 'Salary/Budget %',\n priority: 'primary',\n cell: (d) => {\n const ratio = d.budget > 0 ? Math.round((d.salary / d.budget) * 100) : 0;\n return (\n <span className=\"md:block md:text-right\">\n <Badge variant={ratio > 50 ? 'destructive' : 'default'} className=\"text-[10px]\">\n {ratio}%\n </Badge>\n </span>\n );\n },\n },\n ];\n\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Financial Analytics</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">\n Workforce financial metrics and cost analysis\n </p>\n <PagePurpose className=\"mt-1\">\n Understand the money side of your workforce — payroll, project budgets, and spend trends\n — so you can plan headcount and control costs with confidence.\n </PagePurpose>\n </div>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Monthly Payroll</CardTitle>\n <DollarSign className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(monthlyPayroll / 1000).toFixed(0)}K\n </div>\n <p className=\"text-xs text-text-secondary\">Annual salary ÷ 12 months</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Annual Salary Cost</CardTitle>\n <Wallet className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalPayroll / 1000000).toFixed(1)}M\n </div>\n <p className=\"text-xs text-text-secondary\">{employees.length} employees</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Client Revenue</CardTitle>\n <FileText className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalClientRevenue / 1000).toFixed(1)}K\n </div>\n <p className=\"text-xs text-text-secondary\">{activeClientCount} active clients</p>\n </CardContent>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"border-border-seam\">\n <CardHeader className=\"flex flex-row items-center justify-between space-y-0 pb-2\">\n <CardTitle className=\"text-sm font-medium\">Project Budget</CardTitle>\n <TrendingUp className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n ${(totalProjectBudget / 1000000).toFixed(1)}M\n </div>\n <p className=\"text-xs text-text-secondary\">{projectUtilization}% utilized</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Payroll Trend</CardTitle>\n <CardDescription>Monthly gross payroll over the last 6 months</CardDescription>\n </CardHeader>\n <CardContent>\n {grossSeries.length > 0 ? (\n <LineChart\n series={grossSeries}\n compare={netCompare}\n seriesLabel=\"Gross pay\"\n compareLabel=\"Net pay\"\n height={220}\n valueFormatter={(value) => `$${(value / 1000).toFixed(0)}K`}\n />\n ) : (\n <p className=\"py-6 text-center text-sm text-text-secondary\">No payroll records yet.</p>\n )}\n </CardContent>\n </Card>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Spend by Category</CardTitle>\n <CardDescription>Share of tracked workforce spend</CardDescription>\n </CardHeader>\n <CardContent>\n {spendSegments.length > 0 ? (\n <DonutChart\n segments={spendSegments}\n centerLabel={`$${(spendSegments.reduce((sum, s) => sum + s.value, 0) / 1000).toFixed(0)}K`}\n centerCaption=\"Total spend\"\n valueFormatter={(value) => `$${(value / 1000).toFixed(0)}K`}\n />\n ) : (\n <p className=\"py-6 text-center text-sm text-text-secondary\">No tracked spend yet.</p>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Revenue by Client</CardTitle>\n <CardDescription>Revenue recorded against each client</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {clientsByRevenue.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No client revenue yet\"\n description=\"Record revenue on your clients under Company → Client Management.\"\n />\n )}\n {clientPageItems.map((client) => (\n <div key={client.id} className=\"flex items-center justify-between rounded border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{client.name}</p>\n <p className=\"text-xs text-text-secondary\">{client.industry}</p>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">${client.revenue.toLocaleString()}</p>\n <Badge variant=\"secondary\" className=\"text-[10px]\">\n {formatEnumLabel(client.status)}\n </Badge>\n </div>\n </div>\n ))}\n {clientsByRevenue.length > clientPageSize && (\n <Pagination\n page={clientPage}\n pageSize={clientPageSize}\n totalItems={clientsByRevenue.length}\n onPageChange={setClientPage}\n />\n )}\n </CardContent>\n </Card>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>Department Cost Analysis</CardTitle>\n <CardDescription>Total salary cost and budget allocation by department</CardDescription>\n </CardHeader>\n <CardContent>\n <ResponsiveTable columns={deptCostColumns} data={sortedDeptCost} rowKey={(d) => d.name} />\n <Separator className=\"my-3\" />\n <div className=\"flex flex-wrap items-center justify-between gap-2 text-sm font-bold\">\n <span>Total</span>\n <span>{employees.length} employees</span>\n <span>${(totalPayroll / 1000000).toFixed(1)}M salary</span>\n <span>${(totalDeptBudget / 1000000).toFixed(1)}M budget</span>\n </div>\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AA+BA,SAAgB,IAAsC;CACpD,IAAM,EAAE,cAAW,gBAAa,mBAAgB,YAAS,gBAAa,GAAqB,EAErF,IAAsB,QAAc;EACxC,IAAM,IAAW,EAAe,QAE7B,GAAK,MAAW;GACjB,IAAM,IAAQ,IAAI,KAAK,EAAO,QAAQ,CAAC,eAAe,SAAS,EAAE,OAAO,SAAS,CAAC,EAC5E,IAAU,EAAI,MAAU;IAAE,OAAO;IAAG,KAAK;IAAG,YAAY;IAAG;AAKjE,UAJA,EAAQ,SAAS,EAAO,UACxB,EAAQ,OAAO,EAAO,QACtB,EAAQ,cAAc,EAAO,YAC7B,EAAI,KAAS,GACN;KACN,EAAE,CAAC;AACN,SAAO,OAAO,QAAQ,EAAS,CAC5B,KAAK,CAAC,GAAO,QAAY;GAAE;GAAO,GAAG;GAAO,EAAE,CAC9C,MAAM,GAAG;IACX,CAAC,EAAe,CAAC,EAGd,IAAkB,QAAc;EACpC,IAAM,IAAQ;GACZ;IACE,UAAU;IACV,QAAQ,EAAe,QAAQ,GAAK,MAAW,IAAM,EAAO,UAAU,EAAE;IACxE,MAAM;IACP;GACD;IACE,UAAU;IACV,QAAQ,EAAe,QAAQ,GAAK,MAAW,IAAM,EAAO,YAAY,EAAE;IAC1E,MAAM;IACP;GACD;IACE,UAAU;IACV,QAAQ,EAAS,QAAQ,GAAK,MAAY,IAAM,EAAQ,OAAO,EAAE;IACjE,MAAM;IACP;GACF,EACK,IAAQ,EAAM,QAAQ,GAAK,MAAS,IAAM,EAAK,QAAQ,EAAE,IAAI;AACnE,SAAO,EAAM,KAAK,OAAU;GAAE,GAAG;GAAM,YAAY,KAAK,MAAO,EAAK,SAAS,IAAS,IAAI;GAAE,EAAE;IAC7F,CAAC,GAAgB,EAAS,CAAC,EAExB,IAAe,EAAU,QAAQ,GAAK,MAAM,IAAM,EAAE,QAAQ,EAAE,EAC9D,IAAiB,KAAK,MAAM,IAAe,GAAG,EAG9C,IAAqB,EAAQ,QAAQ,GAAK,MAAW,KAAO,EAAO,WAAW,IAAI,EAAE,EACpF,IAAoB,EAAQ,QAAQ,MAAW,EAAO,WAAW,SAAS,CAAC,QAC3E,IAAqB,EAAS,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,EAAE,EAC/D,IAAoB,EAAS,QAAQ,GAAG,MAAM,IAAI,EAAE,OAAO,EAAE,EAE7D,IAA8B,EAAoB,KAAK,OAAO;EAClE,OAAO,EAAE;EACT,OAAO,EAAE;EACV,EAAE,EACG,IAAa,EAAoB,KAAK,MAAM,EAAE,IAAI,EAClD,IAA2B,EAC9B,QAAQ,MAAa,EAAS,SAAS,EAAE,CACzC,KAAK,OAAc;EAAE,OAAO,EAAS;EAAU,OAAO,EAAS;EAAQ,EAAE,EACtE,IACJ,IAAqB,IAAI,KAAK,MAAO,IAAoB,IAAsB,IAAI,GAAG,GAOlF,IAAiB,CAAC,GALQ,EAAY,KAAK,MAAM;EACrD,IAAM,IAAW,EAAU,QAAQ,MAAM,EAAE,eAAe,EAAE,KAAK,EAC3D,IAAW,EAAS,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,EAAE;AAC3D,SAAO;GAAE,MAAM,EAAE;GAAM,QAAQ;GAAU,QAAQ,EAAE;GAAQ,WAAW,EAAS;GAAQ;GACvF,CACkC,CAAC,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,OAAO,EAClE,IAAkB,EAAY,QAAQ,GAAG,MAAM,IAAI,EAAE,QAAQ,EAAE,EAE/D,IAAmB,CAAC,GAAG,EAAQ,CAClC,QAAQ,OAAY,EAAO,WAAW,KAAK,EAAE,CAC7C,MAAM,GAAG,MAAM,EAAE,UAAU,EAAE,QAAQ,EAClC,EACJ,MAAM,GACN,UAAU,GACV,QAAQ,GACR,SAAS,MACP,EAAc,EAAE,iBAAiB,GAAG,CAAC,EACnC,IAAkB,EAAiB,MAAM,GAAc,IAAe,EAAe;AA8C3F,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAc,uBAAkC,CAAA;MAC7C,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAEvC,CAAA;KACJ,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAGhB,CAAA;KACV,EAAA,CAAA;IACF,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,MAAA;MAAK,WAAU;gBAA5C,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA2B,CAAA,EACtE,kBAAC,GAAD,EAAY,WAAU,+BAAgC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiD;SAC5C,IAAiB,KAAM,QAAQ,EAAE;QAAC;QACjC;UACN,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAA6B,CAAA,CAC5D,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA8B,CAAA,EACzE,kBAAC,GAAD,EAAQ,WAAU,+BAAgC,CAAA,CACvC;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiD;SAC5C,IAAe,KAAS,QAAQ,EAAE;QAAC;QAClC;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAA4C,EAAU,QAAO,aAAc;SAC/D,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA0B,CAAA,EACrE,kBAAC,GAAD,EAAU,WAAU,+BAAgC,CAAA,CACzC;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiD;SAC5C,IAAqB,KAAM,QAAQ,EAAE;QAAC;QACrC;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAA4C,GAAkB,kBAAmB;SACrE,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAA0B,CAAA,EACrE,kBAAC,GAAD,EAAY,WAAU,+BAAgC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf;QAAiD;SAC5C,IAAqB,KAAS,QAAQ,EAAE;QAAC;QACxC;UACN,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAA4C,GAAmB,aAAc;SACjE,EAAA,CAAA,CACJ;;KACR;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,iBAAyB,CAAA,EACpC,kBAAC,GAAD,EAAA,UAAiB,gDAA8D,CAAA,CACpE,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EAAY,SAAS,IACpB,kBAAC,GAAD;IACE,QAAQ;IACR,SAAS;IACT,aAAY;IACZ,cAAa;IACb,QAAQ;IACR,iBAAiB,MAAU,KAAK,IAAQ,KAAM,QAAQ,EAAE,CAAC;IACzD,CAAA,GAEF,kBAAC,KAAD;IAAG,WAAU;cAA+C;IAA2B,CAAA,EAE7E,CAAA,CACT,EAAA,CAAA;GAEP,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,qBAA6B,CAAA,EACxC,kBAAC,GAAD,EAAA,UAAiB,oCAAkD,CAAA,CACxD,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UACG,EAAc,SAAS,IACtB,kBAAC,GAAD;KACE,UAAU;KACV,aAAa,KAAK,EAAc,QAAQ,GAAK,MAAM,IAAM,EAAE,OAAO,EAAE,GAAG,KAAM,QAAQ,EAAE,CAAC;KACxF,eAAc;KACd,iBAAiB,MAAU,KAAK,IAAQ,KAAM,QAAQ,EAAE,CAAC;KACzD,CAAA,GAEF,kBAAC,KAAD;KAAG,WAAU;eAA+C;KAAyB,CAAA,EAE3E,CAAA,CACT,EAAA,CAAA,EAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,qBAA6B,CAAA,EACxC,kBAAC,GAAD,EAAA,UAAiB,wCAAsD,CAAA,CAC5D,EAAA,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eAAvB;MACG,EAAiB,WAAW,KAC3B,kBAAC,GAAD;OACE,cAAa;OACb,OAAM;OACN,aAAY;OACZ,CAAA;MAEH,EAAgB,KAAK,MACpB,kBAAC,OAAD;OAAqB,WAAU;iBAA/B,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;QAAG,WAAU;kBAAuB,EAAO;QAAS,CAAA,EACpD,kBAAC,KAAD;QAAG,WAAU;kBAA+B,EAAO;QAAa,CAAA,CAC5D,EAAA,CAAA,EACN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,KAAD;SAAG,WAAU;mBAAb,CAAmC,KAAE,EAAO,QAAQ,gBAAgB,CAAK;YACzE,kBAAC,GAAD;SAAO,SAAQ;SAAY,WAAU;mBAClC,EAAgB,EAAO,OAAO;SACzB,CAAA,CACJ;UACF;SAXI,EAAO,GAWX,CACN;MACD,EAAiB,SAAS,KACzB,kBAAC,GAAD;OACE,MAAM;OACN,UAAU;OACV,YAAY,EAAiB;OAC7B,cAAc;OACd,CAAA;MAEQ;OACT,EAAA,CAAA,CACH;;GAEN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,4BAAoC,CAAA,EAC/C,kBAAC,GAAD,EAAA,UAAiB,yDAAuE,CAAA,CAC7E,EAAA,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA;IACE,kBAAC,GAAD;KAAiB,SAtMqC;MAC5D;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAe,EAAE;QAAY,CAAA;OAC3D;MACD;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAA0B,EAAE;QAAiB,CAAA;OAC3E;MACD;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MAAM,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SAAyC;UAAG,EAAE,SAAS,KAAM,QAAQ,EAAE;SAAC;SAAQ;;OAC9F;MACD;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MACL,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SAAyC;UAAG,EAAE,SAAS,KAAS,QAAQ,EAAE;SAAC;SAAQ;;OAEtF;MACD;OACE,KAAK;OACL,QAAQ;OACR,UAAU;OACV,OAAO,MAAM;QACX,IAAM,IAAQ,EAAE,SAAS,IAAI,KAAK,MAAO,EAAE,SAAS,EAAE,SAAU,IAAI,GAAG;AACvE,eACE,kBAAC,QAAD;SAAM,WAAU;mBACd,kBAAC,GAAD;UAAO,SAAS,IAAQ,KAAK,gBAAgB;UAAW,WAAU;oBAAlE,CACG,GAAM,IACD;;SACH,CAAA;;OAGZ;MACF;KA4JkD,MAAM;KAAgB,SAAS,MAAM,EAAE;KAAQ,CAAA;IAC1F,kBAAC,GAAD,EAAW,WAAU,QAAS,CAAA;IAC9B,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,QAAD,EAAA,UAAM,SAAY,CAAA;MAClB,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAU,QAAO,aAAiB,EAAA,CAAA;MACzC,kBAAC,QAAD,EAAA,UAAA;OAAM;QAAG,IAAe,KAAS,QAAQ,EAAE;OAAC;OAAe,EAAA,CAAA;MAC3D,kBAAC,QAAD,EAAA,UAAA;OAAM;QAAG,IAAkB,KAAS,QAAQ,EAAE;OAAC;OAAe,EAAA,CAAA;MAC1D;;IACM,EAAA,CAAA,CACT,EAAA,CAAA;GACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AnalyticsHiringAnalyticsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"AnalyticsHiringAnalyticsPagePage.d.ts","sourceRoot":"","sources":["../../../src/crewfoundry/pages/AnalyticsHiringAnalyticsPagePage.tsx"],"names":[],"mappings":"AAuDA,wBAAgB,gCAAgC,gCAkY/C"}
|
|
@@ -13,20 +13,26 @@ var T = (e) => {
|
|
|
13
13
|
let t = new Date(e);
|
|
14
14
|
return `${t.getFullYear()}-${String(t.getMonth()).padStart(2, "0")}`;
|
|
15
15
|
}, E = (/* @__PURE__ */ new Date("2026-03-01")).getTime();
|
|
16
|
-
function D() {
|
|
17
|
-
|
|
16
|
+
function D(e) {
|
|
17
|
+
return e === 0 ? "$0" : e < 1e3 ? `$${e.toLocaleString()}` : `$${Math.round(e / 1e3)}k`;
|
|
18
|
+
}
|
|
19
|
+
function O(e, t) {
|
|
20
|
+
return `${D(e)} – ${D(t)}`;
|
|
21
|
+
}
|
|
22
|
+
function k() {
|
|
23
|
+
let { jobPostings: D, candidates: k, interviews: A } = e(), j = i(() => k.length === 0 && A.length === 0 ? [] : Array.from(new Set([...k.map((e) => T(e.appliedDate)), ...A.map((e) => T(e.date))])).sort().slice(-6).map((e) => {
|
|
18
24
|
let [t, n] = e.split("-").map(Number), r = new Date(t, n, 1).toLocaleString("en-US", {
|
|
19
25
|
month: "short",
|
|
20
26
|
year: "2-digit"
|
|
21
|
-
}), i =
|
|
27
|
+
}), i = k.filter((t) => T(t.appliedDate) === e);
|
|
22
28
|
return {
|
|
23
29
|
month: r,
|
|
24
30
|
applications: i.length,
|
|
25
|
-
interviews:
|
|
31
|
+
interviews: A.filter((t) => T(t.date) === e).length,
|
|
26
32
|
hires: i.filter((e) => e.status === "hired").length
|
|
27
33
|
};
|
|
28
|
-
}), [
|
|
29
|
-
let e =
|
|
34
|
+
}), [k, A]), M = i(() => {
|
|
35
|
+
let e = k.reduce((e, t) => {
|
|
30
36
|
let n = t.source || "Unknown", r = e[n] || {
|
|
31
37
|
applications: 0,
|
|
32
38
|
hires: 0
|
|
@@ -39,7 +45,7 @@ function D() {
|
|
|
39
45
|
hires: t.hires,
|
|
40
46
|
conversionRate: Number((t.hires / Math.max(t.applications, 1) * 100).toFixed(1))
|
|
41
47
|
})).sort((e, t) => t.conversionRate - e.conversionRate);
|
|
42
|
-
}, [
|
|
48
|
+
}, [k]), N = i(() => {
|
|
43
49
|
let e = D.filter((e) => e.status === "active");
|
|
44
50
|
if (e.length === 0) return 0;
|
|
45
51
|
let t = e.reduce((e, t) => {
|
|
@@ -47,54 +53,54 @@ function D() {
|
|
|
47
53
|
return Number.isNaN(n) ? e : e + Math.max(0, (E - n) / (1e3 * 60 * 60 * 24));
|
|
48
54
|
}, 0);
|
|
49
55
|
return Math.round(t / e.length);
|
|
50
|
-
}, [D]),
|
|
56
|
+
}, [D]), P = D.filter((e) => e.status === "active"), F = D.reduce((e, t) => e + t.applicants, 0), I = A.filter((e) => e.status === "scheduled").length, L = A.filter((e) => e.status === "completed").length, R = k.filter((e) => e.status === "offer").length, z = k.filter((e) => e.status === "hired").length, B = k.filter((e) => [
|
|
51
57
|
"interview",
|
|
52
58
|
"assessment",
|
|
53
59
|
"offer",
|
|
54
60
|
"hired"
|
|
55
|
-
].includes(e.status)).length,
|
|
61
|
+
].includes(e.status)).length, V = k.filter((e) => ["offer", "hired"].includes(e.status)).length, H = Math.round(V / Math.max(B, 1) * 100), U = Math.round(z / Math.max(R + z, 1) * 100), W = j.map((e) => ({
|
|
56
62
|
label: e.month,
|
|
57
63
|
value: e.applications
|
|
58
|
-
})),
|
|
64
|
+
})), G = j.map((e) => e.hires), K = [
|
|
59
65
|
{
|
|
60
66
|
status: "New",
|
|
61
|
-
count:
|
|
67
|
+
count: k.filter((e) => e.status === "new").length,
|
|
62
68
|
color: "bg-text-secondary"
|
|
63
69
|
},
|
|
64
70
|
{
|
|
65
71
|
status: "Screening",
|
|
66
|
-
count:
|
|
72
|
+
count: k.filter((e) => e.status === "screening").length,
|
|
67
73
|
color: "bg-status-info-text"
|
|
68
74
|
},
|
|
69
75
|
{
|
|
70
76
|
status: "Interview",
|
|
71
|
-
count:
|
|
77
|
+
count: k.filter((e) => e.status === "interview").length,
|
|
72
78
|
color: "bg-status-info-text"
|
|
73
79
|
},
|
|
74
80
|
{
|
|
75
81
|
status: "Assessment",
|
|
76
|
-
count:
|
|
82
|
+
count: k.filter((e) => e.status === "assessment").length,
|
|
77
83
|
color: "bg-action-primary-bg"
|
|
78
84
|
},
|
|
79
85
|
{
|
|
80
86
|
status: "Offer",
|
|
81
|
-
count:
|
|
87
|
+
count: R,
|
|
82
88
|
color: "bg-status-warning-text"
|
|
83
89
|
},
|
|
84
90
|
{
|
|
85
91
|
status: "Hired",
|
|
86
|
-
count:
|
|
92
|
+
count: z,
|
|
87
93
|
color: "bg-status-success-text"
|
|
88
94
|
},
|
|
89
95
|
{
|
|
90
96
|
status: "Rejected",
|
|
91
|
-
count:
|
|
97
|
+
count: k.filter((e) => e.status === "rejected").length,
|
|
92
98
|
color: "bg-status-error-text"
|
|
93
99
|
}
|
|
94
|
-
],
|
|
100
|
+
], q = K.reduce((e, t) => e + t.count, 0), J = K.map((e) => ({
|
|
95
101
|
label: e.status,
|
|
96
102
|
value: e.count
|
|
97
|
-
})),
|
|
103
|
+
})), Y = [...P].sort((e, t) => t.applicants - e.applicants), { page: X, pageSize: Z, offset: Q, setPage: $ } = w({ initialPageSize: 8 }), ee = Y.slice(Q, Q + Z);
|
|
98
104
|
return /* @__PURE__ */ d("div", {
|
|
99
105
|
className: "space-y-6 p-6",
|
|
100
106
|
children: [
|
|
@@ -130,10 +136,10 @@ function D() {
|
|
|
130
136
|
}), /* @__PURE__ */ u(a, { className: "h-4 w-4 text-text-secondary" })]
|
|
131
137
|
}), /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ u("div", {
|
|
132
138
|
className: "text-2xl font-bold tabular-nums",
|
|
133
|
-
children:
|
|
139
|
+
children: P.length
|
|
134
140
|
}), /* @__PURE__ */ d("p", {
|
|
135
141
|
className: "text-xs text-text-secondary",
|
|
136
|
-
children: [
|
|
142
|
+
children: [F, " total applicants"]
|
|
137
143
|
})] })]
|
|
138
144
|
}),
|
|
139
145
|
/* @__PURE__ */ d(v, {
|
|
@@ -147,7 +153,7 @@ function D() {
|
|
|
147
153
|
}), /* @__PURE__ */ u(o, { className: "h-4 w-4 text-text-secondary" })]
|
|
148
154
|
}), /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ d("div", {
|
|
149
155
|
className: "text-2xl font-bold tabular-nums",
|
|
150
|
-
children: [
|
|
156
|
+
children: [N, " days"]
|
|
151
157
|
}), /* @__PURE__ */ u("p", {
|
|
152
158
|
className: "text-xs text-text-secondary",
|
|
153
159
|
children: "Across active postings"
|
|
@@ -164,13 +170,13 @@ function D() {
|
|
|
164
170
|
}), /* @__PURE__ */ u(s, { className: "h-4 w-4 text-text-secondary" })]
|
|
165
171
|
}), /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ d("div", {
|
|
166
172
|
className: "text-2xl font-bold tabular-nums",
|
|
167
|
-
children: [
|
|
173
|
+
children: [H, "%"]
|
|
168
174
|
}), /* @__PURE__ */ d("p", {
|
|
169
175
|
className: "text-xs text-text-secondary",
|
|
170
176
|
children: [
|
|
171
|
-
F,
|
|
172
|
-
" scheduled, ",
|
|
173
177
|
I,
|
|
178
|
+
" scheduled, ",
|
|
179
|
+
L,
|
|
174
180
|
" completed"
|
|
175
181
|
]
|
|
176
182
|
})] })]
|
|
@@ -186,10 +192,10 @@ function D() {
|
|
|
186
192
|
}), /* @__PURE__ */ u(c, { className: "h-4 w-4 text-text-secondary" })]
|
|
187
193
|
}), /* @__PURE__ */ d(m, { children: [/* @__PURE__ */ d("div", {
|
|
188
194
|
className: "text-2xl font-bold tabular-nums",
|
|
189
|
-
children: [
|
|
195
|
+
children: [U, "%"]
|
|
190
196
|
}), /* @__PURE__ */ d("p", {
|
|
191
197
|
className: "text-xs text-text-secondary",
|
|
192
|
-
children: [
|
|
198
|
+
children: [z, " hired to date"]
|
|
193
199
|
})] })]
|
|
194
200
|
})
|
|
195
201
|
]
|
|
@@ -198,12 +204,12 @@ function D() {
|
|
|
198
204
|
className: "grid gap-4 md:grid-cols-7",
|
|
199
205
|
children: [/* @__PURE__ */ d(p, {
|
|
200
206
|
className: "col-span-4",
|
|
201
|
-
children: [/* @__PURE__ */ d(g, { children: [/* @__PURE__ */ u(_, { children: "Monthly Hiring Activity" }), /* @__PURE__ */ u(h, { children: "Applications, interviews, and hires over time" })] }), /* @__PURE__ */ u(m, { children:
|
|
207
|
+
children: [/* @__PURE__ */ d(g, { children: [/* @__PURE__ */ u(_, { children: "Monthly Hiring Activity" }), /* @__PURE__ */ u(h, { children: "Applications, interviews, and hires over time" })] }), /* @__PURE__ */ u(m, { children: W.length === 0 ? /* @__PURE__ */ u("p", {
|
|
202
208
|
className: "py-6 text-center text-sm text-text-secondary",
|
|
203
209
|
children: "No hiring activity recorded yet."
|
|
204
210
|
}) : /* @__PURE__ */ u(n, {
|
|
205
|
-
series:
|
|
206
|
-
compare:
|
|
211
|
+
series: W,
|
|
212
|
+
compare: G,
|
|
207
213
|
seriesLabel: "Applications",
|
|
208
214
|
compareLabel: "Hires",
|
|
209
215
|
height: 220,
|
|
@@ -211,8 +217,8 @@ function D() {
|
|
|
211
217
|
}) })]
|
|
212
218
|
}), /* @__PURE__ */ d(p, {
|
|
213
219
|
className: "col-span-3",
|
|
214
|
-
children: [/* @__PURE__ */ d(g, { children: [/* @__PURE__ */ u(_, { children: "Pipeline Status" }), /* @__PURE__ */ u(h, { children: "Current candidate distribution" })] }), /* @__PURE__ */ u(m, { children:
|
|
215
|
-
series:
|
|
220
|
+
children: [/* @__PURE__ */ d(g, { children: [/* @__PURE__ */ u(_, { children: "Pipeline Status" }), /* @__PURE__ */ u(h, { children: "Current candidate distribution" })] }), /* @__PURE__ */ u(m, { children: q > 0 ? /* @__PURE__ */ u(r, {
|
|
221
|
+
series: J,
|
|
216
222
|
colorful: !0,
|
|
217
223
|
height: 220,
|
|
218
224
|
title: "Candidate pipeline by stage"
|
|
@@ -265,14 +271,14 @@ function D() {
|
|
|
265
271
|
})
|
|
266
272
|
}
|
|
267
273
|
],
|
|
268
|
-
data:
|
|
274
|
+
data: M,
|
|
269
275
|
rowKey: (e) => e.source,
|
|
270
276
|
emptyState: /* @__PURE__ */ u("p", {
|
|
271
277
|
className: "py-6 text-center text-sm text-text-secondary",
|
|
272
278
|
children: "No candidate source data yet."
|
|
273
279
|
})
|
|
274
280
|
}) })] }),
|
|
275
|
-
/* @__PURE__ */ d(p, { children: [/* @__PURE__ */ d(g, { children: [/* @__PURE__ */ u(_, { children: "Open Positions Detail" }), /* @__PURE__ */ u(h, { children: "Active job postings and applicant counts" })] }), /* @__PURE__ */ u(m, { children:
|
|
281
|
+
/* @__PURE__ */ d(p, { children: [/* @__PURE__ */ d(g, { children: [/* @__PURE__ */ u(_, { children: "Open Positions Detail" }), /* @__PURE__ */ u(h, { children: "Active job postings and applicant counts" })] }), /* @__PURE__ */ u(m, { children: Y.length === 0 ? /* @__PURE__ */ u(b, {
|
|
276
282
|
illustration: "empty-data",
|
|
277
283
|
title: "No open positions",
|
|
278
284
|
description: "Active job postings will appear here once you publish a role."
|
|
@@ -319,15 +325,9 @@ function D() {
|
|
|
319
325
|
key: "salary",
|
|
320
326
|
header: "Salary Range",
|
|
321
327
|
priority: "secondary",
|
|
322
|
-
cell: (e) => /* @__PURE__ */
|
|
328
|
+
cell: (e) => /* @__PURE__ */ u("span", {
|
|
323
329
|
className: "md:block md:text-right text-text-secondary",
|
|
324
|
-
children:
|
|
325
|
-
"$",
|
|
326
|
-
(e.salaryMin / 1e3).toFixed(0),
|
|
327
|
-
"K-$",
|
|
328
|
-
(e.salaryMax / 1e3).toFixed(0),
|
|
329
|
-
"K"
|
|
330
|
-
]
|
|
330
|
+
children: O(e.salaryMin, e.salaryMax)
|
|
331
331
|
})
|
|
332
332
|
},
|
|
333
333
|
{
|
|
@@ -341,19 +341,19 @@ function D() {
|
|
|
341
341
|
})
|
|
342
342
|
}
|
|
343
343
|
],
|
|
344
|
-
data:
|
|
344
|
+
data: ee,
|
|
345
345
|
rowKey: (e) => e.id
|
|
346
|
-
}),
|
|
347
|
-
page:
|
|
348
|
-
pageSize:
|
|
349
|
-
totalItems:
|
|
350
|
-
onPageChange:
|
|
346
|
+
}), Y.length > Z && /* @__PURE__ */ u(S, {
|
|
347
|
+
page: X,
|
|
348
|
+
pageSize: Z,
|
|
349
|
+
totalItems: Y.length,
|
|
350
|
+
onPageChange: $,
|
|
351
351
|
className: "mt-3"
|
|
352
352
|
})] }) })] })
|
|
353
353
|
]
|
|
354
354
|
});
|
|
355
355
|
}
|
|
356
356
|
//#endregion
|
|
357
|
-
export {
|
|
357
|
+
export { k as AnalyticsHiringAnalyticsPagePage };
|
|
358
358
|
|
|
359
359
|
//# sourceMappingURL=AnalyticsHiringAnalyticsPagePage.js.map
|