@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,55 @@
|
|
|
1
|
+
import { Search } from "lucide-react";
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
import { Input } from "../../../../components/ui/input";
|
|
4
|
+
import { debounce } from "../../utils/debounce";
|
|
5
|
+
|
|
6
|
+
const TYPE_DEBOUNCE_MS = 250;
|
|
7
|
+
|
|
8
|
+
interface SearchBarProps {
|
|
9
|
+
value: string;
|
|
10
|
+
onChange: (next: string) => void;
|
|
11
|
+
placeholder?: string;
|
|
12
|
+
className?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Debounced text input for the global `q` term. Keeps a local `draft` so
|
|
17
|
+
* keystrokes feel responsive while the upstream `onChange` is rate-limited.
|
|
18
|
+
*/
|
|
19
|
+
export function SearchBar({ value, onChange, placeholder = "Search…", className }: SearchBarProps) {
|
|
20
|
+
const [draft, setDraft] = useState(value);
|
|
21
|
+
const onChangeRef = useRef(onChange);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
onChangeRef.current = onChange;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Build the debounced caller in a mount effect, not during render: creating it
|
|
27
|
+
// inline would read onChangeRef.current at render time (react-hooks/refs). It
|
|
28
|
+
// stays a single stable instance and always calls the latest onChange via the ref.
|
|
29
|
+
const debouncedRef = useRef<((next: string) => void) | undefined>(undefined);
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
debouncedRef.current = debounce((next: string) => onChangeRef.current(next), TYPE_DEBOUNCE_MS);
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
// External value can change (URL restore, reset). Keep draft in sync.
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
setDraft(value);
|
|
37
|
+
}, [value]);
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div className={`relative flex-1 ${className ?? ""}`}>
|
|
41
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
|
42
|
+
<Input
|
|
43
|
+
type="text"
|
|
44
|
+
value={draft}
|
|
45
|
+
onChange={(e) => {
|
|
46
|
+
setDraft(e.target.value);
|
|
47
|
+
debouncedRef.current?.(e.target.value);
|
|
48
|
+
}}
|
|
49
|
+
placeholder={placeholder}
|
|
50
|
+
className="pl-9"
|
|
51
|
+
aria-label="Unified search"
|
|
52
|
+
/>
|
|
53
|
+
</div>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ArrowDown, ArrowUp } from "lucide-react";
|
|
2
|
+
import {
|
|
3
|
+
Select,
|
|
4
|
+
SelectContent,
|
|
5
|
+
SelectItem,
|
|
6
|
+
SelectTrigger,
|
|
7
|
+
SelectValue,
|
|
8
|
+
} from "../../../../components/ui/select";
|
|
9
|
+
import { Button } from "../../../../components/ui/button";
|
|
10
|
+
import type { SortFieldConfig, SortState } from "../../utils/sortUtils";
|
|
11
|
+
|
|
12
|
+
const NONE_VALUE = "__none__";
|
|
13
|
+
|
|
14
|
+
interface SortControlProps {
|
|
15
|
+
configs: SortFieldConfig[];
|
|
16
|
+
sort: SortState | null;
|
|
17
|
+
onSortChange: (sort: SortState | null) => void;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function SortControl({ configs, sort, onSortChange, className }: SortControlProps) {
|
|
22
|
+
if (configs.length === 0) return null;
|
|
23
|
+
return (
|
|
24
|
+
<div className={`flex items-center gap-2 ${className ?? ""}`}>
|
|
25
|
+
<span className="text-sm text-muted-foreground whitespace-nowrap">Sort by</span>
|
|
26
|
+
<Select
|
|
27
|
+
value={sort?.field ?? NONE_VALUE}
|
|
28
|
+
onValueChange={(v) => {
|
|
29
|
+
if (v === NONE_VALUE) onSortChange(null);
|
|
30
|
+
else onSortChange({ field: v, direction: sort?.direction ?? "ASC" });
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<SelectTrigger size="sm" className="w-[160px]" aria-label="Sort by">
|
|
34
|
+
<SelectValue placeholder="Default" />
|
|
35
|
+
</SelectTrigger>
|
|
36
|
+
<SelectContent>
|
|
37
|
+
<SelectItem value={NONE_VALUE}>Default</SelectItem>
|
|
38
|
+
{configs.map((c) => (
|
|
39
|
+
<SelectItem key={c.field} value={c.field}>
|
|
40
|
+
{c.label}
|
|
41
|
+
</SelectItem>
|
|
42
|
+
))}
|
|
43
|
+
</SelectContent>
|
|
44
|
+
</Select>
|
|
45
|
+
{sort && (
|
|
46
|
+
<Button
|
|
47
|
+
variant="ghost"
|
|
48
|
+
size="icon-sm"
|
|
49
|
+
onClick={() =>
|
|
50
|
+
onSortChange({
|
|
51
|
+
...sort,
|
|
52
|
+
direction: sort.direction === "ASC" ? "DESC" : "ASC",
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
aria-label={`Sort ${sort.direction === "ASC" ? "descending" : "ascending"}`}
|
|
56
|
+
>
|
|
57
|
+
{sort.direction === "ASC" ? <ArrowUp /> : <ArrowDown />}
|
|
58
|
+
</Button>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { X } from "lucide-react";
|
|
2
|
+
import { Button } from "../../../../components/ui/button";
|
|
3
|
+
import type { ActiveFilterValue } from "../../utils/filterUtils";
|
|
4
|
+
|
|
5
|
+
function formatLabel(filter: ActiveFilterValue): string {
|
|
6
|
+
const { label, type, value, min, max } = filter;
|
|
7
|
+
switch (type) {
|
|
8
|
+
case "text":
|
|
9
|
+
case "picklist":
|
|
10
|
+
return `${label}: ${value ?? ""}`;
|
|
11
|
+
case "multipicklist": {
|
|
12
|
+
const values = value ? value.split(",") : [];
|
|
13
|
+
if (values.length <= 2) return `${label}: ${values.join(", ")}`;
|
|
14
|
+
return `${label}: ${values.length} selected`;
|
|
15
|
+
}
|
|
16
|
+
case "boolean":
|
|
17
|
+
return `${label}: ${value === "true" ? "Yes" : "No"}`;
|
|
18
|
+
case "numeric": {
|
|
19
|
+
if (min && max) return `${label}: ${min} – ${max}`;
|
|
20
|
+
if (min) return `${label}: ≥ ${min}`;
|
|
21
|
+
return `${label}: ≤ ${max ?? ""}`;
|
|
22
|
+
}
|
|
23
|
+
case "date":
|
|
24
|
+
case "daterange":
|
|
25
|
+
case "datetime":
|
|
26
|
+
case "datetimerange": {
|
|
27
|
+
if (min && max) return `${label}: ${min} → ${max}`;
|
|
28
|
+
if (min) return `${label}: from ${min}`;
|
|
29
|
+
return `${label}: until ${max ?? ""}`;
|
|
30
|
+
}
|
|
31
|
+
default:
|
|
32
|
+
return label;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ActiveFiltersProps {
|
|
37
|
+
filters: ActiveFilterValue[];
|
|
38
|
+
onRemove: (field: string) => void;
|
|
39
|
+
className?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function ActiveFilters({ filters, onRemove, className }: ActiveFiltersProps) {
|
|
43
|
+
if (filters.length === 0) return null;
|
|
44
|
+
return (
|
|
45
|
+
<div className={`flex flex-wrap gap-2 ${className ?? ""}`}>
|
|
46
|
+
{filters.map((filter) => (
|
|
47
|
+
<Button
|
|
48
|
+
key={filter.field}
|
|
49
|
+
variant="outline"
|
|
50
|
+
size="sm"
|
|
51
|
+
className="gap-1 h-7 text-xs"
|
|
52
|
+
aria-label={`Remove ${formatLabel(filter)}`}
|
|
53
|
+
onClick={() => onRemove(filter.field)}
|
|
54
|
+
>
|
|
55
|
+
{formatLabel(filter)}
|
|
56
|
+
<X className="h-3 w-3" aria-hidden="true" />
|
|
57
|
+
</Button>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -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,
|
|
2
|
-
import { Button } from "
|
|
3
|
-
import type { ActiveFilterValue } from "
|
|
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
|
-
|
|
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 {
|
|
2
|
-
import {
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { Label } from "../../../../../components/ui/label";
|
|
3
3
|
|
|
4
|
-
interface FilterFieldWrapperProps
|
|
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={
|
|
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">
|
|
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
|
)}
|