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

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