@open-mercato/core 0.6.4-develop.4210.1.d412061cfe → 0.6.4-develop.4236.1.9fa6806b34

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 (370) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities/staff_time_entry/index.js +37 -0
  3. package/dist/generated/entities/staff_time_entry/index.js.map +7 -0
  4. package/dist/generated/entities/staff_time_entry_segment/index.js +23 -0
  5. package/dist/generated/entities/staff_time_entry_segment/index.js.map +7 -0
  6. package/dist/generated/entities/staff_time_project/index.js +35 -0
  7. package/dist/generated/entities/staff_time_project/index.js.map +7 -0
  8. package/dist/generated/entities/staff_time_project_member/index.js +29 -0
  9. package/dist/generated/entities/staff_time_project_member/index.js.map +7 -0
  10. package/dist/generated/entities.ids.generated.js +5 -1
  11. package/dist/generated/entities.ids.generated.js.map +2 -2
  12. package/dist/generated/entity-fields-registry.js +64 -0
  13. package/dist/generated/entity-fields-registry.js.map +2 -2
  14. package/dist/helpers/integration/timesheetFixtures.js +50 -0
  15. package/dist/helpers/integration/timesheetFixtures.js.map +7 -0
  16. package/dist/modules/attachments/api/library/[id]/route.js +20 -16
  17. package/dist/modules/attachments/api/library/[id]/route.js.map +2 -2
  18. package/dist/modules/attachments/api/route.js +18 -14
  19. package/dist/modules/attachments/api/route.js.map +2 -2
  20. package/dist/modules/auth/api/roles/acl/route.js +10 -4
  21. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  22. package/dist/modules/auth/api/sidebar/preferences/route.js +27 -20
  23. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  24. package/dist/modules/auth/api/users/acl/route.js +16 -11
  25. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  26. package/dist/modules/auth/commands/users.js +87 -71
  27. package/dist/modules/auth/commands/users.js.map +2 -2
  28. package/dist/modules/auth/services/sidebarPreferencesService.js +39 -30
  29. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  30. package/dist/modules/catalog/commands/categories.js +61 -12
  31. package/dist/modules/catalog/commands/categories.js.map +2 -2
  32. package/dist/modules/catalog/commands/products.js +79 -54
  33. package/dist/modules/catalog/commands/products.js.map +2 -2
  34. package/dist/modules/catalog/commands/variants.js +29 -16
  35. package/dist/modules/catalog/commands/variants.js.map +2 -2
  36. package/dist/modules/currencies/commands/currencies.js +15 -8
  37. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  38. package/dist/modules/customer_accounts/api/admin/users.js +27 -26
  39. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  40. package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
  41. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
  42. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
  43. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  44. package/dist/modules/customers/commands/addresses.js +35 -21
  45. package/dist/modules/customers/commands/addresses.js.map +2 -2
  46. package/dist/modules/customers/commands/companies.js +163 -162
  47. package/dist/modules/customers/commands/companies.js.map +2 -2
  48. package/dist/modules/customers/commands/deals.js +3 -4
  49. package/dist/modules/customers/commands/deals.js.map +2 -2
  50. package/dist/modules/customers/commands/interactions.js +19 -22
  51. package/dist/modules/customers/commands/interactions.js.map +2 -2
  52. package/dist/modules/customers/commands/people.js +18 -15
  53. package/dist/modules/customers/commands/people.js.map +2 -2
  54. package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
  55. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  56. package/dist/modules/customers/commands/pipeline-stages.js +30 -23
  57. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  58. package/dist/modules/customers/commands/pipelines.js +27 -20
  59. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  60. package/dist/modules/customers/commands/tags.js +13 -5
  61. package/dist/modules/customers/commands/tags.js.map +2 -2
  62. package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
  63. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  64. package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
  65. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  66. package/dist/modules/data_sync/lib/sync-engine.js +4 -4
  67. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  68. package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
  69. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  70. package/dist/modules/directory/commands/organizations.js +192 -158
  71. package/dist/modules/directory/commands/organizations.js.map +3 -3
  72. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
  73. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
  74. package/dist/modules/messages/commands/messages.js +77 -75
  75. package/dist/modules/messages/commands/messages.js.map +2 -2
  76. package/dist/modules/messages/commands/shared.js +132 -132
  77. package/dist/modules/messages/commands/shared.js.map +2 -2
  78. package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
  79. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  80. package/dist/modules/resources/commands/resources.js +125 -117
  81. package/dist/modules/resources/commands/resources.js.map +2 -2
  82. package/dist/modules/resources/commands/tags.js +7 -3
  83. package/dist/modules/resources/commands/tags.js.map +2 -2
  84. package/dist/modules/sales/api/quotes/send/route.js +12 -11
  85. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  86. package/dist/modules/sales/commands/documents.js +629 -478
  87. package/dist/modules/sales/commands/documents.js.map +2 -2
  88. package/dist/modules/sales/commands/payments.js +146 -146
  89. package/dist/modules/sales/commands/payments.js.map +2 -2
  90. package/dist/modules/sales/commands/returns.js +68 -60
  91. package/dist/modules/sales/commands/returns.js.map +2 -2
  92. package/dist/modules/staff/acl.js +10 -1
  93. package/dist/modules/staff/acl.js.map +2 -2
  94. package/dist/modules/staff/analytics.js +33 -0
  95. package/dist/modules/staff/analytics.js.map +7 -0
  96. package/dist/modules/staff/api/guards.js +31 -0
  97. package/dist/modules/staff/api/guards.js.map +7 -0
  98. package/dist/modules/staff/api/interceptors.js +96 -0
  99. package/dist/modules/staff/api/interceptors.js.map +7 -0
  100. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
  101. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
  102. package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
  103. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
  104. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
  105. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
  106. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
  107. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
  108. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
  109. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
  110. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
  111. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
  112. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
  113. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
  115. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
  117. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
  119. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
  121. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
  122. package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
  123. package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
  124. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
  125. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
  126. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
  127. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
  128. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
  129. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
  130. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
  131. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
  132. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
  133. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
  134. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
  135. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
  136. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
  137. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
  138. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
  139. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
  140. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
  141. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
  142. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
  143. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
  144. package/dist/modules/staff/cli.js +38 -1
  145. package/dist/modules/staff/cli.js.map +2 -2
  146. package/dist/modules/staff/commands/index.js +2 -0
  147. package/dist/modules/staff/commands/index.js.map +2 -2
  148. package/dist/modules/staff/commands/leave-requests.js +30 -28
  149. package/dist/modules/staff/commands/leave-requests.js.map +3 -3
  150. package/dist/modules/staff/commands/team-members.js +21 -20
  151. package/dist/modules/staff/commands/team-members.js.map +2 -2
  152. package/dist/modules/staff/commands/timesheets-entries.js +409 -0
  153. package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
  154. package/dist/modules/staff/commands/timesheets-projects.js +618 -0
  155. package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
  156. package/dist/modules/staff/data/enrichers.js +104 -0
  157. package/dist/modules/staff/data/enrichers.js.map +7 -0
  158. package/dist/modules/staff/data/entities.js +226 -1
  159. package/dist/modules/staff/data/entities.js.map +2 -2
  160. package/dist/modules/staff/data/validators.js +113 -1
  161. package/dist/modules/staff/data/validators.js.map +2 -2
  162. package/dist/modules/staff/events.js +13 -1
  163. package/dist/modules/staff/events.js.map +2 -2
  164. package/dist/modules/staff/lib/crud.js +7 -1
  165. package/dist/modules/staff/lib/crud.js.map +2 -2
  166. package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
  167. package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
  168. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
  169. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
  170. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
  171. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
  172. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
  173. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
  174. package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
  175. package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
  176. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
  177. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
  178. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
  179. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
  180. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
  181. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
  182. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
  183. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
  184. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
  185. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
  186. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
  187. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
  188. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
  189. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
  190. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
  191. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
  192. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
  193. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
  194. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
  195. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
  196. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
  197. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
  198. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
  199. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
  200. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
  201. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
  202. package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
  203. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
  204. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
  205. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
  206. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
  207. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
  208. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
  209. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
  210. package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
  211. package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
  212. package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
  213. package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
  214. package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
  215. package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
  216. package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
  217. package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
  218. package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
  219. package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
  220. package/dist/modules/staff/search.js +35 -0
  221. package/dist/modules/staff/search.js.map +2 -2
  222. package/dist/modules/staff/setup.js +15 -1
  223. package/dist/modules/staff/setup.js.map +2 -2
  224. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
  225. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
  226. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
  227. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
  228. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
  229. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
  230. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
  231. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
  232. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
  233. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
  234. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
  235. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
  236. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
  237. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
  238. package/dist/modules/staff/widgets/injection-table.js +12 -0
  239. package/dist/modules/staff/widgets/injection-table.js.map +7 -0
  240. package/dist/modules/sync_excel/api/import/route.js +19 -17
  241. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  242. package/dist/modules/translations/commands/translations.js +22 -19
  243. package/dist/modules/translations/commands/translations.js.map +2 -2
  244. package/generated/entities/staff_time_entry/index.ts +17 -0
  245. package/generated/entities/staff_time_entry_segment/index.ts +10 -0
  246. package/generated/entities/staff_time_project/index.ts +16 -0
  247. package/generated/entities/staff_time_project_member/index.ts +13 -0
  248. package/generated/entities.ids.generated.ts +5 -1
  249. package/generated/entity-fields-registry.ts +64 -0
  250. package/package.json +7 -7
  251. package/src/helpers/integration/timesheetFixtures.ts +61 -0
  252. package/src/modules/attachments/api/library/[id]/route.ts +24 -17
  253. package/src/modules/attachments/api/route.ts +20 -14
  254. package/src/modules/auth/api/roles/acl/route.ts +11 -5
  255. package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
  256. package/src/modules/auth/api/users/acl/route.ts +17 -12
  257. package/src/modules/auth/commands/users.ts +96 -80
  258. package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
  259. package/src/modules/catalog/commands/categories.ts +61 -12
  260. package/src/modules/catalog/commands/products.ts +93 -60
  261. package/src/modules/catalog/commands/variants.ts +29 -16
  262. package/src/modules/currencies/commands/currencies.ts +27 -14
  263. package/src/modules/customer_accounts/api/admin/users.ts +31 -26
  264. package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
  265. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
  266. package/src/modules/customers/commands/addresses.ts +35 -23
  267. package/src/modules/customers/commands/companies.ts +166 -165
  268. package/src/modules/customers/commands/deals.ts +2 -4
  269. package/src/modules/customers/commands/interactions.ts +20 -26
  270. package/src/modules/customers/commands/people.ts +18 -15
  271. package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
  272. package/src/modules/customers/commands/pipeline-stages.ts +31 -27
  273. package/src/modules/customers/commands/pipelines.ts +29 -23
  274. package/src/modules/customers/commands/tags.ts +13 -5
  275. package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
  276. package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
  277. package/src/modules/data_sync/lib/sync-engine.ts +4 -5
  278. package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
  279. package/src/modules/directory/commands/organizations.ts +203 -166
  280. package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
  281. package/src/modules/messages/commands/messages.ts +82 -80
  282. package/src/modules/messages/commands/shared.ts +138 -133
  283. package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
  284. package/src/modules/resources/commands/resources.ts +127 -117
  285. package/src/modules/resources/commands/tags.ts +7 -3
  286. package/src/modules/sales/api/quotes/send/route.ts +17 -12
  287. package/src/modules/sales/commands/documents.ts +673 -481
  288. package/src/modules/sales/commands/payments.ts +158 -152
  289. package/src/modules/sales/commands/returns.ts +74 -63
  290. package/src/modules/staff/acl.ts +11 -0
  291. package/src/modules/staff/analytics.ts +30 -0
  292. package/src/modules/staff/api/guards.ts +59 -0
  293. package/src/modules/staff/api/interceptors.ts +122 -0
  294. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
  295. package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
  296. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
  297. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
  298. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
  299. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
  300. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
  301. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
  302. package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
  303. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
  304. package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
  305. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
  306. package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
  307. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
  308. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
  309. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
  310. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
  311. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
  312. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
  313. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
  314. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
  315. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
  316. package/src/modules/staff/cli.ts +40 -1
  317. package/src/modules/staff/commands/index.ts +2 -0
  318. package/src/modules/staff/commands/leave-requests.ts +37 -29
  319. package/src/modules/staff/commands/team-members.ts +25 -20
  320. package/src/modules/staff/commands/timesheets-entries.ts +504 -0
  321. package/src/modules/staff/commands/timesheets-projects.ts +699 -0
  322. package/src/modules/staff/data/enrichers.ts +134 -0
  323. package/src/modules/staff/data/entities.ts +198 -0
  324. package/src/modules/staff/data/validators.ts +129 -0
  325. package/src/modules/staff/events.ts +13 -0
  326. package/src/modules/staff/i18n/de.json +209 -1
  327. package/src/modules/staff/i18n/en.json +209 -1
  328. package/src/modules/staff/i18n/es.json +209 -1
  329. package/src/modules/staff/i18n/pl.json +209 -1
  330. package/src/modules/staff/lib/crud.ts +8 -0
  331. package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
  332. package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
  333. package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
  334. package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
  335. package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
  336. package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
  337. package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
  338. package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
  339. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
  340. package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
  341. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
  342. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
  343. package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
  344. package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
  345. package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
  346. package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
  347. package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
  348. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
  349. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
  350. package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
  351. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
  352. package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
  353. package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
  354. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
  355. package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
  356. package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
  357. package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
  358. package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
  359. package/src/modules/staff/search.ts +35 -0
  360. package/src/modules/staff/setup.ts +15 -0
  361. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
  362. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
  363. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
  364. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
  365. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
  366. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
  367. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
  368. package/src/modules/staff/widgets/injection-table.ts +10 -0
  369. package/src/modules/sync_excel/api/import/route.ts +23 -18
  370. package/src/modules/translations/commands/translations.ts +49 -41
