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

Feature: Create the main WorkspaceReportFieldsPage page #43943

Merged
merged 8 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,10 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/tax/:taxID/value',
getRoute: (policyID: string, taxID: string) => `settings/workspaces/${policyID}/tax/${encodeURIComponent(taxID)}/value` as const,
},
WORKSPACE_REPORT_FIELDS: {
route: 'settings/workspaces/:policyID/reportFields',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/reportFields` as const,
},
WORKSPACE_DISTANCE_RATES: {
route: 'settings/workspaces/:policyID/distance-rates',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/distance-rates` as const,
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ const SCREENS = {
TAGS_EDIT: 'Tags_Edit',
TAG_EDIT: 'Tag_Edit',
TAXES: 'Workspace_Taxes',
REPORT_FIELDS: 'Workspace_ReportFields',
TAX_EDIT: 'Workspace_Tax_Edit',
TAX_NAME: 'Workspace_Tax_Name',
TAX_VALUE: 'Workspace_Tax_Value',
Expand Down
1 change: 1 addition & 0 deletions src/components/FocusTrap/WIDE_LAYOUT_INACTIVE_SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const WIDE_LAYOUT_INACTIVE_SCREENS: string[] = [
SCREENS.WORKSPACE.MORE_FEATURES,
SCREENS.WORKSPACE.TAGS,
SCREENS.WORKSPACE.TAXES,
SCREENS.WORKSPACE.REPORT_FIELDS,
SCREENS.WORKSPACE.DISTANCE_RATES,
SCREENS.SEARCH.CENTRAL_PANE,
];
Expand Down
6 changes: 6 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2258,8 +2258,14 @@ export default {
},
},
reportFields: {
addField: 'Add field',
delete: 'Delete field',
deleteConfirmation: 'Are you sure that you want to delete this field?',
emptyReportFields: {
title: "You haven't created any report fields",
subtitle: 'Add a custom field (text, date, or dropdown) that appears on reports.',
},
subtitle: "Report fields apply to all spend and can be helpful when you'd like to prompt for extra information",
disableReportFields: 'Disable report fields',
disableReportFieldsConfirmation: 'Are you sure? Text and date fields will be deleted, and lists will be disabled.',
},
Expand Down
6 changes: 6 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2294,8 +2294,14 @@ export default {
},
},
reportFields: {
addField: 'Añadir campo',
delete: 'Eliminar campos',
deleteConfirmation: '¿Estás seguro de que quieres eliminar esta campos?',
emptyReportFields: {
title: 'No has creado ningún campo de informe',
subtitle: 'Añade un campo personalizado (texto, fecha o desplegable) que aparezca en los informes.',
},
subtitle: 'Los campos de informe se aplican a todos los gastos y pueden ser útiles cuando desees solicitar información adicional',
disableReportFields: 'Desactivar campos de informe',
disableReportFieldsConfirmation: 'Estás seguro? Se eliminarán los campos de texto y fecha y se desactivarán las listas.',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const CENTRAL_PANE_WORKSPACE_SCREENS = {
[SCREENS.WORKSPACE.MORE_FEATURES]: () => require('../../../../pages/workspace/WorkspaceMoreFeaturesPage').default as React.ComponentType,
[SCREENS.WORKSPACE.TAGS]: () => require('../../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.TAXES]: () => require('../../../../pages/workspace/taxes/WorkspaceTaxesPage').default as React.ComponentType,
[SCREENS.WORKSPACE.REPORT_FIELDS]: () => require('../../../../pages/workspace/reportFields/WorkspaceReportFieldsPage').default as React.ComponentType,
[SCREENS.WORKSPACE.DISTANCE_RATES]: () => require('../../../../pages/workspace/distanceRates/PolicyDistanceRatesPage').default as React.ComponentType,
} satisfies Screens;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ const FULL_SCREEN_TO_RHP_MAPPING: Partial<Record<FullScreenName, string[]>> = {
SCREENS.WORKSPACE.DISTANCE_RATE_TAX_RATE_EDIT,
SCREENS.WORKSPACE.DISTANCE_RATE_DETAILS,
],
[SCREENS.WORKSPACE.REPORT_FIELDS]: [],
};

export default FULL_SCREEN_TO_RHP_MAPPING;
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.TAXES]: {
path: ROUTES.WORKSPACE_TAXES.route,
},
[SCREENS.WORKSPACE.REPORT_FIELDS]: {
path: ROUTES.WORKSPACE_REPORT_FIELDS.route,
},
[SCREENS.WORKSPACE.DISTANCE_RATES]: {
path: ROUTES.WORKSPACE_DISTANCE_RATES.route,
},
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,9 @@ type FullScreenNavigatorParamList = {
[SCREENS.WORKSPACE.TAXES]: {
policyID: string;
};
[SCREENS.WORKSPACE.REPORT_FIELDS]: {
policyID: string;
};
[SCREENS.WORKSPACE.DISTANCE_RATES]: {
policyID: string;
};
Expand Down
13 changes: 12 additions & 1 deletion src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ type WorkspaceMenuItem = {
| typeof SCREENS.WORKSPACE.TAXES
| typeof SCREENS.WORKSPACE.MORE_FEATURES
| typeof SCREENS.WORKSPACE.PROFILE
| typeof SCREENS.WORKSPACE.MEMBERS;
| typeof SCREENS.WORKSPACE.MEMBERS
| typeof SCREENS.WORKSPACE.REPORT_FIELDS;
};

type WorkspaceInitialPageOnyxProps = {
Expand Down Expand Up @@ -107,6 +108,7 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc
[CONST.POLICY.MORE_FEATURES.ARE_TAGS_ENABLED]: policy?.areTagsEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_TAXES_ENABLED]: policy?.tax?.trackingEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_CONNECTIONS_ENABLED]: policy?.areConnectionsEnabled,
[CONST.POLICY.MORE_FEATURES.ARE_REPORTFIELDS_ENABLED]: policy?.areReportFieldsEnabled,
}),
[policy],
) as PolicyFeatureStates;
Expand Down Expand Up @@ -269,6 +271,15 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc
});
}

