@salesforce/templates 66.13.4 → 66.13.6

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 (236) hide show
  1. package/lib/generators/baseGenerator.d.ts +2 -0
  2. package/lib/generators/baseGenerator.js +353 -0
  3. package/lib/generators/baseGenerator.js.map +1 -1
  4. package/lib/index.d.ts +1 -0
  5. package/lib/index.js +3 -1
  6. package/lib/index.js.map +1 -1
  7. package/lib/templates/project/angularextapp/CHANGELOG.md +258 -0
  8. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/angular.json +1 -1
  9. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
  10. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
  11. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +421 -394
  12. package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package.json +2 -2
  13. package/lib/templates/project/angularextapp/package.json +1 -1
  14. package/lib/templates/project/angularextapp/scripts/org-setup-config-schema.mjs +17 -0
  15. package/lib/templates/project/angularextapp/scripts/org-setup-utils.mjs +33 -3
  16. package/lib/templates/project/angularextapp/scripts/org-setup.mjs +535 -3
  17. package/lib/templates/project/angularintapp/CHANGELOG.md +266 -0
  18. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/angular.json +1 -1
  19. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
  20. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
  21. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +431 -404
  22. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package.json +2 -2
  23. package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/features/agentforce/agentforce-embed.service.ts +6 -1
  24. package/lib/templates/project/angularintapp/package.json +1 -1
  25. package/lib/templates/project/angularintapp/scripts/org-setup-config-schema.mjs +17 -0
  26. package/lib/templates/project/angularintapp/scripts/org-setup-utils.mjs +33 -3
  27. package/lib/templates/project/angularintapp/scripts/org-setup.mjs +535 -3
  28. package/lib/templates/project/reactexternalapp/CHANGELOG.md +1313 -0
  29. package/lib/templates/project/reactexternalapp/PRD.md +38 -0
  30. package/lib/templates/project/reactexternalapp/README.md +14 -10
  31. package/lib/templates/project/reactexternalapp/_p_/_m_/_ps_/reactexternalapp_Guest_User_Api_Access.permissionset-meta.xml +25 -0
  32. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +660 -692
  33. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +9 -7
  34. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/scripts/get-graphql-schema.mjs +8 -5
  35. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/components/SocialLogin.tsx +141 -0
  36. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +2 -0
  37. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
  38. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +71 -0
  39. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +277 -0
  40. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +185 -0
  41. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +77 -0
  42. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +182 -0
  43. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
  44. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +55 -0
  45. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
  46. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
  47. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
  48. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
  49. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
  50. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
  51. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
  52. package/lib/templates/project/{reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters → reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs}/FilterFieldWrapper.tsx +11 -6
  53. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
  54. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
  55. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
  56. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
  57. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
  58. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +83 -0
  59. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
  60. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
  61. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +703 -0
  62. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +90 -0
  63. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
  64. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +156 -0
  65. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +307 -0
  66. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
  67. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
  68. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
  69. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
  70. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +24 -8
  71. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +7 -0
  72. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/CustomerWebClientChat.tsx +74 -0
  73. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
  74. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
  75. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
  76. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
  77. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/index.ts +6 -0
  78. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
  79. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +21 -293
  80. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +22 -7
  81. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/TestChatPage.tsx +26 -0
  82. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +11 -5
  83. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/types/chat.ts +36 -0
  84. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
  85. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
  86. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
  87. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls +133 -0
  88. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls-meta.xml +5 -0
  89. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls +101 -0
  90. package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls-meta.xml +5 -0
  91. package/lib/templates/project/reactexternalapp/_p_/_m_/package.xml +4 -0
  92. package/lib/templates/project/reactexternalapp/package.json +8 -2
  93. package/lib/templates/project/reactexternalapp/scripts/org-setup-config-schema.mjs +120 -0
  94. package/lib/templates/project/reactexternalapp/scripts/org-setup-dev.mjs +87 -0
  95. package/lib/templates/project/reactexternalapp/scripts/org-setup-utils.mjs +569 -0
  96. package/lib/templates/project/reactexternalapp/scripts/org-setup-xml.mjs +182 -0
  97. package/lib/templates/project/reactexternalapp/scripts/org-setup.config.json +6 -2
  98. package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +1509 -328
  99. package/lib/templates/project/reactexternalapp/scripts/validate-org-setup-config.mjs +38 -0
  100. package/lib/templates/project/reactinternalapp/CHANGELOG.md +1127 -0
  101. package/lib/templates/project/reactinternalapp/PRD.md +35 -0
  102. package/lib/templates/project/reactinternalapp/README.md +14 -10
  103. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +509 -505
  104. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +9 -8
  105. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
  106. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +71 -0
  107. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +277 -0
  108. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +185 -0
  109. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +77 -0
  110. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +182 -0
  111. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
  112. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +55 -0
  113. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
  114. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
  115. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
  116. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
  117. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
  118. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
  119. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
  120. package/lib/templates/project/{reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters → reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs}/FilterFieldWrapper.tsx +11 -6
  121. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
  122. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
  123. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
  124. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
  125. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
  126. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +83 -0
  127. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
  128. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
  129. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +703 -0
  130. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +90 -0
  131. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
  132. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +156 -0
  133. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +307 -0
  134. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
  135. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
  136. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
  137. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
  138. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +16 -132
  139. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
  140. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
  141. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
  142. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
  143. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
  144. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +21 -293
  145. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +21 -6
  146. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +4 -4
  147. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
  148. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
  149. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
  150. package/lib/templates/project/reactinternalapp/package.json +4 -2
  151. package/lib/templates/project/reactinternalapp/scripts/org-setup-config-schema.mjs +24 -0
  152. package/lib/templates/project/reactinternalapp/scripts/org-setup-dev.mjs +87 -0
  153. package/lib/templates/project/reactinternalapp/scripts/org-setup-utils.mjs +569 -0
  154. package/lib/templates/project/reactinternalapp/scripts/org-setup-xml.mjs +182 -0
  155. package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +1162 -214
  156. package/lib/templates/uiBundles/angularbasic/angular.json +1 -1
  157. package/lib/templates/uiBundles/angularbasic/eslint-plugin-angular-graphql.js +148 -0
  158. package/lib/templates/uiBundles/angularbasic/eslint.config.js +9 -1
  159. package/lib/templates/uiBundles/angularbasic/package-lock.json +421 -394
  160. package/lib/templates/uiBundles/angularbasic/package.json +2 -2
  161. package/lib/templates/uiBundles/reactbasic/package-lock.json +445 -418
  162. package/lib/utils/template-placeholders.js +7 -0
  163. package/lib/utils/template-placeholders.js.map +1 -1
  164. package/lib/utils/uiBundleTemplateUtils.d.ts +3 -1
  165. package/lib/utils/uiBundleTemplateUtils.js +4 -1
  166. package/lib/utils/uiBundleTemplateUtils.js.map +1 -1
  167. package/package.json +7 -7
  168. package/lib/templates/project/reactexternalapp/AGENT.md +0 -252
  169. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
  170. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
  171. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
  172. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
  173. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
  174. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
  175. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
  176. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
  177. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
  178. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
  179. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
  180. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
  181. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
  182. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
  183. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
  184. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
  185. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
  186. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
  187. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
  188. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
  189. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
  190. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
  191. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
  192. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
  193. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
  194. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
  195. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
  196. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
  197. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
  198. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
  199. package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
  200. package/lib/templates/project/reactexternalapp/scripts/prepare-import-unique-fields.js +0 -122
  201. package/lib/templates/project/reactinternalapp/AGENT.md +0 -252
  202. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
  203. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
  204. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
  205. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
  206. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
  207. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
  208. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
  209. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
  210. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
  211. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
  212. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
  213. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
  214. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
  215. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
  216. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
  217. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
  218. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
  219. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
  220. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
  221. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
  222. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
  223. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
  224. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
  225. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
  226. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
  227. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
  228. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
  229. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
  230. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
  231. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
  232. package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
  233. package/lib/templates/project/reactinternalapp/scripts/prepare-import-unique-fields.js +0 -122
  234. /package/lib/templates/project/angularextapp/_p_/_m_/{permissionsets → _ps_}/angularextapp_Guest_User_Api_Access.permissionset-meta.xml +0 -0
  235. /package/lib/templates/project/angularintapp/_p_/_m_/{permissionsets → _ps_}/angularintapp_Access.permissionset-meta.xml +0 -0
  236. /package/lib/templates/project/reactinternalapp/_p_/_m_/{permissionsets → _ps_}/reactinternalapp_Access.permissionset-meta.xml +0 -0
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Default filter panel used when no `renderFilters` is supplied for a source.
3
+ *
4
+ * Renders one input per entry in `source.filterBy`, picking the right
5
+ * component for each `FilterFieldType`:
6
+ *
7
+ * - text → TextFilter
8
+ * - picklist → SelectFilter (inline options or auto-fetched)
9
+ * - multipicklist → MultiSelectFilter (inline options or auto-fetched)
10
+ * - numeric → NumericRangeFilter
11
+ * - boolean → BooleanFilter
12
+ * - date / daterange / datetime / datetimerange → DateRangeFilter
13
+ * (its Between / After / Before chrome is chosen by `filter.dateMode`)
14
+ *
15
+ * Applications can still pass `renderFilters[sourceKey]` to override the
16
+ * entire panel for a specific source.
17
+ */
18
+
19
+ import { TextFilter } from "./inputs/TextFilter";
20
+ import { SelectFilter } from "./inputs/SelectFilter";
21
+ import { MultiSelectFilter } from "./inputs/MultiSelectFilter";
22
+ import { NumericRangeFilter } from "./inputs/NumericRangeFilter";
23
+ import { BooleanFilter } from "./inputs/BooleanFilter";
24
+ import { DateRangeFilter } from "./inputs/DateRangeFilter";
25
+ import { useDistinctValues } from "../../hooks/useDistinctValues";
26
+ import type { FilterFieldConfig } from "../../utils/filterUtils";
27
+ import type { SObjectSourceConfig } from "../../types";
28
+
29
+ interface DefaultFilterPanelProps {
30
+ source: SObjectSourceConfig;
31
+ }
32
+
33
+ export function DefaultFilterPanel({ source }: DefaultFilterPanelProps) {
34
+ const filters = source.filterBy ?? [];
35
+ if (filters.length === 0) return null;
36
+ return (
37
+ <>
38
+ {filters.map((filter) => (
39
+ <DefaultFilterField key={filter.field} filter={filter} source={source} />
40
+ ))}
41
+ </>
42
+ );
43
+ }
44
+
45
+ interface DefaultFilterFieldProps {
46
+ filter: FilterFieldConfig;
47
+ source: SObjectSourceConfig;
48
+ }
49
+
50
+ function DefaultFilterField({ filter, source }: DefaultFilterFieldProps) {
51
+ switch (filter.type) {
52
+ case "text":
53
+ return (
54
+ <TextFilter
55
+ field={filter.field}
56
+ label={filter.label}
57
+ placeholder={filter.placeholder}
58
+ helpText={filter.helpText}
59
+ />
60
+ );
61
+ case "picklist":
62
+ return <PicklistField filter={filter} source={source} multi={false} />;
63
+ case "multipicklist":
64
+ return <PicklistField filter={filter} source={source} multi={true} />;
65
+ case "numeric":
66
+ return (
67
+ <NumericRangeFilter field={filter.field} label={filter.label} helpText={filter.helpText} />
68
+ );
69
+ case "boolean":
70
+ return <BooleanFilter field={filter.field} label={filter.label} helpText={filter.helpText} />;
71
+ case "date":
72
+ case "daterange":
73
+ return (
74
+ <DateRangeFilter
75
+ field={filter.field}
76
+ label={filter.label}
77
+ helpText={filter.helpText}
78
+ filterType="daterange"
79
+ mode={filter.dateMode}
80
+ />
81
+ );
82
+ case "datetime":
83
+ case "datetimerange":
84
+ return (
85
+ <DateRangeFilter
86
+ field={filter.field}
87
+ label={filter.label}
88
+ helpText={filter.helpText}
89
+ filterType="datetimerange"
90
+ mode={filter.dateMode}
91
+ />
92
+ );
93
+ default:
94
+ return null;
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Resolves picklist options from inline config first; falls back to fetching
100
+ * distinct values from the GraphQL aggregate API on first render.
101
+ */
102
+ function PicklistField({
103
+ filter,
104
+ source,
105
+ multi,
106
+ }: {
107
+ filter: FilterFieldConfig;
108
+ source: SObjectSourceConfig;
109
+ multi: boolean;
110
+ }) {
111
+ const inline = filter.options;
112
+ const fetched = useDistinctValues(
113
+ inline ? null : { objectName: source.objectName, fieldName: filter.field },
114
+ );
115
+ const options = inline ?? fetched ?? [];
116
+ const Component = multi ? MultiSelectFilter : SelectFilter;
117
+ return (
118
+ <Component
119
+ field={filter.field}
120
+ label={filter.label}
121
+ options={options}
122
+ helpText={filter.helpText}
123
+ />
124
+ );
125
+ }
@@ -1,6 +1,6 @@
1
- import { createContext, useContext, useCallback, type ReactNode } from "react";
2
- import { Button } from "../../../components/ui/button";
3
- import type { ActiveFilterValue } from "../utils/filterUtils";
1
+ import { createContext, useCallback, useContext, type ReactNode } from "react";
2
+ import { Button } from "../../../../components/ui/button";
3
+ import type { ActiveFilterValue } from "../../utils/filterUtils";
4
4
 
5
5
  interface FilterContextValue {
6
6
  filters: ActiveFilterValue[];
@@ -27,14 +27,7 @@ export function FilterProvider({
27
27
  children,
28
28
  }: FilterProviderProps) {
29
29
  return (
30
- <FilterContext.Provider
31
- value={{
32
- filters,
33
- onFilterChange,
34
- onFilterRemove,
35
- onReset,
36
- }}
37
- >
30
+ <FilterContext.Provider value={{ filters, onFilterChange, onFilterRemove, onReset }}>
38
31
  {children}
39
32
  </FilterContext.Provider>
40
33
  );
@@ -51,11 +44,8 @@ export function useFilterField(field: string) {
51
44
  const value = filters.find((f) => f.field === field);
52
45
  const onChange = useCallback(
53
46
  (next: ActiveFilterValue | undefined) => {
54
- if (next) {
55
- onFilterChange(field, next);
56
- } else {
57
- onFilterRemove(field);
58
- }
47
+ if (next) onFilterChange(field, next);
48
+ else onFilterRemove(field);
59
49
  },
60
50
  [field, onFilterChange, onFilterRemove],
61
51
  );
@@ -64,10 +54,7 @@ export function useFilterField(field: string) {
64
54
 
65
55
  export function useFilterPanel() {
66
56
  const { filters, onReset } = useFilterContext();
67
- return {
68
- hasActiveFilters: filters.length > 0,
69
- resetAll: onReset,
70
- };
57
+ return { hasActiveFilters: filters.length > 0, resetAll: onReset };
71
58
  }
72
59
 
73
60
  type FilterResetButtonProps = Omit<React.ComponentProps<typeof Button>, "onClick">;
@@ -0,0 +1,50 @@
1
+ import {
2
+ Select,
3
+ SelectContent,
4
+ SelectItem,
5
+ SelectTrigger,
6
+ SelectValue,
7
+ } from "../../../../../components/ui/select";
8
+ import { useFilterField } from "../FilterContext";
9
+ import { FilterFieldWrapper } from "./FilterFieldWrapper";
10
+
11
+ const ANY_VALUE = "__any__";
12
+
13
+ interface BooleanFilterProps {
14
+ field: string;
15
+ label: string;
16
+ trueLabel?: string;
17
+ falseLabel?: string;
18
+ helpText?: string;
19
+ }
20
+
21
+ export function BooleanFilter({
22
+ field,
23
+ label,
24
+ trueLabel = "Yes",
25
+ falseLabel = "No",
26
+ helpText,
27
+ }: BooleanFilterProps) {
28
+ const { value, onChange } = useFilterField(field);
29
+ const id = `filter-${field}`;
30
+ return (
31
+ <FilterFieldWrapper label={label} htmlFor={id} helpText={helpText}>
32
+ <Select
33
+ value={value?.value ?? ANY_VALUE}
34
+ onValueChange={(v) => {
35
+ if (v === ANY_VALUE) onChange(undefined);
36
+ else onChange({ field, label, type: "boolean", value: v });
37
+ }}
38
+ >
39
+ <SelectTrigger id={id} className="w-full">
40
+ <SelectValue />
41
+ </SelectTrigger>
42
+ <SelectContent>
43
+ <SelectItem value={ANY_VALUE}>Any</SelectItem>
44
+ <SelectItem value="true">{trueLabel}</SelectItem>
45
+ <SelectItem value="false">{falseLabel}</SelectItem>
46
+ </SelectContent>
47
+ </Select>
48
+ </FilterFieldWrapper>
49
+ );
50
+ }
@@ -0,0 +1,160 @@
1
+ import { useState } from "react";
2
+ import { Input } from "../../../../../components/ui/input";
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectItem,
7
+ SelectTrigger,
8
+ SelectValue,
9
+ } from "../../../../../components/ui/select";
10
+ import { useFilterField } from "../FilterContext";
11
+ import { FilterFieldWrapper } from "./FilterFieldWrapper";
12
+ import type {
13
+ ActiveFilterValue,
14
+ DateFilterMode,
15
+ FilterFieldType,
16
+ } from "../../../utils/filterUtils";
17
+
18
+ interface DateRangeFilterProps {
19
+ field: string;
20
+ label: string;
21
+ helpText?: string;
22
+ /** "daterange" (Date scalar) or "datetimerange" (DateTime scalar). */
23
+ filterType?: Extract<FilterFieldType, "daterange" | "datetimerange">;
24
+ /**
25
+ * Which comparison chrome to show. See {@link DateFilterMode}. Defaults to
26
+ * `"comparison"` (an After / Before selector with one date input).
27
+ */
28
+ mode?: DateFilterMode;
29
+ }
30
+
31
+ /**
32
+ * The three ways to constrain a date field. They map directly onto the
33
+ * `min`/`max` bounds the query builder already understands:
34
+ * - after → only `min` set → `gte`
35
+ * - before → only `max` set → `lte`
36
+ * - between → both set → `gte` AND `lte`
37
+ */
38
+ type DateOperator = "between" | "after" | "before";
39
+
40
+ const OPERATOR_LABELS: Record<DateOperator, string> = {
41
+ between: "Between",
42
+ after: "After",
43
+ before: "Before",
44
+ };
45
+
46
+ /** The operators offered for each {@link DateFilterMode}. */
47
+ const MODE_OPERATORS: Record<DateFilterMode, DateOperator[]> = {
48
+ range: ["between"],
49
+ comparison: ["after", "before"],
50
+ both: ["between", "after", "before"],
51
+ };
52
+
53
+ /** Pick the operator implied by an already-active filter (e.g. restored from the URL). */
54
+ function operatorFor(value: ActiveFilterValue | undefined): DateOperator {
55
+ const hasMin = !!value?.min;
56
+ const hasMax = !!value?.max;
57
+ if (hasMax && !hasMin) return "before";
58
+ if (hasMin && !hasMax) return "after";
59
+ return "between";
60
+ }
61
+
62
+ /**
63
+ * A date constraint whose chrome is driven by `mode`:
64
+ * - `"range"`: a single Between control (two date inputs).
65
+ * - `"comparison"` (default): an After / Before selector with one input.
66
+ * - `"both"`: a Between / After / Before selector spanning the two.
67
+ *
68
+ * "Between" shows two `<input type="date">` controls; "After" / "Before"
69
+ * collapse to one bound to the relevant end. Uses the platform-native picker —
70
+ * no popover/calendar dependency.
71
+ */
72
+ export function DateRangeFilter({
73
+ field,
74
+ label,
75
+ helpText,
76
+ filterType = "daterange",
77
+ mode = "comparison",
78
+ }: DateRangeFilterProps) {
79
+ const { value, onChange } = useFilterField(field);
80
+ const operators = MODE_OPERATORS[mode];
81
+
82
+ // Operator is local UI state: it must survive a moment where no date is yet
83
+ // entered (which clears the active filter), so it can't be derived from
84
+ // `value` on every render. Seed it from any filter restored on mount, clamped
85
+ // to an operator this mode actually offers.
86
+ const [operator, setOperator] = useState<DateOperator>(() => {
87
+ const restored = operatorFor(value);
88
+ return operators.includes(restored) ? restored : operators[0];
89
+ });
90
+
91
+ const emit = (nextMin: string | undefined, nextMax: string | undefined) => {
92
+ if (!nextMin && !nextMax) onChange(undefined);
93
+ else onChange({ field, label, type: filterType, min: nextMin, max: nextMax });
94
+ };
95
+
96
+ const changeOperator = (next: DateOperator) => {
97
+ setOperator(next);
98
+ // Re-emit, keeping only the bound(s) the new operator uses.
99
+ if (next === "after") emit(value?.min, undefined);
100
+ else if (next === "before") emit(undefined, value?.max);
101
+ else emit(value?.min, value?.max);
102
+ };
103
+
104
+ const operatorId = `filter-${field}-op`;
105
+ // A lone "between" operator (mode="range") needs no selector — it can't change.
106
+ const showSelector = operators.length > 1;
107
+
108
+ return (
109
+ <FilterFieldWrapper label={label} helpText={helpText}>
110
+ <div className="flex flex-col gap-2">
111
+ {showSelector && (
112
+ <Select value={operator} onValueChange={(v) => changeOperator(v as DateOperator)}>
113
+ <SelectTrigger id={operatorId} className="w-full" aria-label={`${label} comparison`}>
114
+ <SelectValue />
115
+ </SelectTrigger>
116
+ <SelectContent>
117
+ {operators.map((op) => (
118
+ <SelectItem key={op} value={op}>
119
+ {OPERATOR_LABELS[op]}
120
+ </SelectItem>
121
+ ))}
122
+ </SelectContent>
123
+ </Select>
124
+ )}
125
+
126
+ {operator === "between" ? (
127
+ <div className="flex items-center gap-2">
128
+ <Input
129
+ type="date"
130
+ value={value?.min ?? ""}
131
+ onChange={(e) => emit(e.target.value || undefined, value?.max)}
132
+ aria-label={`${label} from`}
133
+ />
134
+ <span className="text-sm text-muted-foreground">–</span>
135
+ <Input
136
+ type="date"
137
+ value={value?.max ?? ""}
138
+ onChange={(e) => emit(value?.min, e.target.value || undefined)}
139
+ aria-label={`${label} to`}
140
+ />
141
+ </div>
142
+ ) : operator === "after" ? (
143
+ <Input
144
+ type="date"
145
+ value={value?.min ?? ""}
146
+ onChange={(e) => emit(e.target.value || undefined, undefined)}
147
+ aria-label={`${label} after`}
148
+ />
149
+ ) : (
150
+ <Input
151
+ type="date"
152
+ value={value?.max ?? ""}
153
+ onChange={(e) => emit(undefined, e.target.value || undefined)}
154
+ aria-label={`${label} before`}
155
+ />
156
+ )}
157
+ </div>
158
+ </FilterFieldWrapper>
159
+ );
160
+ }
@@ -1,29 +1,34 @@
1
- import { Label } from "../../../../components/ui/label";
2
- import { cn } from "../../../../lib/utils";
1
+ import type { ReactNode } from "react";
2
+ import { Label } from "../../../../../components/ui/label";
3
3
 
4
- interface FilterFieldWrapperProps extends React.ComponentProps<"div"> {
4
+ interface FilterFieldWrapperProps {
5
5
  label: string;
6
6
  htmlFor?: string;
7
+ errorId?: string;
7
8
  helpText?: string;
8
9
  error?: string;
10
+ className?: string;
11
+ children: ReactNode;
9
12
  }
10
13
 
11
14
  export function FilterFieldWrapper({
12
15
  label,
13
16
  htmlFor,
17
+ errorId,
14
18
  helpText,
15
19
  error,
16
20
  className,
17
21
  children,
18
- ...props
19
22
  }: FilterFieldWrapperProps) {
20
23
  return (
21
- <div className={cn("space-y-1", className)} {...props}>
24
+ <div className={`space-y-1 ${className ?? ""}`}>
22
25
  <Label htmlFor={htmlFor}>{label}</Label>
23
26
  {children}
24
27
  <div className="min-h-4">
25
28
  {error ? (
26
- <p className="text-xs text-destructive">{error}</p>
29
+ <p id={errorId} role="alert" className="text-xs text-destructive">
30
+ {error}
31
+ </p>
27
32
  ) : (
28
33
  helpText && <p className="text-xs text-muted-foreground">{helpText}</p>
29
34
  )}
@@ -0,0 +1,89 @@
1
+ import { ChevronDown } from "lucide-react";
2
+ import { Button } from "../../../../../components/ui/button";
3
+ import { Checkbox } from "../../../../../components/ui/checkbox";
4
+ import { Label } from "../../../../../components/ui/label";
5
+ import {
6
+ Popover,
7
+ PopoverContent,
8
+ PopoverTrigger,
9
+ } from "../../../../../components/ui/popover";
10
+ import { useFilterField } from "../FilterContext";
11
+ import { FilterFieldWrapper } from "./FilterFieldWrapper";
12
+
13
+ interface MultiSelectFilterProps {
14
+ field: string;
15
+ label: string;
16
+ options: Array<{ value: string; label: string }>;
17
+ helpText?: string;
18
+ }
19
+
20
+ export function MultiSelectFilter({ field, label, options, helpText }: MultiSelectFilterProps) {
21
+ const { value, onChange } = useFilterField(field);
22
+ const selected = new Set(value?.value ? value.value.split(",").filter(Boolean) : []);
23
+
24
+ const toggle = (v: string, on: boolean) => {
25
+ const next = new Set(selected);
26
+ if (on) next.add(v);
27
+ else next.delete(v);
28
+ const joined = Array.from(next).join(",");
29
+ if (joined === "") onChange(undefined);
30
+ else onChange({ field, label, type: "multipicklist", value: joined });
31
+ };
32
+
33
+ // Summarize the selection on the collapsed trigger: the sole label when one is
34
+ // picked, a count when several are, and a placeholder when none.
35
+ const summary =
36
+ selected.size === 0
37
+ ? `Select ${label.toLowerCase()}`
38
+ : selected.size === 1
39
+ ? (options.find((o) => selected.has(o.value))?.label ?? `${selected.size} selected`)
40
+ : `${selected.size} selected`;
41
+ const triggerId = `filter-${field}`;
42
+
43
+ return (
44
+ <FilterFieldWrapper label={label} htmlFor={triggerId} helpText={helpText}>
45
+ <Popover>
46
+ <PopoverTrigger asChild>
47
+ <Button
48
+ id={triggerId}
49
+ variant="outline"
50
+ className="w-full justify-between font-normal"
51
+ aria-label={`${label}: ${summary}`}
52
+ >
53
+ <span className={`truncate ${selected.size === 0 ? "text-foreground/70" : ""}`}>
54
+ {summary}
55
+ </span>
56
+ <ChevronDown className="ml-2 size-4 shrink-0 opacity-50" aria-hidden="true" />
57
+ </Button>
58
+ </PopoverTrigger>
59
+ <PopoverContent
60
+ align="start"
61
+ aria-label={label}
62
+ className="w-(--radix-popover-trigger-width) max-h-72 overflow-y-auto"
63
+ >
64
+ <div role="group" className="flex flex-col gap-1">
65
+ {options.length === 0 ? (
66
+ <p className="px-1 py-2 text-sm text-muted-foreground">No options available.</p>
67
+ ) : (
68
+ options.map((opt) => {
69
+ const id = `filter-${field}-${opt.value}`;
70
+ return (
71
+ <div key={opt.value} className="flex items-center gap-2">
72
+ <Checkbox
73
+ id={id}
74
+ checked={selected.has(opt.value)}
75
+ onCheckedChange={(checked) => toggle(opt.value, checked === true)}
76
+ />
77
+ <Label htmlFor={id} className="text-sm font-normal cursor-pointer">
78
+ {opt.label}
79
+ </Label>
80
+ </div>
81
+ );
82
+ })
83
+ )}
84
+ </div>
85
+ </PopoverContent>
86
+ </Popover>
87
+ </FilterFieldWrapper>
88
+ );
89
+ }
@@ -0,0 +1,108 @@
1
+ import { useEffect, useId, useRef, useState } from "react";
2
+ import { Input } from "../../../../../components/ui/input";
3
+ import { useFilterField } from "../FilterContext";
4
+ import { FilterFieldWrapper } from "./FilterFieldWrapper";
5
+ import { debounce, FILTER_DEBOUNCE_MS } from "../../../utils/debounce";
6
+
7
+ interface NumericRangeFilterProps {
8
+ field: string;
9
+ label: string;
10
+ helpText?: string;
11
+ min?: number;
12
+ max?: number;
13
+ }
14
+
15
+ export function NumericRangeFilter({ field, label, helpText, min, max }: NumericRangeFilterProps) {
16
+ const { value, onChange } = useFilterField(field);
17
+ const errorId = useId();
18
+ const [draftMin, setDraftMin] = useState(value?.min ?? "");
19
+ const [draftMax, setDraftMax] = useState(value?.max ?? "");
20
+
21
+ const onChangeRef = useRef(onChange);
22
+ useEffect(() => {
23
+ onChangeRef.current = onChange;
24
+ });
25
+ // Build the debounced caller in a mount effect, not during render: creating it
26
+ // inline would read onChangeRef.current at render time (react-hooks/refs). It
27
+ // stays a single stable instance and always calls the latest onChange via the ref.
28
+ const debouncedRef = useRef<((nextMin: string, nextMax: string) => void) | undefined>(undefined);
29
+ useEffect(() => {
30
+ debouncedRef.current = debounce((nextMin: string, nextMax: string) => {
31
+ if (!nextMin && !nextMax) onChangeRef.current(undefined);
32
+ else
33
+ onChangeRef.current({
34
+ field,
35
+ label,
36
+ type: "numeric",
37
+ min: nextMin || undefined,
38
+ max: nextMax || undefined,
39
+ });
40
+ }, FILTER_DEBOUNCE_MS);
41
+ }, [field, label]);
42
+
43
+ useEffect(() => {
44
+ setDraftMin(value?.min ?? "");
45
+ setDraftMax(value?.max ?? "");
46
+ }, [value?.min, value?.max]);
47
+
48
+ const isOutOfBounds = (v: string) => {
49
+ if (v === "") return false;
50
+ const n = Number(v);
51
+ return (min != null && n < min) || (max != null && n > max);
52
+ };
53
+ const minOutOfBounds = isOutOfBounds(draftMin);
54
+ const maxOutOfBounds = isOutOfBounds(draftMax);
55
+ const isRangeInverted = draftMin !== "" && draftMax !== "" && Number(draftMin) > Number(draftMax);
56
+ const hasError = minOutOfBounds || maxOutOfBounds || isRangeInverted;
57
+
58
+ const boundsLabel =
59
+ min != null && max != null
60
+ ? `${min}–${max}`
61
+ : min != null
62
+ ? `${min} or more`
63
+ : max != null
64
+ ? `${max} or less`
65
+ : null;
66
+
67
+ const errorMessage = isRangeInverted
68
+ ? "Min must not exceed max"
69
+ : (minOutOfBounds || maxOutOfBounds) && boundsLabel
70
+ ? `Value must be between ${boundsLabel}`
71
+ : undefined;
72
+
73
+ return (
74
+ <FilterFieldWrapper label={label} helpText={helpText} error={errorMessage} errorId={errorId}>
75
+ <div className="flex items-center gap-2">
76
+ <Input
77
+ type="number"
78
+ placeholder="Min"
79
+ min={min}
80
+ max={max}
81
+ value={draftMin}
82
+ onChange={(e) => {
83
+ setDraftMin(e.target.value);
84
+ debouncedRef.current?.(e.target.value, draftMax);
85
+ }}
86
+ aria-label={`${label} minimum`}
87
+ aria-invalid={hasError || undefined}
88
+ aria-describedby={hasError ? errorId : undefined}
89
+ />
90
+ <span className="text-sm text-muted-foreground">–</span>
91
+ <Input
92
+ type="number"
93
+ placeholder="Max"
94
+ min={min}
95
+ max={max}
96
+ value={draftMax}
97
+ onChange={(e) => {
98
+ setDraftMax(e.target.value);
99
+ debouncedRef.current?.(draftMin, e.target.value);
100
+ }}
101
+ aria-label={`${label} maximum`}
102
+ aria-invalid={hasError || undefined}
103
+ aria-describedby={hasError ? errorId : undefined}
104
+ />
105
+ </div>
106
+ </FilterFieldWrapper>
107
+ );
108
+ }
@@ -0,0 +1,46 @@
1
+ import {
2
+ Select,
3
+ SelectContent,
4
+ SelectItem,
5
+ SelectTrigger,
6
+ SelectValue,
7
+ } from "../../../../../components/ui/select";
8
+ import { useFilterField } from "../FilterContext";
9
+ import { FilterFieldWrapper } from "./FilterFieldWrapper";
10
+
11
+ const ALL_VALUE = "__all__";
12
+
13
+ interface SelectFilterProps {
14
+ field: string;
15
+ label: string;
16
+ options: Array<{ value: string; label: string }>;
17
+ helpText?: string;
18
+ }
19
+
20
+ export function SelectFilter({ field, label, options, helpText }: SelectFilterProps) {
21
+ const { value, onChange } = useFilterField(field);
22
+ const id = `filter-${field}`;
23
+ return (
24
+ <FilterFieldWrapper label={label} htmlFor={id} helpText={helpText}>
25
+ <Select
26
+ value={value?.value ?? ALL_VALUE}
27
+ onValueChange={(v) => {
28
+ if (v === ALL_VALUE) onChange(undefined);
29
+ else onChange({ field, label, type: "picklist", value: v });
30
+ }}
31
+ >
32
+ <SelectTrigger id={id} className="w-full">
33
+ <SelectValue placeholder={`Select ${label.toLowerCase()}`} />
34
+ </SelectTrigger>
35
+ <SelectContent>
36
+ <SelectItem value={ALL_VALUE}>All</SelectItem>
37
+ {options.map((opt) => (
38
+ <SelectItem key={opt.value} value={opt.value}>
39
+ {opt.label}
40
+ </SelectItem>
41
+ ))}
42
+ </SelectContent>
43
+ </Select>
44
+ </FilterFieldWrapper>
45
+ );
46
+ }