@salesforce/templates 66.13.5 → 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.
- package/lib/templates/project/angularextapp/CHANGELOG.md +258 -0
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/angular.json +1 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package-lock.json +299 -279
- package/lib/templates/project/angularextapp/_p_/_m_/_w_/_a_/package.json +2 -2
- package/lib/templates/project/angularextapp/package.json +1 -1
- package/lib/templates/project/angularextapp/scripts/org-setup-config-schema.mjs +17 -0
- package/lib/templates/project/angularextapp/scripts/org-setup-utils.mjs +33 -3
- package/lib/templates/project/angularextapp/scripts/org-setup.mjs +535 -3
- package/lib/templates/project/angularintapp/CHANGELOG.md +266 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/angular.json +1 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/eslint.config.js +9 -1
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package-lock.json +309 -289
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/package.json +2 -2
- package/lib/templates/project/angularintapp/_p_/_m_/_w_/_a_/src/app/features/agentforce/agentforce-embed.service.ts +6 -1
- package/lib/templates/project/angularintapp/package.json +1 -1
- package/lib/templates/project/angularintapp/scripts/org-setup-config-schema.mjs +17 -0
- package/lib/templates/project/angularintapp/scripts/org-setup-utils.mjs +33 -3
- package/lib/templates/project/angularintapp/scripts/org-setup.mjs +535 -3
- package/lib/templates/project/reactexternalapp/CHANGELOG.md +1313 -0
- package/lib/templates/project/reactexternalapp/PRD.md +38 -0
- package/lib/templates/project/reactexternalapp/README.md +14 -10
- package/lib/templates/project/reactexternalapp/_p_/_m_/_ps_/reactexternalapp_Guest_User_Api_Access.permissionset-meta.xml +25 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package-lock.json +587 -626
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/package.json +9 -7
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/scripts/get-graphql-schema.mjs +8 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/components/SocialLogin.tsx +141 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/authentication/pages/Login.tsx +2 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +71 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +277 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +185 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +77 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +182 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +55 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
- 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
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +83 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +703 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +90 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +156 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +307 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphqlClient.ts +24 -8
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/appLayout.tsx +7 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/CustomerWebClientChat.tsx +74 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/index.ts +6 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +21 -293
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +22 -7
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/pages/TestChatPage.tsx +26 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +11 -5
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/types/chat.ts +36 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls +133 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig.cls-meta.xml +5 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls +101 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/classes/UIBundleSocialLoginConfig_Test.cls-meta.xml +5 -0
- package/lib/templates/project/reactexternalapp/_p_/_m_/package.xml +4 -0
- package/lib/templates/project/reactexternalapp/package.json +8 -2
- package/lib/templates/project/reactexternalapp/scripts/org-setup-config-schema.mjs +120 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-dev.mjs +87 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup-xml.mjs +182 -0
- package/lib/templates/project/reactexternalapp/scripts/org-setup.config.json +6 -2
- package/lib/templates/project/reactexternalapp/scripts/org-setup.mjs +1509 -328
- package/lib/templates/project/reactexternalapp/scripts/validate-org-setup-config.mjs +38 -0
- package/lib/templates/project/reactinternalapp/CHANGELOG.md +1127 -0
- package/lib/templates/project/reactinternalapp/PRD.md +35 -0
- package/lib/templates/project/reactinternalapp/README.md +14 -10
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package-lock.json +436 -439
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/package.json +9 -8
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/distinctValuesService.ts +84 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/api/searchService.ts +71 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/MergedSearchResults.tsx +277 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/Search.tsx +185 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SearchResults.tsx +77 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/SourceSection.tsx +182 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/PaginationControls.tsx +185 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/ScopeSelector.tsx +55 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SearchBar.tsx +55 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/controls/SortControl.tsx +62 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/ActiveFilters.tsx +61 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/DefaultFilterPanel.tsx +125 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_/components → search/components/filters}/FilterContext.tsx +7 -20
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/BooleanFilter.tsx +50 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/DateRangeFilter.tsx +160 -0
- 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
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/MultiSelectFilter.tsx +89 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/NumericRangeFilter.tsx +108 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/SelectFilter.tsx +46 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/filters/inputs/TextFilter.tsx +52 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/components/results/DefaultResultRow.tsx +109 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/config.json +83 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/{_os_ → search}/hooks/useAsyncData.ts +3 -10
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useDistinctValues.ts +59 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/hooks/useSearch.ts +703 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/index.ts +90 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/loadConfig.ts +14 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/queryBuilder.ts +156 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/types.ts +307 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/debounce.ts +17 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/fieldUtils.ts +14 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/filterUtils.ts +285 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/search/utils/sortUtils.ts +34 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/AgentforceConversationClient.tsx +16 -132
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/components → components}/ObjectBreadcrumb.tsx +7 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/button.tsx +1 -1
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/datePicker.tsx +2 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/components/ui/table.tsx +9 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountObjectDetailPage.tsx +49 -16
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/AccountSearch.tsx +21 -293
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/pages/Home.tsx +21 -6
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/routes.tsx +4 -4
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/{_f_/_os_/utils/fieldUtils.ts → utils/accountFields.ts} +9 -0
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.json +2 -2
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/tsconfig.tsbuildinfo +1 -0
- package/lib/templates/project/reactinternalapp/package.json +4 -2
- package/lib/templates/project/reactinternalapp/scripts/org-setup-config-schema.mjs +24 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-dev.mjs +87 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-utils.mjs +569 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup-xml.mjs +182 -0
- package/lib/templates/project/reactinternalapp/scripts/org-setup.mjs +1162 -214
- package/lib/templates/uiBundles/angularbasic/angular.json +1 -1
- package/lib/templates/uiBundles/angularbasic/eslint-plugin-angular-graphql.js +148 -0
- package/lib/templates/uiBundles/angularbasic/eslint.config.js +9 -1
- package/lib/templates/uiBundles/angularbasic/package-lock.json +299 -279
- package/lib/templates/uiBundles/angularbasic/package.json +2 -2
- package/lib/templates/uiBundles/reactbasic/package-lock.json +372 -352
- package/lib/utils/template-placeholders.js +7 -0
- package/lib/utils/template-placeholders.js.map +1 -1
- package/lib/utils/uiBundleTemplateUtils.d.ts +3 -1
- package/lib/utils/uiBundleTemplateUtils.js +4 -1
- package/lib/utils/uiBundleTemplateUtils.js.map +1 -1
- package/package.json +7 -7
- package/lib/templates/project/reactexternalapp/AGENT.md +0 -252
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactexternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
- package/lib/templates/project/reactexternalapp/scripts/prepare-import-unique-fields.js +0 -122
- package/lib/templates/project/reactinternalapp/AGENT.md +0 -252
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/accountSearchService.ts +0 -46
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/getAccountDetail.graphql +0 -121
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/api/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountObjectDetailPage.tsx +0 -355
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/AccountSearch.tsx +0 -304
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/_ex_/pages/Home.tsx +0 -34
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/api/objectSearchService.ts +0 -82
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/ActiveFilters.tsx +0 -89
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/PaginationControls.tsx +0 -109
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SearchBar.tsx +0 -41
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/SortControl.tsx +0 -143
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/BooleanFilter.tsx +0 -78
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateFilter.tsx +0 -128
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/DateRangeFilter.tsx +0 -70
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/MultiSelectFilter.tsx +0 -97
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/NumericRangeFilter.tsx +0 -167
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SearchFilter.tsx +0 -52
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/SelectFilter.tsx +0 -97
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/components/filters/TextFilter.tsx +0 -93
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useDebouncedCallback.ts +0 -34
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/hooks/useObjectSearchParams.ts +0 -254
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/debounce.ts +0 -25
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/filterUtils.ts +0 -404
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/_f_/_os_/utils/sortUtils.ts +0 -38
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/accountSearchService.ts +0 -46
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountIndustries.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/distinctAccountTypes.graphql +0 -19
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/account/query/searchAccounts.graphql +0 -51
- package/lib/templates/project/reactinternalapp/_p_/_m_/_w_/_a_/src/api/graphql-operations-types.ts +0 -11260
- package/lib/templates/project/reactinternalapp/scripts/prepare-import-unique-fields.js +0 -122
- /package/lib/templates/project/angularextapp/_p_/_m_/{permissionsets → _ps_}/angularextapp_Guest_User_Api_Access.permissionset-meta.xml +0 -0
- /package/lib/templates/project/angularintapp/_p_/_m_/{permissionsets → _ps_}/angularintapp_Access.permissionset-meta.xml +0 -0
- /package/lib/templates/project/reactinternalapp/_p_/_m_/{permissionsets → _ps_}/reactinternalapp_Access.permissionset-meta.xml +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useEffect, 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 TextFilterProps {
|
|
8
|
+
field: string;
|
|
9
|
+
label: string;
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
helpText?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function TextFilter({ field, label, placeholder, helpText }: TextFilterProps) {
|
|
15
|
+
const { value, onChange } = useFilterField(field);
|
|
16
|
+
const [draft, setDraft] = useState(value?.value ?? "");
|
|
17
|
+
|
|
18
|
+
const onChangeRef = useRef(onChange);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
onChangeRef.current = onChange;
|
|
21
|
+
});
|
|
22
|
+
// Build the debounced caller in a mount effect, not during render: creating it
|
|
23
|
+
// inline would read onChangeRef.current at render time (react-hooks/refs). It
|
|
24
|
+
// stays a single stable instance and always calls the latest onChange via the ref.
|
|
25
|
+
const debouncedRef = useRef<((next: string) => void) | undefined>(undefined);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
debouncedRef.current = debounce((next: string) => {
|
|
28
|
+
if (next === "") onChangeRef.current(undefined);
|
|
29
|
+
else onChangeRef.current({ field, label, type: "text", value: next });
|
|
30
|
+
}, FILTER_DEBOUNCE_MS);
|
|
31
|
+
}, [field, label]);
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
setDraft(value?.value ?? "");
|
|
35
|
+
}, [value?.value]);
|
|
36
|
+
|
|
37
|
+
const id = `filter-${field}`;
|
|
38
|
+
return (
|
|
39
|
+
<FilterFieldWrapper label={label} htmlFor={id} helpText={helpText}>
|
|
40
|
+
<Input
|
|
41
|
+
id={id}
|
|
42
|
+
type="text"
|
|
43
|
+
value={draft}
|
|
44
|
+
placeholder={placeholder}
|
|
45
|
+
onChange={(e) => {
|
|
46
|
+
setDraft(e.target.value);
|
|
47
|
+
debouncedRef.current?.(e.target.value);
|
|
48
|
+
}}
|
|
49
|
+
/>
|
|
50
|
+
</FilterFieldWrapper>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default per-node row used when no `renderResult` is supplied for a source.
|
|
3
|
+
*
|
|
4
|
+
* Layout:
|
|
5
|
+
* - Title: first entry in `displayFields` (preferring displayValue).
|
|
6
|
+
* - Subtitle: remaining display fields joined with " · ".
|
|
7
|
+
* - When `routePattern` is configured on the source, the row is wrapped in
|
|
8
|
+
* a react-router `<Link>` whose `to` is built by substituting `:fieldName`
|
|
9
|
+
* tokens with the corresponding field values (special token `:id`
|
|
10
|
+
* resolves to the configured `idField`, default "Id").
|
|
11
|
+
*
|
|
12
|
+
* Applications can still pass `renderResult[sourceKey]` to override.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ReactNode } from "react";
|
|
16
|
+
import { Link } from "react-router";
|
|
17
|
+
import { fieldValue } from "../../utils/fieldUtils";
|
|
18
|
+
import type { DisplayField, SObjectSourceConfig } from "../../types";
|
|
19
|
+
|
|
20
|
+
const ROUTE_TOKEN = /:([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
21
|
+
|
|
22
|
+
function fieldName(f: DisplayField): string {
|
|
23
|
+
return typeof f === "string" ? f : f.name;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Resolves a single dot-path against a node, returning a string or null. */
|
|
27
|
+
function readPath(node: unknown, path: string): string | null {
|
|
28
|
+
if (!node || typeof node !== "object") return null;
|
|
29
|
+
const parts = path.split(".");
|
|
30
|
+
let cursor: unknown = node;
|
|
31
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
32
|
+
if (!cursor || typeof cursor !== "object") return null;
|
|
33
|
+
cursor = (cursor as Record<string, unknown>)[parts[i]];
|
|
34
|
+
}
|
|
35
|
+
const leaf = (cursor as Record<string, unknown> | null)?.[parts[parts.length - 1]];
|
|
36
|
+
if (leaf == null) return null;
|
|
37
|
+
if (typeof leaf === "object") {
|
|
38
|
+
return fieldValue(leaf as { value?: unknown; displayValue?: string | null });
|
|
39
|
+
}
|
|
40
|
+
return String(leaf);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Reads the user-facing string for a top-level display field. */
|
|
44
|
+
function readDisplayField(node: unknown, field: DisplayField): string | null {
|
|
45
|
+
if (typeof field === "string") return readPath(node, field);
|
|
46
|
+
if ("raw" in field) return readPath(node, field.name);
|
|
47
|
+
// Relationship: prefer the first scalar subfield's display value.
|
|
48
|
+
const sub = field.subfields[0];
|
|
49
|
+
if (!sub) return null;
|
|
50
|
+
const subName = fieldName(sub);
|
|
51
|
+
return readPath(node, `${field.name}.${subName}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Substitutes `:fieldName` tokens in routePattern with node values. */
|
|
55
|
+
function resolveRoute(pattern: string, node: unknown, idField: string): string | null {
|
|
56
|
+
let failed = false;
|
|
57
|
+
const out = pattern.replace(ROUTE_TOKEN, (_, token) => {
|
|
58
|
+
const path = token === "id" ? idField : token;
|
|
59
|
+
const value = readPath(node, path);
|
|
60
|
+
if (value == null) {
|
|
61
|
+
failed = true;
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
return encodeURIComponent(value);
|
|
65
|
+
});
|
|
66
|
+
return failed ? null : out;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface DefaultResultRowProps {
|
|
70
|
+
node: unknown;
|
|
71
|
+
source: SObjectSourceConfig;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function DefaultResultRow({ node, source }: DefaultResultRowProps) {
|
|
75
|
+
const idField = source.idField ?? "Id";
|
|
76
|
+
const fields = source.displayFields;
|
|
77
|
+
const titleField = fields[0];
|
|
78
|
+
const title = titleField ? readDisplayField(node, titleField) : null;
|
|
79
|
+
|
|
80
|
+
const subtitleParts = fields
|
|
81
|
+
.slice(1)
|
|
82
|
+
.map((f) => readDisplayField(node, f))
|
|
83
|
+
.filter((s): s is string => s != null && s !== "");
|
|
84
|
+
|
|
85
|
+
const content: ReactNode = (
|
|
86
|
+
<>
|
|
87
|
+
<span className="font-medium">{title ?? "—"}</span>
|
|
88
|
+
{subtitleParts.length > 0 && (
|
|
89
|
+
<p className="text-sm text-muted-foreground">{subtitleParts.join(" · ")}</p>
|
|
90
|
+
)}
|
|
91
|
+
</>
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (source.routePattern) {
|
|
95
|
+
const href = resolveRoute(source.routePattern, node, idField);
|
|
96
|
+
if (href) {
|
|
97
|
+
return (
|
|
98
|
+
<Link
|
|
99
|
+
to={href}
|
|
100
|
+
className="block hover:bg-accent rounded-md px-3 -mx-3 py-1 transition-colors"
|
|
101
|
+
>
|
|
102
|
+
{content}
|
|
103
|
+
</Link>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return <div className="px-3 -mx-3 py-1">{content}</div>;
|
|
109
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pagination": {
|
|
3
|
+
"pageSize": 10,
|
|
4
|
+
"pageSizeOptions": [5, 10, 20]
|
|
5
|
+
},
|
|
6
|
+
"sources": [
|
|
7
|
+
{
|
|
8
|
+
"kind": "sobject",
|
|
9
|
+
"key": "accounts",
|
|
10
|
+
"objectName": "Account",
|
|
11
|
+
"label": "Accounts",
|
|
12
|
+
"labelSingular": "Account",
|
|
13
|
+
"routePattern": "/accounts/:id",
|
|
14
|
+
"searchableFields": ["Name", "Phone", "Industry"],
|
|
15
|
+
"displayFields": [
|
|
16
|
+
"Name",
|
|
17
|
+
"Industry",
|
|
18
|
+
"Type",
|
|
19
|
+
"Phone",
|
|
20
|
+
"AnnualRevenue",
|
|
21
|
+
{ "name": "Owner", "subfields": ["Name"] }
|
|
22
|
+
],
|
|
23
|
+
"filterBy": [
|
|
24
|
+
{ "field": "Industry", "label": "Industry", "type": "picklist" },
|
|
25
|
+
{ "field": "Type", "label": "Type", "type": "picklist" },
|
|
26
|
+
{ "field": "AnnualRevenue", "label": "Annual Revenue", "type": "numeric" }
|
|
27
|
+
],
|
|
28
|
+
"sortBy": [
|
|
29
|
+
{ "field": "Name", "label": "Name" },
|
|
30
|
+
{ "field": "Industry", "label": "Industry" },
|
|
31
|
+
{ "field": "AnnualRevenue", "label": "Annual Revenue" },
|
|
32
|
+
{ "field": "CreatedDate", "label": "Created Date" }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"kind": "sobject",
|
|
37
|
+
"key": "contacts",
|
|
38
|
+
"objectName": "Contact",
|
|
39
|
+
"label": "Contacts",
|
|
40
|
+
"labelSingular": "Contact",
|
|
41
|
+
"routePattern": "/contacts/:id",
|
|
42
|
+
"searchableFields": ["Name", "Email", "Phone"],
|
|
43
|
+
"displayFields": [
|
|
44
|
+
"Name",
|
|
45
|
+
"Title",
|
|
46
|
+
"Email",
|
|
47
|
+
"Phone",
|
|
48
|
+
{ "name": "Account", "subfields": ["Name"] }
|
|
49
|
+
],
|
|
50
|
+
"sortBy": [
|
|
51
|
+
{ "field": "Name", "label": "Name" },
|
|
52
|
+
{ "field": "CreatedDate", "label": "Created Date" }
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"kind": "sobject",
|
|
57
|
+
"key": "opportunities",
|
|
58
|
+
"objectName": "Opportunity",
|
|
59
|
+
"label": "Opportunities",
|
|
60
|
+
"labelSingular": "Opportunity",
|
|
61
|
+
"routePattern": "/opportunities/:id",
|
|
62
|
+
"searchableFields": ["Name"],
|
|
63
|
+
"displayFields": [
|
|
64
|
+
"Name",
|
|
65
|
+
"StageName",
|
|
66
|
+
"Amount",
|
|
67
|
+
"CloseDate",
|
|
68
|
+
{ "name": "Account", "subfields": ["Name"] }
|
|
69
|
+
],
|
|
70
|
+
"filterBy": [
|
|
71
|
+
{ "field": "StageName", "label": "Stage", "type": "picklist" },
|
|
72
|
+
{ "field": "Amount", "label": "Amount", "type": "numeric" },
|
|
73
|
+
{ "field": "CloseDate", "label": "Close Date", "type": "daterange" }
|
|
74
|
+
],
|
|
75
|
+
"sortBy": [
|
|
76
|
+
{ "field": "CloseDate", "label": "Close Date" },
|
|
77
|
+
{ "field": "Amount", "label": "Amount" },
|
|
78
|
+
{ "field": "Name", "label": "Name" }
|
|
79
|
+
],
|
|
80
|
+
"defaultSort": { "field": "CloseDate", "direction": "DESC" }
|
|
81
|
+
}
|
|
82
|
+
]
|
|
83
|
+
}
|
|
@@ -7,13 +7,9 @@ interface UseAsyncDataResult<T> {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
* Runs an async fetcher on mount and whenever `deps` change.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* A cleanup flag prevents state updates if the component unmounts
|
|
15
|
-
* or deps change before the fetch completes (avoids React warnings
|
|
16
|
-
* and stale updates from out-of-order responses).
|
|
10
|
+
* Runs an async fetcher on mount and whenever `deps` change. Stale responses
|
|
11
|
+
* (deps changed during a fetch, or component unmounted) are dropped via a
|
|
12
|
+
* cancellation flag.
|
|
17
13
|
*/
|
|
18
14
|
export function useAsyncData<T>(
|
|
19
15
|
fetcher: () => Promise<T>,
|
|
@@ -29,7 +25,6 @@ export function useAsyncData<T>(
|
|
|
29
25
|
fetcherRef.current = fetcher;
|
|
30
26
|
});
|
|
31
27
|
|
|
32
|
-
// Detect dep changes during render to reset loading state and bump generation
|
|
33
28
|
const [prevDeps, setPrevDeps] = useState(deps);
|
|
34
29
|
if (deps.length !== prevDeps.length || deps.some((d, i) => d !== prevDeps[i])) {
|
|
35
30
|
setPrevDeps(deps);
|
|
@@ -40,7 +35,6 @@ export function useAsyncData<T>(
|
|
|
40
35
|
|
|
41
36
|
useEffect(() => {
|
|
42
37
|
let cancelled = false;
|
|
43
|
-
|
|
44
38
|
fetcherRef
|
|
45
39
|
.current()
|
|
46
40
|
.then((result) => {
|
|
@@ -53,7 +47,6 @@ export function useAsyncData<T>(
|
|
|
53
47
|
.finally(() => {
|
|
54
48
|
if (!cancelled) setLoading(false);
|
|
55
49
|
});
|
|
56
|
-
|
|
57
50
|
return () => {
|
|
58
51
|
cancelled = true;
|
|
59
52
|
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caches and serves distinct picklist values fetched via the GraphQL
|
|
3
|
+
* aggregate API. The cache key is `${objectName}.${fieldName}`; entries
|
|
4
|
+
* persist for the lifetime of the page so repeated mounts of the same
|
|
5
|
+
* picklist filter share the same response.
|
|
6
|
+
*
|
|
7
|
+
* Pass `null` to short-circuit the fetch (e.g. when inline options are
|
|
8
|
+
* available on the filter config).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useEffect, useState } from "react";
|
|
12
|
+
import { fetchDistinctValues, type PicklistOption } from "../api/distinctValuesService";
|
|
13
|
+
|
|
14
|
+
const cache = new Map<string, PicklistOption[]>();
|
|
15
|
+
const inflight = new Map<string, Promise<PicklistOption[]>>();
|
|
16
|
+
|
|
17
|
+
interface DistinctValuesQuery {
|
|
18
|
+
objectName: string;
|
|
19
|
+
fieldName: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function useDistinctValues(query: DistinctValuesQuery | null): PicklistOption[] | null {
|
|
23
|
+
const cacheKey = query ? `${query.objectName}.${query.fieldName}` : null;
|
|
24
|
+
const [options, setOptions] = useState<PicklistOption[] | null>(() =>
|
|
25
|
+
cacheKey ? (cache.get(cacheKey) ?? null) : null,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!query || !cacheKey) return;
|
|
30
|
+
const cached = cache.get(cacheKey);
|
|
31
|
+
if (cached) {
|
|
32
|
+
setOptions(cached);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let cancelled = false;
|
|
36
|
+
const existing = inflight.get(cacheKey);
|
|
37
|
+
const promise =
|
|
38
|
+
existing ??
|
|
39
|
+
fetchDistinctValues(query.objectName, query.fieldName).then((result) => {
|
|
40
|
+
cache.set(cacheKey, result);
|
|
41
|
+
inflight.delete(cacheKey);
|
|
42
|
+
return result;
|
|
43
|
+
});
|
|
44
|
+
if (!existing) inflight.set(cacheKey, promise);
|
|
45
|
+
promise
|
|
46
|
+
.then((result) => {
|
|
47
|
+
if (!cancelled) setOptions(result);
|
|
48
|
+
})
|
|
49
|
+
.catch((err: unknown) => {
|
|
50
|
+
inflight.delete(cacheKey);
|
|
51
|
+
console.error(`Failed to fetch distinct values for ${cacheKey}:`, err);
|
|
52
|
+
});
|
|
53
|
+
return () => {
|
|
54
|
+
cancelled = true;
|
|
55
|
+
};
|
|
56
|
+
}, [cacheKey, query]);
|
|
57
|
+
|
|
58
|
+
return options;
|
|
59
|
+
}
|