@@ -0,0 +1,103 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
+ import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
6
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
8
+ import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
9
+ import { StaffTimeProjectMember, StaffTeamMember } from "../../../data/entities.js";
10
+ const metadata = {
11
+ GET: { requireAuth: true, requireFeatures: ["staff.timesheets.view"] }
12
+ };
13
+ async function GET(req) {
14
+ try {
15
+ const container = await createRequestContainer();
16
+ const auth = await getAuthFromRequest(req);
17
+ const { translate } = await resolveTranslations();
18
+ if (!auth) throw new CrudHttpError(401, { error: translate("staff.errors.unauthorized", "Unauthorized") });
19
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
20
+ const tenantId = scope?.tenantId ?? auth.tenantId ?? null;
21
+ const organizationId = scope?.selectedId ?? auth.orgId ?? null;
22
+ if (!tenantId || !organizationId) {
23
+ throw new CrudHttpError(400, { error: translate("staff.errors.missingScope", "Missing tenant or organization scope.") });
24
+ }
25
+ const em = container.resolve("em").fork();
26
+ const scopeCtx = { tenantId, organizationId };
27
+ const staffMember = await findOneWithDecryption(
28
+ em,
29
+ StaffTeamMember,
30
+ { userId: auth.sub, tenantId, organizationId, deletedAt: null },
31
+ {},
32
+ scopeCtx
33
+ );
34
+ if (!staffMember) {
35
+ throw new CrudHttpError(403, { error: translate("staff.timesheets.errors.noStaffMember", "No staff member linked to your account.") });
36
+ }
37
+ const assignments = await findWithDecryption(
38
+ em,
39
+ StaffTimeProjectMember,
40
+ { staffMemberId: staffMember.id, tenantId, organizationId, deletedAt: null, status: "active" },
41
+ {},
42
+ scopeCtx
43
+ );
44
+ const items = assignments.map((assignment) => ({
45
+ id: assignment.id,
46
+ time_project_id: assignment.timeProjectId,
47
+ staff_member_id: assignment.staffMemberId,
48
+ role: assignment.role ?? null,
49
+ status: assignment.status ?? null,
50
+ assigned_start_date: assignment.assignedStartDate ?? null,
51
+ assigned_end_date: assignment.assignedEndDate ?? null,
52
+ show_in_grid: assignment.showInGrid ?? false
53
+ }));
54
+ return NextResponse.json({ items, total: items.length }, { status: 200 });
55
+ } catch (err) {
56
+ if (err instanceof CrudHttpError) {
57
+ return NextResponse.json(err.body, { status: err.status });
58
+ }
59
+ console.error("staff.timesheets.my-projects failed", err);
60
+ const { translate } = await resolveTranslations();
61
+ return NextResponse.json(
62
+ { error: translate("staff.timesheets.errors.myProjects", "Failed to load your projects.") },
63
+ { status: 400 }
64
+ );
65
+ }
66
+ }
67
+ const openApi = {
68
+ tag: "Staff",
69
+ summary: "My assigned time projects",
70
+ methods: {
71
+ GET: {
72
+ summary: "List assigned time project memberships for the current user",
73
+ description: "Returns staff_time_project_members where the authenticated user is an active member. Used by the My Timesheets grid to resolve assigned project IDs (spec N+1 mitigation query 1).",
74
+ responses: [
75
+ {
76
+ status: 200,
77
+ description: "Assigned project memberships",
78
+ schema: z.object({
79
+ items: z.array(z.object({
80
+ id: z.string().uuid(),
81
+ time_project_id: z.string().uuid(),
82
+ staff_member_id: z.string().uuid(),
83
+ role: z.string().nullable(),
84
+ status: z.string().nullable(),
85
+ assigned_start_date: z.string().nullable(),
86
+ assigned_end_date: z.string().nullable(),
87
+ show_in_grid: z.boolean()
88
+ })),
89
+ total: z.number()
90
+ })
91
+ },
92
+ { status: 401, description: "Unauthorized", schema: z.object({ error: z.string() }) },
93
+ { status: 403, description: "No staff member linked", schema: z.object({ error: z.string() }) }
94
+ ]
95
+ }
96
+ }
97
+ };
98
+ export {
99
+ GET,
100
+ metadata,
101
+ openApi
102
+ };
103
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/staff/api/timesheets/my-projects/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { StaffTimeProjectMember, StaffTeamMember } from '../../../data/entities'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['staff.timesheets.view'] },\n}\n\n/**\n * GET /api/staff/timesheets/my-projects\n *\n * Spec N+1 Mitigation \u2014 Query 1:\n * Returns staff_time_project_members for the authenticated staff member.\n * Used by \"My Timesheets\" grid to resolve assigned project IDs.\n */\nexport async function GET(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n if (!auth) throw new CrudHttpError(401, { error: translate('staff.errors.unauthorized', 'Unauthorized') })\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.') })\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const scopeCtx = { tenantId, organizationId }\n\n const staffMember = await findOneWithDecryption(\n em,\n StaffTeamMember,\n { userId: auth.sub, tenantId, organizationId, deletedAt: null },\n {},\n scopeCtx,\n )\n if (!staffMember) {\n throw new CrudHttpError(403, { error: translate('staff.timesheets.errors.noStaffMember', 'No staff member linked to your account.') })\n }\n\n const assignments = await findWithDecryption(\n em,\n StaffTimeProjectMember,\n { staffMemberId: staffMember.id, tenantId, organizationId, deletedAt: null, status: 'active' },\n {},\n scopeCtx,\n )\n\n const items = assignments.map((assignment) => ({\n id: assignment.id,\n time_project_id: assignment.timeProjectId,\n staff_member_id: assignment.staffMemberId,\n role: assignment.role ?? null,\n status: assignment.status ?? null,\n assigned_start_date: assignment.assignedStartDate ?? null,\n assigned_end_date: assignment.assignedEndDate ?? null,\n show_in_grid: assignment.showInGrid ?? false,\n }))\n\n return NextResponse.json({ items, total: items.length }, { status: 200 })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('staff.timesheets.my-projects failed', err)\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n { error: translate('staff.timesheets.errors.myProjects', 'Failed to load your projects.') },\n { status: 400 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'My assigned time projects',\n methods: {\n GET: {\n summary: 'List assigned time project memberships for the current user',\n description: 'Returns staff_time_project_members where the authenticated user is an active member. Used by the My Timesheets grid to resolve assigned project IDs (spec N+1 mitigation query 1).',\n responses: [\n {\n status: 200,\n description: 'Assigned project memberships',\n schema: z.object({\n items: z.array(z.object({\n id: z.string().uuid(),\n time_project_id: z.string().uuid(),\n staff_member_id: z.string().uuid(),\n role: z.string().nullable(),\n status: z.string().nullable(),\n assigned_start_date: z.string().nullable(),\n assigned_end_date: z.string().nullable(),\n show_in_grid: z.boolean(),\n })),\n total: z.number(),\n }),\n },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n { status: 403, description: 'No staff member linked', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,147 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
4
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
5
+ import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
6
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
8
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
9
+ import { StaffTeamMember } from "../../../../data/entities.js";
10
+ import {
11
+ computeCollabProjectsKpis,
12
+ computePmProjectsKpis
13
+ } from "../../../../lib/timesheets-projects/computeProjectsKpis.js";
14
+ const VIEW_FEATURE = "staff.timesheets.projects.view";
15
+ const MANAGE_FEATURE = "staff.timesheets.projects.manage";
16
+ const metadata = {
17
+ GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] }
18
+ };
19
+ const deltaSchema = z.object({
20
+ current: z.number(),
21
+ previous: z.number(),
22
+ deltaPct: z.number().nullable()
23
+ });
24
+ const pmResponseSchema = z.object({
25
+ role: z.literal("pm"),
26
+ totals: z.object({
27
+ total: z.number().int(),
28
+ active: z.number().int(),
29
+ onHold: z.number().int(),
30
+ completed: z.number().int()
31
+ }),
32
+ hoursWeek: deltaSchema,
33
+ hoursMonth: deltaSchema,
34
+ teamActive: z.object({ count: z.number().int() }),
35
+ assignedToMe: z.object({ total: z.number().int(), active: z.number().int() })
36
+ });
37
+ const collabResponseSchema = z.object({
38
+ role: z.literal("collab"),
39
+ myProjects: z.object({
40
+ total: z.number().int(),
41
+ active: z.number().int()
42
+ }),
43
+ myHoursWeek: deltaSchema,
44
+ myHoursMonth: deltaSchema
45
+ });
46
+ async function GET(req) {
47
+ try {
48
+ const container = await createRequestContainer();
49
+ const auth = await getAuthFromRequest(req);
50
+ const { translate } = await resolveTranslations();
51
+ if (!auth) {
52
+ throw new CrudHttpError(401, {
53
+ error: translate("staff.errors.unauthorized", "Unauthorized")
54
+ });
55
+ }
56
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
57
+ const tenantId = scope?.tenantId ?? auth.tenantId ?? null;
58
+ const organizationId = scope?.selectedId ?? auth.orgId ?? null;
59
+ if (!tenantId || !organizationId) {
60
+ throw new CrudHttpError(400, {
61
+ error: translate("staff.errors.missingScope", "Missing tenant or organization scope.")
62
+ });
63
+ }
64
+ const em = container.resolve("em");
65
+ const rbac = container.resolve("rbacService");
66
+ const isPm = await rbac.userHasAllFeatures(auth.sub, [MANAGE_FEATURE], {
67
+ tenantId,
68
+ organizationId
69
+ });
70
+ const staffMember = await findOneWithDecryption(
71
+ em.fork(),
72
+ StaffTeamMember,
73
+ { userId: auth.sub, tenantId, organizationId, deletedAt: null },
74
+ {},
75
+ { tenantId, organizationId }
76
+ );
77
+ if (isPm) {
78
+ const result2 = await computePmProjectsKpis({
79
+ em,
80
+ tenantId,
81
+ organizationId,
82
+ callerStaffMemberId: staffMember?.id ?? null
83
+ });
84
+ return NextResponse.json(result2, { status: 200 });
85
+ }
86
+ if (!staffMember) {
87
+ throw new CrudHttpError(403, {
88
+ error: translate(
89
+ "staff.timesheets.errors.noStaffMember",
90
+ "No staff member linked to your account."
91
+ )
92
+ });
93
+ }
94
+ const result = await computeCollabProjectsKpis({
95
+ em,
96
+ tenantId,
97
+ organizationId,
98
+ staffMemberId: staffMember.id
99
+ });
100
+ return NextResponse.json(result, { status: 200 });
101
+ } catch (err) {
102
+ if (err instanceof CrudHttpError) {
103
+ return NextResponse.json(err.body, { status: err.status });
104
+ }
105
+ console.error("staff.timesheets.projects.kpis failed", err);
106
+ const { translate } = await resolveTranslations();
107
+ return NextResponse.json(
108
+ {
109
+ error: translate(
110
+ "staff.timesheets.errors.projectsKpis",
111
+ "Failed to load project KPIs."
112
+ )
113
+ },
114
+ { status: 500 }
115
+ );
116
+ }
117
+ }
118
+ const openApi = {
119
+ tag: "Staff",
120
+ summary: "Timesheet projects KPIs",
121
+ methods: {
122
+ GET: {
123
+ summary: "Aggregate KPIs for the timesheets Projects page",
124
+ description: "Returns a role-aware KPI payload. Users with `staff.timesheets.projects.manage` get the PM shape (portfolio totals, monthly team hours, active team member count). Other viewers get the Collaborator shape scoped to their own memberships and hours.",
125
+ responses: [
126
+ {
127
+ status: 200,
128
+ description: "PM or Collaborator KPIs",
129
+ schema: z.union([pmResponseSchema, collabResponseSchema])
130
+ },
131
+ { status: 401, description: "Unauthorized", schema: z.object({ error: z.string() }) },
132
+ {
133
+ status: 403,
134
+ description: "Missing viewing feature or no staff member linked",
135
+ schema: z.object({ error: z.string() })
136
+ },
137
+ { status: 500, description: "Aggregation failure", schema: z.object({ error: z.string() }) }
138
+ ]
139
+ }
140
+ }
141
+ };
142
+ export {
143
+ GET,
144
+ metadata,
145
+ openApi
146
+ };
147
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../src/modules/staff/api/timesheets/projects/kpis/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { StaffTeamMember } from '../../../../data/entities'\nimport {\n computeCollabProjectsKpis,\n computePmProjectsKpis,\n} from '../../../../lib/timesheets-projects/computeProjectsKpis'\n\nconst VIEW_FEATURE = 'staff.timesheets.projects.view'\nconst MANAGE_FEATURE = 'staff.timesheets.projects.manage'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [VIEW_FEATURE] },\n}\n\nconst deltaSchema = z.object({\n current: z.number(),\n previous: z.number(),\n deltaPct: z.number().nullable(),\n})\n\nconst pmResponseSchema = z.object({\n role: z.literal('pm'),\n totals: z.object({\n total: z.number().int(),\n active: z.number().int(),\n onHold: z.number().int(),\n completed: z.number().int(),\n }),\n hoursWeek: deltaSchema,\n hoursMonth: deltaSchema,\n teamActive: z.object({ count: z.number().int() }),\n assignedToMe: z.object({ total: z.number().int(), active: z.number().int() }),\n})\n\nconst collabResponseSchema = z.object({\n role: z.literal('collab'),\n myProjects: z.object({\n total: z.number().int(),\n active: z.number().int(),\n }),\n myHoursWeek: deltaSchema,\n myHoursMonth: deltaSchema,\n})\n\nexport async function GET(req: Request) {\n try {\n const container = await createRequestContainer()\n const auth = await getAuthFromRequest(req)\n const { translate } = await resolveTranslations()\n\n if (!auth) {\n throw new CrudHttpError(401, {\n error: translate('staff.errors.unauthorized', 'Unauthorized'),\n })\n }\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, {\n error: translate('staff.errors.missingScope', 'Missing tenant or organization scope.'),\n })\n }\n\n const em = container.resolve('em') as EntityManager\n const rbac = container.resolve('rbacService') as RbacService\n const isPm = await rbac.userHasAllFeatures(auth.sub, [MANAGE_FEATURE], {\n tenantId,\n organizationId,\n })\n\n const staffMember = await findOneWithDecryption(\n em.fork(),\n StaffTeamMember,\n { userId: auth.sub, tenantId, organizationId, deletedAt: null },\n {},\n { tenantId, organizationId },\n )\n\n if (isPm) {\n const result = await computePmProjectsKpis({\n em,\n tenantId,\n organizationId,\n callerStaffMemberId: staffMember?.id ?? null,\n })\n return NextResponse.json(result, { status: 200 })\n }\n\n if (!staffMember) {\n throw new CrudHttpError(403, {\n error: translate(\n 'staff.timesheets.errors.noStaffMember',\n 'No staff member linked to your account.',\n ),\n })\n }\n\n const result = await computeCollabProjectsKpis({\n em,\n tenantId,\n organizationId,\n staffMemberId: staffMember.id,\n })\n return NextResponse.json(result, { status: 200 })\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error('staff.timesheets.projects.kpis failed', err)\n const { translate } = await resolveTranslations()\n return NextResponse.json(\n {\n error: translate(\n 'staff.timesheets.errors.projectsKpis',\n 'Failed to load project KPIs.',\n ),\n },\n { status: 500 },\n )\n }\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Timesheet projects KPIs',\n methods: {\n GET: {\n summary: 'Aggregate KPIs for the timesheets Projects page',\n description:\n 'Returns a role-aware KPI payload. Users with `staff.timesheets.projects.manage` get the PM shape (portfolio totals, monthly team hours, active team member count). Other viewers get the Collaborator shape scoped to their own memberships and hours.',\n responses: [\n {\n status: 200,\n description: 'PM or Collaborator KPIs',\n schema: z.union([pmResponseSchema, collabResponseSchema]),\n },\n { status: 401, description: 'Unauthorized', schema: z.object({ error: z.string() }) },\n {\n status: 403,\n description: 'Missing viewing feature or no staff member linked',\n schema: z.object({ error: z.string() }),\n },\n { status: 500, description: 'Aggregation failure', schema: z.object({ error: z.string() }) },\n ],\n },\n },\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["result"]
7
+ }
@@ -0,0 +1,171 @@
1
+ import { NextResponse } from "next/server";
2
+ import { z } from "zod";
3
+ import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
4
+ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
5
+ import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
6
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
7
+ import { readJsonSafe } from "@open-mercato/shared/lib/http/readJsonSafe";
8
+ import { StaffTimeEntry, StaffTimeEntrySegment } from "../../../../../../data/entities.js";
9
+ import { staffTimeEntrySegmentUpdateSchema } from "../../../../../../data/validators.js";
10
+ import { getStaffMemberByUserId } from "../../../../../../lib/staffMemberResolver.js";
11
+ import {
12
+ resolveUserFeatures,
13
+ runStaffMutationGuardAfterSuccess,
14
+ runStaffMutationGuards
15
+ } from "../../../../../guards.js";
16
+ const routeMetadata = {
17
+ PATCH: { requireAuth: true, requireFeatures: ["staff.timesheets.manage_own"] }
18
+ };
19
+ const metadata = routeMetadata;
20
+ function extractIdsFromUrl(request) {
21
+ if (!request?.url) return null;
22
+ try {
23
+ const url = new URL(request.url);
24
+ const match = url.pathname.match(/\/time-entries\/([^/]+)\/segments\/([^/]+)/);
25
+ if (!match?.[1] || !match?.[2]) return null;
26
+ return { entryId: match[1], segmentId: match[2] };
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+ async function PATCH(req) {
32
+ const auth = await getAuthFromRequest(req);
33
+ if (!auth) {
34
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
35
+ }
36
+ const ids = extractIdsFromUrl(req);
37
+ if (!ids) {
38
+ return NextResponse.json({ error: "Segment id is required" }, { status: 400 });
39
+ }
40
+ const rawBody = await readJsonSafe(req, null);
41
+ if (!rawBody) {
42
+ return NextResponse.json({ error: "Invalid payload" }, { status: 400 });
43
+ }
44
+ const parsed = staffTimeEntrySegmentUpdateSchema.safeParse({ ...rawBody, id: ids.segmentId });
45
+ if (!parsed.success) {
46
+ return NextResponse.json({ error: "Invalid payload", details: parsed.error.flatten() }, { status: 400 });
47
+ }
48
+ const container = await createRequestContainer();
49
+ const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req });
50
+ const tenantId = scope?.tenantId ?? auth.tenantId ?? null;
51
+ const organizationId = scope?.selectedId ?? auth.orgId ?? null;
52
+ if (!tenantId || !organizationId) {
53
+ return NextResponse.json({ error: "Missing tenant or organization scope." }, { status: 400 });
54
+ }
55
+ const em = container.resolve("em").fork();
56
+ const scopeCtx = { tenantId, organizationId };
57
+ const entry = await findOneWithDecryption(em, StaffTimeEntry, { id: ids.entryId, tenantId, organizationId, deletedAt: null }, {}, scopeCtx);
58
+ if (!entry) {
59
+ return NextResponse.json({ error: "Time entry not found" }, { status: 404 });
60
+ }
61
+ const staffMember = await getStaffMemberByUserId(em, auth.sub, tenantId, organizationId);
62
+ if (!staffMember || entry.staffMemberId !== staffMember.id) {
63
+ return NextResponse.json({ error: "You can only manage your own time entries." }, { status: 403 });
64
+ }
65
+ const segment = await findOneWithDecryption(em, StaffTimeEntrySegment, {
66
+ id: ids.segmentId,
67
+ timeEntryId: ids.entryId,
68
+ tenantId,
69
+ organizationId,
70
+ deletedAt: null
71
+ }, {}, scopeCtx);
72
+ if (!segment) {
73
+ return NextResponse.json({ error: "Segment not found" }, { status: 404 });
74
+ }
75
+ const guardResult = await runStaffMutationGuards(
76
+ container,
77
+ {
78
+ tenantId,
79
+ organizationId,
80
+ userId: auth.sub ?? "",
81
+ resourceKind: "staff.timesheets.time_entry_segment",
82
+ resourceId: segment.id,
83
+ operation: "update",
84
+ requestMethod: req.method,
85
+ requestHeaders: req.headers,
86
+ mutationPayload: parsed.data
87
+ },
88
+ resolveUserFeatures(auth)
89
+ );
90
+ if (!guardResult.ok) {
91
+ return NextResponse.json(
92
+ guardResult.errorBody ?? { error: "Operation blocked by guard" },
93
+ { status: guardResult.errorStatus ?? 422 }
94
+ );
95
+ }
96
+ if (parsed.data.startedAt !== void 0) {
97
+ segment.startedAt = parsed.data.startedAt;
98
+ }
99
+ if (parsed.data.endedAt !== void 0) {
100
+ segment.endedAt = parsed.data.endedAt ?? null;
101
+ }
102
+ if (parsed.data.segmentType !== void 0) {
103
+ segment.segmentType = parsed.data.segmentType;
104
+ }
105
+ await em.flush();
106
+ if (guardResult.afterSuccessCallbacks.length) {
107
+ await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {
108
+ tenantId,
109
+ organizationId,
110
+ userId: auth.sub ?? "",
111
+ resourceKind: "staff.timesheets.time_entry_segment",
112
+ resourceId: segment.id,
113
+ operation: "update",
114
+ requestMethod: req.method,
115
+ requestHeaders: req.headers
116
+ });
117
+ }
118
+ return NextResponse.json({
119
+ ok: true,
120
+ item: {
121
+ id: segment.id,
122
+ timeEntryId: segment.timeEntryId,
123
+ startedAt: segment.startedAt,
124
+ endedAt: segment.endedAt,
125
+ segmentType: segment.segmentType,
126
+ createdAt: segment.createdAt,
127
+ updatedAt: segment.updatedAt
128
+ }
129
+ });
130
+ }
131
+ const errorSchema = z.object({ error: z.string() });
132
+ const segmentResponseSchema = z.object({
133
+ ok: z.literal(true),
134
+ item: z.object({
135
+ id: z.string(),
136
+ timeEntryId: z.string(),
137
+ startedAt: z.string(),
138
+ endedAt: z.string().nullable(),
139
+ segmentType: z.enum(["work", "break"]),
140
+ createdAt: z.string(),
141
+ updatedAt: z.string()
142
+ })
143
+ });
144
+ const openApi = {
145
+ tag: "Staff",
146
+ summary: "Time entry segment management",
147
+ methods: {
148
+ PATCH: {
149
+ summary: "Update a time entry segment",
150
+ description: "Updates fields on an existing time entry segment (startedAt, endedAt, segmentType).",
151
+ requestBody: {
152
+ contentType: "application/json",
153
+ schema: staffTimeEntrySegmentUpdateSchema.omit({ id: true })
154
+ },
155
+ responses: [
156
+ { status: 200, description: "Segment updated successfully", schema: segmentResponseSchema }
157
+ ],
158
+ errors: [
159
+ { status: 400, description: "Invalid payload or missing segment id", schema: errorSchema },
160
+ { status: 401, description: "Unauthorized", schema: errorSchema },
161
+ { status: 404, description: "Segment not found", schema: errorSchema }
162
+ ]
163
+ }
164
+ }
165
+ };
166
+ export {
167
+ PATCH,
168
+ metadata,
169
+ openApi
170
+ };
171
+ //# sourceMappingURL=route.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../../src/modules/staff/api/timesheets/time-entries/%5Bid%5D/segments/%5BsegmentId%5D/route.ts"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { StaffTimeEntry, StaffTimeEntrySegment } from '../../../../../../data/entities'\nimport { staffTimeEntrySegmentUpdateSchema } from '../../../../../../data/validators'\nimport { getStaffMemberByUserId } from '../../../../../../lib/staffMemberResolver'\nimport {\n resolveUserFeatures,\n runStaffMutationGuardAfterSuccess,\n runStaffMutationGuards,\n} from '../../../../../guards'\n\nconst routeMetadata = {\n PATCH: { requireAuth: true, requireFeatures: ['staff.timesheets.manage_own'] },\n}\n\nexport const metadata = routeMetadata\n\nfunction extractIdsFromUrl(request?: Request): { entryId: string; segmentId: string } | null {\n if (!request?.url) return null\n try {\n const url = new URL(request.url)\n const match = url.pathname.match(/\\/time-entries\\/([^/]+)\\/segments\\/([^/]+)/)\n if (!match?.[1] || !match?.[2]) return null\n return { entryId: match[1], segmentId: match[2] }\n } catch {\n return null\n }\n}\n\nexport async function PATCH(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) {\n return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n }\n\n const ids = extractIdsFromUrl(req)\n if (!ids) {\n return NextResponse.json({ error: 'Segment id is required' }, { status: 400 })\n }\n\n const rawBody = await readJsonSafe<Record<string, unknown>>(req, null)\n if (!rawBody) {\n return NextResponse.json({ error: 'Invalid payload' }, { status: 400 })\n }\n\n const parsed = staffTimeEntrySegmentUpdateSchema.safeParse({ ...rawBody, id: ids.segmentId })\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', details: parsed.error.flatten() }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n const tenantId = scope?.tenantId ?? auth.tenantId ?? null\n const organizationId = scope?.selectedId ?? auth.orgId ?? null\n if (!tenantId || !organizationId) {\n return NextResponse.json({ error: 'Missing tenant or organization scope.' }, { status: 400 })\n }\n\n const em = (container.resolve('em') as EntityManager).fork()\n const scopeCtx = { tenantId, organizationId }\n\n const entry = await findOneWithDecryption(em, StaffTimeEntry, { id: ids.entryId, tenantId, organizationId, deletedAt: null }, {}, scopeCtx)\n if (!entry) {\n return NextResponse.json({ error: 'Time entry not found' }, { status: 404 })\n }\n\n const staffMember = await getStaffMemberByUserId(em, auth.sub, tenantId, organizationId)\n if (!staffMember || entry.staffMemberId !== staffMember.id) {\n return NextResponse.json({ error: 'You can only manage your own time entries.' }, { status: 403 })\n }\n\n const segment = await findOneWithDecryption(em, StaffTimeEntrySegment, {\n id: ids.segmentId,\n timeEntryId: ids.entryId,\n tenantId,\n organizationId,\n deletedAt: null,\n }, {}, scopeCtx)\n\n if (!segment) {\n return NextResponse.json({ error: 'Segment not found' }, { status: 404 })\n }\n\n const guardResult = await runStaffMutationGuards(\n container,\n {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: 'staff.timesheets.time_entry_segment',\n resourceId: segment.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: parsed.data as unknown as Record<string, unknown>,\n },\n resolveUserFeatures(auth),\n )\n if (!guardResult.ok) {\n return NextResponse.json(\n guardResult.errorBody ?? { error: 'Operation blocked by guard' },\n { status: guardResult.errorStatus ?? 422 },\n )\n }\n\n if (parsed.data.startedAt !== undefined) {\n segment.startedAt = parsed.data.startedAt\n }\n if (parsed.data.endedAt !== undefined) {\n segment.endedAt = parsed.data.endedAt ?? null\n }\n if (parsed.data.segmentType !== undefined) {\n segment.segmentType = parsed.data.segmentType\n }\n\n await em.flush()\n\n if (guardResult.afterSuccessCallbacks.length) {\n await runStaffMutationGuardAfterSuccess(guardResult.afterSuccessCallbacks, {\n tenantId,\n organizationId,\n userId: auth.sub ?? '',\n resourceKind: 'staff.timesheets.time_entry_segment',\n resourceId: segment.id,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n })\n }\n\n return NextResponse.json({\n ok: true,\n item: {\n id: segment.id,\n timeEntryId: segment.timeEntryId,\n startedAt: segment.startedAt,\n endedAt: segment.endedAt,\n segmentType: segment.segmentType,\n createdAt: segment.createdAt,\n updatedAt: segment.updatedAt,\n },\n })\n}\n\nconst errorSchema = z.object({ error: z.string() })\nconst segmentResponseSchema = z.object({\n ok: z.literal(true),\n item: z.object({\n id: z.string(),\n timeEntryId: z.string(),\n startedAt: z.string(),\n endedAt: z.string().nullable(),\n segmentType: z.enum(['work', 'break']),\n createdAt: z.string(),\n updatedAt: z.string(),\n }),\n})\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Staff',\n summary: 'Time entry segment management',\n methods: {\n PATCH: {\n summary: 'Update a time entry segment',\n description: 'Updates fields on an existing time entry segment (startedAt, endedAt, segmentType).',\n requestBody: {\n contentType: 'application/json',\n schema: staffTimeEntrySegmentUpdateSchema.omit({ id: true }),\n },\n responses: [\n { status: 200, description: 'Segment updated successfully', schema: segmentResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or missing segment id', schema: errorSchema },\n { status: 401, description: 'Unauthorized', schema: errorSchema },\n { status: 404, description: 'Segment not found', schema: errorSchema },\n ],\n },\n },\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAElB,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,0CAA0C;AACnD,SAAS,6BAA6B;AACtC,SAAS,oBAAoB;AAE7B,SAAS,gBAAgB,6BAA6B;AACtD,SAAS,yCAAyC;AAClD,SAAS,8BAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,gBAAgB;AAAA,EACpB,OAAO,EAAE,aAAa,MAAM,iBAAiB,CAAC,6BAA6B,EAAE;AAC/E;AAEO,MAAM,WAAW;AAExB,SAAS,kBAAkB,SAAkE;AAC3F,MAAI,CAAC,SAAS,IAAK,QAAO;AAC1B,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,QAAQ,IAAI,SAAS,MAAM,4CAA4C;AAC7E,QAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAG,QAAO;AACvC,WAAO,EAAE,SAAS,MAAM,CAAC,GAAG,WAAW,MAAM,CAAC,EAAE;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,MAAM,KAAc;AACxC,QAAM,OAAO,MAAM,mBAAmB,GAAG;AACzC,MAAI,CAAC,MAAM;AACT,WAAO,aAAa,KAAK,EAAE,OAAO,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACrE;AAEA,QAAM,MAAM,kBAAkB,GAAG;AACjC,MAAI,CAAC,KAAK;AACR,WAAO,aAAa,KAAK,EAAE,OAAO,yBAAyB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC/E;AAEA,QAAM,UAAU,MAAM,aAAsC,KAAK,IAAI;AACrE,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,OAAO,kBAAkB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACxE;AAEA,QAAM,SAAS,kCAAkC,UAAU,EAAE,GAAG,SAAS,IAAI,IAAI,UAAU,CAAC;AAC5F,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,aAAa,KAAK,EAAE,OAAO,mBAAmB,SAAS,OAAO,MAAM,QAAQ,EAAE,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACzG;AAEA,QAAM,YAAY,MAAM,uBAAuB;AAC/C,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,SAAS,IAAI,CAAC;AACxF,QAAM,WAAW,OAAO,YAAY,KAAK,YAAY;AACrD,QAAM,iBAAiB,OAAO,cAAc,KAAK,SAAS;AAC1D,MAAI,CAAC,YAAY,CAAC,gBAAgB;AAChC,WAAO,aAAa,KAAK,EAAE,OAAO,wCAAwC,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC9F;AAEA,QAAM,KAAM,UAAU,QAAQ,IAAI,EAAoB,KAAK;AAC3D,QAAM,WAAW,EAAE,UAAU,eAAe;AAE5C,QAAM,QAAQ,MAAM,sBAAsB,IAAI,gBAAgB,EAAE,IAAI,IAAI,SAAS,UAAU,gBAAgB,WAAW,KAAK,GAAG,CAAC,GAAG,QAAQ;AAC1I,MAAI,CAAC,OAAO;AACV,WAAO,aAAa,KAAK,EAAE,OAAO,uBAAuB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC7E;AAEA,QAAM,cAAc,MAAM,uBAAuB,IAAI,KAAK,KAAK,UAAU,cAAc;AACvF,MAAI,CAAC,eAAe,MAAM,kBAAkB,YAAY,IAAI;AAC1D,WAAO,aAAa,KAAK,EAAE,OAAO,6CAA6C,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EACnG;AAEA,QAAM,UAAU,MAAM,sBAAsB,IAAI,uBAAuB;AAAA,IACrE,IAAI,IAAI;AAAA,IACR,aAAa,IAAI;AAAA,IACjB;AAAA,IACA;AAAA,IACA,WAAW;AAAA,EACb,GAAG,CAAC,GAAG,QAAQ;AAEf,MAAI,CAAC,SAAS;AACZ,WAAO,aAAa,KAAK,EAAE,OAAO,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;AAAA,EAC1E;AAEA,QAAM,cAAc,MAAM;AAAA,IACxB;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA,QAAQ,KAAK,OAAO;AAAA,MACpB,cAAc;AAAA,MACd,YAAY,QAAQ;AAAA,MACpB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,MACpB,iBAAiB,OAAO;AAAA,IAC1B;AAAA,IACA,oBAAoB,IAAI;AAAA,EAC1B;AACA,MAAI,CAAC,YAAY,IAAI;AACnB,WAAO,aAAa;AAAA,MAClB,YAAY,aAAa,EAAE,OAAO,6BAA6B;AAAA,MAC/D,EAAE,QAAQ,YAAY,eAAe,IAAI;AAAA,IAC3C;AAAA,EACF;AAEA,MAAI,OAAO,KAAK,cAAc,QAAW;AACvC,YAAQ,YAAY,OAAO,KAAK;AAAA,EAClC;AACA,MAAI,OAAO,KAAK,YAAY,QAAW;AACrC,YAAQ,UAAU,OAAO,KAAK,WAAW;AAAA,EAC3C;AACA,MAAI,OAAO,KAAK,gBAAgB,QAAW;AACzC,YAAQ,cAAc,OAAO,KAAK;AAAA,EACpC;AAEA,QAAM,GAAG,MAAM;AAEf,MAAI,YAAY,sBAAsB,QAAQ;AAC5C,UAAM,kCAAkC,YAAY,uBAAuB;AAAA,MACzE;AAAA,MACA;AAAA,MACA,QAAQ,KAAK,OAAO;AAAA,MACpB,cAAc;AAAA,MACd,YAAY,QAAQ;AAAA,MACpB,WAAW;AAAA,MACX,eAAe,IAAI;AAAA,MACnB,gBAAgB,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,SAAO,aAAa,KAAK;AAAA,IACvB,IAAI;AAAA,IACJ,MAAM;AAAA,MACJ,IAAI,QAAQ;AAAA,MACZ,aAAa,QAAQ;AAAA,MACrB,WAAW,QAAQ;AAAA,MACnB,SAAS,QAAQ;AAAA,MACjB,aAAa,QAAQ;AAAA,MACrB,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ;AAAA,IACrB;AAAA,EACF,CAAC;AACH;AAEA,MAAM,cAAc,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAClD,MAAM,wBAAwB,EAAE,OAAO;AAAA,EACrC,IAAI,EAAE,QAAQ,IAAI;AAAA,EAClB,MAAM,EAAE,OAAO;AAAA,IACb,IAAI,EAAE,OAAO;AAAA,IACb,aAAa,EAAE,OAAO;AAAA,IACtB,WAAW,EAAE,OAAO;AAAA,IACpB,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,IAC7B,aAAa,EAAE,KAAK,CAAC,QAAQ,OAAO,CAAC;AAAA,IACrC,WAAW,EAAE,OAAO;AAAA,IACpB,WAAW,EAAE,OAAO;AAAA,EACtB,CAAC;AACH,CAAC;AAEM,MAAM,UAA2B;AAAA,EACtC,KAAK;AAAA,EACL,SAAS;AAAA,EACT,SAAS;AAAA,IACP,OAAO;AAAA,MACL,SAAS;AAAA,MACT,aAAa;AAAA,MACb,aAAa;AAAA,QACX,aAAa;AAAA,QACb,QAAQ,kCAAkC,KAAK,EAAE,IAAI,KAAK,CAAC;AAAA,MAC7D;AAAA,MACA,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,gCAAgC,QAAQ,sBAAsB;AAAA,MAC5F;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,yCAAyC,QAAQ,YAAY;AAAA,QACzF,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,YAAY;AAAA,QAChE,EAAE,QAAQ,KAAK,aAAa,qBAAqB,QAAQ,YAAY;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }