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: Integrate report fields with backend #34483

Merged
merged 19 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 16 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
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 @@ -528,6 +530,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
10 changes: 7 additions & 3 deletions 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 @@ -53,8 +55,8 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}:
StyleUtils.getColorStyle(theme.textSupporting),
];

const sortedPolicyReportFields = useMemo(
() => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight),
const sortedPolicyReportFields = useMemo<PolicyReportField[]>(
(): PolicyReportField[] => policyReportFields.sort(({orderWeight: firstOrderWeight}, {orderWeight: secondOrderWeight}) => firstOrderWeight - secondOrderWeight),
[policyReportFields],
);

Expand All @@ -68,12 +70,14 @@ function MoneyReportView({report, policyReportFields, shouldShowHorizontalRule}:
return (
<OfflineWithFeedback
pendingAction={report.pendingFields?.[reportField.fieldID]}
errors={report.errorFields?.[reportField.fieldID]}
errorRowStyles={styles.ph5}
key={`menuItem-${reportField.fieldID}`}
>
<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
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1880,6 +1880,7 @@ export default {
report: {
genericCreateReportFailureMessage: 'Unexpected error creating this chat, please try again later',
genericAddCommentFailureMessage: 'Unexpected error while posting the comment, please try again later',
genericUpdateReportFieldFailureMessage: 'Unexpected error while updating the field, please try again later',
noActivityYet: 'No activity yet',
},
chronos: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1907,6 +1907,7 @@ export default {
report: {
genericCreateReportFailureMessage: 'Error inesperado al crear el chat. Por favor, inténtalo más tarde',
genericAddCommentFailureMessage: 'Error inesperado al añadir el comentario. Por favor, inténtalo más tarde',
genericUpdateReportFieldFailureMessage: 'Error inesperado al actualizar el campo. Por favor, inténtalo más tarde',
noActivityYet: 'Sin actividad todavía',
},
chronos: {
Expand Down
1 change: 1 addition & 0 deletions src/libs/Navigation/AppNavigator/ModalStackNavigators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,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
61 changes: 60 additions & 1 deletion src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import ROUTES from '@src/ROUTES';
import type {PersonalDetails, PersonalDetailsList, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx';
import type {PersonalDetails, PersonalDetailsList, PolicyReportField, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx';
import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage';
import type {NotificationPreference, WriteCapability} from '@src/types/onyx/Report';
import type Report from '@src/types/onyx/Report';
Expand Down Expand Up @@ -1498,6 +1498,64 @@ function toggleSubscribeToChildReport(childReportID = '0', parentReportAction: P
}
}

function updatePolicyReportField(reportID: string, policyField: PolicyReportField, fieldValue: string) {
const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
reportFields: {
[policyField.fieldID]: fieldValue,
},
pendingFields: {
[policyField.fieldID]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
];
const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
pendingFields: {
[policyField.fieldID]: null,
},
errorFields: {
[policyField.fieldID]: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReportFieldFailureMessage'),
},
},
},
];

const successData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
pendingFields: {
[policyField.fieldID]: null,
},
errorFields: {
[policyField.fieldID]: null,
},
},
},
];

type UpdateReportFieldParameters = {
reportID: string;
reportFields: string;
};

const parameters: UpdateReportFieldParameters = {
reportID,
reportFields: JSON.stringify({[policyField.fieldID]: {fieldID: policyField.fieldID, value: fieldValue, type: policyField.type, name: policyField.name}}),
};
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we include all values from the policyField at this time? When OpenReport passes back the reports, it will have a reportField list that contains the reportFields at the time of creation.

So if the weights change later, this report should have the same report fields weights at the time of storage for historical context


API.write('Report_SetFields', parameters, {optimisticData, failureData, successData});
}

function updateWelcomeMessage(reportID: string, previousValue: string, newValue: string) {
// No change needed, navigate back
if (previousValue === newValue) {
Expand Down Expand Up @@ -2714,5 +2772,6 @@ export {
getDraftPrivateNote,
updateLastVisitTime,
clearNewRoomFormError,
updatePolicyReportField,
resolveActionableMentionWhisper,
};
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: (form: Record<string, string>) => 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;
85 changes: 85 additions & 0 deletions src/pages/EditReportFieldDropdownPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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;

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

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

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

function EditReportFieldDropdownPage({fieldName, onSubmit, fieldID, 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.includes(searchValue));
return [
{
title: translate('common.recents'),
shouldShow: true,
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={[{text: fieldValue}]}
textInputLabel={translate('common.search')}
boldStyle
sections={sections}
value={searchValue}
onSelectRow={(option: Record<string, string>) => onSubmit({[fieldID]: option.text})}
onChangeText={setSearchValue}
highlightSelectedOptions
isRowMultilineSupported
/>
</>
)}
</ScreenWrapper>
);
}

EditReportFieldDropdownPage.displayName = 'EditReportFieldDropdownPage';

export default EditReportFieldDropdownPage;
Loading
Loading