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,60 @@
1
+ import { Check } from "lucide-react";
2
+ import { Button } from "@/components/ui/button";
3
+ import {
4
+ DropdownMenu,
5
+ DropdownMenuContent,
6
+ DropdownMenuItem,
7
+ DropdownMenuTrigger,
8
+ } from "@/components/ui/dropdown-menu";
9
+ import { cn } from "@/lib/utils";
10
+ import { useLocales, useLocaleState } from "ra-core";
11
+
12
+ /**
13
+ * Language switcher button that displays a menu allowing users to select the interface language.
14
+ *
15
+ * Automatically renders in the header when multiple locales are configured in the i18nProvider.
16
+ * User's language selection is persisted using the store.
17
+ * Returns null if only one language is available.
18
+ *
19
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/localesmenubutton LocalesMenuButton documentation}
20
+ * @see {@link https://marmelab.com/ra-core/translationsetup/ i18nProvider setup}
21
+ */
22
+ export function LocalesMenuButton() {
23
+ const languages = useLocales();
24
+ const [locale, setLocale] = useLocaleState();
25
+
26
+ const getNameForLocale = (locale: string): string => {
27
+ const language = languages.find((language) => language.locale === locale);
28
+ return language ? language.name : "";
29
+ };
30
+
31
+ const changeLocale = (locale: string) => (): void => {
32
+ setLocale(locale);
33
+ };
34
+
35
+ if (languages.length <= 1) {
36
+ return null; // No need to render the dropdown if there's only one language
37
+ }
38
+ return (
39
+ <DropdownMenu modal={false}>
40
+ <DropdownMenuTrigger asChild>
41
+ <Button variant="ghost" size="icon" className="hidden sm:inline-flex">
42
+ {locale.toUpperCase()}
43
+ </Button>
44
+ </DropdownMenuTrigger>
45
+ <DropdownMenuContent align="end">
46
+ {languages.map((language) => (
47
+ <DropdownMenuItem
48
+ key={language.locale}
49
+ onClick={changeLocale(language.locale)}
50
+ >
51
+ {getNameForLocale(language.locale)}
52
+ <Check
53
+ className={cn("ml-auto", locale !== language.locale && "hidden")}
54
+ />
55
+ </DropdownMenuItem>
56
+ ))}
57
+ </DropdownMenuContent>
58
+ </DropdownMenu>
59
+ );
60
+ }
@@ -0,0 +1,104 @@
1
+ import { useState } from "react";
2
+ import { Form, required, useLogin, useNotify } from "ra-core";
3
+ import type { SubmitHandler, FieldValues } from "react-hook-form";
4
+ import { Link } from "react-router";
5
+ import { Button } from "@/components/ui/button";
6
+ import { TextInput } from "@/components/admin/text-input";
7
+ import { Notification } from "@/components/admin/notification";
8
+ import { useConfigurationContext } from "@/components/atomic-crm/root/ConfigurationContext.tsx";
9
+
10
+ /**
11
+ * Login page displayed when authentication is enabled and the user is not authenticated.
12
+ *
13
+ * Automatically shown when an unauthenticated user tries to access a protected route.
14
+ * Handles login via authProvider.login() and displays error notifications on failure.
15
+ *
16
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/loginpage LoginPage documentation}
17
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/security Security documentation}
18
+ */
19
+ export const LoginPage = (props: { redirectTo?: string }) => {
20
+ const { darkModeLogo, title } = useConfigurationContext();
21
+ const { redirectTo } = props;
22
+ const [loading, setLoading] = useState(false);
23
+ const login = useLogin();
24
+ const notify = useNotify();
25
+
26
+ const handleSubmit: SubmitHandler<FieldValues> = (values) => {
27
+ setLoading(true);
28
+ login(values, redirectTo)
29
+ .then(() => {
30
+ setLoading(false);
31
+ })
32
+ .catch((error) => {
33
+ setLoading(false);
34
+ notify(
35
+ typeof error === "string"
36
+ ? error
37
+ : typeof error === "undefined" || !error.message
38
+ ? "ra.auth.sign_in_error"
39
+ : error.message,
40
+ {
41
+ type: "error",
42
+ messageArgs: {
43
+ _:
44
+ typeof error === "string"
45
+ ? error
46
+ : error && error.message
47
+ ? error.message
48
+ : undefined,
49
+ },
50
+ },
51
+ );
52
+ });
53
+ };
54
+
55
+ return (
56
+ <div className="min-h-screen flex">
57
+ <div className="container relative grid flex-col items-center justify-center sm:max-w-none lg:grid-cols-2 lg:px-0">
58
+ <div className="relative hidden h-full flex-col bg-muted p-10 text-white dark:border-r lg:flex">
59
+ <div className="absolute inset-0 bg-zinc-900" />
60
+ <div className="relative z-20 flex items-center text-lg font-medium">
61
+ <img className="h-6 mr-2" src={darkModeLogo} alt={title} />
62
+ {title}
63
+ </div>
64
+ </div>
65
+ <div className="lg:p-8">
66
+ <div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
67
+ <div className="flex flex-col space-y-2 text-center">
68
+ <h1 className="text-2xl font-semibold tracking-tight">Sign in</h1>
69
+ </div>
70
+ <Form className="space-y-8" onSubmit={handleSubmit}>
71
+ <TextInput
72
+ label="Email"
73
+ source="email"
74
+ type="email"
75
+ validate={required()}
76
+ />
77
+ <TextInput
78
+ label="Password"
79
+ source="password"
80
+ type="password"
81
+ validate={required()}
82
+ />
83
+ <Button
84
+ type="submit"
85
+ className="cursor-pointer"
86
+ disabled={loading}
87
+ >
88
+ Sign in
89
+ </Button>
90
+ </Form>
91
+
92
+ <Link
93
+ to={"/forgot-password"}
94
+ className="text-sm text-center hover:underline"
95
+ >
96
+ Forgot your password?
97
+ </Link>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ <Notification />
102
+ </div>
103
+ );
104
+ };
@@ -0,0 +1,114 @@
1
+ import * as React from "react";
2
+ import { useCallback, useEffect } from "react";
3
+ import type { ToasterProps } from "sonner";
4
+ import { Toaster, toast } from "sonner";
5
+ import { useTheme } from "@/components/admin/theme-provider";
6
+ import {
7
+ CloseNotificationContext,
8
+ useNotificationContext,
9
+ useTakeUndoableMutation,
10
+ useTranslate,
11
+ } from "ra-core";
12
+
13
+ /**
14
+ * Displays notifications triggered with the useNotify hook.
15
+ *
16
+ * Supports different notification types (info, success, warning, error) and undoable mutations.
17
+ * Automatically adapts to the current theme (light/dark).
18
+ *
19
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/notification Notification documentation}
20
+ * @see {@link https://marmelab.com/ra-core/usenotify/ useNotify hook}
21
+ *
22
+ * @example
23
+ * // Trigger a notification
24
+ * import { useNotify } from 'ra-core';
25
+ *
26
+ * const NotifyButton = () => {
27
+ * const notify = useNotify();
28
+ * const handleClick = () => {
29
+ * notify('Comment approved', { type: 'success' });
30
+ * };
31
+ * return <button onClick={handleClick}>Notify</button>;
32
+ * };
33
+ */
34
+ export const Notification = (props: ToasterProps) => {
35
+ const translate = useTranslate();
36
+ const { notifications, takeNotification } = useNotificationContext();
37
+ const takeMutation = useTakeUndoableMutation();
38
+ const { theme } = useTheme();
39
+
40
+ useEffect(() => {
41
+ if (notifications.length) {
42
+ const notification = takeNotification();
43
+ if (notification) {
44
+ const { message, type = "info", notificationOptions } = notification;
45
+ const { messageArgs, undoable } = notificationOptions || {};
46
+
47
+ const beforeunload = (e: BeforeUnloadEvent) => {
48
+ e.preventDefault();
49
+ const confirmationMessage = "";
50
+ e.returnValue = confirmationMessage;
51
+ return confirmationMessage;
52
+ };
53
+
54
+ if (undoable) {
55
+ window.addEventListener("beforeunload", beforeunload);
56
+ }
57
+
58
+ const handleExited = () => {
59
+ if (undoable) {
60
+ const mutation = takeMutation();
61
+ if (mutation) {
62
+ mutation({ isUndo: false });
63
+ }
64
+ window.removeEventListener("beforeunload", beforeunload);
65
+ }
66
+ };
67
+
68
+ const handleUndo = () => {
69
+ const mutation = takeMutation();
70
+ if (mutation) {
71
+ mutation({ isUndo: true });
72
+ }
73
+ window.removeEventListener("beforeunload", beforeunload);
74
+ };
75
+
76
+ const finalMessage = message
77
+ ? typeof message === "string"
78
+ ? translate(message, messageArgs)
79
+ : React.isValidElement(message)
80
+ ? message
81
+ : undefined
82
+ : undefined;
83
+
84
+ toast[type](finalMessage, {
85
+ action: undoable
86
+ ? {
87
+ label: translate("ra.action.undo"),
88
+ onClick: handleUndo,
89
+ }
90
+ : undefined,
91
+ onDismiss: handleExited,
92
+ onAutoClose: handleExited,
93
+ });
94
+ }
95
+ }
96
+ }, [notifications, takeMutation, takeNotification, translate]);
97
+
98
+ const handleRequestClose = useCallback(() => {
99
+ // Dismiss all toasts
100
+ toast.dismiss();
101
+ }, []);
102
+
103
+ return (
104
+ <CloseNotificationContext.Provider value={handleRequestClose}>
105
+ <Toaster
106
+ richColors
107
+ theme={theme}
108
+ closeButton
109
+ position="bottom-center"
110
+ {...props}
111
+ />
112
+ </CloseNotificationContext.Provider>
113
+ );
114
+ };
@@ -0,0 +1,84 @@
1
+ import type { HTMLAttributes } from "react";
2
+ import { useFieldValue, useTranslate } from "ra-core";
3
+ import type { FieldProps } from "@/lib/field.type";
4
+
5
+ /**
6
+ * Displays a numeric value with locale-specific formatting.
7
+ *
8
+ * This field automatically formats numbers according to the user's locale using Intl.NumberFormat.
9
+ * It supports custom locales and formatting options for currencies, percentages, and more.
10
+ * To be used with RecordField or DataTable.Col components, or anywhere a RecordContext is available.
11
+ *
12
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/numberfield/ NumberField documentation}
13
+ *
14
+ * @example
15
+ * import { Show, RecordField, NumberField } from '@/components/admin';
16
+ *
17
+ * const ProductShow = () => (
18
+ * <Show>
19
+ * <div className="flex flex-col gap-4">
20
+ * <RecordField source="reference" />
21
+ * <RecordField source="price">
22
+ * <NumberField source="price" options={{ style: 'currency', currency: 'USD' }} />
23
+ * </RecordField>
24
+ * </div>
25
+ * </Show>
26
+ * );
27
+ */
28
+ export const NumberField = <
29
+ RecordType extends Record<string, any> = Record<string, any>,
30
+ >({
31
+ defaultValue,
32
+ source,
33
+ record,
34
+ empty,
35
+ transform = defaultTransform,
36
+ locales,
37
+ options,
38
+ ...rest
39
+ }: NumberFieldProps<RecordType>) => {
40
+ let value = useFieldValue({ defaultValue, source, record });
41
+ const translate = useTranslate();
42
+
43
+ if (value == null) {
44
+ if (!empty) {
45
+ return null;
46
+ }
47
+
48
+ return (
49
+ <span {...rest}>
50
+ {typeof empty === "string" ? translate(empty, { _: empty }) : empty}
51
+ </span>
52
+ );
53
+ }
54
+
55
+ if (transform) {
56
+ value = transform(value);
57
+ }
58
+
59
+ return (
60
+ <span {...rest}>
61
+ {hasNumberFormat && typeof value === "number"
62
+ ? value.toLocaleString(locales, options)
63
+ : value}
64
+ </span>
65
+ );
66
+ };
67
+
68
+ export interface NumberFieldProps<
69
+ RecordType extends Record<string, any> = Record<string, any>,
70
+ > extends FieldProps<RecordType>,
71
+ HTMLAttributes<HTMLSpanElement> {
72
+ locales?: string | string[];
73
+ options?: object;
74
+ transform?: (value: any) => number;
75
+ }
76
+
77
+ const defaultTransform = (value: any) =>
78
+ value && typeof value === "string" && !isNaN(value as any) ? +value : value;
79
+
80
+ const hasNumberFormat = !!(
81
+ typeof Intl === "object" &&
82
+ Intl &&
83
+ typeof Intl.NumberFormat === "function"
84
+ );
@@ -0,0 +1,124 @@
1
+ import * as React from "react";
2
+ import { useEffect, useState } from "react";
3
+ import type { InputProps } from "ra-core";
4
+ import { FieldTitle, useInput, useResourceContext } from "ra-core";
5
+ import { FormControl, FormField, FormLabel } from "@/components/admin/form";
6
+ import { Input } from "@/components/ui/input";
7
+ import { FormError } from "@/components/admin/form";
8
+ import { InputHelperText } from "@/components/admin/input-helper-text";
9
+
10
+ /**
11
+ * Input component for numeric values (integers and floats) with parsing and formatting support.
12
+ *
13
+ * Use `<NumberInput>` for prices, quantities, counts, or any numeric field. Manages a local string
14
+ * state internally so users can type incomplete numbers (e.g. '-' or '0.') before the value is parsed.
15
+ * Supports min/max constraints and step increments.
16
+ *
17
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/numberinput/ NumberInput documentation}
18
+ *
19
+ * @example
20
+ * import { Edit, SimpleForm, NumberInput, TextInput } from '@/components/admin';
21
+ *
22
+ * const ProductEdit = () => (
23
+ * <Edit>
24
+ * <SimpleForm>
25
+ * <TextInput source="name" />
26
+ * <NumberInput source="price" step={0.01} min={0} />
27
+ * <NumberInput source="quantity" min={0} />
28
+ * </SimpleForm>
29
+ * </Edit>
30
+ * );
31
+ */
32
+ export const NumberInput = (props: NumberInputProps) => {
33
+ const {
34
+ label,
35
+ source,
36
+ className,
37
+ resource: resourceProp,
38
+ validate: _validateProp,
39
+ format: _formatProp,
40
+ parse = convertStringToNumber,
41
+ onFocus,
42
+ ...rest
43
+ } = props;
44
+ const resource = useResourceContext({ resource: resourceProp });
45
+
46
+ const { id, field, isRequired } = useInput(props);
47
+
48
+ const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
49
+ const value = event.target.value;
50
+ const numberValue = parse(value);
51
+
52
+ setValue(value);
53
+ field.onChange(numberValue ?? 0);
54
+ };
55
+
56
+ const [value, setValue] = useState<string | undefined>(
57
+ field.value?.toString() ?? "",
58
+ );
59
+
60
+ const hasFocus = React.useRef(false);
61
+
62
+ const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
63
+ onFocus?.(event);
64
+ hasFocus.current = true;
65
+ };
66
+
67
+ const handleBlur = (event: React.FocusEvent<HTMLInputElement>) => {
68
+ field.onBlur?.(event);
69
+ hasFocus.current = false;
70
+ setValue(field.value?.toString() ?? "");
71
+ };
72
+
73
+ useEffect(() => {
74
+ if (!hasFocus.current) {
75
+ setValue(field.value?.toString() ?? "");
76
+ }
77
+ }, [field.value]);
78
+
79
+ return (
80
+ <FormField id={id} className={className} name={field.name}>
81
+ {label !== false && (
82
+ <FormLabel>
83
+ <FieldTitle
84
+ label={label}
85
+ source={source}
86
+ resource={resource}
87
+ isRequired={isRequired}
88
+ />
89
+ </FormLabel>
90
+ )}
91
+ <FormControl>
92
+ <Input
93
+ {...rest}
94
+ {...field}
95
+ type="number"
96
+ value={value}
97
+ onChange={handleChange}
98
+ onFocus={handleFocus}
99
+ onBlur={handleBlur}
100
+ />
101
+ </FormControl>
102
+ <InputHelperText helperText={props.helperText} />
103
+ <FormError />
104
+ </FormField>
105
+ );
106
+ };
107
+
108
+ export interface NumberInputProps
109
+ extends InputProps,
110
+ Omit<
111
+ React.ComponentProps<"input">,
112
+ "defaultValue" | "onBlur" | "onChange" | "type"
113
+ > {
114
+ parse?: (value: string) => number;
115
+ }
116
+
117
+ const convertStringToNumber = (value?: string | null) => {
118
+ if (value == null || value === "") {
119
+ return null;
120
+ }
121
+ const float = parseFloat(value);
122
+
123
+ return isNaN(float) ? 0 : float;
124
+ };
@@ -0,0 +1,184 @@
1
+ import * as React from "react";
2
+ import type { ChoicesProps, InputProps } from "ra-core";
3
+ import { FieldTitle, useChoices, useChoicesContext, useInput } from "ra-core";
4
+ import { cn } from "@/lib/utils";
5
+ import {
6
+ FormField,
7
+ FormControl,
8
+ FormLabel,
9
+ FormError,
10
+ } from "@/components/admin/form";
11
+ import { Label } from "@/components/ui/label";
12
+ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
13
+ import { Skeleton } from "@/components/ui/skeleton";
14
+ import { InputHelperText } from "@/components/admin/input-helper-text";
15
+
16
+ /**
17
+ * Single-select input rendered as a list of radio buttons, arranged vertically or horizontally.
18
+ *
19
+ * Use `<RadioButtonGroupInput>` when you have a small set of options (2-5) that users should
20
+ * see all at once, like status, priority, or category fields. For longer lists, prefer
21
+ * `<SelectInput>`. Set `row` to `true` for horizontal layout.
22
+ *
23
+ * @see {@link https://marmelab.com/shadcn-admin-kit/docs/radiobuttongroupinput/ RadioButtonGroupInput documentation}
24
+ *
25
+ * @example
26
+ * import { Edit, SimpleForm, TextInput, RadioButtonGroupInput } from '@/components/admin';
27
+ *
28
+ * const PostEdit = () => (
29
+ * <Edit>
30
+ * <SimpleForm>
31
+ * <TextInput source="title" />
32
+ * <RadioButtonGroupInput
33
+ * source="category"
34
+ * choices={[
35
+ * { id: 'tech', name: 'Tech' },
36
+ * { id: 'lifestyle', name: 'Lifestyle' },
37
+ * { id: 'people', name: 'People' },
38
+ * ]}
39
+ * />
40
+ * </SimpleForm>
41
+ * </Edit>
42
+ * );
43
+ */
44
+ export const RadioButtonGroupInput = (inProps: RadioButtonGroupInputProps) => {
45
+ const {
46
+ choices: choicesProp,
47
+ isFetching: isFetchingProp,
48
+ isLoading: isLoadingProp,
49
+ isPending: isPendingProp,
50
+ resource: resourceProp,
51
+ source: sourceProp,
52
+
53
+ format,
54
+ onBlur,
55
+ onChange,
56
+ parse,
57
+ validate,
58
+ disabled,
59
+ readOnly,
60
+
61
+ optionText,
62
+ optionValue = "id",
63
+ translateChoice,
64
+ disableValue = "disabled",
65
+
66
+ className,
67
+ helperText,
68
+ label,
69
+ row,
70
+ ...rest
71
+ } = inProps;
72
+
73
+ const {
74
+ allChoices,
75
+ isPending,
76
+ error: fetchError,
77
+ resource,
78
+ source,
79
+ } = useChoicesContext({
80
+ choices: choicesProp,
81
+ isFetching: isFetchingProp,
82
+ isLoading: isLoadingProp,
83
+ isPending: isPendingProp,
84
+ resource: resourceProp,
85
+ source: sourceProp,
86
+ });
87
+
88
+ if (source === undefined) {
89
+ throw new Error(
90
+ `If you're not wrapping the RadioButtonGroupInput inside a ReferenceArrayInput, you must provide the source prop`,
91
+ );
92
+ }
93
+
94
+ if (!isPending && !fetchError && allChoices === undefined) {
95
+ throw new Error(
96
+ `If you're not wrapping the RadioButtonGroupInput inside a ReferenceArrayInput, you must provide the choices prop`,
97
+ );
98
+ }
99
+
100
+ const { id, field, isRequired } = useInput({
101
+ format,
102
+ onBlur,
103
+ onChange,
104
+ parse,
105
+ resource,
106
+ source,
107
+ validate,
108
+ disabled,
109
+ readOnly,
110
+ ...rest,
111
+ });
112
+
113
+ const { getChoiceText, getChoiceValue, getDisableValue } = useChoices({
114
+ optionText,
115
+ optionValue,
116
+ translateChoice,
117
+ disableValue,
118
+ });
119
+
120
+ if (isPending) {
121
+ return <Skeleton className="w-full h-9" />;
122
+ }
123
+
124
+ return (
125
+ <FormField id={id} className={className} name={field.name}>
126
+ {label && (
127
+ <FormLabel>
128
+ <FieldTitle
129
+ label={label}
130
+ source={source}
131
+ resource={resource}
132
+ isRequired={isRequired}
133
+ />
134
+ </FormLabel>
135
+ )}
136
+
137
+ <FormControl>
138
+ <RadioGroup
139
+ {...rest}
140
+ value={field.value || ""}
141
+ onValueChange={field.onChange}
142
+ className={cn("flex", row ? "flex-row gap-4" : "flex-col gap-2")}
143
+ disabled={disabled || readOnly}
144
+ >
145
+ {allChoices?.map((choice) => {
146
+ const value = getChoiceValue(choice);
147
+ const isDisabled = disabled || readOnly || getDisableValue(choice);
148
+
149
+ return (
150
+ <div key={value} className="flex items-center space-x-2">
151
+ <RadioGroupItem
152
+ value={value}
153
+ id={`${id}-${value}`}
154
+ disabled={isDisabled}
155
+ />
156
+ <Label
157
+ htmlFor={`${id}-${value}`}
158
+ className={cn(
159
+ "text-sm font-normal cursor-pointer",
160
+ isDisabled && "opacity-50 cursor-not-allowed",
161
+ )}
162
+ >
163
+ {getChoiceText(choice)}
164
+ </Label>
165
+ </div>
166
+ );
167
+ })}
168
+ </RadioGroup>
169
+ </FormControl>
170
+ <InputHelperText helperText={helperText} />
171
+ <FormError />
172
+ </FormField>
173
+ );
174
+ };
175
+
176
+ export interface RadioButtonGroupInputProps
177
+ extends Partial<InputProps>,
178
+ ChoicesProps,
179
+ Omit<
180
+ React.ComponentProps<typeof RadioGroup>,
181
+ "defaultValue" | "onBlur" | "onChange" | "type"
182
+ > {
183
+ row?: boolean;
184
+ }