Skip to content

Commit

Permalink
Merge pull request #43943 from waterim/feat-43677-report-fields-main-…
Browse files Browse the repository at this point in the history
…page

Feature: Create the main WorkspaceReportFieldsPage page
  • Loading branch information
mountiny authored Jun 24, 2024
2 parents 7eeede6 + 505ea66 commit 5161249
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1730,7 +1730,7 @@ const CONST = {
ARE_TAGS_ENABLED: 'areTagsEnabled',
ARE_DISTANCE_RATES_ENABLED: 'areDistanceRatesEnabled',
ARE_WORKFLOWS_ENABLED: 'areWorkflowsEnabled',
ARE_REPORTFIELDS_ENABLED: 'areReportFieldsEnabled',
ARE_REPORT_FIELDS_ENABLED: 'areReportFieldsEnabled',
ARE_CONNECTIONS_ENABLED: 'areConnectionsEnabled',
ARE_TAXES_ENABLED: 'tax',
},
Expand Down
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 @@ -288,6 +288,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 @@ -2263,8 +2263,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 @@ -2299,8 +2299,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 @@ -793,6 +793,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 @@ -834,6 +834,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 @@ -61,7 +61,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 @@ -105,6 +106,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_REPORT_FIELDS_ENABLED]: policy?.areReportFieldsEnabled,
}),
[policy],
) as PolicyFeatureStates;
Expand Down Expand Up @@ -267,6 +269,15 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, reimbursementAcc
});
}

if (featureStates?.[CONST.POLICY.MORE_FEATURES.ARE_REPORT_FIELDS_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_REPORT_FIELDS_ENABLED}
>
<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>}
{(!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;

0 comments on commit 5161249

Please sign in to comment.