Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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