Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5091,6 +5091,8 @@ const CONST = {
STANDARD_LENGTH_LIMIT: 100,
STANDARD_LIST_ITEM_LIMIT: 12,
SEARCH_BAR_THRESHOLD: 3,
// Number of approval workflow cards rendered before the "Load more" affordance on Workspace → Workflows.
WORKFLOW_APPROVALS_INITIAL_BATCH: 5,
LEGAL_NAMES_CHARACTER_LIMIT: 150,
LOGIN_CHARACTER_LIMIT: 254,
CATEGORY_NAME_LIMIT: 256,
Expand Down Expand Up @@ -8487,6 +8489,7 @@ const CONST = {
WORKFLOWS: {
AUTO_REPORTING_FREQUENCY: 'WorkspaceWorkflows-AutoReportingFrequency',
ADD_APPROVAL: 'WorkspaceWorkflows-AddApproval',
LOAD_MORE_APPROVALS: 'WorkspaceWorkflows-LoadMoreApprovals',
BANK_ACCOUNT: 'WorkspaceWorkflows-BankAccount',
ADD_BANK_ACCOUNT: 'WorkspaceWorkflows-AddBankAccount',
AUTHORIZED_PAYER: 'WorkspaceWorkflows-AuthorizedPayer',
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2631,6 +2631,7 @@ ${amount} für ${merchant} – ${date}`,
addApprovalsTitle: 'Genehmigungen',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `Ausgaben von ${members} und die genehmigende Person ist ${approvers}`,
addApprovalButton: 'Genehmigungsablauf hinzufügen',
loadMoreWorkflows: ({count}: {count: number}) => `${count} weitere laden`,
editWorkflowAction: 'Bearbeiten',
findWorkflow: 'Workflow suchen',
addApprovalTip: 'Dieser Standard-Workflow gilt für alle Mitglieder, sofern kein spezifischerer Workflow vorhanden ist.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2698,6 +2698,7 @@ const translations = {
addApprovalsTitle: 'Approvals',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `expenses from ${members}, and the approver is ${approvers}`,
addApprovalButton: 'Add approval workflow',
loadMoreWorkflows: ({count}: {count: number}) => `Load ${count} more`,
editWorkflowAction: 'Edit',
findWorkflow: 'Find workflow',
addApprovalTip: 'This default workflow applies to all members, unless a more specific workflow exists.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2501,6 +2501,7 @@ ${amount} para ${merchant} - ${date}`,
addApprovalsTitle: 'Aprobaciones',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `gastos de ${members}, y el aprobador es ${approvers}`,
addApprovalButton: 'Añadir flujo de aprobación',
loadMoreWorkflows: ({count}: {count: number}) => `Cargar ${count} más`,
editWorkflowAction: 'Editar',
findWorkflow: 'Buscar flujo de trabajo',
addApprovalTip: 'Este flujo de trabajo por defecto se aplica a todos los miembros, a menos que exista un flujo de trabajo más específico.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2637,6 +2637,7 @@ ${amount} pour ${merchant} - ${date}`,
addApprovalsTitle: 'Approbations',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `dépenses de ${members}, et l'approbateur est ${approvers}`,
addApprovalButton: 'Ajouter un workflow d’approbation',
loadMoreWorkflows: ({count}: {count: number}) => `Charger ${count} de plus`,
editWorkflowAction: 'Modifier',
findWorkflow: 'Rechercher un flux de travail',
addApprovalTip: 'Ce workflow par défaut s’applique à tous les membres, sauf si un workflow plus spécifique existe.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,7 @@ ${amount} per ${merchant} - ${date}`,
addApprovalsTitle: 'Approvazioni',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `spese di ${members} e l'approvatore è ${approvers}`,
addApprovalButton: 'Aggiungi flusso di approvazione',
loadMoreWorkflows: ({count}: {count: number}) => `Carica altri ${count}`,
editWorkflowAction: 'Modifica',
findWorkflow: 'Cerca flusso di lavoro',
addApprovalTip: 'Questo flusso di lavoro predefinito si applica a tutti i membri, a meno che non esista un flusso di lavoro più specifico.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2604,6 +2604,7 @@ ${date} の ${merchant} への ${amount}`,
addApprovalsTitle: '承認',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `${members} の経費で、承認者は ${approvers} です`,
addApprovalButton: '承認ワークフローを追加',
loadMoreWorkflows: ({count}: {count: number}) => `さらに${count}件を読み込む`,
editWorkflowAction: '編集',
findWorkflow: 'ワークフローを検索',
addApprovalTip: 'より詳細なワークフローが存在する場合を除き、このデフォルトのワークフローがすべてのメンバーに適用されます。',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2624,6 +2624,7 @@ ${amount} voor ${merchant} - ${date}`,
addApprovalsTitle: 'Goedkeuringen',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `de uitgaven van ${members}, en de goedkeurder is ${approvers}`,
addApprovalButton: 'Goedkeuringsworkflow toevoegen',
loadMoreWorkflows: ({count}: {count: number}) => `${count} meer laden`,
editWorkflowAction: 'Bewerken',
findWorkflow: 'Workflow zoeken',
addApprovalTip: 'Deze standaardworkflow is van toepassing op alle leden, tenzij er een specifiekere workflow bestaat.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2620,6 +2620,7 @@ ${amount} dla ${merchant} - ${date}`,
addApprovalsTitle: 'Zatwierdzenia',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `wydatki od ${members}, a zatwierdzającym jest ${approvers}`,
addApprovalButton: 'Dodaj proces akceptacji',
loadMoreWorkflows: ({count}: {count: number}) => `Załaduj ${count} więcej`,
editWorkflowAction: 'Edytuj',
findWorkflow: 'Znajdź przepływ pracy',
addApprovalTip: 'Domyślny proces pracy ma zastosowanie do wszystkich członków, chyba że istnieje bardziej szczegółowy proces pracy.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2618,6 +2618,7 @@ ${amount} para ${merchant} - ${date}`,
addApprovalsTitle: 'Aprovações',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `despesas de ${members}, e o aprovador é ${approvers}`,
addApprovalButton: 'Adicionar fluxo de aprovação',
loadMoreWorkflows: ({count}: {count: number}) => `Carregar mais ${count}`,
editWorkflowAction: 'Editar',
findWorkflow: 'Buscar fluxo de trabalho',
addApprovalTip: 'Este fluxo de trabalho padrão se aplica a todos os membros, a menos que exista um fluxo de trabalho mais específico.',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2554,6 +2554,7 @@ ${amount},商户:${merchant} - 日期:${date}`,
addApprovalsTitle: '审批',
accessibilityLabel: ({members, approvers}: {members: string; approvers: string}) => `来自${members}的报销,审批人是${approvers}`,
addApprovalButton: '添加审批工作流',
loadMoreWorkflows: ({count}: {count: number}) => `加载更多 ${count} 个`,
editWorkflowAction: '编辑',
findWorkflow: '查找工作流',
addApprovalTip: '除非存在更具体的工作流程,否则此默认工作流程适用于所有成员。',
Expand Down
57 changes: 53 additions & 4 deletions src/pages/workspace/workflows/WorkspaceWorkflowsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {hasSeenTourSelector} from '@selectors/Onboarding';
import {Str} from 'expensify-common';
import React, {useCallback, useEffect, useMemo} from 'react';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
// eslint-disable-next-line no-restricted-imports
import {InteractionManager, View} from 'react-native';
import type {TupleToUnion} from 'type-fest';
Expand All @@ -13,6 +13,7 @@ import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import {ModalActions} from '@components/Modal/Global/ModalContext';
import OfflineWithFeedback from '@components/OfflineWithFeedback';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import RenderHTML from '@components/RenderHTML';
import SearchBar from '@components/SearchBar';
import Section from '@components/Section';
Expand Down Expand Up @@ -70,6 +71,7 @@ import ExpenseReportRulesSection from '@pages/workspace/rules/ExpenseReportRules
import type {WithPolicyProps} from '@pages/workspace/withPolicy';
import withPolicy from '@pages/workspace/withPolicy';
import WorkspacePageWithSections from '@pages/workspace/WorkspacePageWithSections';
import variables from '@styles/variables';
import {pressLockedBankAccount} from '@userActions/BankAccounts';
import {getPaymentMethods} from '@userActions/PaymentMethods';
import {navigateToBankAccountRoute} from '@userActions/ReimbursementAccount';
Expand All @@ -79,6 +81,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type ApprovalWorkflow from '@src/types/onyx/ApprovalWorkflow';
import type IconAsset from '@src/types/utils/IconAsset';
import type {ToggleSettingOptionRowProps} from './ToggleSettingsOptionRow';
import ToggleSettingOptionRow from './ToggleSettingsOptionRow';
import {getAutoReportingFrequencyDisplayNames} from './WorkspaceAutoReportingFrequencyPage';
Expand Down Expand Up @@ -107,13 +110,43 @@ function WorkflowNoResultsView({message, shouldShow, searchValue}: {message: str
);
}

// Bordered "Load more" card matching the workflow rows: the whole card is the tap target and gets the row-hover state (per #91727 design).
function WorkflowsLoadMoreCard({count, icon, onPress}: {count: number; icon: IconAsset; onPress: () => void}) {
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const {shouldUseNarrowLayout} = useResponsiveLayout();
const label = translate('workflowsPage.loadMoreWorkflows', {count});

return (
<PressableWithFeedback
accessibilityLabel={label}
role={CONST.ROLE.BUTTON}
onPress={onPress}
sentryLabel={CONST.SENTRY_LABEL.WORKSPACE.WORKFLOWS.LOAD_MORE_APPROVALS}
hoverStyle={styles.hoveredComponentBG}
style={[styles.border, shouldUseNarrowLayout ? styles.ph3 : styles.ph4, styles.pv3, styles.mt6, styles.mbn3, styles.alignItemsCenter, styles.justifyContentCenter]}
>
<View style={[styles.flexRow, styles.alignItemsCenter, styles.justifyContentCenter, {minHeight: variables.componentSizeSmall}]}>
<Icon
src={icon}
fill={theme.textSupporting}
extraSmall
additionalStyles={styles.mr1}
/>
<Text style={[styles.buttonSmallText, {color: theme.textSupporting}]}>{label}</Text>
</View>
</PressableWithFeedback>
);
}

function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
useWorkspaceDocumentTitle(policy?.name, 'workspace.common.workflows');
const {translate, localeCompare} = useLocalize();
const styles = useThemeStyles();
const theme = useTheme();
const illustrations = useMemoizedLazyIllustrations(['Workflows']);
const expensifyIcons = useMemoizedLazyExpensifyIcons(['DotIndicator', 'Info', 'Plus']);
const expensifyIcons = useMemoizedLazyExpensifyIcons(['CircularArrowBackwards', 'DotIndicator', 'Info', 'Plus']);
// We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to apply a correct padding style
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
Expand Down Expand Up @@ -294,6 +327,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
};

const [workflowSearchInput, setWorkflowSearchInput, searchFilteredWorkflows] = useSearchResults(filteredApprovalWorkflows, filterWorkflow);
const [isWorkflowListExpanded, setIsWorkflowListExpanded] = useState(false);

useEffect(() => {
if (filteredApprovalWorkflows.length > CONST.SEARCH_BAR_THRESHOLD) {
Expand All @@ -302,6 +336,11 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
setWorkflowSearchInput('');
}, [filteredApprovalWorkflows.length, setWorkflowSearchInput]);

// Searching reveals every match, so pagination is bypassed while a query is active. Pressing "Load more" reveals all remaining workflows at once.
const isSearchingWorkflows = workflowSearchInput.length > 0;
const displayedWorkflows = isWorkflowListExpanded || isSearchingWorkflows ? searchFilteredWorkflows : searchFilteredWorkflows.slice(0, CONST.WORKFLOW_APPROVALS_INITIAL_BATCH);
const hiddenWorkflowsCount = searchFilteredWorkflows.length - displayedWorkflows.length;

const isDEWEnabled = hasDynamicExternalWorkflow(policy);
const isHRConnected = isAnyHRConnected(policy);
const shouldBlockApprovalWorkflowEditing = isAnyHRReadOnlyWorkflowMode(policy);
Expand Down Expand Up @@ -473,7 +512,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
shouldShow={searchFilteredWorkflows.length === 0 && workflowSearchInput.length > 0}
searchValue={workflowSearchInput}
/>
{searchFilteredWorkflows.map((workflow) => {
{displayedWorkflows.map((workflow) => {
const firstApproverEmail = workflow.approvers.at(0)?.email ?? '';

return (
Expand Down Expand Up @@ -514,6 +553,13 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
</OfflineWithFeedback>
);
})}
{hiddenWorkflowsCount > 0 && (
<WorkflowsLoadMoreCard
count={hiddenWorkflowsCount}
icon={expensifyIcons.CircularArrowBackwards}
onPress={() => setIsWorkflowListExpanded(true)}
/>
)}
{!shouldBlockApprovalWorkflowEditing && canWriteApprovals && (
<MenuItem
title={translate('workflowsPage.addApprovalButton')}
Expand Down Expand Up @@ -742,6 +788,7 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
promptConfigureApprovalsInHR,
isDEWEnabled,
shouldUseNarrowLayout,
expensifyIcons.CircularArrowBackwards,
expensifyIcons.Info,
expensifyIcons.Plus,
expensifyIcons.DotIndicator,
Expand All @@ -750,7 +797,9 @@ function WorkspaceWorkflowsPage({policy, route}: WorkspaceWorkflowsPageProps) {
filteredApprovalWorkflows.length,
workflowSearchInput,
setWorkflowSearchInput,
searchFilteredWorkflows,
searchFilteredWorkflows.length,
displayedWorkflows,
hiddenWorkflowsCount,
addApprovalAction,
isOffline,
isPolicyAdmin,
Expand Down
Loading
Loading