Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SelectionList: Added listEmptyContent prop + QBO: Added an illustration for empty lists (when there is no accounts found) #42561

Merged
Merged
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}
Copy link
Contributor

@tienifr tienifr Jun 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @s77rt Could you explain why we need flexGrow1 here? Is it to let the listEmptyContent occupy all remaining space. If so, BlockingView already has flex1 which covers that case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tienifr That's correct however the style on the BlockingView is not enough because the blocking view is rendered inside another view. contentContainerStyle sets the style on that wrapping view.

Screenshot 2024-06-10 at 6 15 38 PM Screenshot 2024-06-10 at 6 16 00 PM

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
Loading