@open-mercato/core 0.8.1-develop.7274.1.21a0083828 → 0.8.1-develop.7294.1.0ef99fc518

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  3. package/dist/modules/auth/components/AclEditor.js +21 -3
  4. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  5. package/dist/modules/customers/backend/customers/companies/page.js +6 -4
  6. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  7. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
  8. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
  9. package/dist/modules/customers/backend/customers/deals/page.js +6 -4
  10. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  11. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
  12. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
  13. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
  14. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  15. package/dist/modules/customers/backend/customers/people/page.js +6 -4
  16. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  17. package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
  18. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  19. package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
  20. package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
  21. package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
  22. package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
  23. package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
  24. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  25. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
  26. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  27. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
  28. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  29. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
  30. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
  31. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
  32. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  33. package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
  34. package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
  35. package/dist/modules/customers/components/detail/schedule/index.js +3 -1
  36. package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
  37. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
  38. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  39. package/dist/modules/customers/components/formConfig.js +17 -9
  40. package/dist/modules/customers/components/formConfig.js.map +2 -2
  41. package/dist/modules/customers/data/validators.js +12 -7
  42. package/dist/modules/customers/data/validators.js.map +2 -2
  43. package/dist/modules/customers/lib/assignableStaff.js +103 -22
  44. package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
  45. package/dist/modules/customers/setup.js +10 -2
  46. package/dist/modules/customers/setup.js.map +2 -2
  47. package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
  48. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  49. package/dist/modules/data_sync/api/schedules/route.js +5 -0
  50. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  51. package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
  52. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  53. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
  54. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  55. package/dist/modules/data_sync/data/validators.js +13 -2
  56. package/dist/modules/data_sync/data/validators.js.map +2 -2
  57. package/dist/modules/data_sync/lib/schedule-value.js +30 -0
  58. package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
  59. package/dist/modules/eudr/data/validators.js.map +2 -2
  60. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
  61. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  62. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
  63. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
  64. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
  65. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  66. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
  67. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
  68. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
  69. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
  70. package/dist/modules/portal/lib/navigation.js +3 -4
  71. package/dist/modules/portal/lib/navigation.js.map +2 -2
  72. package/dist/modules/query_index/lib/engine.js +79 -14
  73. package/dist/modules/query_index/lib/engine.js.map +2 -2
  74. package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
  75. package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
  76. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
  77. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  78. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
  79. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
  80. package/dist/modules/staff/extension-points.js +16 -0
  81. package/dist/modules/staff/extension-points.js.map +2 -2
  82. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
  83. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
  84. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
  85. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
  86. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
  87. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
  88. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
  89. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
  90. package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
  91. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
  92. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
  93. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
  94. package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
  95. package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
  96. package/dist/modules/warranty_claims/api/assign/route.js +3 -3
  97. package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
  98. package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
  99. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
  100. package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
  101. package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
  102. package/dist/modules/warranty_claims/api/events/route.js +4 -4
  103. package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
  104. package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
  105. package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
  106. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
  107. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
  108. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +2 -2
  109. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
  110. package/dist/modules/warranty_claims/api/portal/claims/route.js +2 -2
  111. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  112. package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
  113. package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
  114. package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
  115. package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
  116. package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
  117. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
  118. package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
  119. package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
  120. package/dist/modules/warranty_claims/api/risk/route.js +2 -2
  121. package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
  122. package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
  123. package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
  124. package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
  125. package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
  126. package/dist/modules/warranty_claims/api/stats/route.js +2 -2
  127. package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
  128. package/dist/modules/warranty_claims/api/submit/route.js +3 -3
  129. package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
  130. package/dist/modules/warranty_claims/api/transition/route.js +3 -3
  131. package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
  132. package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
  133. package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
  134. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -2
  135. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
  136. package/dist/modules/warranty_claims/backend/page.js +4 -2
  137. package/dist/modules/warranty_claims/backend/page.js.map +2 -2
  138. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
  139. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
  140. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
  141. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
  142. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
  143. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
  144. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
  145. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
  146. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
  147. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
  148. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
  149. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  150. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
  151. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
  152. package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
  153. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  154. package/package.json +7 -7
  155. package/src/modules/auth/api/users/acl/route.ts +4 -3
  156. package/src/modules/auth/components/AclEditor.tsx +38 -2
  157. package/src/modules/auth/i18n/de.json +5 -1
  158. package/src/modules/auth/i18n/en.json +5 -1
  159. package/src/modules/auth/i18n/es.json +5 -1
  160. package/src/modules/auth/i18n/ko.json +5 -1
  161. package/src/modules/auth/i18n/pl.json +5 -1
  162. package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
  163. package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
  164. package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
  165. package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
  166. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
  167. package/src/modules/customers/backend/customers/people/page.tsx +6 -4
  168. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
  169. package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
  170. package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
  171. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
  172. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
  173. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
  174. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
  175. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
  176. package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
  177. package/src/modules/customers/components/detail/schedule/index.ts +1 -1
  178. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
  179. package/src/modules/customers/components/formConfig.tsx +35 -12
  180. package/src/modules/customers/data/validators.ts +14 -8
  181. package/src/modules/customers/i18n/de.json +1 -0
  182. package/src/modules/customers/i18n/en.json +1 -0
  183. package/src/modules/customers/i18n/es.json +1 -0
  184. package/src/modules/customers/i18n/ko.json +1 -0
  185. package/src/modules/customers/i18n/pl.json +1 -0
  186. package/src/modules/customers/lib/assignableStaff.ts +172 -34
  187. package/src/modules/customers/setup.ts +8 -0
  188. package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
  189. package/src/modules/data_sync/api/schedules/route.ts +5 -0
  190. package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
  191. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
  192. package/src/modules/data_sync/data/validators.ts +26 -2
  193. package/src/modules/data_sync/i18n/de.json +2 -0
  194. package/src/modules/data_sync/i18n/en.json +2 -0
  195. package/src/modules/data_sync/i18n/es.json +2 -0
  196. package/src/modules/data_sync/i18n/ko.json +2 -0
  197. package/src/modules/data_sync/i18n/pl.json +2 -0
  198. package/src/modules/data_sync/lib/schedule-value.ts +45 -0
  199. package/src/modules/eudr/data/validators.ts +3 -4
  200. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
  201. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
  202. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
  203. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
  204. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
  205. package/src/modules/portal/i18n/de.json +1 -0
  206. package/src/modules/portal/i18n/en.json +1 -0
  207. package/src/modules/portal/i18n/es.json +1 -0
  208. package/src/modules/portal/i18n/ko.json +1 -0
  209. package/src/modules/portal/i18n/pl.json +1 -0
  210. package/src/modules/portal/lib/navigation.ts +8 -3
  211. package/src/modules/query_index/lib/engine.ts +111 -16
  212. package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
  213. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
  214. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
  215. package/src/modules/staff/extension-points.ts +16 -0
  216. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
  217. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
  218. package/src/modules/staff/i18n/de.json +3 -0
  219. package/src/modules/staff/i18n/en.json +3 -0
  220. package/src/modules/staff/i18n/es.json +3 -0
  221. package/src/modules/staff/i18n/ko.json +3 -0
  222. package/src/modules/staff/i18n/pl.json +3 -0
  223. package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
  224. package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
  225. package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
  226. package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
  227. package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
  228. package/src/modules/warranty_claims/api/assign/route.ts +3 -3
  229. package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
  230. package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
  231. package/src/modules/warranty_claims/api/events/route.ts +4 -4
  232. package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
  233. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
  234. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
  235. package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
  236. package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
  237. package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
  238. package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
  239. package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
  240. package/src/modules/warranty_claims/api/risk/route.ts +2 -2
  241. package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
  242. package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
  243. package/src/modules/warranty_claims/api/stats/route.ts +2 -2
  244. package/src/modules/warranty_claims/api/submit/route.ts +3 -3
  245. package/src/modules/warranty_claims/api/transition/route.ts +3 -3
  246. package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
  247. package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
  248. package/src/modules/warranty_claims/backend/page.tsx +4 -2
  249. package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
  250. package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
  251. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
  252. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
  253. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
  254. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
  255. package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
  256. package/src/modules/workflows/i18n/de.json +1 -1
  257. package/src/modules/workflows/i18n/en.json +1 -1
  258. package/src/modules/workflows/i18n/es.json +1 -1
  259. package/src/modules/workflows/i18n/ko.json +1 -1
  260. package/src/modules/workflows/i18n/pl.json +1 -1
  261. package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
@@ -7,6 +7,7 @@ import { Page, PageBody, PageHeader } from "@open-mercato/ui/backend/Page";
7
7
  import { Button } from "@open-mercato/ui/primitives/button";
8
8
  import { Progress } from "@open-mercato/ui/primitives/progress";
9
9
  import { StatusBadge } from "@open-mercato/ui/primitives/status-badge";
10
+ import { DataTable } from "@open-mercato/ui/backend/DataTable";
10
11
  import {
11
12
  Select,
12
13
  SelectContent,
@@ -210,6 +211,60 @@ function TimeTrackingMyWorkPage() {
210
211
  setQuickTargetKey(quickTargets[0].key);
211
212
  }, [quickTargetKey, quickTargets]);
212
213
  const todayTotal = data.entries.reduce((sum, entry) => sum + entry.durationMinutes, 0);
214
+ const todayEntriesColumns = React.useMemo(
215
+ () => [
216
+ {
217
+ id: "task",
218
+ header: t("staff.time_tracking.myWork.today.columns.task", "Task"),
219
+ cell: ({ row }) => {
220
+ const entry = row.original;
221
+ return /* @__PURE__ */ jsxs("div", { children: [
222
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
223
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: entry.taskTitle ?? entry.description ?? t("staff.time_tracking.entries.table.noTask", "No task") }),
224
+ entry.isBillable ? null : /* @__PURE__ */ jsx(StatusBadge, { variant: "neutral", children: t("staff.time_tracking.entries.badges.nonBillable", "non-billable") }),
225
+ entry.isLocked ? /* @__PURE__ */ jsxs(StatusBadge, { variant: "neutral", children: [
226
+ /* @__PURE__ */ jsx(Lock, { className: "size-3", "aria-hidden": "true" }),
227
+ t("staff.time_tracking.entries.badges.locked", "locked")
228
+ ] }) : null
229
+ ] }),
230
+ /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: [entry.projectName, entry.taskTitle ? entry.description : null].filter((part) => Boolean(part)).join(" \xB7 ") })
231
+ ] });
232
+ }
233
+ },
234
+ {
235
+ id: "clock",
236
+ header: t("staff.time_tracking.myWork.today.columns.time", "Time"),
237
+ meta: { maxWidth: "8rem" },
238
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatClockRange(row.original) ?? "\u2014" })
239
+ },
240
+ {
241
+ id: "duration",
242
+ header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: t("staff.time_tracking.myWork.today.columns.duration", "Duration") }),
243
+ meta: { maxWidth: "6rem" },
244
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block text-right font-mono font-semibold tabular-nums", children: formatDuration(row.original.durationMinutes, "clock") })
245
+ },
246
+ {
247
+ id: "actions",
248
+ header: "",
249
+ meta: { maxWidth: "3rem" },
250
+ cell: ({ row }) => {
251
+ const entry = row.original;
252
+ return canManageOwn && !entry.isLocked ? /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx(
253
+ Button,
254
+ {
255
+ type: "button",
256
+ variant: "ghost",
257
+ size: "2xs",
258
+ "aria-label": t("staff.time_tracking.timesheet.list.edit", "Edit"),
259
+ onClick: () => setDialog({ open: true, entryId: entry.id }),
260
+ children: /* @__PURE__ */ jsx(Pencil, { className: "size-3.5", "aria-hidden": "true" })
261
+ }
262
+ ) }) : null;
263
+ }
264
+ }
265
+ ],
266
+ [canManageOwn, t]
267
+ );
213
268
  const submitQuickEntry = React.useCallback(async () => {
214
269
  if (!quickTargetKey || quickMinutes === null || quickMinutes <= 0 || quickSaving) return;
215
270
  const target = parseTargetKey(quickTargetKey);
@@ -402,97 +457,78 @@ function TimeTrackingMyWorkPage() {
402
457
  /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", asChild: true, children: /* @__PURE__ */ jsx(Link, { href: ENTRIES_HREF, children: t("staff.time_tracking.myWork.today.allEntries", "All entries") }) })
403
458
  ] })
404
459
  ] }),
