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 delete option to deletable report fields #36039

Merged
merged 24 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions src/components/ReportActionItem/MoneyReportView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ 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 * as reportActions from '@src/libs/actions/Report';
import ROUTES from '@src/ROUTES';
import type {Policy, PolicyReportField, Report} from '@src/types/onyx';

Expand Down Expand Up @@ -81,6 +82,7 @@ function MoneyReportView({report, policy, shouldShowHorizontalRule}: MoneyReport
errors={report.errorFields?.[fieldKey]}
errorRowStyles={styles.ph5}
key={`menuItem-${fieldKey}`}
onClose={() => reportActions.clearReportFieldErrors(report.reportID, reportField)}
>
<MenuItemWithTopDescription
description={Str.UCFirst(reportField.name)}
Expand Down
4 changes: 4 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,10 @@ export default {
subtitle: 'Sync your chart of accounts and more.',
},
},
reportFields: {
delete: 'Delete field',
deleteConfirmation: 'Are you sure that you want to delete this field?',
Copy link
Contributor

Choose a reason for hiding this comment

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

@allroundexperts Just double checking is this copy & translation all approved?

},
tags: {
tagName: 'Tag name',
requiresTag: 'Members must tag all spend',
Expand Down
4 changes: 4 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1918,6 +1918,10 @@ export default {
subtitle: 'Sincroniza tu plan de cuentas y otras opciones.',
},
},
reportFields: {
delete: 'Eliminar campos',
deleteConfirmation: '¿Estás seguro de que quieres eliminar esta campos?',
},
tags: {
tagName: 'Nombre de etiqueta',
requiresTag: 'Los miembros deben etiquetar todos los gastos',
Expand Down
5 changes: 5 additions & 0 deletions src/libs/API/parameters/DeleteReportFieldParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type DeleteReportFieldParams = {
fieldID: string;
};

export default DeleteReportFieldParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ export type {default as CompleteEngagementModalParams} from './CompleteEngagemen
export type {default as SetNameValuePairParams} from './SetNameValuePairParams';
export type {default as SetReportFieldParams} from './SetReportFieldParams';
export type {default as SetReportNameParams} from './SetReportNameParams';
export type {default as DeleteReportFieldParams} from './DeleteReportFieldParams';
export type {default as CompleteSplitBillParams} from './CompleteSplitBillParams';
export type {default as UpdateMoneyRequestParams} from './UpdateMoneyRequestParams';
export type {default as RequestMoneyParams} from './RequestMoneyParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ const WRITE_COMMANDS = {
COMPLETE_ENGAGEMENT_MODAL: 'CompleteEngagementModal',
SET_NAME_VALUE_PAIR: 'SetNameValuePair',
SET_REPORT_FIELD: 'Report_SetFields',
DELETE_REPORT_FIELD: 'RemoveReportField',
SET_REPORT_NAME: 'RenameReport',
COMPLETE_SPLIT_BILL: 'CompleteSplitBill',
UPDATE_MONEY_REQUEST_DATE: 'UpdateMoneyRequestDate',
Expand Down Expand Up @@ -324,6 +325,7 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams;
[WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams;
[WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams;
[WRITE_COMMANDS.DELETE_REPORT_FIELD]: Parameters.DeleteReportFieldParams;
[WRITE_COMMANDS.COMPLETE_SPLIT_BILL]: Parameters.CompleteSplitBillParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_DATE]: Parameters.UpdateMoneyRequestParams;
[WRITE_COMMANDS.UPDATE_MONEY_REQUEST_MERCHANT]: Parameters.UpdateMoneyRequestParams;
Expand Down
102 changes: 102 additions & 0 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ type GetOptionsConfig = {
includeSelectedOptions?: boolean;
includeTaxRates?: boolean;
taxRates?: TaxRatesWithDefault;
includePolicyReportFieldOptions?: boolean;
policyReportFieldOptions?: string[];
recentlyUsedPolicyReportFieldOptions?: string[];
transactionViolations?: OnyxCollection<TransactionViolation[]>;
};

Expand Down Expand Up @@ -168,6 +171,7 @@ type GetOptions = {
categoryOptions: CategoryTreeSection[];
tagOptions: CategorySection[];
taxRatesOptions: CategorySection[];
policyReportFieldOptions?: CategorySection[] | null;
};

type PreviewConfig = {showChatPreviewLine?: boolean; forcePolicyNamePreview?: boolean; showPersonalDetails?: boolean};
Expand Down Expand Up @@ -1216,6 +1220,81 @@ function hasEnabledTags(policyTagList: Array<PolicyTagList[keyof PolicyTagList]>
return hasEnabledOptions(policyTagValueList);
}

/**
* Transforms the provided report field options into option objects.
*
* @param reportFieldOptions - an initial report field options array
*/
function getReportFieldOptions(reportFieldOptions: string[]): Option[] {
return reportFieldOptions.map((name) => ({
text: name,
keyForList: name,
searchText: name,
tooltipText: name,
isDisabled: false,
}));
}

/**
* Build the section list for report field options
*/
function getReportFieldOptionsSection(options: string[], recentlyUsedOptions: string[], selectedOptions: Array<Partial<ReportUtils.OptionData>>, searchInputValue: string) {
const reportFieldOptionsSections = [];
const selectedOptionKeys = selectedOptions.map(({text, keyForList, name}) => text ?? keyForList ?? name ?? '').filter((o) => !!o);
let indexOffset = 0;

if (searchInputValue) {
const searchOptions = options.filter((option) => option.toLowerCase().includes(searchInputValue.toLowerCase()));

reportFieldOptionsSections.push({
// "Search" section
title: '',
shouldShow: true,
indexOffset,
data: getReportFieldOptions(searchOptions),
});

return reportFieldOptionsSections;
}

const filteredRecentlyUsedOptions = recentlyUsedOptions.filter((recentlyUsedOption) => !selectedOptionKeys.includes(recentlyUsedOption));
const filteredOptions = options.filter((option) => !selectedOptionKeys.includes(option));

if (selectedOptionKeys.length) {
reportFieldOptionsSections.push({
// "Selected" section
title: '',
shouldShow: true,
indexOffset,
data: getReportFieldOptions(selectedOptionKeys),
});

indexOffset += selectedOptionKeys.length;
}

if (filteredRecentlyUsedOptions.length > 0) {
reportFieldOptionsSections.push({
// "Recent" section
title: Localize.translateLocal('common.recent'),
shouldShow: true,
indexOffset,
data: getReportFieldOptions(filteredRecentlyUsedOptions),
});

indexOffset += filteredRecentlyUsedOptions.length;
}

reportFieldOptionsSections.push({
// "All" section when items amount more than the threshold
title: Localize.translateLocal('common.all'),
shouldShow: true,
indexOffset,
data: getReportFieldOptions(filteredOptions),
});

return reportFieldOptionsSections;
}

/**
* Transforms tax rates to a new object format - to add codes and new name with concatenated name and value.
*
Expand Down Expand Up @@ -1441,6 +1520,9 @@ function getOptions(
includeTaxRates,
taxRates,
includeSelfDM = false,
includePolicyReportFieldOptions = false,
policyReportFieldOptions = [],
recentlyUsedPolicyReportFieldOptions = [],
}: GetOptionsConfig,
): GetOptions {
if (includeCategories) {
Expand Down Expand Up @@ -1485,6 +1567,20 @@ function getOptions(
};
}

if (includePolicyReportFieldOptions) {
const transformedPolicyReportFieldOptions = getReportFieldOptionsSection(policyReportFieldOptions, recentlyUsedPolicyReportFieldOptions, selectedOptions, searchInputValue);
return {
recentReports: [],
personalDetails: [],
userToInvite: null,
currentUserOption: null,
categoryOptions: [],
tagOptions: [],
taxRatesOptions: [],
policyReportFieldOptions: transformedPolicyReportFieldOptions,
};
}

const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchInputValue)));
const searchValue = parsedPhoneNumber.possible ? parsedPhoneNumber.number?.e164 : searchInputValue.toLowerCase();
const topmostReportId = Navigation.getTopmostReportId() ?? '';
Expand Down Expand Up @@ -1868,6 +1964,9 @@ function getFilteredOptions(
includeTaxRates = false,
taxRates: TaxRatesWithDefault = {} as TaxRatesWithDefault,
includeSelfDM = false,
includePolicyReportFieldOptions = false,
policyReportFieldOptions: string[] = [],
recentlyUsedPolicyReportFieldOptions: string[] = [],
) {
return getOptions(
{reports, personalDetails},
Expand All @@ -1892,6 +1991,9 @@ function getFilteredOptions(
includeTaxRates,
taxRates,
includeSelfDM,
includePolicyReportFieldOptions,
policyReportFieldOptions,
recentlyUsedPolicyReportFieldOptions,
},
);
}
Expand Down
73 changes: 73 additions & 0 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,18 @@ function updateReportName(reportID: string, value: string, previousValue: string
API.write(WRITE_COMMANDS.SET_REPORT_NAME, parameters, {optimisticData, failureData, successData});
}

function clearReportFieldErrors(reportID: string, reportField: PolicyReportField) {
const fieldKey = ReportUtils.getReportFieldKey(reportField.fieldID);
Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
pendingFields: {
[fieldKey]: null,
},
errorFields: {
[fieldKey]: null,
},
});
}

function updateReportField(reportID: string, reportField: PolicyReportField, previousReportField: PolicyReportField) {
const fieldKey = ReportUtils.getReportFieldKey(reportField.fieldID);
const recentlyUsedValues = allRecentlyUsedReportFields?.[fieldKey] ?? [];
Expand Down Expand Up @@ -1695,6 +1707,65 @@ function updateReportField(reportID: string, reportField: PolicyReportField, pre
API.write(WRITE_COMMANDS.SET_REPORT_FIELD, parameters, {optimisticData, failureData, successData});
}

function deleteReportField(reportID: string, reportField: PolicyReportField) {
const fieldKey = ReportUtils.getReportFieldKey(reportField.fieldID);

const optimisticData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
fieldList: {
[fieldKey]: null,
},
pendingFields: {
[fieldKey]: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
},
},
},
];

const failureData: OnyxUpdate[] = [
{
onyxMethod: Onyx.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
value: {
fieldList: {
[fieldKey]: reportField,
},
pendingFields: {
[fieldKey]: null,
},
errorFields: {
[fieldKey]: ErrorUtils.getMicroSecondOnyxError('report.genericUpdateReportFieldFailureMessage'),
},
},
},
];

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

const parameters = {
reportID,
fieldID: fieldKey,
};

API.write(WRITE_COMMANDS.DELETE_REPORT_FIELD, parameters, {optimisticData, failureData, successData});
}

function updateDescription(reportID: string, previousValue: string, newValue: string) {
// No change needed, navigate back
if (previousValue === newValue) {
Expand Down Expand Up @@ -3040,6 +3111,8 @@ export {
clearNewRoomFormError,
updateReportField,
updateReportName,
deleteReportField,
clearReportFieldErrors,
resolveActionableMentionWhisper,
updateRoomVisibility,
setGroupDraft,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import DatePicker from '@components/DatePicker';
import FormProvider from '@components/Form/FormProvider';
import InputWrapper from '@components/Form/InputWrapper';
import type {FormInputErrors, FormOnyxValues} from '@components/Form/types';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import type {AnimatedTextInputRef} from '@components/RNTextInput';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import CONST from '@src/CONST';
Expand Down Expand Up @@ -46,40 +44,29 @@ function EditReportFieldDatePage({fieldName, isRequired, onSubmit, fieldValue, f
);

return (
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
shouldEnableMaxHeight
onEntryTransitionEnd={() => {
inputRef.current?.focus();
}}
testID={EditReportFieldDatePage.displayName}
<FormProvider
style={[styles.flexGrow1, styles.ph5]}
formID={ONYXKEYS.FORMS.REPORT_FIELD_EDIT_FORM}
onSubmit={onSubmit}
validate={validate}
submitButtonText={translate('common.save')}
enabledWhenOffline
>
<HeaderWithBackButton title={fieldName} />
<FormProvider
style={[styles.flexGrow1, styles.ph5]}
formID={ONYXKEYS.FORMS.REPORT_FIELD_EDIT_FORM}
onSubmit={onSubmit}
validate={validate}
submitButtonText={translate('common.save')}
enabledWhenOffline
>
<View style={styles.mb4}>
{/* @ts-expect-error TODO: Remove this once DatePicker (https://github.com/Expensify/App/issues/25148) is migrated to TypeScript. */}
<InputWrapper<unknown>
InputComponent={DatePicker}
inputID={fieldKey}
name={fieldKey}
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>
<View style={styles.mb4}>
<InputWrapper
InputComponent={DatePicker}
inputID={fieldKey}
name={fieldKey}
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>
);
}

Expand Down
Loading
Loading