@open-mercato/core 0.6.4-develop.4217.1.c9aa050183 → 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,260 @@
1
+ import { StaffTimeProject, StaffTimeProjectMember } from "../../data/entities.js";
2
+ import {
3
+ addUtcDays,
4
+ getFirstDayOfMonthUtc,
5
+ getFirstDayOfNextMonthUtc,
6
+ getMondayUtc,
7
+ toDateOnlyString
8
+ } from "./dateBuckets.js";
9
+ import { deltaPct, minutesToHours } from "./kpiMath.js";
10
+ async function computePmProjectsKpis(scope) {
11
+ const em = scope.em.fork();
12
+ const now = scope.now ?? /* @__PURE__ */ new Date();
13
+ const projects = await em.find(StaffTimeProject, {
14
+ tenantId: scope.tenantId,
15
+ organizationId: scope.organizationId,
16
+ deletedAt: null
17
+ });
18
+ const totals = {
19
+ total: projects.length,
20
+ active: 0,
21
+ onHold: 0,
22
+ completed: 0
23
+ };
24
+ for (const p of projects) {
25
+ if (p.status === "active") totals.active += 1;
26
+ else if (p.status === "on_hold") totals.onHold += 1;
27
+ else if (p.status === "completed") totals.completed += 1;
28
+ }
29
+ const monthStart = getFirstDayOfMonthUtc(now);
30
+ const nextMonthStart = getFirstDayOfNextMonthUtc(now);
31
+ const prevMonthStart = getFirstDayOfMonthUtc(addUtcDays(monthStart, -1));
32
+ const weekMonday = getMondayUtc(now);
33
+ const nextMonday = addUtcDays(weekMonday, 7);
34
+ const prevMonday = addUtcDays(weekMonday, -7);
35
+ const hoursRows = await em.getConnection().execute(
36
+ `
37
+ SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes
38
+ FROM (
39
+ SELECT
40
+ CASE
41
+ WHEN date >= ?::date AND date < ?::date THEN 'current'
42
+ WHEN date >= ?::date AND date < ?::date THEN 'previous'
43
+ END AS bucket,
44
+ duration_minutes
45
+ FROM staff_time_entries
46
+ WHERE organization_id = ?
47
+ AND tenant_id = ?
48
+ AND deleted_at IS NULL
49
+ AND date >= ?::date
50
+ AND date < ?::date
51
+ ) t
52
+ WHERE bucket IS NOT NULL
53
+ GROUP BY bucket
54
+ `,
55
+ [
56
+ toDateOnlyString(monthStart),
57
+ toDateOnlyString(nextMonthStart),
58
+ toDateOnlyString(prevMonthStart),
59
+ toDateOnlyString(monthStart),
60
+ scope.organizationId,
61
+ scope.tenantId,
62
+ toDateOnlyString(prevMonthStart),
63
+ toDateOnlyString(nextMonthStart)
64
+ ]
65
+ );
66
+ const weekRows = await em.getConnection().execute(
67
+ `
68
+ SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes
69
+ FROM (
70
+ SELECT
71
+ CASE
72
+ WHEN date >= ?::date AND date < ?::date THEN 'current'
73
+ WHEN date >= ?::date AND date < ?::date THEN 'previous'
74
+ END AS bucket,
75
+ duration_minutes
76
+ FROM staff_time_entries
77
+ WHERE organization_id = ?
78
+ AND tenant_id = ?
79
+ AND deleted_at IS NULL
80
+ AND date >= ?::date
81
+ AND date < ?::date
82
+ ) t
83
+ WHERE bucket IS NOT NULL
84
+ GROUP BY bucket
85
+ `,
86
+ [
87
+ toDateOnlyString(weekMonday),
88
+ toDateOnlyString(nextMonday),
89
+ toDateOnlyString(prevMonday),
90
+ toDateOnlyString(weekMonday),
91
+ scope.organizationId,
92
+ scope.tenantId,
93
+ toDateOnlyString(prevMonday),
94
+ toDateOnlyString(nextMonday)
95
+ ]
96
+ );
97
+ let currentHours = 0;
98
+ let previousHours = 0;
99
+ for (const row of hoursRows) {
100
+ const minutes = typeof row.total_minutes === "string" ? Number(row.total_minutes) : row.total_minutes;
101
+ if (row.bucket === "current") currentHours = minutesToHours(minutes);
102
+ else previousHours = minutesToHours(minutes);
103
+ }
104
+ let weekCurrentHours = 0;
105
+ let weekPreviousHours = 0;
106
+ for (const row of weekRows) {
107
+ const minutes = typeof row.total_minutes === "string" ? Number(row.total_minutes) : row.total_minutes;
108
+ if (row.bucket === "current") weekCurrentHours = minutesToHours(minutes);
109
+ else weekPreviousHours = minutesToHours(minutes);
110
+ }
111
+ const teamRows = await em.getConnection().execute(
112
+ `
113
+ SELECT COUNT(DISTINCT staff_member_id)::bigint AS count
114
+ FROM staff_time_entries
115
+ WHERE organization_id = ?
116
+ AND tenant_id = ?
117
+ AND deleted_at IS NULL
118
+ AND date >= ?::date
119
+ AND date < ?::date
120
+ `,
121
+ [
122
+ scope.organizationId,
123
+ scope.tenantId,
124
+ toDateOnlyString(monthStart),
125
+ toDateOnlyString(nextMonthStart)
126
+ ]
127
+ );
128
+ const teamCount = teamRows.length ? Number(teamRows[0].count) : 0;
129
+ const assignedToMe = { total: 0, active: 0 };
130
+ if (scope.callerStaffMemberId) {
131
+ const memberships = await em.find(StaffTimeProjectMember, {
132
+ staffMemberId: scope.callerStaffMemberId,
133
+ tenantId: scope.tenantId,
134
+ organizationId: scope.organizationId,
135
+ status: "active",
136
+ deletedAt: null
137
+ });
138
+ const assignedProjectIds = memberships.map((m) => m.timeProjectId);
139
+ const assignedProjects = projects.filter((p) => assignedProjectIds.includes(p.id));
140
+ assignedToMe.total = assignedProjects.length;
141
+ assignedToMe.active = assignedProjects.filter((p) => p.status === "active").length;
142
+ }
143
+ return {
144
+ role: "pm",
145
+ totals,
146
+ hoursWeek: {
147
+ current: weekCurrentHours,
148
+ previous: weekPreviousHours,
149
+ deltaPct: deltaPct(weekCurrentHours, weekPreviousHours)
150
+ },
151
+ hoursMonth: {
152
+ current: currentHours,
153
+ previous: previousHours,
154
+ deltaPct: deltaPct(currentHours, previousHours)
155
+ },
156
+ teamActive: { count: teamCount },
157
+ assignedToMe
158
+ };
159
+ }
160
+ async function computeCollabProjectsKpis(scope) {
161
+ const em = scope.em.fork();
162
+ const now = scope.now ?? /* @__PURE__ */ new Date();
163
+ const memberships = await em.find(StaffTimeProjectMember, {
164
+ staffMemberId: scope.staffMemberId,
165
+ tenantId: scope.tenantId,
166
+ organizationId: scope.organizationId,
167
+ status: "active",
168
+ deletedAt: null
169
+ });
170
+ const projectIds = memberships.map((m) => m.timeProjectId);
171
+ const myProjects = { total: projectIds.length, active: 0 };
172
+ if (projectIds.length > 0) {
173
+ const projects = await em.find(StaffTimeProject, {
174
+ id: { $in: projectIds },
175
+ tenantId: scope.tenantId,
176
+ organizationId: scope.organizationId,
177
+ status: "active",
178
+ deletedAt: null
179
+ });
180
+ myProjects.active = projects.length;
181
+ }
182
+ const weekMonday = getMondayUtc(now);
183
+ const nextMonday = addUtcDays(weekMonday, 7);
184
+ const prevMonday = addUtcDays(weekMonday, -7);
185
+ const monthStart = getFirstDayOfMonthUtc(now);
186
+ const nextMonthStart = getFirstDayOfNextMonthUtc(now);
187
+ const prevMonthStart = getFirstDayOfMonthUtc(addUtcDays(monthStart, -1));
188
+ const rows = await em.getConnection().execute(
189
+ `
190
+ SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes
191
+ FROM (
192
+ SELECT
193
+ CASE
194
+ WHEN date >= ?::date AND date < ?::date THEN 'week_current'
195
+ WHEN date >= ?::date AND date < ?::date THEN 'week_previous'
196
+ WHEN date >= ?::date AND date < ?::date THEN 'month_current'
197
+ WHEN date >= ?::date AND date < ?::date THEN 'month_previous'
198
+ END AS bucket,
199
+ duration_minutes
200
+ FROM staff_time_entries
201
+ WHERE organization_id = ?
202
+ AND tenant_id = ?
203
+ AND staff_member_id = ?
204
+ AND deleted_at IS NULL
205
+ AND date >= LEAST(?::date, ?::date)
206
+ AND date < GREATEST(?::date, ?::date)
207
+ ) t
208
+ WHERE bucket IS NOT NULL
209
+ GROUP BY bucket
210
+ `,
211
+ [
212
+ toDateOnlyString(weekMonday),
213
+ toDateOnlyString(nextMonday),
214
+ toDateOnlyString(prevMonday),
215
+ toDateOnlyString(weekMonday),
216
+ toDateOnlyString(monthStart),
217
+ toDateOnlyString(nextMonthStart),
218
+ toDateOnlyString(prevMonthStart),
219
+ toDateOnlyString(monthStart),
220
+ scope.organizationId,
221
+ scope.tenantId,
222
+ scope.staffMemberId,
223
+ toDateOnlyString(prevMonday),
224
+ toDateOnlyString(prevMonthStart),
225
+ toDateOnlyString(nextMonday),
226
+ toDateOnlyString(nextMonthStart)
227
+ ]
228
+ );
229
+ let weekCurrent = 0;
230
+ let weekPrevious = 0;
231
+ let monthCurrent = 0;
232
+ let monthPrevious = 0;
233
+ for (const row of rows) {
234
+ const minutes = typeof row.total_minutes === "string" ? Number(row.total_minutes) : row.total_minutes;
235
+ const hours = minutesToHours(minutes);
236
+ if (row.bucket === "week_current") weekCurrent = hours;
237
+ else if (row.bucket === "week_previous") weekPrevious = hours;
238
+ else if (row.bucket === "month_current") monthCurrent = hours;
239
+ else if (row.bucket === "month_previous") monthPrevious = hours;
240
+ }
241
+ return {
242
+ role: "collab",
243
+ myProjects,
244
+ myHoursWeek: {
245
+ current: weekCurrent,
246
+ previous: weekPrevious,
247
+ deltaPct: deltaPct(weekCurrent, weekPrevious)
248
+ },
249
+ myHoursMonth: {
250
+ current: monthCurrent,
251
+ previous: monthPrevious,
252
+ deltaPct: deltaPct(monthCurrent, monthPrevious)
253
+ }
254
+ };
255
+ }
256
+ export {
257
+ computeCollabProjectsKpis,
258
+ computePmProjectsKpis
259
+ };
260
+ //# sourceMappingURL=computeProjectsKpis.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { StaffTimeProject, StaffTimeProjectMember } from '../../data/entities'\nimport {\n addUtcDays,\n getFirstDayOfMonthUtc,\n getFirstDayOfNextMonthUtc,\n getMondayUtc,\n toDateOnlyString,\n} from './dateBuckets'\nimport { deltaPct, minutesToHours } from './kpiMath'\n\ntype WeekRow = { bucket: 'current' | 'previous'; total_minutes: string | number }\n\nexport type KpiScope = {\n em: EntityManager\n organizationId: string\n tenantId: string\n now?: Date\n}\n\nexport type KpiDelta = {\n current: number\n previous: number\n deltaPct: number | null\n}\n\nexport type ProjectKpisPmResult = {\n role: 'pm'\n totals: { total: number; active: number; onHold: number; completed: number }\n hoursWeek: KpiDelta\n hoursMonth: KpiDelta\n teamActive: { count: number }\n assignedToMe: { total: number; active: number }\n}\n\nexport type ProjectKpisCollabResult = {\n role: 'collab'\n myProjects: { total: number; active: number }\n myHoursWeek: KpiDelta\n myHoursMonth: KpiDelta\n}\n\nexport async function computePmProjectsKpis(\n scope: KpiScope & { callerStaffMemberId?: string | null },\n): Promise<ProjectKpisPmResult> {\n const em = scope.em.fork()\n const now = scope.now ?? new Date()\n\n const projects = await em.find(StaffTimeProject, {\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n\n const totals = {\n total: projects.length,\n active: 0,\n onHold: 0,\n completed: 0,\n }\n for (const p of projects) {\n if (p.status === 'active') totals.active += 1\n else if (p.status === 'on_hold') totals.onHold += 1\n else if (p.status === 'completed') totals.completed += 1\n }\n\n const monthStart = getFirstDayOfMonthUtc(now)\n const nextMonthStart = getFirstDayOfNextMonthUtc(now)\n const prevMonthStart = getFirstDayOfMonthUtc(addUtcDays(monthStart, -1))\n const weekMonday = getMondayUtc(now)\n const nextMonday = addUtcDays(weekMonday, 7)\n const prevMonday = addUtcDays(weekMonday, -7)\n\n const hoursRows = (await em.getConnection().execute(\n `\n SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes\n FROM (\n SELECT\n CASE\n WHEN date >= ?::date AND date < ?::date THEN 'current'\n WHEN date >= ?::date AND date < ?::date THEN 'previous'\n END AS bucket,\n duration_minutes\n FROM staff_time_entries\n WHERE organization_id = ?\n AND tenant_id = ?\n AND deleted_at IS NULL\n AND date >= ?::date\n AND date < ?::date\n ) t\n WHERE bucket IS NOT NULL\n GROUP BY bucket\n `,\n [\n toDateOnlyString(monthStart),\n toDateOnlyString(nextMonthStart),\n toDateOnlyString(prevMonthStart),\n toDateOnlyString(monthStart),\n scope.organizationId,\n scope.tenantId,\n toDateOnlyString(prevMonthStart),\n toDateOnlyString(nextMonthStart),\n ],\n )) as WeekRow[]\n\n const weekRows = (await em.getConnection().execute(\n `\n SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes\n FROM (\n SELECT\n CASE\n WHEN date >= ?::date AND date < ?::date THEN 'current'\n WHEN date >= ?::date AND date < ?::date THEN 'previous'\n END AS bucket,\n duration_minutes\n FROM staff_time_entries\n WHERE organization_id = ?\n AND tenant_id = ?\n AND deleted_at IS NULL\n AND date >= ?::date\n AND date < ?::date\n ) t\n WHERE bucket IS NOT NULL\n GROUP BY bucket\n `,\n [\n toDateOnlyString(weekMonday),\n toDateOnlyString(nextMonday),\n toDateOnlyString(prevMonday),\n toDateOnlyString(weekMonday),\n scope.organizationId,\n scope.tenantId,\n toDateOnlyString(prevMonday),\n toDateOnlyString(nextMonday),\n ],\n )) as WeekRow[]\n\n let currentHours = 0\n let previousHours = 0\n for (const row of hoursRows) {\n const minutes = typeof row.total_minutes === 'string' ? Number(row.total_minutes) : row.total_minutes\n if (row.bucket === 'current') currentHours = minutesToHours(minutes)\n else previousHours = minutesToHours(minutes)\n }\n\n let weekCurrentHours = 0\n let weekPreviousHours = 0\n for (const row of weekRows) {\n const minutes = typeof row.total_minutes === 'string' ? Number(row.total_minutes) : row.total_minutes\n if (row.bucket === 'current') weekCurrentHours = minutesToHours(minutes)\n else weekPreviousHours = minutesToHours(minutes)\n }\n\n const teamRows = (await em.getConnection().execute(\n `\n SELECT COUNT(DISTINCT staff_member_id)::bigint AS count\n FROM staff_time_entries\n WHERE organization_id = ?\n AND tenant_id = ?\n AND deleted_at IS NULL\n AND date >= ?::date\n AND date < ?::date\n `,\n [\n scope.organizationId,\n scope.tenantId,\n toDateOnlyString(monthStart),\n toDateOnlyString(nextMonthStart),\n ],\n )) as Array<{ count: string | number }>\n const teamCount = teamRows.length ? Number(teamRows[0].count) : 0\n\n const assignedToMe = { total: 0, active: 0 }\n if (scope.callerStaffMemberId) {\n const memberships = await em.find(StaffTimeProjectMember, {\n staffMemberId: scope.callerStaffMemberId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n })\n const assignedProjectIds = memberships.map((m) => m.timeProjectId)\n const assignedProjects = projects.filter((p) => assignedProjectIds.includes(p.id))\n assignedToMe.total = assignedProjects.length\n assignedToMe.active = assignedProjects.filter((p) => p.status === 'active').length\n }\n\n return {\n role: 'pm',\n totals,\n hoursWeek: {\n current: weekCurrentHours,\n previous: weekPreviousHours,\n deltaPct: deltaPct(weekCurrentHours, weekPreviousHours),\n },\n hoursMonth: {\n current: currentHours,\n previous: previousHours,\n deltaPct: deltaPct(currentHours, previousHours),\n },\n teamActive: { count: teamCount },\n assignedToMe,\n }\n}\n\nexport async function computeCollabProjectsKpis(\n scope: KpiScope & { staffMemberId: string },\n): Promise<ProjectKpisCollabResult> {\n const em = scope.em.fork()\n const now = scope.now ?? new Date()\n\n const memberships = await em.find(StaffTimeProjectMember, {\n staffMemberId: scope.staffMemberId,\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n })\n\n const projectIds = memberships.map((m) => m.timeProjectId)\n const myProjects = { total: projectIds.length, active: 0 }\n\n if (projectIds.length > 0) {\n const projects = await em.find(StaffTimeProject, {\n id: { $in: projectIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n })\n myProjects.active = projects.length\n }\n\n const weekMonday = getMondayUtc(now)\n const nextMonday = addUtcDays(weekMonday, 7)\n const prevMonday = addUtcDays(weekMonday, -7)\n\n const monthStart = getFirstDayOfMonthUtc(now)\n const nextMonthStart = getFirstDayOfNextMonthUtc(now)\n const prevMonthStart = getFirstDayOfMonthUtc(addUtcDays(monthStart, -1))\n\n const rows = (await em.getConnection().execute(\n `\n SELECT bucket, COALESCE(SUM(duration_minutes), 0)::bigint AS total_minutes\n FROM (\n SELECT\n CASE\n WHEN date >= ?::date AND date < ?::date THEN 'week_current'\n WHEN date >= ?::date AND date < ?::date THEN 'week_previous'\n WHEN date >= ?::date AND date < ?::date THEN 'month_current'\n WHEN date >= ?::date AND date < ?::date THEN 'month_previous'\n END AS bucket,\n duration_minutes\n FROM staff_time_entries\n WHERE organization_id = ?\n AND tenant_id = ?\n AND staff_member_id = ?\n AND deleted_at IS NULL\n AND date >= LEAST(?::date, ?::date)\n AND date < GREATEST(?::date, ?::date)\n ) t\n WHERE bucket IS NOT NULL\n GROUP BY bucket\n `,\n [\n toDateOnlyString(weekMonday),\n toDateOnlyString(nextMonday),\n toDateOnlyString(prevMonday),\n toDateOnlyString(weekMonday),\n toDateOnlyString(monthStart),\n toDateOnlyString(nextMonthStart),\n toDateOnlyString(prevMonthStart),\n toDateOnlyString(monthStart),\n scope.organizationId,\n scope.tenantId,\n scope.staffMemberId,\n toDateOnlyString(prevMonday),\n toDateOnlyString(prevMonthStart),\n toDateOnlyString(nextMonday),\n toDateOnlyString(nextMonthStart),\n ],\n )) as Array<{ bucket: string; total_minutes: string | number }>\n\n let weekCurrent = 0\n let weekPrevious = 0\n let monthCurrent = 0\n let monthPrevious = 0\n for (const row of rows) {\n const minutes = typeof row.total_minutes === 'string' ? Number(row.total_minutes) : row.total_minutes\n const hours = minutesToHours(minutes)\n if (row.bucket === 'week_current') weekCurrent = hours\n else if (row.bucket === 'week_previous') weekPrevious = hours\n else if (row.bucket === 'month_current') monthCurrent = hours\n else if (row.bucket === 'month_previous') monthPrevious = hours\n }\n\n return {\n role: 'collab',\n myProjects,\n myHoursWeek: {\n current: weekCurrent,\n previous: weekPrevious,\n deltaPct: deltaPct(weekCurrent, weekPrevious),\n },\n myHoursMonth: {\n current: monthCurrent,\n previous: monthPrevious,\n deltaPct: deltaPct(monthCurrent, monthPrevious),\n },\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,41 @@
1
+ function getMondayUtc(input) {
2
+ const d = new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth(), input.getUTCDate()));
3
+ const day = d.getUTCDay();
4
+ const diff = day === 0 ? -6 : 1 - day;
5
+ d.setUTCDate(d.getUTCDate() + diff);
6
+ return d;
7
+ }
8
+ function addUtcDays(input, days) {
9
+ const d = new Date(input);
10
+ d.setUTCDate(d.getUTCDate() + days);
11
+ return d;
12
+ }
13
+ function getFirstDayOfMonthUtc(input) {
14
+ return new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth(), 1));
15
+ }
16
+ function getFirstDayOfNextMonthUtc(input) {
17
+ return new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth() + 1, 1));
18
+ }
19
+ function getLastNWeekStarts(n, now = /* @__PURE__ */ new Date()) {
20
+ const currentMonday = getMondayUtc(now);
21
+ const starts = [];
22
+ for (let i = n - 1; i >= 0; i--) {
23
+ starts.push(addUtcDays(currentMonday, -i * 7));
24
+ }
25
+ return starts;
26
+ }
27
+ function toDateOnlyString(date) {
28
+ const y = date.getUTCFullYear();
29
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
30
+ const d = String(date.getUTCDate()).padStart(2, "0");
31
+ return `${y}-${m}-${d}`;
32
+ }
33
+ export {
34
+ addUtcDays,
35
+ getFirstDayOfMonthUtc,
36
+ getFirstDayOfNextMonthUtc,
37
+ getLastNWeekStarts,
38
+ getMondayUtc,
39
+ toDateOnlyString
40
+ };
41
+ //# sourceMappingURL=dateBuckets.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/timesheets-projects/dateBuckets.ts"],
4
+ "sourcesContent": ["export function getMondayUtc(input: Date): Date {\n const d = new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth(), input.getUTCDate()))\n const day = d.getUTCDay()\n const diff = day === 0 ? -6 : 1 - day\n d.setUTCDate(d.getUTCDate() + diff)\n return d\n}\n\nexport function addUtcDays(input: Date, days: number): Date {\n const d = new Date(input)\n d.setUTCDate(d.getUTCDate() + days)\n return d\n}\n\nexport function getFirstDayOfMonthUtc(input: Date): Date {\n return new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth(), 1))\n}\n\nexport function getFirstDayOfNextMonthUtc(input: Date): Date {\n return new Date(Date.UTC(input.getUTCFullYear(), input.getUTCMonth() + 1, 1))\n}\n\nexport function getLastNWeekStarts(n: number, now: Date = new Date()): Date[] {\n const currentMonday = getMondayUtc(now)\n const starts: Date[] = []\n for (let i = n - 1; i >= 0; i--) {\n starts.push(addUtcDays(currentMonday, -i * 7))\n }\n return starts\n}\n\nexport function toDateOnlyString(date: Date): string {\n const y = date.getUTCFullYear()\n const m = String(date.getUTCMonth() + 1).padStart(2, '0')\n const d = String(date.getUTCDate()).padStart(2, '0')\n return `${y}-${m}-${d}`\n}\n"],
5
+ "mappings": "AAAO,SAAS,aAAa,OAAmB;AAC9C,QAAM,IAAI,IAAI,KAAK,KAAK,IAAI,MAAM,eAAe,GAAG,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,CAAC;AAC5F,QAAM,MAAM,EAAE,UAAU;AACxB,QAAM,OAAO,QAAQ,IAAI,KAAK,IAAI;AAClC,IAAE,WAAW,EAAE,WAAW,IAAI,IAAI;AAClC,SAAO;AACT;AAEO,SAAS,WAAW,OAAa,MAAoB;AAC1D,QAAM,IAAI,IAAI,KAAK,KAAK;AACxB,IAAE,WAAW,EAAE,WAAW,IAAI,IAAI;AAClC,SAAO;AACT;AAEO,SAAS,sBAAsB,OAAmB;AACvD,SAAO,IAAI,KAAK,KAAK,IAAI,MAAM,eAAe,GAAG,MAAM,YAAY,GAAG,CAAC,CAAC;AAC1E;AAEO,SAAS,0BAA0B,OAAmB;AAC3D,SAAO,IAAI,KAAK,KAAK,IAAI,MAAM,eAAe,GAAG,MAAM,YAAY,IAAI,GAAG,CAAC,CAAC;AAC9E;AAEO,SAAS,mBAAmB,GAAW,MAAY,oBAAI,KAAK,GAAW;AAC5E,QAAM,gBAAgB,aAAa,GAAG;AACtC,QAAM,SAAiB,CAAC;AACxB,WAAS,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;AAC/B,WAAO,KAAK,WAAW,eAAe,CAAC,IAAI,CAAC,CAAC;AAAA,EAC/C;AACA,SAAO;AACT;AAEO,SAAS,iBAAiB,MAAoB;AACnD,QAAM,IAAI,KAAK,eAAe;AAC9B,QAAM,IAAI,OAAO,KAAK,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACxD,QAAM,IAAI,OAAO,KAAK,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG;AACnD,SAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AACvB;",
6
+ "names": []
7
+ }
@@ -0,0 +1,10 @@
1
+ function computeInitials(displayName) {
2
+ const parts = displayName.trim().split(/\s+/).filter(Boolean);
3
+ if (parts.length === 0) return "?";
4
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
5
+ return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase();
6
+ }
7
+ export {
8
+ computeInitials
9
+ };
10
+ //# sourceMappingURL=initials.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/timesheets-projects/initials.ts"],
4
+ "sourcesContent": ["export function computeInitials(displayName: string): string {\n const parts = displayName.trim().split(/\\s+/).filter(Boolean)\n if (parts.length === 0) return '?'\n if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase()\n return `${parts[0][0]}${parts[parts.length - 1][0]}`.toUpperCase()\n}\n"],
5
+ "mappings": "AAAO,SAAS,gBAAgB,aAA6B;AAC3D,QAAM,QAAQ,YAAY,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,OAAO;AAC5D,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,MAAI,MAAM,WAAW,EAAG,QAAO,MAAM,CAAC,EAAE,MAAM,GAAG,CAAC,EAAE,YAAY;AAChE,SAAO,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,MAAM,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,YAAY;AACnE;",
6
+ "names": []
7
+ }
@@ -0,0 +1,12 @@
1
+ function deltaPct(current, previous) {
2
+ if (previous <= 0) return null;
3
+ return Math.round((current - previous) / previous * 100 * 10) / 10;
4
+ }
5
+ function minutesToHours(minutes) {
6
+ return Math.round(minutes / 60 * 10) / 10;
7
+ }
8
+ export {
9
+ deltaPct,
10
+ minutesToHours
11
+ };
12
+ //# sourceMappingURL=kpiMath.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/timesheets-projects/kpiMath.ts"],
4
+ "sourcesContent": ["export function deltaPct(current: number, previous: number): number | null {\n if (previous <= 0) return null\n return Math.round(((current - previous) / previous) * 100 * 10) / 10\n}\n\nexport function minutesToHours(minutes: number): number {\n return Math.round((minutes / 60) * 10) / 10\n}\n"],
5
+ "mappings": "AAAO,SAAS,SAAS,SAAiB,UAAiC;AACzE,MAAI,YAAY,EAAG,QAAO;AAC1B,SAAO,KAAK,OAAQ,UAAU,YAAY,WAAY,MAAM,EAAE,IAAI;AACpE;AAEO,SAAS,eAAe,SAAyB;AACtD,SAAO,KAAK,MAAO,UAAU,KAAM,EAAE,IAAI;AAC3C;",
6
+ "names": []
7
+ }
@@ -0,0 +1,55 @@
1
+ import { StaffTeamMember, StaffTimeProjectMember } from "../../data/entities.js";
2
+ import { computeInitials } from "./initials.js";
3
+ const DEFAULT_MAX = 4;
4
+ async function listProjectMembersPreview(scope) {
5
+ const maxPerProject = scope.maxPerProject ?? DEFAULT_MAX;
6
+ const result = /* @__PURE__ */ new Map();
7
+ for (const id of scope.projectIds) {
8
+ result.set(id, { total: 0, preview: [], myRole: null });
9
+ }
10
+ if (scope.projectIds.length === 0) return result;
11
+ const em = scope.em.fork();
12
+ const memberships = await em.find(
13
+ StaffTimeProjectMember,
14
+ {
15
+ timeProjectId: { $in: scope.projectIds },
16
+ tenantId: scope.tenantId,
17
+ organizationId: scope.organizationId,
18
+ status: "active",
19
+ deletedAt: null
20
+ },
21
+ { orderBy: { createdAt: "asc" } }
22
+ );
23
+ const staffMemberIds = Array.from(new Set(memberships.map((m) => m.staffMemberId)));
24
+ if (staffMemberIds.length === 0) return result;
25
+ const teamMembers = await em.find(StaffTeamMember, {
26
+ id: { $in: staffMemberIds },
27
+ tenantId: scope.tenantId,
28
+ organizationId: scope.organizationId,
29
+ deletedAt: null
30
+ });
31
+ const teamById = new Map(teamMembers.map((tm) => [tm.id, tm]));
32
+ for (const membership of memberships) {
33
+ const bucket = result.get(membership.timeProjectId);
34
+ if (!bucket) continue;
35
+ bucket.total += 1;
36
+ if (scope.callerStaffMemberId && membership.staffMemberId === scope.callerStaffMemberId) {
37
+ bucket.myRole = membership.role ?? null;
38
+ }
39
+ if (bucket.preview.length >= maxPerProject) continue;
40
+ const team = teamById.get(membership.staffMemberId);
41
+ if (!team) continue;
42
+ bucket.preview.push({
43
+ id: team.id,
44
+ name: team.displayName,
45
+ initials: computeInitials(team.displayName),
46
+ avatarUrl: null
47
+ });
48
+ }
49
+ return result;
50
+ }
51
+ export {
52
+ computeInitials,
53
+ listProjectMembersPreview
54
+ };
55
+ //# sourceMappingURL=listProjectMembersPreview.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts"],
4
+ "sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { StaffTeamMember, StaffTimeProjectMember } from '../../data/entities'\nimport { computeInitials } from './initials'\n\nexport { computeInitials }\n\nexport type MembersPreviewScope = {\n em: EntityManager\n organizationId: string\n tenantId: string\n projectIds: string[]\n maxPerProject?: number\n}\n\nexport type MemberPreview = {\n id: string\n name: string\n initials: string\n avatarUrl: string | null\n}\n\nexport type ProjectMembersPreview = {\n total: number\n preview: MemberPreview[]\n myRole: string | null\n}\n\nconst DEFAULT_MAX = 4\n\nexport async function listProjectMembersPreview(\n scope: MembersPreviewScope & { callerStaffMemberId?: string | null },\n): Promise<Map<string, ProjectMembersPreview>> {\n const maxPerProject = scope.maxPerProject ?? DEFAULT_MAX\n const result = new Map<string, ProjectMembersPreview>()\n for (const id of scope.projectIds) {\n result.set(id, { total: 0, preview: [], myRole: null })\n }\n if (scope.projectIds.length === 0) return result\n\n const em = scope.em.fork()\n const memberships = await em.find(\n StaffTimeProjectMember,\n {\n timeProjectId: { $in: scope.projectIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n status: 'active',\n deletedAt: null,\n },\n { orderBy: { createdAt: 'asc' } },\n )\n\n const staffMemberIds = Array.from(new Set(memberships.map((m) => m.staffMemberId)))\n if (staffMemberIds.length === 0) return result\n\n const teamMembers = await em.find(StaffTeamMember, {\n id: { $in: staffMemberIds },\n tenantId: scope.tenantId,\n organizationId: scope.organizationId,\n deletedAt: null,\n })\n const teamById = new Map(teamMembers.map((tm) => [tm.id, tm]))\n\n for (const membership of memberships) {\n const bucket = result.get(membership.timeProjectId)\n if (!bucket) continue\n bucket.total += 1\n if (scope.callerStaffMemberId && membership.staffMemberId === scope.callerStaffMemberId) {\n bucket.myRole = membership.role ?? null\n }\n if (bucket.preview.length >= maxPerProject) continue\n const team = teamById.get(membership.staffMemberId)\n if (!team) continue\n bucket.preview.push({\n id: team.id,\n name: team.displayName,\n initials: computeInitials(team.displayName),\n avatarUrl: null,\n })\n }\n\n return result\n}\n"],
5
+ "mappings": "AACA,SAAS,iBAAiB,8BAA8B;AACxD,SAAS,uBAAuB;AAyBhC,MAAM,cAAc;AAEpB,eAAsB,0BACpB,OAC6C;AAC7C,QAAM,gBAAgB,MAAM,iBAAiB;AAC7C,QAAM,SAAS,oBAAI,IAAmC;AACtD,aAAW,MAAM,MAAM,YAAY;AACjC,WAAO,IAAI,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC,GAAG,QAAQ,KAAK,CAAC;AAAA,EACxD;AACA,MAAI,MAAM,WAAW,WAAW,EAAG,QAAO;AAE1C,QAAM,KAAK,MAAM,GAAG,KAAK;AACzB,QAAM,cAAc,MAAM,GAAG;AAAA,IAC3B;AAAA,IACA;AAAA,MACE,eAAe,EAAE,KAAK,MAAM,WAAW;AAAA,MACvC,UAAU,MAAM;AAAA,MAChB,gBAAgB,MAAM;AAAA,MACtB,QAAQ;AAAA,MACR,WAAW;AAAA,IACb;AAAA,IACA,EAAE,SAAS,EAAE,WAAW,MAAM,EAAE;AAAA,EAClC;AAEA,QAAM,iBAAiB,MAAM,KAAK,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AAClF,MAAI,eAAe,WAAW,EAAG,QAAO;AAExC,QAAM,cAAc,MAAM,GAAG,KAAK,iBAAiB;AAAA,IACjD,IAAI,EAAE,KAAK,eAAe;AAAA,IAC1B,UAAU,MAAM;AAAA,IAChB,gBAAgB,MAAM;AAAA,IACtB,WAAW;AAAA,EACb,CAAC;AACD,QAAM,WAAW,IAAI,IAAI,YAAY,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC;AAE7D,aAAW,cAAc,aAAa;AACpC,UAAM,SAAS,OAAO,IAAI,WAAW,aAAa;AAClD,QAAI,CAAC,OAAQ;AACb,WAAO,SAAS;AAChB,QAAI,MAAM,uBAAuB,WAAW,kBAAkB,MAAM,qBAAqB;AACvF,aAAO,SAAS,WAAW,QAAQ;AAAA,IACrC;AACA,QAAI,OAAO,QAAQ,UAAU,cAAe;AAC5C,UAAM,OAAO,SAAS,IAAI,WAAW,aAAa;AAClD,QAAI,CAAC,KAAM;AACX,WAAO,QAAQ,KAAK;AAAA,MAClB,IAAI,KAAK;AAAA,MACT,MAAM,KAAK;AAAA,MACX,UAAU,gBAAgB,KAAK,WAAW;AAAA,MAC1C,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AAEA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ const DEFAULT_COLOR = "#6366f1";
4
+ function HoursSparkline({
5
+ values,
6
+ color = DEFAULT_COLOR,
7
+ width = 54,
8
+ height = 14,
9
+ ariaLabel,
10
+ className
11
+ }) {
12
+ const nonEmpty = values.length > 0;
13
+ const max = nonEmpty ? Math.max(...values, 0) : 0;
14
+ const allZero = !nonEmpty || max === 0;
15
+ if (allZero) {
16
+ return /* @__PURE__ */ jsx(
17
+ "svg",
18
+ {
19
+ role: "img",
20
+ "aria-label": ariaLabel,
21
+ width,
22
+ height,
23
+ viewBox: `0 0 ${width} ${height}`,
24
+ className,
25
+ children: /* @__PURE__ */ jsx(
26
+ "line",
27
+ {
28
+ x1: 0,
29
+ x2: width,
30
+ y1: height / 2,
31
+ y2: height / 2,
32
+ className: "stroke-border",
33
+ strokeWidth: 1
34
+ }
35
+ )
36
+ }
37
+ );
38
+ }
39
+ const stepX = values.length === 1 ? 0 : width / (values.length - 1);
40
+ const points = values.map((value, idx) => {
41
+ const x = values.length === 1 ? width / 2 : idx * stepX;
42
+ const y = height - value / max * (height - 2) - 1;
43
+ return { x, y };
44
+ });
45
+ const linePath = points.map((p, idx) => idx === 0 ? `M${p.x},${p.y}` : `L${p.x},${p.y}`).join(" ");
46
+ const areaPath = `${linePath} L${points[points.length - 1].x},${height} L${points[0].x},${height} Z`;
47
+ return /* @__PURE__ */ jsxs(
48
+ "svg",
49
+ {
50
+ role: "img",
51
+ "aria-label": ariaLabel,
52
+ width,
53
+ height,
54
+ viewBox: `0 0 ${width} ${height}`,
55
+ className,
56
+ children: [
57
+ /* @__PURE__ */ jsx("path", { d: areaPath, fill: color, opacity: 0.15 }),
58
+ /* @__PURE__ */ jsx("path", { d: linePath, fill: "none", stroke: color, strokeWidth: 1.25, strokeLinecap: "round", strokeLinejoin: "round" })
59
+ ]
60
+ }
61
+ );
62
+ }
63
+ export {
64
+ HoursSparkline
65
+ };
66
+ //# sourceMappingURL=HoursSparkline.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\n\nexport type HoursSparklineProps = {\n values: number[]\n color?: string\n width?: number\n height?: number\n ariaLabel: string\n className?: string\n}\n\nconst DEFAULT_COLOR = '#6366f1'\n\nexport function HoursSparkline({\n values,\n color = DEFAULT_COLOR,\n width = 54,\n height = 14,\n ariaLabel,\n className,\n}: HoursSparklineProps) {\n const nonEmpty = values.length > 0\n const max = nonEmpty ? Math.max(...values, 0) : 0\n const allZero = !nonEmpty || max === 0\n\n if (allZero) {\n return (\n <svg\n role=\"img\"\n aria-label={ariaLabel}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n className={className}\n >\n <line\n x1={0}\n x2={width}\n y1={height / 2}\n y2={height / 2}\n className=\"stroke-border\"\n strokeWidth={1}\n />\n </svg>\n )\n }\n\n const stepX = values.length === 1 ? 0 : width / (values.length - 1)\n const points = values.map((value, idx) => {\n const x = values.length === 1 ? width / 2 : idx * stepX\n const y = height - (value / max) * (height - 2) - 1\n return { x, y }\n })\n\n const linePath = points\n .map((p, idx) => (idx === 0 ? `M${p.x},${p.y}` : `L${p.x},${p.y}`))\n .join(' ')\n const areaPath = `${linePath} L${points[points.length - 1].x},${height} L${points[0].x},${height} Z`\n\n return (\n <svg\n role=\"img\"\n aria-label={ariaLabel}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n className={className}\n >\n <path d={areaPath} fill={color} opacity={0.15} />\n <path d={linePath} fill=\"none\" stroke={color} strokeWidth={1.25} strokeLinecap=\"round\" strokeLinejoin=\"round\" />\n </svg>\n )\n}\n"],
5
+ "mappings": ";AAqCQ,cAyBJ,YAzBI;AAxBR,MAAM,gBAAgB;AAEf,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT;AAAA,EACA;AACF,GAAwB;AACtB,QAAM,WAAW,OAAO,SAAS;AACjC,QAAM,MAAM,WAAW,KAAK,IAAI,GAAG,QAAQ,CAAC,IAAI;AAChD,QAAM,UAAU,CAAC,YAAY,QAAQ;AAErC,MAAI,SAAS;AACX,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,cAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA,SAAS,OAAO,KAAK,IAAI,MAAM;AAAA,QAC/B;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,IAAI;AAAA,YACJ,IAAI;AAAA,YACJ,IAAI,SAAS;AAAA,YACb,IAAI,SAAS;AAAA,YACb,WAAU;AAAA,YACV,aAAa;AAAA;AAAA,QACf;AAAA;AAAA,IACF;AAAA,EAEJ;AAEA,QAAM,QAAQ,OAAO,WAAW,IAAI,IAAI,SAAS,OAAO,SAAS;AACjE,QAAM,SAAS,OAAO,IAAI,CAAC,OAAO,QAAQ;AACxC,UAAM,IAAI,OAAO,WAAW,IAAI,QAAQ,IAAI,MAAM;AAClD,UAAM,IAAI,SAAU,QAAQ,OAAQ,SAAS,KAAK;AAClD,WAAO,EAAE,GAAG,EAAE;AAAA,EAChB,CAAC;AAED,QAAM,WAAW,OACd,IAAI,CAAC,GAAG,QAAS,QAAQ,IAAI,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,EAAG,EACjE,KAAK,GAAG;AACX,QAAM,WAAW,GAAG,QAAQ,KAAK,OAAO,OAAO,SAAS,CAAC,EAAE,CAAC,IAAI,MAAM,KAAK,OAAO,CAAC,EAAE,CAAC,IAAI,MAAM;AAEhG,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA,SAAS,OAAO,KAAK,IAAI,MAAM;AAAA,MAC/B;AAAA,MAEA;AAAA,4BAAC,UAAK,GAAG,UAAU,MAAM,OAAO,SAAS,MAAM;AAAA,QAC/C,oBAAC,UAAK,GAAG,UAAU,MAAK,QAAO,QAAQ,OAAO,aAAa,MAAM,eAAc,SAAQ,gBAAe,SAAQ;AAAA;AAAA;AAAA,EAChH;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,81 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import Link from "next/link";
4
+ import { ProjectColorDot } from "../timesheets-ui/ProjectColorDot.js";
5
+ import { resolveProjectColorHex } from "../timesheets-ui/colors.js";
6
+ import { HoursSparkline } from "./HoursSparkline.js";
7
+ import { ProjectMembersAvatarStack } from "./ProjectMembersAvatarStack.js";
8
+ const STATUS_BADGE_CLASSES = {
9
+ active: "bg-lime-100 text-lime-800 dark:bg-lime-900/30 dark:text-lime-300",
10
+ on_hold: "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300",
11
+ completed: "bg-muted text-muted-foreground"
12
+ };
13
+ function ProjectCard({ data, labels, showTeam, href }) {
14
+ const badgeClass = STATUS_BADGE_CLASSES[data.status] ?? "bg-muted text-muted-foreground";
15
+ const statusLabel = labels.statuses[data.status] ?? data.status;
16
+ const stripeColor = resolveProjectColorHex(data.color, data.name);
17
+ const hoursPanelLabel = showTeam ? labels.hoursPanelPm : labels.hoursPanelCollab;
18
+ return /* @__PURE__ */ jsxs(
19
+ Link,
20
+ {
21
+ href,
22
+ className: "group relative flex flex-col overflow-hidden rounded-lg border border-border bg-card transition-colors hover:border-foreground/30",
23
+ children: [
24
+ /* @__PURE__ */ jsx("div", { className: "h-[3px] w-full", style: { backgroundColor: stripeColor }, "aria-hidden": "true" }),
25
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 p-4", children: [
26
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
27
+ /* @__PURE__ */ jsx(
28
+ "span",
29
+ {
30
+ className: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${badgeClass}`,
31
+ children: statusLabel
32
+ }
33
+ ),
34
+ /* @__PURE__ */ jsx(ProjectColorDot, { colorKey: data.color, projectName: data.name, size: "sm" })
35
+ ] }),
36
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-0.5", children: [
37
+ /* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold text-foreground", title: data.name, children: data.name }),
38
+ /* @__PURE__ */ jsxs("p", { className: "truncate font-mono text-[11px] text-muted-foreground", children: [
39
+ data.code ?? "\u2014",
40
+ data.customerName ? ` \xB7 ${data.customerName}` : ""
41
+ ] })
42
+ ] }),
43
+ /* @__PURE__ */ jsxs("div", { className: "rounded-md border border-border/50 bg-muted/40 p-3", children: [
44
+ /* @__PURE__ */ jsx("p", { className: "text-[10px] uppercase tracking-wide text-muted-foreground", children: hoursPanelLabel }),
45
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex items-end justify-between gap-3", children: [
46
+ /* @__PURE__ */ jsx("p", { className: "text-xl font-semibold tabular-nums text-foreground", children: data.hoursWeek > 0 ? `${data.hoursWeek}h` : "\u2014" }),
47
+ /* @__PURE__ */ jsx(
48
+ HoursSparkline,
49
+ {
50
+ values: data.hoursTrend,
51
+ color: stripeColor,
52
+ width: 80,
53
+ height: 26,
54
+ ariaLabel: labels.sparklineAria
55
+ }
56
+ )
57
+ ] })
58
+ ] }),
59
+ /* @__PURE__ */ jsx("div", { className: "mt-auto flex items-center justify-between pt-1", children: showTeam ? /* @__PURE__ */ jsx(
60
+ ProjectMembersAvatarStack,
61
+ {
62
+ members: data.members,
63
+ total: data.memberCount,
64
+ peopleCountLabel: labels.peopleCount(data.memberCount)
65
+ }
66
+ ) : /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
67
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground/70", children: [
68
+ labels.role,
69
+ ": "
70
+ ] }),
71
+ data.myRole ?? "\u2014"
72
+ ] }) })
73
+ ] })
74
+ ]
75
+ }
76
+ );
77
+ }
78
+ export {
79
+ ProjectCard
80
+ };
81
+ //# sourceMappingURL=ProjectCard.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { ProjectColorDot } from '../timesheets-ui/ProjectColorDot'\nimport { resolveProjectColorHex } from '../timesheets-ui/colors'\nimport { HoursSparkline } from './HoursSparkline'\nimport { ProjectMembersAvatarStack, type AvatarMember } from './ProjectMembersAvatarStack'\n\nexport type ProjectCardData = {\n id: string\n name: string\n code: string | null\n customerName: string | null\n color: string | null\n status: string\n hoursWeek: number\n hoursTrend: number[]\n members: AvatarMember[]\n memberCount: number\n myRole: string | null\n updatedAt: string | null\n}\n\nexport type ProjectCardLabels = {\n hoursPanelPm: string\n hoursPanelCollab: string\n sparklineAria: string\n peopleCount: (count: number) => string\n role: string\n noCustomer: string\n statuses: Record<string, string>\n}\n\nexport type ProjectCardProps = {\n data: ProjectCardData\n labels: ProjectCardLabels\n showTeam: boolean\n href: string\n}\n\nconst STATUS_BADGE_CLASSES: Record<string, string> = {\n active: 'bg-lime-100 text-lime-800 dark:bg-lime-900/30 dark:text-lime-300',\n on_hold: 'bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300',\n completed: 'bg-muted text-muted-foreground',\n}\n\nexport function ProjectCard({ data, labels, showTeam, href }: ProjectCardProps) {\n const badgeClass = STATUS_BADGE_CLASSES[data.status] ?? 'bg-muted text-muted-foreground'\n const statusLabel = labels.statuses[data.status] ?? data.status\n const stripeColor = resolveProjectColorHex(data.color, data.name)\n const hoursPanelLabel = showTeam ? labels.hoursPanelPm : labels.hoursPanelCollab\n\n return (\n <Link\n href={href}\n className=\"group relative flex flex-col overflow-hidden rounded-lg border border-border bg-card transition-colors hover:border-foreground/30\"\n >\n <div className=\"h-[3px] w-full\" style={{ backgroundColor: stripeColor }} aria-hidden=\"true\" />\n <div className=\"flex flex-1 flex-col gap-3 p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${badgeClass}`}\n >\n {statusLabel}\n </span>\n <ProjectColorDot colorKey={data.color} projectName={data.name} size=\"sm\" />\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <h3 className=\"truncate text-sm font-semibold text-foreground\" title={data.name}>\n {data.name}\n </h3>\n <p className=\"truncate font-mono text-[11px] text-muted-foreground\">\n {data.code ?? '\u2014'}\n {data.customerName ? ` \u00B7 ${data.customerName}` : ''}\n </p>\n </div>\n <div className=\"rounded-md border border-border/50 bg-muted/40 p-3\">\n <p className=\"text-[10px] uppercase tracking-wide text-muted-foreground\">{hoursPanelLabel}</p>\n <div className=\"mt-1 flex items-end justify-between gap-3\">\n <p className=\"text-xl font-semibold tabular-nums text-foreground\">\n {data.hoursWeek > 0 ? `${data.hoursWeek}h` : '\u2014'}\n </p>\n <HoursSparkline\n values={data.hoursTrend}\n color={stripeColor}\n width={80}\n height={26}\n ariaLabel={labels.sparklineAria}\n />\n </div>\n </div>\n <div className=\"mt-auto flex items-center justify-between pt-1\">\n {showTeam ? (\n <ProjectMembersAvatarStack\n members={data.members}\n total={data.memberCount}\n peopleCountLabel={labels.peopleCount(data.memberCount)}\n />\n ) : (\n <p className=\"text-xs text-muted-foreground\">\n <span className=\"text-muted-foreground/70\">{labels.role}: </span>\n {data.myRole ?? '\u2014'}\n </p>\n )}\n </div>\n </div>\n </Link>\n )\n}\n"],
5
+ "mappings": ";AA0DM,cAEE,YAFF;AAvDN,OAAO,UAAU;AACjB,SAAS,uBAAuB;AAChC,SAAS,8BAA8B;AACvC,SAAS,sBAAsB;AAC/B,SAAS,iCAAoD;AAkC7D,MAAM,uBAA+C;AAAA,EACnD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,WAAW;AACb;AAEO,SAAS,YAAY,EAAE,MAAM,QAAQ,UAAU,KAAK,GAAqB;AAC9E,QAAM,aAAa,qBAAqB,KAAK,MAAM,KAAK;AACxD,QAAM,cAAc,OAAO,SAAS,KAAK,MAAM,KAAK,KAAK;AACzD,QAAM,cAAc,uBAAuB,KAAK,OAAO,KAAK,IAAI;AAChE,QAAM,kBAAkB,WAAW,OAAO,eAAe,OAAO;AAEhE,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,WAAU;AAAA,MAEV;AAAA,4BAAC,SAAI,WAAU,kBAAiB,OAAO,EAAE,iBAAiB,YAAY,GAAG,eAAY,QAAO;AAAA,QAC5F,qBAAC,SAAI,WAAU,kCACb;AAAA,+BAAC,SAAI,WAAU,2CACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAW,6EAA6E,UAAU;AAAA,gBAEjG;AAAA;AAAA,YACH;AAAA,YACA,oBAAC,mBAAgB,UAAU,KAAK,OAAO,aAAa,KAAK,MAAM,MAAK,MAAK;AAAA,aAC3E;AAAA,UACA,qBAAC,SAAI,WAAU,yBACb;AAAA,gCAAC,QAAG,WAAU,kDAAiD,OAAO,KAAK,MACxE,eAAK,MACR;AAAA,YACA,qBAAC,OAAE,WAAU,wDACV;AAAA,mBAAK,QAAQ;AAAA,cACb,KAAK,eAAe,SAAM,KAAK,YAAY,KAAK;AAAA,eACnD;AAAA,aACF;AAAA,UACA,qBAAC,SAAI,WAAU,sDACb;AAAA,gCAAC,OAAE,WAAU,6DAA6D,2BAAgB;AAAA,YAC1F,qBAAC,SAAI,WAAU,6CACb;AAAA,kCAAC,OAAE,WAAU,sDACV,eAAK,YAAY,IAAI,GAAG,KAAK,SAAS,MAAM,UAC/C;AAAA,cACA;AAAA,gBAAC;AAAA;AAAA,kBACC,QAAQ,KAAK;AAAA,kBACb,OAAO;AAAA,kBACP,OAAO;AAAA,kBACP,QAAQ;AAAA,kBACR,WAAW,OAAO;AAAA;AAAA,cACpB;AAAA,eACF;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,WAAU,kDACZ,qBACC;AAAA,YAAC;AAAA;AAAA,cACC,SAAS,KAAK;AAAA,cACd,OAAO,KAAK;AAAA,cACZ,kBAAkB,OAAO,YAAY,KAAK,WAAW;AAAA;AAAA,UACvD,IAEA,qBAAC,OAAE,WAAU,iCACX;AAAA,iCAAC,UAAK,WAAU,4BAA4B;AAAA,qBAAO;AAAA,cAAK;AAAA,eAAE;AAAA,YACzD,KAAK,UAAU;AAAA,aAClB,GAEJ;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;",
6
+ "names": []
7
+ }