405
- data.entries.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: t("staff.time_tracking.myWork.today.empty", "Nothing logged today yet.") }) : /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
406
- /* @__PURE__ */ jsxs("tbody", { children: [
407
- data.entries.map((entry) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-0", children: [
408
- /* @__PURE__ */ jsxs("td", { className: "p-3 align-top", children: [
409
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
410
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: entry.taskTitle ?? entry.description ?? t("staff.time_tracking.entries.table.noTask", "No task") }),
411
- entry.isBillable ? null : /* @__PURE__ */ jsx(StatusBadge, { variant: "neutral", children: t("staff.time_tracking.entries.badges.nonBillable", "non-billable") }),
412
- entry.isLocked ? /* @__PURE__ */ jsxs(StatusBadge, { variant: "neutral", children: [
413
- /* @__PURE__ */ jsx(Lock, { className: "size-3", "aria-hidden": "true" }),
414
- t("staff.time_tracking.entries.badges.locked", "locked")
415
- ] }) : null
416
- ] }),
417
- /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: [entry.projectName, entry.taskTitle ? entry.description : null].filter((part) => Boolean(part)).join(" \xB7 ") })
418
- ] }),
419
- /* @__PURE__ */ jsx("td", { className: "w-32 p-3 align-top text-xs text-muted-foreground", children: formatClockRange(entry) ?? "\u2014" }),
420
- /* @__PURE__ */ jsx("td", { className: "w-20 p-3 text-right align-top font-mono font-semibold tabular-nums", children: formatDuration(entry.durationMinutes, "clock") }),
421
- /* @__PURE__ */ jsx("td", { className: "w-16 p-3 text-right align-top", children: canManageOwn && !entry.isLocked ? /* @__PURE__ */ jsx(
422
- Button,
423
- {
424
- type: "button",
425
- variant: "ghost",
426
- size: "2xs",
427
- "aria-label": t("staff.time_tracking.timesheet.list.edit", "Edit"),
428
- onClick: () => setDialog({ open: true, entryId: entry.id }),
429
- children: /* @__PURE__ */ jsx(Pencil, { className: "size-3.5", "aria-hidden": "true" })
460
+ data.entries.length === 0 ? /* @__PURE__ */ jsx("p", { className: "p-4 text-sm text-muted-foreground", children: t("staff.time_tracking.myWork.today.empty", "Nothing logged today yet.") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
461
+ /* @__PURE__ */ jsx(
462
+ DataTable,
463
+ {
464
+ embedded: true,
465
+ extensionTableId: extensionPoints.hosts.myWorkTodayEntriesTable.tableId,
466
+ disableRowClick: true,
467
+ columns: todayEntriesColumns,
468
+ data: data.entries
469
+ }
470
+ ),
471
+ canManageOwn && quickTargets.length > 0 ? /* @__PURE__ */ jsxs(
472
+ "div",
473
+ {
474
+ className: "flex flex-wrap items-start gap-2 border-t p-3",
475
+ onKeyDown: (event) => {
476
+ if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
477
+ event.preventDefault();
478
+ void submitQuickEntry();
430
479
  }
431
- ) : null })
432
- ] }, entry.id)),
433
- canManageOwn && quickTargets.length > 0 ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 4, className: "p-3", children: /* @__PURE__ */ jsxs(
434
- "div",
435
- {
436
- className: "flex flex-wrap items-start gap-2",
437
- onKeyDown: (event) => {
438
- if ((event.metaKey || event.ctrlKey) && event.key === "Enter") {
439
- event.preventDefault();
440
- void submitQuickEntry();
441
- }
442
- },
443
- children: [
444
- /* @__PURE__ */ jsxs(Select, { value: quickTargetKey, onValueChange: setQuickTargetKey, children: [
445
- /* @__PURE__ */ jsx(
446
- SelectTrigger,
447
- {
448
- className: "w-72",
449
- "aria-label": t("staff.time_tracking.myWork.quickEntry.target", "Project or task"),
450
- children: /* @__PURE__ */ jsx(SelectValue, {})
451
- }
452
- ),
453
- /* @__PURE__ */ jsx(SelectContent, { children: quickTargets.map((target) => /* @__PURE__ */ jsx(SelectItem, { value: target.key, children: targetLabel(target) }, target.key)) })
454
- ] }),
455
- /* @__PURE__ */ jsx("div", { className: "w-32", children: /* @__PURE__ */ jsx(
456
- DurationInput,
457
- {
458
- variant: "compact",
459
- value: quickMinutes,
460
- onChange: (minutes) => setQuickMinutes(minutes),
461
- ariaLabel: t("staff.time_tracking.myWork.quickEntry.duration", "Duration")
462
- },
463
- quickEpoch
464
- ) }),
480
+ },
481
+ children: [
482
+ /* @__PURE__ */ jsxs(Select, { value: quickTargetKey, onValueChange: setQuickTargetKey, children: [
465
483
  /* @__PURE__ */ jsx(
466
- Button,
484
+ SelectTrigger,
467
485
  {
468
- type: "button",
469
- size: "sm",
470
- disabled: quickSaving || quickMinutes === null || quickMinutes <= 0,
471
- onClick: () => void submitQuickEntry(),
472
- children: t("staff.time_tracking.myWork.quickEntry.save", "Save")
486
+ className: "w-72",
487
+ "aria-label": t("staff.time_tracking.myWork.quickEntry.target", "Project or task"),
488
+ children: /* @__PURE__ */ jsx(SelectValue, {})
473
489
  }
474
490
  ),
475
- /* @__PURE__ */ jsx("span", { className: "self-center text-xs text-muted-foreground", children: t("staff.time_tracking.entryDialog.shortcutSave", "\u2318\u21B5 to save") }),
476
- /* @__PURE__ */ jsx("div", { className: "w-full", children: /* @__PURE__ */ jsx(
477
- EntryDetailsFields,
478
- {
479
- idPrefix: "my-work-quick",
480
- value: quickDetails,
481
- onChange: setQuickDetails,
482
- tagOptions,
483
- onCreateTag: createTag,
484
- disabled: quickSaving
485
- }
486
- ) })
487
- ]
488
- }
489
- ) }) }) : null
490
- ] }),
491
- /* @__PURE__ */ jsx("tfoot", { children: /* @__PURE__ */ jsxs("tr", { className: "border-t bg-muted/40 font-medium", children: [
492
- /* @__PURE__ */ jsx("td", { className: "p-3", colSpan: 2, children: t("staff.time_tracking.myWork.today.total", "Total today") }),
493
- /* @__PURE__ */ jsx("td", { className: "p-3 text-right font-mono tabular-nums", children: formatDuration(todayTotal, "clock") }),
494
- /* @__PURE__ */ jsx("td", {})
495
- ] }) })
491
+ /* @__PURE__ */ jsx(SelectContent, { children: quickTargets.map((target) => /* @__PURE__ */ jsx(SelectItem, { value: target.key, children: targetLabel(target) }, target.key)) })
492
+ ] }),
493
+ /* @__PURE__ */ jsx("div", { className: "w-32", children: /* @__PURE__ */ jsx(
494
+ DurationInput,
495
+ {
496
+ variant: "compact",
497
+ value: quickMinutes,
498
+ onChange: (minutes) => setQuickMinutes(minutes),
499
+ ariaLabel: t("staff.time_tracking.myWork.quickEntry.duration", "Duration")
500
+ },
501
+ quickEpoch
502
+ ) }),
503
+ /* @__PURE__ */ jsx(
504
+ Button,
505
+ {
506
+ type: "button",
507
+ size: "sm",
508
+ disabled: quickSaving || quickMinutes === null || quickMinutes <= 0,
509
+ onClick: () => void submitQuickEntry(),
510
+ children: t("staff.time_tracking.myWork.quickEntry.save", "Save")
511
+ }
512
+ ),
513
+ /* @__PURE__ */ jsx("span", { className: "self-center text-xs text-muted-foreground", children: t("staff.time_tracking.entryDialog.shortcutSave", "\u2318\u21B5 to save") }),
514
+ /* @__PURE__ */ jsx("div", { className: "w-full", children: /* @__PURE__ */ jsx(
515
+ EntryDetailsFields,
516
+ {
517
+ idPrefix: "my-work-quick",
518
+ value: quickDetails,
519
+ onChange: setQuickDetails,
520
+ tagOptions,
521
+ onCreateTag: createTag,
522
+ disabled: quickSaving
523
+ }
524
+ ) })
525
+ ]
526
+ }
527
+ ) : null,
528
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-t bg-muted/40 p-3 text-sm font-medium", children: [
529
+ /* @__PURE__ */ jsx("span", { children: t("staff.time_tracking.myWork.today.total", "Total today") }),
530
+ /* @__PURE__ */ jsx("span", { className: "font-mono tabular-nums", children: formatDuration(todayTotal, "clock") })
531
+ ] })
496
532
  ] })
497
533
  ] }),
