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

[Simplified Collect][Taxes] Create WorkspaceTaxesPage #37870

Merged
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
049c487
Create WorkspaceTaxesPage
filip-solecki Mar 7, 2024
a491463
Remove unnecessary check
filip-solecki Mar 7, 2024
bc18001
Remove unnecessary import
filip-solecki Mar 7, 2024
0ff7067
Fix icon fill color
filip-solecki Mar 7, 2024
2d43808
CR fixes
filip-solecki Mar 7, 2024
b63bcbb
use ListItem type
filip-solecki Mar 7, 2024
a97c874
Fix tax icon
filip-solecki Mar 7, 2024
60aa7d3
Fix icon tax fill
filip-solecki Mar 7, 2024
e933a60
Header buttons
filip-solecki Mar 7, 2024
be9c328
Merge branch 'main' into wave8/WorkspaceTaxesPage
filip-solecki Mar 8, 2024
f06d07b
Merge branch 'main' into wave8/WorkspaceTaxesPage
filip-solecki Mar 11, 2024
c160d40
Add translations and adjust SelectionList
filip-solecki Mar 11, 2024
6838836
Merge branch 'main' into wave8/WorkspaceTaxesPage
filip-solecki Mar 11, 2024
940370c
Merge branch 'main' into wave8/WorkspaceTaxesPage
filip-solecki Mar 12, 2024
fd6716b
Fix failing TS
filip-solecki Mar 12, 2024
8bfd4ef
Fix Lint
filip-solecki Mar 12, 2024
e0ca61f
CR fixes
filip-solecki Mar 12, 2024
4ba47a8
Add possibility to remove foreign currency default tax
filip-solecki Mar 12, 2024
606c12d
Rename prop
filip-solecki Mar 12, 2024
2687bfb
handle isOffline mode and fetching taxes data
filip-solecki Mar 12, 2024
f3ab59c
Merge branch 'main' into wave8/WorkspaceTaxesPage
filip-solecki Mar 12, 2024
9fd30e5
Merge branch 'main' into wave8/WorkspaceTaxesPage
filip-solecki Mar 13, 2024
d772221
Fix type for WorkspaceTaxesPage
filip-solecki Mar 13, 2024
d696446
Refactor isDisabledCheckbox
filip-solecki Mar 13, 2024
14072ae
Merge branch 'main' into wave8/WorkspaceTaxesPage
kosmydel Mar 13, 2024
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
8 changes: 8 additions & 0 deletions assets/images/tax.svg
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,10 @@ const ROUTES = {
route: 'workspace/:policyID/tags',
getRoute: (policyID: string) => `workspace/${policyID}/tags` as const,
},
WORKSPACE_TAXES: {
route: 'workspace/:policyID/taxes',
getRoute: (policyID: string) => `workspace/${policyID}/taxes` as const,
},
WORKSPACE_MEMBER_DETAILS: {
route: 'workspace/:policyID/members/:accountID',
getRoute: (policyID: string, accountID: number, backTo?: string) => getUrlWithBackToParam(`workspace/${policyID}/members/${accountID}`, backTo),
Expand All @@ -581,7 +585,6 @@ const ROUTES = {
route: 'workspace/:policyID/distance-rates',
getRoute: (policyID: string) => `workspace/${policyID}/distance-rates` as const,
},

// Referral program promotion
REFERRAL_DETAILS_MODAL: {
route: 'referral/:contentType',
Expand Down
1 change: 1 addition & 0 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ const SCREENS = {
INVITE_MESSAGE: 'Workspace_Invite_Message',
CATEGORIES: 'Workspace_Categories',
TAGS: 'Workspace_Tags',
TAXES: 'Workspace_Taxes',
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
CURRENCY: 'Workspace_Profile_Currency',
WORKFLOWS: 'Workspace_Workflows',
WORKFLOWS_APPROVER: 'Workspace_Workflows_Approver',
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Expensicons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ import Stopwatch from '@assets/images/stopwatch.svg';
import Sync from '@assets/images/sync.svg';
import Tag from '@assets/images/tag.svg';
import Task from '@assets/images/task.svg';
import Tax from '@assets/images/tax.svg';
import ThreeDots from '@assets/images/three-dots.svg';
import ThumbsUp from '@assets/images/thumbs-up.svg';
import Transfer from '@assets/images/transfer.svg';
Expand Down Expand Up @@ -226,6 +227,7 @@ export {
Fullscreen,
Folder,
Tag,
Tax,
Gallery,
Gear,
Globe,
Expand Down
6 changes: 4 additions & 2 deletions src/components/SelectionList/BaseListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ function BaseListItem<TItem extends ListItem>({
const StyleUtils = useStyleUtils();
const {hovered, bind} = useHover();

const isSelectable = item.isSelectable === undefined || item.isSelectable;
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved

const rightHandSideComponentRender = () => {
if (canSelectMultiple || !rightHandSideComponent) {
return null;
Expand Down Expand Up @@ -80,9 +82,9 @@ function BaseListItem<TItem extends ListItem>({
<PressableWithFeedback
accessibilityLabel={item.text}
role={CONST.ROLE.BUTTON}
disabled={isDisabled}
disabled={isDisabled || !isSelectable}
onPress={handleCheckboxPress}
style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle()]}
style={[styles.cursorUnset, StyleUtils.getCheckboxPressableStyle(), !isSelectable && styles.cursorDisabled]}
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
>
<View style={selectMultipleStyle}>
{item.isSelected && (
Expand Down
3 changes: 2 additions & 1 deletion src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ function BaseSelectionList<TItem extends ListItem>(
});

// If disabled, add to the disabled indexes array
if (!!section.isDisabled || item.isDisabled) {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if (!!section.isDisabled || item.isDisabled || (item.isSelectable !== undefined && !item.isSelectable)) {
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
disabledOptionsIndexes.push(disabledIndex);
}
disabledIndex += 1;
Expand Down
3 changes: 3 additions & 0 deletions src/components/SelectionList/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ type ListItem = {
/** Whether this option is selected */
isSelected?: boolean;

/** Whether this option is selectable */
isSelectable?: boolean;
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved

/** Whether this option is disabled for selection */
isDisabled?: boolean;

Expand Down
7 changes: 7 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,7 @@ export default {
reimburse: 'Reimbursements',
categories: 'Categories',
tags: 'Tags',
taxes: 'Taxes',
bills: 'Bills',
invoices: 'Invoices',
travel: 'Travel',
Expand Down Expand Up @@ -1831,6 +1832,12 @@ export default {
subtitle: 'Add a tag to track projects, locations, departments, and more.',
},
},
taxes: {
subtitle: 'Add tax names, rates, and set defaults.',
addRate: 'Add rate',
workspaceDefault: 'Workspace currency default',
foreignDefault: 'Foreign currency default',
},
emptyWorkspace: {
title: 'Create a workspace',
subtitle: 'Workspaces are where you’ll chat with your team, reimburse expenses, issue cards, send invoices, pay bills, and more - all in one place.',
Expand Down
7 changes: 7 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1757,6 +1757,7 @@ export default {
reimburse: 'Reembolsos',
categories: 'Categorías',
tags: 'Etiquetas',
taxes: 'Impuestos',
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
bills: 'Pagar facturas',
invoices: 'Enviar facturas',
travel: 'Viajes',
Expand Down Expand Up @@ -1855,6 +1856,12 @@ export default {
subtitle: 'Añade una etiqueta para realizar el seguimiento de proyectos, ubicaciones, departamentos y otros.',
},
},
taxes: {
subtitle: 'Añade nombres, tasas y establezca valores por defecto para los impuestos.',
addRate: 'Añade tasa',
workspaceDefault: 'Moneda por defecto del espacio de trabajo',
foreignDefault: 'Impago de divisas',
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
},
emptyWorkspace: {
title: 'Crea un espacio de trabajo',
subtitle: 'En los espacios de trabajo podrás chatear con tu equipo, reembolsar gastos, emitir tarjetas, enviar y pagar facturas, y mucho más - todo en un mismo lugar.',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const workspaceSettingsScreens = {
[SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType,
[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.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 @@ -14,6 +14,7 @@ const TAB_TO_CENTRAL_PANE_MAPPING: Record<BottomTabName, CentralPaneName[]> = {
SCREENS.WORKSPACE.TRAVEL,
SCREENS.WORKSPACE.MEMBERS,
SCREENS.WORKSPACE.CATEGORIES,
SCREENS.WORKSPACE.TAXES,
SCREENS.WORKSPACE.MORE_FEATURES,
SCREENS.WORKSPACE.DISTANCE_RATES,
],
Expand Down
3 changes: 3 additions & 0 deletions src/libs/Navigation/linkingConfig/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.TAGS]: {
path: ROUTES.WORKSPACE_TAGS.route,
},
[SCREENS.WORKSPACE.TAXES]: {
path: ROUTES.WORKSPACE_TAXES.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 @@ -98,6 +98,9 @@ type CentralPaneNavigatorParamList = {
policyID: string;
categoryName: string;
};
[SCREENS.WORKSPACE.TAXES]: {
policyID: string;
};
[SCREENS.WORKSPACE.DISTANCE_RATES]: {
policyID: string;
};
Expand Down
9 changes: 9 additions & 0 deletions src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
});
}

if (policy?.tax?.trackingEnabled) {
protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.taxes',
icon: Expensicons.Tax,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_TAXES.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.TAXES,
});
}

protectedCollectPolicyMenuItems.push({
translationKey: 'workspace.common.moreFeatures',
icon: Expensicons.Gear,
Expand Down
10 changes: 10 additions & 0 deletions src/pages/workspace/WorkspaceMoreFeaturesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,16 @@ function WorkspaceMoreFeaturesPage({policy, route}: WorkspaceMoreFeaturesPagePro
Policy.enablePolicyTags(policy?.id ?? '', isEnabled);
},
},
{
icon: Illustrations.Coins,
titleTranslationKey: 'workspace.moreFeatures.taxes.title',
subtitleTranslationKey: 'workspace.moreFeatures.taxes.subtitle',
isActive: policy?.tax?.trackingEnabled ?? false,
pendingAction: policy?.pendingFields?.isTaxTrackingEnabled,
action: (isEnabled: boolean) => {
Policy.enablePolicyTaxes(policy?.id ?? '', isEnabled);
},
},
];

const sections: SectionObject[] = [
Expand Down
163 changes: 163 additions & 0 deletions src/pages/workspace/taxes/WorkspaceTaxesPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useCallback, useMemo, useState} from 'react';
import {View} from 'react-native';
import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import Icon from '@components/Icon';
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 TableListItem from '@components/SelectionList/TableListItem';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import type {CentralPaneNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading';
import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading';
import type SCREENS from '@src/SCREENS';

type WorkspaceTaxesPageProps = WithPolicyAndFullscreenLoadingProps & StackScreenProps<CentralPaneNavigatorParamList, typeof SCREENS.WORKSPACE.TAXES>;

function WorkspaceTaxesPage({policy}: WorkspaceTaxesPageProps) {
const {isSmallScreenWidth} = useWindowDimensions();
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const [selectedTaxesIDs, setSelectedTaxesIDs] = useState<string[]>([]);
const defaultExternalID = policy?.taxRates?.defaultExternalID;
const foreignTaxDefault = policy?.taxRates?.foreignTaxDefault;

const textForDefault = useCallback(
(taxID: string): string => {
if (taxID === defaultExternalID && taxID === foreignTaxDefault) {
return translate('common.default');
}
if (taxID === defaultExternalID) {
return translate('workspace.taxes.workspaceDefault');
}
if (taxID === foreignTaxDefault) {
return translate('workspace.taxes.foreignDefault');
}
return '';
},
[defaultExternalID, foreignTaxDefault, translate],
);

const taxesList = useMemo<ListItem[]>(
() =>
Object.entries(policy?.taxRates?.taxes ?? {}).map(([key, value]) => ({
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
text: value.name,
alternateText: textForDefault(key),
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
keyForList: key,
isSelected: !!selectedTaxesIDs.includes(key),
isSelectable: !(key === defaultExternalID || key === foreignTaxDefault),
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
rightElement: (
<View style={styles.flexRow}>
<Text style={[styles.disabledText, styles.alignSelfCenter]}>{value.isDisabled ? translate('workspace.common.disabled') : translate('workspace.common.enabled')}</Text>
<View style={[styles.p1, styles.pl2]}>
<Icon
src={Expensicons.ArrowRight}
fill={theme.icon}
/>
</View>
</View>
),
})),
[policy?.taxRates?.taxes, textForDefault, foreignTaxDefault, defaultExternalID, selectedTaxesIDs, styles, theme.icon, translate],
);

const toggleTax = (tax: ListItem) => {
setSelectedTaxesIDs((prev) => {
if (prev.includes(tax.keyForList)) {
return prev.filter((item) => item !== tax.keyForList);
}
return [...prev, tax.keyForList];
});
};

const toggleAllTaxes = () => {
const taxesToSelect = taxesList.filter((tax) => tax.keyForList !== defaultExternalID && tax.keyForList !== foreignTaxDefault);
setSelectedTaxesIDs((prev) => {
if (prev.length === taxesToSelect.length) {
return [];
}

return taxesToSelect.map((item) => item.keyForList);
});
};

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('statusPage.status')}</Text>
</View>
);

const headerButtons = (
<View style={[styles.w100, styles.flexRow, isSmallScreenWidth && styles.mb3]}>
<Button
medium
success
onPress={() => {}}
icon={Expensicons.Plus}
text={translate('workspace.taxes.addRate')}
style={[styles.mr3, isSmallScreenWidth && styles.w50]}
Copy link
Contributor

@suneox suneox Oct 27, 2024

Choose a reason for hiding this comment

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

In other places, we’re using mr2 or gap2 between buttons on headers, which is causing this issue.

/>
<Button
medium
onPress={() => {}}
icon={Expensicons.Gear}
text={translate('common.settings')}
style={[isSmallScreenWidth && styles.w50]}
/>
</View>
);

return (
<AdminPolicyAccessOrNotFoundWrapper policyID={policy?.id ?? ''}>
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
<PaidPolicyAccessOrNotFoundWrapper policyID={policy?.id ?? ''}>
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={WorkspaceTaxesPage.displayName}
shouldShowOfflineIndicatorInWideScreen
>
<HeaderWithBackButton
icon={Illustrations.Coins}
title={translate('workspace.common.taxes')}
shouldShowBackButton={isSmallScreenWidth}
>
{!isSmallScreenWidth && headerButtons}
</HeaderWithBackButton>

{isSmallScreenWidth && <View style={[styles.pl5, styles.pr5]}>{headerButtons}</View>}

<View style={[styles.ph5, styles.pb5]}>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.taxes.subtitle')}</Text>
</View>
<SelectionList
filip-solecki marked this conversation as resolved.
Show resolved Hide resolved
canSelectMultiple
sections={[{data: taxesList, indexOffset: 0, isDisabled: false}]}
onCheckboxPress={toggleTax}
onSelectRow={() => {}}
onSelectAll={toggleAllTaxes}
showScrollIndicator
ListItem={TableListItem}
customListHeader={getCustomListHeader()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
/>
</ScreenWrapper>
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
}

WorkspaceTaxesPage.displayName = 'WorkspaceTaxesPage';

export default withPolicyAndFullscreenLoading(WorkspaceTaxesPage);
Loading