@open-mercato/core 0.6.4-develop.4217.1.c9aa050183 → 0.6.4-develop.4239.1.4a264a5828

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 (408) 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/business_rules/backend/logs/[id]/page.js +24 -5
  31. package/dist/modules/business_rules/backend/logs/[id]/page.js.map +2 -2
  32. package/dist/modules/catalog/api/offers/route.js +15 -5
  33. package/dist/modules/catalog/api/offers/route.js.map +2 -2
  34. package/dist/modules/catalog/commands/categories.js +61 -12
  35. package/dist/modules/catalog/commands/categories.js.map +2 -2
  36. package/dist/modules/catalog/commands/products.js +79 -54
  37. package/dist/modules/catalog/commands/products.js.map +2 -2
  38. package/dist/modules/catalog/commands/variants.js +29 -16
  39. package/dist/modules/catalog/commands/variants.js.map +2 -2
  40. package/dist/modules/currencies/backend/currencies/[id]/page.js +19 -2
  41. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  42. package/dist/modules/currencies/commands/currencies.js +15 -8
  43. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  44. package/dist/modules/customer_accounts/api/admin/users.js +27 -26
  45. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  46. package/dist/modules/customer_accounts/api/password/reset-confirm.js +5 -5
  47. package/dist/modules/customer_accounts/api/password/reset-confirm.js.map +2 -2
  48. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +11 -10
  49. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  50. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +27 -7
  51. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  52. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +27 -7
  53. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  54. package/dist/modules/customers/backend/customers/people/[id]/page.js +29 -8
  55. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  56. package/dist/modules/customers/commands/addresses.js +35 -21
  57. package/dist/modules/customers/commands/addresses.js.map +2 -2
  58. package/dist/modules/customers/commands/companies.js +163 -162
  59. package/dist/modules/customers/commands/companies.js.map +2 -2
  60. package/dist/modules/customers/commands/deals.js +3 -4
  61. package/dist/modules/customers/commands/deals.js.map +2 -2
  62. package/dist/modules/customers/commands/interactions.js +19 -22
  63. package/dist/modules/customers/commands/interactions.js.map +2 -2
  64. package/dist/modules/customers/commands/people.js +18 -15
  65. package/dist/modules/customers/commands/people.js.map +2 -2
  66. package/dist/modules/customers/commands/personCompanyLinks.js +105 -94
  67. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  68. package/dist/modules/customers/commands/pipeline-stages.js +30 -23
  69. package/dist/modules/customers/commands/pipeline-stages.js.map +2 -2
  70. package/dist/modules/customers/commands/pipelines.js +27 -20
  71. package/dist/modules/customers/commands/pipelines.js.map +2 -2
  72. package/dist/modules/customers/commands/tags.js +13 -5
  73. package/dist/modules/customers/commands/tags.js.map +2 -2
  74. package/dist/modules/dashboards/api/users/widgets/route.js +0 -1
  75. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  76. package/dist/modules/dashboards/api/widgets/data/route.js +29 -1
  77. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  78. package/dist/modules/data_sync/lib/sync-engine.js +4 -4
  79. package/dist/modules/data_sync/lib/sync-engine.js.map +2 -2
  80. package/dist/modules/data_sync/lib/sync-run-service.js +51 -27
  81. package/dist/modules/data_sync/lib/sync-run-service.js.map +2 -2
  82. package/dist/modules/directory/commands/organizations.js +192 -158
  83. package/dist/modules/directory/commands/organizations.js.map +3 -3
  84. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js +22 -16
  85. package/dist/modules/inbox_ops/api/emails/[id]/reprocess/route.js.map +2 -2
  86. package/dist/modules/messages/commands/messages.js +77 -75
  87. package/dist/modules/messages/commands/messages.js.map +2 -2
  88. package/dist/modules/messages/commands/shared.js +132 -132
  89. package/dist/modules/messages/commands/shared.js.map +2 -2
  90. package/dist/modules/perspectives/api/[tableId]/route.js +37 -26
  91. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  92. package/dist/modules/progress/acl.js +8 -4
  93. package/dist/modules/progress/acl.js.map +2 -2
  94. package/dist/modules/resources/commands/resources.js +125 -117
  95. package/dist/modules/resources/commands/resources.js.map +2 -2
  96. package/dist/modules/resources/commands/tags.js +7 -3
  97. package/dist/modules/resources/commands/tags.js.map +2 -2
  98. package/dist/modules/sales/api/quotes/send/route.js +12 -11
  99. package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
  100. package/dist/modules/sales/commands/documents.js +629 -478
  101. package/dist/modules/sales/commands/documents.js.map +2 -2
  102. package/dist/modules/sales/commands/payments.js +146 -146
  103. package/dist/modules/sales/commands/payments.js.map +2 -2
  104. package/dist/modules/sales/commands/returns.js +68 -60
  105. package/dist/modules/sales/commands/returns.js.map +2 -2
  106. package/dist/modules/staff/acl.js +10 -1
  107. package/dist/modules/staff/acl.js.map +2 -2
  108. package/dist/modules/staff/analytics.js +33 -0
  109. package/dist/modules/staff/analytics.js.map +7 -0
  110. package/dist/modules/staff/api/guards.js +31 -0
  111. package/dist/modules/staff/api/guards.js.map +7 -0
  112. package/dist/modules/staff/api/interceptors.js +96 -0
  113. package/dist/modules/staff/api/interceptors.js.map +7 -0
  114. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js +170 -0
  115. package/dist/modules/staff/api/timesheets/my-projects/[projectId]/route.js.map +7 -0
  116. package/dist/modules/staff/api/timesheets/my-projects/route.js +103 -0
  117. package/dist/modules/staff/api/timesheets/my-projects/route.js.map +7 -0
  118. package/dist/modules/staff/api/timesheets/projects/kpis/route.js +147 -0
  119. package/dist/modules/staff/api/timesheets/projects/kpis/route.js.map +7 -0
  120. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js +171 -0
  121. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.js.map +7 -0
  122. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js +180 -0
  123. package/dist/modules/staff/api/timesheets/time-entries/[id]/segments/route.js.map +7 -0
  124. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js +155 -0
  125. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.js.map +7 -0
  126. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js +173 -0
  127. package/dist/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.js.map +7 -0
  128. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js +260 -0
  129. package/dist/modules/staff/api/timesheets/time-entries/bulk/route.js.map +7 -0
  130. package/dist/modules/staff/api/timesheets/time-entries/route.js +188 -0
  131. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +7 -0
  132. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js +159 -0
  133. package/dist/modules/staff/api/timesheets/time-projects/[id]/employees/route.js.map +7 -0
  134. package/dist/modules/staff/api/timesheets/time-projects/route.js +230 -0
  135. package/dist/modules/staff/api/timesheets/time-projects/route.js.map +7 -0
  136. package/dist/modules/staff/backend/staff/timesheets/page.js +710 -0
  137. package/dist/modules/staff/backend/staff/timesheets/page.js.map +7 -0
  138. package/dist/modules/staff/backend/staff/timesheets/page.meta.js +22 -0
  139. package/dist/modules/staff/backend/staff/timesheets/page.meta.js.map +7 -0
  140. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js +125 -0
  141. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.js.map +7 -0
  142. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js +16 -0
  143. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.js.map +7 -0
  144. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js +418 -0
  145. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +7 -0
  146. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js +16 -0
  147. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.js.map +7 -0
  148. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js +79 -0
  149. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.js.map +7 -0
  150. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js +16 -0
  151. package/dist/modules/staff/backend/staff/timesheets/projects/create/page.meta.js.map +7 -0
  152. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +602 -0
  153. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +7 -0
  154. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js +25 -0
  155. package/dist/modules/staff/backend/staff/timesheets/projects/page.meta.js.map +7 -0
  156. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js +123 -0
  157. package/dist/modules/staff/backend/staff/timesheets/projects/projectFormConfig.js.map +7 -0
  158. package/dist/modules/staff/cli.js +38 -1
  159. package/dist/modules/staff/cli.js.map +2 -2
  160. package/dist/modules/staff/commands/index.js +2 -0
  161. package/dist/modules/staff/commands/index.js.map +2 -2
  162. package/dist/modules/staff/commands/leave-requests.js +30 -28
  163. package/dist/modules/staff/commands/leave-requests.js.map +3 -3
  164. package/dist/modules/staff/commands/team-members.js +21 -20
  165. package/dist/modules/staff/commands/team-members.js.map +2 -2
  166. package/dist/modules/staff/commands/timesheets-entries.js +409 -0
  167. package/dist/modules/staff/commands/timesheets-entries.js.map +7 -0
  168. package/dist/modules/staff/commands/timesheets-projects.js +618 -0
  169. package/dist/modules/staff/commands/timesheets-projects.js.map +7 -0
  170. package/dist/modules/staff/data/enrichers.js +104 -0
  171. package/dist/modules/staff/data/enrichers.js.map +7 -0
  172. package/dist/modules/staff/data/entities.js +226 -1
  173. package/dist/modules/staff/data/entities.js.map +2 -2
  174. package/dist/modules/staff/data/validators.js +113 -1
  175. package/dist/modules/staff/data/validators.js.map +2 -2
  176. package/dist/modules/staff/events.js +13 -1
  177. package/dist/modules/staff/events.js.map +2 -2
  178. package/dist/modules/staff/lib/crud.js +7 -1
  179. package/dist/modules/staff/lib/crud.js.map +2 -2
  180. package/dist/modules/staff/lib/staffMemberResolver.js +15 -0
  181. package/dist/modules/staff/lib/staffMemberResolver.js.map +7 -0
  182. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js +60 -0
  183. package/dist/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.js.map +7 -0
  184. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js +260 -0
  185. package/dist/modules/staff/lib/timesheets-projects/computeProjectsKpis.js.map +7 -0
  186. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js +41 -0
  187. package/dist/modules/staff/lib/timesheets-projects/dateBuckets.js.map +7 -0
  188. package/dist/modules/staff/lib/timesheets-projects/initials.js +10 -0
  189. package/dist/modules/staff/lib/timesheets-projects/initials.js.map +7 -0
  190. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js +12 -0
  191. package/dist/modules/staff/lib/timesheets-projects/kpiMath.js.map +7 -0
  192. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js +55 -0
  193. package/dist/modules/staff/lib/timesheets-projects/listProjectMembersPreview.js.map +7 -0
  194. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js +66 -0
  195. package/dist/modules/staff/lib/timesheets-projects-ui/HoursSparkline.js.map +7 -0
  196. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js +81 -0
  197. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectCard.js.map +7 -0
  198. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js +58 -0
  199. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.js.map +7 -0
  200. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js +152 -0
  201. package/dist/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.js.map +7 -0
  202. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +37 -0
  203. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +7 -0
  204. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js +57 -0
  205. package/dist/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.js.map +7 -0
  206. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js +50 -0
  207. package/dist/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.js.map +7 -0
  208. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js +163 -0
  209. package/dist/modules/staff/lib/timesheets-ui/AddRowDropdown.js.map +7 -0
  210. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js +209 -0
  211. package/dist/modules/staff/lib/timesheets-ui/CalendarPicker.js.map +7 -0
  212. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js +52 -0
  213. package/dist/modules/staff/lib/timesheets-ui/ColorPicker.js.map +7 -0
  214. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js +77 -0
  215. package/dist/modules/staff/lib/timesheets-ui/CreateProjectDialog.js.map +7 -0
  216. package/dist/modules/staff/lib/timesheets-ui/ListView.js +173 -0
  217. package/dist/modules/staff/lib/timesheets-ui/ListView.js.map +7 -0
  218. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js +32 -0
  219. package/dist/modules/staff/lib/timesheets-ui/ProjectColorDot.js.map +7 -0
  220. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js +270 -0
  221. package/dist/modules/staff/lib/timesheets-ui/TimerBar.js.map +7 -0
  222. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js +57 -0
  223. package/dist/modules/staff/lib/timesheets-ui/ViewSwitcher.js.map +7 -0
  224. package/dist/modules/staff/lib/timesheets-ui/colors.js +43 -0
  225. package/dist/modules/staff/lib/timesheets-ui/colors.js.map +7 -0
  226. package/dist/modules/staff/migrations/Migration20260326135612.js +24 -0
  227. package/dist/modules/staff/migrations/Migration20260326135612.js.map +7 -0
  228. package/dist/modules/staff/migrations/Migration20260413102715.js +23 -0
  229. package/dist/modules/staff/migrations/Migration20260413102715.js.map +7 -0
  230. package/dist/modules/staff/migrations/Migration20260413111602.js +13 -0
  231. package/dist/modules/staff/migrations/Migration20260413111602.js.map +7 -0
  232. package/dist/modules/staff/migrations/Migration20260511112759.js +19 -0
  233. package/dist/modules/staff/migrations/Migration20260511112759.js.map +7 -0
  234. package/dist/modules/staff/search.js +35 -0
  235. package/dist/modules/staff/search.js.map +2 -2
  236. package/dist/modules/staff/setup.js +15 -1
  237. package/dist/modules/staff/setup.js.map +2 -2
  238. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js +16 -0
  239. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.js.map +7 -0
  240. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js +126 -0
  241. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.js.map +7 -0
  242. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js +26 -0
  243. package/dist/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.js.map +7 -0
  244. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js +15 -0
  245. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/config.js.map +7 -0
  246. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js +238 -0
  247. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.js.map +7 -0
  248. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js +26 -0
  249. package/dist/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.js.map +7 -0
  250. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js +145 -0
  251. package/dist/modules/staff/widgets/injection/timer-sidebar-indicator/widget.js.map +7 -0
  252. package/dist/modules/staff/widgets/injection-table.js +12 -0
  253. package/dist/modules/staff/widgets/injection-table.js.map +7 -0
  254. package/dist/modules/sync_excel/api/import/route.js +19 -17
  255. package/dist/modules/sync_excel/api/import/route.js.map +2 -2
  256. package/dist/modules/translations/commands/translations.js +22 -19
  257. package/dist/modules/translations/commands/translations.js.map +2 -2
  258. package/dist/modules/workflows/backend/events/[id]/page.js +24 -6
  259. package/dist/modules/workflows/backend/events/[id]/page.js.map +2 -2
  260. package/dist/modules/workflows/backend/instances/[id]/page.js +27 -5
  261. package/dist/modules/workflows/backend/instances/[id]/page.js.map +2 -2
  262. package/dist/modules/workflows/backend/tasks/[id]/page.js +25 -6
  263. package/dist/modules/workflows/backend/tasks/[id]/page.js.map +2 -2
  264. package/generated/entities/staff_time_entry/index.ts +17 -0
  265. package/generated/entities/staff_time_entry_segment/index.ts +10 -0
  266. package/generated/entities/staff_time_project/index.ts +16 -0
  267. package/generated/entities/staff_time_project_member/index.ts +13 -0
  268. package/generated/entities.ids.generated.ts +5 -1
  269. package/generated/entity-fields-registry.ts +64 -0
  270. package/package.json +7 -7
  271. package/src/helpers/integration/timesheetFixtures.ts +61 -0
  272. package/src/modules/attachments/api/library/[id]/route.ts +24 -17
  273. package/src/modules/attachments/api/route.ts +20 -14
  274. package/src/modules/auth/api/roles/acl/route.ts +11 -5
  275. package/src/modules/auth/api/sidebar/preferences/route.ts +33 -24
  276. package/src/modules/auth/api/users/acl/route.ts +17 -12
  277. package/src/modules/auth/commands/users.ts +96 -80
  278. package/src/modules/auth/services/sidebarPreferencesService.ts +40 -32
  279. package/src/modules/business_rules/backend/logs/[id]/page.tsx +32 -7
  280. package/src/modules/catalog/api/offers/route.ts +20 -5
  281. package/src/modules/catalog/commands/categories.ts +61 -12
  282. package/src/modules/catalog/commands/products.ts +93 -60
  283. package/src/modules/catalog/commands/variants.ts +29 -16
  284. package/src/modules/currencies/backend/currencies/[id]/page.tsx +21 -2
  285. package/src/modules/currencies/commands/currencies.ts +27 -14
  286. package/src/modules/currencies/i18n/de.json +1 -0
  287. package/src/modules/currencies/i18n/en.json +1 -0
  288. package/src/modules/currencies/i18n/es.json +1 -0
  289. package/src/modules/currencies/i18n/pl.json +1 -0
  290. package/src/modules/customer_accounts/api/admin/users.ts +31 -26
  291. package/src/modules/customer_accounts/api/password/reset-confirm.ts +5 -6
  292. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +14 -13
  293. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -11
  294. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +34 -11
  295. package/src/modules/customers/backend/customers/people/[id]/page.tsx +35 -11
  296. package/src/modules/customers/commands/addresses.ts +35 -23
  297. package/src/modules/customers/commands/companies.ts +166 -165
  298. package/src/modules/customers/commands/deals.ts +2 -4
  299. package/src/modules/customers/commands/interactions.ts +20 -26
  300. package/src/modules/customers/commands/people.ts +18 -15
  301. package/src/modules/customers/commands/personCompanyLinks.ts +109 -100
  302. package/src/modules/customers/commands/pipeline-stages.ts +31 -27
  303. package/src/modules/customers/commands/pipelines.ts +29 -23
  304. package/src/modules/customers/commands/tags.ts +13 -5
  305. package/src/modules/dashboards/api/users/widgets/route.ts +0 -1
  306. package/src/modules/dashboards/api/widgets/data/route.ts +36 -1
  307. package/src/modules/data_sync/lib/sync-engine.ts +4 -5
  308. package/src/modules/data_sync/lib/sync-run-service.ts +57 -28
  309. package/src/modules/directory/commands/organizations.ts +203 -166
  310. package/src/modules/inbox_ops/api/emails/[id]/reprocess/route.ts +26 -18
  311. package/src/modules/messages/commands/messages.ts +82 -80
  312. package/src/modules/messages/commands/shared.ts +138 -133
  313. package/src/modules/perspectives/api/[tableId]/route.ts +38 -27
  314. package/src/modules/progress/acl.ts +4 -0
  315. package/src/modules/resources/commands/resources.ts +127 -117
  316. package/src/modules/resources/commands/tags.ts +7 -3
  317. package/src/modules/sales/api/quotes/send/route.ts +17 -12
  318. package/src/modules/sales/commands/documents.ts +673 -481
  319. package/src/modules/sales/commands/payments.ts +158 -152
  320. package/src/modules/sales/commands/returns.ts +74 -63
  321. package/src/modules/staff/acl.ts +11 -0
  322. package/src/modules/staff/analytics.ts +30 -0
  323. package/src/modules/staff/api/guards.ts +59 -0
  324. package/src/modules/staff/api/interceptors.ts +122 -0
  325. package/src/modules/staff/api/timesheets/my-projects/[projectId]/route.ts +191 -0
  326. package/src/modules/staff/api/timesheets/my-projects/route.ts +115 -0
  327. package/src/modules/staff/api/timesheets/projects/kpis/route.ts +159 -0
  328. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/[segmentId]/route.ts +187 -0
  329. package/src/modules/staff/api/timesheets/time-entries/[id]/segments/route.ts +191 -0
  330. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-start/route.ts +168 -0
  331. package/src/modules/staff/api/timesheets/time-entries/[id]/timer-stop/route.ts +191 -0
  332. package/src/modules/staff/api/timesheets/time-entries/bulk/route.ts +292 -0
  333. package/src/modules/staff/api/timesheets/time-entries/route.ts +193 -0
  334. package/src/modules/staff/api/timesheets/time-projects/[id]/employees/route.ts +167 -0
  335. package/src/modules/staff/api/timesheets/time-projects/route.ts +244 -0
  336. package/src/modules/staff/backend/staff/timesheets/page.meta.ts +20 -0
  337. package/src/modules/staff/backend/staff/timesheets/page.tsx +899 -0
  338. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.meta.ts +12 -0
  339. package/src/modules/staff/backend/staff/timesheets/projects/[id]/edit/page.tsx +141 -0
  340. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.meta.ts +12 -0
  341. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +579 -0
  342. package/src/modules/staff/backend/staff/timesheets/projects/create/page.meta.ts +12 -0
  343. package/src/modules/staff/backend/staff/timesheets/projects/create/page.tsx +90 -0
  344. package/src/modules/staff/backend/staff/timesheets/projects/page.meta.ts +23 -0
  345. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +765 -0
  346. package/src/modules/staff/backend/staff/timesheets/projects/projectFormConfig.ts +138 -0
  347. package/src/modules/staff/cli.ts +40 -1
  348. package/src/modules/staff/commands/index.ts +2 -0
  349. package/src/modules/staff/commands/leave-requests.ts +37 -29
  350. package/src/modules/staff/commands/team-members.ts +25 -20
  351. package/src/modules/staff/commands/timesheets-entries.ts +504 -0
  352. package/src/modules/staff/commands/timesheets-projects.ts +699 -0
  353. package/src/modules/staff/data/enrichers.ts +134 -0
  354. package/src/modules/staff/data/entities.ts +198 -0
  355. package/src/modules/staff/data/validators.ts +129 -0
  356. package/src/modules/staff/events.ts +13 -0
  357. package/src/modules/staff/i18n/de.json +209 -1
  358. package/src/modules/staff/i18n/en.json +209 -1
  359. package/src/modules/staff/i18n/es.json +209 -1
  360. package/src/modules/staff/i18n/pl.json +209 -1
  361. package/src/modules/staff/lib/crud.ts +8 -0
  362. package/src/modules/staff/lib/staffMemberResolver.ts +22 -0
  363. package/src/modules/staff/lib/timesheets-projects/computeProjectHoursTrend.ts +89 -0
  364. package/src/modules/staff/lib/timesheets-projects/computeProjectsKpis.ts +311 -0
  365. package/src/modules/staff/lib/timesheets-projects/dateBuckets.ts +37 -0
  366. package/src/modules/staff/lib/timesheets-projects/initials.ts +6 -0
  367. package/src/modules/staff/lib/timesheets-projects/kpiMath.ts +8 -0
  368. package/src/modules/staff/lib/timesheets-projects/listProjectMembersPreview.ts +83 -0
  369. package/src/modules/staff/lib/timesheets-projects-ui/HoursSparkline.tsx +75 -0
  370. package/src/modules/staff/lib/timesheets-projects-ui/ProjectCard.tsx +110 -0
  371. package/src/modules/staff/lib/timesheets-projects-ui/ProjectMembersAvatarStack.tsx +73 -0
  372. package/src/modules/staff/lib/timesheets-projects-ui/ProjectsKpiStrip.tsx +185 -0
  373. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +53 -0
  374. package/src/modules/staff/lib/timesheets-projects-ui/ViewModeToggle.tsx +63 -0
  375. package/src/modules/staff/lib/timesheets-projects-ui/useProjectsViewMode.ts +63 -0
  376. package/src/modules/staff/lib/timesheets-ui/AddRowDropdown.tsx +188 -0
  377. package/src/modules/staff/lib/timesheets-ui/CalendarPicker.tsx +229 -0
  378. package/src/modules/staff/lib/timesheets-ui/ColorPicker.tsx +65 -0
  379. package/src/modules/staff/lib/timesheets-ui/CreateProjectDialog.tsx +99 -0
  380. package/src/modules/staff/lib/timesheets-ui/ListView.tsx +230 -0
  381. package/src/modules/staff/lib/timesheets-ui/ProjectColorDot.tsx +40 -0
  382. package/src/modules/staff/lib/timesheets-ui/TimerBar.tsx +327 -0
  383. package/src/modules/staff/lib/timesheets-ui/ViewSwitcher.tsx +60 -0
  384. package/src/modules/staff/lib/timesheets-ui/colors.ts +58 -0
  385. package/src/modules/staff/migrations/.snapshot-open-mercato.json +1148 -0
  386. package/src/modules/staff/migrations/Migration20260326135612.ts +26 -0
  387. package/src/modules/staff/migrations/Migration20260413102715.ts +25 -0
  388. package/src/modules/staff/migrations/Migration20260413111602.ts +13 -0
  389. package/src/modules/staff/migrations/Migration20260511112759.ts +21 -0
  390. package/src/modules/staff/search.ts +35 -0
  391. package/src/modules/staff/setup.ts +15 -0
  392. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/config.ts +17 -0
  393. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.client.tsx +158 -0
  394. package/src/modules/staff/widgets/dashboard/timesheets-hours-by-project/widget.ts +25 -0
  395. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/config.ts +15 -0
  396. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.client.tsx +297 -0
  397. package/src/modules/staff/widgets/dashboard/timesheets-time-reporting/widget.ts +25 -0
  398. package/src/modules/staff/widgets/injection/timer-sidebar-indicator/widget.tsx +161 -0
  399. package/src/modules/staff/widgets/injection-table.ts +10 -0
  400. package/src/modules/sync_excel/api/import/route.ts +23 -18
  401. package/src/modules/translations/commands/translations.ts +49 -41
  402. package/src/modules/workflows/backend/events/[id]/page.tsx +32 -10
  403. package/src/modules/workflows/backend/instances/[id]/page.tsx +33 -9
  404. package/src/modules/workflows/backend/tasks/[id]/page.tsx +33 -10
  405. package/src/modules/workflows/i18n/de.json +1 -0
  406. package/src/modules/workflows/i18n/en.json +1 -0
  407. package/src/modules/workflows/i18n/es.json +1 -0
  408. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -0,0 +1,579 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
