diff --git a/cspell.json b/cspell.json index 2241a26e2fb6..641cad3c0d60 100644 --- a/cspell.json +++ b/cspell.json @@ -566,13 +566,16 @@ "customairshipextender", "customfield", "customise", + "darwinbox", "dateexported", + "dayforce", "deapex", "dgst", "deapexer", "debitamount", "deburr", "deburred", + "decidium", "dedupe", "dedup", "deeplink", @@ -656,6 +659,7 @@ "foreignamount", "formatjs", "freetext", + "freshteam", "frontpart", "fullstory", "gastos", @@ -677,6 +681,7 @@ "gsib", "gsst", "gödecke", + "hailey", "hanno", "hanno_gödecke", "headshot", @@ -687,6 +692,7 @@ "hibob", "hrefs", "hris", + "humaans", "hybridapp", "iOSQRCode", "iaco", @@ -698,7 +704,10 @@ "inbetweenCompo", "initialises", "inputmethod", + "insperity", "instancetype", + "intelli", + "intellihr", "intenthandler", "ionatan", "iphonesimulator", @@ -715,6 +724,11 @@ "jridgewell", "jsSrcsDir", "jsbundle", + "jumpcloud", + "justworks", + "kallidus", + "keka", + "kenjo", "keyalg", "keycap", "keycommand", @@ -847,6 +861,7 @@ "passplus", "passwordless", "pathspec", + "paycor", "payrollcode", "payrollid", "pbxproj", @@ -854,6 +869,7 @@ "pdfs", "perdiem", "perdiems", + "peoplehr", "persistable", "personaltrackcase", "personaltrackgoal", @@ -956,6 +972,7 @@ "sideproject", "simctl", "showcerts", + "simployer", "skia", "skip_codesigning", "soloader", @@ -984,6 +1001,7 @@ "subtab", "subtabs", "subview", + "successfactors", "superapp", "superpowered", "supportal", @@ -1102,6 +1120,7 @@ "yalc", "yourcompany", "yourname", + "zelt", "zencdn", "zipalign", "zoneinfo", diff --git a/src/CONST/MERGE_HR_PROVIDERS.ts b/src/CONST/MERGE_HR_PROVIDERS.ts index ed9bc6376d57..51dc524627e1 100644 --- a/src/CONST/MERGE_HR_PROVIDERS.ts +++ b/src/CONST/MERGE_HR_PROVIDERS.ts @@ -1,3 +1,5 @@ +// The provider slug keys below are dictated by Merge's API. +/* eslint-disable @typescript-eslint/naming-convention */ type MergeHRProviderEntry = { /** Human-readable label used in the UI */ displayName: string; @@ -6,19 +8,172 @@ type MergeHRProviderEntry = { iconUrl: string; }; +// Providers commented out below are supported by Merge but not enabled on our side yet. Uncomment them as they are rolled out. const MERGE_HR_PROVIDERS = { - workday: { - displayName: 'Workday', - iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformWorkday.png', - }, + // 'adp-decidium': { + // displayName: 'ADP DECIDIUM', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/ADP_Decidium_Square.png', + // }, + // 'adp-run': { + // displayName: 'ADP RUN', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/ADP_RUN_Square.png', + // }, + // 'adp-workforce-now': { + // displayName: 'ADP Workforce Now', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/ADP_Workforce_Now_Square.png', + // }, bamboohr: { displayName: 'BambooHR', iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/BambooHR_Square_Logo.jpg', }, + breathe: { + displayName: 'Breathe', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Breathe_Square_Logo_FY9Zgjm.jpg', + }, + // 'cezanne-hr': { + // displayName: 'Cezanne HR', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Cezanne-square.png', + // }, + // darwinbox: { + // displayName: 'Darwinbox', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Darwinbox-square.png', + // }, + dayforce: { + displayName: 'Dayforce', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformDayforce.png', + }, + // 'folks-hr': { + // displayName: 'Folks HR', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformFolks_square.png', + // }, + // fourth: { + // displayName: 'Fourth', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Fourth_square.png', + // }, + freshteam: { + displayName: 'Freshteam', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Freshteam_Square.png', + }, + // 'generic-sftp': { + // displayName: 'Generic SFTP', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/generic_sftp_square_mJ3x0IU.png', + // }, + // 'hailey-hr': { + // displayName: 'Hailey HR', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformHailey_HR.png', + // }, hibob: { displayName: 'HiBob', iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformHibob.png', }, + 'hr-cloud': { + displayName: 'HR Cloud', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/HR_Cloud_Square_Logo.jpg', + }, + humaans: { + displayName: 'Humaans', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Humaans_Square_Logo.jpg', + }, + 'insperity-premier': { + displayName: 'Insperity Premier', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Platforminsperity_square.png', + }, + intellihr: { + displayName: 'IntelliHR', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/IntelliHR_Square_Logo.jpg', + }, + // 'iris-cascade': { + // displayName: 'IRIS Cascade', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/IRIS_Cascade-square.png', + // }, + jumpcloud: { + displayName: 'JumpCloud', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Jumpcloud_square.png', + }, + justworks: { + displayName: 'Justworks', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Justworks_Square_Logo.jpg', + }, + kallidus: { + displayName: 'Kallidus', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Kallidus_square.png', + }, + keka: { + displayName: 'Keka', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/keka_square.png', + }, + // kenjo: { + // displayName: 'Kenjo', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformKenjo_square.png', + // }, + lucca: { + displayName: 'Lucca', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Lucca.jpg', + }, + namely: { + displayName: 'Namely', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Namely_Square_Logo.jpg', + }, + // 'oracle-cloud-human-capital-management-hcm': { + // displayName: 'Oracle Cloud Human Capital Management (HCM)', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Oracle_square.png', + // }, + paychex: { + displayName: 'Paychex', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Paychex_Square_Logo.png', + }, + // paycom: { + // displayName: 'Paycom', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformPaycom.png', + // }, + paycor: { + displayName: 'Paycor', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Platformpaycor_square.png', + }, + paylocity: { + displayName: 'Paylocity', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Paylocity_Square_Logo_LGm1Fdn.png', + }, + peoplehr: { + displayName: 'PeopleHR', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PeopleHR_Access_Square.png', + }, + // rippling: { + // displayName: 'Rippling', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Platformrippling_rR8x3bN.png', + // }, + // 'sage-hr': { + // displayName: 'Sage HR', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Sage_HR_Square_L3bw47M.png', + // }, + // 'sap-successfactors': { + // displayName: 'SAP SuccessFactors', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/SuccessFactors_Square_Logo_BcXogF0.jpg', + // }, + simployer: { + displayName: 'Simployer', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/Simployer_Square.png', + }, + 'ukg-pro': { + displayName: 'UKG Pro', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/UKG_square.png', + }, + 'ukg-pro-workforce-management': { + displayName: 'UKG Pro Workforce Management', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/UKG_square_da9Q05B.png', + }, + 'ukg-ready': { + displayName: 'UKG Ready', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/UKG_square_geaH8qP.png', + }, + workday: { + displayName: 'Workday', + iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformWorkday.png', + }, + // zelt: { + // displayName: 'Zelt', + // iconUrl: 'https://merge-api-public.s3.amazonaws.com/media/PlatformZelt.png', + // }, } as const satisfies Record; type MergeHRProviderSlug = keyof typeof MERGE_HR_PROVIDERS; diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar/BaseSearchBar.tsx similarity index 74% rename from src/components/SearchBar.tsx rename to src/components/SearchBar/BaseSearchBar.tsx index b76748a1909a..0f70b440ea66 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar/BaseSearchBar.tsx @@ -1,25 +1,22 @@ +import Text from '@components/Text'; +import TextInput from '@components/TextInput'; +import type {BaseTextInputProps, BaseTextInputRef} from '@components/TextInput/BaseTextInput/types'; + import useDebouncedAccessibilityAnnouncement from '@hooks/useDebouncedAccessibilityAnnouncement'; -import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -import type IconAsset from '@src/types/utils/IconAsset'; import type {StyleProp, ViewStyle} from 'react-native'; import React from 'react'; import {View} from 'react-native'; -import type {BaseTextInputRef} from './TextInput/BaseTextInput/types'; - -import Text from './Text'; -import TextInput from './TextInput'; - -type SearchBarProps = { +type SharedSearchBarProps = { + /** Label describing the search input. Also used as the accessibility label. */ label: string; - icon?: IconAsset; inputValue: string; onChangeText?: (text: string) => void; onSubmitEditing?: (text: string) => void; @@ -29,11 +26,15 @@ type SearchBarProps = { ref?: React.Ref; }; -function SearchBar({ref, label, style, icon, inputValue, onChangeText, onSubmitEditing, shouldShowEmptyState, emptyStateContainerStyle}: SearchBarProps) { +type BaseSearchBarProps = SharedSearchBarProps & { + /** Variant-specific TextInput props (floating label vs placeholder, icon, size styles). */ + textInputProps?: BaseTextInputProps; +}; + +function BaseSearchBar({ref, label, style, inputValue, onChangeText, onSubmitEditing, shouldShowEmptyState, emptyStateContainerStyle, textInputProps}: BaseSearchBarProps) { const styles = useThemeStyles(); const {shouldUseNarrowLayout, isInLandscapeMode} = useResponsiveLayout(); const {translate} = useLocalize(); - const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass']); const noResultsMessage = translate('common.noResultsFoundMatching', inputValue); const shouldAnnounceNoResults = !!shouldShowEmptyState && inputValue.length !== 0; @@ -44,7 +45,6 @@ function SearchBar({ref, label, style, icon, inputValue, onChangeText, onSubmitE onSubmitEditing?.(inputValue)} shouldShowClearButton shouldHideClearButton={!inputValue?.length} + {...textInputProps} /> {shouldAnnounceNoResults && ( @@ -73,4 +72,5 @@ function SearchBar({ref, label, style, icon, inputValue, onChangeText, onSubmitE ); } -export default SearchBar; +export default BaseSearchBar; +export type {SharedSearchBarProps}; diff --git a/src/components/SearchBar/CompactSearchBar.tsx b/src/components/SearchBar/CompactSearchBar.tsx new file mode 100644 index 000000000000..c623e6196f2c --- /dev/null +++ b/src/components/SearchBar/CompactSearchBar.tsx @@ -0,0 +1,45 @@ +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; + +import React from 'react'; + +import type {SharedSearchBarProps} from './BaseSearchBar'; + +import BaseSearchBar from './BaseSearchBar'; + +type CompactSearchBarProps = SharedSearchBarProps; + +/** + * The compact list-style search input (placeholder instead of a floating label), matching the one used above selection lists. + */ +function CompactSearchBar({ref, label, style, inputValue, onChangeText, onSubmitEditing, shouldShowEmptyState, emptyStateContainerStyle}: CompactSearchBarProps) { + const styles = useThemeStyles(); + const theme = useTheme(); + // The compact search input is sized by the physical device width (`isSmallScreenWidth`), not `shouldUseNarrowLayout`, so + // it stays compact when rendered inside an RHP/narrow pane on web/desktop and only grows to the tall size on mobile. + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth} = useResponsiveLayout(); + + return ( + + ); +} + +export default CompactSearchBar; diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx new file mode 100644 index 000000000000..af13fa005cf1 --- /dev/null +++ b/src/components/SearchBar/index.tsx @@ -0,0 +1,40 @@ +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useThemeStyles from '@hooks/useThemeStyles'; + +import type IconAsset from '@src/types/utils/IconAsset'; + +import React from 'react'; + +import type {SharedSearchBarProps} from './BaseSearchBar'; + +import BaseSearchBar from './BaseSearchBar'; + +type SearchBarProps = SharedSearchBarProps & { + /** Icon shown inside the input while it is empty. Defaults to the magnifying glass. */ + icon?: IconAsset; +}; + +function SearchBar({ref, label, style, icon, inputValue, onChangeText, onSubmitEditing, shouldShowEmptyState, emptyStateContainerStyle}: SearchBarProps) { + const styles = useThemeStyles(); + const expensifyIcons = useMemoizedLazyExpensifyIcons(['MagnifyingGlass']); + + return ( + + ); +} + +export default SearchBar; diff --git a/src/languages/de.ts b/src/languages/de.ts index c1bd8cea6dfa..9958fa4288df 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -8240,6 +8240,7 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc authenticationError: (providerName: string) => `Verbindung mit ${providerName} aufgrund einer abgelaufenen Verknüpfung nicht möglich.`, reconnect: 'Erneut verbinden', reconnectLink: 'Erneut verbinden.', + findIntegration: 'Integration finden', }, emptyDomain: { title: 'Stärken Sie Ihre Sicherheit mit Domains', diff --git a/src/languages/el.ts b/src/languages/el.ts index b2e70b991e20..a02d71628eae 100644 --- a/src/languages/el.ts +++ b/src/languages/el.ts @@ -7331,6 +7331,7 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ prompt: 'Έχετε φτάσει το όριο συγχρονισμών σας για σήμερα.', }, }, + findIntegration: 'Βρείτε ενοποίηση', }, export: { notReadyHeading: 'Μη έτοιμο για εξαγωγή', diff --git a/src/languages/en.ts b/src/languages/en.ts index 9ce81da43e8b..255db8597a0d 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -7190,6 +7190,7 @@ const translations = { "Connect to your HR system to sync employee data, auto-match reimbursements to the right people, and keep your team's expenses accurate without the manual work.", subtitle: 'Connect HR tools and keep employee approvals in sync.', connect: 'Connect', + findIntegration: 'Find integration', syncNow: 'Sync now', disconnect: 'Disconnect', disconnectTitle: (providerName: string) => `Disconnect ${providerName}`, diff --git a/src/languages/es.ts b/src/languages/es.ts index 693a7c7015cc..d6a96348128e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -7049,6 +7049,7 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`, authenticationError: (providerName: string) => `No se puede conectar a ${providerName} porque la conexión ha caducado.`, reconnect: 'Volver a conectar', reconnectLink: 'Volver a conectar.', + findIntegration: 'Buscar integración', }, export: { notReadyHeading: 'No está listo para exportar', diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 0aefec5e1eeb..aff18c532a90 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -8271,6 +8271,7 @@ Ajoutez davantage de règles de dépenses pour protéger la trésorerie de l’e authenticationError: (providerName: string) => `Connexion à ${providerName} impossible en raison d'une connexion expirée.`, reconnect: 'Reconnect', reconnectLink: 'Reconnectez-vous.', + findIntegration: 'Rechercher une intégration', }, emptyDomain: { title: 'Renforcez votre sécurité avec des domaines', diff --git a/src/languages/it.ts b/src/languages/it.ts index f8a7f6379a7a..90f6a4b4f364 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -8205,6 +8205,7 @@ Aggiungi altre regole di spesa per proteggere il flusso di cassa aziendale.`, authenticationError: (providerName: string) => `Impossibile connettersi a ${providerName} a causa di una connessione scaduta.`, reconnect: 'Riconnetti', reconnectLink: 'Riconnetti.', + findIntegration: 'Trova integrazione', }, emptyDomain: { title: 'Migliora la tua sicurezza con i domini', diff --git a/src/languages/ja.ts b/src/languages/ja.ts index 976481906bc2..bd38c03a5f5c 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -8105,6 +8105,7 @@ ${reportName}`, authenticationError: (providerName: string) => `有効期限が切れた接続のため、${providerName} に接続できません。`, reconnect: '再接続', reconnectLink: '再接続する', + findIntegration: '連携を検索', }, emptyDomain: { title: 'ドメインでセキュリティを強化しましょう', diff --git a/src/languages/nl.ts b/src/languages/nl.ts index b016bea91f90..05a31becc14e 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -8183,6 +8183,7 @@ er bestedingsregels toe om de kasstroom van het bedrijf te beschermen.`, authenticationError: (providerName: string) => `Kan geen verbinding maken met ${providerName} vanwege een verlopen koppeling.`, reconnect: 'Opnieuw verbinden', reconnectLink: 'Opnieuw verbinden.', + findIntegration: 'Integratie zoeken', }, emptyDomain: {title: 'Verbeter je beveiliging met domeinen', subtitle: 'Vereis dat leden op je domein inloggen via single sign-on, beperk het aanmaken van werkruimtes en meer.'}, }, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 5de5d5591e32..ca063822c9e6 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -8164,6 +8164,7 @@ Dodaj więcej zasad wydatków, żeby chronić płynność finansową firmy.`, authenticationError: (providerName: string) => `Nie można połączyć z ${providerName} z powodu wygasłego połączenia.`, reconnect: 'Połącz ponownie', reconnectLink: 'Połącz ponownie.', + findIntegration: 'Znajdź integrację', }, emptyDomain: { title: 'Zwiększ swoje bezpieczeństwo dzięki domenom', diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 40f5a92b1b82..d011328730ee 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -8185,6 +8185,7 @@ Adicione mais regras de gasto para proteger o fluxo de caixa da empresa.`, authenticationError: (providerName: string) => `Não é possível conectar a ${providerName} devido a uma conexão expirada.`, reconnect: 'Reconectar', reconnectLink: 'Reconectar.', + findIntegration: 'Encontrar integração', }, emptyDomain: { title: 'Aumente sua segurança com domínios', diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 87868905f11f..3e20717bafd8 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -7896,6 +7896,7 @@ ${reportName}`, authenticationError: (providerName: string) => `由于连接已过期,无法连接到 ${providerName}。`, reconnect: '重新连接', reconnectLink: '重新连接。', + findIntegration: '查找集成', }, emptyDomain: {title: '通过域名提升安全性', subtitle: '要求您域中的成员通过单点登录登录、限制工作区创建等。'}, }, diff --git a/src/pages/workspace/hr/WorkspaceHRPage.tsx b/src/pages/workspace/hr/WorkspaceHRPage.tsx index 081ffd46ee30..9b446726fb71 100644 --- a/src/pages/workspace/hr/WorkspaceHRPage.tsx +++ b/src/pages/workspace/hr/WorkspaceHRPage.tsx @@ -3,6 +3,7 @@ import ConnectToHRFlow from '@components/ConnectToHRFlow'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; +import CompactSearchBar from '@components/SearchBar/CompactSearchBar'; import Section from '@components/Section'; import useConfirmModal from '@hooks/useConfirmModal'; @@ -15,6 +16,7 @@ import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import usePolicyFeatureWriteAccess from '@hooks/usePolicyFeatureWriteAccess'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useSearchResults from '@hooks/useSearchResults'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceDocumentTitle from '@hooks/useWorkspaceDocumentTitle'; @@ -23,6 +25,7 @@ import {openPolicyHRPage} from '@libs/actions/PolicyConnections'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@libs/Navigation/types'; +import tokenizedSearch from '@libs/tokenizedSearch'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; @@ -90,6 +93,11 @@ function WorkspaceHRPage({ connectedCards.sort(byName); disconnectedCards.sort(byName); + const filterCard = (card: HRCardDescriptor, searchInput: string) => { + return tokenizedSearch([card], searchInput, (c) => [c.displayName]).length > 0; + }; + const [inputValue, setInputValue, filteredDisconnectedCards] = useSearchResults(disconnectedCards, filterCard); + const {canWrite: canWriteMoreFeatures, showReadOnlyModal} = usePolicyFeatureWriteAccess(policy, CONST.POLICY.POLICY_FEATURE.MORE_FEATURES); const handleConnect = (card: HRCardDescriptor) => { @@ -117,6 +125,26 @@ function WorkspaceHRPage({ setActiveHRFlow({setupLink: card.setupLink, key: Math.random()}); }; + const maybeSearchBar = disconnectedCards.length >= CONST.STANDARD_LIST_ITEM_LIMIT && ( + + ); + const filteredDisconnectedHRProviderCards = filteredDisconnectedCards.map((card) => ( + handleConnect(card)} + canWriteMoreFeatures={canWriteMoreFeatures} + showReadOnlyModal={showReadOnlyModal} + /> + )); + return ( ))} - {connectedCards.length === 0 && - disconnectedCards.map((card) => ( - handleConnect(card)} - canWriteMoreFeatures={canWriteMoreFeatures} - showReadOnlyModal={showReadOnlyModal} - /> - ))} + {connectedCards.length === 0 && ( + <> + {maybeSearchBar} + {filteredDisconnectedHRProviderCards} + + )} {connectedCards.length > 0 && disconnectedCards.length > 0 && !connectedCards.some((c) => c.isInitialSyncInProgress) && ( @@ -186,16 +209,8 @@ function WorkspaceHRPage({ titleStyle={[styles.textNormal, styles.colorMuted]} textStyle={[styles.flex1, styles.userSelectNone, styles.textNormal, styles.colorMuted]} > - {disconnectedCards.map((card) => ( - handleConnect(card)} - canWriteMoreFeatures={canWriteMoreFeatures} - showReadOnlyModal={showReadOnlyModal} - /> - ))} + {maybeSearchBar} + {filteredDisconnectedHRProviderCards} )} diff --git a/src/pages/workspace/hr/utils.ts b/src/pages/workspace/hr/utils.ts index 0b6c7149322d..8f268838a0b4 100644 --- a/src/pages/workspace/hr/utils.ts +++ b/src/pages/workspace/hr/utils.ts @@ -109,7 +109,7 @@ type GetHRCardStateParams = { /** Locale helper that converts an ISO datetime to a localized date string. */ getLocalDateFromDatetime: LocaleContextProps['getLocalDateFromDatetime']; - /** Slug identifying a specific Merge HR sub-provider (e.g. "bamboohr", "rippling"). */ + /** Slug identifying a specific Merge HR sub-provider (e.g. "bamboohr", "workday"). */ mergeSlug?: MergeHRProviderSlug; };