realtimex-crm 0.1.2

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 (468) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +104 -0
  3. package/dist/assets/DealList-DqDrFeDV.js +59 -0
  4. package/dist/assets/DealList-DqDrFeDV.js.map +1 -0
  5. package/dist/assets/index-BiQoGq1P.css +1 -0
  6. package/dist/assets/index-CDIy4x-0.js +152 -0
  7. package/dist/assets/index-CDIy4x-0.js.map +1 -0
  8. package/dist/auth-callback.html +140 -0
  9. package/dist/favicon.ico +0 -0
  10. package/dist/img/adding-users.png +0 -0
  11. package/dist/img/empty.svg +42 -0
  12. package/dist/index.html +1 -0
  13. package/dist/logo192.png +0 -0
  14. package/dist/logo512.png +0 -0
  15. package/dist/logos/0.png +0 -0
  16. package/dist/logos/1.png +0 -0
  17. package/dist/logos/10.png +0 -0
  18. package/dist/logos/11.png +0 -0
  19. package/dist/logos/12.png +0 -0
  20. package/dist/logos/13.png +0 -0
  21. package/dist/logos/14.png +0 -0
  22. package/dist/logos/15.png +0 -0
  23. package/dist/logos/16.png +0 -0
  24. package/dist/logos/17.png +0 -0
  25. package/dist/logos/18.png +0 -0
  26. package/dist/logos/19.png +0 -0
  27. package/dist/logos/2.png +0 -0
  28. package/dist/logos/20.png +0 -0
  29. package/dist/logos/21.png +0 -0
  30. package/dist/logos/22.png +0 -0
  31. package/dist/logos/23.png +0 -0
  32. package/dist/logos/24.png +0 -0
  33. package/dist/logos/25.png +0 -0
  34. package/dist/logos/26.png +0 -0
  35. package/dist/logos/27.png +0 -0
  36. package/dist/logos/28.png +0 -0
  37. package/dist/logos/29.png +0 -0
  38. package/dist/logos/3.png +0 -0
  39. package/dist/logos/30.png +0 -0
  40. package/dist/logos/31.png +0 -0
  41. package/dist/logos/32.png +0 -0
  42. package/dist/logos/33.png +0 -0
  43. package/dist/logos/34.png +0 -0
  44. package/dist/logos/35.png +0 -0
  45. package/dist/logos/36.png +0 -0
  46. package/dist/logos/37.png +0 -0
  47. package/dist/logos/38.png +0 -0
  48. package/dist/logos/39.png +0 -0
  49. package/dist/logos/4.png +0 -0
  50. package/dist/logos/40.png +0 -0
  51. package/dist/logos/41.png +0 -0
  52. package/dist/logos/42.png +0 -0
  53. package/dist/logos/43.png +0 -0
  54. package/dist/logos/44.png +0 -0
  55. package/dist/logos/45.png +0 -0
  56. package/dist/logos/46.png +0 -0
  57. package/dist/logos/47.png +0 -0
  58. package/dist/logos/48.png +0 -0
  59. package/dist/logos/49.png +0 -0
  60. package/dist/logos/5.png +0 -0
  61. package/dist/logos/50.png +0 -0
  62. package/dist/logos/51.png +0 -0
  63. package/dist/logos/52.png +0 -0
  64. package/dist/logos/53.png +0 -0
  65. package/dist/logos/54.png +0 -0
  66. package/dist/logos/55.png +0 -0
  67. package/dist/logos/6.png +0 -0
  68. package/dist/logos/7.png +0 -0
  69. package/dist/logos/8.png +0 -0
  70. package/dist/logos/9.png +0 -0
  71. package/dist/logos/Readme.md +1 -0
  72. package/dist/logos/logo_atomic_crm.svg +14 -0
  73. package/dist/logos/logo_atomic_crm_dark.svg +14 -0
  74. package/dist/logos/logo_atomic_crm_light.svg +14 -0
  75. package/dist/manifest.json +25 -0
  76. package/dist/robots.txt +3 -0
  77. package/dist/stats.html +4949 -0
  78. package/package.json +152 -0
  79. package/public/auth-callback.html +140 -0
  80. package/public/favicon.ico +0 -0
  81. package/public/img/adding-users.png +0 -0
  82. package/public/img/empty.svg +42 -0
  83. package/public/logo192.png +0 -0
  84. package/public/logo512.png +0 -0
  85. package/public/logos/0.png +0 -0
  86. package/public/logos/1.png +0 -0
  87. package/public/logos/10.png +0 -0
  88. package/public/logos/11.png +0 -0
  89. package/public/logos/12.png +0 -0
  90. package/public/logos/13.png +0 -0
  91. package/public/logos/14.png +0 -0
  92. package/public/logos/15.png +0 -0
  93. package/public/logos/16.png +0 -0
  94. package/public/logos/17.png +0 -0
  95. package/public/logos/18.png +0 -0
  96. package/public/logos/19.png +0 -0
  97. package/public/logos/2.png +0 -0
  98. package/public/logos/20.png +0 -0
  99. package/public/logos/21.png +0 -0
  100. package/public/logos/22.png +0 -0
  101. package/public/logos/23.png +0 -0
  102. package/public/logos/24.png +0 -0
  103. package/public/logos/25.png +0 -0
  104. package/public/logos/26.png +0 -0
  105. package/public/logos/27.png +0 -0
  106. package/public/logos/28.png +0 -0
  107. package/public/logos/29.png +0 -0
  108. package/public/logos/3.png +0 -0
  109. package/public/logos/30.png +0 -0
  110. package/public/logos/31.png +0 -0
  111. package/public/logos/32.png +0 -0
  112. package/public/logos/33.png +0 -0
  113. package/public/logos/34.png +0 -0
  114. package/public/logos/35.png +0 -0
  115. package/public/logos/36.png +0 -0
  116. package/public/logos/37.png +0 -0
  117. package/public/logos/38.png +0 -0
  118. package/public/logos/39.png +0 -0
  119. package/public/logos/4.png +0 -0
  120. package/public/logos/40.png +0 -0
  121. package/public/logos/41.png +0 -0
  122. package/public/logos/42.png +0 -0
  123. package/public/logos/43.png +0 -0
  124. package/public/logos/44.png +0 -0
  125. package/public/logos/45.png +0 -0
  126. package/public/logos/46.png +0 -0
  127. package/public/logos/47.png +0 -0
  128. package/public/logos/48.png +0 -0
  129. package/public/logos/49.png +0 -0
  130. package/public/logos/5.png +0 -0
  131. package/public/logos/50.png +0 -0
  132. package/public/logos/51.png +0 -0
  133. package/public/logos/52.png +0 -0
  134. package/public/logos/53.png +0 -0
  135. package/public/logos/54.png +0 -0
  136. package/public/logos/55.png +0 -0
  137. package/public/logos/6.png +0 -0
  138. package/public/logos/7.png +0 -0
  139. package/public/logos/8.png +0 -0
  140. package/public/logos/9.png +0 -0
  141. package/public/logos/Readme.md +1 -0
  142. package/public/logos/logo_atomic_crm.svg +14 -0
  143. package/public/logos/logo_atomic_crm_dark.svg +14 -0
  144. package/public/logos/logo_atomic_crm_light.svg +14 -0
  145. package/public/manifest.json +25 -0
  146. package/public/robots.txt +3 -0
  147. package/src/App.css +42 -0
  148. package/src/App.tsx +58 -0
  149. package/src/assets/react.svg +1 -0
  150. package/src/components/admin/Readme.md +40 -0
  151. package/src/components/admin/admin.tsx +132 -0
  152. package/src/components/admin/app-sidebar.tsx +166 -0
  153. package/src/components/admin/array-field.tsx +59 -0
  154. package/src/components/admin/array-input.tsx +201 -0
  155. package/src/components/admin/authentication.tsx +86 -0
  156. package/src/components/admin/autocomplete-array-input.tsx +254 -0
  157. package/src/components/admin/autocomplete-input.tsx +296 -0
  158. package/src/components/admin/badge-field.tsx +65 -0
  159. package/src/components/admin/boolean-input.tsx +116 -0
  160. package/src/components/admin/breadcrumb.tsx +135 -0
  161. package/src/components/admin/bulk-actions-toolbar.tsx +83 -0
  162. package/src/components/admin/bulk-delete-button.tsx +70 -0
  163. package/src/components/admin/bulk-export-button.tsx +76 -0
  164. package/src/components/admin/cancel-button.tsx +46 -0
  165. package/src/components/admin/columns-button.tsx +345 -0
  166. package/src/components/admin/confirm.tsx +166 -0
  167. package/src/components/admin/count.tsx +94 -0
  168. package/src/components/admin/create-button.tsx +58 -0
  169. package/src/components/admin/create.tsx +132 -0
  170. package/src/components/admin/data-table.tsx +520 -0
  171. package/src/components/admin/date-field.tsx +136 -0
  172. package/src/components/admin/date-input.tsx +317 -0
  173. package/src/components/admin/date-time-input.tsx +331 -0
  174. package/src/components/admin/delete-button.tsx +113 -0
  175. package/src/components/admin/edit-button.tsx +64 -0
  176. package/src/components/admin/edit-guesser.tsx +157 -0
  177. package/src/components/admin/edit.tsx +152 -0
  178. package/src/components/admin/email-field.tsx +74 -0
  179. package/src/components/admin/error.tsx +111 -0
  180. package/src/components/admin/export-button.tsx +126 -0
  181. package/src/components/admin/field-toggle.tsx +164 -0
  182. package/src/components/admin/file-field.tsx +123 -0
  183. package/src/components/admin/file-input.tsx +361 -0
  184. package/src/components/admin/filter-form.tsx +510 -0
  185. package/src/components/admin/form.tsx +312 -0
  186. package/src/components/admin/icon-button-with-tooltip.tsx +85 -0
  187. package/src/components/admin/index.ts +73 -0
  188. package/src/components/admin/input-helper-text.tsx +29 -0
  189. package/src/components/admin/layout.tsx +69 -0
  190. package/src/components/admin/list-guesser.tsx +239 -0
  191. package/src/components/admin/list-pagination.tsx +247 -0
  192. package/src/components/admin/list.tsx +178 -0
  193. package/src/components/admin/loading.tsx +40 -0
  194. package/src/components/admin/locales-menu-button.tsx +60 -0
  195. package/src/components/admin/login-page.tsx +104 -0
  196. package/src/components/admin/notification.tsx +114 -0
  197. package/src/components/admin/number-field.tsx +84 -0
  198. package/src/components/admin/number-input.tsx +124 -0
  199. package/src/components/admin/radio-button-group-input.tsx +184 -0
  200. package/src/components/admin/ready.tsx +55 -0
  201. package/src/components/admin/record-field.tsx +132 -0
  202. package/src/components/admin/reference-array-field.tsx +152 -0
  203. package/src/components/admin/reference-array-input.tsx +68 -0
  204. package/src/components/admin/reference-field.tsx +153 -0
  205. package/src/components/admin/reference-input.tsx +46 -0
  206. package/src/components/admin/reference-many-count.tsx +92 -0
  207. package/src/components/admin/reference-many-field.tsx +132 -0
  208. package/src/components/admin/refresh-button.tsx +31 -0
  209. package/src/components/admin/saved-queries.tsx +174 -0
  210. package/src/components/admin/search-input.tsx +57 -0
  211. package/src/components/admin/select-field.tsx +111 -0
  212. package/src/components/admin/select-input.tsx +323 -0
  213. package/src/components/admin/show-button.tsx +57 -0
  214. package/src/components/admin/show-guesser.tsx +215 -0
  215. package/src/components/admin/show.tsx +184 -0
  216. package/src/components/admin/simple-form-iterator.tsx +582 -0
  217. package/src/components/admin/simple-form.tsx +95 -0
  218. package/src/components/admin/simple-show-layout.tsx +8 -0
  219. package/src/components/admin/single-field-list.tsx +67 -0
  220. package/src/components/admin/sort-button.tsx +152 -0
  221. package/src/components/admin/spinner.tsx +46 -0
  222. package/src/components/admin/text-field.tsx +60 -0
  223. package/src/components/admin/text-input.tsx +77 -0
  224. package/src/components/admin/theme-mode-toggle.tsx +48 -0
  225. package/src/components/admin/theme-provider.tsx +74 -0
  226. package/src/components/admin/toggle-filter-button.tsx +77 -0
  227. package/src/components/admin/url-field.tsx +83 -0
  228. package/src/components/admin/user-menu.tsx +84 -0
  229. package/src/components/atomic-crm/activity/ActivityLog.tsx +54 -0
  230. package/src/components/atomic-crm/activity/ActivityLogCompanyCreated.tsx +50 -0
  231. package/src/components/atomic-crm/activity/ActivityLogContactCreated.tsx +42 -0
  232. package/src/components/atomic-crm/activity/ActivityLogContactNoteCreated.tsx +71 -0
  233. package/src/components/atomic-crm/activity/ActivityLogContext.tsx +11 -0
  234. package/src/components/atomic-crm/activity/ActivityLogDealCreated.tsx +41 -0
  235. package/src/components/atomic-crm/activity/ActivityLogDealNoteCreated.tsx +84 -0
  236. package/src/components/atomic-crm/activity/ActivityLogIterator.tsx +80 -0
  237. package/src/components/atomic-crm/activity/ActivityLogNote.tsx +36 -0
  238. package/src/components/atomic-crm/companies/AutocompleteCompanyInput.tsx +43 -0
  239. package/src/components/atomic-crm/companies/CompanyAside.tsx +207 -0
  240. package/src/components/atomic-crm/companies/CompanyAvatar.tsx +29 -0
  241. package/src/components/atomic-crm/companies/CompanyCard.tsx +88 -0
  242. package/src/components/atomic-crm/companies/CompanyCreate.tsx +41 -0
  243. package/src/components/atomic-crm/companies/CompanyEdit.tsx +33 -0
  244. package/src/components/atomic-crm/companies/CompanyEmpty.tsx +26 -0
  245. package/src/components/atomic-crm/companies/CompanyInputs.tsx +160 -0
  246. package/src/components/atomic-crm/companies/CompanyList.tsx +54 -0
  247. package/src/components/atomic-crm/companies/CompanyListFilter.tsx +55 -0
  248. package/src/components/atomic-crm/companies/CompanyShow.tsx +241 -0
  249. package/src/components/atomic-crm/companies/GridList.tsx +46 -0
  250. package/src/components/atomic-crm/companies/index.ts +11 -0
  251. package/src/components/atomic-crm/companies/sizes.ts +7 -0
  252. package/src/components/atomic-crm/consts.ts +5 -0
  253. package/src/components/atomic-crm/contacts/Avatar.tsx +40 -0
  254. package/src/components/atomic-crm/contacts/ContactAside.tsx +187 -0
  255. package/src/components/atomic-crm/contacts/ContactCreate.tsx +34 -0
  256. package/src/components/atomic-crm/contacts/ContactEdit.tsx +32 -0
  257. package/src/components/atomic-crm/contacts/ContactEmpty.tsx +28 -0
  258. package/src/components/atomic-crm/contacts/ContactImportButton.tsx +213 -0
  259. package/src/components/atomic-crm/contacts/ContactInputs.tsx +209 -0
  260. package/src/components/atomic-crm/contacts/ContactList.tsx +116 -0
  261. package/src/components/atomic-crm/contacts/ContactListContent.tsx +107 -0
  262. package/src/components/atomic-crm/contacts/ContactListFilter.tsx +126 -0
  263. package/src/components/atomic-crm/contacts/ContactMergeButton.tsx +263 -0
  264. package/src/components/atomic-crm/contacts/ContactShow.tsx +76 -0
  265. package/src/components/atomic-crm/contacts/ExportVCardButton.tsx +79 -0
  266. package/src/components/atomic-crm/contacts/TagsList.tsx +33 -0
  267. package/src/components/atomic-crm/contacts/TagsListEdit.tsx +155 -0
  268. package/src/components/atomic-crm/contacts/contacts_export.csv +3 -0
  269. package/src/components/atomic-crm/contacts/exportToVCard.ts +104 -0
  270. package/src/components/atomic-crm/contacts/index.tsx +14 -0
  271. package/src/components/atomic-crm/contacts/useContactImport.tsx +206 -0
  272. package/src/components/atomic-crm/dashboard/Dashboard.tsx +66 -0
  273. package/src/components/atomic-crm/dashboard/DashboardActivityLog.tsx +22 -0
  274. package/src/components/atomic-crm/dashboard/DashboardStepper.tsx +72 -0
  275. package/src/components/atomic-crm/dashboard/DealsChart.tsx +202 -0
  276. package/src/components/atomic-crm/dashboard/DealsPipeline.tsx +90 -0
  277. package/src/components/atomic-crm/dashboard/HotContacts.tsx +92 -0
  278. package/src/components/atomic-crm/dashboard/LatestNotes.tsx +116 -0
  279. package/src/components/atomic-crm/dashboard/TasksList.tsx +69 -0
  280. package/src/components/atomic-crm/dashboard/TasksListEmpty.tsx +22 -0
  281. package/src/components/atomic-crm/dashboard/TasksListFilter.tsx +72 -0
  282. package/src/components/atomic-crm/dashboard/Welcome.tsx +41 -0
  283. package/src/components/atomic-crm/deals/ContactList.tsx +31 -0
  284. package/src/components/atomic-crm/deals/DealArchivedList.tsx +105 -0
  285. package/src/components/atomic-crm/deals/DealCard.tsx +78 -0
  286. package/src/components/atomic-crm/deals/DealColumn.tsx +52 -0
  287. package/src/components/atomic-crm/deals/DealCreate.tsx +95 -0
  288. package/src/components/atomic-crm/deals/DealEdit.tsx +81 -0
  289. package/src/components/atomic-crm/deals/DealEmpty.tsx +63 -0
  290. package/src/components/atomic-crm/deals/DealInputs.tsx +103 -0
  291. package/src/components/atomic-crm/deals/DealList.tsx +95 -0
  292. package/src/components/atomic-crm/deals/DealListContent.tsx +245 -0
  293. package/src/components/atomic-crm/deals/DealShow.tsx +260 -0
  294. package/src/components/atomic-crm/deals/OnlyMineInput.tsx +30 -0
  295. package/src/components/atomic-crm/deals/deal.ts +5 -0
  296. package/src/components/atomic-crm/deals/dealUtils.ts +26 -0
  297. package/src/components/atomic-crm/deals/index.ts +6 -0
  298. package/src/components/atomic-crm/deals/stages.ts +28 -0
  299. package/src/components/atomic-crm/filters/FilterCategory.tsx +20 -0
  300. package/src/components/atomic-crm/layout/FormToolbar.tsx +12 -0
  301. package/src/components/atomic-crm/layout/Header.tsx +134 -0
  302. package/src/components/atomic-crm/layout/Layout.tsx +21 -0
  303. package/src/components/atomic-crm/layout/TopToolbar.tsx +24 -0
  304. package/src/components/atomic-crm/login/LoginSkeleton.tsx +18 -0
  305. package/src/components/atomic-crm/login/SignupPage.tsx +150 -0
  306. package/src/components/atomic-crm/login/StartPage.tsx +27 -0
  307. package/src/components/atomic-crm/misc/AsideSection.tsx +21 -0
  308. package/src/components/atomic-crm/misc/ContactOption.tsx +26 -0
  309. package/src/components/atomic-crm/misc/ImageEditorField.tsx +206 -0
  310. package/src/components/atomic-crm/misc/RelativeDate.tsx +5 -0
  311. package/src/components/atomic-crm/misc/Status.tsx +28 -0
  312. package/src/components/atomic-crm/misc/fetchWithTimeout.ts +19 -0
  313. package/src/components/atomic-crm/misc/isLinkedInUrl.ts +15 -0
  314. package/src/components/atomic-crm/misc/unsupportedDomains.const.ts +105 -0
  315. package/src/components/atomic-crm/misc/useAppBarHeight.ts +9 -0
  316. package/src/components/atomic-crm/misc/usePapaParse.tsx +144 -0
  317. package/src/components/atomic-crm/notes/Note.tsx +187 -0
  318. package/src/components/atomic-crm/notes/NoteAttachments.tsx +56 -0
  319. package/src/components/atomic-crm/notes/NoteCreate.tsx +112 -0
  320. package/src/components/atomic-crm/notes/NoteInputs.tsx +92 -0
  321. package/src/components/atomic-crm/notes/NotesIterator.tsx +37 -0
  322. package/src/components/atomic-crm/notes/StatusSelector.tsx +39 -0
  323. package/src/components/atomic-crm/notes/index.ts +3 -0
  324. package/src/components/atomic-crm/notes/utils.ts +13 -0
  325. package/src/components/atomic-crm/providers/commons/activity.ts +174 -0
  326. package/src/components/atomic-crm/providers/commons/canAccess.ts +26 -0
  327. package/src/components/atomic-crm/providers/commons/getCompanyAvatar.spec.ts +20 -0
  328. package/src/components/atomic-crm/providers/commons/getCompanyAvatar.ts +21 -0
  329. package/src/components/atomic-crm/providers/commons/getContactAvatar.spec.ts +80 -0
  330. package/src/components/atomic-crm/providers/commons/getContactAvatar.ts +70 -0
  331. package/src/components/atomic-crm/providers/commons/mergeContacts.ts +185 -0
  332. package/src/components/atomic-crm/providers/fakerest/authProvider.ts +74 -0
  333. package/src/components/atomic-crm/providers/fakerest/dataGenerator/companies.ts +53 -0
  334. package/src/components/atomic-crm/providers/fakerest/dataGenerator/contactNotes.ts +25 -0
  335. package/src/components/atomic-crm/providers/fakerest/dataGenerator/contacts.ts +103 -0
  336. package/src/components/atomic-crm/providers/fakerest/dataGenerator/dealNotes.ts +19 -0
  337. package/src/components/atomic-crm/providers/fakerest/dataGenerator/deals.ts +53 -0
  338. package/src/components/atomic-crm/providers/fakerest/dataGenerator/finalize.ts +10 -0
  339. package/src/components/atomic-crm/providers/fakerest/dataGenerator/index.ts +25 -0
  340. package/src/components/atomic-crm/providers/fakerest/dataGenerator/sales.ts +37 -0
  341. package/src/components/atomic-crm/providers/fakerest/dataGenerator/tags.ts +14 -0
  342. package/src/components/atomic-crm/providers/fakerest/dataGenerator/tasks.ts +55 -0
  343. package/src/components/atomic-crm/providers/fakerest/dataGenerator/types.ts +21 -0
  344. package/src/components/atomic-crm/providers/fakerest/dataGenerator/utils.ts +28 -0
  345. package/src/components/atomic-crm/providers/fakerest/dataProvider.ts +518 -0
  346. package/src/components/atomic-crm/providers/fakerest/index.ts +2 -0
  347. package/src/components/atomic-crm/providers/fakerest/internal/listParser.ts +48 -0
  348. package/src/components/atomic-crm/providers/fakerest/internal/supabaseAdapter.spec.ts +721 -0
  349. package/src/components/atomic-crm/providers/fakerest/internal/supabaseAdapter.ts +49 -0
  350. package/src/components/atomic-crm/providers/fakerest/internal/transformContainsFilter.spec.ts +35 -0
  351. package/src/components/atomic-crm/providers/fakerest/internal/transformContainsFilter.ts +17 -0
  352. package/src/components/atomic-crm/providers/fakerest/internal/transformFilter.ts +57 -0
  353. package/src/components/atomic-crm/providers/fakerest/internal/transformInFilter.spec.ts +32 -0
  354. package/src/components/atomic-crm/providers/fakerest/internal/transformInFilter.ts +17 -0
  355. package/src/components/atomic-crm/providers/fakerest/internal/transformOrFilter.spec.ts +23 -0
  356. package/src/components/atomic-crm/providers/fakerest/internal/transformOrFilter.ts +17 -0
  357. package/src/components/atomic-crm/providers/supabase/authProvider.ts +121 -0
  358. package/src/components/atomic-crm/providers/supabase/dataProvider.ts +407 -0
  359. package/src/components/atomic-crm/providers/supabase/index.ts +2 -0
  360. package/src/components/atomic-crm/providers/supabase/supabase.ts +34 -0
  361. package/src/components/atomic-crm/providers/types.ts +1 -0
  362. package/src/components/atomic-crm/root/CRM.tsx +167 -0
  363. package/src/components/atomic-crm/root/ConfigurationContext.tsx +80 -0
  364. package/src/components/atomic-crm/root/defaultConfiguration.ts +64 -0
  365. package/src/components/atomic-crm/root/i18nProvider.tsx +25 -0
  366. package/src/components/atomic-crm/sales/SaleName.tsx +13 -0
  367. package/src/components/atomic-crm/sales/SalesCreate.tsx +51 -0
  368. package/src/components/atomic-crm/sales/SalesEdit.tsx +82 -0
  369. package/src/components/atomic-crm/sales/SalesInputs.tsx +31 -0
  370. package/src/components/atomic-crm/sales/SalesList.tsx +62 -0
  371. package/src/components/atomic-crm/sales/index.ts +12 -0
  372. package/src/components/atomic-crm/settings/DatabaseSettings.tsx +169 -0
  373. package/src/components/atomic-crm/settings/SettingsPage.tsx +259 -0
  374. package/src/components/atomic-crm/setup/SupabaseSetupWizard.tsx +215 -0
  375. package/src/components/atomic-crm/simple-list/ListNoResults.tsx +53 -0
  376. package/src/components/atomic-crm/simple-list/ListPlaceholder.tsx +9 -0
  377. package/src/components/atomic-crm/simple-list/SimpleList.tsx +245 -0
  378. package/src/components/atomic-crm/simple-list/SimpleListItem.tsx +138 -0
  379. package/src/components/atomic-crm/simple-list/SimpleListLoading.tsx +60 -0
  380. package/src/components/atomic-crm/tags/RoundButton.tsx +10 -0
  381. package/src/components/atomic-crm/tags/TagChip.tsx +45 -0
  382. package/src/components/atomic-crm/tags/TagCreateModal.tsx +39 -0
  383. package/src/components/atomic-crm/tags/TagDialog.tsx +118 -0
  384. package/src/components/atomic-crm/tags/TagEditModal.tsx +42 -0
  385. package/src/components/atomic-crm/tags/colors.ts +12 -0
  386. package/src/components/atomic-crm/tasks/AddTask.tsx +191 -0
  387. package/src/components/atomic-crm/tasks/Task.tsx +184 -0
  388. package/src/components/atomic-crm/tasks/TaskEdit.tsx +96 -0
  389. package/src/components/atomic-crm/tasks/TasksIterator.tsx +30 -0
  390. package/src/components/atomic-crm/types.ts +226 -0
  391. package/src/components/supabase/forgot-password-page.tsx +86 -0
  392. package/src/components/supabase/layout.tsx +27 -0
  393. package/src/components/supabase/set-password-page.tsx +119 -0
  394. package/src/components/ui/README.md +34 -0
  395. package/src/components/ui/accordion.tsx +64 -0
  396. package/src/components/ui/alert.tsx +66 -0
  397. package/src/components/ui/avatar.tsx +99 -0
  398. package/src/components/ui/badge.tsx +46 -0
  399. package/src/components/ui/breadcrumb.tsx +109 -0
  400. package/src/components/ui/button.tsx +59 -0
  401. package/src/components/ui/card.tsx +92 -0
  402. package/src/components/ui/checkbox.tsx +30 -0
  403. package/src/components/ui/command.tsx +175 -0
  404. package/src/components/ui/dialog.tsx +133 -0
  405. package/src/components/ui/drawer.tsx +133 -0
  406. package/src/components/ui/dropdown-menu.tsx +255 -0
  407. package/src/components/ui/input.tsx +21 -0
  408. package/src/components/ui/label.tsx +24 -0
  409. package/src/components/ui/navigation-menu.tsx +168 -0
  410. package/src/components/ui/pagination.tsx +127 -0
  411. package/src/components/ui/popover.tsx +46 -0
  412. package/src/components/ui/progress.tsx +29 -0
  413. package/src/components/ui/radio-group.tsx +43 -0
  414. package/src/components/ui/select.tsx +183 -0
  415. package/src/components/ui/separator.tsx +26 -0
  416. package/src/components/ui/sheet.tsx +137 -0
  417. package/src/components/ui/sidebar.tsx +724 -0
  418. package/src/components/ui/skeleton.tsx +13 -0
  419. package/src/components/ui/sonner.tsx +38 -0
  420. package/src/components/ui/spinner.tsx +51 -0
  421. package/src/components/ui/switch.tsx +29 -0
  422. package/src/components/ui/table.tsx +114 -0
  423. package/src/components/ui/tabs.tsx +64 -0
  424. package/src/components/ui/textarea.tsx +18 -0
  425. package/src/components/ui/tooltip.tsx +61 -0
  426. package/src/hooks/saved-queries.tsx +67 -0
  427. package/src/hooks/simple-form-iterator-context.tsx +70 -0
  428. package/src/hooks/use-mobile.ts +21 -0
  429. package/src/hooks/useBulkExport.tsx +61 -0
  430. package/src/hooks/useSupportCreateSuggestion.tsx +188 -0
  431. package/src/hooks/user-menu-context.tsx +24 -0
  432. package/src/index.css +170 -0
  433. package/src/lib/field.type.ts +22 -0
  434. package/src/lib/genericMemo.ts +18 -0
  435. package/src/lib/i18nProvider.ts +9 -0
  436. package/src/lib/sanitizeInputRestProps.ts +46 -0
  437. package/src/lib/supabase-config.ts +123 -0
  438. package/src/lib/utils.ts +6 -0
  439. package/src/main.tsx +10 -0
  440. package/src/setupTests.js +5 -0
  441. package/src/vite-env.d.ts +1 -0
  442. package/supabase/config.toml +157 -0
  443. package/supabase/functions/.env.development +7 -0
  444. package/supabase/functions/_shared/db.ts +187 -0
  445. package/supabase/functions/_shared/supabaseAdmin.ts +13 -0
  446. package/supabase/functions/_shared/utils.ts +13 -0
  447. package/supabase/functions/mergeContacts/index.ts +215 -0
  448. package/supabase/functions/postmark/addNoteToContact.ts +129 -0
  449. package/supabase/functions/postmark/extractMailContactData.ts +41 -0
  450. package/supabase/functions/postmark/getExpectedAuthorization.ts +4 -0
  451. package/supabase/functions/postmark/getNoteContent.ts +6 -0
  452. package/supabase/functions/postmark/index.ts +210 -0
  453. package/supabase/functions/updatePassword/index.ts +50 -0
  454. package/supabase/functions/users/index.ts +206 -0
  455. package/supabase/migrations/20240730075029_init_db.sql +600 -0
  456. package/supabase/migrations/20240730075425_init_triggers.sql +57 -0
  457. package/supabase/migrations/20240806124555_task_sales_id.sql +1 -0
  458. package/supabase/migrations/20240807082449_remove-aquisition.sql +20 -0
  459. package/supabase/migrations/20240808141826_init_state_configure.sql +9 -0
  460. package/supabase/migrations/20240813084010_tags_policy.sql +18 -0
  461. package/supabase/migrations/20241104153231_sales_policies.sql +7 -0
  462. package/supabase/migrations/20250109152531_email_jsonb.sql +43 -0
  463. package/supabase/migrations/20250113132531_phone_jsonb.sql +67 -0
  464. package/supabase/migrations/20251204172855_merge_contacts_function.sql +153 -0
  465. package/supabase/migrations/20251204201317_drop_merge_contacts_function.sql +2 -0
  466. package/supabase/seed.sql +0 -0
  467. package/supabase/templates/invite.html +70 -0
  468. package/supabase/templates/recovery.html +75 -0
