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

feat: Add report field picker components #34157

Merged
merged 14 commits into from
Jan 23, 2024
Merged
6 changes: 5 additions & 1 deletion src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,8 @@ const ONYXKEYS = {
REPORT_VIRTUAL_CARD_FRAUD_DRAFT: 'reportVirtualCardFraudFormDraft',
GET_PHYSICAL_CARD_FORM: 'getPhysicalCardForm',
GET_PHYSICAL_CARD_FORM_DRAFT: 'getPhysicalCardFormDraft',
POLICY_REPORT_FIELD_EDIT_FORM: 'policyReportFieldEditForm',
POLICY_REPORT_FIELD_EDIT_FORM_DRAFT: 'policyReportFieldEditFormDraft',
},
} as const;

Expand Down Expand Up @@ -442,7 +444,7 @@ type OnyxValues = {
[ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers;
[ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories;
[ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS]: OnyxTypes.PolicyReportField;
[ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS]: OnyxTypes.PolicyReportFields;
[ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields;
[ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers;
[ONYXKEYS.COLLECTION.WORKSPACE_INVITE_MEMBERS_DRAFT]: Record<string, number>;
Expand Down Expand Up @@ -527,6 +529,8 @@ type OnyxValues = {
[ONYXKEYS.FORMS.REPORT_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form;
[ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.GET_PHYSICAL_CARD_FORM_DRAFT]: OnyxTypes.Form | undefined;
[ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM]: OnyxTypes.Form;
[ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM_DRAFT]: OnyxTypes.Form | undefined;
};

type OnyxKeyValue<TOnyxKey extends (OnyxKey | OnyxCollectionKey) & keyof OnyxValues> = OnyxEntry<OnyxValues[TOnyxKey]>;
Expand Down
4 changes: 4 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ const ROUTES = {
route: 'r/:threadReportID/edit/currency',
getRoute: (threadReportID: string, currency: string, backTo: string) => `r/${threadReportID}/edit/currency?currency=${currency}&backTo=${backTo}` as const,
},
EDIT_REPORT_FIELD_REQUEST: {
route: 'r/:reportID/edit/policyField/:policyID/:fieldID',
getRoute: (reportID: string, policyID: string, fieldID: string) => `r/${reportID}/edit/policyField/${policyID}/${fieldID}` as const,
},
REPORT_WITH_ID_DETAILS_SHARE_CODE: {
route: 'r/:reportID/details/shareCode',
getRoute: (reportID: string) => `r/${reportID}/details/shareCode` 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 @@ -208,6 +208,7 @@ const SCREENS = {
EDIT_REQUEST: {
ROOT: 'EditRequest_Root',
CURRENCY: 'EditRequest_Currency',
REPORT_FIELD: 'EditRequest_ReportField',
},

NEW_CHAT: {
Expand Down
4 changes: 3 additions & 1 deletion src/components/ReportActionItem/MoneyReportView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground';
import variables from '@styles/variables';
import ROUTES from '@src/ROUTES';
import type {PolicyReportField, Report} from '@src/types/onyx';

type MoneyReportViewProps = {
Expand Down Expand Up @@ -73,7 +75,7 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}:
<MenuItemWithTopDescription
description={reportField.name}
title={title}
onPress={() => {}}
onPress={() => Navigation.navigate(ROUTES.EDIT_REPORT_FIELD_REQUEST.getRoute(report.reportID, report.policyID ?? '', reportField.fieldID))}
shouldShowRightIcon
disabled={false}
wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ const FlagCommentStackNavigator = createModalStackNavigator<FlagCommentNavigator
const EditRequestStackNavigator = createModalStackNavigator<EditRequestNavigatorParamList>({
[SCREENS.EDIT_REQUEST.ROOT]: () => require('../../../pages/EditRequestPage').default as React.ComponentType,
[SCREENS.EDIT_REQUEST.CURRENCY]: () => require('../../../pages/iou/IOUCurrencySelection').default as React.ComponentType,
[SCREENS.EDIT_REQUEST.REPORT_FIELD]: () => require('../../../pages/EditReportFieldPage').default as React.ComponentType,
});

const PrivateNotesModalStackNavigator = createModalStackNavigator<PrivateNotesNavigatorParamList>({
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/linkingConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ const linkingConfig: LinkingOptions<RootStackParamList> = {
screens: {
[SCREENS.EDIT_REQUEST.ROOT]: ROUTES.EDIT_REQUEST.route,
[SCREENS.EDIT_REQUEST.CURRENCY]: ROUTES.EDIT_CURRENCY_REQUEST.route,
[SCREENS.EDIT_REQUEST.REPORT_FIELD]: ROUTES.EDIT_REPORT_FIELD_REQUEST.route,
},
},
[SCREENS.RIGHT_MODAL.SIGN_IN]: {
Expand Down
82 changes: 82 additions & 0 deletions src/pages/EditReportFieldDatePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, {useCallback, useRef} from 'react';
import {View} from 'react-native';
import DatePicker from '@components/DatePicker';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';

type EditReportFieldDatePageProps = {
/** Value of the policy report field */
fieldValue: string;

/** Name of the policy report field */
fieldName: string;

/** ID of the policy report field */
fieldID: string;

/** Callback to fire when the Save button is pressed */
onSubmit: () => void;
};

function EditReportFieldDatePage({fieldName, onSubmit, fieldValue, fieldID}: EditReportFieldDatePageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const inputRef = useRef<HTMLInputElement>(null);

const validate = useCallback(
(value: Record<string, string>) => {
const errors: Record<string, string> = {};
if (value[fieldID].trim() === '') {
errors[fieldID] = 'common.error.fieldRequired';
}
return errors;
},
[fieldID],
);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
onEntryTransitionEnd={() => inputRef.current?.focus()}
testID={EditReportFieldDatePage.displayName}
>
<HeaderWithBackButton title={fieldName} />
{/* @ts-expect-error TODO: TS migration */}
<FormProvider
style={[styles.flexGrow1, styles.ph5]}
formID={ONYXKEYS.FORMS.POLICY_REPORT_FIELD_EDIT_FORM}
onSubmit={onSubmit}
validate={validate}
submitButtonText={translate('common.save')}
enabledWhenOffline
>
<View style={styles.mb4}>
<InputWrapper
// @ts-expect-error TODO: TS migration
InputComponent={DatePicker}
inputID={fieldID}
name={fieldID}
defaultValue={fieldValue}
label={fieldName}
accessibilityLabel={fieldName}
role={CONST.ROLE.PRESENTATION}
maxDate={CONST.CALENDAR_PICKER.MAX_DATE}
minDate={CONST.CALENDAR_PICKER.MIN_DATE}
ref={inputRef}
/>
</View>
</FormProvider>
</ScreenWrapper>
);
}

EditReportFieldDatePage.displayName = 'EditReportFieldDatePage';

export default EditReportFieldDatePage;
82 changes: 82 additions & 0 deletions src/pages/EditReportFieldDropdownPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import React, {useMemo, useState} from 'react';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import OptionsSelector from '@components/OptionsSelector';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';

type EditReportFieldDropdownPageProps = {
/** Value of the policy report field */
fieldValue: string;

/** Name of the policy report field */
fieldName: string;

/** Options of the policy report field */
fieldOptions: string[];

/** Callback to fire when the Save button is pressed */
onSubmit: () => void;
};

function EditReportFieldDropdownPage({fieldName, onSubmit, fieldValue, fieldOptions}: EditReportFieldDropdownPageProps) {
const [searchValue, setSearchValue] = useState('');
const styles = useThemeStyles();
const {getSafeAreaMargins} = useStyleUtils();
const {translate} = useLocalize();

const sections = useMemo(() => {
const filteredOptions = fieldOptions.filter((option) => option.toLowerCase().includes(searchValue.toLowerCase()));
return [
{
title: translate('common.recents'),
shouldShow: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should have shown this only if recent options were greater than 0. This later caused #35832

data: [],
},
{
title: translate('common.all'),
shouldShow: true,
data: filteredOptions.map((option) => ({
text: option,
keyForList: option,
searchText: option,
tooltipText: option,
})),
},
];
}, [fieldOptions, searchValue, translate]);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
testID={EditReportFieldDropdownPage.displayName}
>
{({insets}) => (
<>
<HeaderWithBackButton title={fieldName} />
<OptionsSelector
// @ts-expect-error TODO: TS migration
contentContainerStyles={[{paddingBottom: getSafeAreaMargins(insets).marginBottom}]}
optionHoveredStyle={styles.hoveredComponentBG}
sectionHeaderStyle={styles.mt5}
selectedOptions={[{name: fieldValue}]}
textInputLabel={translate('common.search')}
boldStyle
sections={sections}
value={searchValue}
onSelectRow={onSubmit}
onChangeText={setSearchValue}
highlightSelectedOptions
isRowMultilineSupported
/>
</>
)}
</ScreenWrapper>
);
}

EditReportFieldDropdownPage.displayName = 'EditReportFieldDropdownPage';

export default EditReportFieldDropdownPage;
103 changes: 103 additions & 0 deletions src/pages/EditReportFieldPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, {useEffect} from 'react';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView';
import ScreenWrapper from '@components/ScreenWrapper';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PolicyReportFields, Report} from '@src/types/onyx';
import EditReportFieldDatePage from './EditReportFieldDatePage';
import EditReportFieldDropdownPage from './EditReportFieldDropdownPage';
import EditReportFieldTextPage from './EditReportFieldTextPage';

type EditReportFieldPageOnyxProps = {
/** The report object for the expense report */
report: OnyxEntry<Report>;

/** Policy report fields */
policyReportFields: OnyxEntry<PolicyReportFields>;
};

type EditReportFieldPageProps = EditReportFieldPageOnyxProps & {
/** Route from navigation */
route: {
/** Params from the route */
params: {
/** Which field we are editing */
fieldID: string;

/** reportID for the expense report */
reportID: string;

/** policyID for the expense report */
policyID: string;
};
};
};

function EditReportFieldPage({route, report, policyReportFields}: EditReportFieldPageProps) {
const policyReportField = policyReportFields?.[route.params.fieldID];
const reportFieldValue = report?.reportFields?.[policyReportField?.fieldID ?? ''];

// Decides whether to allow or disallow editing a money request
useEffect(() => {}, []);

if (policyReportField) {
if (policyReportField.type === 'text' || policyReportField.type === 'formula') {
return (
<EditReportFieldTextPage
fieldName={policyReportField.name}
fieldID={policyReportField.fieldID}
fieldValue={reportFieldValue ?? policyReportField.defaultValue}
onSubmit={() => {}}
/>
);
}

if (policyReportField.type === 'date') {
return (
<EditReportFieldDatePage
fieldName={policyReportField.name}
fieldID={policyReportField.fieldID}
fieldValue={reportFieldValue ?? policyReportField.defaultValue}
onSubmit={() => {}}
/>
);
}

if (policyReportField.type === 'dropdown') {
return (
<EditReportFieldDropdownPage
fieldName={policyReportField.name}
fieldValue={reportFieldValue ?? policyReportField.defaultValue}
fieldOptions={policyReportField.values}
onSubmit={() => {}}
/>
);
}
}

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
testID={EditReportFieldPage.displayName}
>
<FullPageNotFoundView
shouldShow
onBackButtonPress={() => {}}
onLinkPress={() => {}}
/>
</ScreenWrapper>
);
}

EditReportFieldPage.displayName = 'EditReportFieldPage';

export default withOnyx<EditReportFieldPageProps, EditReportFieldPageOnyxProps>({
report: {
key: ({route}) => `${ONYXKEYS.COLLECTION.REPORT}${route.params.reportID}`,
},
policyReportFields: {
key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_REPORT_FIELDS}${route.params.policyID}`,
},
})(EditReportFieldPage);
Loading
Loading