@open-mercato/core 0.8.1-develop.7274.1.21a0083828 → 0.8.1-develop.7294.1.0ef99fc518

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 (261) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  3. package/dist/modules/auth/components/AclEditor.js +21 -3
  4. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  5. package/dist/modules/customers/backend/customers/companies/page.js +6 -4
  6. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  7. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js +6 -4
  8. package/dist/modules/customers/backend/customers/deals/map/components/DealsMapView.js.map +2 -2
  9. package/dist/modules/customers/backend/customers/deals/page.js +6 -4
  10. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  11. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js +5 -3
  12. package/dist/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.js.map +2 -2
  13. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +6 -4
  14. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  15. package/dist/modules/customers/backend/customers/people/page.js +6 -4
  16. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  17. package/dist/modules/customers/components/calendar/editor/PeopleField.js +9 -2
  18. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  19. package/dist/modules/customers/components/calendar/editor/hooks.js +4 -2
  20. package/dist/modules/customers/components/calendar/editor/hooks.js.map +2 -2
  21. package/dist/modules/customers/components/calendar/editor/lookups.js +7 -3
  22. package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
  23. package/dist/modules/customers/components/detail/AssignRoleDialog.js +5 -2
  24. package/dist/modules/customers/components/detail/AssignRoleDialog.js.map +2 -2
  25. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +4 -2
  26. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  27. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +8 -5
  28. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  29. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js +10 -7
  30. package/dist/modules/customers/components/detail/schedule/DateTimeFields.js.map +2 -2
  31. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js +4 -2
  32. package/dist/modules/customers/components/detail/schedule/ParticipantsField.js.map +2 -2
  33. package/dist/modules/customers/components/detail/schedule/fieldConfig.js +9 -0
  34. package/dist/modules/customers/components/detail/schedule/fieldConfig.js.map +2 -2
  35. package/dist/modules/customers/components/detail/schedule/index.js +3 -1
  36. package/dist/modules/customers/components/detail/schedule/index.js.map +2 -2
  37. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js +11 -4
  38. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  39. package/dist/modules/customers/components/formConfig.js +17 -9
  40. package/dist/modules/customers/components/formConfig.js.map +2 -2
  41. package/dist/modules/customers/data/validators.js +12 -7
  42. package/dist/modules/customers/data/validators.js.map +2 -2
  43. package/dist/modules/customers/lib/assignableStaff.js +103 -22
  44. package/dist/modules/customers/lib/assignableStaff.js.map +2 -2
  45. package/dist/modules/customers/setup.js +10 -2
  46. package/dist/modules/customers/setup.js.map +2 -2
  47. package/dist/modules/data_sync/api/schedules/[id]/route.js +5 -0
  48. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  49. package/dist/modules/data_sync/api/schedules/route.js +5 -0
  50. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  51. package/dist/modules/data_sync/backend/data-sync/page.js +29 -5
  52. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  53. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +48 -11
  54. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  55. package/dist/modules/data_sync/data/validators.js +13 -2
  56. package/dist/modules/data_sync/data/validators.js.map +2 -2
  57. package/dist/modules/data_sync/lib/schedule-value.js +30 -0
  58. package/dist/modules/data_sync/lib/schedule-value.js.map +7 -0
  59. package/dist/modules/eudr/data/validators.js.map +2 -2
  60. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js +3 -4
  61. package/dist/modules/portal/frontend/[orgSlug]/portal/dashboard/page.js.map +2 -2
  62. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js +3 -1
  63. package/dist/modules/portal/frontend/[orgSlug]/portal/invite/page.js.map +2 -2
  64. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js +2 -1
  65. package/dist/modules/portal/frontend/[orgSlug]/portal/login/page.js.map +2 -2
  66. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js +3 -4
  67. package/dist/modules/portal/frontend/[orgSlug]/portal/page.js.map +2 -2
  68. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js +3 -4
  69. package/dist/modules/portal/frontend/[orgSlug]/portal/profile/page.js.map +2 -2
  70. package/dist/modules/portal/lib/navigation.js +3 -4
  71. package/dist/modules/portal/lib/navigation.js.map +2 -2
  72. package/dist/modules/query_index/lib/engine.js +79 -14
  73. package/dist/modules/query_index/lib/engine.js.map +2 -2
  74. package/dist/modules/staff/backend/staff/time-tracking/page.js +122 -86
  75. package/dist/modules/staff/backend/staff/time-tracking/page.js.map +2 -2
  76. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +38 -17
  77. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  78. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js +45 -18
  79. package/dist/modules/staff/backend/staff/time-tracking/reports/[id]/page.js.map +2 -2
  80. package/dist/modules/staff/extension-points.js +16 -0
  81. package/dist/modules/staff/extension-points.js.map +2 -2
  82. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js +3 -4
  83. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.js.map +2 -2
  84. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js +3 -4
  85. package/dist/modules/staff/frontend/[orgSlug]/portal/time-reports/page.js.map +2 -2
  86. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js +14 -13
  87. package/dist/modules/staff/lib/time-tracking-ui/ReportSheet.js.map +2 -2
  88. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js +60 -41
  89. package/dist/modules/staff/lib/time-tracking-ui/TaskListView.js.map +2 -2
  90. package/dist/modules/warranty_claims/api/ai/assess/route.js +2 -2
  91. package/dist/modules/warranty_claims/api/ai/assess/route.js.map +2 -2
  92. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js +3 -4
  93. package/dist/modules/warranty_claims/api/ai/draft-reply/route.js.map +3 -3
  94. package/dist/modules/warranty_claims/api/ai/suggest/route.js +3 -3
  95. package/dist/modules/warranty_claims/api/ai/suggest/route.js.map +2 -2
  96. package/dist/modules/warranty_claims/api/assign/route.js +3 -3
  97. package/dist/modules/warranty_claims/api/assign/route.js.map +2 -2
  98. package/dist/modules/warranty_claims/api/credit-memo/route.js +2 -2
  99. package/dist/modules/warranty_claims/api/credit-memo/route.js.map +2 -2
  100. package/dist/modules/warranty_claims/api/entitlement/route.js +2 -2
  101. package/dist/modules/warranty_claims/api/entitlement/route.js.map +2 -2
  102. package/dist/modules/warranty_claims/api/events/route.js +4 -4
  103. package/dist/modules/warranty_claims/api/events/route.js.map +2 -2
  104. package/dist/modules/warranty_claims/api/external/claims/route.js +3 -3
  105. package/dist/modules/warranty_claims/api/external/claims/route.js.map +2 -2
  106. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js +2 -2
  107. package/dist/modules/warranty_claims/api/portal/claims/[id]/submit/route.js.map +2 -2
  108. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js +2 -2
  109. package/dist/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.js.map +2 -2
  110. package/dist/modules/warranty_claims/api/portal/claims/route.js +2 -2
  111. package/dist/modules/warranty_claims/api/portal/claims/route.js.map +2 -2
  112. package/dist/modules/warranty_claims/api/portal/events/route.js +2 -2
  113. package/dist/modules/warranty_claims/api/portal/events/route.js.map +2 -2
  114. package/dist/modules/warranty_claims/api/receiving/route.js +2 -2
  115. package/dist/modules/warranty_claims/api/receiving/route.js.map +2 -2
  116. package/dist/modules/warranty_claims/api/replacement-order/route.js +2 -2
  117. package/dist/modules/warranty_claims/api/replacement-order/route.js.map +2 -2
  118. package/dist/modules/warranty_claims/api/return-label/route.js +2 -2
  119. package/dist/modules/warranty_claims/api/return-label/route.js.map +2 -2
  120. package/dist/modules/warranty_claims/api/risk/route.js +2 -2
  121. package/dist/modules/warranty_claims/api/risk/route.js.map +2 -2
  122. package/dist/modules/warranty_claims/api/sales-return/route.js +2 -2
  123. package/dist/modules/warranty_claims/api/sales-return/route.js.map +2 -2
  124. package/dist/modules/warranty_claims/api/settings-general/route.js +4 -4
  125. package/dist/modules/warranty_claims/api/settings-general/route.js.map +2 -2
  126. package/dist/modules/warranty_claims/api/stats/route.js +2 -2
  127. package/dist/modules/warranty_claims/api/stats/route.js.map +2 -2
  128. package/dist/modules/warranty_claims/api/submit/route.js +3 -3
  129. package/dist/modules/warranty_claims/api/submit/route.js.map +2 -2
  130. package/dist/modules/warranty_claims/api/transition/route.js +3 -3
  131. package/dist/modules/warranty_claims/api/transition/route.js.map +2 -2
  132. package/dist/modules/warranty_claims/api/vendor-recovery/route.js +3 -3
  133. package/dist/modules/warranty_claims/api/vendor-recovery/route.js.map +2 -2
  134. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js +2 -2
  135. package/dist/modules/warranty_claims/api/vendor-recovery-suggestions/route.js.map +2 -2
  136. package/dist/modules/warranty_claims/backend/page.js +4 -2
  137. package/dist/modules/warranty_claims/backend/page.js.map +2 -2
  138. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js +9 -2
  139. package/dist/modules/warranty_claims/backend/warranty_claims/[id]/page.js.map +2 -2
  140. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js +4 -2
  141. package/dist/modules/warranty_claims/backend/warranty_claims/settings/page.js.map +2 -2
  142. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js +3 -4
  143. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.js.map +2 -2
  144. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js +3 -2
  145. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.js.map +2 -2
  146. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js +3 -2
  147. package/dist/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.js.map +2 -2
  148. package/dist/modules/workflows/components/DefinitionTriggersEditor.js +1 -1
  149. package/dist/modules/workflows/components/DefinitionTriggersEditor.js.map +2 -2
  150. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js +2 -2
  151. package/dist/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.js.map +2 -2
  152. package/dist/modules/workflows/lib/event-trigger-service.js +56 -1
  153. package/dist/modules/workflows/lib/event-trigger-service.js.map +2 -2
  154. package/package.json +7 -7
  155. package/src/modules/auth/api/users/acl/route.ts +4 -3
  156. package/src/modules/auth/components/AclEditor.tsx +38 -2
  157. package/src/modules/auth/i18n/de.json +5 -1
  158. package/src/modules/auth/i18n/en.json +5 -1
  159. package/src/modules/auth/i18n/es.json +5 -1
  160. package/src/modules/auth/i18n/ko.json +5 -1
  161. package/src/modules/auth/i18n/pl.json +5 -1
  162. package/src/modules/customers/backend/customers/companies/page.tsx +6 -4
  163. package/src/modules/customers/backend/customers/deals/map/components/DealsMapView.tsx +6 -4
  164. package/src/modules/customers/backend/customers/deals/page.tsx +6 -4
  165. package/src/modules/customers/backend/customers/deals/pipeline/components/ChangeOwnerDialog.tsx +5 -3
  166. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +6 -4
  167. package/src/modules/customers/backend/customers/people/page.tsx +6 -4
  168. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +9 -2
  169. package/src/modules/customers/components/calendar/editor/hooks.ts +4 -2
  170. package/src/modules/customers/components/calendar/editor/lookups.ts +12 -4
  171. package/src/modules/customers/components/detail/AssignRoleDialog.tsx +4 -1
  172. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +4 -2
  173. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +15 -7
  174. package/src/modules/customers/components/detail/schedule/DateTimeFields.tsx +16 -7
  175. package/src/modules/customers/components/detail/schedule/ParticipantsField.tsx +4 -2
  176. package/src/modules/customers/components/detail/schedule/fieldConfig.ts +15 -0
  177. package/src/modules/customers/components/detail/schedule/index.ts +1 -1
  178. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +16 -9
  179. package/src/modules/customers/components/formConfig.tsx +35 -12
  180. package/src/modules/customers/data/validators.ts +14 -8
  181. package/src/modules/customers/i18n/de.json +1 -0
  182. package/src/modules/customers/i18n/en.json +1 -0
  183. package/src/modules/customers/i18n/es.json +1 -0
  184. package/src/modules/customers/i18n/ko.json +1 -0
  185. package/src/modules/customers/i18n/pl.json +1 -0
  186. package/src/modules/customers/lib/assignableStaff.ts +172 -34
  187. package/src/modules/customers/setup.ts +8 -0
  188. package/src/modules/data_sync/api/schedules/[id]/route.ts +5 -0
  189. package/src/modules/data_sync/api/schedules/route.ts +5 -0
  190. package/src/modules/data_sync/backend/data-sync/page.tsx +48 -4
  191. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +61 -2
  192. package/src/modules/data_sync/data/validators.ts +26 -2
  193. package/src/modules/data_sync/i18n/de.json +2 -0
  194. package/src/modules/data_sync/i18n/en.json +2 -0
  195. package/src/modules/data_sync/i18n/es.json +2 -0
  196. package/src/modules/data_sync/i18n/ko.json +2 -0
  197. package/src/modules/data_sync/i18n/pl.json +2 -0
  198. package/src/modules/data_sync/lib/schedule-value.ts +45 -0
  199. package/src/modules/eudr/data/validators.ts +3 -4
  200. package/src/modules/portal/frontend/[orgSlug]/portal/dashboard/page.tsx +5 -4
  201. package/src/modules/portal/frontend/[orgSlug]/portal/invite/page.tsx +3 -1
  202. package/src/modules/portal/frontend/[orgSlug]/portal/login/page.tsx +2 -1
  203. package/src/modules/portal/frontend/[orgSlug]/portal/page.tsx +5 -5
  204. package/src/modules/portal/frontend/[orgSlug]/portal/profile/page.tsx +5 -4
  205. package/src/modules/portal/i18n/de.json +1 -0
  206. package/src/modules/portal/i18n/en.json +1 -0
  207. package/src/modules/portal/i18n/es.json +1 -0
  208. package/src/modules/portal/i18n/ko.json +1 -0
  209. package/src/modules/portal/i18n/pl.json +1 -0
  210. package/src/modules/portal/lib/navigation.ts +8 -3
  211. package/src/modules/query_index/lib/engine.ts +111 -16
  212. package/src/modules/staff/backend/staff/time-tracking/page.tsx +161 -130
  213. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +55 -43
  214. package/src/modules/staff/backend/staff/time-tracking/reports/[id]/page.tsx +61 -38
  215. package/src/modules/staff/extension-points.ts +16 -0
  216. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/[id]/page.tsx +3 -4
  217. package/src/modules/staff/frontend/[orgSlug]/portal/time-reports/page.tsx +3 -4
  218. package/src/modules/staff/i18n/de.json +3 -0
  219. package/src/modules/staff/i18n/en.json +3 -0
  220. package/src/modules/staff/i18n/es.json +3 -0
  221. package/src/modules/staff/i18n/ko.json +3 -0
  222. package/src/modules/staff/i18n/pl.json +3 -0
  223. package/src/modules/staff/lib/time-tracking-ui/ReportSheet.tsx +32 -29
  224. package/src/modules/staff/lib/time-tracking-ui/TaskListView.tsx +89 -79
  225. package/src/modules/warranty_claims/api/ai/assess/route.ts +2 -2
  226. package/src/modules/warranty_claims/api/ai/draft-reply/route.ts +2 -3
  227. package/src/modules/warranty_claims/api/ai/suggest/route.ts +3 -3
  228. package/src/modules/warranty_claims/api/assign/route.ts +3 -3
  229. package/src/modules/warranty_claims/api/credit-memo/route.ts +2 -2
  230. package/src/modules/warranty_claims/api/entitlement/route.ts +2 -2
  231. package/src/modules/warranty_claims/api/events/route.ts +4 -4
  232. package/src/modules/warranty_claims/api/external/claims/route.ts +3 -3
  233. package/src/modules/warranty_claims/api/portal/claims/[id]/submit/route.ts +2 -2
  234. package/src/modules/warranty_claims/api/portal/claims/[id]/withdraw/route.ts +2 -2
  235. package/src/modules/warranty_claims/api/portal/claims/route.ts +2 -2
  236. package/src/modules/warranty_claims/api/portal/events/route.ts +2 -2
  237. package/src/modules/warranty_claims/api/receiving/route.ts +2 -2
  238. package/src/modules/warranty_claims/api/replacement-order/route.ts +2 -2
  239. package/src/modules/warranty_claims/api/return-label/route.ts +2 -2
  240. package/src/modules/warranty_claims/api/risk/route.ts +2 -2
  241. package/src/modules/warranty_claims/api/sales-return/route.ts +2 -2
  242. package/src/modules/warranty_claims/api/settings-general/route.ts +4 -4
  243. package/src/modules/warranty_claims/api/stats/route.ts +2 -2
  244. package/src/modules/warranty_claims/api/submit/route.ts +3 -3
  245. package/src/modules/warranty_claims/api/transition/route.ts +3 -3
  246. package/src/modules/warranty_claims/api/vendor-recovery/route.ts +3 -3
  247. package/src/modules/warranty_claims/api/vendor-recovery-suggestions/route.ts +2 -2
  248. package/src/modules/warranty_claims/backend/page.tsx +4 -2
  249. package/src/modules/warranty_claims/backend/warranty_claims/[id]/page.tsx +9 -2
  250. package/src/modules/warranty_claims/backend/warranty_claims/settings/page.tsx +4 -2
  251. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/[id]/page.tsx +5 -4
  252. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/new/page.tsx +5 -2
  253. package/src/modules/warranty_claims/frontend/[orgSlug]/portal/claims/page.tsx +5 -2
  254. package/src/modules/workflows/components/DefinitionTriggersEditor.tsx +1 -1
  255. package/src/modules/workflows/frontend/[orgSlug]/portal/tasks/[id]/page.tsx +2 -2
  256. package/src/modules/workflows/i18n/de.json +1 -1
  257. package/src/modules/workflows/i18n/en.json +1 -1
  258. package/src/modules/workflows/i18n/es.json +1 -1
  259. package/src/modules/workflows/i18n/ko.json +1 -1
  260. package/src/modules/workflows/i18n/pl.json +1 -1
  261. package/src/modules/workflows/lib/event-trigger-service.ts +139 -0