498
534
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-6", children: [
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/staff/backend/staff/time-tracking/page.tsx"],
4
- "sourcesContent": ["\"use client\"\n\n/**\n * Screen 1 \u2014 My work, the Team Member's landing page (T5.6).\n *\n * Everything on it comes from one request (`GET \u2026/timesheets/my-work`), so the\n * four KPIs, today's entries and the project budget bars can never disagree with\n * each other the way six independent list calls could.\n *\n * The mockup's five notes:\n *\n * 1. The running-timer pill lives in the top bar and is contributed by the\n * `timer-sidebar-indicator` injection widget, not by this page.\n * 2. **TL-only navigation is absent, not disabled** \u2014 the sidebar is built from\n * page metadata whose `requireFeatures` are evaluated per user, so a TM never\n * receives the Projects / Reports / Settings entries at all (confirmed at\n * `nav.ts`: an unmet feature `continue`s past the entry).\n * 3. The quick-entry row uses the same duration parser as the rest of the module\n * and saves with `\u2318\u21B5`.\n * 4. Budget bars come from the project's own budget; a project without one shows\n * hours and no bar. An `amount` budget is money, so it is absent for a caller\n * without `staff.timesheets.rates.view` rather than rendered at zero.\n * 5. \"Copy yesterday\" duplicates the previous day's entries as drafts to correct\n * (US-D6), and reports what it skipped \u2014 a locked entry is skipped, never\n * silently dropped.\n *\n * Screen 2 (no assignments) is the same page with `projects: []`: it renders\n * `NoTimeProjectAssignments`, which already carries the copy and the single\n * available action.\n */\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { CalendarDays, Copy, Lock, Pencil, Plus } from 'lucide-react'\nimport { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { KpiCard } from '@open-mercato/ui/backend/charts'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { DurationInput } from '../../../lib/time-tracking-ui/DurationInput'\nimport {\n EntryDetailsFields,\n emptyEntryDetails,\n type EntryDetailsValue,\n} from '../../../lib/time-tracking-ui/EntryDetailsFields'\nimport { buildEntryClocks } from '../../../lib/time-tracking-ui/taskDrawerData'\nimport { slugifyProjectName } from '../../../lib/time-tracking/projectCode'\nimport type { TagOption } from '../../../lib/time-tracking-ui/timeEntryDialogState'\nimport { TimeEntryDialog } from '../../../lib/time-tracking-ui/TimeEntryDialog'\nimport { NoTimeProjectAssignments } from '../../../lib/time-tracking-ui/NoProjectAccess'\nimport { formatDuration } from '../../../lib/time-tracking/duration'\nimport { formatLongDayLabel, todayIso } from '../../../lib/time-tracking-ui/timesheetPeriod'\nimport { buildLogTargets, parseTargetKey, targetLabel } from '../../../lib/time-tracking-ui/timesheetTargets'\nimport { isEntryLockedError } from '../../../lib/time-tracking-ui/timeEntryListData'\n\nconst logger = createLogger('staff').child({ component: 'time-tracking/my-work' })\n\nconst MY_WORK_ENDPOINT = '/api/staff/timesheets/my-work'\nconst ENTRIES_API_PATH = 'staff/timesheets/time-entries'\nconst MANAGE_OWN_FEATURE = 'staff.timesheets.manage_own'\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.myWork'\nconst TIMESHEET_HREF = '/backend/staff/time-tracking/timesheet'\nconst ENTRIES_HREF = '/backend/staff/time-tracking/entries'\n\ntype MyWorkEntry = {\n id: string\n date: string\n taskId: string | null\n taskTitle: string | null\n timeProjectId: string | null\n projectName: string | null\n description: string | null\n startedAt: string | null\n endedAt: string | null\n durationMinutes: number\n isBillable: boolean\n isLocked: boolean\n}\n\ntype MyWorkProject = {\n id: string\n name: string\n code: string | null\n color: string | null\n customerName: string | null\n myMinutes: number\n totalMinutes: number\n hourlyRate?: number | null\n currencyCode?: string | null\n budget: {\n kind: 'hours' | 'amount'\n budgetValue: number\n usedValue: number\n percent: number\n barPercent: number\n tone: string\n } | null\n}\n\ntype MyWorkTask = {\n id: string\n title: string\n timeProjectId: string\n projectName: string | null\n}\n\ntype MyWorkPayload = {\n staffMember: { id: string; displayName: string } | null\n today: string\n kpis: {\n todayMinutes: number\n weekMinutes: number\n monthMinutes: number\n monthNonBillableMinutes: number\n dailyTargetMinutes: number | null\n weekTargetMinutes: number | null\n monthTargetMinutes: number | null\n weekWorkingDays: number\n monthWorkingDays: number\n nonBillableSharePercent: number | null\n }\n entries: MyWorkEntry[]\n projects: MyWorkProject[]\n recentTasks: MyWorkTask[]\n}\n\nconst EMPTY_PAYLOAD: MyWorkPayload = {\n staffMember: null,\n today: todayIso(),\n kpis: {\n todayMinutes: 0,\n weekMinutes: 0,\n monthMinutes: 0,\n monthNonBillableMinutes: 0,\n dailyTargetMinutes: null,\n weekTargetMinutes: null,\n monthTargetMinutes: null,\n weekWorkingDays: 0,\n monthWorkingDays: 0,\n nonBillableSharePercent: null,\n },\n entries: [],\n projects: [],\n recentTasks: [],\n}\n\nfunction formatClockRange(entry: MyWorkEntry, locale?: string): string | null {\n if (!entry.startedAt || !entry.endedAt) return null\n const format = (iso: string) => {\n const parsed = new Date(iso)\n if (Number.isNaN(parsed.getTime())) return ''\n try {\n return new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit', hour12: false }).format(parsed)\n } catch {\n return ''\n }\n }\n const start = format(entry.startedAt)\n const end = format(entry.endedAt)\n return start && end ? `${start} \u2013 ${end}` : null\n}\n\nexport default function TimeTrackingMyWorkPage() {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload: chrome } = useBackendChrome()\n const canManageOwn = hasFeature(chrome?.grantedFeatures, MANAGE_OWN_FEATURE)\n\n const [data, setData] = React.useState<MyWorkPayload>(EMPTY_PAYLOAD)\n const [loadState, setLoadState] = React.useState<'loading' | 'ready' | 'error'>('loading')\n const [dialog, setDialog] = React.useState<{ open: boolean; entryId: string | null }>({\n open: false,\n entryId: null,\n })\n const [quickTargetKey, setQuickTargetKey] = React.useState('')\n const [quickMinutes, setQuickMinutes] = React.useState<number | null>(null)\n const [quickEpoch, setQuickEpoch] = React.useState(0)\n const [quickDetails, setQuickDetails] = React.useState<EntryDetailsValue>(() => emptyEntryDetails(''))\n const [tagOptions, setTagOptions] = React.useState<TagOption[]>([])\n\n // The date defaults to the day the page is showing, once that is known.\n React.useEffect(() => {\n if (!data.today) return\n setQuickDetails((current) => (current.date ? current : { ...current, date: data.today }))\n }, [data.today])\n\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const res = await Promise.resolve(\n apiCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/tags?page=1&pageSize=100'),\n )\n if (cancelled || !res?.ok) return\n const items = Array.isArray(res.result?.items) ? res.result.items : []\n setTagOptions(\n items\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.label === 'string' ? row.label : null\n return id && label\n ? { id, label, color: typeof row.color === 'string' ? row.color : null }\n : null\n })\n .filter((tag): tag is TagOption => tag !== null),\n )\n } catch {\n // A missing tag vocabulary leaves the picker empty rather than the page broken.\n }\n })()\n return () => { cancelled = true }\n }, [])\n\n const createTag = React.useCallback(\n async (label: string): Promise<TagOption | null> => {\n try {\n const created = await createCrud('/api/staff/timesheets/tags', {\n label,\n slug: slugifyProjectName(label).toLowerCase(),\n }, { errorMessage: t('staff.time_tracking.entryDialog.errors.createTag', 'Could not create the tag.') })\n const payload = created as unknown as Record<string, unknown> | null\n const raw = payload?.id ?? (payload?.result as Record<string, unknown> | undefined)?.id\n const id = typeof raw === 'string' ? raw : null\n if (!id) return null\n const tag: TagOption = { id, label, color: null }\n setTagOptions((current) => [...current, tag])\n return tag\n } catch (error) {\n logger.warn('staff.time_tracking.my-work tag create failed', { err: error })\n return null\n }\n },\n [t],\n )\n const [quickSaving, setQuickSaving] = React.useState(false)\n\n const loadError = t('staff.time_tracking.myWork.errors.load', 'Failed to load your work summary.')\n\n // A failed request leaves `data` at EMPTY_PAYLOAD, which reads as screen 2 \u2014 \"no\n // assignments\" \u2014 so the failure has to be a state of its own or the page reports a\n // server fault as an empty account.\n const load = React.useCallback(async () => {\n try {\n const result = await readApiResultOrThrow<MyWorkPayload>(MY_WORK_ENDPOINT, undefined, {\n errorMessage: loadError,\n fallback: EMPTY_PAYLOAD,\n })\n setData({ ...EMPTY_PAYLOAD, ...result })\n setLoadState('ready')\n } catch (error) {\n logger.error('staff.time_tracking.my-work load failed', { err: error })\n setLoadState('error')\n }\n }, [loadError])\n\n const retryLoad = React.useCallback(() => {\n setLoadState('loading')\n void load()\n }, [load])\n\n React.useEffect(() => {\n void load()\n }, [load, scopeVersion])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const mutationContext = React.useMemo(\n () => ({\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_entry',\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const myWorkInjectionContext = React.useMemo(\n () => ({\n staffMemberId: data.staffMember?.id ?? null,\n today: data.today,\n projectIds: data.projects.map((project) => project.id),\n retryLastMutation,\n }),\n [data.projects, data.staffMember?.id, data.today, retryLastMutation],\n )\n\n const quickTargets = React.useMemo(\n () =>\n buildLogTargets(\n data.projects.map((project) => ({\n id: project.id,\n name: project.name,\n code: project.code,\n color: project.color,\n })),\n data.recentTasks.map((task) => ({\n id: task.id,\n title: task.title,\n timeProjectId: task.timeProjectId,\n })),\n ),\n [data.projects, data.recentTasks],\n )\n\n React.useEffect(() => {\n if (quickTargetKey || quickTargets.length === 0) return\n setQuickTargetKey(quickTargets[0].key)\n }, [quickTargetKey, quickTargets])\n\n const todayTotal = data.entries.reduce((sum, entry) => sum + entry.durationMinutes, 0)\n\n const submitQuickEntry = React.useCallback(async () => {\n if (!quickTargetKey || quickMinutes === null || quickMinutes <= 0 || quickSaving) return\n const target = parseTargetKey(quickTargetKey)\n const body = {\n staffMemberId: data.staffMember?.id,\n date: quickDetails.date || data.today,\n timeProjectId: target.timeProjectId,\n taskId: target.taskId,\n durationMinutes: quickMinutes,\n description: quickDetails.description.trim() || null,\n isBillable: quickDetails.isBillable,\n tagIds: quickDetails.tagIds.length > 0 ? quickDetails.tagIds : undefined,\n // Paired or omitted \u2014 a lone start is how the list identifies a running\n // timer, and a manual entry has no segment to stop.\n ...buildEntryClocks({\n date: quickDetails.date || data.today,\n startClock: quickDetails.startClock || null,\n endClock: quickDetails.endClock || null,\n minutes: quickMinutes,\n }),\n source: 'manual',\n }\n setQuickSaving(true)\n try {\n await runMutation({\n operation: () =>\n createCrud(ENTRIES_API_PATH, body, {\n errorMessage: t('staff.timesheets.my.errors.save', 'Failed to save timesheets.'),\n }),\n context: mutationContext,\n mutationPayload: body as unknown as Record<string, unknown>,\n })\n setQuickMinutes(null)\n setQuickEpoch((epoch) => epoch + 1)\n // The date and the billable flag survive; the rest clears. Logging three\n // lines against yesterday should not mean re-picking yesterday each time.\n setQuickDetails((current) => ({\n ...emptyEntryDetails(current.date, current.isBillable),\n }))\n await load()\n } catch (error) {\n logger.error('staff.time_tracking.my-work quick entry failed', { err: error })\n flash(t('staff.timesheets.my.errors.save', 'Failed to save timesheets.'), 'error')\n } finally {\n setQuickSaving(false)\n }\n }, [data.staffMember?.id, data.today, load, mutationContext, quickDetails, quickMinutes, quickSaving, quickTargetKey, runMutation, t])\n\n const copyYesterday = React.useCallback(async () => {\n const today = new Date(`${data.today}T00:00:00.000Z`)\n const yesterday = new Date(today)\n yesterday.setUTCDate(yesterday.getUTCDate() - 1)\n const body = {\n fromDate: yesterday.toISOString().slice(0, 10),\n toDate: data.today,\n }\n try {\n const result = await runMutation({\n operation: () =>\n createCrud<{ copied?: number; skipped?: unknown[] }>(`${ENTRIES_API_PATH}/copy-day`, body, {\n errorMessage: t('staff.time_tracking.entries.errors.copyDay', 'Could not copy the day.'),\n }),\n context: mutationContext,\n mutationPayload: body,\n })\n const copied = (result?.result as { copied?: number } | undefined)?.copied ?? 0\n flash(\n t('staff.time_tracking.myWork.copyDay.done', 'Copied {count} entries from yesterday.', {\n count: String(copied),\n }),\n 'success',\n )\n await load()\n } catch (error) {\n if (isEntryLockedError(error)) {\n flash(\n t(\n 'staff.time_tracking.entries.errors.entryLocked',\n 'This time entry is locked in a closed report and cannot be changed. Unlock the report first.',\n ),\n 'error',\n )\n return\n }\n logger.error('staff.time_tracking.my-work copy day failed', { err: error })\n flash(t('staff.time_tracking.entries.errors.copyDay', 'Could not copy the day.'), 'error')\n }\n }, [data.today, load, mutationContext, runMutation, t])\n\n if (loadState === 'loading') {\n return (\n <Page>\n <PageHeader title={t('staff.time_tracking.nav.my_work', 'My work')} />\n <PageBody>\n <LoadingMessage label={t('staff.timesheets.my.loading', 'Loading timesheets...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (loadState === 'error') {\n return (\n <Page>\n <PageHeader title={t('staff.time_tracking.nav.my_work', 'My work')} />\n <PageBody>\n <ErrorMessage\n label={loadError}\n action={\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={retryLoad}>\n {t('staff.time_tracking.myWork.errors.retry', 'Try again')}\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const subtitle = [formatLongDayLabel(data.today), data.staffMember?.displayName]\n .filter((part): part is string => Boolean(part))\n .join(' \u00B7 ')\n\n const noAssignments = data.projects.length === 0 && data.entries.length === 0\n\n return (\n <Page>\n <PageHeader\n title={t('staff.time_tracking.nav.my_work', 'My work')}\n description={subtitle}\n actions={\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" asChild>\n <Link href={TIMESHEET_HREF}>\n <CalendarDays className=\"size-4\" aria-hidden=\"true\" />\n {t('staff.time_tracking.nav.timesheet', 'Timesheet')}\n </Link>\n </Button>\n {canManageOwn && !noAssignments ? (\n <Button type=\"button\" onClick={() => setDialog({ open: true, entryId: null })}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n {t('staff.time_tracking.entries.actions.add', 'Add entry')}\n </Button>\n ) : null}\n </div>\n }\n />\n <PageBody>\n {noAssignments ? (\n <NoTimeProjectAssignments />\n ) : (\n <div className=\"flex flex-col gap-6\">\n <InjectionSpot\n spotId={extensionPoints.hosts.myWorkBeforeSections.spotId}\n context={myWorkInjectionContext}\n data={data}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <KpiCard\n title={t('staff.time_tracking.myWork.kpi.today', 'Today')}\n value={data.kpis.todayMinutes}\n formatValue={(value) => formatDuration(value, 'clock')}\n comparisonLabel={\n data.kpis.dailyTargetMinutes !== null\n ? t('staff.time_tracking.myWork.kpi.targetGap', 'target {target} \u2014 {gap} to go', {\n target: formatDuration(data.kpis.dailyTargetMinutes, 'clock'),\n gap: formatDuration(\n Math.max(0, data.kpis.dailyTargetMinutes - data.kpis.todayMinutes),\n 'clock',\n ),\n })\n : undefined\n }\n />\n <KpiCard\n title={t('staff.time_tracking.myWork.kpi.week', 'This week')}\n value={data.kpis.weekMinutes}\n formatValue={(value) => formatDuration(value, 'clock')}\n comparisonLabel={\n data.kpis.weekTargetMinutes !== null\n ? t('staff.time_tracking.myWork.kpi.ofTarget', 'of {target}', {\n target: formatDuration(data.kpis.weekTargetMinutes, 'clock'),\n })\n : undefined\n }\n />\n <KpiCard\n title={t('staff.time_tracking.myWork.kpi.month', 'This month')}\n value={data.kpis.monthMinutes}\n formatValue={(value) => formatDuration(value, 'clock')}\n comparisonLabel={t('staff.time_tracking.myWork.kpi.workingDays', '{days} working days', {\n days: String(data.kpis.monthWorkingDays),\n })}\n />\n <KpiCard\n title={t('staff.time_tracking.myWork.kpi.nonBillable', 'Non-billable')}\n value={data.kpis.monthNonBillableMinutes}\n formatValue={(value) => formatDuration(value, 'clock')}\n comparisonLabel={\n data.kpis.nonBillableSharePercent !== null\n ? t('staff.time_tracking.myWork.kpi.shareOfMonth', '{percent}% of the month', {\n percent: String(data.kpis.nonBillableSharePercent),\n })\n : undefined\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\">\n <section className=\"rounded-lg border xl:col-span-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-2 border-b p-4\">\n <h2 className=\"text-sm font-semibold\">\n {t('staff.time_tracking.myWork.today.title', 'Today\u2019s entries')}\n </h2>\n <div className=\"flex items-center gap-2\">\n {canManageOwn ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void copyYesterday()}>\n <Copy className=\"size-4\" aria-hidden=\"true\" />\n {t('staff.time_tracking.myWork.today.copyYesterday', 'Copy yesterday')}\n </Button>\n ) : null}\n <Button variant=\"outline\" size=\"sm\" asChild>\n <Link href={ENTRIES_HREF}>\n {t('staff.time_tracking.myWork.today.allEntries', 'All entries')}\n </Link>\n </Button>\n </div>\n </div>\n\n {data.entries.length === 0 ? (\n <p className=\"p-4 text-sm text-muted-foreground\">\n {t('staff.time_tracking.myWork.today.empty', 'Nothing logged today yet.')}\n </p>\n ) : (\n <table className=\"w-full text-sm\">\n <tbody>\n {data.entries.map((entry) => (\n <tr key={entry.id} className=\"border-b last:border-0\">\n <td className=\"p-3 align-top\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium\">\n {entry.taskTitle ??\n entry.description ??\n t('staff.time_tracking.entries.table.noTask', 'No task')}\n </span>\n {entry.isBillable ? null : (\n <StatusBadge variant=\"neutral\">\n {t('staff.time_tracking.entries.badges.nonBillable', 'non-billable')}\n </StatusBadge>\n )}\n {entry.isLocked ? (\n <StatusBadge variant=\"neutral\">\n <Lock className=\"size-3\" aria-hidden=\"true\" />\n {t('staff.time_tracking.entries.badges.locked', 'locked')}\n </StatusBadge>\n ) : null}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {[entry.projectName, entry.taskTitle ? entry.description : null]\n .filter((part): part is string => Boolean(part))\n .join(' \u00B7 ')}\n </div>\n </td>\n <td className=\"w-32 p-3 align-top text-xs text-muted-foreground\">\n {formatClockRange(entry) ?? '\u2014'}\n </td>\n <td className=\"w-20 p-3 text-right align-top font-mono font-semibold tabular-nums\">\n {formatDuration(entry.durationMinutes, 'clock')}\n </td>\n <td className=\"w-16 p-3 text-right align-top\">\n {canManageOwn && !entry.isLocked ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"2xs\"\n aria-label={t('staff.time_tracking.timesheet.list.edit', 'Edit')}\n onClick={() => setDialog({ open: true, entryId: entry.id })}\n >\n <Pencil className=\"size-3.5\" aria-hidden=\"true\" />\n </Button>\n ) : null}\n </td>\n </tr>\n ))}\n {canManageOwn && quickTargets.length > 0 ? (\n <tr>\n <td colSpan={4} className=\"p-3\">\n <div\n className=\"flex flex-wrap items-start gap-2\"\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void submitQuickEntry()\n }\n }}\n >\n <Select value={quickTargetKey} onValueChange={setQuickTargetKey}>\n <SelectTrigger\n className=\"w-72\"\n aria-label={t('staff.time_tracking.myWork.quickEntry.target', 'Project or task')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {quickTargets.map((target) => (\n <SelectItem key={target.key} value={target.key}>\n {targetLabel(target)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <div className=\"w-32\">\n <DurationInput\n key={quickEpoch}\n variant=\"compact\"\n value={quickMinutes}\n onChange={(minutes) => setQuickMinutes(minutes)}\n ariaLabel={t('staff.time_tracking.myWork.quickEntry.duration', 'Duration')}\n />\n </div>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={quickSaving || quickMinutes === null || quickMinutes <= 0}\n onClick={() => void submitQuickEntry()}\n >\n {t('staff.time_tracking.myWork.quickEntry.save', 'Save')}\n </Button>\n <span className=\"self-center text-xs text-muted-foreground\">\n {t('staff.time_tracking.entryDialog.shortcutSave', '\u2318\u21B5 to save')}\n </span>\n {/*\n * The same block the task drawer offers, from the same\n * component: a row that can log an hour but not say what\n * it was for, or when, sends people to the full dialog\n * for the one field they were missing.\n */}\n <div className=\"w-full\">\n <EntryDetailsFields\n idPrefix=\"my-work-quick\"\n value={quickDetails}\n onChange={setQuickDetails}\n tagOptions={tagOptions}\n onCreateTag={createTag}\n disabled={quickSaving}\n />\n </div>\n </div>\n </td>\n </tr>\n ) : null}\n </tbody>\n <tfoot>\n <tr className=\"border-t bg-muted/40 font-medium\">\n <td className=\"p-3\" colSpan={2}>\n {t('staff.time_tracking.myWork.today.total', 'Total today')}\n </td>\n <td className=\"p-3 text-right font-mono tabular-nums\">\n {formatDuration(todayTotal, 'clock')}\n </td>\n <td />\n </tr>\n </tfoot>\n </table>\n )}\n </section>\n\n <div className=\"flex flex-col gap-6\">\n <section className=\"rounded-lg border\">\n <div className=\"flex items-center justify-between border-b p-4\">\n <h2 className=\"text-sm font-semibold\">\n {t('staff.time_tracking.myWork.projects.title', 'My projects')}\n </h2>\n <span className=\"text-xs text-muted-foreground\">{data.projects.length}</span>\n </div>\n <div className=\"flex flex-col gap-4 p-4\">\n {data.projects.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('staff.time_tracking.myWork.projects.empty', 'No projects assigned yet.')}\n </p>\n ) : (\n data.projects.map((project) => (\n <div key={project.id} className=\"flex flex-col gap-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <Link\n href={`/backend/staff/time-tracking/projects/${project.id}`}\n className=\"min-w-0 truncate font-medium hover:underline\"\n >\n {project.name}\n </Link>\n <span className=\"shrink-0 font-mono text-xs tabular-nums text-muted-foreground\">\n {formatDuration(project.myMinutes, 'clock')}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {[\n project.customerName,\n project.hourlyRate != null && project.currencyCode\n ? t('staff.time_tracking.myWork.projects.rate', '{rate}/h', {\n rate: formatCurrency(project.hourlyRate, project.currencyCode) ?? '',\n })\n : null,\n ]\n .filter((part): part is string => Boolean(part))\n .join(' \u00B7 ')}\n </p>\n {project.budget ? (\n <>\n <Progress\n value={project.budget.barPercent}\n size=\"sm\"\n tone={\n project.budget.tone === 'destructive'\n ? 'destructive'\n : project.budget.tone === 'warning'\n ? 'warning'\n : 'accent'\n }\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.myWork.projects.budget', '{percent}% of budget ({value})', {\n percent: String(project.budget.percent),\n value:\n project.budget.kind === 'hours'\n ? t('staff.time_tracking.myWork.projects.budgetHours', '{hours} h', {\n hours: String(project.budget.budgetValue),\n })\n : formatCurrency(project.budget.budgetValue, project.currencyCode ?? undefined) ??\n String(project.budget.budgetValue),\n })}\n </p>\n </>\n ) : null}\n </div>\n ))\n )}\n </div>\n </section>\n\n <section className=\"rounded-lg border\">\n <div className=\"border-b p-4\">\n <h2 className=\"text-sm font-semibold\">\n {t('staff.time_tracking.myWork.recentTasks.title', 'Recent tasks')}\n </h2>\n </div>\n <div className=\"flex flex-col gap-2 p-4\">\n {data.recentTasks.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('staff.time_tracking.myWork.recentTasks.empty', 'No tasks logged against yet.')}\n </p>\n ) : (\n data.recentTasks.map((task) => (\n <Link\n key={task.id}\n href={`/backend/staff/time-tracking/projects/${task.timeProjectId}/board?task=${task.id}`}\n className=\"flex items-center justify-between gap-2 truncate text-sm hover:underline\"\n >\n <span className=\"truncate\">{task.title}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{task.projectName}</span>\n </Link>\n ))\n )}\n </div>\n </section>\n </div>\n </div>\n <InjectionSpot\n spotId={extensionPoints.hosts.myWorkAfterSections.spotId}\n context={myWorkInjectionContext}\n data={data}\n />\n </div>\n )}\n </PageBody>\n\n <TimeEntryDialog\n open={dialog.open}\n onOpenChange={(open) => setDialog((current) => ({ ...current, open }))}\n entryId={dialog.entryId}\n defaults={{ date: data.today }}\n onSaved={() => {\n void load()\n }}\n />\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\n/**\n * Screen 1 \u2014 My work, the Team Member's landing page (T5.6).\n *\n * Everything on it comes from one request (`GET \u2026/timesheets/my-work`), so the\n * four KPIs, today's entries and the project budget bars can never disagree with\n * each other the way six independent list calls could.\n *\n * The mockup's five notes:\n *\n * 1. The running-timer pill lives in the top bar and is contributed by the\n * `timer-sidebar-indicator` injection widget, not by this page.\n * 2. **TL-only navigation is absent, not disabled** \u2014 the sidebar is built from\n * page metadata whose `requireFeatures` are evaluated per user, so a TM never\n * receives the Projects / Reports / Settings entries at all (confirmed at\n * `nav.ts`: an unmet feature `continue`s past the entry).\n * 3. The quick-entry row uses the same duration parser as the rest of the module\n * and saves with `\u2318\u21B5`.\n * 4. Budget bars come from the project's own budget; a project without one shows\n * hours and no bar. An `amount` budget is money, so it is absent for a caller\n * without `staff.timesheets.rates.view` rather than rendered at zero.\n * 5. \"Copy yesterday\" duplicates the previous day's entries as drafts to correct\n * (US-D6), and reports what it skipped \u2014 a locked entry is skipped, never\n * silently dropped.\n *\n * Screen 2 (no assignments) is the same page with `projects: []`: it renders\n * `NoTimeProjectAssignments`, which already carries the copy and the single\n * available action.\n */\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { CalendarDays, Copy, Lock, Pencil, Plus } from 'lucide-react'\nimport { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Progress } from '@open-mercato/ui/primitives/progress'\nimport { StatusBadge } from '@open-mercato/ui/primitives/status-badge'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { KpiCard } from '@open-mercato/ui/backend/charts'\nimport { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'\nimport { InjectionSpot } from '@open-mercato/ui/backend/injection/InjectionSpot'\nimport { extensionPoints } from '@open-mercato/core/modules/staff/extension-points'\nimport { useBackendChrome } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport { formatCurrency } from '@open-mercato/ui/utils/format'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport { DurationInput } from '../../../lib/time-tracking-ui/DurationInput'\nimport {\n EntryDetailsFields,\n emptyEntryDetails,\n type EntryDetailsValue,\n} from '../../../lib/time-tracking-ui/EntryDetailsFields'\nimport { buildEntryClocks } from '../../../lib/time-tracking-ui/taskDrawerData'\nimport { slugifyProjectName } from '../../../lib/time-tracking/projectCode'\nimport type { TagOption } from '../../../lib/time-tracking-ui/timeEntryDialogState'\nimport { TimeEntryDialog } from '../../../lib/time-tracking-ui/TimeEntryDialog'\nimport { NoTimeProjectAssignments } from '../../../lib/time-tracking-ui/NoProjectAccess'\nimport { formatDuration } from '../../../lib/time-tracking/duration'\nimport { formatLongDayLabel, todayIso } from '../../../lib/time-tracking-ui/timesheetPeriod'\nimport { buildLogTargets, parseTargetKey, targetLabel } from '../../../lib/time-tracking-ui/timesheetTargets'\nimport { isEntryLockedError } from '../../../lib/time-tracking-ui/timeEntryListData'\n\nconst logger = createLogger('staff').child({ component: 'time-tracking/my-work' })\n\nconst MY_WORK_ENDPOINT = '/api/staff/timesheets/my-work'\nconst ENTRIES_API_PATH = 'staff/timesheets/time-entries'\nconst MANAGE_OWN_FEATURE = 'staff.timesheets.manage_own'\nconst MUTATION_CONTEXT_ID = 'staff.time_tracking.myWork'\nconst TIMESHEET_HREF = '/backend/staff/time-tracking/timesheet'\nconst ENTRIES_HREF = '/backend/staff/time-tracking/entries'\n\ntype MyWorkEntry = {\n id: string\n date: string\n taskId: string | null\n taskTitle: string | null\n timeProjectId: string | null\n projectName: string | null\n description: string | null\n startedAt: string | null\n endedAt: string | null\n durationMinutes: number\n isBillable: boolean\n isLocked: boolean\n}\n\ntype MyWorkProject = {\n id: string\n name: string\n code: string | null\n color: string | null\n customerName: string | null\n myMinutes: number\n totalMinutes: number\n hourlyRate?: number | null\n currencyCode?: string | null\n budget: {\n kind: 'hours' | 'amount'\n budgetValue: number\n usedValue: number\n percent: number\n barPercent: number\n tone: string\n } | null\n}\n\ntype MyWorkTask = {\n id: string\n title: string\n timeProjectId: string\n projectName: string | null\n}\n\ntype MyWorkPayload = {\n staffMember: { id: string; displayName: string } | null\n today: string\n kpis: {\n todayMinutes: number\n weekMinutes: number\n monthMinutes: number\n monthNonBillableMinutes: number\n dailyTargetMinutes: number | null\n weekTargetMinutes: number | null\n monthTargetMinutes: number | null\n weekWorkingDays: number\n monthWorkingDays: number\n nonBillableSharePercent: number | null\n }\n entries: MyWorkEntry[]\n projects: MyWorkProject[]\n recentTasks: MyWorkTask[]\n}\n\nconst EMPTY_PAYLOAD: MyWorkPayload = {\n staffMember: null,\n today: todayIso(),\n kpis: {\n todayMinutes: 0,\n weekMinutes: 0,\n monthMinutes: 0,\n monthNonBillableMinutes: 0,\n dailyTargetMinutes: null,\n weekTargetMinutes: null,\n monthTargetMinutes: null,\n weekWorkingDays: 0,\n monthWorkingDays: 0,\n nonBillableSharePercent: null,\n },\n entries: [],\n projects: [],\n recentTasks: [],\n}\n\nfunction formatClockRange(entry: MyWorkEntry, locale?: string): string | null {\n if (!entry.startedAt || !entry.endedAt) return null\n const format = (iso: string) => {\n const parsed = new Date(iso)\n if (Number.isNaN(parsed.getTime())) return ''\n try {\n return new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit', hour12: false }).format(parsed)\n } catch {\n return ''\n }\n }\n const start = format(entry.startedAt)\n const end = format(entry.endedAt)\n return start && end ? `${start} \u2013 ${end}` : null\n}\n\nexport default function TimeTrackingMyWorkPage() {\n const t = useT()\n const scopeVersion = useOrganizationScopeVersion()\n const { payload: chrome } = useBackendChrome()\n const canManageOwn = hasFeature(chrome?.grantedFeatures, MANAGE_OWN_FEATURE)\n\n const [data, setData] = React.useState<MyWorkPayload>(EMPTY_PAYLOAD)\n const [loadState, setLoadState] = React.useState<'loading' | 'ready' | 'error'>('loading')\n const [dialog, setDialog] = React.useState<{ open: boolean; entryId: string | null }>({\n open: false,\n entryId: null,\n })\n const [quickTargetKey, setQuickTargetKey] = React.useState('')\n const [quickMinutes, setQuickMinutes] = React.useState<number | null>(null)\n const [quickEpoch, setQuickEpoch] = React.useState(0)\n const [quickDetails, setQuickDetails] = React.useState<EntryDetailsValue>(() => emptyEntryDetails(''))\n const [tagOptions, setTagOptions] = React.useState<TagOption[]>([])\n\n // The date defaults to the day the page is showing, once that is known.\n React.useEffect(() => {\n if (!data.today) return\n setQuickDetails((current) => (current.date ? current : { ...current, date: data.today }))\n }, [data.today])\n\n React.useEffect(() => {\n let cancelled = false\n void (async () => {\n try {\n const res = await Promise.resolve(\n apiCall<{ items?: Array<Record<string, unknown>> }>('/api/staff/timesheets/tags?page=1&pageSize=100'),\n )\n if (cancelled || !res?.ok) return\n const items = Array.isArray(res.result?.items) ? res.result.items : []\n setTagOptions(\n items\n .map((row: Record<string, unknown>) => {\n const id = typeof row.id === 'string' ? row.id : null\n const label = typeof row.label === 'string' ? row.label : null\n return id && label\n ? { id, label, color: typeof row.color === 'string' ? row.color : null }\n : null\n })\n .filter((tag): tag is TagOption => tag !== null),\n )\n } catch {\n // A missing tag vocabulary leaves the picker empty rather than the page broken.\n }\n })()\n return () => { cancelled = true }\n }, [])\n\n const createTag = React.useCallback(\n async (label: string): Promise<TagOption | null> => {\n try {\n const created = await createCrud('/api/staff/timesheets/tags', {\n label,\n slug: slugifyProjectName(label).toLowerCase(),\n }, { errorMessage: t('staff.time_tracking.entryDialog.errors.createTag', 'Could not create the tag.') })\n const payload = created as unknown as Record<string, unknown> | null\n const raw = payload?.id ?? (payload?.result as Record<string, unknown> | undefined)?.id\n const id = typeof raw === 'string' ? raw : null\n if (!id) return null\n const tag: TagOption = { id, label, color: null }\n setTagOptions((current) => [...current, tag])\n return tag\n } catch (error) {\n logger.warn('staff.time_tracking.my-work tag create failed', { err: error })\n return null\n }\n },\n [t],\n )\n const [quickSaving, setQuickSaving] = React.useState(false)\n\n const loadError = t('staff.time_tracking.myWork.errors.load', 'Failed to load your work summary.')\n\n // A failed request leaves `data` at EMPTY_PAYLOAD, which reads as screen 2 \u2014 \"no\n // assignments\" \u2014 so the failure has to be a state of its own or the page reports a\n // server fault as an empty account.\n const load = React.useCallback(async () => {\n try {\n const result = await readApiResultOrThrow<MyWorkPayload>(MY_WORK_ENDPOINT, undefined, {\n errorMessage: loadError,\n fallback: EMPTY_PAYLOAD,\n })\n setData({ ...EMPTY_PAYLOAD, ...result })\n setLoadState('ready')\n } catch (error) {\n logger.error('staff.time_tracking.my-work load failed', { err: error })\n setLoadState('error')\n }\n }, [loadError])\n\n const retryLoad = React.useCallback(() => {\n setLoadState('loading')\n void load()\n }, [load])\n\n React.useEffect(() => {\n void load()\n }, [load, scopeVersion])\n\n const { runMutation, retryLastMutation } = useGuardedMutation<{\n formId: string\n resourceKind: string\n retryLastMutation: () => Promise<boolean>\n }>({\n contextId: MUTATION_CONTEXT_ID,\n blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),\n })\n\n const mutationContext = React.useMemo(\n () => ({\n formId: MUTATION_CONTEXT_ID,\n resourceKind: 'staff.timesheets.time_entry',\n retryLastMutation,\n }),\n [retryLastMutation],\n )\n\n const myWorkInjectionContext = React.useMemo(\n () => ({\n staffMemberId: data.staffMember?.id ?? null,\n today: data.today,\n projectIds: data.projects.map((project) => project.id),\n retryLastMutation,\n }),\n [data.projects, data.staffMember?.id, data.today, retryLastMutation],\n )\n\n const quickTargets = React.useMemo(\n () =>\n buildLogTargets(\n data.projects.map((project) => ({\n id: project.id,\n name: project.name,\n code: project.code,\n color: project.color,\n })),\n data.recentTasks.map((task) => ({\n id: task.id,\n title: task.title,\n timeProjectId: task.timeProjectId,\n })),\n ),\n [data.projects, data.recentTasks],\n )\n\n React.useEffect(() => {\n if (quickTargetKey || quickTargets.length === 0) return\n setQuickTargetKey(quickTargets[0].key)\n }, [quickTargetKey, quickTargets])\n\n const todayTotal = data.entries.reduce((sum, entry) => sum + entry.durationMinutes, 0)\n\n const todayEntriesColumns = React.useMemo<ColumnDef<MyWorkEntry>[]>(\n () => [\n {\n id: 'task',\n header: t('staff.time_tracking.myWork.today.columns.task', 'Task'),\n cell: ({ row }) => {\n const entry = row.original\n return (\n <div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"font-medium\">\n {entry.taskTitle ??\n entry.description ??\n t('staff.time_tracking.entries.table.noTask', 'No task')}\n </span>\n {entry.isBillable ? null : (\n <StatusBadge variant=\"neutral\">\n {t('staff.time_tracking.entries.badges.nonBillable', 'non-billable')}\n </StatusBadge>\n )}\n {entry.isLocked ? (\n <StatusBadge variant=\"neutral\">\n <Lock className=\"size-3\" aria-hidden=\"true\" />\n {t('staff.time_tracking.entries.badges.locked', 'locked')}\n </StatusBadge>\n ) : null}\n </div>\n <div className=\"text-xs text-muted-foreground\">\n {[entry.projectName, entry.taskTitle ? entry.description : null]\n .filter((part): part is string => Boolean(part))\n .join(' \u00B7 ')}\n </div>\n </div>\n )\n },\n },\n {\n id: 'clock',\n header: t('staff.time_tracking.myWork.today.columns.time', 'Time'),\n meta: { maxWidth: '8rem' },\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">{formatClockRange(row.original) ?? '\u2014'}</span>\n ),\n },\n {\n id: 'duration',\n header: () => (\n <span className=\"block text-right\">\n {t('staff.time_tracking.myWork.today.columns.duration', 'Duration')}\n </span>\n ),\n meta: { maxWidth: '6rem' },\n cell: ({ row }) => (\n <span className=\"block text-right font-mono font-semibold tabular-nums\">\n {formatDuration(row.original.durationMinutes, 'clock')}\n </span>\n ),\n },\n {\n id: 'actions',\n header: '',\n meta: { maxWidth: '3rem' },\n cell: ({ row }) => {\n const entry = row.original\n return canManageOwn && !entry.isLocked ? (\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"2xs\"\n aria-label={t('staff.time_tracking.timesheet.list.edit', 'Edit')}\n onClick={() => setDialog({ open: true, entryId: entry.id })}\n >\n <Pencil className=\"size-3.5\" aria-hidden=\"true\" />\n </Button>\n </div>\n ) : null\n },\n },\n ],\n [canManageOwn, t],\n )\n\n const submitQuickEntry = React.useCallback(async () => {\n if (!quickTargetKey || quickMinutes === null || quickMinutes <= 0 || quickSaving) return\n const target = parseTargetKey(quickTargetKey)\n const body = {\n staffMemberId: data.staffMember?.id,\n date: quickDetails.date || data.today,\n timeProjectId: target.timeProjectId,\n taskId: target.taskId,\n durationMinutes: quickMinutes,\n description: quickDetails.description.trim() || null,\n isBillable: quickDetails.isBillable,\n tagIds: quickDetails.tagIds.length > 0 ? quickDetails.tagIds : undefined,\n // Paired or omitted \u2014 a lone start is how the list identifies a running\n // timer, and a manual entry has no segment to stop.\n ...buildEntryClocks({\n date: quickDetails.date || data.today,\n startClock: quickDetails.startClock || null,\n endClock: quickDetails.endClock || null,\n minutes: quickMinutes,\n }),\n source: 'manual',\n }\n setQuickSaving(true)\n try {\n await runMutation({\n operation: () =>\n createCrud(ENTRIES_API_PATH, body, {\n errorMessage: t('staff.timesheets.my.errors.save', 'Failed to save timesheets.'),\n }),\n context: mutationContext,\n mutationPayload: body as unknown as Record<string, unknown>,\n })\n setQuickMinutes(null)\n setQuickEpoch((epoch) => epoch + 1)\n // The date and the billable flag survive; the rest clears. Logging three\n // lines against yesterday should not mean re-picking yesterday each time.\n setQuickDetails((current) => ({\n ...emptyEntryDetails(current.date, current.isBillable),\n }))\n await load()\n } catch (error) {\n logger.error('staff.time_tracking.my-work quick entry failed', { err: error })\n flash(t('staff.timesheets.my.errors.save', 'Failed to save timesheets.'), 'error')\n } finally {\n setQuickSaving(false)\n }\n }, [data.staffMember?.id, data.today, load, mutationContext, quickDetails, quickMinutes, quickSaving, quickTargetKey, runMutation, t])\n\n const copyYesterday = React.useCallback(async () => {\n const today = new Date(`${data.today}T00:00:00.000Z`)\n const yesterday = new Date(today)\n yesterday.setUTCDate(yesterday.getUTCDate() - 1)\n const body = {\n fromDate: yesterday.toISOString().slice(0, 10),\n toDate: data.today,\n }\n try {\n const result = await runMutation({\n operation: () =>\n createCrud<{ copied?: number; skipped?: unknown[] }>(`${ENTRIES_API_PATH}/copy-day`, body, {\n errorMessage: t('staff.time_tracking.entries.errors.copyDay', 'Could not copy the day.'),\n }),\n context: mutationContext,\n mutationPayload: body,\n })\n const copied = (result?.result as { copied?: number } | undefined)?.copied ?? 0\n flash(\n t('staff.time_tracking.myWork.copyDay.done', 'Copied {count} entries from yesterday.', {\n count: String(copied),\n }),\n 'success',\n )\n await load()\n } catch (error) {\n if (isEntryLockedError(error)) {\n flash(\n t(\n 'staff.time_tracking.entries.errors.entryLocked',\n 'This time entry is locked in a closed report and cannot be changed. Unlock the report first.',\n ),\n 'error',\n )\n return\n }\n logger.error('staff.time_tracking.my-work copy day failed', { err: error })\n flash(t('staff.time_tracking.entries.errors.copyDay', 'Could not copy the day.'), 'error')\n }\n }, [data.today, load, mutationContext, runMutation, t])\n\n if (loadState === 'loading') {\n return (\n <Page>\n <PageHeader title={t('staff.time_tracking.nav.my_work', 'My work')} />\n <PageBody>\n <LoadingMessage label={t('staff.timesheets.my.loading', 'Loading timesheets...')} />\n </PageBody>\n </Page>\n )\n }\n\n if (loadState === 'error') {\n return (\n <Page>\n <PageHeader title={t('staff.time_tracking.nav.my_work', 'My work')} />\n <PageBody>\n <ErrorMessage\n label={loadError}\n action={\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={retryLoad}>\n {t('staff.time_tracking.myWork.errors.retry', 'Try again')}\n </Button>\n }\n />\n </PageBody>\n </Page>\n )\n }\n\n const subtitle = [formatLongDayLabel(data.today), data.staffMember?.displayName]\n .filter((part): part is string => Boolean(part))\n .join(' \u00B7 ')\n\n const noAssignments = data.projects.length === 0 && data.entries.length === 0\n\n return (\n <Page>\n <PageHeader\n title={t('staff.time_tracking.nav.my_work', 'My work')}\n description={subtitle}\n actions={\n <div className=\"flex items-center gap-2\">\n <Button variant=\"outline\" asChild>\n <Link href={TIMESHEET_HREF}>\n <CalendarDays className=\"size-4\" aria-hidden=\"true\" />\n {t('staff.time_tracking.nav.timesheet', 'Timesheet')}\n </Link>\n </Button>\n {canManageOwn && !noAssignments ? (\n <Button type=\"button\" onClick={() => setDialog({ open: true, entryId: null })}>\n <Plus className=\"size-4\" aria-hidden=\"true\" />\n {t('staff.time_tracking.entries.actions.add', 'Add entry')}\n </Button>\n ) : null}\n </div>\n }\n />\n <PageBody>\n {noAssignments ? (\n <NoTimeProjectAssignments />\n ) : (\n <div className=\"flex flex-col gap-6\">\n <InjectionSpot\n spotId={extensionPoints.hosts.myWorkBeforeSections.spotId}\n context={myWorkInjectionContext}\n data={data}\n />\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n <KpiCard\n title={t('staff.time_tracking.myWork.kpi.today', 'Today')}\n value={data.kpis.todayMinutes}\n formatValue={(value) => formatDuration(value, 'clock')}\n comparisonLabel={\n data.kpis.dailyTargetMinutes !== null\n ? t('staff.time_tracking.myWork.kpi.targetGap', 'target {target} \u2014 {gap} to go', {\n target: formatDuration(data.kpis.dailyTargetMinutes, 'clock'),\n gap: formatDuration(\n Math.max(0, data.kpis.dailyTargetMinutes - data.kpis.todayMinutes),\n 'clock',\n ),\n })\n : undefined\n }\n />\n <KpiCard\n title={t('staff.time_tracking.myWork.kpi.week', 'This week')}\n value={data.kpis.weekMinutes}\n formatValue={(value) => formatDuration(value, 'clock')}\n comparisonLabel={\n data.kpis.weekTargetMinutes !== null\n ? t('staff.time_tracking.myWork.kpi.ofTarget', 'of {target}', {\n target: formatDuration(data.kpis.weekTargetMinutes, 'clock'),\n })\n : undefined\n }\n />\n <KpiCard\n title={t('staff.time_tracking.myWork.kpi.month', 'This month')}\n value={data.kpis.monthMinutes}\n formatValue={(value) => formatDuration(value, 'clock')}\n comparisonLabel={t('staff.time_tracking.myWork.kpi.workingDays', '{days} working days', {\n days: String(data.kpis.monthWorkingDays),\n })}\n />\n <KpiCard\n title={t('staff.time_tracking.myWork.kpi.nonBillable', 'Non-billable')}\n value={data.kpis.monthNonBillableMinutes}\n formatValue={(value) => formatDuration(value, 'clock')}\n comparisonLabel={\n data.kpis.nonBillableSharePercent !== null\n ? t('staff.time_tracking.myWork.kpi.shareOfMonth', '{percent}% of the month', {\n percent: String(data.kpis.nonBillableSharePercent),\n })\n : undefined\n }\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-3\">\n <section className=\"rounded-lg border xl:col-span-2\">\n <div className=\"flex flex-wrap items-center justify-between gap-2 border-b p-4\">\n <h2 className=\"text-sm font-semibold\">\n {t('staff.time_tracking.myWork.today.title', 'Today\u2019s entries')}\n </h2>\n <div className=\"flex items-center gap-2\">\n {canManageOwn ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => void copyYesterday()}>\n <Copy className=\"size-4\" aria-hidden=\"true\" />\n {t('staff.time_tracking.myWork.today.copyYesterday', 'Copy yesterday')}\n </Button>\n ) : null}\n <Button variant=\"outline\" size=\"sm\" asChild>\n <Link href={ENTRIES_HREF}>\n {t('staff.time_tracking.myWork.today.allEntries', 'All entries')}\n </Link>\n </Button>\n </div>\n </div>\n\n {data.entries.length === 0 ? (\n <p className=\"p-4 text-sm text-muted-foreground\">\n {t('staff.time_tracking.myWork.today.empty', 'Nothing logged today yet.')}\n </p>\n ) : (\n <>\n <DataTable<MyWorkEntry>\n embedded\n extensionTableId={extensionPoints.hosts.myWorkTodayEntriesTable.tableId}\n disableRowClick\n columns={todayEntriesColumns}\n data={data.entries}\n />\n {canManageOwn && quickTargets.length > 0 ? (\n <div\n className=\"flex flex-wrap items-start gap-2 border-t p-3\"\n onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void submitQuickEntry()\n }\n }}\n >\n <Select value={quickTargetKey} onValueChange={setQuickTargetKey}>\n <SelectTrigger\n className=\"w-72\"\n aria-label={t('staff.time_tracking.myWork.quickEntry.target', 'Project or task')}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {quickTargets.map((target) => (\n <SelectItem key={target.key} value={target.key}>\n {targetLabel(target)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <div className=\"w-32\">\n <DurationInput\n key={quickEpoch}\n variant=\"compact\"\n value={quickMinutes}\n onChange={(minutes) => setQuickMinutes(minutes)}\n ariaLabel={t('staff.time_tracking.myWork.quickEntry.duration', 'Duration')}\n />\n </div>\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={quickSaving || quickMinutes === null || quickMinutes <= 0}\n onClick={() => void submitQuickEntry()}\n >\n {t('staff.time_tracking.myWork.quickEntry.save', 'Save')}\n </Button>\n <span className=\"self-center text-xs text-muted-foreground\">\n {t('staff.time_tracking.entryDialog.shortcutSave', '\u2318\u21B5 to save')}\n </span>\n {/*\n * The same block the task drawer offers, from the same\n * component: a row that can log an hour but not say what\n * it was for, or when, sends people to the full dialog\n * for the one field they were missing.\n */}\n <div className=\"w-full\">\n <EntryDetailsFields\n idPrefix=\"my-work-quick\"\n value={quickDetails}\n onChange={setQuickDetails}\n tagOptions={tagOptions}\n onCreateTag={createTag}\n disabled={quickSaving}\n />\n </div>\n </div>\n ) : null}\n <div className=\"flex items-center justify-between border-t bg-muted/40 p-3 text-sm font-medium\">\n <span>{t('staff.time_tracking.myWork.today.total', 'Total today')}</span>\n <span className=\"font-mono tabular-nums\">{formatDuration(todayTotal, 'clock')}</span>\n </div>\n </>\n )}\n </section>\n\n <div className=\"flex flex-col gap-6\">\n <section className=\"rounded-lg border\">\n <div className=\"flex items-center justify-between border-b p-4\">\n <h2 className=\"text-sm font-semibold\">\n {t('staff.time_tracking.myWork.projects.title', 'My projects')}\n </h2>\n <span className=\"text-xs text-muted-foreground\">{data.projects.length}</span>\n </div>\n <div className=\"flex flex-col gap-4 p-4\">\n {data.projects.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('staff.time_tracking.myWork.projects.empty', 'No projects assigned yet.')}\n </p>\n ) : (\n data.projects.map((project) => (\n <div key={project.id} className=\"flex flex-col gap-1\">\n <div className=\"flex items-center justify-between gap-2\">\n <Link\n href={`/backend/staff/time-tracking/projects/${project.id}`}\n className=\"min-w-0 truncate font-medium hover:underline\"\n >\n {project.name}\n </Link>\n <span className=\"shrink-0 font-mono text-xs tabular-nums text-muted-foreground\">\n {formatDuration(project.myMinutes, 'clock')}\n </span>\n </div>\n <p className=\"text-xs text-muted-foreground\">\n {[\n project.customerName,\n project.hourlyRate != null && project.currencyCode\n ? t('staff.time_tracking.myWork.projects.rate', '{rate}/h', {\n rate: formatCurrency(project.hourlyRate, project.currencyCode) ?? '',\n })\n : null,\n ]\n .filter((part): part is string => Boolean(part))\n .join(' \u00B7 ')}\n </p>\n {project.budget ? (\n <>\n <Progress\n value={project.budget.barPercent}\n size=\"sm\"\n tone={\n project.budget.tone === 'destructive'\n ? 'destructive'\n : project.budget.tone === 'warning'\n ? 'warning'\n : 'accent'\n }\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('staff.time_tracking.myWork.projects.budget', '{percent}% of budget ({value})', {\n percent: String(project.budget.percent),\n value:\n project.budget.kind === 'hours'\n ? t('staff.time_tracking.myWork.projects.budgetHours', '{hours} h', {\n hours: String(project.budget.budgetValue),\n })\n : formatCurrency(project.budget.budgetValue, project.currencyCode ?? undefined) ??\n String(project.budget.budgetValue),\n })}\n </p>\n </>\n ) : null}\n </div>\n ))\n )}\n </div>\n </section>\n\n <section className=\"rounded-lg border\">\n <div className=\"border-b p-4\">\n <h2 className=\"text-sm font-semibold\">\n {t('staff.time_tracking.myWork.recentTasks.title', 'Recent tasks')}\n </h2>\n </div>\n <div className=\"flex flex-col gap-2 p-4\">\n {data.recentTasks.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {t('staff.time_tracking.myWork.recentTasks.empty', 'No tasks logged against yet.')}\n </p>\n ) : (\n data.recentTasks.map((task) => (\n <Link\n key={task.id}\n href={`/backend/staff/time-tracking/projects/${task.timeProjectId}/board?task=${task.id}`}\n className=\"flex items-center justify-between gap-2 truncate text-sm hover:underline\"\n >\n <span className=\"truncate\">{task.title}</span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{task.projectName}</span>\n </Link>\n ))\n )}\n </div>\n </section>\n </div>\n </div>\n <InjectionSpot\n spotId={extensionPoints.hosts.myWorkAfterSections.spotId}\n context={myWorkInjectionContext}\n data={data}\n />\n </div>\n )}\n </PageBody>\n\n <TimeEntryDialog\n open={dialog.open}\n onOpenChange={(open) => setDialog((current) => ({ ...current, open }))}\n entryId={dialog.entryId}\n defaults={{ date: data.today }}\n onSaved={() => {\n void load()\n }}\n />\n </Page>\n )\n}\n"],
5
+ "mappings": ";AA6VgB,SAkTE,UAlTF,KAWE,YAXF;AA9ThB,YAAY,WAAW;AACvB,OAAO,UAAU;AAEjB,SAAS,cAAc,MAAM,MAAM,QAAQ,YAAY;AACvD,SAAS,MAAM,UAAU,kBAAkB;AAC3C,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,eAAe;AACxB,SAAS,cAAc,sBAAsB;AAC7C,SAAS,aAAa;AACtB,SAAS,SAAS,4BAA4B;AAC9C,SAAS,kBAAkB;AAC3B,SAAS,0BAA0B;AACnC,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAChC,SAAS,wBAAwB;AACjC,SAAS,mCAAmC;AAC5C,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,YAAY;AACrB,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,wBAAwB;AACjC,SAAS,0BAA0B;AAEnC,SAAS,uBAAuB;AAChC,SAAS,gCAAgC;AACzC,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB,gBAAgB;AAC7C,SAAS,iBAAiB,gBAAgB,mBAAmB;AAC7D,SAAS,0BAA0B;AAEnC,MAAM,SAAS,aAAa,OAAO,EAAE,MAAM,EAAE,WAAW,wBAAwB,CAAC;AAEjF,MAAM,mBAAmB;AACzB,MAAM,mBAAmB;AACzB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,iBAAiB;AACvB,MAAM,eAAe;AAgErB,MAAM,gBAA+B;AAAA,EACnC,aAAa;AAAA,EACb,OAAO,SAAS;AAAA,EAChB,MAAM;AAAA,IACJ,cAAc;AAAA,IACd,aAAa;AAAA,IACb,cAAc;AAAA,IACd,yBAAyB;AAAA,IACzB,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,IACpB,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,yBAAyB;AAAA,EAC3B;AAAA,EACA,SAAS,CAAC;AAAA,EACV,UAAU,CAAC;AAAA,EACX,aAAa,CAAC;AAChB;AAEA,SAAS,iBAAiB,OAAoB,QAAgC;AAC5E,MAAI,CAAC,MAAM,aAAa,CAAC,MAAM,QAAS,QAAO;AAC/C,QAAM,SAAS,CAAC,QAAgB;AAC9B,UAAM,SAAS,IAAI,KAAK,GAAG;AAC3B,QAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,EAAG,QAAO;AAC3C,QAAI;AACF,aAAO,IAAI,KAAK,eAAe,QAAQ,EAAE,MAAM,WAAW,QAAQ,WAAW,QAAQ,MAAM,CAAC,EAAE,OAAO,MAAM;AAAA,IAC7G,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,QAAM,QAAQ,OAAO,MAAM,SAAS;AACpC,QAAM,MAAM,OAAO,MAAM,OAAO;AAChC,SAAO,SAAS,MAAM,GAAG,KAAK,WAAM,GAAG,KAAK;AAC9C;AAEe,SAAR,yBAA0C;AAC/C,QAAM,IAAI,KAAK;AACf,QAAM,eAAe,4BAA4B;AACjD,QAAM,EAAE,SAAS,OAAO,IAAI,iBAAiB;AAC7C,QAAM,eAAe,WAAW,QAAQ,iBAAiB,kBAAkB;AAE3E,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAwB,aAAa;AACnE,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAwC,SAAS;AACzF,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAoD;AAAA,IACpF,MAAM;AAAA,IACN,SAAS;AAAA,EACX,CAAC;AACD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,MAAM,SAAS,EAAE;AAC7D,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAwB,IAAI;AAC1E,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAS,CAAC;AACpD,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAA4B,MAAM,kBAAkB,EAAE,CAAC;AACrG,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAsB,CAAC,CAAC;AAGlE,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,KAAK,MAAO;AACjB,oBAAgB,CAAC,YAAa,QAAQ,OAAO,UAAU,EAAE,GAAG,SAAS,MAAM,KAAK,MAAM,CAAE;AAAA,EAC1F,GAAG,CAAC,KAAK,KAAK,CAAC;AAEf,QAAM,UAAU,MAAM;AACpB,QAAI,YAAY;AAChB,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,MAAM,MAAM,QAAQ;AAAA,UACxB,QAAoD,gDAAgD;AAAA,QACtG;AACA,YAAI,aAAa,CAAC,KAAK,GAAI;AAC3B,cAAM,QAAQ,MAAM,QAAQ,IAAI,QAAQ,KAAK,IAAI,IAAI,OAAO,QAAQ,CAAC;AACrE;AAAA,UACE,MACG,IAAI,CAAC,QAAiC;AACrC,kBAAM,KAAK,OAAO,IAAI,OAAO,WAAW,IAAI,KAAK;AACjD,kBAAM,QAAQ,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ;AAC1D,mBAAO,MAAM,QACT,EAAE,IAAI,OAAO,OAAO,OAAO,IAAI,UAAU,WAAW,IAAI,QAAQ,KAAK,IACrE;AAAA,UACN,CAAC,EACA,OAAO,CAAC,QAA0B,QAAQ,IAAI;AAAA,QACnD;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,GAAG;AACH,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAK;AAAA,EAClC,GAAG,CAAC,CAAC;AAEL,QAAM,YAAY,MAAM;AAAA,IACtB,OAAO,UAA6C;AAClD,UAAI;AACF,cAAM,UAAU,MAAM,WAAW,8BAA8B;AAAA,UAC7D;AAAA,UACA,MAAM,mBAAmB,KAAK,EAAE,YAAY;AAAA,QAC9C,GAAG,EAAE,cAAc,EAAE,oDAAoD,2BAA2B,EAAE,CAAC;AACvG,cAAM,UAAU;AAChB,cAAM,MAAM,SAAS,MAAO,SAAS,QAAgD;AACrF,cAAM,KAAK,OAAO,QAAQ,WAAW,MAAM;AAC3C,YAAI,CAAC,GAAI,QAAO;AAChB,cAAM,MAAiB,EAAE,IAAI,OAAO,OAAO,KAAK;AAChD,sBAAc,CAAC,YAAY,CAAC,GAAG,SAAS,GAAG,CAAC;AAC5C,eAAO;AAAA,MACT,SAAS,OAAO;AACd,eAAO,KAAK,iDAAiD,EAAE,KAAK,MAAM,CAAC;AAC3E,eAAO;AAAA,MACT;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AACA,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,KAAK;AAE1D,QAAM,YAAY,EAAE,0CAA0C,mCAAmC;AAKjG,QAAM,OAAO,MAAM,YAAY,YAAY;AACzC,QAAI;AACF,YAAM,SAAS,MAAM,qBAAoC,kBAAkB,QAAW;AAAA,QACpF,cAAc;AAAA,QACd,UAAU;AAAA,MACZ,CAAC;AACD,cAAQ,EAAE,GAAG,eAAe,GAAG,OAAO,CAAC;AACvC,mBAAa,OAAO;AAAA,IACtB,SAAS,OAAO;AACd,aAAO,MAAM,2CAA2C,EAAE,KAAK,MAAM,CAAC;AACtE,mBAAa,OAAO;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,SAAS,CAAC;AAEd,QAAM,YAAY,MAAM,YAAY,MAAM;AACxC,iBAAa,SAAS;AACtB,SAAK,KAAK;AAAA,EACZ,GAAG,CAAC,IAAI,CAAC;AAET,QAAM,UAAU,MAAM;AACpB,SAAK,KAAK;AAAA,EACZ,GAAG,CAAC,MAAM,YAAY,CAAC;AAEvB,QAAM,EAAE,aAAa,kBAAkB,IAAI,mBAIxC;AAAA,IACD,WAAW;AAAA,IACX,gBAAgB,EAAE,8BAA8B,4BAA4B;AAAA,EAC9E,CAAC;AAED,QAAM,kBAAkB,MAAM;AAAA,IAC5B,OAAO;AAAA,MACL,QAAQ;AAAA,MACR,cAAc;AAAA,MACd;AAAA,IACF;AAAA,IACA,CAAC,iBAAiB;AAAA,EACpB;AAEA,QAAM,yBAAyB,MAAM;AAAA,IACnC,OAAO;AAAA,MACL,eAAe,KAAK,aAAa,MAAM;AAAA,MACvC,OAAO,KAAK;AAAA,MACZ,YAAY,KAAK,SAAS,IAAI,CAAC,YAAY,QAAQ,EAAE;AAAA,MACrD;AAAA,IACF;AAAA,IACA,CAAC,KAAK,UAAU,KAAK,aAAa,IAAI,KAAK,OAAO,iBAAiB;AAAA,EACrE;AAEA,QAAM,eAAe,MAAM;AAAA,IACzB,MACE;AAAA,MACE,KAAK,SAAS,IAAI,CAAC,aAAa;AAAA,QAC9B,IAAI,QAAQ;AAAA,QACZ,MAAM,QAAQ;AAAA,QACd,MAAM,QAAQ;AAAA,QACd,OAAO,QAAQ;AAAA,MACjB,EAAE;AAAA,MACF,KAAK,YAAY,IAAI,CAAC,UAAU;AAAA,QAC9B,IAAI,KAAK;AAAA,QACT,OAAO,KAAK;AAAA,QACZ,eAAe,KAAK;AAAA,MACtB,EAAE;AAAA,IACJ;AAAA,IACF,CAAC,KAAK,UAAU,KAAK,WAAW;AAAA,EAClC;AAEA,QAAM,UAAU,MAAM;AACpB,QAAI,kBAAkB,aAAa,WAAW,EAAG;AACjD,sBAAkB,aAAa,CAAC,EAAE,GAAG;AAAA,EACvC,GAAG,CAAC,gBAAgB,YAAY,CAAC;AAEjC,QAAM,aAAa,KAAK,QAAQ,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,iBAAiB,CAAC;AAErF,QAAM,sBAAsB,MAAM;AAAA,IAChC,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,iDAAiD,MAAM;AAAA,QACjE,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,gBAAM,QAAQ,IAAI;AAClB,iBACE,qBAAC,SACC;AAAA,iCAAC,SAAI,WAAU,qCACb;AAAA,kCAAC,UAAK,WAAU,eACb,gBAAM,aACL,MAAM,eACN,EAAE,4CAA4C,SAAS,GAC3D;AAAA,cACC,MAAM,aAAa,OAClB,oBAAC,eAAY,SAAQ,WAClB,YAAE,kDAAkD,cAAc,GACrE;AAAA,cAED,MAAM,WACL,qBAAC,eAAY,SAAQ,WACnB;AAAA,oCAAC,QAAK,WAAU,UAAS,eAAY,QAAO;AAAA,gBAC3C,EAAE,6CAA6C,QAAQ;AAAA,iBAC1D,IACE;AAAA,eACN;AAAA,YACA,oBAAC,SAAI,WAAU,iCACZ,WAAC,MAAM,aAAa,MAAM,YAAY,MAAM,cAAc,IAAI,EAC5D,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC,EAC9C,KAAK,QAAK,GACf;AAAA,aACF;AAAA,QAEJ;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,iDAAiD,MAAM;AAAA,QACjE,MAAM,EAAE,UAAU,OAAO;AAAA,QACzB,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,UAAK,WAAU,iCAAiC,2BAAiB,IAAI,QAAQ,KAAK,UAAI;AAAA,MAE3F;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,MACN,oBAAC,UAAK,WAAU,oBACb,YAAE,qDAAqD,UAAU,GACpE;AAAA,QAEF,MAAM,EAAE,UAAU,OAAO;AAAA,QACzB,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,UAAK,WAAU,yDACb,yBAAe,IAAI,SAAS,iBAAiB,OAAO,GACvD;AAAA,MAEJ;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,MAAM,EAAE,UAAU,OAAO;AAAA,QACzB,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,gBAAM,QAAQ,IAAI;AAClB,iBAAO,gBAAgB,CAAC,MAAM,WAC5B,oBAAC,SAAI,WAAU,oBACb;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,cAAY,EAAE,2CAA2C,MAAM;AAAA,cAC/D,SAAS,MAAM,UAAU,EAAE,MAAM,MAAM,SAAS,MAAM,GAAG,CAAC;AAAA,cAE1D,8BAAC,UAAO,WAAU,YAAW,eAAY,QAAO;AAAA;AAAA,UAClD,GACF,IACE;AAAA,QACN;AAAA,MACF;AAAA,IACF;AAAA,IACA,CAAC,cAAc,CAAC;AAAA,EAClB;AAEA,QAAM,mBAAmB,MAAM,YAAY,YAAY;AACrD,QAAI,CAAC,kBAAkB,iBAAiB,QAAQ,gBAAgB,KAAK,YAAa;AAClF,UAAM,SAAS,eAAe,cAAc;AAC5C,UAAM,OAAO;AAAA,MACX,eAAe,KAAK,aAAa;AAAA,MACjC,MAAM,aAAa,QAAQ,KAAK;AAAA,MAChC,eAAe,OAAO;AAAA,MACtB,QAAQ,OAAO;AAAA,MACf,iBAAiB;AAAA,MACjB,aAAa,aAAa,YAAY,KAAK,KAAK;AAAA,MAChD,YAAY,aAAa;AAAA,MACzB,QAAQ,aAAa,OAAO,SAAS,IAAI,aAAa,SAAS;AAAA;AAAA;AAAA,MAG/D,GAAG,iBAAiB;AAAA,QAClB,MAAM,aAAa,QAAQ,KAAK;AAAA,QAChC,YAAY,aAAa,cAAc;AAAA,QACvC,UAAU,aAAa,YAAY;AAAA,QACnC,SAAS;AAAA,MACX,CAAC;AAAA,MACD,QAAQ;AAAA,IACV;AACA,mBAAe,IAAI;AACnB,QAAI;AACF,YAAM,YAAY;AAAA,QAChB,WAAW,MACT,WAAW,kBAAkB,MAAM;AAAA,UACjC,cAAc,EAAE,mCAAmC,4BAA4B;AAAA,QACjF,CAAC;AAAA,QACH,SAAS;AAAA,QACT,iBAAiB;AAAA,MACnB,CAAC;AACD,sBAAgB,IAAI;AACpB,oBAAc,CAAC,UAAU,QAAQ,CAAC;AAGlC,sBAAgB,CAAC,aAAa;AAAA,QAC5B,GAAG,kBAAkB,QAAQ,MAAM,QAAQ,UAAU;AAAA,MACvD,EAAE;AACF,YAAM,KAAK;AAAA,IACb,SAAS,OAAO;AACd,aAAO,MAAM,kDAAkD,EAAE,KAAK,MAAM,CAAC;AAC7E,YAAM,EAAE,mCAAmC,4BAA4B,GAAG,OAAO;AAAA,IACnF,UAAE;AACA,qBAAe,KAAK;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,KAAK,aAAa,IAAI,KAAK,OAAO,MAAM,iBAAiB,cAAc,cAAc,aAAa,gBAAgB,aAAa,CAAC,CAAC;AAErI,QAAM,gBAAgB,MAAM,YAAY,YAAY;AAClD,UAAM,QAAQ,oBAAI,KAAK,GAAG,KAAK,KAAK,gBAAgB;AACpD,UAAM,YAAY,IAAI,KAAK,KAAK;AAChC,cAAU,WAAW,UAAU,WAAW,IAAI,CAAC;AAC/C,UAAM,OAAO;AAAA,MACX,UAAU,UAAU,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,MAC7C,QAAQ,KAAK;AAAA,IACf;AACA,QAAI;AACF,YAAM,SAAS,MAAM,YAAY;AAAA,QAC/B,WAAW,MACT,WAAqD,GAAG,gBAAgB,aAAa,MAAM;AAAA,UACzF,cAAc,EAAE,8CAA8C,yBAAyB;AAAA,QACzF,CAAC;AAAA,QACH,SAAS;AAAA,QACT,iBAAiB;AAAA,MACnB,CAAC;AACD,YAAM,SAAU,QAAQ,QAA4C,UAAU;AAC9E;AAAA,QACE,EAAE,2CAA2C,0CAA0C;AAAA,UACrF,OAAO,OAAO,MAAM;AAAA,QACtB,CAAC;AAAA,QACD;AAAA,MACF;AACA,YAAM,KAAK;AAAA,IACb,SAAS,OAAO;AACd,UAAI,mBAAmB,KAAK,GAAG;AAC7B;AAAA,UACE;AAAA,YACE;AAAA,YACA;AAAA,UACF;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF;AACA,aAAO,MAAM,+CAA+C,EAAE,KAAK,MAAM,CAAC;AAC1E,YAAM,EAAE,8CAA8C,yBAAyB,GAAG,OAAO;AAAA,IAC3F;AAAA,EACF,GAAG,CAAC,KAAK,OAAO,MAAM,iBAAiB,aAAa,CAAC,CAAC;AAEtD,MAAI,cAAc,WAAW;AAC3B,WACE,qBAAC,QACC;AAAA,0BAAC,cAAW,OAAO,EAAE,mCAAmC,SAAS,GAAG;AAAA,MACpE,oBAAC,YACC,8BAAC,kBAAe,OAAO,EAAE,+BAA+B,uBAAuB,GAAG,GACpF;AAAA,OACF;AAAA,EAEJ;AAEA,MAAI,cAAc,SAAS;AACzB,WACE,qBAAC,QACC;AAAA,0BAAC,cAAW,OAAO,EAAE,mCAAmC,SAAS,GAAG;AAAA,MACpE,oBAAC,YACC;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,QACE,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,WACxD,YAAE,2CAA2C,WAAW,GAC3D;AAAA;AAAA,MAEJ,GACF;AAAA,OACF;AAAA,EAEJ;AAEA,QAAM,WAAW,CAAC,mBAAmB,KAAK,KAAK,GAAG,KAAK,aAAa,WAAW,EAC5E,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC,EAC9C,KAAK,QAAK;AAEb,QAAM,gBAAgB,KAAK,SAAS,WAAW,KAAK,KAAK,QAAQ,WAAW;AAE5E,SACE,qBAAC,QACC;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,mCAAmC,SAAS;AAAA,QACrD,aAAa;AAAA,QACb,SACE,qBAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,UAAO,SAAQ,WAAU,SAAO,MAC/B,+BAAC,QAAK,MAAM,gBACV;AAAA,gCAAC,gBAAa,WAAU,UAAS,eAAY,QAAO;AAAA,YACnD,EAAE,qCAAqC,WAAW;AAAA,aACrD,GACF;AAAA,UACC,gBAAgB,CAAC,gBAChB,qBAAC,UAAO,MAAK,UAAS,SAAS,MAAM,UAAU,EAAE,MAAM,MAAM,SAAS,KAAK,CAAC,GAC1E;AAAA,gCAAC,QAAK,WAAU,UAAS,eAAY,QAAO;AAAA,YAC3C,EAAE,2CAA2C,WAAW;AAAA,aAC3D,IACE;AAAA,WACN;AAAA;AAAA,IAEJ;AAAA,IACA,oBAAC,YACE,0BACC,oBAAC,4BAAyB,IAE1B,qBAAC,SAAI,WAAU,uBACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ,gBAAgB,MAAM,qBAAqB;AAAA,UACnD,SAAS;AAAA,UACT;AAAA;AAAA,MACF;AAAA,MACA,qBAAC,SAAI,WAAU,wDACb;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,EAAE,wCAAwC,OAAO;AAAA,YACxD,OAAO,KAAK,KAAK;AAAA,YACjB,aAAa,CAAC,UAAU,eAAe,OAAO,OAAO;AAAA,YACrD,iBACE,KAAK,KAAK,uBAAuB,OAC7B,EAAE,4CAA4C,sCAAiC;AAAA,cAC7E,QAAQ,eAAe,KAAK,KAAK,oBAAoB,OAAO;AAAA,cAC5D,KAAK;AAAA,gBACH,KAAK,IAAI,GAAG,KAAK,KAAK,qBAAqB,KAAK,KAAK,YAAY;AAAA,gBACjE;AAAA,cACF;AAAA,YACF,CAAC,IACD;AAAA;AAAA,QAER;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,EAAE,uCAAuC,WAAW;AAAA,YAC3D,OAAO,KAAK,KAAK;AAAA,YACjB,aAAa,CAAC,UAAU,eAAe,OAAO,OAAO;AAAA,YACrD,iBACE,KAAK,KAAK,sBAAsB,OAC5B,EAAE,2CAA2C,eAAe;AAAA,cAC1D,QAAQ,eAAe,KAAK,KAAK,mBAAmB,OAAO;AAAA,YAC7D,CAAC,IACD;AAAA;AAAA,QAER;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,EAAE,wCAAwC,YAAY;AAAA,YAC7D,OAAO,KAAK,KAAK;AAAA,YACjB,aAAa,CAAC,UAAU,eAAe,OAAO,OAAO;AAAA,YACrD,iBAAiB,EAAE,8CAA8C,uBAAuB;AAAA,cACtF,MAAM,OAAO,KAAK,KAAK,gBAAgB;AAAA,YACzC,CAAC;AAAA;AAAA,QACH;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,EAAE,8CAA8C,cAAc;AAAA,YACrE,OAAO,KAAK,KAAK;AAAA,YACjB,aAAa,CAAC,UAAU,eAAe,OAAO,OAAO;AAAA,YACrD,iBACE,KAAK,KAAK,4BAA4B,OAClC,EAAE,+CAA+C,2BAA2B;AAAA,cAC1E,SAAS,OAAO,KAAK,KAAK,uBAAuB;AAAA,YACnD,CAAC,IACD;AAAA;AAAA,QAER;AAAA,SACF;AAAA,MAEA,qBAAC,SAAI,WAAU,yCACb;AAAA,6BAAC,aAAQ,WAAU,mCACjB;AAAA,+BAAC,SAAI,WAAU,kEACb;AAAA,gCAAC,QAAG,WAAU,yBACX,YAAE,0CAA0C,sBAAiB,GAChE;AAAA,YACA,qBAAC,SAAI,WAAU,2BACZ;AAAA,6BACC,qBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,MAAM,KAAK,cAAc,GAClF;AAAA,oCAAC,QAAK,WAAU,UAAS,eAAY,QAAO;AAAA,gBAC3C,EAAE,kDAAkD,gBAAgB;AAAA,iBACvE,IACE;AAAA,cACJ,oBAAC,UAAO,SAAQ,WAAU,MAAK,MAAK,SAAO,MACzC,8BAAC,QAAK,MAAM,cACT,YAAE,+CAA+C,aAAa,GACjE,GACF;AAAA,eACF;AAAA,aACF;AAAA,UAEC,KAAK,QAAQ,WAAW,IACvB,oBAAC,OAAE,WAAU,qCACV,YAAE,0CAA0C,2BAA2B,GAC1E,IAEA,iCACE;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,UAAQ;AAAA,gBACR,kBAAkB,gBAAgB,MAAM,wBAAwB;AAAA,gBAChE,iBAAe;AAAA,gBACf,SAAS;AAAA,gBACT,MAAM,KAAK;AAAA;AAAA,YACb;AAAA,YACC,gBAAgB,aAAa,SAAS,IACrC;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,WAAW,CAAC,UAAU;AACpB,uBAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,SAAS;AAC7D,0BAAM,eAAe;AACrB,yBAAK,iBAAiB;AAAA,kBACxB;AAAA,gBACF;AAAA,gBAEA;AAAA,uCAAC,UAAO,OAAO,gBAAgB,eAAe,mBAC5C;AAAA;AAAA,sBAAC;AAAA;AAAA,wBACC,WAAU;AAAA,wBACV,cAAY,EAAE,gDAAgD,iBAAiB;AAAA,wBAE/E,8BAAC,eAAY;AAAA;AAAA,oBACf;AAAA,oBACA,oBAAC,iBACE,uBAAa,IAAI,CAAC,WACjB,oBAAC,cAA4B,OAAO,OAAO,KACxC,sBAAY,MAAM,KADJ,OAAO,GAExB,CACD,GACH;AAAA,qBACF;AAAA,kBACA,oBAAC,SAAI,WAAU,QACb;AAAA,oBAAC;AAAA;AAAA,sBAEC,SAAQ;AAAA,sBACR,OAAO;AAAA,sBACP,UAAU,CAAC,YAAY,gBAAgB,OAAO;AAAA,sBAC9C,WAAW,EAAE,kDAAkD,UAAU;AAAA;AAAA,oBAJpE;AAAA,kBAKP,GACF;AAAA,kBACA;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,MAAK;AAAA,sBACL,UAAU,eAAe,iBAAiB,QAAQ,gBAAgB;AAAA,sBAClE,SAAS,MAAM,KAAK,iBAAiB;AAAA,sBAEpC,YAAE,8CAA8C,MAAM;AAAA;AAAA,kBACzD;AAAA,kBACA,oBAAC,UAAK,WAAU,6CACb,YAAE,gDAAgD,sBAAY,GACjE;AAAA,kBAOA,oBAAC,SAAI,WAAU,UACb;AAAA,oBAAC;AAAA;AAAA,sBACC,UAAS;AAAA,sBACT,OAAO;AAAA,sBACP,UAAU;AAAA,sBACV;AAAA,sBACA,aAAa;AAAA,sBACb,UAAU;AAAA;AAAA,kBACZ,GACF;AAAA;AAAA;AAAA,YACF,IACE;AAAA,YACJ,qBAAC,SAAI,WAAU,kFACb;AAAA,kCAAC,UAAM,YAAE,0CAA0C,aAAa,GAAE;AAAA,cAClE,oBAAC,UAAK,WAAU,0BAA0B,yBAAe,YAAY,OAAO,GAAE;AAAA,eAChF;AAAA,aACF;AAAA,WAEJ;AAAA,QAEA,qBAAC,SAAI,WAAU,uBACb;AAAA,+BAAC,aAAQ,WAAU,qBACjB;AAAA,iCAAC,SAAI,WAAU,kDACb;AAAA,kCAAC,QAAG,WAAU,yBACX,YAAE,6CAA6C,aAAa,GAC/D;AAAA,cACA,oBAAC,UAAK,WAAU,iCAAiC,eAAK,SAAS,QAAO;AAAA,eACxE;AAAA,YACA,oBAAC,SAAI,WAAU,2BACZ,eAAK,SAAS,WAAW,IACxB,oBAAC,OAAE,WAAU,iCACV,YAAE,6CAA6C,2BAA2B,GAC7E,IAEA,KAAK,SAAS,IAAI,CAAC,YACjB,qBAAC,SAAqB,WAAU,uBAC9B;AAAA,mCAAC,SAAI,WAAU,2CACb;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAM,yCAAyC,QAAQ,EAAE;AAAA,oBACzD,WAAU;AAAA,oBAET,kBAAQ;AAAA;AAAA,gBACX;AAAA,gBACA,oBAAC,UAAK,WAAU,iEACb,yBAAe,QAAQ,WAAW,OAAO,GAC5C;AAAA,iBACF;AAAA,cACA,oBAAC,OAAE,WAAU,iCACV;AAAA,gBACC,QAAQ;AAAA,gBACR,QAAQ,cAAc,QAAQ,QAAQ,eAClC,EAAE,4CAA4C,YAAY;AAAA,kBACxD,MAAM,eAAe,QAAQ,YAAY,QAAQ,YAAY,KAAK;AAAA,gBACpE,CAAC,IACD;AAAA,cACN,EACG,OAAO,CAAC,SAAyB,QAAQ,IAAI,CAAC,EAC9C,KAAK,QAAK,GACf;AAAA,cACC,QAAQ,SACP,iCACE;AAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,OAAO,QAAQ,OAAO;AAAA,oBACtB,MAAK;AAAA,oBACL,MACE,QAAQ,OAAO,SAAS,gBACpB,gBACA,QAAQ,OAAO,SAAS,YACtB,YACA;AAAA;AAAA,gBAEV;AAAA,gBACA,oBAAC,OAAE,WAAU,iCACV,YAAE,8CAA8C,kCAAkC;AAAA,kBACjF,SAAS,OAAO,QAAQ,OAAO,OAAO;AAAA,kBACtC,OACE,QAAQ,OAAO,SAAS,UACpB,EAAE,mDAAmD,aAAa;AAAA,oBAChE,OAAO,OAAO,QAAQ,OAAO,WAAW;AAAA,kBAC1C,CAAC,IACD,eAAe,QAAQ,OAAO,aAAa,QAAQ,gBAAgB,MAAS,KAC5E,OAAO,QAAQ,OAAO,WAAW;AAAA,gBACzC,CAAC,GACH;AAAA,iBACF,IACE;AAAA,iBAlDI,QAAQ,EAmDlB,CACD,GAEL;AAAA,aACF;AAAA,UAEA,qBAAC,aAAQ,WAAU,qBACjB;AAAA,gCAAC,SAAI,WAAU,gBACb,8BAAC,QAAG,WAAU,yBACX,YAAE,gDAAgD,cAAc,GACnE,GACF;AAAA,YACA,oBAAC,SAAI,WAAU,2BACZ,eAAK,YAAY,WAAW,IAC3B,oBAAC,OAAE,WAAU,iCACV,YAAE,gDAAgD,8BAA8B,GACnF,IAEA,KAAK,YAAY,IAAI,CAAC,SACpB;AAAA,cAAC;AAAA;AAAA,gBAEC,MAAM,yCAAyC,KAAK,aAAa,eAAe,KAAK,EAAE;AAAA,gBACvF,WAAU;AAAA,gBAEV;AAAA,sCAAC,UAAK,WAAU,YAAY,eAAK,OAAM;AAAA,kBACvC,oBAAC,UAAK,WAAU,0CAA0C,eAAK,aAAY;AAAA;AAAA;AAAA,cALtE,KAAK;AAAA,YAMZ,CACD,GAEL;AAAA,aACF;AAAA,WACF;AAAA,SACF;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,QAAQ,gBAAgB,MAAM,oBAAoB;AAAA,UAClD,SAAS;AAAA,UACT;AAAA;AAAA,MACF;AAAA,OACF,GAEJ;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,OAAO;AAAA,QACb,cAAc,CAAC,SAAS,UAAU,CAAC,aAAa,EAAE,GAAG,SAAS,KAAK,EAAE;AAAA,QACrE,SAAS,OAAO;AAAA,QAChB,UAAU,EAAE,MAAM,KAAK,MAAM;AAAA,QAC7B,SAAS,MAAM;AACb,eAAK,KAAK;AAAA,QACZ;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -2,6 +2,7 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Page, PageBody } from "@open-mercato/ui/backend/Page";
5
+ import { DataTable } from "@open-mercato/ui/backend/DataTable";
5
6
  import { Button } from "@open-mercato/ui/primitives/button";
6
7
  import { Badge } from "@open-mercato/ui/primitives/badge";
7
8
  import { Spinner } from "@open-mercato/ui/primitives/spinner";
@@ -196,6 +197,34 @@ function TimesheetProjectDetailPage({ params }) {
196
197
  );
197
198
  const activeCount = employees.filter((emp) => emp.status === "active").length;
198
199
  const inactiveCount = employees.length - activeCount;
200
+ const recentEntriesColumns = React.useMemo(
201
+ () => [
202
+ {
203
+ accessorKey: "date",
204
+ header: t("staff.timesheets.projects.detail.colDate", "Date"),
205
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: row.original.date })
206
+ },
207
+ {
208
+ id: "person",
209
+ header: t("staff.timesheets.projects.detail.colWho", "Person"),
210
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "whitespace-nowrap", children: employeeNameById.get(row.original.staffMemberId ?? "") ?? "\u2014" })
211
+ },
212
+ {
213
+ accessorKey: "description",
214
+ header: t("staff.timesheets.projects.detail.colDescription", "Description"),
215
+ cell: ({ row }) => /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
216
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: row.original.description ?? "\u2014" }),
217
+ row.original.isBillable ? null : /* @__PURE__ */ jsx(Badge, { variant: "neutral", children: t("staff.timesheets.projects.detail.nonBillableTag", "non-billable") })
218
+ ] })
219
+ },
220
+ {
221
+ id: "hours",
222
+ header: () => /* @__PURE__ */ jsx("span", { className: "block text-right", children: t("staff.timesheets.projects.detail.colHours", "Hours") }),
223
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "block whitespace-nowrap text-right font-mono tabular-nums", children: formatHours(row.original.durationMinutes) })
224
+ }
225
+ ],
226
+ [employeeNameById, t]
227
+ );
199
228
  React.useEffect(() => {
200
229
  if (!projectId) return;
201
230
  let cancelled = false;
@@ -779,23 +808,15 @@ function TimesheetProjectDetailPage({ params }) {
779
808
  /* @__PURE__ */ jsx(TabsContent, { value: "time", children: recentEntriesLoading ? /* @__PURE__ */ jsx(LoadingMessage, { label: t("staff.timesheets.projects.detail.loadingTime", "Loading time entries\u2026") }) : recentEntries.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed p-8 text-center", children: [
780
809
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("staff.timesheets.projects.detail.noTime", "No time logged yet") }),
781
810
  /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t("staff.timesheets.projects.detail.noTimeHint", "Hours logged against this project appear here.") })
782
- ] }) : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
783
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/40 text-left", children: [
784
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-muted-foreground", children: t("staff.timesheets.projects.detail.colDate", "Date") }),
785
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-muted-foreground", children: t("staff.timesheets.projects.detail.colWho", "Person") }),
786
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-muted-foreground", children: t("staff.timesheets.projects.detail.colDescription", "Description") }),
787
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-right font-medium text-muted-foreground", children: t("staff.timesheets.projects.detail.colHours", "Hours") })
788
- ] }) }),
789
- /* @__PURE__ */ jsx("tbody", { children: recentEntries.map((entry) => /* @__PURE__ */ jsxs("tr", { className: "border-b last:border-b-0", children: [
790
- /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-3 py-2", children: entry.date }),
791
- /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-3 py-2", children: employeeNameById.get(entry.staffMemberId ?? "") ?? "\u2014" }),
792
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
793
- /* @__PURE__ */ jsx("span", { className: "truncate", children: entry.description ?? "\u2014" }),
794
- entry.isBillable ? null : /* @__PURE__ */ jsx(Badge, { variant: "neutral", children: t("staff.timesheets.projects.detail.nonBillableTag", "non-billable") })
795
- ] }) }),
796
- /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-3 py-2 text-right font-mono tabular-nums", children: formatHours(entry.durationMinutes) })
797
- ] }, entry.id)) })
798
- ] }) }) }),
811
+ ] }) : /* @__PURE__ */ jsx(
812
+ DataTable,
813
+ {
814
+ extensionTableId: extensionPoints.hosts.projectDetailTimeEntriesTable.tableId,
815
+ disableRowClick: true,
816
+ columns: recentEntriesColumns,
817
+ data: recentEntries
818
+ }
819
+ ) }),
799
820
  /* @__PURE__ */ jsx(TabsContent, { value: "tasks", children: projectTasksLoading ? /* @__PURE__ */ jsx(LoadingMessage, { label: t("staff.timesheets.projects.detail.loadingTasks", "Loading tasks\u2026") }) : projectTasks.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-dashed p-8 text-center", children: /* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("staff.timesheets.projects.detail.noTasks", "No tasks yet") }) }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded-lg border", children: projectTasks.map((task) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2 text-sm", children: [
800
821
  task.reference ? /* @__PURE__ */ jsx("span", { className: "shrink-0 rounded-sm border border-border bg-muted px-1.5 py-0.5 font-mono text-xs", children: task.reference }) : null,
801
822
  /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: task.title }),