@burdenoff/microfe-crewfoundry 2026.625.2 → 2026.703.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/forms/ConfirmDialog.js +43 -0
- package/dist/crewfoundry/components/forms/ConfirmDialog.js.map +1 -0
- package/dist/crewfoundry/components/forms/FormModal.js +220 -0
- package/dist/crewfoundry/components/forms/FormModal.js.map +1 -0
- package/dist/crewfoundry/components/forms/formValues.js +20 -0
- package/dist/crewfoundry/components/forms/formValues.js.map +1 -0
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js +255 -65
- package/dist/crewfoundry/pages/CommunicationsAnnouncementsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js +28 -17
- package/dist/crewfoundry/pages/CommunicationsCallsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsFilesPagePage.js +57 -53
- package/dist/crewfoundry/pages/CommunicationsFilesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js +32 -23
- package/dist/crewfoundry/pages/CommunicationsMessagesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js +44 -29
- package/dist/crewfoundry/pages/CommunicationsVideoCallsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js +183 -57
- package/dist/crewfoundry/pages/CompanyClientManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyCompanyDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js +214 -92
- package/dist/crewfoundry/pages/CompanyVendorManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js +165 -131
- package/dist/crewfoundry/pages/DashboardDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/DashboardEnhancedDashboardPagePage.js +163 -133
- package/dist/crewfoundry/pages/DashboardEnhancedDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js +269 -54
- package/dist/crewfoundry/pages/HiringCandidatesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js +225 -78
- package/dist/crewfoundry/pages/HiringCreateJobPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js +213 -59
- package/dist/crewfoundry/pages/HiringInterviewsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js +287 -90
- package/dist/crewfoundry/pages/HiringJobDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HiringJobsPagePage.js +49 -47
- package/dist/crewfoundry/pages/HiringJobsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js +166 -59
- package/dist/crewfoundry/pages/HrDepartmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrEmployeesPagePage.js +274 -83
- package/dist/crewfoundry/pages/HrEmployeesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js +198 -88
- package/dist/crewfoundry/pages/HrLeaveManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrPayrollPagePage.js +216 -88
- package/dist/crewfoundry/pages/HrPayrollPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js +216 -66
- package/dist/crewfoundry/pages/HrTimeTrackingPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js +6 -7
- package/dist/crewfoundry/pages/LearningAssessmentsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js +83 -68
- package/dist/crewfoundry/pages/LearningCourseDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js +167 -46
- package/dist/crewfoundry/pages/LearningCoursesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js +75 -55
- package/dist/crewfoundry/pages/LearningLearningPathDetailPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js +152 -79
- package/dist/crewfoundry/pages/LearningLearningPathsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js +75 -63
- package/dist/crewfoundry/pages/LearningMyLearningPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js +3 -1
- package/dist/crewfoundry/pages/MarketplaceBookingManagementPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMarketplacePagePage.js.map +1 -1
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js +235 -87
- package/dist/crewfoundry/pages/MarketplaceMyServicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js +199 -86
- package/dist/crewfoundry/pages/PaymentsInvoicesPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentDashboardPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js +21 -40
- package/dist/crewfoundry/pages/PaymentsPaymentMethodsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsCreateProjectPagePage.js +117 -45
- package/dist/crewfoundry/pages/ProjectsCreateProjectPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js +249 -90
- package/dist/crewfoundry/pages/ProjectsProjectDetailsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js +47 -45
- package/dist/crewfoundry/pages/ProjectsProjectsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamAnalyticsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js +262 -133
- package/dist/crewfoundry/pages/TeamsTeamSettingsPagePage.js.map +1 -1
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js +144 -77
- package/dist/crewfoundry/pages/TeamsTeamsPagePage.js.map +1 -1
- package/dist/crewfoundry/store/crewfoundryStore.js +306 -51
- package/dist/crewfoundry/store/crewfoundryStore.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,160 +1,283 @@
|
|
|
1
1
|
import { useCrewFoundryStore as e } from "../store/crewfoundryStore.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { FormModal as t, useFormModal as n } from "../components/forms/FormModal.js";
|
|
3
|
+
import { getNumber as r, getText as i, newClientId as a } from "../components/forms/formValues.js";
|
|
4
|
+
import { useMemo as o } from "react";
|
|
5
|
+
import { CheckCircle as s, Clock as c, LogIn as l, LogOut as u, Timer as d } from "lucide-react";
|
|
6
|
+
import { jsx as f, jsxs as p } from "react/jsx-runtime";
|
|
7
|
+
import { Badge as m, Button as h, Card as g, CardContent as _, CardDescription as v, CardHeader as y, CardTitle as b, GlassCard as x, GradientText as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
|
|
5
8
|
//#region src/crewfoundry/pages/HrTimeTrackingPagePage.tsx
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
var w = [
|
|
10
|
+
{
|
|
11
|
+
label: "Pending",
|
|
12
|
+
value: "pending"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
label: "Approved",
|
|
16
|
+
value: "approved"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
label: "Rejected",
|
|
20
|
+
value: "rejected"
|
|
21
|
+
}
|
|
22
|
+
], T = [
|
|
23
|
+
{
|
|
24
|
+
name: "clockOut",
|
|
25
|
+
label: "Clock-out time",
|
|
26
|
+
type: "text",
|
|
27
|
+
required: !0,
|
|
28
|
+
placeholder: "e.g. 17:30"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "totalHours",
|
|
32
|
+
label: "Total hours",
|
|
33
|
+
type: "number",
|
|
34
|
+
required: !0
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: "overtime",
|
|
38
|
+
label: "Overtime hours",
|
|
39
|
+
type: "number"
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "status",
|
|
43
|
+
label: "Status",
|
|
44
|
+
type: "select",
|
|
45
|
+
options: w
|
|
46
|
+
}
|
|
47
|
+
];
|
|
48
|
+
function E() {
|
|
49
|
+
let { timeEntries: E, employees: D, addTimeEntry: O, updateTimeEntry: k } = e(), A = n(), j = n(), M = o(() => D.map((e) => ({
|
|
50
|
+
label: `${e.firstName} ${e.lastName}`,
|
|
51
|
+
value: e.id
|
|
52
|
+
})), [D]), N = o(() => [
|
|
53
|
+
{
|
|
54
|
+
name: "employeeId",
|
|
55
|
+
label: "Employee",
|
|
56
|
+
type: "select",
|
|
57
|
+
required: !0,
|
|
58
|
+
options: M,
|
|
59
|
+
colSpan: 2
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "date",
|
|
63
|
+
label: "Date",
|
|
64
|
+
type: "date",
|
|
65
|
+
required: !0
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "project",
|
|
69
|
+
label: "Project",
|
|
70
|
+
type: "text",
|
|
71
|
+
required: !0
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "clockIn",
|
|
75
|
+
label: "Clock-in time",
|
|
76
|
+
type: "text",
|
|
77
|
+
required: !0,
|
|
78
|
+
placeholder: "e.g. 09:00"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "clockOut",
|
|
82
|
+
label: "Clock-out time",
|
|
83
|
+
type: "text",
|
|
84
|
+
placeholder: "e.g. 17:30"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: "totalHours",
|
|
88
|
+
label: "Total hours",
|
|
89
|
+
type: "number"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: "overtime",
|
|
93
|
+
label: "Overtime hours",
|
|
94
|
+
type: "number"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "status",
|
|
98
|
+
label: "Status",
|
|
99
|
+
type: "select",
|
|
100
|
+
options: w
|
|
101
|
+
}
|
|
102
|
+
], [M]), P = E.reduce((e, t) => e + t.totalHours, 0), F = E.reduce((e, t) => e + t.overtime, 0), I = E.filter((e) => e.status === "approved").length, L = E.filter((e) => e.status === "pending").length, R = E.reduce((e, t) => (e[t.project] = (e[t.project] || 0) + t.totalHours, e), {}), z = Math.max(...Object.values(R), 1), B = async (e) => {
|
|
103
|
+
let t = i(e, "employeeId"), n = D.find((e) => e.id === t);
|
|
104
|
+
await O({
|
|
105
|
+
id: a(),
|
|
106
|
+
employeeId: t,
|
|
107
|
+
employeeName: n ? `${n.firstName} ${n.lastName}` : "",
|
|
108
|
+
date: i(e, "date"),
|
|
109
|
+
clockIn: i(e, "clockIn"),
|
|
110
|
+
clockOut: i(e, "clockOut"),
|
|
111
|
+
totalHours: r(e, "totalHours"),
|
|
112
|
+
overtime: r(e, "overtime"),
|
|
113
|
+
project: i(e, "project"),
|
|
114
|
+
status: i(e, "status") || "pending"
|
|
115
|
+
});
|
|
116
|
+
}, V = async (e) => {
|
|
117
|
+
let t = j.context;
|
|
118
|
+
t && await k(t.id, {
|
|
119
|
+
clockOut: i(e, "clockOut"),
|
|
120
|
+
totalHours: r(e, "totalHours"),
|
|
121
|
+
overtime: r(e, "overtime"),
|
|
122
|
+
status: i(e, "status") || t.status
|
|
123
|
+
});
|
|
124
|
+
}, H = j.context ? {
|
|
125
|
+
clockOut: j.context.clockOut,
|
|
126
|
+
totalHours: j.context.totalHours,
|
|
127
|
+
overtime: j.context.overtime,
|
|
128
|
+
status: j.context.status
|
|
129
|
+
} : void 0;
|
|
130
|
+
return /* @__PURE__ */ p("div", {
|
|
9
131
|
className: "space-y-6 p-6",
|
|
10
132
|
children: [
|
|
11
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ p("div", {
|
|
12
134
|
className: "flex items-center justify-between",
|
|
13
|
-
children: [/* @__PURE__ */
|
|
14
|
-
/* @__PURE__ */
|
|
135
|
+
children: [/* @__PURE__ */ p("div", { children: [
|
|
136
|
+
/* @__PURE__ */ f("h1", {
|
|
15
137
|
className: "text-2xl font-semibold",
|
|
16
|
-
children: /* @__PURE__ */
|
|
138
|
+
children: /* @__PURE__ */ f(S, { children: "Time Tracking" })
|
|
17
139
|
}),
|
|
18
|
-
/* @__PURE__ */
|
|
140
|
+
/* @__PURE__ */ f("p", {
|
|
19
141
|
className: "text-sm text-text-secondary",
|
|
20
142
|
children: "Monitor employee work hours and overtime"
|
|
21
143
|
}),
|
|
22
|
-
/* @__PURE__ */
|
|
144
|
+
/* @__PURE__ */ f(C, {
|
|
23
145
|
className: "mt-1",
|
|
24
146
|
children: "Track hours and attendance across your team — use it to monitor logged time, overtime, and timesheet approvals."
|
|
25
147
|
})
|
|
26
|
-
] }), /* @__PURE__ */
|
|
148
|
+
] }), /* @__PURE__ */ p(h, {
|
|
27
149
|
size: "sm",
|
|
28
|
-
|
|
150
|
+
onClick: () => A.openModal(),
|
|
151
|
+
children: [/* @__PURE__ */ f(d, { className: "h-4 w-4 mr-1" }), "Add Entry"]
|
|
29
152
|
})]
|
|
30
153
|
}),
|
|
31
|
-
/* @__PURE__ */
|
|
154
|
+
/* @__PURE__ */ p("div", {
|
|
32
155
|
className: "grid gap-4 md:grid-cols-2 lg:grid-cols-4",
|
|
33
156
|
children: [
|
|
34
|
-
/* @__PURE__ */
|
|
157
|
+
/* @__PURE__ */ p(x, {
|
|
35
158
|
treatment: "glass",
|
|
36
159
|
glow: !0,
|
|
37
160
|
className: "border-border-seam",
|
|
38
|
-
children: [/* @__PURE__ */
|
|
161
|
+
children: [/* @__PURE__ */ p(y, {
|
|
39
162
|
className: "flex flex-row items-center justify-between space-y-0 pb-2",
|
|
40
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ f(b, {
|
|
41
164
|
className: "text-sm font-medium",
|
|
42
165
|
children: "Total Hours"
|
|
43
|
-
}), /* @__PURE__ */
|
|
44
|
-
}), /* @__PURE__ */
|
|
166
|
+
}), /* @__PURE__ */ f(c, { className: "h-4 w-4 text-text-secondary" })]
|
|
167
|
+
}), /* @__PURE__ */ p(_, { children: [/* @__PURE__ */ f("div", {
|
|
45
168
|
className: "text-2xl font-bold tabular-nums",
|
|
46
|
-
children:
|
|
47
|
-
}), /* @__PURE__ */
|
|
169
|
+
children: P.toFixed(1)
|
|
170
|
+
}), /* @__PURE__ */ f("p", {
|
|
48
171
|
className: "text-xs text-text-secondary",
|
|
49
172
|
children: "Today"
|
|
50
173
|
})] })]
|
|
51
174
|
}),
|
|
52
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ p(x, {
|
|
53
176
|
treatment: "metric",
|
|
54
177
|
className: "border-border-seam",
|
|
55
|
-
children: [/* @__PURE__ */
|
|
178
|
+
children: [/* @__PURE__ */ p(y, {
|
|
56
179
|
className: "flex flex-row items-center justify-between space-y-0 pb-2",
|
|
57
|
-
children: [/* @__PURE__ */
|
|
180
|
+
children: [/* @__PURE__ */ f(b, {
|
|
58
181
|
className: "text-sm font-medium",
|
|
59
182
|
children: "Overtime"
|
|
60
|
-
}), /* @__PURE__ */
|
|
61
|
-
}), /* @__PURE__ */
|
|
183
|
+
}), /* @__PURE__ */ f(d, { className: "h-4 w-4 text-status-warning-text" })]
|
|
184
|
+
}), /* @__PURE__ */ p(_, { children: [/* @__PURE__ */ p("div", {
|
|
62
185
|
className: "text-2xl font-bold tabular-nums",
|
|
63
|
-
children: [
|
|
64
|
-
}), /* @__PURE__ */
|
|
186
|
+
children: [F.toFixed(1), "h"]
|
|
187
|
+
}), /* @__PURE__ */ f("p", {
|
|
65
188
|
className: "text-xs text-text-secondary",
|
|
66
189
|
children: "Extra hours logged"
|
|
67
190
|
})] })]
|
|
68
191
|
}),
|
|
69
|
-
/* @__PURE__ */
|
|
192
|
+
/* @__PURE__ */ p(x, {
|
|
70
193
|
treatment: "metric",
|
|
71
194
|
className: "border-border-seam",
|
|
72
|
-
children: [/* @__PURE__ */
|
|
195
|
+
children: [/* @__PURE__ */ p(y, {
|
|
73
196
|
className: "flex flex-row items-center justify-between space-y-0 pb-2",
|
|
74
|
-
children: [/* @__PURE__ */
|
|
197
|
+
children: [/* @__PURE__ */ f(b, {
|
|
75
198
|
className: "text-sm font-medium",
|
|
76
199
|
children: "Approved"
|
|
77
|
-
}), /* @__PURE__ */
|
|
78
|
-
}), /* @__PURE__ */
|
|
200
|
+
}), /* @__PURE__ */ f(s, { className: "h-4 w-4 text-status-success-text" })]
|
|
201
|
+
}), /* @__PURE__ */ p(_, { children: [/* @__PURE__ */ f("div", {
|
|
79
202
|
className: "text-2xl font-bold tabular-nums",
|
|
80
|
-
children:
|
|
81
|
-
}), /* @__PURE__ */
|
|
203
|
+
children: I
|
|
204
|
+
}), /* @__PURE__ */ f("p", {
|
|
82
205
|
className: "text-xs text-text-secondary",
|
|
83
206
|
children: "time entries"
|
|
84
207
|
})] })]
|
|
85
208
|
}),
|
|
86
|
-
/* @__PURE__ */
|
|
209
|
+
/* @__PURE__ */ p(x, {
|
|
87
210
|
treatment: "metric",
|
|
88
211
|
className: "border-border-seam",
|
|
89
|
-
children: [/* @__PURE__ */
|
|
212
|
+
children: [/* @__PURE__ */ p(y, {
|
|
90
213
|
className: "flex flex-row items-center justify-between space-y-0 pb-2",
|
|
91
|
-
children: [/* @__PURE__ */
|
|
214
|
+
children: [/* @__PURE__ */ f(b, {
|
|
92
215
|
className: "text-sm font-medium",
|
|
93
216
|
children: "Pending"
|
|
94
|
-
}), /* @__PURE__ */
|
|
95
|
-
}), /* @__PURE__ */
|
|
217
|
+
}), /* @__PURE__ */ f(c, { className: "h-4 w-4 text-status-warning-text" })]
|
|
218
|
+
}), /* @__PURE__ */ p(_, { children: [/* @__PURE__ */ f("div", {
|
|
96
219
|
className: "text-2xl font-bold tabular-nums",
|
|
97
|
-
children:
|
|
98
|
-
}), /* @__PURE__ */
|
|
220
|
+
children: L
|
|
221
|
+
}), /* @__PURE__ */ f("p", {
|
|
99
222
|
className: "text-xs text-text-secondary",
|
|
100
223
|
children: "awaiting approval"
|
|
101
224
|
})] })]
|
|
102
225
|
})
|
|
103
226
|
]
|
|
104
227
|
}),
|
|
105
|
-
/* @__PURE__ */
|
|
228
|
+
/* @__PURE__ */ p("div", {
|
|
106
229
|
className: "grid gap-4 md:grid-cols-2",
|
|
107
|
-
children: [/* @__PURE__ */
|
|
230
|
+
children: [/* @__PURE__ */ p(g, { children: [/* @__PURE__ */ p(y, { children: [/* @__PURE__ */ f(b, { children: "Hours by Project" }), /* @__PURE__ */ f(v, { children: "Time allocation across active projects" })] }), /* @__PURE__ */ f(_, {
|
|
108
231
|
className: "space-y-3",
|
|
109
|
-
children: Object.entries(
|
|
232
|
+
children: Object.entries(R).sort(([, e], [, t]) => t - e).map(([e, t]) => /* @__PURE__ */ p("div", {
|
|
110
233
|
className: "flex items-center gap-3",
|
|
111
234
|
children: [
|
|
112
|
-
/* @__PURE__ */
|
|
235
|
+
/* @__PURE__ */ f("span", {
|
|
113
236
|
className: "w-48 text-sm truncate",
|
|
114
237
|
children: e
|
|
115
238
|
}),
|
|
116
|
-
/* @__PURE__ */
|
|
239
|
+
/* @__PURE__ */ f("div", {
|
|
117
240
|
className: "flex-1 h-4 bg-bg-sunken rounded overflow-hidden",
|
|
118
|
-
children: /* @__PURE__ */
|
|
241
|
+
children: /* @__PURE__ */ f("div", {
|
|
119
242
|
className: "h-4 bg-action-primary-bg rounded",
|
|
120
|
-
style: { width: `${Math.round(t /
|
|
243
|
+
style: { width: `${Math.round(t / z * 100)}%` }
|
|
121
244
|
})
|
|
122
245
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
246
|
+
/* @__PURE__ */ p("span", {
|
|
124
247
|
className: "text-sm font-medium w-14 text-right",
|
|
125
248
|
children: [t.toFixed(1), "h"]
|
|
126
249
|
})
|
|
127
250
|
]
|
|
128
251
|
}, e))
|
|
129
|
-
})] }), /* @__PURE__ */
|
|
252
|
+
})] }), /* @__PURE__ */ p(g, { children: [/* @__PURE__ */ p(y, { children: [/* @__PURE__ */ f(b, { children: "Today's Activity" }), /* @__PURE__ */ f(v, { children: "Employee clock-in/out for today" })] }), /* @__PURE__ */ f(_, {
|
|
130
253
|
className: "space-y-3",
|
|
131
|
-
children:
|
|
254
|
+
children: E.map((e) => /* @__PURE__ */ p("div", {
|
|
132
255
|
className: "flex items-center justify-between rounded border p-3",
|
|
133
|
-
children: [/* @__PURE__ */
|
|
256
|
+
children: [/* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("p", {
|
|
134
257
|
className: "text-sm font-medium",
|
|
135
258
|
children: e.employeeName
|
|
136
|
-
}), /* @__PURE__ */
|
|
259
|
+
}), /* @__PURE__ */ f("p", {
|
|
137
260
|
className: "text-xs text-text-secondary",
|
|
138
261
|
children: e.project
|
|
139
|
-
})] }), /* @__PURE__ */
|
|
262
|
+
})] }), /* @__PURE__ */ p("div", {
|
|
140
263
|
className: "flex items-center gap-3",
|
|
141
264
|
children: [
|
|
142
|
-
/* @__PURE__ */
|
|
265
|
+
/* @__PURE__ */ p("div", {
|
|
143
266
|
className: "text-right text-xs",
|
|
144
|
-
children: [/* @__PURE__ */
|
|
267
|
+
children: [/* @__PURE__ */ p("div", {
|
|
145
268
|
className: "flex items-center gap-1",
|
|
146
|
-
children: [/* @__PURE__ */
|
|
147
|
-
}), /* @__PURE__ */
|
|
269
|
+
children: [/* @__PURE__ */ f(l, { className: "h-3 w-3 text-status-success-text" }), e.clockIn]
|
|
270
|
+
}), /* @__PURE__ */ p("div", {
|
|
148
271
|
className: "flex items-center gap-1",
|
|
149
|
-
children: [/* @__PURE__ */
|
|
272
|
+
children: [/* @__PURE__ */ f(u, { className: "h-3 w-3 text-status-error-text" }), e.clockOut]
|
|
150
273
|
})]
|
|
151
274
|
}),
|
|
152
|
-
/* @__PURE__ */
|
|
275
|
+
/* @__PURE__ */ p("div", {
|
|
153
276
|
className: "text-right",
|
|
154
|
-
children: [/* @__PURE__ */
|
|
277
|
+
children: [/* @__PURE__ */ p("p", {
|
|
155
278
|
className: "text-sm font-medium",
|
|
156
279
|
children: [e.totalHours, "h"]
|
|
157
|
-
}), e.overtime > 0 && /* @__PURE__ */
|
|
280
|
+
}), e.overtime > 0 && /* @__PURE__ */ p("p", {
|
|
158
281
|
className: "text-xs text-status-warning-text",
|
|
159
282
|
children: [
|
|
160
283
|
"+",
|
|
@@ -163,20 +286,47 @@ function v() {
|
|
|
163
286
|
]
|
|
164
287
|
})]
|
|
165
288
|
}),
|
|
166
|
-
/* @__PURE__ */
|
|
289
|
+
/* @__PURE__ */ f(m, {
|
|
167
290
|
variant: e.status === "approved" ? "default" : "secondary",
|
|
168
291
|
className: "text-[10px]",
|
|
169
292
|
children: e.status
|
|
293
|
+
}),
|
|
294
|
+
/* @__PURE__ */ f(h, {
|
|
295
|
+
variant: "ghost",
|
|
296
|
+
size: "sm",
|
|
297
|
+
className: "h-8 w-8 p-0",
|
|
298
|
+
"aria-label": `Clock out ${e.employeeName}`,
|
|
299
|
+
onClick: () => j.openModal(e),
|
|
300
|
+
children: /* @__PURE__ */ f(u, { className: "h-4 w-4" })
|
|
170
301
|
})
|
|
171
302
|
]
|
|
172
303
|
})]
|
|
173
304
|
}, e.id))
|
|
174
305
|
})] })]
|
|
306
|
+
}),
|
|
307
|
+
/* @__PURE__ */ f(t, {
|
|
308
|
+
open: A.open,
|
|
309
|
+
onOpenChange: A.onOpenChange,
|
|
310
|
+
title: "Add time entry",
|
|
311
|
+
description: "Log a work session for a team member.",
|
|
312
|
+
fields: N,
|
|
313
|
+
submitLabel: "Add entry",
|
|
314
|
+
onSubmit: B
|
|
315
|
+
}),
|
|
316
|
+
/* @__PURE__ */ f(t, {
|
|
317
|
+
open: j.open,
|
|
318
|
+
onOpenChange: j.onOpenChange,
|
|
319
|
+
title: "Clock out",
|
|
320
|
+
description: j.context ? `Record clock-out for ${j.context.employeeName}.` : void 0,
|
|
321
|
+
fields: T,
|
|
322
|
+
initialValues: H,
|
|
323
|
+
submitLabel: "Save",
|
|
324
|
+
onSubmit: V
|
|
175
325
|
})
|
|
176
326
|
]
|
|
177
327
|
});
|
|
178
328
|
}
|
|
179
329
|
//#endregion
|
|
180
|
-
export {
|
|
330
|
+
export { E as HrTimeTrackingPagePage };
|
|
181
331
|
|
|
182
332
|
//# sourceMappingURL=HrTimeTrackingPagePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HrTimeTrackingPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrTimeTrackingPagePage.tsx"],"sourcesContent":["import {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle, Clock, LogIn, LogOut, Timer } from 'lucide-react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\nexport function HrTimeTrackingPagePage() {\n const { timeEntries } = useCrewFoundryStore();\n\n const totalHours = timeEntries.reduce((s, t) => s + t.totalHours, 0);\n const totalOvertime = timeEntries.reduce((s, t) => s + t.overtime, 0);\n const approvedEntries = timeEntries.filter((t) => t.status === 'approved').length;\n const pendingEntries = timeEntries.filter((t) => t.status === 'pending').length;\n\n const projectBreakdown = timeEntries.reduce<Record<string, number>>((acc, t) => {\n acc[t.project] = (acc[t.project] || 0) + t.totalHours;\n return acc;\n }, {});\n const maxProjectHours = Math.max(...Object.values(projectBreakdown));\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>Time Tracking</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Monitor employee work hours and overtime</p>\n <PagePurpose className=\"mt-1\">Track hours and attendance across your team — use it to monitor logged time, overtime, and timesheet approvals.</PagePurpose>\n </div>\n <Button size=\"sm\">\n <Timer className=\"h-4 w-4 mr-1\" />\n Clock In\n </Button>\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\">Total Hours</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalHours.toFixed(1)}</div>\n <p className=\"text-xs text-text-secondary\">Today</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\">Overtime</CardTitle>\n <Timer className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalOvertime.toFixed(1)}h</div>\n <p className=\"text-xs text-text-secondary\">Extra hours logged</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\">Approved</CardTitle>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{approvedEntries}</div>\n <p className=\"text-xs text-text-secondary\">time entries</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\">Pending</CardTitle>\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{pendingEntries}</div>\n <p className=\"text-xs text-text-secondary\">awaiting approval</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Hours by Project</CardTitle>\n <CardDescription>Time allocation across active projects</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(projectBreakdown)\n .sort(([, a], [, b]) => b - a)\n .map(([project, hours]) => (\n <div key={project} className=\"flex items-center gap-3\">\n <span className=\"w-48 text-sm truncate\">{project}</span>\n <div className=\"flex-1 h-4 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-4 bg-action-primary-bg rounded\"\n style={{ width: `${Math.round((hours / maxProjectHours) * 100)}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-14 text-right\">{hours.toFixed(1)}h</span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Today's Activity</CardTitle>\n <CardDescription>Employee clock-in/out for today</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {timeEntries.map((entry) => (\n <div key={entry.id} className=\"flex items-center justify-between rounded border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{entry.employeeName}</p>\n <p className=\"text-xs text-text-secondary\">{entry.project}</p>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right text-xs\">\n <div className=\"flex items-center gap-1\">\n <LogIn className=\"h-3 w-3 text-status-success-text\" />\n {entry.clockIn}\n </div>\n <div className=\"flex items-center gap-1\">\n <LogOut className=\"h-3 w-3 text-status-error-text\" />\n {entry.clockOut}\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">{entry.totalHours}h</p>\n {entry.overtime > 0 && (\n <p className=\"text-xs text-status-warning-text\">+{entry.overtime}h OT</p>\n )}\n </div>\n <Badge\n variant={entry.status === 'approved' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {entry.status}\n </Badge>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"HrTimeTrackingPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/HrTimeTrackingPagePage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle, Clock, LogIn, LogOut, Timer } from 'lucide-react';\nimport { useCrewFoundryStore, type TimeEntry } from '../store/crewfoundryStore';\nimport { FormModal, useFormModal, type FieldConfig, type FormValues } from '../components/forms/FormModal';\nimport { getNumber, getText, newClientId } from '../components/forms/formValues';\n\nconst STATUS_OPTIONS = [\n { label: 'Pending', value: 'pending' },\n { label: 'Approved', value: 'approved' },\n { label: 'Rejected', value: 'rejected' },\n];\n\nconst CLOCK_OUT_FIELDS: FieldConfig[] = [\n { name: 'clockOut', label: 'Clock-out time', type: 'text', required: true, placeholder: 'e.g. 17:30' },\n { name: 'totalHours', label: 'Total hours', type: 'number', required: true },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n];\n\nexport function HrTimeTrackingPagePage() {\n const { timeEntries, employees, addTimeEntry, updateTimeEntry } = useCrewFoundryStore();\n const createForm = useFormModal<void>();\n const clockOutForm = useFormModal<TimeEntry>();\n\n const employeeOptions = useMemo(\n () =>\n employees.map((employee) => ({\n label: `${employee.firstName} ${employee.lastName}`,\n value: employee.id,\n })),\n [employees]\n );\n\n const entryFields = useMemo<FieldConfig[]>(\n () => [\n {\n name: 'employeeId',\n label: 'Employee',\n type: 'select',\n required: true,\n options: employeeOptions,\n colSpan: 2,\n },\n { name: 'date', label: 'Date', type: 'date', required: true },\n { name: 'project', label: 'Project', type: 'text', required: true },\n { name: 'clockIn', label: 'Clock-in time', type: 'text', required: true, placeholder: 'e.g. 09:00' },\n { name: 'clockOut', label: 'Clock-out time', type: 'text', placeholder: 'e.g. 17:30' },\n { name: 'totalHours', label: 'Total hours', type: 'number' },\n { name: 'overtime', label: 'Overtime hours', type: 'number' },\n { name: 'status', label: 'Status', type: 'select', options: STATUS_OPTIONS },\n ],\n [employeeOptions]\n );\n\n const totalHours = timeEntries.reduce((s, t) => s + t.totalHours, 0);\n const totalOvertime = timeEntries.reduce((s, t) => s + t.overtime, 0);\n const approvedEntries = timeEntries.filter((t) => t.status === 'approved').length;\n const pendingEntries = timeEntries.filter((t) => t.status === 'pending').length;\n\n const projectBreakdown = timeEntries.reduce<Record<string, number>>((acc, t) => {\n acc[t.project] = (acc[t.project] || 0) + t.totalHours;\n return acc;\n }, {});\n const maxProjectHours = Math.max(...Object.values(projectBreakdown), 1);\n\n const handleCreate = async (values: FormValues) => {\n const employeeId = getText(values, 'employeeId');\n const employee = employees.find((item) => item.id === employeeId);\n const payload: TimeEntry = {\n id: newClientId(),\n employeeId,\n employeeName: employee ? `${employee.firstName} ${employee.lastName}` : '',\n date: getText(values, 'date'),\n clockIn: getText(values, 'clockIn'),\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n project: getText(values, 'project'),\n status: (getText(values, 'status') as TimeEntry['status']) || 'pending',\n };\n await addTimeEntry(payload);\n };\n\n const handleClockOut = async (values: FormValues) => {\n const entry = clockOutForm.context;\n if (!entry) {\n return;\n }\n await updateTimeEntry(entry.id, {\n clockOut: getText(values, 'clockOut'),\n totalHours: getNumber(values, 'totalHours'),\n overtime: getNumber(values, 'overtime'),\n status: (getText(values, 'status') as TimeEntry['status']) || entry.status,\n });\n };\n\n const clockOutInitial: FormValues | undefined = clockOutForm.context\n ? {\n clockOut: clockOutForm.context.clockOut,\n totalHours: clockOutForm.context.totalHours,\n overtime: clockOutForm.context.overtime,\n status: clockOutForm.context.status,\n }\n : undefined;\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>Time Tracking</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Monitor employee work hours and overtime</p>\n <PagePurpose className=\"mt-1\">Track hours and attendance across your team — use it to monitor logged time, overtime, and timesheet approvals.</PagePurpose>\n </div>\n <Button size=\"sm\" onClick={() => createForm.openModal()}>\n <Timer className=\"h-4 w-4 mr-1\" />\n Add Entry\n </Button>\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\">Total Hours</CardTitle>\n <Clock className=\"h-4 w-4 text-text-secondary\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalHours.toFixed(1)}</div>\n <p className=\"text-xs text-text-secondary\">Today</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\">Overtime</CardTitle>\n <Timer className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{totalOvertime.toFixed(1)}h</div>\n <p className=\"text-xs text-text-secondary\">Extra hours logged</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\">Approved</CardTitle>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{approvedEntries}</div>\n <p className=\"text-xs text-text-secondary\">time entries</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\">Pending</CardTitle>\n <Clock className=\"h-4 w-4 text-status-warning-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">{pendingEntries}</div>\n <p className=\"text-xs text-text-secondary\">awaiting approval</p>\n </CardContent>\n </GlassCard>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-2\">\n <Card>\n <CardHeader>\n <CardTitle>Hours by Project</CardTitle>\n <CardDescription>Time allocation across active projects</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {Object.entries(projectBreakdown)\n .sort(([, a], [, b]) => b - a)\n .map(([project, hours]) => (\n <div key={project} className=\"flex items-center gap-3\">\n <span className=\"w-48 text-sm truncate\">{project}</span>\n <div className=\"flex-1 h-4 bg-bg-sunken rounded overflow-hidden\">\n <div\n className=\"h-4 bg-action-primary-bg rounded\"\n style={{ width: `${Math.round((hours / maxProjectHours) * 100)}%` }}\n />\n </div>\n <span className=\"text-sm font-medium w-14 text-right\">{hours.toFixed(1)}h</span>\n </div>\n ))}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Today's Activity</CardTitle>\n <CardDescription>Employee clock-in/out for today</CardDescription>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {timeEntries.map((entry) => (\n <div key={entry.id} className=\"flex items-center justify-between rounded border p-3\">\n <div>\n <p className=\"text-sm font-medium\">{entry.employeeName}</p>\n <p className=\"text-xs text-text-secondary\">{entry.project}</p>\n </div>\n <div className=\"flex items-center gap-3\">\n <div className=\"text-right text-xs\">\n <div className=\"flex items-center gap-1\">\n <LogIn className=\"h-3 w-3 text-status-success-text\" />\n {entry.clockIn}\n </div>\n <div className=\"flex items-center gap-1\">\n <LogOut className=\"h-3 w-3 text-status-error-text\" />\n {entry.clockOut}\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-sm font-medium\">{entry.totalHours}h</p>\n {entry.overtime > 0 && (\n <p className=\"text-xs text-status-warning-text\">+{entry.overtime}h OT</p>\n )}\n </div>\n <Badge\n variant={entry.status === 'approved' ? 'default' : 'secondary'}\n className=\"text-[10px]\"\n >\n {entry.status}\n </Badge>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 w-8 p-0\"\n aria-label={`Clock out ${entry.employeeName}`}\n onClick={() => clockOutForm.openModal(entry)}\n >\n <LogOut className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n\n <FormModal\n open={createForm.open}\n onOpenChange={createForm.onOpenChange}\n title=\"Add time entry\"\n description=\"Log a work session for a team member.\"\n fields={entryFields}\n submitLabel=\"Add entry\"\n onSubmit={handleCreate}\n />\n\n <FormModal\n open={clockOutForm.open}\n onOpenChange={clockOutForm.onOpenChange}\n title=\"Clock out\"\n description={\n clockOutForm.context\n ? `Record clock-out for ${clockOutForm.context.employeeName}.`\n : undefined\n }\n fields={CLOCK_OUT_FIELDS}\n initialValues={clockOutInitial}\n submitLabel=\"Save\"\n onSubmit={handleClockOut}\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;AAkBA,IAAM,IAAiB;CACrB;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAY,OAAO;EAAY;CACxC;EAAE,OAAO;EAAY,OAAO;EAAY;CACzC,EAEK,IAAkC;CACtC;EAAE,MAAM;EAAY,OAAO;EAAkB,MAAM;EAAQ,UAAU;EAAM,aAAa;EAAc;CACtG;EAAE,MAAM;EAAc,OAAO;EAAe,MAAM;EAAU,UAAU;EAAM;CAC5E;EAAE,MAAM;EAAY,OAAO;EAAkB,MAAM;EAAU;CAC7D;EAAE,MAAM;EAAU,OAAO;EAAU,MAAM;EAAU,SAAS;EAAgB;CAC7E;AAED,SAAgB,IAAyB;CACvC,IAAM,EAAE,gBAAa,cAAW,iBAAc,uBAAoB,GAAqB,EACjF,IAAa,GAAoB,EACjC,IAAe,GAAyB,EAExC,IAAkB,QAEpB,EAAU,KAAK,OAAc;EAC3B,OAAO,GAAG,EAAS,UAAU,GAAG,EAAS;EACzC,OAAO,EAAS;EACjB,EAAE,EACL,CAAC,EAAU,CACZ,EAEK,IAAc,QACZ;EACJ;GACE,MAAM;GACN,OAAO;GACP,MAAM;GACN,UAAU;GACV,SAAS;GACT,SAAS;GACV;EACD;GAAE,MAAM;GAAQ,OAAO;GAAQ,MAAM;GAAQ,UAAU;GAAM;EAC7D;GAAE,MAAM;GAAW,OAAO;GAAW,MAAM;GAAQ,UAAU;GAAM;EACnE;GAAE,MAAM;GAAW,OAAO;GAAiB,MAAM;GAAQ,UAAU;GAAM,aAAa;GAAc;EACpG;GAAE,MAAM;GAAY,OAAO;GAAkB,MAAM;GAAQ,aAAa;GAAc;EACtF;GAAE,MAAM;GAAc,OAAO;GAAe,MAAM;GAAU;EAC5D;GAAE,MAAM;GAAY,OAAO;GAAkB,MAAM;GAAU;EAC7D;GAAE,MAAM;GAAU,OAAO;GAAU,MAAM;GAAU,SAAS;GAAgB;EAC7E,EACD,CAAC,EAAgB,CAClB,EAEK,IAAa,EAAY,QAAQ,GAAG,MAAM,IAAI,EAAE,YAAY,EAAE,EAC9D,IAAgB,EAAY,QAAQ,GAAG,MAAM,IAAI,EAAE,UAAU,EAAE,EAC/D,IAAkB,EAAY,QAAQ,MAAM,EAAE,WAAW,WAAW,CAAC,QACrE,IAAiB,EAAY,QAAQ,MAAM,EAAE,WAAW,UAAU,CAAC,QAEnE,IAAmB,EAAY,QAAgC,GAAK,OACxE,EAAI,EAAE,YAAY,EAAI,EAAE,YAAY,KAAK,EAAE,YACpC,IACN,EAAE,CAAC,EACA,IAAkB,KAAK,IAAI,GAAG,OAAO,OAAO,EAAiB,EAAE,EAAE,EAEjE,IAAe,OAAO,MAAuB;EACjD,IAAM,IAAa,EAAQ,GAAQ,aAAa,EAC1C,IAAW,EAAU,MAAM,MAAS,EAAK,OAAO,EAAW;AAajE,QAAM,EAZqB;GACzB,IAAI,GAAa;GACjB;GACA,cAAc,IAAW,GAAG,EAAS,UAAU,GAAG,EAAS,aAAa;GACxE,MAAM,EAAQ,GAAQ,OAAO;GAC7B,SAAS,EAAQ,GAAQ,UAAU;GACnC,UAAU,EAAQ,GAAQ,WAAW;GACrC,YAAY,EAAU,GAAQ,aAAa;GAC3C,UAAU,EAAU,GAAQ,WAAW;GACvC,SAAS,EAAQ,GAAQ,UAAU;GACnC,QAAS,EAAQ,GAAQ,SAAS,IAA4B;GAC/D,CAC0B;IAGvB,IAAiB,OAAO,MAAuB;EACnD,IAAM,IAAQ,EAAa;AACtB,OAGL,MAAM,EAAgB,EAAM,IAAI;GAC9B,UAAU,EAAQ,GAAQ,WAAW;GACrC,YAAY,EAAU,GAAQ,aAAa;GAC3C,UAAU,EAAU,GAAQ,WAAW;GACvC,QAAS,EAAQ,GAAQ,SAAS,IAA4B,EAAM;GACrE,CAAC;IAGE,IAA0C,EAAa,UACzD;EACE,UAAU,EAAa,QAAQ;EAC/B,YAAY,EAAa,QAAQ;EACjC,UAAU,EAAa,QAAQ;EAC/B,QAAQ,EAAa,QAAQ;EAC9B,GACD,KAAA;AAEJ,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD,EAAA,UAAA;KACE,kBAAC,MAAD;MAAI,WAAU;gBACZ,kBAAC,GAAD,EAAA,UAAc,iBAA4B,CAAA;MACvC,CAAA;KACL,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAA4C,CAAA;KACvF,kBAAC,GAAD;MAAa,WAAU;gBAAO;MAA6H,CAAA;KACvJ,EAAA,CAAA,EACN,kBAAC,GAAD;KAAQ,MAAK;KAAK,eAAe,EAAW,WAAW;eAAvD,CACE,kBAAC,GAAD,EAAO,WAAU,gBAAiB,CAAA,EAAA,YAE3B;OACL;;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;QAAuB,CAAA,EAClE,kBAAC,GAAD,EAAO,WAAU,+BAAgC,CAAA,CACtC;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC,EAAW,QAAQ,EAAE;OAAO,CAAA,EAC9E,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAS,CAAA,CACxC,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAoB,CAAA,EAC/D,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CAAkD,EAAc,QAAQ,EAAE,EAAC,IAAO;UAClF,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAsB,CAAA,CACrD,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAoB,CAAA,EAC/D,kBAAC,GAAD,EAAa,WAAU,oCAAqC,CAAA,CACjD;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC;OAAsB,CAAA,EACxE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAgB,CAAA,CAC/C,EAAA,CAAA,CACJ;;KACZ,kBAAC,GAAD;MAAW,WAAU;MAAS,WAAU;gBAAxC,CACE,kBAAC,GAAD;OAAY,WAAU;iBAAtB,CACE,kBAAC,GAAD;QAAW,WAAU;kBAAsB;QAAmB,CAAA,EAC9D,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,CAC3C;UACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAmC;OAAqB,CAAA,EACvE,kBAAC,KAAD;OAAG,WAAU;iBAA8B;OAAqB,CAAA,CACpD,EAAA,CAAA,CACJ;;KACR;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,oBAA4B,CAAA,EACvC,kBAAC,GAAD,EAAA,UAAiB,0CAAwD,CAAA,CAC9D,EAAA,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eACpB,OAAO,QAAQ,EAAiB,CAC9B,MAAM,GAAG,IAAI,GAAG,OAAO,IAAI,EAAE,CAC7B,KAAK,CAAC,GAAS,OACd,kBAAC,OAAD;MAAmB,WAAU;gBAA7B;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAyB;QAAe,CAAA;OACxD,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,OAAD;SACE,WAAU;SACV,OAAO,EAAE,OAAO,GAAG,KAAK,MAAO,IAAQ,IAAmB,IAAI,CAAC,IAAI;SACnE,CAAA;QACE,CAAA;OACN,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CAAuD,EAAM,QAAQ,EAAE,EAAC,IAAQ;;OAC5E;QATI,EASJ,CACN;KACQ,CAAA,CACT,EAAA,CAAA,EAEP,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAW,oBAAiC,CAAA,EAC5C,kBAAC,GAAD,EAAA,UAAiB,mCAAiD,CAAA,CACvD,EAAA,CAAA,EACb,kBAAC,GAAD;KAAa,WAAU;eACpB,EAAY,KAAK,MAChB,kBAAC,OAAD;MAAoB,WAAU;gBAA9B,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAuB,EAAM;OAAiB,CAAA,EAC3D,kBAAC,KAAD;OAAG,WAAU;iBAA+B,EAAM;OAAY,CAAA,CAC1D,EAAA,CAAA,EACN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAO,WAAU,oCAAqC,CAAA,EACrD,EAAM,QACH;aACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,kCAAmC,CAAA,EACpD,EAAM,SACH;YACF;;QACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAb,CAAoC,EAAM,YAAW,IAAK;aACzD,EAAM,WAAW,KAChB,kBAAC,KAAD;UAAG,WAAU;oBAAb;WAAgD;WAAE,EAAM;WAAS;WAAQ;YAEvE;;QACN,kBAAC,GAAD;SACE,SAAS,EAAM,WAAW,aAAa,YAAY;SACnD,WAAU;mBAET,EAAM;SACD,CAAA;QACR,kBAAC,GAAD;SACE,SAAQ;SACR,MAAK;SACL,WAAU;SACV,cAAY,aAAa,EAAM;SAC/B,eAAe,EAAa,UAAU,EAAM;mBAE5C,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;SACvB,CAAA;QACL;SACF;QAtCI,EAAM,GAsCV,CACN;KACU,CAAA,CACT,EAAA,CAAA,CACH;;GAEN,kBAAC,GAAD;IACE,MAAM,EAAW;IACjB,cAAc,EAAW;IACzB,OAAM;IACN,aAAY;IACZ,QAAQ;IACR,aAAY;IACZ,UAAU;IACV,CAAA;GAEF,kBAAC,GAAD;IACE,MAAM,EAAa;IACnB,cAAc,EAAa;IAC3B,OAAM;IACN,aACE,EAAa,UACT,wBAAwB,EAAa,QAAQ,aAAa,KAC1D,KAAA;IAEN,QAAQ;IACR,eAAe;IACf,aAAY;IACZ,UAAU;IACV,CAAA;GACE"}
|
|
@@ -134,14 +134,13 @@ function y() {
|
|
|
134
134
|
className: "text-[10px]",
|
|
135
135
|
children: e.status
|
|
136
136
|
}),
|
|
137
|
-
e.status === "available" ? /* @__PURE__ */ s(l, {
|
|
137
|
+
e.status === "available" || e.status === "overdue" ? /* @__PURE__ */ s(l, {
|
|
138
138
|
size: "sm",
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
children: [/* @__PURE__ */ o(a, { className: "mr-1 h-3 w-3" }), "Take Now"]
|
|
139
|
+
variant: "outline",
|
|
140
|
+
disabled: !0,
|
|
141
|
+
title: "Assessment taking is coming soon",
|
|
142
|
+
"aria-label": `Take ${e.title} — coming soon`,
|
|
143
|
+
children: [/* @__PURE__ */ o(a, { className: "mr-1 h-3 w-3" }), "Coming soon"]
|
|
145
144
|
}) : null
|
|
146
145
|
]
|
|
147
146
|
})]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LearningAssessmentsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningAssessmentsPagePage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle, Clock, FileText, Play } from 'lucide-react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\ntype Assessment = {\n id: string;\n title: string;\n course: string;\n questions: number;\n duration: string;\n status: 'available' | 'completed' | 'overdue';\n score?: number;\n dueDate: string;\n passingScore: number;\n};\n\nconst BASE_TS = new Date('2026-03-01T00:00:00Z').getTime();\n\nconst formatDate = (value: string): string => {\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return 'Unknown';\n }\n return parsed.toISOString().split('T')[0];\n};\n\nexport function LearningAssessmentsPagePage() {\n const { courses } = useCrewFoundryStore();\n\n const assessments = useMemo<Assessment[]>(() => {\n return courses.slice(0, 16).map((course, index) => {\n const status: Assessment['status'] =\n index % 6 === 0 ? 'overdue' : index % 2 === 0 ? 'available' : 'completed';\n const passingScore = 70 + (index % 4) * 5;\n\n return {\n id: course.id,\n title: `${course.title} Assessment`,\n course: course.title,\n questions: Math.max(10, course.modules * 4),\n duration: `${Math.max(15, course.modules * 8)} min`,\n status,\n score:\n status === 'completed' ? Math.min(99, Math.round(course.rating * 20 + 20)) : undefined,\n dueDate: formatDate(new Date(BASE_TS + (index - 3) * 86400000 * 6).toISOString()),\n passingScore,\n };\n });\n }, [courses]);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Assessments</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Quizzes and exams for your enrolled courses</p>\n <PagePurpose className=\"mt-1\">Create and review assessments that measure what your team has learned — use them to certify skills and identify knowledge gaps.</PagePurpose>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\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\">Available</CardTitle>\n <FileText className=\"h-4 w-4 text-status-info-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {assessments.filter((assessment) => assessment.status === 'available').length}\n </div>\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\">Completed</CardTitle>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {assessments.filter((assessment) => assessment.status === 'completed').length}\n </div>\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\">Overdue</CardTitle>\n <Clock className=\"h-4 w-4 text-status-error-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {assessments.filter((assessment) => assessment.status === 'overdue').length}\n </div>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>All Assessments</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {assessments.map((assessment) => (\n <div\n key={assessment.id}\n className=\"flex items-center justify-between rounded border p-4\"\n >\n <div>\n <p className=\"text-sm font-medium\">{assessment.title}</p>\n <p className=\"text-xs text-text-secondary\">\n {assessment.course} · {assessment.questions} questions · {assessment.duration}\n </p>\n <p className=\"text-xs text-text-secondary\">\n Due: {assessment.dueDate} · Passing: {assessment.passingScore}%\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {assessment.status === 'completed' && assessment.score ? (\n <Badge\n variant={\n assessment.score >= assessment.passingScore ? 'default' : 'destructive'\n }\n className=\"text-[10px]\"\n >\n Score: {assessment.score}%\n </Badge>\n ) : null}\n <Badge\n variant={\n assessment.status === 'completed'\n ? 'default'\n : assessment.status === 'overdue'\n ? 'destructive'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {assessment.status}\n </Badge>\n {assessment.status === 'available' ? (\n <Button
|
|
1
|
+
{"version":3,"file":"LearningAssessmentsPagePage.js","names":[],"sources":["../../../src/crewfoundry/pages/LearningAssessmentsPagePage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n GlassCard,\n GradientText,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { CheckCircle, Clock, FileText, Play } from 'lucide-react';\nimport { useCrewFoundryStore } from '../store/crewfoundryStore';\n\ntype Assessment = {\n id: string;\n title: string;\n course: string;\n questions: number;\n duration: string;\n status: 'available' | 'completed' | 'overdue';\n score?: number;\n dueDate: string;\n passingScore: number;\n};\n\nconst BASE_TS = new Date('2026-03-01T00:00:00Z').getTime();\n\nconst formatDate = (value: string): string => {\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime())) {\n return 'Unknown';\n }\n return parsed.toISOString().split('T')[0];\n};\n\nexport function LearningAssessmentsPagePage() {\n const { courses } = useCrewFoundryStore();\n\n const assessments = useMemo<Assessment[]>(() => {\n return courses.slice(0, 16).map((course, index) => {\n const status: Assessment['status'] =\n index % 6 === 0 ? 'overdue' : index % 2 === 0 ? 'available' : 'completed';\n const passingScore = 70 + (index % 4) * 5;\n\n return {\n id: course.id,\n title: `${course.title} Assessment`,\n course: course.title,\n questions: Math.max(10, course.modules * 4),\n duration: `${Math.max(15, course.modules * 8)} min`,\n status,\n score:\n status === 'completed' ? Math.min(99, Math.round(course.rating * 20 + 20)) : undefined,\n dueDate: formatDate(new Date(BASE_TS + (index - 3) * 86400000 * 6).toISOString()),\n passingScore,\n };\n });\n }, [courses]);\n\n return (\n <div className=\"space-y-6 p-6\">\n <div>\n <h1 className=\"text-2xl font-semibold\">\n <GradientText>Assessments</GradientText>\n </h1>\n <p className=\"text-sm text-text-secondary\">Quizzes and exams for your enrolled courses</p>\n <PagePurpose className=\"mt-1\">Create and review assessments that measure what your team has learned — use them to certify skills and identify knowledge gaps.</PagePurpose>\n </div>\n\n <div className=\"grid gap-4 md:grid-cols-3\">\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\">Available</CardTitle>\n <FileText className=\"h-4 w-4 text-status-info-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {assessments.filter((assessment) => assessment.status === 'available').length}\n </div>\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\">Completed</CardTitle>\n <CheckCircle className=\"h-4 w-4 text-status-success-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {assessments.filter((assessment) => assessment.status === 'completed').length}\n </div>\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\">Overdue</CardTitle>\n <Clock className=\"h-4 w-4 text-status-error-text\" />\n </CardHeader>\n <CardContent>\n <div className=\"text-2xl font-bold tabular-nums\">\n {assessments.filter((assessment) => assessment.status === 'overdue').length}\n </div>\n </CardContent>\n </GlassCard>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>All Assessments</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n {assessments.map((assessment) => (\n <div\n key={assessment.id}\n className=\"flex items-center justify-between rounded border p-4\"\n >\n <div>\n <p className=\"text-sm font-medium\">{assessment.title}</p>\n <p className=\"text-xs text-text-secondary\">\n {assessment.course} · {assessment.questions} questions · {assessment.duration}\n </p>\n <p className=\"text-xs text-text-secondary\">\n Due: {assessment.dueDate} · Passing: {assessment.passingScore}%\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {assessment.status === 'completed' && assessment.score ? (\n <Badge\n variant={\n assessment.score >= assessment.passingScore ? 'default' : 'destructive'\n }\n className=\"text-[10px]\"\n >\n Score: {assessment.score}%\n </Badge>\n ) : null}\n <Badge\n variant={\n assessment.status === 'completed'\n ? 'default'\n : assessment.status === 'overdue'\n ? 'destructive'\n : 'secondary'\n }\n className=\"text-[10px]\"\n >\n {assessment.status}\n </Badge>\n {assessment.status === 'available' || assessment.status === 'overdue' ? (\n <Button\n size=\"sm\"\n variant=\"outline\"\n disabled\n title=\"Assessment taking is coming soon\"\n aria-label={`Take ${assessment.title} — coming soon`}\n >\n <Play className=\"mr-1 h-3 w-3\" />\n Coming soon\n </Button>\n ) : null}\n </div>\n </div>\n ))}\n </CardContent>\n </Card>\n </div>\n );\n}\n"],"mappings":"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"}
|