@@ -31,11 +31,13 @@
31
31
 
32
32
  import * as React from 'react'
33
33
  import Link from 'next/link'
34
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
34
35
  import { CalendarDays, Copy, Lock, Pencil, Plus } from 'lucide-react'
35
36
  import { Page, PageBody, PageHeader } from '@open-mercato/ui/backend/Page'
36
37
  import { Button } from '@open-mercato/ui/primitives/button'
37
38
  import { Progress } from '@open-mercato/ui/primitives/progress'
38
39
  import { StatusBadge } from '@open-mercato/ui/primitives/status-badge'
40
+ import { DataTable } from '@open-mercato/ui/backend/DataTable'
39
41
  import {
40
42
  Select,
41
43
  SelectContent,
@@ -335,6 +337,89 @@ export default function TimeTrackingMyWorkPage() {
335
337
 
336
338
  const todayTotal = data.entries.reduce((sum, entry) => sum + entry.durationMinutes, 0)
337
339
 
340
+ const todayEntriesColumns = React.useMemo<ColumnDef<MyWorkEntry>[]>(
341
+ () => [
342
+ {
343
+ id: 'task',
344
+ header: t('staff.time_tracking.myWork.today.columns.task', 'Task'),
345
+ cell: ({ row }) => {
346
+ const entry = row.original
347
+ return (
348
+ <div>
349
+ <div className="flex flex-wrap items-center gap-2">
350
+ <span className="font-medium">
351
+ {entry.taskTitle ??
352
+ entry.description ??
353
+ t('staff.time_tracking.entries.table.noTask', 'No task')}
354
+ </span>
355
+ {entry.isBillable ? null : (
356
+ <StatusBadge variant="neutral">
357
+ {t('staff.time_tracking.entries.badges.nonBillable', 'non-billable')}
358
+ </StatusBadge>
359
+ )}
360
+ {entry.isLocked ? (
361
+ <StatusBadge variant="neutral">
362
+ <Lock className="size-3" aria-hidden="true" />
363
+ {t('staff.time_tracking.entries.badges.locked', 'locked')}
364
+ </StatusBadge>
365
+ ) : null}
366
+ </div>
367
+ <div className="text-xs text-muted-foreground">
368
+ {[entry.projectName, entry.taskTitle ? entry.description : null]
369
+ .filter((part): part is string => Boolean(part))
370
+ .join(' · ')}
371
+ </div>
372
+ </div>
373
+ )
374
+ },
375
+ },
376
+ {
377
+ id: 'clock',
378
+ header: t('staff.time_tracking.myWork.today.columns.time', 'Time'),
379
+ meta: { maxWidth: '8rem' },
380
+ cell: ({ row }) => (
381
+ <span className="text-xs text-muted-foreground">{formatClockRange(row.original) ?? '—'}</span>
382
+ ),
383
+ },
384
+ {
385
+ id: 'duration',
386
+ header: () => (
387
+ <span className="block text-right">
388
+ {t('staff.time_tracking.myWork.today.columns.duration', 'Duration')}
389
+ </span>
390
+ ),
391
+ meta: { maxWidth: '6rem' },
392
+ cell: ({ row }) => (
393
+ <span className="block text-right font-mono font-semibold tabular-nums">
394
+ {formatDuration(row.original.durationMinutes, 'clock')}
395
+ </span>
396
+ ),
397
+ },
398
+ {
399
+ id: 'actions',
400
+ header: '',
401
+ meta: { maxWidth: '3rem' },
402
+ cell: ({ row }) => {
403
+ const entry = row.original
404
+ return canManageOwn && !entry.isLocked ? (
405
+ <div className="flex justify-end">
406
+ <Button
407
+ type="button"
408
+ variant="ghost"
409
+ size="2xs"
410
+ aria-label={t('staff.time_tracking.timesheet.list.edit', 'Edit')}
411
+ onClick={() => setDialog({ open: true, entryId: entry.id })}
412
+ >
413
+ <Pencil className="size-3.5" aria-hidden="true" />
414
+ </Button>
415
+ </div>
416
+ ) : null
417
+ },
418
+ },
419
+ ],
420
+ [canManageOwn, t],
421
+ )
422
+
338
423
  const submitQuickEntry = React.useCallback(async () => {
339
424
  if (!quickTargetKey || quickMinutes === null || quickMinutes <= 0 || quickSaving) return
340
425
  const target = parseTargetKey(quickTargetKey)
@@ -568,136 +653,82 @@ export default function TimeTrackingMyWorkPage() {
568
653
  {t('staff.time_tracking.myWork.today.empty', 'Nothing logged today yet.')}
569
654
  </p>
570
655
  ) : (
571
- <table className="w-full text-sm">
572
- <tbody>
573
- {data.entries.map((entry) => (
574
- <tr key={entry.id} className="border-b last:border-0">
575
- <td className="p-3 align-top">
576
- <div className="flex flex-wrap items-center gap-2">
577
- <span className="font-medium">
578
- {entry.taskTitle ??
579
- entry.description ??
580
- t('staff.time_tracking.entries.table.noTask', 'No task')}
581
- </span>
582
- {entry.isBillable ? null : (
583
- <StatusBadge variant="neutral">
584
- {t('staff.time_tracking.entries.badges.nonBillable', 'non-billable')}
585
- </StatusBadge>
586
- )}
587
- {entry.isLocked ? (
588
- <StatusBadge variant="neutral">
589
- <Lock className="size-3" aria-hidden="true" />
590
- {t('staff.time_tracking.entries.badges.locked', 'locked')}
591
- </StatusBadge>
592
- ) : null}
593
- </div>
594
- <div className="text-xs text-muted-foreground">
595
- {[entry.projectName, entry.taskTitle ? entry.description : null]
596
- .filter((part): part is string => Boolean(part))
597
- .join(' · ')}
598
- </div>
599
- </td>
600
- <td className="w-32 p-3 align-top text-xs text-muted-foreground">
601
- {formatClockRange(entry) ?? '—'}
602
- </td>
603
- <td className="w-20 p-3 text-right align-top font-mono font-semibold tabular-nums">
604
- {formatDuration(entry.durationMinutes, 'clock')}
605
- </td>
606
- <td className="w-16 p-3 text-right align-top">
607
- {canManageOwn && !entry.isLocked ? (
608
- <Button
609
- type="button"
610
- variant="ghost"
611
- size="2xs"
612
- aria-label={t('staff.time_tracking.timesheet.list.edit', 'Edit')}
613
- onClick={() => setDialog({ open: true, entryId: entry.id })}
614
- >
615
- <Pencil className="size-3.5" aria-hidden="true" />
616
- </Button>
617
- ) : null}
618
- </td>
619
- </tr>
620
- ))}
621
- {canManageOwn && quickTargets.length > 0 ? (
622
- <tr>
623
- <td colSpan={4} className="p-3">
624
- <div
625
- className="flex flex-wrap items-start gap-2"
626
- onKeyDown={(event) => {
627
- if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
628
- event.preventDefault()
629
- void submitQuickEntry()
630
- }
631
- }}
632
- >
633
- <Select value={quickTargetKey} onValueChange={setQuickTargetKey}>
634
- <SelectTrigger
635
- className="w-72"
636
- aria-label={t('staff.time_tracking.myWork.quickEntry.target', 'Project or task')}
637
- >
638
- <SelectValue />
639
- </SelectTrigger>
640
- <SelectContent>
641
- {quickTargets.map((target) => (
642
- <SelectItem key={target.key} value={target.key}>
643
- {targetLabel(target)}
644
- </SelectItem>
645
- ))}
646
- </SelectContent>
647
- </Select>
648
- <div className="w-32">
649
- <DurationInput
650
- key={quickEpoch}
651
- variant="compact"
652
- value={quickMinutes}
653
- onChange={(minutes) => setQuickMinutes(minutes)}
654
- ariaLabel={t('staff.time_tracking.myWork.quickEntry.duration', 'Duration')}
655
- />
656
- </div>
657
- <Button
658
- type="button"
659
- size="sm"
660
- disabled={quickSaving || quickMinutes === null || quickMinutes <= 0}
661
- onClick={() => void submitQuickEntry()}
662
- >
663
- {t('staff.time_tracking.myWork.quickEntry.save', 'Save')}
664
- </Button>
665
- <span className="self-center text-xs text-muted-foreground">
666
- {t('staff.time_tracking.entryDialog.shortcutSave', '⌘↵ to save')}
667
- </span>
668
- {/*
669
- * The same block the task drawer offers, from the same
670
- * component: a row that can log an hour but not say what
671
- * it was for, or when, sends people to the full dialog
672
- * for the one field they were missing.
673
- */}
674
- <div className="w-full">
675
- <EntryDetailsFields
676
- idPrefix="my-work-quick"
677
- value={quickDetails}
678
- onChange={setQuickDetails}
679
- tagOptions={tagOptions}
680
- onCreateTag={createTag}
681
- disabled={quickSaving}
682
- />
683
- </div>
684
- </div>
685
- </td>
686
- </tr>
687
- ) : null}
688
- </tbody>
689
- <tfoot>
690
- <tr className="border-t bg-muted/40 font-medium">
691
- <td className="p-3" colSpan={2}>
692
- {t('staff.time_tracking.myWork.today.total', 'Total today')}
693
- </td>
694
- <td className="p-3 text-right font-mono tabular-nums">
695
- {formatDuration(todayTotal, 'clock')}
696
- </td>
697
- <td />
698
- </tr>
699
- </tfoot>
700
- </table>
656
+ <>
657
+ <DataTable<MyWorkEntry>
658
+ embedded
659
+ extensionTableId={extensionPoints.hosts.myWorkTodayEntriesTable.tableId}
660
+ disableRowClick
661
+ columns={todayEntriesColumns}
662
+ data={data.entries}
663
+ />
664
+ {canManageOwn && quickTargets.length > 0 ? (
665
+ <div
666
+ className="flex flex-wrap items-start gap-2 border-t p-3"
667
+ onKeyDown={(event) => {
668
+ if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
669
+ event.preventDefault()
670
+ void submitQuickEntry()
671
+ }
672
+ }}
673
+ >
674
+ <Select value={quickTargetKey} onValueChange={setQuickTargetKey}>
675
+ <SelectTrigger
676
+ className="w-72"
677
+ aria-label={t('staff.time_tracking.myWork.quickEntry.target', 'Project or task')}
678
+ >
679
+ <SelectValue />
680
+ </SelectTrigger>
681
+ <SelectContent>
682
+ {quickTargets.map((target) => (
683
+ <SelectItem key={target.key} value={target.key}>
684
+ {targetLabel(target)}
685
+ </SelectItem>
686
+ ))}
687
+ </SelectContent>
688
+ </Select>
689
+ <div className="w-32">
690
+ <DurationInput
691
+ key={quickEpoch}
692
+ variant="compact"
693
+ value={quickMinutes}
694
+ onChange={(minutes) => setQuickMinutes(minutes)}
695
+ ariaLabel={t('staff.time_tracking.myWork.quickEntry.duration', 'Duration')}
696
+ />
697
+ </div>
698
+ <Button
699
+ type="button"
700
+ size="sm"
701
+ disabled={quickSaving || quickMinutes === null || quickMinutes <= 0}
702
+ onClick={() => void submitQuickEntry()}
703
+ >
704
+ {t('staff.time_tracking.myWork.quickEntry.save', 'Save')}
705
+ </Button>
706
+ <span className="self-center text-xs text-muted-foreground">
707
+ {t('staff.time_tracking.entryDialog.shortcutSave', '⌘↵ to save')}
708
+ </span>
709
+ {/*
710
+ * The same block the task drawer offers, from the same
711
+ * component: a row that can log an hour but not say what
712
+ * it was for, or when, sends people to the full dialog
713
+ * for the one field they were missing.
714
+ */}
715
+ <div className="w-full">
716
+ <EntryDetailsFields
717
+ idPrefix="my-work-quick"
718
+ value={quickDetails}
719
+ onChange={setQuickDetails}
720
+ tagOptions={tagOptions}
721
+ onCreateTag={createTag}
722
+ disabled={quickSaving}
723
+ />
724
+ </div>
725
+ </div>
726
+ ) : null}
727
+ <div className="flex items-center justify-between border-t bg-muted/40 p-3 text-sm font-medium">
728
+ <span>{t('staff.time_tracking.myWork.today.total', 'Total today')}</span>
729
+ <span className="font-mono tabular-nums">{formatDuration(todayTotal, 'clock')}</span>
730
+ </div>
731
+ </>
701
732
  )}
702
733
  </section>
703
734
 
@@ -1,7 +1,9 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
4
5
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
+ import { DataTable } from '@open-mercato/ui/backend/DataTable'
5
7
  import { Button } from '@open-mercato/ui/primitives/button'
6
8
  import { Badge } from '@open-mercato/ui/primitives/badge'
7
9
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
@@ -330,6 +332,53 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
330
332
  const activeCount = employees.filter((emp) => emp.status === 'active').length
331
333
  const inactiveCount = employees.length - activeCount
332
334
 
335
+ const recentEntriesColumns = React.useMemo<ColumnDef<ProjectEntryRow>[]>(
336
+ () => [
337
+ {
338
+ accessorKey: 'date',
339
+ header: t('staff.timesheets.projects.detail.colDate', 'Date'),
340
+ cell: ({ row }) => <span className="whitespace-nowrap">{row.original.date}</span>,
341
+ },
342
+ {
343
+ id: 'person',
344
+ header: t('staff.timesheets.projects.detail.colWho', 'Person'),
345
+ cell: ({ row }) => (
346
+ <span className="whitespace-nowrap">
347
+ {employeeNameById.get(row.original.staffMemberId ?? '') ?? '—'}
348
+ </span>
349
+ ),
350
+ },
351
+ {
352
+ accessorKey: 'description',
353
+ header: t('staff.timesheets.projects.detail.colDescription', 'Description'),
354
+ cell: ({ row }) => (
355
+ <span className="flex items-center gap-2">
356
+ <span className="truncate">{row.original.description ?? '—'}</span>
357
+ {row.original.isBillable ? null : (
358
+ <Badge variant="neutral">
359
+ {t('staff.timesheets.projects.detail.nonBillableTag', 'non-billable')}
360
+ </Badge>
361
+ )}
362
+ </span>
363
+ ),
364
+ },
365
+ {
366
+ id: 'hours',
367
+ header: () => (
368
+ <span className="block text-right">
369
+ {t('staff.timesheets.projects.detail.colHours', 'Hours')}
370
+ </span>
371
+ ),
372
+ cell: ({ row }) => (
373
+ <span className="block whitespace-nowrap text-right font-mono tabular-nums">
374
+ {formatHours(row.original.durationMinutes)}
375
+ </span>
376
+ ),
377
+ },
378
+ ],
379
+ [employeeNameById, t],
380
+ )
381
+
333
382
  // --- Load project ---
334
383
  React.useEffect(() => {
335
384
  if (!projectId) return
@@ -1131,49 +1180,12 @@ export default function TimesheetProjectDetailPage({ params }: { params?: { id?:
1131
1180
  </p>
1132
1181
  </div>
1133
1182
  ) : (
1134
- <div className="overflow-x-auto rounded-lg border">
1135
- <table className="w-full text-sm">
1136
- <thead>
1137
- <tr className="border-b bg-muted/40 text-left">
1138
- <th className="px-3 py-2 font-medium text-muted-foreground">
1139
- {t('staff.timesheets.projects.detail.colDate', 'Date')}
1140
- </th>
1141
- <th className="px-3 py-2 font-medium text-muted-foreground">
1142
- {t('staff.timesheets.projects.detail.colWho', 'Person')}
1143
- </th>
1144
- <th className="px-3 py-2 font-medium text-muted-foreground">
1145
- {t('staff.timesheets.projects.detail.colDescription', 'Description')}
1146
- </th>
1147
- <th className="px-3 py-2 text-right font-medium text-muted-foreground">
1148
- {t('staff.timesheets.projects.detail.colHours', 'Hours')}
1149
- </th>
1150
- </tr>
1151
- </thead>
1152
- <tbody>
1153
- {recentEntries.map((entry) => (
1154
- <tr key={entry.id} className="border-b last:border-b-0">
1155
- <td className="whitespace-nowrap px-3 py-2">{entry.date}</td>
1156
- <td className="whitespace-nowrap px-3 py-2">
1157
- {employeeNameById.get(entry.staffMemberId ?? '') ?? '—'}
1158
- </td>
1159
- <td className="px-3 py-2">
1160
- <span className="flex items-center gap-2">
1161
- <span className="truncate">{entry.description ?? '—'}</span>
1162
- {entry.isBillable ? null : (
1163
- <Badge variant="neutral">
1164
- {t('staff.timesheets.projects.detail.nonBillableTag', 'non-billable')}
1165
- </Badge>
1166
- )}
1167
- </span>
1168
- </td>
1169
- <td className="whitespace-nowrap px-3 py-2 text-right font-mono tabular-nums">
1170
- {formatHours(entry.durationMinutes)}
1171
- </td>
1172
- </tr>
1173
- ))}
1174
- </tbody>
1175
- </table>
1176
- </div>
1183
+ <DataTable<ProjectEntryRow>
1184
+ extensionTableId={extensionPoints.hosts.projectDetailTimeEntriesTable.tableId}
1185
+ disableRowClick
1186
+ columns={recentEntriesColumns}
1187
+ data={recentEntries}
1188
+ />
1177
1189
  )}
1178
1190
  </TabsContent>
1179
1191
 
@@ -23,8 +23,10 @@
23
23
 
24
24
  import * as React from 'react'
25
25
  import { useSearchParams } from 'next/navigation'
26
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
26
27
  import { Download, Lock, LockOpen } from 'lucide-react'
27
28
  import { Page, PageBody } from '@open-mercato/ui/backend/Page'
29
+ import { DataTable } from '@open-mercato/ui/backend/DataTable'
28
30
  import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
29
31
  import { Badge } from '@open-mercato/ui/primitives/badge'
30
32
  import { Button } from '@open-mercato/ui/primitives/button'
@@ -55,6 +57,7 @@ import {
55
57
  parseReportSheet,
56
58
  type ReportHistoryEvent,
57
59
  type ReportSheetPayload,
60
+ type ReportSheetRow,
58
61
  } from '../../../../../lib/timesheets-reports/reportSheetData'
59
62
  import { formatReportMinutes } from '../../../../../lib/timesheets-reports/reportTotals'
60
63
 
@@ -205,6 +208,57 @@ export default function TimeTrackingReportDetailPage({ params }: { params?: { id
205
208
  [sheet?.report.currencyCode],
206
209
  )
207
210
 
211
+ const lockedEntriesColumns = React.useMemo<ColumnDef<ReportSheetRow>[]>(
212
+ () => [
213
+ {
214
+ accessorKey: 'date',
215
+ header: t('staff.time_tracking.reports.export.date', 'Date'),
216
+ cell: ({ row }) => <span className="text-muted-foreground">{row.original.date}</span>,
217
+ },
218
+ {
219
+ accessorKey: 'taskLabel',
220
+ header: t('staff.time_tracking.reports.export.task', 'Task'),
221
+ },
222
+ {
223
+ accessorKey: 'personLabel',
224
+ header: t('staff.time_tracking.reports.export.person', 'Person'),
225
+ },
226
+ {
227
+ id: 'hours',
228
+ header: () => (
229
+ <span className="block text-right">
230
+ {t('staff.time_tracking.reports.sheet.time', 'Time')}
231
+ </span>
232
+ ),
233
+ cell: ({ row }) => (
234
+ <span className="block font-mono tabular-nums text-right">{row.original.hours}</span>
235
+ ),
236
+ },
237
+ {
238
+ id: 'amount',
239
+ header: () => (
240
+ <span className="block text-right">
241
+ {t('staff.time_tracking.reports.sheet.amount', 'Amount')}
242
+ </span>
243
+ ),
244
+ cell: ({ row }) => (
245
+ <span className="block font-mono tabular-nums text-right">{money(row.original.amount)}</span>
246
+ ),
247
+ },
248
+ {
249
+ id: 'lock',
250
+ header: '',
251
+ meta: { maxWidth: '2.5rem' },
252
+ cell: () => (
253
+ <span className="text-muted-foreground">
254
+ <Lock aria-hidden="true" className="size-4" />
255
+ </span>
256
+ ),
257
+ },
258
+ ],
259
+ [money, t],
260
+ )
261
+
208
262
  const roundingLabel = React.useMemo(() => {
209
263
  const unit = sheet?.report.roundingUnitMinutes ?? 0
210
264
  if (!unit) return t('staff.time_tracking.reports.rounding.off', 'no rounding')
@@ -470,44 +524,13 @@ export default function TimeTrackingReportDetailPage({ params }: { params?: { id
470
524
  .replace('{hours}', formatReportMinutes(sheet.totals.billableMinutes))}
471
525
  </span>
472
526
  </header>
473
- <div className="overflow-x-auto">
474
- <table className="w-full text-sm">
475
- <thead>
476
- <tr className="border-b border-border text-xs uppercase tracking-wide text-muted-foreground">
477
- <th className="px-4 py-2 text-left font-medium">
478
- {t('staff.time_tracking.reports.export.date', 'Date')}
479
- </th>
480
- <th className="px-4 py-2 text-left font-medium">
481
- {t('staff.time_tracking.reports.export.task', 'Task')}
482
- </th>
483
- <th className="px-4 py-2 text-left font-medium">
484
- {t('staff.time_tracking.reports.export.person', 'Person')}
485
- </th>
486
- <th className="px-4 py-2 text-right font-medium">
487
- {t('staff.time_tracking.reports.sheet.time', 'Time')}
488
- </th>
489
- <th className="px-4 py-2 text-right font-medium">
490
- {t('staff.time_tracking.reports.sheet.amount', 'Amount')}
491
- </th>
492
- <th className="w-10 px-4 py-2" />
493
- </tr>
494
- </thead>
495
- <tbody>
496
- {sheet.rows.map((row) => (
497
- <tr key={row.entryId} className="border-b border-border/60 last:border-b-0">
498
- <td className="px-4 py-2 text-muted-foreground">{row.date}</td>
499
- <td className="px-4 py-2">{row.taskLabel}</td>
500
- <td className="px-4 py-2">{row.personLabel}</td>
501
- <td className="px-4 py-2 text-right font-mono tabular-nums">{row.hours}</td>
502
- <td className="px-4 py-2 text-right font-mono tabular-nums">{money(row.amount)}</td>
503
- <td className="px-4 py-2 text-muted-foreground">
504
- <Lock aria-hidden="true" className="size-4" />
505
- </td>
506
- </tr>
507
- ))}
508
- </tbody>
509
- </table>
510
- </div>
527
+ <DataTable<ReportSheetRow>
528
+ embedded
529
+ extensionTableId={extensionPoints.hosts.reportDetailLockedEntriesTable.tableId}
530
+ disableRowClick
531
+ columns={lockedEntriesColumns}
532
+ data={sheet.rows}
533
+ />
511
534
  {sheet.rowsTruncated ? (
512
535
  <p className="border-t border-border p-3 text-xs text-muted-foreground">
513
536
  {t(
@@ -82,6 +82,22 @@ export const extensionPoints = defineModuleExtensionPoints({
82
82
  tableId: 'staff.time_reports.list',
83
83
  source: 'backend/staff/time-tracking/reports/page.tsx',
84
84
  }),
85
+ myWorkTodayEntriesTable: dataTableExtensionHost({
86
+ tableId: 'staff.my_work.today_entries.list',
87
+ source: 'backend/staff/time-tracking/page.tsx',
88
+ }),
89
+ projectDetailTimeEntriesTable: dataTableExtensionHost({
90
+ tableId: 'staff.time_project_detail.entries.list',
91
+ source: 'backend/staff/time-tracking/projects/[id]/page.tsx',
92
+ }),
93
+ reportDetailLockedEntriesTable: dataTableExtensionHost({
94
+ tableId: 'staff.time_report_detail.locked_entries.list',
95
+ source: 'backend/staff/time-tracking/reports/[id]/page.tsx',
96
+ }),
97
+ taskBoardListViewTable: dataTableExtensionHost({
98
+ tableId: 'staff.time_task_board.list_view',
99
+ source: 'lib/time-tracking-ui/TaskListView.tsx',
100
+ }),
85
101
 
86
102
  projectForm: crudFormExtensionHost({
87
103
  entityId: 'staff.staff_time_project',
@@ -11,8 +11,8 @@
11
11
 
12
12
  import * as React from 'react'
13
13
  import Link from 'next/link'
14
- import { useRouter } from 'next/navigation'
15
14
  import { ArrowLeft } from 'lucide-react'
15
+ import { replaceWithPageReload } from '@open-mercato/shared/lib/navigation/pageReload'
16
16
  import { useT } from '@open-mercato/shared/lib/i18n/context'
17
17
  import { Button } from '@open-mercato/ui/primitives/button'
18
18
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
@@ -50,7 +50,6 @@ type PortalTimeReportDetail = {
50
50
 
51
51
  export default function PortalTimeReportDetailPage({ params }: Props) {
52
52
  const t = useT()
53
- const router = useRouter()
54
53
  const { auth } = usePortalContext()
55
54
  const { user, loading } = auth
56
55
 
@@ -60,8 +59,8 @@ export default function PortalTimeReportDetailPage({ params }: Props) {
60
59
  const [error, setError] = React.useState<string | null>(null)
61
60
 
62
61
  React.useEffect(() => {
63
- if (!loading && !user) router.replace(`/${params.orgSlug}/portal/login`)
64
- }, [loading, user, router, params.orgSlug])
62
+ if (!loading && !user) replaceWithPageReload(`/${params.orgSlug}/portal/login`)
63
+ }, [loading, user, params.orgSlug])
65
64
 
66
65
  const load = React.useCallback(async () => {
67
66
  if (!user) return
@@ -12,7 +12,7 @@
12
12
 
13
13
  import * as React from 'react'
14
14
  import Link from 'next/link'
15
- import { useRouter } from 'next/navigation'
15
+ import { replaceWithPageReload } from '@open-mercato/shared/lib/navigation/pageReload'
16
16
  import { useT } from '@open-mercato/shared/lib/i18n/context'
17
17
  import { Button } from '@open-mercato/ui/primitives/button'
18
18
  import { Spinner } from '@open-mercato/ui/primitives/spinner'
@@ -50,7 +50,6 @@ const PAGE_SIZE = 20
50
50
 
51
51
  export default function PortalTimeReportsPage({ params }: Props) {
52
52
  const t = useT()
53
- const router = useRouter()
54
53
  const { auth } = usePortalContext()
55
54
  const { user, loading } = auth
56
55
 
@@ -61,8 +60,8 @@ export default function PortalTimeReportsPage({ params }: Props) {
61
60
  const [error, setError] = React.useState<string | null>(null)
62
61
 
63
62
  React.useEffect(() => {
64
- if (!loading && !user) router.replace(`/${params.orgSlug}/portal/login`)
65
- }, [loading, user, router, params.orgSlug])
63
+ if (!loading && !user) replaceWithPageReload(`/${params.orgSlug}/portal/login`)
64
+ }, [loading, user, params.orgSlug])
66
65
 
67
66
  const load = React.useCallback(async () => {
68
67
  if (!user) return