Skip to content

Commit

Permalink
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Browse files Browse the repository at this point in the history
…-fork into Guccio163/47927-ExportCSV
  • Loading branch information
Guccio163 committed Sep 9, 2024
2 parents 9edd3cb + 61ee515 commit dc9cd2d
Show file tree
Hide file tree
Showing 39 changed files with 2,021 additions and 182 deletions.
120 changes: 52 additions & 68 deletions .github/workflows/deploy.yml

Large diffs are not rendered by default.

84 changes: 84 additions & 0 deletions patches/date-fns-tz+2.0.0.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
diff --git a/node_modules/date-fns-tz/_lib/tzTokenizeDate/index.js b/node_modules/date-fns-tz/_lib/tzTokenizeDate/index.js
index 9222a61..8540224 100644
--- a/node_modules/date-fns-tz/_lib/tzTokenizeDate/index.js
+++ b/node_modules/date-fns-tz/_lib/tzTokenizeDate/index.js
@@ -59,20 +59,23 @@ function hackyOffset(dtf, date) {

var dtfCache = {};

+// New browsers use `hourCycle`, IE and Chrome <73 does not support it and uses `hour12`
+const testDateFormatted = new Intl.DateTimeFormat('en-US', {
+ hourCycle: 'h23',
+ timeZone: 'America/New_York',
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+}).format(new Date('2014-06-25T04:00:00.123Z'))
+const hourCycleSupported =
+ testDateFormatted === '06/25/2014, 00:00:00' ||
+ testDateFormatted === '‎06‎/‎25‎/‎2014‎ ‎00‎:‎00‎:‎00'
+
function getDateTimeFormat(timeZone) {
if (!dtfCache[timeZone]) {
- // New browsers use `hourCycle`, IE and Chrome <73 does not support it and uses `hour12`
- var testDateFormatted = new Intl.DateTimeFormat('en-US', {
- hour12: false,
- timeZone: 'America/New_York',
- year: 'numeric',
- month: 'numeric',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit'
- }).format(new Date('2014-06-25T04:00:00.123Z'));
- var hourCycleSupported = testDateFormatted === '06/25/2014, 00:00:00' || testDateFormatted === '‎06‎/‎25‎/‎2014‎ ‎00‎:‎00‎:‎00';
dtfCache[timeZone] = hourCycleSupported ? new Intl.DateTimeFormat('en-US', {
hour12: false,
timeZone: timeZone,
diff --git a/node_modules/date-fns-tz/esm/_lib/tzTokenizeDate/index.js b/node_modules/date-fns-tz/esm/_lib/tzTokenizeDate/index.js
index cc1d143..17333cc 100644
--- a/node_modules/date-fns-tz/esm/_lib/tzTokenizeDate/index.js
+++ b/node_modules/date-fns-tz/esm/_lib/tzTokenizeDate/index.js
@@ -48,23 +48,24 @@ function hackyOffset(dtf, date) {
// to get deterministic local date/time output according to the `en-US` locale which
// can be used to extract local time parts as necessary.
var dtfCache = {}
+
+// New browsers use `hourCycle`, IE and Chrome <73 does not support it and uses `hour12`
+const testDateFormatted = new Intl.DateTimeFormat('en-US', {
+ hourCycle: 'h23',
+ timeZone: 'America/New_York',
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+}).format(new Date('2014-06-25T04:00:00.123Z'))
+const hourCycleSupported =
+ testDateFormatted === '06/25/2014, 00:00:00' ||
+ testDateFormatted === '‎06‎/‎25‎/‎2014‎ ‎00‎:‎00‎:‎00'
+
function getDateTimeFormat(timeZone) {
if (!dtfCache[timeZone]) {
- // New browsers use `hourCycle`, IE and Chrome <73 does not support it and uses `hour12`
- var testDateFormatted = new Intl.DateTimeFormat('en-US', {
- hour12: false,
- timeZone: 'America/New_York',
- year: 'numeric',
- month: 'numeric',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit',
- }).format(new Date('2014-06-25T04:00:00.123Z'))
- var hourCycleSupported =
- testDateFormatted === '06/25/2014, 00:00:00' ||
- testDateFormatted === '‎06‎/‎25‎/‎2014‎ ‎00‎:‎00‎:‎00'
-
dtfCache[timeZone] = hourCycleSupported
? new Intl.DateTimeFormat('en-US', {
hour12: false,
9 changes: 9 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2340,6 +2340,15 @@ const CONST = {
DEFAULT_MAX_EXPENSE_AGE: 90,
DEFAULT_MAX_EXPENSE_AMOUNT: 200000,
DEFAULT_MAX_AMOUNT_NO_RECEIPT: 2500,
REQUIRE_RECEIPTS_OVER_OPTIONS: {
DEFAULT: 'default',
NEVER: 'never',
ALWAYS: 'always',
},
EXPENSE_LIMIT_TYPES: {
EXPENSE: 'expense',
DAILY: 'daily',
},
},

CUSTOM_UNITS: {
Expand Down
6 changes: 6 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,10 @@ const ONYXKEYS = {
WORKSPACE_SETTINGS_FORM: 'workspaceSettingsForm',
WORKSPACE_CATEGORY_FORM: 'workspaceCategoryForm',
WORKSPACE_CATEGORY_FORM_DRAFT: 'workspaceCategoryFormDraft',
WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM: 'workspaceCategoryDescriptionHintForm',
WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM_DRAFT: 'workspaceCategoryDescriptionHintFormDraft',
WORKSPACE_CATEGORY_FLAG_AMOUNTS_OVER_FORM: 'workspaceCategoryFlagAmountsOverForm',
WORKSPACE_CATEGORY_FLAG_AMOUNTS_OVER_FORM_DRAFT: 'workspaceCategoryFlagAmountsOverFormDraft',
WORKSPACE_TAG_FORM: 'workspaceTagForm',
WORKSPACE_TAG_FORM_DRAFT: 'workspaceTagFormDraft',
WORKSPACE_SETTINGS_FORM_DRAFT: 'workspaceSettingsFormDraft',
Expand Down Expand Up @@ -677,6 +681,8 @@ type OnyxFormValuesMapping = {
[ONYXKEYS.FORMS.WORKSPACE_TAX_CUSTOM_NAME]: FormTypes.WorkspaceTaxCustomName;
[ONYXKEYS.FORMS.WORKSPACE_COMPANY_CARD_FEED_NAME]: FormTypes.WorkspaceCompanyCardFeedName;
[ONYXKEYS.FORMS.WORKSPACE_REPORT_FIELDS_FORM]: FormTypes.WorkspaceReportFieldForm;
[ONYXKEYS.FORMS.WORKSPACE_CATEGORY_DESCRIPTION_HINT_FORM]: FormTypes.WorkspaceCategoryDescriptionHintForm;
[ONYXKEYS.FORMS.WORKSPACE_CATEGORY_FLAG_AMOUNTS_OVER_FORM]: FormTypes.WorkspaceCategoryFlagAmountsOverForm;
[ONYXKEYS.FORMS.CLOSE_ACCOUNT_FORM]: FormTypes.CloseAccountForm;
[ONYXKEYS.FORMS.PROFILE_SETTINGS_FORM]: FormTypes.ProfileSettingsForm;
[ONYXKEYS.FORMS.DISPLAY_NAME_FORM]: FormTypes.DisplayNameForm;
Expand Down
20 changes: 20 additions & 0 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,26 @@ const ROUTES = {
route: 'settings/workspaces/:policyID/categories/:categoryName/gl-code',
getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/gl-code` as const,
},
WORSKPACE_CATEGORY_DEFAULT_TAX_RATE: {
route: 'settings/workspaces/:policyID/categories/:categoryName/tax-rate',
getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/tax-rate` as const,
},
WORSKPACE_CATEGORY_FLAG_AMOUNTS_OVER: {
route: 'settings/workspaces/:policyID/categories/:categoryName/flag-amounts',
getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/flag-amounts` as const,
},
WORSKPACE_CATEGORY_DESCRIPTION_HINT: {
route: 'settings/workspaces/:policyID/categories/:categoryName/description-hint',
getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/description-hint` as const,
},
WORSKPACE_CATEGORY_REQUIRE_RECEIPTS_OVER: {
route: 'settings/workspaces/:policyID/categories/:categoryName/require-receipts-over',
getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/require-receipts-over` as const,
},
WORSKPACE_CATEGORY_APPROVER: {
route: 'settings/workspaces/:policyID/categories/:categoryName/approver',
getRoute: (policyID: string, categoryName: string) => `settings/workspaces/${policyID}/categories/${encodeURIComponent(categoryName)}/approver` as const,
},
WORKSPACE_MORE_FEATURES: {
route: 'settings/workspaces/:policyID/more-features',
getRoute: (policyID: string) => `settings/workspaces/${policyID}/more-features` as const,
Expand Down
5 changes: 5 additions & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,11 @@ const SCREENS = {
CATEGORY_PAYROLL_CODE: 'Category_Payroll_Code',
CATEGORY_GL_CODE: 'Category_GL_Code',
CATEGORY_SETTINGS: 'Category_Settings',
CATEGORY_DEFAULT_TAX_RATE: 'Category_Default_Tax_Rate',
CATEGORY_FLAG_AMOUNTS_OVER: 'Category_Flag_Amounts_Over',
CATEGORY_DESCRIPTION_HINT: 'Category_Description_Hint',
CATEGORY_APPROVER: 'Category_Approver',
CATEGORY_REQUIRE_RECEIPTS_OVER: 'Category_Require_Receipts_Over',
CATEGORIES_SETTINGS: 'Categories_Settings',
CATEGORIES_IMPORT: 'Categories_Import',
CATEGORIES_IMPORTED: 'Categories_Imported',
Expand Down
115 changes: 115 additions & 0 deletions src/components/WorkspaceMembersSelectionList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import React, {useMemo} from 'react';
import type {SectionListData} from 'react-native';
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import CONST from '@src/CONST';
import type {Icon} from '@src/types/onyx/OnyxCommon';
import Badge from './Badge';
import {FallbackAvatar} from './Icon/Expensicons';
import {usePersonalDetails} from './OnyxProvider';
import SelectionList from './SelectionList';
import InviteMemberListItem from './SelectionList/InviteMemberListItem';
import type {Section} from './SelectionList/types';

type SelectionListApprover = {
text: string;
alternateText: string;
keyForList: string;
isSelected: boolean;
login: string;
rightElement?: React.ReactNode;
icons: Icon[];
};
type ApproverSection = SectionListData<SelectionListApprover, Section<SelectionListApprover>>;

type WorkspaceMembersSelectionListProps = {
policyID: string;
selectedApprover: string;
setApprover: (email: string) => void;
};

function WorkspaceMembersSelectionList({policyID, selectedApprover, setApprover}: WorkspaceMembersSelectionListProps) {
const {translate} = useLocalize();
const {didScreenTransitionEnd} = useScreenWrapperTranstionStatus();
const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState('');
const personalDetails = usePersonalDetails();
const policy = usePolicy(policyID);

const sections: ApproverSection[] = useMemo(() => {
const approvers: SelectionListApprover[] = [];

if (policy?.employeeList) {
const availableApprovers = Object.values(policy.employeeList)
.map((employee): SelectionListApprover | null => {
const isAdmin = employee?.role === CONST.REPORT.ROLE.ADMIN;
const email = employee.email;

if (!email) {
return null;
}

const policyMemberEmailsToAccountIDs = PolicyUtils.getMemberAccountIDsForWorkspace(policy?.employeeList);
const accountID = Number(policyMemberEmailsToAccountIDs[email] ?? '');
const {avatar, displayName = email} = personalDetails?.[accountID] ?? {};

return {
text: displayName,
alternateText: email,
keyForList: email,
isSelected: selectedApprover === email,
login: email,
icons: [{source: avatar ?? FallbackAvatar, type: CONST.ICON_TYPE_AVATAR, name: displayName, id: accountID}],
rightElement: isAdmin ? <Badge text={translate('common.admin')} /> : undefined,
};
})
.filter((approver): approver is SelectionListApprover => !!approver);

approvers.push(...availableApprovers);
}

const filteredApprovers =
debouncedSearchTerm !== ''
? approvers.filter((option) => {
const searchValue = OptionsListUtils.getSearchValueForPhoneOrEmail(debouncedSearchTerm);
const isPartOfSearchTerm = !!option.text?.toLowerCase().includes(searchValue) || !!option.login?.toLowerCase().includes(searchValue);
return isPartOfSearchTerm;
})
: approvers;

return [
{
title: undefined,
data: OptionsListUtils.sortAlphabetically(filteredApprovers, 'text'),
shouldShow: true,
},
];
}, [debouncedSearchTerm, personalDetails, policy?.employeeList, selectedApprover, translate]);

const handleOnSelectRow = (approver: SelectionListApprover) => {
setApprover(approver.login);
};

const headerMessage = useMemo(() => (searchTerm && !sections[0].data.length ? translate('common.noResultsFound') : ''), [searchTerm, sections, translate]);

return (
<SelectionList
sections={sections}
ListItem={InviteMemberListItem}
textInputLabel={translate('selectionList.nameEmailOrPhoneNumber')}
textInputValue={searchTerm}
onChangeText={setSearchTerm}
headerMessage={headerMessage}
onSelectRow={handleOnSelectRow}
showScrollIndicator
showLoadingPlaceholder={!didScreenTransitionEnd}
shouldPreventDefaultFocusOnSelectRow={!DeviceCapabilities.canUseTouchScreen()}
/>
);
}

export default WorkspaceMembersSelectionList;
29 changes: 29 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3765,6 +3765,35 @@ export default {
unlockFeatureGoToSubtitle: 'Go to',
unlockFeatureEnableWorkflowsSubtitle: (featureName: string) => `and enable workflows, then add ${featureName} to unlock this feature.`,
},
categoryRules: {
title: 'Category rules',
approver: 'Approver',
requireDescription: 'Require description',
descriptionHint: 'Description hint',
descriptionHintDescription: (categoryName: string) =>
`Remind employees to provide additional information for “${categoryName}” spend. This hint appears in the description field on expenses.`,
descriptionHintLabel: 'Hint',
descriptionHintSubtitle: 'Pro-tip: The shorter the better!',
maxAmount: 'Max amount',
flagAmountsOver: 'Flag amounts over',
flagAmountsOverDescription: (categoryName) => `Applies to the category “${categoryName}”.`,
flagAmountsOverSubtitle: 'This overrides the max amount for all expenses.',
expenseLimitTypes: {
expense: 'Individual expense',
expenseSubtitle: 'Flag expense amounts by category. This rule overrides the general workspace rule for max expense amount.',
daily: 'Category total',
dailySubtitle: 'Flag total category spend per expense report.',
},
requireReceiptsOver: 'Require receipts over',
requireReceiptsOverList: {
default: (defaultAmount: string) => `${defaultAmount} ${CONST.DOT_SEPARATOR} Default`,
never: 'Never require receipts',
always: 'Always require receipts',
},
defaultTaxRate: 'Default tax rate',
goTo: 'Go to',
andEnableWorkflows: 'and enable workflows, then add approvals to unlock this feature.',
},
},
},
getAssistancePage: {
Expand Down
29 changes: 29 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3815,6 +3815,35 @@ export default {
unlockFeatureGoToSubtitle: 'Ir a',
unlockFeatureEnableWorkflowsSubtitle: (featureName: string) => `y habilita flujos de trabajo, luego agrega ${featureName} para desbloquear esta función.`,
},
categoryRules: {
title: 'Reglas de categoría',
approver: 'Aprobador',
requireDescription: 'Requerir descripción',
descriptionHint: 'Sugerencia de descripción',
descriptionHintDescription: (categoryName: string) =>
`Recuerda a los empleados que deben proporcionar información adicional para los gastos de “${categoryName}”. Esta sugerencia aparece en el campo de descripción en los gastos.`,
descriptionHintLabel: 'Sugerencia',
descriptionHintSubtitle: 'Consejo: ¡Cuanto más corta, mejor!',
maxAmount: 'Importe máximo',
flagAmountsOver: 'Señala importes superiores a',
flagAmountsOverDescription: (categoryName: string) => `Aplica a la categoría “${categoryName}”.`,
flagAmountsOverSubtitle: 'Esto anula el importe máximo para todos los gastos.',
expenseLimitTypes: {
expense: 'Gasto individual',
expenseSubtitle: 'Señala importes de gastos por categoría. Esta regla anula la regla general del espacio de trabajo para el importe máximo de gastos.',
daily: 'Total por categoría',
dailySubtitle: 'Marcar el gasto total por categoría en cada informe de gastos.',
},
requireReceiptsOver: 'Requerir recibos para importes superiores a',
requireReceiptsOverList: {
default: (defaultAmount: string) => `${defaultAmount} ${CONST.DOT_SEPARATOR} Predeterminado`,
never: 'Nunca requerir recibos',
always: 'Requerir recibos siempre',
},
defaultTaxRate: 'Tasa de impuesto predeterminada',
goTo: 'Ve a',
andEnableWorkflows: 'y habilita los flujos de trabajo, luego añade aprobaciones para desbloquear esta función.',
},
},
},
getAssistancePage: {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type RemovePolicyCategoryReceiptsRequiredParams = {
policyID: string;
categoryName: string;
};

export default RemovePolicyCategoryReceiptsRequiredParams;
7 changes: 7 additions & 0 deletions src/libs/API/parameters/SetPolicyCategoryApproverParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type SetPolicyCategoryApproverParams = {
policyID: string;
categoryName: string;
approver: string;
};

export default SetPolicyCategoryApproverParams;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type SetPolicyCategoryDescriptionRequiredParams = {
policyID: string;
categoryName: string;
areCommentsRequired: boolean;
};

export default SetPolicyCategoryDescriptionRequiredParams;
10 changes: 10 additions & 0 deletions src/libs/API/parameters/SetPolicyCategoryMaxAmountParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type {PolicyCategoryExpenseLimitType} from '@src/types/onyx/PolicyCategory';

type SetPolicyCategoryMaxAmountParams = {
policyID: string;
categoryName: string;
maxExpenseAmount: number | null;
expenseLimitType: PolicyCategoryExpenseLimitType;
};

export default SetPolicyCategoryMaxAmountParams;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type SetPolicyCategoryReceiptsRequiredParams = {
policyID: string;
categoryName: string;
maxExpenseAmountNoReceipt: number;
};

export default SetPolicyCategoryReceiptsRequiredParams;
7 changes: 7 additions & 0 deletions src/libs/API/parameters/SetPolicyCategoryTaxParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type SetPolicyCategoryTaxParams = {
policyID: string;
categoryName: string;
taxID: string;
};

export default SetPolicyCategoryTaxParams;
Loading

0 comments on commit dc9cd2d

Please sign in to comment.