@@ -0,0 +1,317 @@
1
+ import * as React from "react";
2
+ import type { InputProps } from "ra-core";
3
+ import { useInput, FieldTitle, useEvent, useResourceContext } from "ra-core";
4
+ import {
5
+ FormControl,
6
+ FormError,
7
+ FormField,
8
+ FormLabel,
9
+ } from "@/components/admin/form";
10
+ import { Input } from "@/components/ui/input";
11
+ import { InputHelperText } from "@/components/admin/input-helper-text";
12
+ import { cn } from "@/lib/utils";
13
+
14
+ /**
15
+ * Date picker input for editing date values in "YYYY-MM-DD" format.
16
+ *
17
+ * Use `<DateInput>` for publication dates, deadlines, birthdays, or any date field. Renders
18
+ * a native browser date picker (appearance varies by browser). Automatically handles date parsing
19
+ * from ISO strings, Date objects, or timestamps.
20
+ *
21
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/dateinput/ DateInput documentation}
22
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/date MDN documentation for input type="date"}
23
+ *
24
+ * @example
25
+ * import {
26
+ * Edit,
27
+ * SimpleForm,
28
+ * DateInput,
29
+ * TextInput,
30
+ * } from '@/components/admin';
31
+ *
32
+ * const PostEdit = () => (
33
+ * <Edit>
34
+ * <SimpleForm>
35
+ * <TextInput source="title" />
36
+ * <DateInput source="published_at" />
37
+ * <DateInput source="expires_at" />
38
+ * </SimpleForm>
39
+ * </Edit>
40
+ * );
41
+ *
42
+ * @example
43
+ * // If the initial value string contains more than a date (e.g. an hour, a timezone),
44
+ * // these details are ignored.
45
+ * <DateInput source="published_at" defaultValue="2021-09-11T20:46:20.000-04:00" />
46
+ * // The input will display '2021-09-11' regardless of the browser timezone.
47
+ *
48
+ * @example
49
+ * // If the initial value is a Date object, DateInput converts it to a string
50
+ * // and ignores the timezone.
51
+ * <DateInput source="published_at" defaultValue={new Date("2021-09-11T20:46:20.000-04:00")} />
52
+ * // The input will display '2021-09-11' regardless of the browser timezone.
53
+ *
54
+ * @example
55
+ * // If you want to manipulate the value from the field to adjust its timezone, use the format prop
56
+ * <DateInput source="published_at" format={value => new Date(value).toISOString().split("T")[0]} />
57
+ * // The input will display the UTC day regardless of the browser timezone.
58
+ *
59
+ * @example
60
+ * // If you want the returned value to be a Date, you must pass a custom parse method
61
+ * // to convert the form value (which is always a date string) back to a Date object.
62
+ * <DateInput source="published_at" parse={val => new Date(val)} />
63
+ */
64
+ export const DateInput = (props: DateInputProps) => {
65
+ const {
66
+ className,
67
+ inputClassName,
68
+ defaultValue,
69
+ format = defaultFormat,
70
+ label,
71
+ source,
72
+ helperText,
73
+ onChange,
74
+ onFocus,
75
+ validate,
76
+ disabled,
77
+ readOnly,
78
+ ...rest
79
+ } = props;
80
+
81
+ const resource = useResourceContext(props);
82
+
83
+ const {
84
+ field,
85
+ fieldState: _fieldState,
86
+ id,
87
+ isRequired,
88
+ } = useInput({
89
+ defaultValue,
90
+ source,
91
+ validate,
92
+ disabled,
93
+ readOnly,
94
+ format,
95
+ ...rest,
96
+ });
97
+ const localInputRef = React.useRef<HTMLInputElement>(null);
98
+ // DateInput is not a really controlled input to ensure users can start entering a date, go to another input and come back to complete it.
99
+ // This ref stores the value that is passed to the input defaultValue prop to solve this issue.
100
+ const initialDefaultValueRef = React.useRef(field.value);
101
+ // As the defaultValue prop won't trigger a remount of the HTML input, we will force it by changing the key.
102
+ const [inputKey, setInputKey] = React.useState(1);
103
+ // This ref let us track that the last change of the form state value was made by the input itself
104
+ const wasLastChangedByInput = React.useRef(false);
105
+
106
+ // This effect ensures we stays in sync with the react-hook-form state when the value changes from outside the input
107
+ // for instance by using react-hook-form reset or setValue methods.
108
+ React.useEffect(() => {
109
+ // Ignore react-hook-form state changes if it came from the input itself
110
+ if (wasLastChangedByInput.current) {
111
+ // Resets the flag to ensure futures changes are handled
112
+ wasLastChangedByInput.current = false;
113
+ return;
114
+ }
115
+
116
+ const hasNewValueFromForm =
117
+ localInputRef.current?.value !== field.value &&
118
+ !(localInputRef.current?.value === "" && field.value == null);
119
+
120
+ if (hasNewValueFromForm) {
121
+ // The value has changed from outside the input, we update the input value
122
+ initialDefaultValueRef.current = field.value;
123
+ // Trigger a remount of the HTML input
124
+ setInputKey((r) => r + 1);
125
+ // Resets the flag to ensure futures changes are handled
126
+ wasLastChangedByInput.current = false;
127
+ }
128
+ }, [setInputKey, field.value]);
129
+
130
+ const { onBlur: onBlurFromField } = field;
131
+ const hasFocus = React.useRef(false);
132
+
133
+ // Update the input text when the user types in the input.
134
+ // Also, update the react-hook-form value if the input value is a valid date string.
135
+ const handleChange = useEvent(
136
+ (event: React.ChangeEvent<HTMLInputElement>) => {
137
+ if (onChange) {
138
+ onChange(event);
139
+ }
140
+ if (
141
+ typeof event.target === "undefined" ||
142
+ typeof event.target.value === "undefined"
143
+ ) {
144
+ return;
145
+ }
146
+ const target = event.target;
147
+ const newValue = target.value;
148
+ const isNewValueValid =
149
+ newValue === "" ||
150
+ (target.valueAsDate != null &&
151
+ !isNaN(new Date(target.valueAsDate).getTime()));
152
+
153
+ // Some browsers will return null for an invalid date
154
+ // so we only change react-hook-form value if it's not null.
155
+ // The input reset is handled in the onBlur event handler
156
+ if (newValue !== "" && newValue != null && isNewValueValid) {
157
+ field.onChange(newValue);
158
+ // Track the fact that the next react-hook-form state change was triggered by the input itself
159
+ wasLastChangedByInput.current = true;
160
+ }
161
+ },
162
+ );
163
+
164
+ const handleFocus = useEvent((event: React.FocusEvent<HTMLInputElement>) => {
165
+ if (onFocus) {
166
+ onFocus(event);
167
+ }
168
+ hasFocus.current = true;
169
+ });
170
+
171
+ const handleBlur = useEvent(() => {
172
+ hasFocus.current = false;
173
+
174
+ if (!localInputRef.current) {
175
+ return;
176
+ }
177
+
178
+ const newValue = localInputRef.current.value;
179
+ // To ensure users can clear the input, we check its value on blur
180
+ // and submit it to react-hook-form
181
+ const isNewValueValid =
182
+ newValue === "" ||
183
+ (localInputRef.current.valueAsDate != null &&
184
+ !isNaN(new Date(localInputRef.current.valueAsDate).getTime()));
185
+
186
+ if (isNewValueValid && field.value !== newValue) {
187
+ field.onChange(newValue ?? "");
188
+ }
189
+
190
+ if (onBlurFromField) {
191
+ onBlurFromField();
192
+ }
193
+ });
194
+
195
+ const { ref, name } = field;
196
+ const inputRef = React.useCallback(
197
+ (node: HTMLInputElement | null) => {
198
+ if (typeof ref === "function") {
199
+ ref(node);
200
+ } else if (ref && node) {
201
+ (ref as React.MutableRefObject<HTMLInputElement | null>).current = node;
202
+ }
203
+ localInputRef.current = node;
204
+ },
205
+ [ref],
206
+ );
207
+
208
+ return (
209
+ <FormField id={id} className={className} name={name}>
210
+ {label !== false && (
211
+ <FormLabel>
212
+ <FieldTitle
213
+ label={label}
214
+ source={source}
215
+ resource={resource}
216
+ isRequired={isRequired}
217
+ />
218
+ </FormLabel>
219
+ )}
220
+ <FormControl>
221
+ <Input
222
+ ref={inputRef}
223
+ defaultValue={format(initialDefaultValueRef.current) ?? ""}
224
+ key={inputKey}
225
+ type="date"
226
+ onChange={handleChange}
227
+ onFocus={handleFocus}
228
+ onBlur={handleBlur}
229
+ className={cn(
230
+ "ra-input",
231
+ `ra-input-${source}`,
232
+ "[color-scheme:light] dark:[color-scheme:dark] relative [&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:absolute [&::-webkit-calendar-picker-indicator]:right-3 [&::-webkit-calendar-picker-indicator]:opacity-100",
233
+ inputClassName,
234
+ )}
235
+ disabled={disabled || readOnly}
236
+ readOnly={readOnly}
237
+ {...rest}
238
+ />
239
+ </FormControl>
240
+ <InputHelperText helperText={helperText} />
241
+ <FormError />
242
+ </FormField>
243
+ );
244
+ };
245
+
246
+ export type DateInputProps = InputProps & {
247
+ inputClassName?: string;
248
+ } & Omit<React.ComponentProps<"input">, "label" | "defaultValue">;
249
+
250
+ /**
251
+ * Convert Date object to String, using the local timezone
252
+ *
253
+ * @param {Date} value value to convert
254
+ * @returns {String} A standardized date (yyyy-MM-dd), to be passed to an <input type="date" />
255
+ */
256
+ const convertDateToString = (value: Date) => {
257
+ if (!(value instanceof Date) || isNaN(value.getDate())) return "";
258
+ const localDate = new Date(value.getTime());
259
+ const pad = "00";
260
+ const yyyy = localDate.getFullYear().toString();
261
+ const MM = (localDate.getMonth() + 1).toString();
262
+ const dd = localDate.getDate().toString();
263
+ return `${yyyy}-${(pad + MM).slice(-2)}-${(pad + dd).slice(-2)}`;
264
+ };
265
+
266
+ const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
267
+
268
+ /**
269
+ * Convert a form state value to a date string for the `<input type="date">` value.
270
+ *
271
+ * Form state values can be anything from:
272
+ * - a string in the "YYYY-MM-DD" format
273
+ * - A valid date string
274
+ * - an ISO date string
275
+ * - a Date object
276
+ * - a Linux timestamp
277
+ * - an empty string
278
+ *
279
+ * When it's not a bare date string (YYYY-MM-DD), the value is converted to
280
+ * this format using the JS Date object.
281
+ * THIS MAY CHANGE THE DATE VALUE depending on the browser locale.
282
+ * For example, the string "09/11/2021" may be converted to "2021-09-10"
283
+ * in Honolulu. This is expected behavior.
284
+ * If this is not what you want, you should provide your own parse method.
285
+ *
286
+ * The output is always a string in the "YYYY-MM-DD" format.
287
+ *
288
+ * @example
289
+ * defaultFormat('2021-09-11'); // '2021-09-11'
290
+ * defaultFormat('09/11/2021'); // '2021-09-11' (may change depending on the browser locale)
291
+ * defaultFormat('2021-09-11T20:46:20.000Z'); // '2021-09-11' (may change depending on the browser locale)
292
+ * defaultFormat(new Date('2021-09-11T20:46:20.000Z')); // '2021-09-11' (may change depending on the browser locale)
293
+ * defaultFormat(1631385980000); // '2021-09-11' (may change depending on the browser locale)
294
+ * defaultFormat(''); // null
295
+ */
296
+ const defaultFormat = (value: string | Date | number) => {
297
+ // null, undefined and empty string values should not go through dateFormatter
298
+ // otherwise, it returns undefined and will make the input an uncontrolled one.
299
+ if (value == null || value === "") {
300
+ return null;
301
+ }
302
+
303
+ // Date objects should be converted to strings
304
+ if (value instanceof Date) {
305
+ return convertDateToString(value);
306
+ }
307
+
308
+ // Valid date strings (YYYY-MM-DD) should be considered as is
309
+ if (typeof value === "string") {
310
+ if (dateRegex.test(value)) {
311
+ return value;
312
+ }
313
+ }
314
+
315
+ // other values (e.g., localized date strings, timestamps) need to be converted to Dates first
316
+ return convertDateToString(new Date(value));
317
+ };
@@ -0,0 +1,331 @@
1
+ import * as React from "react";
2
+ import clsx from "clsx";
3
+ import type { InputProps } from "ra-core";
4
+ import { useInput, FieldTitle } from "ra-core";
5
+ import {
6
+ FormControl,
7
+ FormError,
8
+ FormField,
9
+ FormLabel,
10
+ } from "@/components/admin/form";
11
+ import { Input } from "@/components/ui/input";
12
+ import { InputHelperText } from "@/components/admin/input-helper-text";
13
+
14
+ /**
15
+ * Date and time picker input for editing datetime values with timezone support.
16
+ *
17
+ * Use `<DateTimeInput>` for timestamps like "created at", "updated at", or scheduled events.
18
+ * Renders a native browser datetime-local picker. Expects and returns ISO 8601 formatted strings
19
+ * (e.g. '2025-11-17T10:10:32.390Z'), automatically converting other formats like Date objects or timestamps.
20
+ *
21
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/datetimeinput/ DateTimeInput documentation}
22
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local MDN documentation for input type="datetime-local"}
23
+ *
24
+ * @example
25
+ * import {
26
+ * Edit,
27
+ * SimpleForm,
28
+ * DateTimeInput,
29
+ * TextInput,
30
+ * } from '@/components/admin';
31
+ *
32
+ * const EventEdit = () => (
33
+ * <Edit>
34
+ * <SimpleForm>
35
+ * <TextInput source="title" />
36
+ * <DateTimeInput source="starts_at" />
37
+ * <DateTimeInput source="ends_at" />
38
+ * </SimpleForm>
39
+ * </Edit>
40
+ * );
41
+ */
42
+ export const DateTimeInput = ({
43
+ className,
44
+ defaultValue,
45
+ format = formatDateTime,
46
+ parse = convertDateStringToISO,
47
+ label,
48
+ helperText,
49
+ onBlur,
50
+ onChange,
51
+ onFocus,
52
+ source,
53
+ resource,
54
+ validate,
55
+ disabled,
56
+ readOnly,
57
+ ...rest
58
+ }: DateTimeInputProps) => {
59
+ const { field, id, isRequired } = useInput({
60
+ defaultValue,
61
+ onBlur,
62
+ resource,
63
+ source,
64
+ validate,
65
+ disabled,
66
+ readOnly,
67
+ format,
68
+ parse,
69
+ ...rest,
70
+ });
71
+ const localInputRef = React.useRef<HTMLInputElement>(undefined);
72
+ // DateInput is not a really controlled input to ensure users can start entering a date, go to another input and come back to complete it.
73
+ // This ref stores the value that is passed to the input defaultValue prop to solve this issue.
74
+ const initialDefaultValueRef = React.useRef(field.value);
75
+ // As the defaultValue prop won't trigger a remount of the HTML input, we will force it by changing the key.
76
+ const [inputKey, setInputKey] = React.useState(1);
77
+ // This ref let us track that the last change of the form state value was made by the input itself
78
+ const wasLastChangedByInput = React.useRef(false);
79
+
80
+ // This effect ensures we stays in sync with the react-hook-form state when the value changes from outside the input
81
+ // for instance by using react-hook-form reset or setValue methods.
82
+ React.useEffect(() => {
83
+ // Ignore react-hook-form state changes if it came from the input itself
84
+ if (wasLastChangedByInput.current) {
85
+ // Resets the flag to ensure futures changes are handled
86
+ wasLastChangedByInput.current = false;
87
+ return;
88
+ }
89
+
90
+ const hasNewValueFromForm =
91
+ localInputRef.current?.value !== field.value &&
92
+ !(localInputRef.current?.value === "" && field.value == null);
93
+
94
+ if (hasNewValueFromForm) {
95
+ // The value has changed from outside the input, we update the input value
96
+ initialDefaultValueRef.current = field.value;
97
+ // Trigger a remount of the HTML input
98
+ setInputKey((r) => r + 1);
99
+ // Resets the flag to ensure futures changes are handled
100
+ wasLastChangedByInput.current = false;
101
+ }
102
+ }, [setInputKey, field.value]);
103
+
104
+ const { onBlur: onBlurFromField } = field;
105
+ const hasFocus = React.useRef(false);
106
+
107
+ // update the input text when the user types in the input
108
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
109
+ if (onChange) {
110
+ onChange(event);
111
+ }
112
+ if (
113
+ typeof event.target === "undefined" ||
114
+ typeof event.target.value === "undefined"
115
+ ) {
116
+ return;
117
+ }
118
+ const target = event.target;
119
+ const newValue = target.value;
120
+ const isNewValueValid =
121
+ newValue === "" || !isNaN(new Date(target.value).getTime());
122
+
123
+ // Some browsers will return null for an invalid date
124
+ // so we only change react-hook-form value if it's not null.
125
+ // The input reset is handled in the onBlur event handler
126
+ if (newValue !== "" && newValue != null && isNewValueValid) {
127
+ field.onChange(newValue);
128
+ // Track the fact that the next react-hook-form state change was triggered by the input itself
129
+ wasLastChangedByInput.current = true;
130
+ }
131
+ };
132
+
133
+ const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
134
+ if (onFocus) {
135
+ onFocus(event);
136
+ }
137
+ hasFocus.current = true;
138
+ };
139
+
140
+ const handleBlur = () => {
141
+ hasFocus.current = false;
142
+
143
+ if (!localInputRef.current) {
144
+ return;
145
+ }
146
+
147
+ const newValue = localInputRef.current.value;
148
+ // To ensure users can clear the input, we check its value on blur
149
+ // and submit it to react-hook-form
150
+ const isNewValueValid =
151
+ newValue === "" ||
152
+ !isNaN(new Date(localInputRef.current.value).getTime());
153
+
154
+ if (isNewValueValid && field.value !== newValue) {
155
+ field.onChange(newValue ?? "");
156
+ }
157
+
158
+ if (onBlurFromField) {
159
+ onBlurFromField();
160
+ }
161
+ };
162
+
163
+ const { ref, name } = field;
164
+ const inputRef = useForkRef(ref, localInputRef);
165
+
166
+ return (
167
+ <FormField id={id} className={className} name={field.name}>
168
+ {label !== false && (
169
+ <FormLabel>
170
+ <FieldTitle
171
+ label={label}
172
+ source={source}
173
+ resource={resource}
174
+ isRequired={isRequired}
175
+ />
176
+ </FormLabel>
177
+ )}
178
+ <FormControl>
179
+ <Input
180
+ id={id}
181
+ ref={inputRef}
182
+ name={name}
183
+ defaultValue={format(initialDefaultValueRef.current)}
184
+ key={inputKey}
185
+ type="datetime-local"
186
+ onChange={handleChange}
187
+ onFocus={handleFocus}
188
+ onBlur={handleBlur}
189
+ className={clsx(
190
+ "ra-input",
191
+ `ra-input-${source}`,
192
+ "[color-scheme:light] dark:[color-scheme:dark] relative [&::-webkit-calendar-picker-indicator]:cursor-pointer [&::-webkit-calendar-picker-indicator]:absolute [&::-webkit-calendar-picker-indicator]:right-3 [&::-webkit-calendar-picker-indicator]:opacity-100",
193
+ className,
194
+ )}
195
+ disabled={disabled || readOnly}
196
+ readOnly={readOnly}
197
+ />
198
+ </FormControl>
199
+ <InputHelperText helperText={helperText} />
200
+ <FormError />
201
+ </FormField>
202
+ );
203
+ };
204
+
205
+ export type DateTimeInputProps = Omit<InputProps, "defaultValue"> & {
206
+ defaultValue?: string | number | Date;
207
+ } & Omit<React.ComponentProps<"input">, "defaultValue" | "type">;
208
+
209
+ const leftPad =
210
+ (nb = 2) =>
211
+ (value: number) =>
212
+ ("0".repeat(nb) + value).slice(-nb);
213
+ const leftPad4 = leftPad(4);
214
+ const leftPad2 = leftPad(2);
215
+
216
+ /**
217
+ * @param {Date} value value to convert
218
+ * @returns {String} A standardized datetime (yyyy-MM-ddThh:mm), to be passed to an <input type="datetime-local" />
219
+ */
220
+ const convertDateToString = (value: Date) => {
221
+ if (!(value instanceof Date) || isNaN(value.getDate())) return "";
222
+ const yyyy = leftPad4(value.getFullYear());
223
+ const MM = leftPad2(value.getMonth() + 1);
224
+ const dd = leftPad2(value.getDate());
225
+ const hh = leftPad2(value.getHours());
226
+ const mm = leftPad2(value.getMinutes());
227
+ return `${yyyy}-${MM}-${dd}T${hh}:${mm}`;
228
+ };
229
+
230
+ // yyyy-MM-ddThh:mm
231
+ const dateTimeRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}$/;
232
+
233
+ /**
234
+ * Converts a date from the dataProvider, with timezone, to a date string
235
+ * without timezone for use in an <input type="datetime-local" />.
236
+ *
237
+ * @param {Date | String} value date string or object
238
+ */
239
+ const formatDateTime = (value: string | Date) => {
240
+ // null, undefined and empty string values should not go through convertDateToString
241
+ // otherwise, it returns undefined and will make the input an uncontrolled one.
242
+ if (value == null || value === "") {
243
+ return "";
244
+ }
245
+
246
+ if (value instanceof Date) {
247
+ return convertDateToString(value);
248
+ }
249
+ // valid dates should not be converted
250
+ if (dateTimeRegex.test(value)) {
251
+ return value;
252
+ }
253
+
254
+ return convertDateToString(new Date(value));
255
+ };
256
+
257
+ // converts a date string entered usinf a datetime-local input
258
+ // into an ISO date using the browser timezone
259
+ const convertDateStringToISO = (date: string) => {
260
+ const localDate = new Date(date);
261
+ return localDate.toISOString();
262
+ };
263
+
264
+ /**
265
+ * Imported from material-ui
266
+ * Merges refs into a single memoized callback ref or `null`.
267
+ *
268
+ * ```tsx
269
+ * const rootRef = React.useRef<Instance>(null);
270
+ * const refFork = useForkRef(rootRef, props.ref);
271
+ *
272
+ * return (
273
+ * <Root {...props} ref={refFork} />
274
+ * );
275
+ * ```
276
+ *
277
+ * @param {Array<React.Ref<Instance> | undefined>} refs The ref array.
278
+ * @returns {React.RefCallback<Instance> | null} The new ref callback.
279
+ */
280
+ function useForkRef<Instance>(
281
+ ...refs: Array<React.Ref<Instance> | undefined>
282
+ ): React.RefCallback<Instance> | null {
283
+ const cleanupRef = React.useRef<() => void>(undefined);
284
+
285
+ const refEffect = React.useCallback((instance: Instance) => {
286
+ const cleanups = refs.map((ref) => {
287
+ if (ref == null) {
288
+ return null;
289
+ }
290
+
291
+ if (typeof ref === "function") {
292
+ const refCallback = ref;
293
+ const refCleanup: void | (() => void) = refCallback(instance);
294
+ return typeof refCleanup === "function"
295
+ ? refCleanup
296
+ : () => {
297
+ refCallback(null);
298
+ };
299
+ }
300
+
301
+ ref.current = instance;
302
+ return () => {
303
+ ref.current = null;
304
+ };
305
+ });
306
+
307
+ return () => {
308
+ cleanups.forEach((refCleanup) => refCleanup?.());
309
+ };
310
+ // eslint-disable-next-line react-hooks/exhaustive-deps
311
+ }, refs);
312
+
313
+ return React.useMemo(() => {
314
+ if (refs.every((ref) => ref == null)) {
315
+ return null;
316
+ }
317
+
318
+ return (value) => {
319
+ if (cleanupRef.current) {
320
+ cleanupRef.current();
321
+ cleanupRef.current = undefined;
322
+ }
323
+
324
+ if (value != null) {
325
+ cleanupRef.current = refEffect(value);
326
+ }
327
+ };
328
+ // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- intentionally ignoring that the dependency array must be an array literal
329
+ // eslint-disable-next-line react-hooks/exhaustive-deps
330
+ }, refs);
331
+ }