if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_REPORTFIELDS_ENABLED]) {
protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.reportFields',
icon: Expensicons.Pencil,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_REPORT_FIELDS.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.REPORT_FIELDS,
});
}

protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.moreFeatures',
icon: Expensicons.Gear,
Expand Down
171 changes: 171 additions & 0 deletions src/pages/workspace/reportFields/WorkspaceReportFieldsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import {useIsFocused} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import {Str} from 'expensify-common';
import React, {useEffect, useMemo, useState} from 'react';
import {ActivityIndicator, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import * as Expensicons from '@components/Icon/Expensicons';
import * as Illustrations from '@components/Icon/Illustrations';
import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import ListItemRightCaretWithLabel from '@components/SelectionList/ListItemRightCaretWithLabel';
import TableListItem from '@components/SelectionList/TableListItem';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import type {FullScreenNavigatorParamList} from '@libs/Navigation/types';
import * as ReportUtils from '@libs/ReportUtils';
import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import type {PolicyReportField} from '@src/types/onyx/Policy';

type ReportFieldForList = ListItem & {value: string; fieldID: string};

type WorkspaceReportFieldsPageProps = StackScreenProps<FullScreenNavigatorParamList, typeof SCREENS.WORKSPACE.REPORT_FIELDS>;

function WorkspaceReportFieldsPage({
route: {
params: {policyID},
},
}: WorkspaceReportFieldsPageProps) {
const {isSmallScreenWidth} = useWindowDimensions();
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const isFocused = useIsFocused();
const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`);
const filteredPolicyFieldList = useMemo(() => {
if (!policy?.fieldList) {
return {};
}
return Object.fromEntries(Object.entries(policy.fieldList).filter(([key]) => key !== 'text_title'));
}, [policy]);
const [selectedReportFields, setSelectedReportFields] = useState<PolicyReportField[]>([]);

useEffect(() => {
if (isFocused) {
return;
}
setSelectedReportFields([]);
}, [isFocused]);

const reportFieldsList = useMemo<ReportFieldForList[]>(() => {
if (!policy) {
return [];
}
return Object.values(filteredPolicyFieldList).map((reportField) => ({
value: reportField.name,
fieldID: reportField.fieldID,
keyForList: String(reportField.orderWeight),
orderWeight: reportField.orderWeight,
isSelected: selectedReportFields.find((selectedReportField) => selectedReportField.name === reportField.name) !== undefined,
text: reportField.name,
rightElement: <ListItemRightCaretWithLabel labelText={Str.recapitalize(reportField.type)} />,
}));
}, [filteredPolicyFieldList, policy, selectedReportFields]);

const updateSelectedReportFields = (item: ReportFieldForList) => {
const fieldKey = ReportUtils.getReportFieldKey(item.fieldID);
const updatedReportFields = selectedReportFields.find((selectedReportField) => selectedReportField.name === item.value)
? selectedReportFields.filter((selectedReportField) => selectedReportField.name !== item.value)
: [...selectedReportFields, filteredPolicyFieldList[fieldKey]];
setSelectedReportFields(updatedReportFields);
};

const isLoading = reportFieldsList === undefined;
const shouldShowEmptyState = Object.values(filteredPolicyFieldList).length <= 0 && !isLoading;

const getHeaderButtons = () => (
<View style={[styles.w100, styles.flexRow, styles.gap2, isSmallScreenWidth && styles.mb3]}>
<Button
medium
success
onPress={() => {}}
icon={Expensicons.Plus}
text={translate('workspace.reportFields.addField')}
style={[isSmallScreenWidth && styles.flex1]}
/>
</View>
);

const getCustomListHeader = () => (
<View style={[styles.flex1, styles.flexRow, styles.justifyContentBetween, styles.pl3, styles.pr9]}>
<Text style={styles.searchInputStyle}>{translate('common.name')}</Text>
<Text style={[styles.searchInputStyle, styles.textAlignCenter]}>{translate('common.type')}</Text>
</View>
);

const getHeaderText = () => (
<View style={[styles.ph5, styles.pb5, styles.pt3]}>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.reportFields.subtitle')}</Text>
</View>
);

return (
<AccessOrNotFoundWrapper
policyID={policyID}
accessVariants={[CONST.POLICY.ACCESS_VARIANTS.ADMIN, CONST.POLICY.ACCESS_VARIANTS.PAID]}
featureName={CONST.POLICY.MORE_FEATURES.ARE_REPORTFIELDS_ENABLED}
waterim marked this conversation as resolved.
Show resolved Hide resolved
>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={WorkspaceReportFieldsPage.displayName}
shouldShowOfflineIndicatorInWideScreen
offlineIndicatorStyle={styles.mtAuto}
>
<HeaderWithBackButton
icon={Illustrations.Pencil}
title={translate('workspace.common.reportFields')}
shouldShowBackButton={isSmallScreenWidth}
>
{!isSmallScreenWidth && getHeaderButtons()}
</HeaderWithBackButton>
{isSmallScreenWidth && <View style={[styles.pl5, styles.pr5]}>{getHeaderButtons()}</View>}
Comment on lines +119 to +133
Copy link
Contributor

Choose a reason for hiding this comment

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

Why we didn't use the WorkspacePageWithSections component? It is used in almost every workspace-related page.

{(!isSmallScreenWidth || reportFieldsList.length === 0 || isLoading) && getHeaderText()}
{isLoading && (
<ActivityIndicator
size={CONST.ACTIVITY_INDICATOR_SIZE.LARGE}
style={[styles.flex1]}
color={theme.spinner}
/>
)}
{shouldShowEmptyState && (
<WorkspaceEmptyStateSection
title={translate('workspace.reportFields.emptyReportFields.title')}
icon={Illustrations.EmptyStateExpenses}
subtitle={translate('workspace.reportFields.emptyReportFields.subtitle')}
/>
)}
{!shouldShowEmptyState && !isLoading && (
<SelectionList
canSelectMultiple
sections={[{data: reportFieldsList, isDisabled: false}]}
onCheckboxPress={updateSelectedReportFields}
onSelectRow={() => {}}
onSelectAll={() => {}}
ListItem={TableListItem}
customListHeader={getCustomListHeader()}
listHeaderContent={isSmallScreenWidth ? getHeaderText() : null}
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
showScrollIndicator={false}
/>
)}
</ScreenWrapper>
</AccessOrNotFoundWrapper>
);
}

WorkspaceReportFieldsPage.displayName = 'WorkspaceReportFieldsPage';

export default WorkspaceReportFieldsPage;
Loading