@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
@@ -11,6 +11,7 @@ import { Avatar } from '@open-mercato/ui/primitives/avatar'
11
11
  import { Button } from '@open-mercato/ui/primitives/button'
12
12
  import { Badge } from '@open-mercato/ui/primitives/badge'
13
13
  import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
14
+ import { useCurrentOrganization } from '@open-mercato/ui/backend/BackendChromeProvider'
14
15
  import { fetchAssignableStaffMembers } from './assignableStaff'
15
16
  import { createLogger } from '@open-mercato/shared/lib/logger'
16
17
 
@@ -61,13 +62,14 @@ export function RoleAssignmentRow({
61
62
  onUpdated,
62
63
  }: RoleAssignmentRowProps) {
63
64
  const t = useT()
65
+ const activeOrgId = useCurrentOrganization()?.id ?? null
64
66
  const { confirm, ConfirmDialogElement } = useConfirmDialog()
65
67
  const [removing, setRemoving] = React.useState(false)
66
68
  const [changingUser, setChangingUser] = React.useState(false)
67
69
 
68
70
  const searchUsers = React.useCallback(async (query: string): Promise<LookupSelectItem[]> => {
69
71
  try {
70
- const members = await fetchAssignableStaffMembers(query, { pageSize: 20 })
72
+ const members = await fetchAssignableStaffMembers(query, { pageSize: 20, activeOrgId })
71
73
  return members.map((member) => ({
72
74
  id: member.userId,
73
75
  title: member.displayName,
@@ -77,7 +79,7 @@ export function RoleAssignmentRow({
77
79
  logger.error('customers.roles.searchUsers failed', { err: error })
78
80
  return []
79
81
  }
80
- }, [])
82
+ }, [activeOrgId])
81
83
 
82
84
  const handleUserChange = React.useCallback(async (userId: string | null) => {
83
85
  if (!userId || userId === role.userId) return
@@ -21,6 +21,8 @@ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
21
21
  import {
22
22
  useScheduleFormState,
23
23
  FIELD_VISIBILITY,
24
+ isDateRequired,
25
+ isTimeRequired,
24
26
  getFieldLabel,
25
27
  DateTimeFields,
26
28
  ParticipantsField,
@@ -365,8 +367,10 @@ export function ScheduleActivityDialog({
365
367
  const trimmedDate = state.date.trim()
366
368
  const trimmedStartTime = state.startTime.trim()
367
369
  const trimmedCallPhone = callPhoneNumber.trim()
368
- const isDateMissing = !trimmedDate
369
- const isTimeMissing = !state.allDay && !trimmedStartTime
370
+ // Only calendar-bound types must be scheduled; a task may stay an undated
371
+ // backlog item, so blocking its save on an empty date was wrong (#5941).
372
+ const isDateMissing = isDateRequired(state.activityType) && !trimmedDate
373
+ const isTimeMissing = isTimeRequired(state.activityType) && !state.allDay && !trimmedStartTime
370
374
  const isSubmitDisabled =
371
375
  state.saving ||
372
376
  !state.title.trim() ||
@@ -399,9 +403,13 @@ export function ScheduleActivityDialog({
399
403
  }
400
404
  state.setSaving(true)
401
405
  try {
402
- const scheduledAt = state.allDay
403
- ? new Date(`${state.date}T00:00:00`).toISOString()
404
- : new Date(`${state.date}T${state.startTime}:00`).toISOString()
406
+ // An undated activity (a backlog task) has no moment to compute — sending
407
+ // an explicit null clears `scheduled_at` instead of posting an
408
+ // `Invalid Date` built from an empty date string (#5941).
409
+ const timeForPayload = state.allDay ? '00:00' : trimmedStartTime
410
+ const scheduledAt = trimmedDate
411
+ ? new Date(`${trimmedDate}T${timeForPayload || '00:00'}:00`).toISOString()
412
+ : null
405
413
 
406
414
  const recurrenceRule = state.recurrenceEnabled
407
415
  ? buildRecurrenceRule(state.recurrenceDays, state.recurrenceEndType, state.recurrenceCount, state.recurrenceEndDate)
@@ -422,8 +430,8 @@ export function ScheduleActivityDialog({
422
430
  title: state.title.trim(),
423
431
  body: state.description.trim() || null,
424
432
  status: 'planned',
425
- date: trimmedDate,
426
- time: state.allDay ? '00:00' : trimmedStartTime,
433
+ date: trimmedDate || null,
434
+ time: trimmedDate ? timeForPayload || null : null,
427
435
  phoneNumber: state.activityType === 'call' ? phoneNumberForPayload : undefined,
428
436
  // Only tasks expose the priority control, so other types leave the column
429
437
  // untouched rather than clearing it on a type switch (#5943).
@@ -15,7 +15,7 @@ import {
15
15
  SelectValue,
16
16
  } from '@open-mercato/ui/primitives/select'
17
17
  import type { ActivityType, ScheduleFieldId } from './fieldConfig'
18
- import { isVisible, getFieldLabel } from './fieldConfig'
18
+ import { isVisible, isDateRequired, isTimeRequired, getFieldLabel } from './fieldConfig'
19
19
  import { USER_TIMEZONE } from '../../../lib/localDay'
20
20
 
21
21
  function parseIsoDate(value: string): Date | null {
@@ -100,8 +100,10 @@ export function DateTimeFields({
100
100
  const showAllDay = isVisible(activityType, 'allDay')
101
101
  const showRecurrence = isVisible(activityType, 'recurrence')
102
102
 
103
- const dateMissing = !date.trim()
104
- const timeMissing = showStartTime && !allDay && !startTime.trim()
103
+ const dateRequired = isDateRequired(activityType)
104
+ const timeRequired = isTimeRequired(activityType)
105
+ const dateMissing = dateRequired && !date.trim()
106
+ const timeMissing = timeRequired && showStartTime && !allDay && !startTime.trim()
105
107
  const dateErrorId = 'schedule-date-error'
106
108
  const timeErrorId = 'schedule-time-error'
107
109
 
@@ -112,13 +114,18 @@ export function DateTimeFields({
112
114
  <div className="flex min-w-0 flex-[1.5] flex-col gap-1.5">
113
115
  <label className="text-overline font-semibold text-muted-foreground tracking-wider">
114
116
  {getFieldLabel(activityType, 'date', t, 'customers.schedule.date', 'Date')}
115
- <span aria-hidden="true" className="ml-1 text-status-error-foreground">*</span>
117
+ {dateRequired ? (
118
+ <span aria-hidden="true" className="ml-1 text-status-error-foreground">*</span>
119
+ ) : null}
116
120
  </label>
117
121
  <DatePicker
118
122
  value={parseIsoDate(date)}
119
123
  onChange={(next) => setDate(formatIsoDate(next))}
120
124
  placeholder={t('customers.schedule.date.placeholder', 'Pick a date')}
121
- required
125
+ required={dateRequired}
126
+ // An optional date seeds to today, so the picker needs a way back to
127
+ // "no due date"; the default apply/cancel footer cannot reach null.
128
+ footer={dateRequired ? undefined : 'today-clear'}
122
129
  aria-describedby={dateMissing ? dateErrorId : undefined}
123
130
  className={cn(
124
131
  'h-10',
@@ -135,7 +142,9 @@ export function DateTimeFields({
135
142
  <div className="flex min-w-0 flex-1 flex-col gap-1.5">
136
143
  <label className="text-overline font-semibold text-muted-foreground tracking-wider">
137
144
  {getFieldLabel(activityType, 'startTime', t, 'customers.schedule.start', 'Start')}
138
- <span aria-hidden="true" className="ml-1 text-status-error-foreground">*</span>
145
+ {timeRequired ? (
146
+ <span aria-hidden="true" className="ml-1 text-status-error-foreground">*</span>
147
+ ) : null}
139
148
  </label>
140
149
  <TimePicker
141
150
  value={startTime || null}
@@ -147,7 +156,7 @@ export function DateTimeFields({
147
156
  timeMissing ? 'border-status-error-border' : undefined,
148
157
  )}
149
158
  showNowButton
150
- showClearButton={false}
159
+ showClearButton={!timeRequired}
151
160
  />
152
161
  {timeMissing ? (
153
162
  <p id={timeErrorId} className="text-xs text-status-error-foreground">
@@ -10,6 +10,7 @@ import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
10
10
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
11
11
  import { Popover, PopoverContent, PopoverTrigger } from '@open-mercato/ui/primitives/popover'
12
12
  import { SearchInput } from '@open-mercato/ui/primitives/search-input'
13
+ import { useCurrentOrganization } from '@open-mercato/ui/backend/BackendChromeProvider'
13
14
  import { fetchAssignableStaffMembersPage } from '../assignableStaff'
14
15
  import type { ActivityType, ScheduleFieldId } from './fieldConfig'
15
16
  import { isVisible, getFieldLabel } from './fieldConfig'
@@ -39,6 +40,7 @@ function ParticipantSearchPopover({
39
40
  const [hasMore, setHasMore] = React.useState(false)
40
41
  const [loading, setLoading] = React.useState(false)
41
42
  const [loadError, setLoadError] = React.useState<string | null>(null)
43
+ const activeOrgId = useCurrentOrganization()?.id ?? null
42
44
  const selectableResults = React.useMemo(
43
45
  () => results.filter((result) => !existingIds.has(result.userId)),
44
46
  [existingIds, results],
@@ -48,7 +50,7 @@ function ParticipantSearchPopover({
48
50
  if (!open) return
49
51
  const controller = new AbortController()
50
52
  setLoading(true)
51
- fetchAssignableStaffMembersPage(query, { page, pageSize: PAGE_SIZE, signal: controller.signal })
53
+ fetchAssignableStaffMembersPage(query, { page, pageSize: PAGE_SIZE, activeOrgId, signal: controller.signal })
52
54
  .then((result) => {
53
55
  const members = result.items
54
56
  const nextResults = members.map((member) => ({
@@ -77,7 +79,7 @@ function ParticipantSearchPopover({
77
79
  })
78
80
  .finally(() => setLoading(false))
79
81
  return () => controller.abort()
80
- }, [open, page, query, t])
82
+ }, [activeOrgId, open, page, query, t])
81
83
 
82
84
  React.useEffect(() => {
83
85
  if (!open) return
@@ -84,6 +84,21 @@ export function isVisible(type: ActivityType, fieldId: ScheduleFieldId): boolean
84
84
  return FIELD_VISIBILITY[type].has(fieldId)
85
85
  }
86
86
 
87
+ // Calendar-bound types must land on a concrete moment, but a task is a backlog
88
+ // item whose due date is optional by design (`interactions.scheduled_at` is
89
+ // nullable and the task lists already render an undated row) — #5941.
90
+ const OPTIONAL_DATE_TYPES: Set<ActivityType> = new Set(['task'])
91
+
92
+ export function isDateRequired(type: ActivityType): boolean {
93
+ return isVisible(type, 'date') && !OPTIONAL_DATE_TYPES.has(type)
94
+ }
95
+
96
+ // A time without a date is meaningless, so the time follows the date: it is
97
+ // only demanded for the types that must be scheduled in the first place.
98
+ export function isTimeRequired(type: ActivityType): boolean {
99
+ return isVisible(type, 'startTime') && isDateRequired(type)
100
+ }
101
+
87
102
  export function getFieldLabel(
88
103
  type: ActivityType,
89
104
  fieldId: ScheduleFieldId,
@@ -3,7 +3,7 @@ export { ParticipantsField } from './ParticipantsField'
3
3
  export { LocationField } from './LocationField'
4
4
  export { FooterFields } from './FooterFields'
5
5
  export { LinkedEntitiesField } from './LinkedEntitiesField'
6
- export { isVisible, getFieldLabel, FIELD_VISIBILITY, FIELD_LABEL_OVERRIDES } from './fieldConfig'
6
+ export { isVisible, isDateRequired, isTimeRequired, getFieldLabel, FIELD_VISIBILITY, FIELD_LABEL_OVERRIDES } from './fieldConfig'
7
7
  export type { ActivityType, ScheduleFieldId } from './fieldConfig'
8
8
  export { useScheduleFormState, PARTICIPANT_COLORS } from './useScheduleFormState'
9
9
  export type { Participant, LinkedEntity, RsvpStatus, ScheduleActivityEditData } from './useScheduleFormState'
@@ -1,5 +1,6 @@
1
1
  import * as React from 'react'
2
2
  import type { ActivityType } from './fieldConfig'
3
+ import { isDateRequired } from './fieldConfig'
3
4
 
4
5
  export type RsvpStatus = 'pending' | 'accepted' | 'declined' | 'tentative'
5
6
 
@@ -165,14 +166,20 @@ export function useScheduleFormState({ open, editData }: UseScheduleFormStatePar
165
166
  // local-day convention.
166
167
  const sourceTimestamp = editData.occurredAt ?? editData.scheduledAt ?? null
167
168
  const seedDate = sourceTimestamp ? new Date(sourceTimestamp) : null
168
- // No usable timestamp means this is a preset create (or a corrupt row), so
169
- // fall forward to the create-mode default instead of "now" (#5940).
170
- const dateForForm =
171
- seedDate && !Number.isNaN(seedDate.getTime())
172
- ? seedDate
173
- : resolveDefaultActivityStart(resolvedType, new Date())
174
- setDate(formatLocalDateInput(dateForForm))
175
- setStartTime(formatLocalTimeInput(dateForForm))
169
+ const seedDateValid = seedDate !== null && !Number.isNaN(seedDate.getTime())
170
+ // A real edit of an undated task must stay undated: falling back to a
171
+ // computed default here would silently give a backlog item a due date on
172
+ // the next save (#5941). Menu-driven "New Task" (no id yet, so `isEditing`
173
+ // is false) still gets the create-mode default so #5940's forward-seeded
174
+ // default keeps working.
175
+ if (isEditing && !seedDateValid && !isDateRequired(resolvedType)) {
176
+ setDate('')
177
+ setStartTime('')
178
+ } else {
179
+ const dateForForm = seedDateValid ? (seedDate as Date) : resolveDefaultActivityStart(resolvedType, new Date())
180
+ setDate(formatLocalDateInput(dateForForm))
181
+ setStartTime(formatLocalTimeInput(dateForForm))
182
+ }
176
183
  setDuration(editData.durationMinutes ?? 30)
177
184
  setAllDay(editData.allDay ?? false)
178
185
  setDescription(editData.body ?? '')
@@ -253,7 +260,7 @@ export function useScheduleFormState({ open, editData }: UseScheduleFormStatePar
253
260
  document.body.style.removeProperty('overflow')
254
261
  document.body.style.removeProperty('pointer-events')
255
262
  }
256
- }, [open, editData])
263
+ }, [open, editData, isEditing])
257
264
 
258
265
  // Update the Reminder default when the activity type changes in create mode.
259
266
  // Skipped in edit mode (the persisted value wins), and gated by `open` to
@@ -30,7 +30,7 @@ import {
30
30
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
31
31
  import { apiCall, apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
32
32
  import { collectCustomFieldValues } from '@open-mercato/ui/backend/utils/customFieldValues'
33
- import { PhoneNumberField } from '@open-mercato/ui/backend/inputs/PhoneNumberField'
33
+ import { PhoneNumberField, type PhoneCountry } from '@open-mercato/ui/backend/inputs/PhoneNumberField'
34
34
  import { isValidPhoneNumber } from '@open-mercato/shared/lib/phone'
35
35
  import type {
36
36
  CrudCustomFieldRenderProps,
@@ -59,7 +59,11 @@ import {
59
59
  type CustomerDictionaryKind,
60
60
  } from '../lib/dictionaries'
61
61
  import { normalizeCustomFieldSubmitValue } from './detail/customFieldUtils'
62
- import { CUSTOMER_PHONE_INVALID_MESSAGE_KEY, CUSTOMER_URL_INVALID_MESSAGE_KEY } from '../data/validators'
62
+ import {
63
+ CUSTOMER_EMAIL_INVALID_MESSAGE_KEY,
64
+ CUSTOMER_PHONE_INVALID_MESSAGE_KEY,
65
+ CUSTOMER_URL_INVALID_MESSAGE_KEY,
66
+ } from '../data/validators'
63
67
 
64
68
  export const metadata = {
65
69
  navHidden: true,
@@ -75,6 +79,17 @@ export type Translator = (
75
79
  params?: Record<string, string | number>,
76
80
  ) => string
77
81
 
82
+ export type CustomerFormFieldOptions = {
83
+ /** Country pre-selected in the phone field when the value is empty. */
84
+ defaultCountryIso2?: string
85
+ /**
86
+ * Country list for the phone field's selector. Supply it to limit the picker
87
+ * to specific markets or to name the countries yourself; omitted, the field
88
+ * localizes its built-in list for the active locale.
89
+ */
90
+ phoneCountries?: PhoneCountry[]
91
+ }
92
+
78
93
  export type PersonFormValues = {
79
94
  displayName: string
80
95
  firstName: string
@@ -124,7 +139,7 @@ type DictionarySelectFieldProps = {
124
139
 
125
140
  export { CUSTOMER_DICTIONARIES_MANAGE_HREF, getCustomerDictionaryManageHref }
126
141
 
127
- const emailValidationSchema = z.string().email()
142
+ const emailValidationSchema = z.string().email(CUSTOMER_EMAIL_INVALID_MESSAGE_KEY)
128
143
  const EMAIL_CHECK_DEBOUNCE_MS = 350
129
144
 
130
145
  const createSectionHeadingField = (id: string, title: string): CrudField => ({
@@ -418,7 +433,11 @@ const companyDictionaryFieldDefinitions: DictionaryFieldDefinition[] = [
418
433
  },
419
434
  ]
420
435
 
421
- const createPrimaryPhoneField = (t: Translator, defaultCountryIso2?: string): CrudField => ({
436
+ const createPrimaryPhoneField = (
437
+ t: Translator,
438
+ defaultCountryIso2?: string,
439
+ phoneCountries?: PhoneCountry[],
440
+ ): CrudField => ({
422
441
  id: 'primaryPhone',
423
442
  label: t('customers.people.form.primaryPhone'),
424
443
  type: 'custom',
@@ -449,6 +468,7 @@ const createPrimaryPhoneField = (t: Translator, defaultCountryIso2?: string): Cr
449
468
  minDigits={7}
450
469
  onDuplicateLookup={!disabled && !error ? duplicateLookup : undefined}
451
470
  defaultCountryIso2={defaultCountryIso2}
471
+ countries={phoneCountries}
452
472
  />
453
473
  )
454
474
  },
@@ -723,7 +743,7 @@ export const createPersonFormSchema = () =>
723
743
  primaryEmail: z
724
744
  .string()
725
745
  .trim()
726
- .email()
746
+ .email(CUSTOMER_EMAIL_INVALID_MESSAGE_KEY)
727
747
  .optional()
728
748
  .or(z.literal(''))
729
749
  .transform((val) => (val === '' ? undefined : val)),
@@ -871,8 +891,9 @@ export const createDisplayNameSection = (t: Translator) =>
871
891
  )
872
892
  }
873
893
 
874
- export const createPersonFormFields = (t: Translator, options?: { defaultCountryIso2?: string }): CrudField[] => {
894
+ export const createPersonFormFields = (t: Translator, options?: CustomerFormFieldOptions): CrudField[] => {
875
895
  const defaultCountryIso2 = options?.defaultCountryIso2
896
+ const phoneCountries = options?.phoneCountries
876
897
  const contactSection = createSectionHeadingField('__contactInformationSection', t('customers.people.form.sections.contactInformation'))
877
898
  const companySection = createSectionHeadingField('__companyInformationSection', t('customers.people.form.sections.companyInformation'))
878
899
  const dictionaryFields: CrudField[] = dictionaryFieldDefinitions.map((definition) => ({
@@ -933,7 +954,7 @@ export const createPersonFormFields = (t: Translator, options?: { defaultCountry
933
954
  },
934
955
  contactSection,
935
956
  createPrimaryEmailField(t),
936
- createPrimaryPhoneField(t, defaultCountryIso2),
957
+ createPrimaryPhoneField(t, defaultCountryIso2, phoneCountries),
937
958
  companySection,
938
959
  {
939
960
  id: 'companyEntityId',
@@ -1141,7 +1162,7 @@ export const createCompanyFormSchema = () =>
1141
1162
  primaryEmail: z
1142
1163
  .string()
1143
1164
  .trim()
1144
- .email()
1165
+ .email(CUSTOMER_EMAIL_INVALID_MESSAGE_KEY)
1145
1166
  .optional()
1146
1167
  .or(z.literal(''))
1147
1168
  .transform((val) => (val === '' ? undefined : val)),
@@ -1228,8 +1249,9 @@ export const createCompanyFormSchema = () =>
1228
1249
  })
1229
1250
  .passthrough()
1230
1251
 
1231
- export const createCompanyFormFields = (t: Translator, options?: { defaultCountryIso2?: string }): CrudField[] => {
1252
+ export const createCompanyFormFields = (t: Translator, options?: CustomerFormFieldOptions): CrudField[] => {
1232
1253
  const defaultCountryIso2 = options?.defaultCountryIso2
1254
+ const phoneCountries = options?.phoneCountries
1233
1255
  const dictionaryFields: CrudField[] = companyDictionaryFieldDefinitions.map((definition) => ({
1234
1256
  id: definition.id,
1235
1257
  label: t(definition.labelKey),
@@ -1276,6 +1298,7 @@ export const createCompanyFormFields = (t: Translator, options?: { defaultCountr
1276
1298
  invalidLabel={t('customers.people.form.primaryPhone.invalid', 'Enter a valid phone number with country code (e.g. +1 212 555 1234)')}
1277
1299
  minDigits={7}
1278
1300
  defaultCountryIso2={defaultCountryIso2}
1301
+ countries={phoneCountries}
1279
1302
  />
1280
1303
  ),
1281
1304
  } as CrudField,
@@ -1569,7 +1592,7 @@ const clearableEmailField = () =>
1569
1592
  z
1570
1593
  .string()
1571
1594
  .trim()
1572
- .email()
1595
+ .email(CUSTOMER_EMAIL_INVALID_MESSAGE_KEY)
1573
1596
  .optional()
1574
1597
  .or(z.literal(''))
1575
1598
  .transform((val) => (val === '' ? null : val))
@@ -1657,7 +1680,7 @@ const buildIndustryLabels = (t: Translator): DictionarySelectLabels => ({
1657
1680
  manageTitle: t('customers.people.form.dictionary.manage'),
1658
1681
  })
1659
1682
 
1660
- export const createCompanyEditFields = (t: Translator, options?: { defaultCountryIso2?: string }): CrudField[] => {
1683
+ export const createCompanyEditFields = (t: Translator, options?: CustomerFormFieldOptions): CrudField[] => {
1661
1684
  const baseFields = createCompanyFormFields(t, options)
1662
1685
  const industryLabels = buildIndustryLabels(t)
1663
1686
 
@@ -1682,7 +1705,7 @@ export const createCompanyEditFields = (t: Translator, options?: { defaultCountr
1682
1705
  })
1683
1706
  }
1684
1707
 
1685
- export const createPersonEditFields = (t: Translator, options?: { defaultCountryIso2?: string }): CrudField[] => {
1708
+ export const createPersonEditFields = (t: Translator, options?: CustomerFormFieldOptions): CrudField[] => {
1686
1709
  const baseFields = createPersonFormFields(t, options)
1687
1710
  return [
1688
1711
  ...baseFields,
@@ -6,6 +6,7 @@ import { dictionaryEntrySortModeSchema } from '@open-mercato/core/modules/dictio
6
6
  const uuid = () => z.string().uuid()
7
7
 
8
8
  export const CUSTOMER_PHONE_INVALID_MESSAGE_KEY = 'customers.people.form.primaryPhone.invalid'
9
+ export const CUSTOMER_EMAIL_INVALID_MESSAGE_KEY = 'customers.people.form.primaryEmail.invalid'
9
10
  export const CUSTOMER_URL_INVALID_MESSAGE_KEY = 'customers.people.form.websiteUrl.invalid'
10
11
  export const ACTIVITY_DATE_REQUIRED_MESSAGE_KEY = 'customers.activities.errors.dateRequired'
11
12
  export const ACTIVITY_TIME_REQUIRED_MESSAGE_KEY = 'customers.activities.errors.timeRequired'
@@ -37,7 +38,7 @@ const phoneSchema = z.preprocess(
37
38
 
38
39
  const clearableEmailSchema = z.preprocess(
39
40
  emptyStringToNull,
40
- z.string().email().max(320).nullable().optional(),
41
+ z.string().email(CUSTOMER_EMAIL_INVALID_MESSAGE_KEY).max(320).nullable().optional(),
41
42
  )
42
43
 
43
44
  const clearableUrlSchema = z.preprocess(
@@ -219,8 +220,8 @@ export const activityCreateSchema = scopedSchema.extend({
219
220
  activityType: z.string().min(1).max(100),
220
221
  subject: z.string().max(200).optional(),
221
222
  body: z.string().max(8000).optional(),
222
- date: z.string().trim().min(1, ACTIVITY_DATE_REQUIRED_MESSAGE_KEY).optional(),
223
- time: z.string().trim().min(1, ACTIVITY_TIME_REQUIRED_MESSAGE_KEY).optional(),
223
+ date: z.string().trim().min(1, ACTIVITY_DATE_REQUIRED_MESSAGE_KEY).optional().nullable(),
224
+ time: z.string().trim().min(1, ACTIVITY_TIME_REQUIRED_MESSAGE_KEY).optional().nullable(),
224
225
  phoneNumber: interactionPhoneNumberSchema,
225
226
  occurredAt: z.coerce.date().optional(),
226
227
  dealId: uuid().optional(),
@@ -500,8 +501,10 @@ const interactionCreateBaseSchema = scopedSchema.extend({
500
501
  // rows, external writers, and the dispatch-crm MCP keep working. Open/terminal semantics
501
502
  // live in lib/interactionStatus.ts, not in this validator.
502
503
  status: z.string().max(50).optional().default('planned'),
503
- date: z.string().trim().min(1, ACTIVITY_DATE_REQUIRED_MESSAGE_KEY).optional(),
504
- time: z.string().trim().min(1, ACTIVITY_TIME_REQUIRED_MESSAGE_KEY).optional(),
504
+ // Nullable like the sibling `scheduledAt` below: an undated activity (a
505
+ // backlog task) says "no date" with an explicit null, not by omission (#5941).
506
+ date: z.string().trim().min(1, ACTIVITY_DATE_REQUIRED_MESSAGE_KEY).optional().nullable(),
507
+ time: z.string().trim().min(1, ACTIVITY_TIME_REQUIRED_MESSAGE_KEY).optional().nullable(),
505
508
  phoneNumber: interactionPhoneNumberSchema,
506
509
  scheduledAt: z.coerce.date().optional().nullable(),
507
510
  occurredAt: z.coerce.date().optional().nullable(),
@@ -515,7 +518,7 @@ const interactionCreateBaseSchema = scopedSchema.extend({
515
518
  ...interactionExtendedFields,
516
519
  })
517
520
 
518
- function deriveScheduledAtFromDateTime(date?: string, time?: string): Date | null {
521
+ function deriveScheduledAtFromDateTime(date?: string | null, time?: string | null): Date | null {
519
522
  if (!date || typeof date !== 'string') return null
520
523
  const trimmedDate = date.trim()
521
524
  if (!trimmedDate) return null
@@ -567,8 +570,8 @@ const interactionUpdateBaseSchema = z
567
570
  title: z.string().trim().max(500).optional().nullable(),
568
571
  body: z.string().trim().max(10000).optional().nullable(),
569
572
  status: z.string().max(50).optional(),
570
- date: z.string().trim().min(1, ACTIVITY_DATE_REQUIRED_MESSAGE_KEY).optional(),
571
- time: z.string().trim().min(1, ACTIVITY_TIME_REQUIRED_MESSAGE_KEY).optional(),
573
+ date: z.string().trim().min(1, ACTIVITY_DATE_REQUIRED_MESSAGE_KEY).optional().nullable(),
574
+ time: z.string().trim().min(1, ACTIVITY_TIME_REQUIRED_MESSAGE_KEY).optional().nullable(),
572
575
  phoneNumber: interactionPhoneNumberSchema,
573
576
  scheduledAt: z.coerce.date().optional().nullable(),
574
577
  occurredAt: z.coerce.date().optional().nullable(),
@@ -608,6 +611,9 @@ export const interactionUpdateSchema = interactionUpdateBaseSchema
608
611
  // the update doesn't silently leave `scheduled_at` stale.
609
612
  .transform((value) => {
610
613
  if (value.scheduledAt !== undefined) return value
614
+ // An explicit `date: null` is how a caller drops the due date; mirror it
615
+ // onto `scheduledAt` rather than leaving the old timestamp behind (#5941).
616
+ if (value.date === null) return { ...value, scheduledAt: null }
611
617
  if (!value.date && !value.time) return value
612
618
  const derived = deriveScheduledAtFromDateTime(value.date, value.time)
613
619
  return derived ? { ...value, scheduledAt: derived } : value
@@ -2337,6 +2337,7 @@
2337
2337
  "customers.people.form.phoneDuplicateNotice": "Diese Telefonnummer ist bereits {{name}} zugeordnet.",
2338
2338
  "customers.people.form.preferredName": "Bevorzugter Name",
2339
2339
  "customers.people.form.primaryEmail": "E-Mail",
2340
+ "customers.people.form.primaryEmail.invalid": "Geben Sie eine gültige E-Mail-Adresse ein (z. B. name@beispiel.de)",
2340
2341
  "customers.people.form.primaryEmailPlaceholder": "name@beispiel.de",
2341
2342
  "customers.people.form.primaryPhone": "Telefon",
2342
2343
  "customers.people.form.primaryPhone.invalid": "Enter a valid phone number (digits, spaces, dashes, parentheses)",
@@ -2337,6 +2337,7 @@
2337
2337
  "customers.people.form.phoneDuplicateNotice": "This phone number is already assigned to {{name}}.",
2338
2338
  "customers.people.form.preferredName": "Preferred name",
2339
2339
  "customers.people.form.primaryEmail": "Primary email",
2340
+ "customers.people.form.primaryEmail.invalid": "Enter a valid email address (e.g. name@example.com)",
2340
2341
  "customers.people.form.primaryEmailPlaceholder": "name@example.com",
2341
2342
  "customers.people.form.primaryPhone": "Primary phone",
2342
2343
  "customers.people.form.primaryPhone.invalid": "Enter a valid phone number with country code (e.g. +1 212 555 1234)",
@@ -2337,6 +2337,7 @@
2337
2337
  "customers.people.form.phoneDuplicateNotice": "Este número de teléfono ya está asignado a {{name}}.",
2338
2338
  "customers.people.form.preferredName": "Nombre preferido",
2339
2339
  "customers.people.form.primaryEmail": "Correo electrónico",
2340
+ "customers.people.form.primaryEmail.invalid": "Introduce una dirección de correo electrónico válida (p. ej., nombre@ejemplo.com)",
2340
2341
  "customers.people.form.primaryEmailPlaceholder": "nombre@ejemplo.com",
2341
2342
  "customers.people.form.primaryPhone": "Teléfono",
2342
2343
  "customers.people.form.primaryPhone.invalid": "Enter a valid phone number (digits, spaces, dashes, parentheses)",
@@ -2337,6 +2337,7 @@
2337
2337
  "customers.people.form.phoneDuplicateNotice": "이 전화번호는 이미 {{name}}에 지정되어 있습니다.",
2338
2338
  "customers.people.form.preferredName": "선호하는 이름",
2339
2339
  "customers.people.form.primaryEmail": "기본 이메일",
2340
+ "customers.people.form.primaryEmail.invalid": "유효한 이메일 주소를 입력하세요 (예: name@example.com)",
2340
2341
  "customers.people.form.primaryEmailPlaceholder": "name@example.com",
2341
2342
  "customers.people.form.primaryPhone": "기본 전화번호",
2342
2343
  "customers.people.form.primaryPhone.invalid": "국가 코드를 포함한 유효한 전화번호를 입력하세요 (예: +1 212 555 1234)",
@@ -2337,6 +2337,7 @@
2337
2337
  "customers.people.form.phoneDuplicateNotice": "Ten numer telefonu jest już przypisany do {{name}}.",
2338
2338
  "customers.people.form.preferredName": "Preferowane imię",
2339
2339
  "customers.people.form.primaryEmail": "E-mail",
2340
+ "customers.people.form.primaryEmail.invalid": "Wprowadź prawidłowy adres e-mail (np. nazwa@example.com)",
2340
2341
  "customers.people.form.primaryEmailPlaceholder": "nazwa@example.com",
2341
2342
  "customers.people.form.primaryPhone": "Telefon",
2342
2343
  "customers.people.form.primaryPhone.invalid": "Enter a valid phone number (digits, spaces, dashes, parentheses)",