@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,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/staff/backend/staff/timesheets/projects/create/page.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { CrudForm } from '@open-mercato/ui/backend/CrudForm'\nimport { createCrud } from '@open-mercato/ui/backend/utils/crud'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { E } from '#generated/entities.ids.generated'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n buildProjectPayload,\n createProjectFormFields,\n createProjectFormGroups,\n createProjectFormSchema,\n type ProjectFormValues,\n} from '../projectFormConfig'\n\nconst BACK_HREF = '/backend/staff/timesheets/projects'\n\nexport default function TimesheetProjectCreatePage() {\n const t = useT()\n const router = useRouter()\n\n const formSchema = React.useMemo(() => createProjectFormSchema(), [])\n const fields = React.useMemo(() => createProjectFormFields(t), [t])\n const groups = React.useMemo(() => createProjectFormGroups(t), [t])\n\n return (\n <Page>\n <PageBody>\n <CrudForm<ProjectFormValues>\n title={t('staff.timesheets.projects.form.createTitle', 'Create project')}\n backHref={BACK_HREF}\n cancelHref={BACK_HREF}\n fields={fields}\n groups={groups}\n schema={formSchema}\n initialValues={{}}\n entityIds={[E.staff.staff_time_project]}\n submitLabel={t('staff.timesheets.projects.form.actions.create', 'Create')}\n onSubmit={async (values) => {\n if (!values.name?.trim() || !values.code?.trim()) {\n const fieldErrors: Record<string, string> = {}\n if (!values.name?.trim()) fieldErrors.name = 'Required'\n if (!values.code?.trim()) fieldErrors.code = 'Required'\n throw createCrudFormError(\n t('staff.timesheets.projects.errors.required', 'Name and code are required.'),\n fieldErrors,\n )\n }\n\n const payload = buildProjectPayload(values)\n\n const { result: created } = await createCrud<{ id?: string }>(\n 'staff/timesheets/time-projects',\n payload,\n { errorMessage: t('staff.timesheets.projects.errors.save', 'Failed to save project.') },\n )\n\n const newId = created?.id\n\n // Auto-assign creator to the project\n if (newId) {\n try {\n const selfRes = await apiCall<{ member?: { id?: string } | null }>('/api/staff/team-members/self')\n const staffMemberId = selfRes.result?.member?.id\n if (staffMemberId) {\n await createCrud('staff/timesheets/time-projects/' + newId + '/employees', {\n staffMemberId,\n assignedStartDate: new Date().toISOString().slice(0, 10),\n status: 'active',\n }, { errorMessage: '' })\n }\n } catch {\n // non-critical \u2014 project created, self-assignment is best-effort\n }\n }\n\n flash(t('staff.timesheets.projects.messages.saved', 'Project saved.'), 'success')\n if (newId) router.push(`/backend/staff/timesheets/projects/${newId}`)\n else router.push(BACK_HREF)\n }}\n />\n </PageBody>\n </Page>\n )\n}\n"],
5
+ "mappings": ";AAiCQ;AA/BR,YAAY,WAAW;AACvB,SAAS,iBAAiB;AAC1B,SAAS,MAAM,gBAAgB;AAC/B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,eAAe;AACxB,SAAS,2BAA2B;AACpC,SAAS,SAAS;AAClB,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP,MAAM,YAAY;AAEH,SAAR,6BAA8C;AACnD,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AAEzB,QAAM,aAAa,MAAM,QAAQ,MAAM,wBAAwB,GAAG,CAAC,CAAC;AACpE,QAAM,SAAS,MAAM,QAAQ,MAAM,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;AAClE,QAAM,SAAS,MAAM,QAAQ,MAAM,wBAAwB,CAAC,GAAG,CAAC,CAAC,CAAC;AAElE,SACE,oBAAC,QACC,8BAAC,YACC;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,8CAA8C,gBAAgB;AAAA,MACvE,UAAU;AAAA,MACV,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,eAAe,CAAC;AAAA,MAChB,WAAW,CAAC,EAAE,MAAM,kBAAkB;AAAA,MACtC,aAAa,EAAE,iDAAiD,QAAQ;AAAA,MACxE,UAAU,OAAO,WAAW;AAC1B,YAAI,CAAC,OAAO,MAAM,KAAK,KAAK,CAAC,OAAO,MAAM,KAAK,GAAG;AAChD,gBAAM,cAAsC,CAAC;AAC7C,cAAI,CAAC,OAAO,MAAM,KAAK,EAAG,aAAY,OAAO;AAC7C,cAAI,CAAC,OAAO,MAAM,KAAK,EAAG,aAAY,OAAO;AAC7C,gBAAM;AAAA,YACJ,EAAE,6CAA6C,6BAA6B;AAAA,YAC5E;AAAA,UACF;AAAA,QACF;AAEA,cAAM,UAAU,oBAAoB,MAAM;AAE1C,cAAM,EAAE,QAAQ,QAAQ,IAAI,MAAM;AAAA,UAChC;AAAA,UACA;AAAA,UACA,EAAE,cAAc,EAAE,yCAAyC,yBAAyB,EAAE;AAAA,QACxF;AAEA,cAAM,QAAQ,SAAS;AAGvB,YAAI,OAAO;AACT,cAAI;AACF,kBAAM,UAAU,MAAM,QAA6C,8BAA8B;AACjG,kBAAM,gBAAgB,QAAQ,QAAQ,QAAQ;AAC9C,gBAAI,eAAe;AACjB,oBAAM,WAAW,oCAAoC,QAAQ,cAAc;AAAA,gBACzE;AAAA,gBACA,oBAAmB,oBAAI,KAAK,GAAE,YAAY,EAAE,MAAM,GAAG,EAAE;AAAA,gBACvD,QAAQ;AAAA,cACV,GAAG,EAAE,cAAc,GAAG,CAAC;AAAA,YACzB;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AAEA,cAAM,EAAE,4CAA4C,gBAAgB,GAAG,SAAS;AAChF,YAAI,MAAO,QAAO,KAAK,sCAAsC,KAAK,EAAE;AAAA,YAC/D,QAAO,KAAK,SAAS;AAAA,MAC5B;AAAA;AAAA,EACF,GACF,GACF;AAEJ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ const metadata = {
2
+ requireAuth: true,
3
+ requireFeatures: ["staff.timesheets.projects.manage"],
4
+ navHidden: true,
5
+ pageTitle: "Create Project",
6
+ pageTitleKey: "staff.timesheets.nav.create_project",
7
+ breadcrumb: [
8
+ { label: "My Timesheets", labelKey: "staff.timesheets.nav.my_timesheets", href: "/backend/staff/timesheets" },
9
+ { label: "Projects", labelKey: "staff.timesheets.nav.projects", href: "/backend/staff/timesheets/projects" },
10
+ { label: "Create", labelKey: "staff.timesheets.nav.create_project" }
11
+ ]
12
+ };
13
+ export {
14
+ metadata
15
+ };
16
+ //# sourceMappingURL=page.meta.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../../../src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts"],
4
+ "sourcesContent": ["export const metadata = {\n requireAuth: true,\n requireFeatures: ['staff.timesheets.projects.manage'],\n navHidden: true,\n pageTitle: 'Create Project',\n pageTitleKey: 'staff.timesheets.nav.create_project',\n breadcrumb: [\n { label: 'My Timesheets', labelKey: 'staff.timesheets.nav.my_timesheets', href: '/backend/staff/timesheets' },\n { label: 'Projects', labelKey: 'staff.timesheets.nav.projects', href: '/backend/staff/timesheets/projects' },\n { label: 'Create', labelKey: 'staff.timesheets.nav.create_project' },\n ],\n}\n"],
5
+ "mappings": "AAAO,MAAM,WAAW;AAAA,EACtB,aAAa;AAAA,EACb,iBAAiB,CAAC,kCAAkC;AAAA,EACpD,WAAW;AAAA,EACX,WAAW;AAAA,EACX,cAAc;AAAA,EACd,YAAY;AAAA,IACV,EAAE,OAAO,iBAAiB,UAAU,sCAAsC,MAAM,4BAA4B;AAAA,IAC5G,EAAE,OAAO,YAAY,UAAU,iCAAiC,MAAM,qCAAqC;AAAA,IAC3G,EAAE,OAAO,UAAU,UAAU,sCAAsC;AAAA,EACrE;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,602 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { useRouter, useSearchParams } from "next/navigation";
6
+ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
7
+ import { DataTable, withDataTableNamespaces } from "@open-mercato/ui/backend/DataTable";
8
+ import { RowActions } from "@open-mercato/ui/backend/RowActions";
9
+ import { Button } from "@open-mercato/ui/primitives/button";
10
+ import { readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
11
+ import { deleteCrud } from "@open-mercato/ui/backend/utils/crud";
12
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
13
+ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
14
+ import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
15
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
16
+ import { formatDateTime } from "@open-mercato/shared/lib/time";
17
+ import { ProjectColorDot } from "../../../../lib/timesheets-ui/ProjectColorDot.js";
18
+ import { resolveProjectColorHex } from "../../../../lib/timesheets-ui/colors.js";
19
+ import {
20
+ ProjectsKpiStrip
21
+ } from "../../../../lib/timesheets-projects-ui/ProjectsKpiStrip.js";
22
+ import { SavedViewTabs } from "../../../../lib/timesheets-projects-ui/SavedViewTabs.js";
23
+ import { HoursSparkline } from "../../../../lib/timesheets-projects-ui/HoursSparkline.js";
24
+ import {
25
+ ProjectMembersAvatarStack
26
+ } from "../../../../lib/timesheets-projects-ui/ProjectMembersAvatarStack.js";
27
+ import { ViewModeToggle } from "../../../../lib/timesheets-projects-ui/ViewModeToggle.js";
28
+ import {
29
+ useProjectsViewMode
30
+ } from "../../../../lib/timesheets-projects-ui/useProjectsViewMode.js";
31
+ import {
32
+ ProjectCard
33
+ } from "../../../../lib/timesheets-projects-ui/ProjectCard.js";
34
+ const PAGE_SIZE = 50;
35
+ const INCLUDE_FIELDS = "hoursWeek,hoursTrend,members,myRole";
36
+ function formatRelativeTime(iso, fallback, t) {
37
+ if (!iso) return fallback;
38
+ const parsed = new Date(iso);
39
+ if (Number.isNaN(parsed.getTime())) return fallback;
40
+ const diffMs = Date.now() - parsed.getTime();
41
+ const minutes = Math.round(diffMs / 6e4);
42
+ if (minutes < 1) return t("staff.timesheets.projects.portfolio.relativeTime.justNow", "just now");
43
+ if (minutes < 60) return t("staff.timesheets.projects.portfolio.relativeTime.minutesAgo", "{minutes}m ago", { minutes });
44
+ const hours = Math.round(minutes / 60);
45
+ if (hours < 24) return t("staff.timesheets.projects.portfolio.relativeTime.hoursAgo", "{hours}h ago", { hours });
46
+ const days = Math.round(hours / 24);
47
+ if (days < 14) return t("staff.timesheets.projects.portfolio.relativeTime.daysAgo", "{days}d ago", { days });
48
+ return formatDateTime(iso) ?? fallback;
49
+ }
50
+ function mapApiProject(item) {
51
+ const id = typeof item.id === "string" ? item.id : "";
52
+ const name = typeof item.name === "string" ? item.name : id;
53
+ const code = typeof item.code === "string" && item.code.trim().length ? item.code.trim() : null;
54
+ const customerId = typeof item.customerId === "string" ? item.customerId : typeof item.customer_id === "string" ? item.customer_id : null;
55
+ const customerName = typeof item.customerName === "string" ? item.customerName : typeof item.customer_name === "string" ? item.customer_name : null;
56
+ const status = typeof item.status === "string" ? item.status : "active";
57
+ const projectType = typeof item.projectType === "string" ? item.projectType : typeof item.project_type === "string" ? item.project_type : null;
58
+ const color = typeof item.color === "string" ? item.color : null;
59
+ const startDate = typeof item.startDate === "string" ? item.startDate : typeof item.start_date === "string" ? item.start_date : null;
60
+ const updatedAt = typeof item.updatedAt === "string" ? item.updatedAt : typeof item.updated_at === "string" ? item.updated_at : null;
61
+ const enrichment = item._staff ?? {};
62
+ const hoursWeek = typeof enrichment.hoursWeek === "number" ? enrichment.hoursWeek : 0;
63
+ const hoursTrend = Array.isArray(enrichment.hoursTrend) ? enrichment.hoursTrend.filter((v) => typeof v === "number") : [];
64
+ const members = Array.isArray(enrichment.members) ? enrichment.members.filter((m) => !!m && typeof m === "object" && typeof m.id === "string").map((m) => ({
65
+ id: m.id,
66
+ name: m.name ?? "",
67
+ initials: m.initials ?? "",
68
+ avatarUrl: m.avatarUrl ?? null
69
+ })) : [];
70
+ const memberCount = typeof enrichment.memberCount === "number" ? enrichment.memberCount : members.length;
71
+ const myRole = typeof enrichment.myRole === "string" ? enrichment.myRole : null;
72
+ return withDataTableNamespaces(
73
+ {
74
+ id,
75
+ name,
76
+ code,
77
+ customerId,
78
+ customerName,
79
+ status,
80
+ projectType,
81
+ color,
82
+ startDate,
83
+ updatedAt,
84
+ hoursWeek,
85
+ hoursTrend,
86
+ myRole,
87
+ members,
88
+ memberCount
89
+ },
90
+ item
91
+ );
92
+ }
93
+ function TimesheetProjectsPage() {
94
+ const t = useT();
95
+ const router = useRouter();
96
+ const searchParams = useSearchParams();
97
+ const scopeVersion = useOrganizationScopeVersion();
98
+ const { confirm, ConfirmDialogElement } = useConfirmDialog();
99
+ const [rows, setRows] = React.useState([]);
100
+ const [page, setPage] = React.useState(1);
101
+ const [total, setTotal] = React.useState(0);
102
+ const [totalPages, setTotalPages] = React.useState(1);
103
+ const [sorting, setSorting] = React.useState([{ id: "updatedAt", desc: true }]);
104
+ const [search, setSearch] = React.useState("");
105
+ const [filterValues, setFilterValues] = React.useState({});
106
+ const [isLoading, setIsLoading] = React.useState(true);
107
+ const [isRefreshing, setIsRefreshing] = React.useState(false);
108
+ const hasLoadedOnceRef = React.useRef(false);
109
+ const [reloadToken, setReloadToken] = React.useState(0);
110
+ const [kpis, setKpis] = React.useState(null);
111
+ const [isLoadingKpis, setIsLoadingKpis] = React.useState(true);
112
+ const activeTab = searchParams.get("tab") ?? "all";
113
+ const urlViewMode = searchParams.get("view");
114
+ const [viewMode, setViewMode] = useProjectsViewMode({
115
+ userKey: null,
116
+ urlOverride: urlViewMode
117
+ });
118
+ const labels = React.useMemo(
119
+ () => ({
120
+ title: t("staff.timesheets.projects.page.title", "Projects"),
121
+ table: {
122
+ name: t("staff.timesheets.projects.table.name", "Project"),
123
+ status: t("staff.timesheets.projects.table.status", "Status"),
124
+ type: t("staff.timesheets.projects.table.type", "Type"),
125
+ updatedAt: t("staff.timesheets.projects.table.updatedAt", "Updated"),
126
+ empty: t("staff.timesheets.projects.table.empty", "No projects yet."),
127
+ search: t("staff.timesheets.projects.table.search", "Search projects..."),
128
+ team: t("staff.timesheets.projects.portfolio.team", "Team"),
129
+ myRole: t("staff.timesheets.projects.portfolio.myRole", "My role"),
130
+ hoursWeek: t("staff.timesheets.projects.portfolio.hoursWeek", "Hours / week"),
131
+ myHoursWeek: t("staff.timesheets.projects.portfolio.myHoursWeek", "My hours / week")
132
+ },
133
+ actions: {
134
+ add: t("staff.timesheets.projects.actions.add", "Add Project"),
135
+ addFirst: t("staff.timesheets.projects.actions.addFirst", "+ Add first project"),
136
+ viewDetails: t("staff.timesheets.projects.actions.viewDetails", "View Details"),
137
+ delete: t("staff.timesheets.projects.actions.delete", "Delete"),
138
+ deleteConfirm: t("staff.timesheets.projects.actions.deleteConfirm", 'Delete project "{{name}}"?'),
139
+ refresh: t("staff.timesheets.projects.actions.refresh", "Refresh")
140
+ },
141
+ messages: {
142
+ deleted: t("staff.timesheets.projects.messages.deleted", "Project deleted.")
143
+ },
144
+ errors: {
145
+ load: t("staff.timesheets.projects.errors.load", "Failed to load projects."),
146
+ delete: t("staff.timesheets.projects.errors.delete", "Failed to delete project.")
147
+ },
148
+ statuses: {
149
+ all: t("staff.timesheets.projects.statuses.all", "All"),
150
+ active: t("staff.timesheets.projects.statuses.active", "Active"),
151
+ on_hold: t("staff.timesheets.projects.statuses.onHold", "On Hold"),
152
+ completed: t("staff.timesheets.projects.statuses.completed", "Completed")
153
+ },
154
+ tabs: {
155
+ all: t("staff.timesheets.projects.portfolio.tabs.all", "All"),
156
+ active: t("staff.timesheets.projects.portfolio.tabs.active", "Active"),
157
+ onHold: t("staff.timesheets.projects.portfolio.tabs.onHold", "On Hold"),
158
+ completed: t("staff.timesheets.projects.portfolio.tabs.completed", "Completed"),
159
+ mine: t("staff.timesheets.projects.portfolio.tabs.mine", "Mine")
160
+ },
161
+ viewMode: {
162
+ table: t("staff.timesheets.projects.portfolio.viewMode.table", "Table"),
163
+ cards: t("staff.timesheets.projects.portfolio.viewMode.cards", "Cards")
164
+ },
165
+ kpi: {
166
+ totalProjects: t("staff.timesheets.projects.portfolio.kpi.totalProjects", "Total Projects"),
167
+ hoursWeek: t("staff.timesheets.projects.portfolio.kpi.hoursWeek", "Hours this week"),
168
+ hoursWeekSub: t("staff.timesheets.projects.portfolio.kpi.hoursWeekSub", "vs previous week"),
169
+ assignedToMe: t("staff.timesheets.projects.portfolio.kpi.assignedToMe", "Assigned to me"),
170
+ hoursMonth: t("staff.timesheets.projects.portfolio.kpi.hoursMonth", "Hours this month"),
171
+ hoursMonthSub: t("staff.timesheets.projects.portfolio.kpi.hoursMonthSub", "vs previous month"),
172
+ teamActive: t("staff.timesheets.projects.portfolio.kpi.teamActive", "Active team"),
173
+ teamActiveSub: t("staff.timesheets.projects.portfolio.kpi.teamActiveSub", "Members with entries this month"),
174
+ myProjects: t("staff.timesheets.projects.portfolio.kpi.myProjects", "My projects"),
175
+ myHoursWeek: t("staff.timesheets.projects.portfolio.kpi.myHoursWeek", "My hours this week"),
176
+ myHoursMonth: t("staff.timesheets.projects.portfolio.kpi.myHoursMonth", "My hours this month"),
177
+ deltaFlat: t("staff.timesheets.projects.portfolio.kpi.deltaFlat", "no change"),
178
+ noPrevious: t("staff.timesheets.projects.portfolio.kpi.noPrevious", "no previous data")
179
+ },
180
+ card: {
181
+ hoursPanelPm: t("staff.timesheets.projects.portfolio.card.hoursPanelPm", "Team hours \xB7 last 7w"),
182
+ hoursPanelCollab: t("staff.timesheets.projects.portfolio.card.hoursPanelCollab", "My hours \xB7 last 7w"),
183
+ sparklineAria: t("staff.timesheets.projects.portfolio.sparkline.ariaLabel", "Hours per week, last 7 weeks"),
184
+ role: t("staff.timesheets.projects.portfolio.card.role", "Role")
185
+ },
186
+ emptyState: {
187
+ noProjects: t("staff.timesheets.projects.portfolio.emptyState.noProjects", "No projects yet."),
188
+ noAssignments: t(
189
+ "staff.timesheets.projects.portfolio.emptyState.noAssignments",
190
+ "You aren't assigned to any projects yet. Ask a PM to add you."
191
+ ),
192
+ noMatches: t("staff.timesheets.projects.portfolio.emptyState.noMatches", "No projects match these filters.")
193
+ }
194
+ }),
195
+ [t]
196
+ );
197
+ const kpiLabels = React.useMemo(
198
+ () => ({
199
+ totalProjects: labels.kpi.totalProjects,
200
+ totalProjectsSub: ({ active, onHold }) => `${active} ${labels.statuses.active.toLowerCase()} \xB7 ${onHold} ${labels.statuses.on_hold.toLowerCase()}`,
201
+ hoursWeek: labels.kpi.hoursWeek,
202
+ hoursWeekSub: labels.kpi.hoursWeekSub,
203
+ assignedToMe: labels.kpi.assignedToMe,
204
+ assignedToMeSub: (active) => `${active} ${labels.statuses.active.toLowerCase()}`,
205
+ hoursMonth: labels.kpi.hoursMonth,
206
+ hoursMonthSub: labels.kpi.hoursMonthSub,
207
+ teamActive: labels.kpi.teamActive,
208
+ teamActiveSub: labels.kpi.teamActiveSub,
209
+ myProjects: labels.kpi.myProjects,
210
+ myProjectsSub: (active) => `${active} ${labels.statuses.active.toLowerCase()}`,
211
+ myHoursWeek: labels.kpi.myHoursWeek,
212
+ myHoursMonth: labels.kpi.myHoursMonth,
213
+ deltaUp: (pct) => `up ${pct}%`,
214
+ deltaDown: (pct) => `down ${pct}%`,
215
+ deltaFlat: labels.kpi.deltaFlat,
216
+ noPrevious: labels.kpi.noPrevious
217
+ }),
218
+ [labels]
219
+ );
220
+ const isPmRole = kpis?.role === "pm";
221
+ const filters = React.useMemo(
222
+ () => [
223
+ {
224
+ id: "status",
225
+ label: labels.table.status,
226
+ type: "select",
227
+ options: [
228
+ { value: "active", label: labels.statuses.active },
229
+ { value: "on_hold", label: labels.statuses.on_hold },
230
+ { value: "completed", label: labels.statuses.completed }
231
+ ]
232
+ }
233
+ ],
234
+ [labels.table.status, labels.statuses]
235
+ );
236
+ const tabs = React.useMemo(() => {
237
+ const base = [
238
+ { id: "all", label: labels.tabs.all },
239
+ { id: "active", label: labels.tabs.active }
240
+ ];
241
+ if (isPmRole) {
242
+ base.push({ id: "on_hold", label: labels.tabs.onHold });
243
+ }
244
+ base.push({ id: "completed", label: labels.tabs.completed });
245
+ if (isPmRole) {
246
+ base.push({ id: "mine", label: labels.tabs.mine });
247
+ }
248
+ return base;
249
+ }, [labels.tabs, isPmRole]);
250
+ const statusFromTab = (tabId) => {
251
+ if (tabId === "active" || tabId === "on_hold" || tabId === "completed") return tabId;
252
+ return null;
253
+ };
254
+ const mineFromTab = (tabId) => tabId === "mine" || !isPmRole;
255
+ const loadKpis = React.useCallback(async () => {
256
+ setIsLoadingKpis(true);
257
+ try {
258
+ const payload = await readApiResultOrThrow(
259
+ "/api/staff/timesheets/projects/kpis",
260
+ void 0,
261
+ {
262
+ errorMessage: labels.errors.load,
263
+ fallback: null
264
+ }
265
+ );
266
+ setKpis(payload);
267
+ } catch {
268
+ setKpis(null);
269
+ } finally {
270
+ setIsLoadingKpis(false);
271
+ }
272
+ }, [labels.errors.load]);
273
+ const loadProjects = React.useCallback(async () => {
274
+ if (hasLoadedOnceRef.current) {
275
+ setIsRefreshing(true);
276
+ } else {
277
+ setIsLoading(true);
278
+ }
279
+ try {
280
+ const params = new URLSearchParams({
281
+ page: String(page),
282
+ pageSize: String(PAGE_SIZE),
283
+ include: INCLUDE_FIELDS
284
+ });
285
+ const sort = sorting[0];
286
+ if (sort?.id) {
287
+ params.set("sortField", sort.id);
288
+ params.set("sortDir", sort.desc ? "desc" : "asc");
289
+ }
290
+ if (search.trim()) params.set("q", search.trim());
291
+ const tabStatus = statusFromTab(activeTab);
292
+ if (tabStatus) params.set("status", tabStatus);
293
+ else if (typeof filterValues.status === "string" && filterValues.status.length > 0) {
294
+ params.set("status", filterValues.status);
295
+ }
296
+ if (mineFromTab(activeTab)) params.set("mine", "1");
297
+ const payload = await readApiResultOrThrow(
298
+ `/api/staff/timesheets/time-projects?${params.toString()}`,
299
+ void 0,
300
+ { errorMessage: labels.errors.load, fallback: { items: [], total: 0, totalPages: 1 } }
301
+ );
302
+ const items = Array.isArray(payload.items) ? payload.items : [];
303
+ setRows(items.map(mapApiProject));
304
+ setTotal(typeof payload.total === "number" ? payload.total : items.length);
305
+ setTotalPages(
306
+ typeof payload.totalPages === "number" ? payload.totalPages : Math.max(1, Math.ceil(items.length / PAGE_SIZE))
307
+ );
308
+ } catch (error) {
309
+ console.error("staff.timesheets.projects.list", error);
310
+ flash(labels.errors.load, "error");
311
+ } finally {
312
+ setIsLoading(false);
313
+ setIsRefreshing(false);
314
+ hasLoadedOnceRef.current = true;
315
+ }
316
+ }, [labels.errors.load, page, search, sorting, filterValues.status, activeTab, isPmRole]);
317
+ React.useEffect(() => {
318
+ void loadKpis();
319
+ }, [loadKpis, scopeVersion, reloadToken]);
320
+ React.useEffect(() => {
321
+ void loadProjects();
322
+ }, [loadProjects, scopeVersion, reloadToken]);
323
+ const handleTabSelect = React.useCallback(
324
+ (id) => {
325
+ const params = new URLSearchParams(searchParams.toString());
326
+ if (id === "all") params.delete("tab");
327
+ else params.set("tab", id);
328
+ router.replace(`?${params.toString()}`);
329
+ setPage(1);
330
+ },
331
+ [router, searchParams]
332
+ );
333
+ const handleViewModeChange = React.useCallback(
334
+ (next) => {
335
+ setViewMode(next);
336
+ const params = new URLSearchParams(searchParams.toString());
337
+ if (next === "table") params.delete("view");
338
+ else params.set("view", next);
339
+ router.replace(`?${params.toString()}`);
340
+ },
341
+ [router, searchParams, setViewMode]
342
+ );
343
+ const handleSearchChange = React.useCallback((value) => {
344
+ setSearch(value);
345
+ setPage(1);
346
+ }, []);
347
+ const handleFiltersApply = React.useCallback((values) => {
348
+ setFilterValues(values);
349
+ setPage(1);
350
+ }, []);
351
+ const handleFiltersClear = React.useCallback(() => {
352
+ setFilterValues({});
353
+ setPage(1);
354
+ }, []);
355
+ const handleRefresh = React.useCallback(() => {
356
+ setReloadToken((token) => token + 1);
357
+ }, []);
358
+ const handleDelete = React.useCallback(
359
+ async (entry) => {
360
+ const message = labels.actions.deleteConfirm.replace("{{name}}", entry.name);
361
+ const confirmed = await confirm({
362
+ title: labels.actions.delete,
363
+ text: message,
364
+ variant: "destructive"
365
+ });
366
+ if (!confirmed) return;
367
+ try {
368
+ await deleteCrud("staff/timesheets/time-projects", entry.id, { errorMessage: labels.errors.delete });
369
+ flash(labels.messages.deleted, "success");
370
+ handleRefresh();
371
+ } catch (error) {
372
+ console.error("staff.timesheets.projects.delete", error);
373
+ flash(labels.errors.delete, "error");
374
+ }
375
+ },
376
+ [confirm, handleRefresh, labels.actions.deleteConfirm, labels.actions.delete, labels.errors.delete, labels.messages.deleted]
377
+ );
378
+ const columns = React.useMemo(
379
+ () => [
380
+ {
381
+ accessorKey: "name",
382
+ header: labels.table.name,
383
+ meta: { priority: 1, sticky: true },
384
+ cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
385
+ /* @__PURE__ */ jsx(ProjectColorDot, { colorKey: row.original.color, projectName: row.original.name, size: "sm" }),
386
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col", children: [
387
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-foreground", children: row.original.name }),
388
+ /* @__PURE__ */ jsxs("span", { className: "truncate font-mono text-[11px] text-muted-foreground", children: [
389
+ row.original.code ?? "\u2014",
390
+ row.original.customerName ? ` \xB7 ${row.original.customerName}` : ""
391
+ ] })
392
+ ] })
393
+ ] })
394
+ },
395
+ {
396
+ accessorKey: "status",
397
+ header: labels.table.status,
398
+ meta: { priority: 2 },
399
+ cell: ({ row }) => {
400
+ const badgeClass = row.original.status === "active" ? "bg-lime-100 text-lime-800 dark:bg-lime-900/30 dark:text-lime-300" : row.original.status === "on_hold" ? "bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-300" : "bg-muted text-muted-foreground";
401
+ const statusLabel = labels.statuses[row.original.status] ?? row.original.status;
402
+ return /* @__PURE__ */ jsx(
403
+ "span",
404
+ {
405
+ className: `inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-medium ${badgeClass}`,
406
+ children: statusLabel
407
+ }
408
+ );
409
+ }
410
+ },
411
+ {
412
+ accessorKey: "projectType",
413
+ header: labels.table.type,
414
+ meta: { priority: 3 },
415
+ cell: ({ row }) => row.original.projectType ? /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: row.original.projectType }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground/70", children: "\u2014" })
416
+ },
417
+ isPmRole ? {
418
+ accessorKey: "members",
419
+ id: "members",
420
+ header: labels.table.team,
421
+ enableSorting: false,
422
+ meta: { priority: 4 },
423
+ cell: ({ row }) => /* @__PURE__ */ jsx(
424
+ ProjectMembersAvatarStack,
425
+ {
426
+ members: row.original.members,
427
+ total: row.original.memberCount,
428
+ peopleCountLabel: `${row.original.memberCount}`
429
+ }
430
+ )
431
+ } : {
432
+ accessorKey: "myRole",
433
+ header: labels.table.myRole,
434
+ enableSorting: false,
435
+ meta: { priority: 4 },
436
+ cell: ({ row }) => row.original.myRole ? /* @__PURE__ */ jsx("span", { className: "text-sm text-foreground", children: row.original.myRole }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground/70", children: "\u2014" })
437
+ },
438
+ {
439
+ accessorKey: "hoursWeek",
440
+ header: isPmRole ? labels.table.hoursWeek : labels.table.myHoursWeek,
441
+ enableSorting: false,
442
+ meta: { priority: 5 },
443
+ cell: ({ row }) => {
444
+ const stripe = resolveProjectColorHex(row.original.color, row.original.name);
445
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
446
+ /* @__PURE__ */ jsx(
447
+ HoursSparkline,
448
+ {
449
+ values: row.original.hoursTrend,
450
+ color: stripe,
451
+ ariaLabel: labels.card.sparklineAria
452
+ }
453
+ ),
454
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium tabular-nums text-foreground", children: row.original.hoursWeek > 0 ? `${row.original.hoursWeek}h` : "\u2014" })
455
+ ] });
456
+ }
457
+ },
458
+ {
459
+ accessorKey: "updatedAt",
460
+ header: labels.table.updatedAt,
461
+ meta: { priority: 6 },
462
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: formatRelativeTime(row.original.updatedAt, "\u2014", t) })
463
+ }
464
+ ],
465
+ [labels.table, labels.statuses, labels.card.sparklineAria, isPmRole]
466
+ );
467
+ const cardLabels = React.useMemo(
468
+ () => ({
469
+ hoursPanelPm: labels.card.hoursPanelPm,
470
+ hoursPanelCollab: labels.card.hoursPanelCollab,
471
+ sparklineAria: labels.card.sparklineAria,
472
+ peopleCount: (count) => `${count}`,
473
+ role: labels.card.role,
474
+ noCustomer: "\u2014",
475
+ statuses: labels.statuses
476
+ }),
477
+ [labels.card, labels.statuses]
478
+ );
479
+ const canManage = isPmRole;
480
+ const emptyStateCopy = React.useMemo(() => {
481
+ const hasFiltersApplied = activeTab !== "all" || search.trim().length > 0 || Object.values(filterValues).some(Boolean);
482
+ if (hasFiltersApplied) return labels.emptyState.noMatches;
483
+ if (!canManage) return labels.emptyState.noAssignments;
484
+ return labels.emptyState.noProjects;
485
+ }, [activeTab, search, filterValues, canManage, labels.emptyState]);
486
+ const cardsData = rows.map((row) => ({
487
+ id: row.id,
488
+ name: row.name,
489
+ code: row.code,
490
+ customerName: row.customerName,
491
+ color: row.color,
492
+ status: row.status,
493
+ hoursWeek: row.hoursWeek,
494
+ hoursTrend: row.hoursTrend,
495
+ members: row.members,
496
+ memberCount: row.memberCount,
497
+ myRole: row.myRole,
498
+ updatedAt: row.updatedAt
499
+ }));
500
+ return /* @__PURE__ */ jsxs(Page, { children: [
501
+ /* @__PURE__ */ jsxs(PageBody, { children: [
502
+ /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx(ProjectsKpiStrip, { kpis, labels: kpiLabels, isLoading: isLoadingKpis }) }),
503
+ /* @__PURE__ */ jsxs("div", { className: "mb-3 flex flex-wrap items-center justify-between gap-3", children: [
504
+ /* @__PURE__ */ jsx(
505
+ SavedViewTabs,
506
+ {
507
+ tabs,
508
+ activeId: activeTab,
509
+ onSelect: handleTabSelect,
510
+ ariaLabel: t("staff.timesheets.projects.portfolio.savedViews.ariaLabel", "Saved views")
511
+ }
512
+ ),
513
+ /* @__PURE__ */ jsx(
514
+ ViewModeToggle,
515
+ {
516
+ mode: viewMode,
517
+ onChange: handleViewModeChange,
518
+ tableLabel: labels.viewMode.table,
519
+ cardsLabel: labels.viewMode.cards,
520
+ ariaLabel: t("staff.timesheets.projects.portfolio.viewMode.ariaLabel", "View mode")
521
+ }
522
+ )
523
+ ] }),
524
+ viewMode === "cards" ? /* @__PURE__ */ jsx("div", { children: isLoading ? /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3", children: Array.from({ length: 6 }).map((_, idx) => /* @__PURE__ */ jsx("div", { className: "h-48 animate-pulse rounded-lg border border-border bg-muted/40" }, idx)) }) : rows.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed border-border bg-card p-10 text-center", children: [
525
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: emptyStateCopy }),
526
+ canManage ? /* @__PURE__ */ jsx("div", { className: "mt-3", children: /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/staff/timesheets/projects/create", children: labels.actions.addFirst }) }) }) : null
527
+ ] }) : /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 md:grid-cols-2 lg:grid-cols-3", children: cardsData.map((card) => /* @__PURE__ */ jsx(
528
+ ProjectCard,
529
+ {
530
+ data: card,
531
+ labels: cardLabels,
532
+ showTeam: isPmRole,
533
+ href: `/backend/staff/timesheets/projects/${card.id}`
534
+ },
535
+ card.id
536
+ )) }) }) : /* @__PURE__ */ jsx(
537
+ DataTable,
538
+ {
539
+ title: labels.title,
540
+ data: rows,
541
+ columns,
542
+ isLoading,
543
+ searchValue: search,
544
+ onSearchChange: handleSearchChange,
545
+ searchPlaceholder: labels.table.search,
546
+ filters,
547
+ filterValues,
548
+ onFiltersApply: handleFiltersApply,
549
+ onFiltersClear: handleFiltersClear,
550
+ emptyState: /* @__PURE__ */ jsxs("div", { className: "py-12 text-center", children: [
551
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: emptyStateCopy }),
552
+ canManage ? /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/staff/timesheets/projects/create", children: labels.actions.addFirst }) }) : null
553
+ ] }),
554
+ actions: canManage ? /* @__PURE__ */ jsx(Button, { asChild: true, size: "sm", children: /* @__PURE__ */ jsx(Link, { href: "/backend/staff/timesheets/projects/create", children: labels.actions.add }) }) : void 0,
555
+ refreshButton: {
556
+ label: labels.actions.refresh,
557
+ onRefresh: handleRefresh,
558
+ isRefreshing: isLoading || isRefreshing
559
+ },
560
+ sortable: true,
561
+ sorting,
562
+ onSortingChange: setSorting,
563
+ pagination: {
564
+ page,
565
+ pageSize: PAGE_SIZE,
566
+ total,
567
+ totalPages,
568
+ onPageChange: setPage
569
+ },
570
+ rowActions: (row) => /* @__PURE__ */ jsx(
571
+ RowActions,
572
+ {
573
+ items: [
574
+ {
575
+ id: "view",
576
+ label: labels.actions.viewDetails,
577
+ href: `/backend/staff/timesheets/projects/${row.id}`
578
+ },
579
+ ...canManage ? [
580
+ {
581
+ id: "delete",
582
+ label: labels.actions.delete,
583
+ destructive: true,
584
+ onSelect: () => {
585
+ void handleDelete(row);
586
+ }
587
+ }
588
+ ] : []
589
+ ]
590
+ }
591
+ ),
592
+ onRowClick: (row) => router.push(`/backend/staff/timesheets/projects/${row.id}`)
593
+ }
594
+ )
595
+ ] }),
596
+ ConfirmDialogElement
597
+ ] });
598
+ }
599
+ export {
600
+ TimesheetProjectsPage as default
601
+ };
602
+ //# sourceMappingURL=page.js.map