Skip to content

Commit

Permalink
Merge pull request #42561 from s77rt/SelectionList-listEmptyContent
Browse files Browse the repository at this point in the history
SelectionList: Added listEmptyContent prop + QBO: Added an illustration for empty lists (when there is no accounts found)
  • Loading branch information
pecanoro authored May 28, 2024
2 parents a85c237 + b503938 commit c35ab9d
Show file tree
Hide file tree
Showing 18 changed files with 207 additions and 16 deletions.
6 changes: 5 additions & 1 deletion src/components/BlockingViews/BlockingView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ type BaseBlockingViewProps = {

/** Render custom subtitle */
CustomSubtitle?: React.ReactElement;

/** Additional styles to apply to the container */
containerStyle?: StyleProp<ViewStyle>;
};

type BlockingViewIconProps = {
Expand Down Expand Up @@ -81,6 +84,7 @@ function BlockingView({
animationStyles = [],
animationWebStyle = {},
CustomSubtitle,
containerStyle,
}: BlockingViewProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
Expand Down Expand Up @@ -117,7 +121,7 @@ function BlockingView({
}, [styles, subtitleText, shouldEmbedLinkWithSubtitle, CustomSubtitle]);

return (
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter, styles.ph10]}>
<View style={[styles.flex1, styles.alignItemsCenter, styles.justifyContentCenter, styles.ph10, containerStyle]}>
{animation && (
<Lottie
source={animation}
Expand Down
7 changes: 6 additions & 1 deletion src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function BaseSelectionList<TItem extends ListItem>(
headerContent,
footerContent,
listFooterContent,
listEmptyContent,
showScrollIndicator = true,
showLoadingPlaceholder = false,
showConfirmButton = false,
Expand Down Expand Up @@ -99,6 +100,7 @@ function BaseSelectionList<TItem extends ListItem>(
const itemFocusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const [currentPage, setCurrentPage] = useState(1);
const isTextInputFocusedRef = useRef<boolean>(false);
const isEmptyList = sections.length === 0;

const incrementPage = () => setCurrentPage((prev) => prev + 1);

Expand Down Expand Up @@ -642,8 +644,11 @@ function BaseSelectionList<TItem extends ListItem>(
testID="selection-list"
onLayout={onSectionListLayout}
style={(!maxToRenderPerBatch || (shouldHideListOnInitialRender && isInitialSectionListRender)) && styles.opacity0}
ListHeaderComponent={listHeaderContent}
ListFooterComponent={listFooterContent ?? ShowMoreButtonInstance}
ListHeaderComponent={listHeaderContent && listHeaderContent}
ListEmptyComponent={listEmptyContent}
contentContainerStyle={isEmptyList && listEmptyContent ? styles.flexGrow1 : undefined}
scrollEnabled={!isEmptyList || !listEmptyContent}
onEndReached={onEndReached}
onEndReachedThreshold={onEndReachedThreshold}
/>
Expand Down
3 changes: 3 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,9 @@ type BaseSelectionListProps<TItem extends ListItem> = Partial<ChildrenProps> & {
/** Custom content to display in the footer of list component. If present ShowMore button won't be displayed */
listFooterContent?: React.JSX.Element | null;

/** Content to display if the list is empty */
listEmptyContent?: React.JSX.Element | null;

/** Whether to use dynamic maxToRenderPerBatch depending on the visible number of elements */
shouldUseDynamicMaxToRenderPerBatch?: boolean;

Expand Down
5 changes: 5 additions & 0 deletions src/components/SelectionScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ type SelectionScreenProps = {
/** Custom content to display in the header */
headerContent?: React.ReactNode;

/** Content to display if the list is empty */
listEmptyContent?: React.JSX.Element | null;

/** Sections for the section list */
sections: Array<SectionListDataType<SelectorType>>;

Expand Down Expand Up @@ -58,6 +61,7 @@ function SelectionScreen({
displayName,
title,
headerContent,
listEmptyContent,
sections,
listItem,
initiallyFocusedOptionKey,
Expand Down Expand Up @@ -92,6 +96,7 @@ function SelectionScreen({
showScrollIndicator
shouldShowTooltips={false}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
listEmptyContent={listEmptyContent}
/>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2037,6 +2037,8 @@ export default {
[`${CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.CHECK}Error`]: 'Check is not available when locations are enabled. Please select a different export option.',
[`${CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY}Error`]: 'Journal entry is not available when taxes enabled. please select a different export option.',
},
noAccountsFound: 'No accounts found',
noAccountsFoundDescription: 'Add the account in Quickbooks Online and sync the connection again',
},
xero: {
organization: 'Xero organization',
Expand Down Expand Up @@ -2115,6 +2117,8 @@ export default {
},
exportPreferredExporterNote: 'This can be any workspace admin, but must be a domain admin if you set different export accounts for individual company cards in domain settings.',
exportPreferredExporterSubNote: 'Once set, the preferred exporter will see reports for export in their account.',
noAccountsFound: 'No accounts found',
noAccountsFoundDescription: 'Add the account in Xero and sync the connection again',
},
type: {
free: 'Free',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2070,6 +2070,8 @@ export default {
[`${CONST.QUICKBOOKS_REIMBURSABLE_ACCOUNT_TYPE.JOURNAL_ENTRY}Error`]:
'El asiento de diario no está disponible cuando los impuestos están habilitados. seleccione una opción de exportación diferente.',
},
noAccountsFound: 'No se ha encontrado ninguna cuenta',
noAccountsFoundDescription: 'Añade la cuenta en Quickbooks Online y sincroniza de nuevo la conexión',
},
xero: {
organization: 'Organización Xero',
Expand Down Expand Up @@ -2152,6 +2154,8 @@ export default {
exportPreferredExporterNote:
'Puede ser cualquier administrador del espacio de trabajo, pero debe ser un administrador de dominio si configura diferentes cuentas de exportación para tarjetas de empresa individuales en la configuración del dominio.',
exportPreferredExporterSubNote: 'Una vez configurado, el exportador preferido verá los informes para exportar en su cuenta.',
noAccountsFound: 'No se ha encontrado ninguna cuenta',
noAccountsFoundDescription: 'Añade la cuenta en Xero y sincroniza de nuevo la conexión',
},
type: {
free: 'Gratis',
Expand Down
4 changes: 2 additions & 2 deletions src/pages/workspace/WorkspaceNewRoomPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ function WorkspaceNewRoomPage({policies, reports, formState, session, activePoli
<>
<BlockingView
icon={Illustrations.TeleScope}
iconWidth={variables.emptyWorkspaceIconWidth}
iconHeight={variables.emptyWorkspaceIconHeight}
iconWidth={variables.emptyListIconWidth}
iconHeight={variables.emptyListIconHeight}
title={translate('workspace.emptyWorkspace.notFound')}
subtitle={translate('workspace.emptyWorkspace.description')}
shouldShowLink={false}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import BlockingView from '@components/BlockingViews/BlockingView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
Expand All @@ -13,6 +15,7 @@ import Navigation from '@libs/Navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

Expand Down Expand Up @@ -58,6 +61,20 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyConnectionsProps) {
[policyID],
);

const listEmptyContent = useMemo(
() => (
<BlockingView
icon={Illustrations.TeleScope}
iconWidth={variables.emptyListIconWidth}
iconHeight={variables.emptyListIconHeight}
title={translate('workspace.qbo.noAccountsFound')}
subtitle={translate('workspace.qbo.noAccountsFoundDescription')}
containerStyle={styles.pb10}
/>
),
[translate, styles.pb10],
);

return (
<AccessOrNotFoundWrapper
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
Expand All @@ -71,11 +88,12 @@ function QuickbooksAccountSelectPage({policy}: WithPolicyConnectionsProps) {
<HeaderWithBackButton title={translate('workspace.qbo.advancedConfig.qboBillPaymentAccount')} />

<SelectionList
sections={[{data: qboOnlineSelectorOptions}]}
sections={qboOnlineSelectorOptions.length ? [{data: qboOnlineSelectorOptions}] : []}
ListItem={RadioListItem}
headerContent={listHeaderComponent}
onSelectRow={saveSelection}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
listEmptyContent={listEmptyContent}
/>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, {useCallback, useMemo} from 'react';
import {View} from 'react-native';
import BlockingView from '@components/BlockingViews/BlockingView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
Expand All @@ -13,6 +15,7 @@ import Navigation from '@libs/Navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

Expand Down Expand Up @@ -59,6 +62,20 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyConnectionsProps
[policyID],
);

const listEmptyContent = useMemo(
() => (
<BlockingView
icon={Illustrations.TeleScope}
iconWidth={variables.emptyListIconWidth}
iconHeight={variables.emptyListIconHeight}
title={translate('workspace.qbo.noAccountsFound')}
subtitle={translate('workspace.qbo.noAccountsFoundDescription')}
containerStyle={styles.pb10}
/>
),
[translate, styles.pb10],
);

return (
<AccessOrNotFoundWrapper
policyID={policyID}
Expand All @@ -72,11 +89,12 @@ function QuickbooksInvoiceAccountSelectPage({policy}: WithPolicyConnectionsProps
<HeaderWithBackButton title={translate('workspace.qbo.advancedConfig.qboInvoiceCollectionAccount')} />

<SelectionList
sections={[{data: qboOnlineSelectorOptions}]}
sections={qboOnlineSelectorOptions.length ? [{data: qboOnlineSelectorOptions}] : []}
ListItem={RadioListItem}
headerContent={listHeaderComponent}
onSelectRow={updateAccount}
initiallyFocusedOptionKey={initiallyFocusedOptionKey}
listEmptyContent={listEmptyContent}
/>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, {useCallback, useMemo} from 'react';
import BlockingView from '@components/BlockingViews/BlockingView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
Expand All @@ -12,6 +14,7 @@ import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Account} from '@src/types/onyx/Policy';
Expand Down Expand Up @@ -62,6 +65,20 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne
[nonReimbursableExpensesAccount, policyID],
);

const listEmptyContent = useMemo(
() => (
<BlockingView
icon={Illustrations.TeleScope}
iconWidth={variables.emptyListIconWidth}
iconHeight={variables.emptyListIconHeight}
title={translate('workspace.qbo.noAccountsFound')}
subtitle={translate('workspace.qbo.noAccountsFoundDescription')}
containerStyle={styles.pb10}
/>
),
[translate, styles.pb10],
);

return (
<AccessOrNotFoundWrapper
policyID={policyID}
Expand All @@ -82,10 +99,11 @@ function QuickbooksCompanyCardExpenseAccountSelectPage({policy}: WithPolicyConne
<Text style={[styles.ph5, styles.pb5]}>{translate(`workspace.qbo.accounts.${nonReimbursableExpensesExportDestination}AccountDescription`)}</Text>
) : null
}
sections={[{data}]}
sections={data.length ? [{data}] : []}
ListItem={RadioListItem}
onSelectRow={selectExportAccount}
initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList}
listEmptyContent={listEmptyContent}
/>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, {useCallback, useMemo} from 'react';
import BlockingView from '@components/BlockingViews/BlockingView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
Expand All @@ -12,6 +14,7 @@ import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';
import type {Account} from '@src/types/onyx/Policy';
Expand Down Expand Up @@ -48,6 +51,20 @@ function QuickbooksExportInvoiceAccountSelectPage({policy}: WithPolicyConnection
[receivableAccount, policyID],
);

const listEmptyContent = useMemo(
() => (
<BlockingView
icon={Illustrations.TeleScope}
iconWidth={variables.emptyListIconWidth}
iconHeight={variables.emptyListIconHeight}
title={translate('workspace.qbo.noAccountsFound')}
subtitle={translate('workspace.qbo.noAccountsFoundDescription')}
containerStyle={styles.pb10}
/>
),
[translate, styles.pb10],
);

return (
<AccessOrNotFoundWrapper
policyID={policyID}
Expand All @@ -58,10 +75,11 @@ function QuickbooksExportInvoiceAccountSelectPage({policy}: WithPolicyConnection
<HeaderWithBackButton title={translate('workspace.qbo.exportInvoices')} />
<SelectionList
headerContent={<Text style={[styles.ph5, styles.pb5]}>{translate('workspace.qbo.exportInvoicesDescription')}</Text>}
sections={[{data}]}
sections={data.length ? [{data}] : []}
ListItem={RadioListItem}
onSelectRow={selectExportInvoice}
initiallyFocusedOptionKey={data.find((mode) => mode.isSelected)?.keyForList}
listEmptyContent={listEmptyContent}
/>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React, {useCallback, useMemo} from 'react';
import BlockingView from '@components/BlockingViews/BlockingView';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
Expand All @@ -12,6 +14,7 @@ import Navigation from '@navigation/Navigation';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import type {WithPolicyConnectionsProps} from '@pages/workspace/withPolicyConnections';
import withPolicyConnections from '@pages/workspace/withPolicyConnections';
import variables from '@styles/variables';
import CONST from '@src/CONST';
import ROUTES from '@src/ROUTES';

Expand All @@ -34,7 +37,7 @@ function QuickbooksNonReimbursableDefaultVendorSelectPage({policy}: WithPolicyCo
keyForList: vendor.name,
isSelected: vendor.id === nonReimbursableBillDefaultVendor,
})) ?? [];
return [{data}];
return data.length ? [{data}] : [];
}, [nonReimbursableBillDefaultVendor, vendors]);

const selectVendor = useCallback(
Expand All @@ -47,6 +50,20 @@ function QuickbooksNonReimbursableDefaultVendorSelectPage({policy}: WithPolicyCo
[nonReimbursableBillDefaultVendor, policyID],
);

const listEmptyContent = useMemo(
() => (
<BlockingView
icon={Illustrations.TeleScope}
iconWidth={variables.emptyListIconWidth}
iconHeight={variables.emptyListIconHeight}
title={translate('workspace.qbo.noAccountsFound')}
subtitle={translate('workspace.qbo.noAccountsFoundDescription')}
containerStyle={styles.pb10}
/>
),
[translate, styles.pb10],
);

return (
<AccessOrNotFoundWrapper
policyID={policyID}
Expand All @@ -60,7 +77,8 @@ function QuickbooksNonReimbursableDefaultVendorSelectPage({policy}: WithPolicyCo
sections={sections}
ListItem={RadioListItem}
onSelectRow={selectVendor}
initiallyFocusedOptionKey={sections[0].data.find((mode) => mode.isSelected)?.keyForList}
initiallyFocusedOptionKey={sections[0]?.data.find((mode) => mode.isSelected)?.keyForList}
listEmptyContent={listEmptyContent}
/>
</ScreenWrapper>
</AccessOrNotFoundWrapper>
Expand Down
Loading

0 comments on commit c35ab9d

Please sign in to comment.