5
+ import { Button } from '@open-mercato/ui/primitives/button'
6
+ import { Input } from '@open-mercato/ui/primitives/input'
7
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'
8
+ import { LookupSelect } from '@open-mercato/ui/backend/inputs/LookupSelect'
9
+ import { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
10
+ import { deleteCrud, createCrud } from '@open-mercato/ui/backend/utils/crud'
11
+ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
12
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
13
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
14
+ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
15
+ import { LoadingMessage } from '@open-mercato/ui/backend/detail'
16
+ import { ErrorMessage } from '@open-mercato/ui/backend/detail'
17
+ import { ArrowLeft, Plus, Pencil, ChevronDown, Trash2, User } from 'lucide-react'
18
+ import Link from 'next/link'
19
+
20
+ const BACK_HREF = '/backend/staff/timesheets/projects'
21
+
22
+ type ProjectRecord = {
23
+ id: string
24
+ name: string
25
+ code: string
26
+ description?: string | null
27
+ projectType?: string | null
28
+ project_type?: string | null
29
+ startDate?: string | null
30
+ start_date?: string | null
31
+ costCenter?: string | null
32
+ cost_center?: string | null
33
+ status?: string | null
34
+ customerId?: string | null
35
+ customer_id?: string | null
36
+ } & Record<string, unknown>
37
+
38
+ type ProjectResponse = {
39
+ items?: ProjectRecord[]
40
+ }
41
+
42
+ type EmployeeAssignment = {
43
+ id: string
44
+ staffMemberId: string
45
+ role: string | null
46
+ status: string | null
47
+ assignedStartDate: string | null
48
+ assignedEndDate: string | null
49
+ displayName: string | null
50
+ teamName: string | null
51
+ }
52
+
53
+ type EmployeesResponse = {
54
+ items?: Array<Record<string, unknown>>
55
+ total?: number
56
+ }
57
+
58
+ type StaffMemberRecord = {
59
+ id: string
60
+ display_name?: string
61
+ displayName?: string
62
+ team?: { id: string; name: string } | null
63
+ }
64
+
65
+ type StaffMembersResponse = {
66
+ items?: StaffMemberRecord[]
67
+ }
68
+
69
+ export default function TimesheetProjectDetailPage({ params }: { params?: { id?: string } }) {
70
+ const projectId = params?.id
71
+ const t = useT()
72
+ const scopeVersion = useOrganizationScopeVersion()
73
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
74
+
75
+ const [project, setProject] = React.useState<ProjectRecord | null>(null)
76
+ const [loading, setLoading] = React.useState(true)
77
+ const [error, setError] = React.useState<string | null>(null)
78
+
79
+ const [employees, setEmployees] = React.useState<EmployeeAssignment[]>([])
80
+ const [employeesLoading, setEmployeesLoading] = React.useState(false)
81
+ const [expandedCards, setExpandedCards] = React.useState<Set<string>>(new Set())
82
+ const [reloadToken, setReloadToken] = React.useState(0)
83
+
84
+ const [canManageProjects, setCanManageProjects] = React.useState(false)
85
+
86
+ React.useEffect(() => {
87
+ let cancelled = false
88
+ void (async () => {
89
+ try {
90
+ const res = await apiCall<{ ok: boolean; granted: string[] }>('/api/auth/feature-check', {
91
+ method: 'POST',
92
+ headers: { 'content-type': 'application/json' },
93
+ body: JSON.stringify({ features: ['staff.timesheets.projects.manage'] }),
94
+ })
95
+ if (!cancelled) {
96
+ setCanManageProjects(new Set(res.result?.granted ?? []).has('staff.timesheets.projects.manage'))
97
+ }
98
+ } catch {
99
+ // default: no manage access
100
+ }
101
+ })()
102
+ return () => { cancelled = true }
103
+ }, [])
104
+
105
+ const [addDialogOpen, setAddDialogOpen] = React.useState(false)
106
+ const [addStaffMemberId, setAddStaffMemberId] = React.useState<string | null>(null)
107
+ const [addRole, setAddRole] = React.useState('')
108
+ const [addStartDate, setAddStartDate] = React.useState('')
109
+ const [addSaving, setAddSaving] = React.useState(false)
110
+
111
+ const activeCount = employees.filter((emp) => emp.status === 'active').length
112
+ const inactiveCount = employees.length - activeCount
113
+
114
+ // --- Load project ---
115
+ React.useEffect(() => {
116
+ if (!projectId) return
117
+ let cancelled = false
118
+ async function loadProject() {
119
+ setLoading(true)
120
+ setError(null)
121
+ try {
122
+ const queryParams = new URLSearchParams({ page: '1', pageSize: '1', ids: projectId! })
123
+ const payload = await readApiResultOrThrow<ProjectResponse>(
124
+ `/api/staff/timesheets/time-projects?${queryParams.toString()}`,
125
+ undefined,
126
+ { errorMessage: t('staff.timesheets.projects.errors.load', 'Failed to load project.') },
127
+ )
128
+ const record = Array.isArray(payload.items) ? payload.items[0] : null
129
+ if (!record) throw new Error(t('staff.timesheets.projects.errors.notFound', 'Project not found.'))
130
+ if (!cancelled) setProject(record)
131
+ } catch (loadError) {
132
+ if (!cancelled) {
133
+ setError(loadError instanceof Error ? loadError.message : t('staff.timesheets.projects.errors.load', 'Failed to load project.'))
134
+ }
135
+ } finally {
136
+ if (!cancelled) setLoading(false)
137
+ }
138
+ }
139
+ loadProject()
140
+ return () => { cancelled = true }
141
+ }, [projectId, t, scopeVersion])
142
+
143
+ // --- Load employees with name resolution ---
144
+ const loadEmployees = React.useCallback(async () => {
145
+ if (!projectId) return
146
+ setEmployeesLoading(true)
147
+ try {
148
+ const payload = await readApiResultOrThrow<EmployeesResponse>(
149
+ `/api/staff/timesheets/time-projects/${projectId}/employees?page=1&pageSize=100`,
150
+ undefined,
151
+ { errorMessage: t('staff.timesheets.projects.employees.empty', 'No employees assigned yet.'), fallback: { items: [], total: 0 } },
152
+ )
153
+ const items = Array.isArray(payload.items) ? payload.items : []
154
+
155
+ const staffMemberIds = items
156
+ .map((item) => String(item.staff_member_id ?? item.staffMemberId ?? ''))
157
+ .filter((id) => id.length > 0)
158
+
159
+ let staffMap = new Map<string, StaffMemberRecord>()
160
+ if (staffMemberIds.length > 0) {
161
+ try {
162
+ const staffRes = await apiCall<StaffMembersResponse>(
163
+ `/api/staff/team-members?ids=${staffMemberIds.join(',')}&pageSize=100`,
164
+ )
165
+ if (staffRes.ok) {
166
+ const staffItems = Array.isArray(staffRes.result?.items) ? staffRes.result.items : []
167
+ staffMap = new Map(staffItems.map((member) => [member.id, member]))
168
+ }
169
+ } catch {
170
+ // name resolution failed (e.g. 403 for employees) — show IDs as fallback
171
+ }
172
+ }
173
+
174
+ const mapped: EmployeeAssignment[] = items.map((item) => {
175
+ const staffMemberId = String(item.staff_member_id ?? item.staffMemberId ?? '')
176
+ const staff = staffMap.get(staffMemberId)
177
+ return {
178
+ id: String(item.id ?? ''),
179
+ staffMemberId,
180
+ role: typeof item.role === 'string' ? item.role : null,
181
+ status: typeof item.status === 'string' ? item.status : null,
182
+ assignedStartDate: String(item.assigned_start_date ?? item.assignedStartDate ?? ''),
183
+ assignedEndDate: typeof (item.assigned_end_date ?? item.assignedEndDate) === 'string'
184
+ ? String(item.assigned_end_date ?? item.assignedEndDate)
185
+ : null,
186
+ displayName: staff?.display_name ?? staff?.displayName ?? null,
187
+ teamName: staff?.team?.name ?? null,
188
+ }
189
+ })
190
+
191
+ setEmployees(mapped)
192
+ } catch (loadError) {
193
+ console.error('staff.timesheets.projects.employees.list', loadError)
194
+ } finally {
195
+ setEmployeesLoading(false)
196
+ }
197
+ }, [projectId, t])
198
+
199
+ React.useEffect(() => {
200
+ void loadEmployees()
201
+ }, [loadEmployees, reloadToken, scopeVersion])
202
+
203
+ // --- Toggle card expand/collapse ---
204
+ const toggleCard = React.useCallback((employeeId: string) => {
205
+ setExpandedCards((prev) => {
206
+ const next = new Set(prev)
207
+ if (next.has(employeeId)) next.delete(employeeId)
208
+ else next.add(employeeId)
209
+ return next
210
+ })
211
+ }, [])
212
+
213
+ // --- Remove employee ---
214
+ const handleRemoveEmployee = React.useCallback(async (emp: EmployeeAssignment) => {
215
+ const confirmed = await confirm({
216
+ title: t('staff.timesheets.projects.employees.remove', 'Remove'),
217
+ text: t('staff.timesheets.projects.employees.removeConfirm', 'Remove this employee from the project?'),
218
+ variant: 'destructive',
219
+ })
220
+ if (!confirmed) return
221
+ try {
222
+ await deleteCrud(
223
+ `staff/timesheets/time-projects/${projectId}/employees`,
224
+ emp.id,
225
+ { errorMessage: t('staff.timesheets.projects.employees.removeError', 'Failed to remove employee.') },
226
+ )
227
+ flash(t('staff.timesheets.projects.employees.removed', 'Employee removed.'), 'success')
228
+ setReloadToken((token) => token + 1)
229
+ } catch {
230
+ flash(t('staff.timesheets.projects.employees.removeError', 'Failed to remove employee.'), 'error')
231
+ }
232
+ }, [projectId, confirm, t])
233
+
234
+ // --- Add employee dialog ---
235
+ const openAddDialog = React.useCallback(() => {
236
+ setAddStaffMemberId(null)
237
+ setAddRole('')
238
+ setAddStartDate(new Date().toISOString().slice(0, 10))
239
+ setAddDialogOpen(true)
240
+ }, [])
241
+
242
+ const fetchStaffMembers = React.useCallback(async (query: string) => {
243
+ try {
244
+ const params = new URLSearchParams({ search: query, pageSize: '20', isActive: 'true' })
245
+ const payload = await readApiResultOrThrow<StaffMembersResponse>(
246
+ `/api/staff/team-members?${params.toString()}`,
247
+ undefined,
248
+ { errorMessage: '', fallback: { items: [] } },
249
+ )
250
+ const items = Array.isArray(payload.items) ? payload.items : []
251
+ return items.map((member) => ({
252
+ id: member.id,
253
+ title: member.display_name ?? member.displayName ?? member.id,
254
+ subtitle: member.team?.name ?? null,
255
+ }))
256
+ } catch {
257
+ return []
258
+ }
259
+ }, [])
260
+
261
+ const handleAddEmployee = React.useCallback(async () => {
262
+ if (!projectId || !addStaffMemberId || !addStartDate) return
263
+ setAddSaving(true)
264
+ try {
265
+ await createCrud(`staff/timesheets/time-projects/${projectId}/employees`, {
266
+ staffMemberId: addStaffMemberId,
267
+ timeProjectId: projectId,
268
+ role: addRole.trim() || null,
269
+ assignedStartDate: addStartDate,
270
+ }, {
271
+ errorMessage: t('staff.timesheets.projects.employees.addError', 'Failed to add employee.'),
272
+ })
273
+ flash(t('staff.timesheets.projects.employees.added', 'Employee added.'), 'success')
274
+ setAddDialogOpen(false)
275
+ setReloadToken((token) => token + 1)
276
+ } catch (addError) {
277
+ flash(addError instanceof Error ? addError.message : t('staff.timesheets.projects.employees.addError', 'Failed to add employee.'), 'error')
278
+ } finally {
279
+ setAddSaving(false)
280
+ }
281
+ }, [projectId, addStaffMemberId, addRole, addStartDate, t])
282
+
283
+ const handleAddDialogKeyDown = React.useCallback((event: React.KeyboardEvent) => {
284
+ if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {
285
+ event.preventDefault()
286
+ void handleAddEmployee()
287
+ }
288
+ }, [handleAddEmployee])
289
+
290
+ // --- Render ---
291
+ if (loading) {
292
+ return (
293
+ <Page>
294
+ <PageBody>
295
+ <LoadingMessage label={t('staff.timesheets.projects.loading', 'Loading project...')} />
296
+ </PageBody>
297
+ </Page>
298
+ )
299
+ }
300
+
301
+ if (error || !project) {
302
+ return (
303
+ <Page>
304
+ <PageBody>
305
+ <ErrorMessage label={error ?? t('staff.timesheets.projects.errors.notFound', 'Project not found.')} />
306
+ </PageBody>
307
+ </Page>
308
+ )
309
+ }
310
+
311
+ const projectStatus = project.status ?? 'active'
312
+ const projectType = project.projectType ?? project.project_type ?? null
313
+ const projectStartDate = project.startDate ?? project.start_date ?? null
314
+ const projectCode = project.code
315
+
316
+ return (
317
+ <Page>
318
+ <PageBody>
319
+ <div className="space-y-6">
320
+ {/* Header: Back arrow, title, subtitle, Edit button */}
321
+ <div className="flex items-center justify-between">
322
+ <div className="flex items-center gap-3">
323
+ <Button variant="ghost" size="icon" asChild>
324
+ <Link href={BACK_HREF}>
325
+ <ArrowLeft className="h-4 w-4" aria-hidden />
326
+ </Link>
327
+ </Button>
328
+ <div>
329
+ <h1 className="text-xl font-semibold">{project.name}</h1>
330
+ <p className="text-sm text-muted-foreground">{t('staff.timesheets.projects.detail.subtitle', 'Project Settings')}</p>
331
+ </div>
332
+ </div>
333
+ {canManageProjects && (
334
+ <Button variant="outline" size="sm" asChild>
335
+ <Link href={`/backend/staff/timesheets/projects/${projectId}/edit`}>
336
+ <Pencil className="mr-2 h-4 w-4" aria-hidden />
337
+ {t('staff.timesheets.projects.form.actions.edit', 'Edit Project')}
338
+ </Link>
339
+ </Button>
340
+ )}
341
+ </div>
342
+
343
+ {/* Project Information (read-only) */}
344
+ {(
345
+ <div className="max-w-2xl rounded-lg border p-4">
346
+ <dl className="grid grid-cols-2 gap-4 text-sm">
347
+ <div>
348
+ <dt className="font-medium text-muted-foreground">{t('staff.timesheets.projects.form.code', 'Code')}</dt>
349
+ <dd className="font-mono">{projectCode}</dd>
350
+ </div>
351
+ <div>
352
+ <dt className="font-medium text-muted-foreground">{t('staff.timesheets.projects.form.status', 'Status')}</dt>
353
+ <dd>
354
+ <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${projectStatus === 'active' ? 'bg-green-100 text-green-800' : projectStatus === 'on_hold' ? 'bg-yellow-100 text-yellow-800' : 'bg-gray-100 text-gray-800'}`}>
355
+ {projectStatus}
356
+ </span>
357
+ </dd>
358
+ </div>
359
+ {projectType ? (
360
+ <div>
361
+ <dt className="font-medium text-muted-foreground">{t('staff.timesheets.projects.form.projectType', 'Project type')}</dt>
362
+ <dd>{projectType}</dd>
363
+ </div>
364
+ ) : null}
365
+ {projectStartDate ? (
366
+ <div>
367
+ <dt className="font-medium text-muted-foreground">{t('staff.timesheets.projects.form.startDate', 'Start date')}</dt>
368
+ <dd>{projectStartDate}</dd>
369
+ </div>
370
+ ) : null}
371
+ {project.description ? (
372
+ <div className="col-span-2">
373
+ <dt className="font-medium text-muted-foreground">{t('staff.timesheets.projects.form.description', 'Description')}</dt>
374
+ <dd className="whitespace-pre-wrap">{project.description}</dd>
375
+ </div>
376
+ ) : null}
377
+ </dl>
378
+ </div>
379
+ )}
380
+
381
+ {/* Summary Cards: Active / Inactive employees */}
382
+ <div className="grid grid-cols-2 gap-4 max-w-md">
383
+ <div className="rounded-lg border p-4 text-center">
384
+ <p className="text-2xl font-bold">{activeCount}</p>
385
+ <p className="text-xs text-muted-foreground">{t('staff.timesheets.projects.active_employees', 'Active Employees')}</p>
386
+ </div>
387
+ <div className="rounded-lg border p-4 text-center">
388
+ <p className="text-2xl font-bold">{inactiveCount}</p>
389
+ <p className="text-xs text-muted-foreground">{t('staff.timesheets.projects.inactive_employees', 'Inactive Employees')}</p>
390
+ </div>
391
+ </div>
392
+
393
+ {/* Assigned Employees — collapsible cards */}
394
+ <div className="space-y-3">
395
+ <div className="flex items-center justify-between">
396
+ <h2 className="text-base font-semibold">
397
+ {t('staff.timesheets.projects.employees.title', 'Assigned Employees')}
398
+ </h2>
399
+ {canManageProjects && (
400
+ <Button size="sm" onClick={openAddDialog}>
401
+ <Plus className="mr-2 h-4 w-4" aria-hidden />
402
+ {t('staff.timesheets.projects.add_employee', 'Add Employee')}
403
+ </Button>
404
+ )}
405
+ </div>
406
+
407
+ {employeesLoading ? (
408
+ <LoadingMessage label={t('staff.timesheets.projects.employees.loading', 'Loading employees...')} />
409
+ ) : employees.length === 0 ? (
410
+ <div className="rounded-lg border border-dashed p-8 text-center">
411
+ <User className="mx-auto h-8 w-8 text-muted-foreground/50" aria-hidden />
412
+ <p className="mt-2 text-sm text-muted-foreground">
413
+ {t('staff.timesheets.projects.employees.empty', 'No employees assigned yet.')}
414
+ </p>
415
+ </div>
416
+ ) : (
417
+ <div className="space-y-2">
418
+ {employees.map((emp) => {
419
+ const isExpanded = expandedCards.has(emp.id)
420
+ const isInactive = emp.status !== 'active'
421
+ return (
422
+ <div
423
+ key={emp.id}
424
+ className={`rounded-lg border ${isInactive ? 'opacity-60' : ''}`}
425
+ >
426
+ {/* Collapsed view: name, role, status badge, start date, expand toggle */}
427
+ <button
428
+ type="button"
429
+ className="flex w-full items-center justify-between p-4 text-left hover:bg-muted/50 transition-colors"
430
+ onClick={() => toggleCard(emp.id)}
431
+ >
432
+ <div className="flex items-center gap-3 min-w-0">
433
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted">
434
+ <User className="h-4 w-4 text-muted-foreground" aria-hidden />
435
+ </div>
436
+ <div className="min-w-0">
437
+ <p className="text-sm font-medium truncate">
438
+ {emp.displayName ?? emp.staffMemberId}
439
+ </p>
440
+ {emp.role ? (
441
+ <p className="text-xs text-muted-foreground truncate">{emp.role}</p>
442
+ ) : null}
443
+ </div>
444
+ </div>
445
+ <div className="flex items-center gap-3 shrink-0">
446
+ <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium capitalize ${emp.status === 'active' ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-800'}`}>
447
+ {emp.status ?? 'active'}
448
+ </span>
449
+ {emp.assignedStartDate ? (
450
+ <span className="text-xs text-muted-foreground hidden sm:inline">
451
+ {emp.assignedStartDate}
452
+ </span>
453
+ ) : null}
454
+ <ChevronDown className={`h-4 w-4 text-muted-foreground transition-transform ${isExpanded ? 'rotate-180' : ''}`} aria-hidden />
455
+ </div>
456
+ </button>
457
+
458
+ {/* Expanded view: assignment details */}
459
+ {isExpanded ? (
460
+ <div className="border-t px-4 pb-4 pt-3 space-y-3">
461
+ <dl className="grid grid-cols-2 gap-3 text-sm">
462
+ <div>
463
+ <dt className="font-medium text-muted-foreground">
464
+ {t('staff.timesheets.projects.employees.role', 'Role')}
465
+ </dt>
466
+ <dd>{emp.role || '-'}</dd>
467
+ </div>
468
+ <div>
469
+ <dt className="font-medium text-muted-foreground">
470
+ {t('staff.timesheets.projects.employees.status', 'Status')}
471
+ </dt>
472
+ <dd className="capitalize">{emp.status ?? 'active'}</dd>
473
+ </div>
474
+ <div>
475
+ <dt className="font-medium text-muted-foreground">
476
+ {t('staff.timesheets.projects.employees.startDate', 'Assignment start')}
477
+ </dt>
478
+ <dd>{emp.assignedStartDate || '-'}</dd>
479
+ </div>
480
+ <div>
481
+ <dt className="font-medium text-muted-foreground">
482
+ {t('staff.timesheets.projects.employees.endDate', 'Assignment end')}
483
+ </dt>
484
+ <dd>{emp.assignedEndDate || '-'}</dd>
485
+ </div>
486
+ {emp.teamName ? (
487
+ <div>
488
+ <dt className="font-medium text-muted-foreground">
489
+ {t('staff.timesheets.projects.employees.department', 'Department')}
490
+ </dt>
491
+ <dd>{emp.teamName}</dd>
492
+ </div>
493
+ ) : null}
494
+ </dl>
495
+ {canManageProjects && (
496
+ <div className="flex justify-end">
497
+ <Button
498
+ variant="outline"
499
+ size="sm"
500
+ className="text-destructive hover:text-destructive"
501
+ onClick={() => { void handleRemoveEmployee(emp) }}
502
+ >
503
+ <Trash2 className="mr-2 h-3.5 w-3.5" aria-hidden />
504
+ {t('staff.timesheets.projects.employees.remove', 'Remove')}
505
+ </Button>
506
+ </div>
507
+ )}
508
+ </div>
509
+ ) : null}
510
+ </div>
511
+ )
512
+ })}
513
+ </div>
514
+ )}
515
+ </div>
516
+ </div>
517
+
518
+ {ConfirmDialogElement}
519
+
520
+ {/* Add Employee Dialog */}
521
+ <Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>
522
+ <DialogContent className="sm:max-w-md" onKeyDown={handleAddDialogKeyDown}>
523
+ <DialogHeader>
524
+ <DialogTitle>{t('staff.timesheets.projects.add_employee', 'Add Employee')}</DialogTitle>
525
+ </DialogHeader>
526
+ <div className="space-y-4 pt-2">
527
+ <div className="space-y-2">
528
+ <label className="text-sm font-medium">
529
+ {t('staff.timesheets.projects.employees.selectEmployee', 'Select Employee')}
530
+ </label>
531
+ <LookupSelect
532
+ value={addStaffMemberId}
533
+ onChange={setAddStaffMemberId}
534
+ fetchItems={fetchStaffMembers}
535
+ searchPlaceholder={t('staff.timesheets.projects.employees.searchEmployee', 'Search team members...')}
536
+ emptyLabel={t('staff.timesheets.projects.employees.noResults', 'No team members found')}
537
+ />
538
+ </div>
539
+ <div className="space-y-2">
540
+ <label className="text-sm font-medium" htmlFor="add-role">
541
+ {t('staff.timesheets.projects.employees.roleOnProject', 'Role on Project')}
542
+ </label>
543
+ <Input
544
+ id="add-role"
545
+ value={addRole}
546
+ onChange={(event) => setAddRole(event.target.value)}
547
+ placeholder={t('staff.timesheets.projects.employees.rolePlaceholder', 'e.g. Developer, Designer...')}
548
+ />
549
+ </div>
550
+ <div className="space-y-2">
551
+ <label className="text-sm font-medium" htmlFor="add-start-date">
552
+ {t('staff.timesheets.projects.employees.assignmentStartDate', 'Assignment Start Date')}
553
+ </label>
554
+ <Input
555
+ id="add-start-date"
556
+ type="date"
557
+ value={addStartDate}
558
+ onChange={(event) => setAddStartDate(event.target.value)}
559
+ />
560
+ </div>
561
+ <div className="flex justify-end gap-2 pt-2">
562
+ <Button variant="outline" size="sm" onClick={() => setAddDialogOpen(false)}>
563
+ {t('staff.timesheets.projects.form.actions.cancel', 'Cancel')}
564
+ </Button>
565
+ <Button
566
+ size="sm"
567
+ disabled={addSaving || !addStaffMemberId || !addStartDate}
568
+ onClick={handleAddEmployee}
569
+ >
570
+ {t('staff.timesheets.projects.add_employee', 'Add Employee')}
571
+ </Button>
572
+ </div>
573
+ </div>
574
+ </DialogContent>
575
+ </Dialog>
576
+ </PageBody>
577
+ </Page>
578
+ )
579
+ }
@@ -0,0 +1,12 @@
1
+ export 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
+ }
@@ -0,0 +1,90 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { useRouter } from 'next/navigation'
5
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
+ import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
7
+ import { createCrud } from '@open-mercato/ui/backend/utils/crud'
8
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
9
+ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
10
+ import { E } from '#generated/entities.ids.generated'
11
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
12
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
13
+ import {
14
+ buildProjectPayload,
15
+ createProjectFormFields,
16
+ createProjectFormGroups,
17
+ createProjectFormSchema,
18
+ type ProjectFormValues,
19
+ } from '../projectFormConfig'
20
+
21
+ const BACK_HREF = '/backend/staff/timesheets/projects'
22
+
23
+ export default function TimesheetProjectCreatePage() {
24
+ const t = useT()
25
+ const router = useRouter()
26
+
27
+ const formSchema = React.useMemo(() => createProjectFormSchema(), [])
28
+ const fields = React.useMemo(() => createProjectFormFields(t), [t])
29
+ const groups = React.useMemo(() => createProjectFormGroups(t), [t])
30
+
31
+ return (
32
+ <Page>
33
+ <PageBody>
34
+ <CrudForm<ProjectFormValues>
35
+ title={t('staff.timesheets.projects.form.createTitle', 'Create project')}
36
+ backHref={BACK_HREF}
37
+ cancelHref={BACK_HREF}
38
+ fields={fields}
39
+ groups={groups}
40
+ schema={formSchema}
41
+ initialValues={{}}
42
+ entityIds={[E.staff.staff_time_project]}
43
+ submitLabel={t('staff.timesheets.projects.form.actions.create', 'Create')}
44
+ onSubmit={async (values) => {
45
+ if (!values.name?.trim() || !values.code?.trim()) {
46
+ const fieldErrors: Record<string, string> = {}
47
+ if (!values.name?.trim()) fieldErrors.name = 'Required'
48
+ if (!values.code?.trim()) fieldErrors.code = 'Required'
49
+ throw createCrudFormError(
50
+ t('staff.timesheets.projects.errors.required', 'Name and code are required.'),
51
+ fieldErrors,
52
+ )
53
+ }
54
+
55
+ const payload = buildProjectPayload(values)
56
+
57
+ const { result: created } = await createCrud<{ id?: string }>(
58
+ 'staff/timesheets/time-projects',
59
+ payload,
60
+ { errorMessage: t('staff.timesheets.projects.errors.save', 'Failed to save project.') },
61
+ )
62
+
63
+ const newId = created?.id
64
+
65
+ // Auto-assign creator to the project
66
+ if (newId) {
67
+ try {
68
+ const selfRes = await apiCall<{ member?: { id?: string } | null }>('/api/staff/team-members/self')
69
+ const staffMemberId = selfRes.result?.member?.id
70
+ if (staffMemberId) {
71
+ await createCrud('staff/timesheets/time-projects/' + newId + '/employees', {
72
+ staffMemberId,
73
+ assignedStartDate: new Date().toISOString().slice(0, 10),
74
+ status: 'active',
75
+ }, { errorMessage: '' })
76
+ }
77
+ } catch {
78
+ // non-critical — project created, self-assignment is best-effort
79
+ }
80
+ }
81
+
82
+ flash(t('staff.timesheets.projects.messages.saved', 'Project saved.'), 'success')
83
+ if (newId) router.push(`/backend/staff/timesheets/projects/${newId}`)
84
+ else router.push(BACK_HREF)
85
+ }}
86
+ />
87
+ </PageBody>
88
+ </Page>
89
+ )
90
+ }