Skip to content

Commit

Permalink
Merge pull request #37621 from waterim/feat-37309
Browse files Browse the repository at this point in the history
Create WorkspaceTagsPage
  • Loading branch information
luacmartins authored Mar 4, 2024
2 parents ef54773 + 40ab003 commit 4f21d80
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 1 deletion.
33 changes: 33 additions & 0 deletions assets/images/simple-illustrations/simple-illustration__tag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions assets/images/tag.svg
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 @@ -546,7 +546,10 @@ const ROUTES = {
route: 'workspace/:policyID/categories/settings',
getRoute: (policyID: string) => `workspace/${policyID}/categories/settings` as const,
},

WORKSPACE_TAGS: {
route: 'workspace/:policyID/tags',
getRoute: (policyID: string) => `workspace/${policyID}/tags` 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 @@ -214,6 +214,7 @@ const SCREENS = {
INVITE: 'Workspace_Invite',
INVITE_MESSAGE: 'Workspace_Invite_Message',
CATEGORIES: 'Workspace_Categories',
TAGS: 'Workspace_Tags',
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 @@ -135,6 +135,7 @@ import Twitter from '@assets/images/social-twitter.svg';
import Youtube from '@assets/images/social-youtube.svg';
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 ThreeDots from '@assets/images/three-dots.svg';
import ThumbsUp from '@assets/images/thumbs-up.svg';
Expand Down Expand Up @@ -222,6 +223,7 @@ export {
FlagLevelThree,
Fullscreen,
Folder,
Tag,
Gallery,
Gear,
Globe,
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icon/Illustrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import ReceiptWrangler from '@assets/images/simple-illustrations/simple-illustra
import SanFrancisco from '@assets/images/simple-illustrations/simple-illustration__sanfrancisco.svg';
import ShieldYellow from '@assets/images/simple-illustrations/simple-illustration__shield.svg';
import SmallRocket from '@assets/images/simple-illustrations/simple-illustration__smallrocket.svg';
import Tag from '@assets/images/simple-illustrations/simple-illustration__tag.svg';
import ThumbsUpStars from '@assets/images/simple-illustrations/simple-illustration__thumbsupstars.svg';
import TrackShoe from '@assets/images/simple-illustrations/simple-illustration__track-shoe.svg';
import TrashCan from '@assets/images/simple-illustrations/simple-illustration__trashcan.svg';
Expand Down Expand Up @@ -146,4 +147,5 @@ export {
Workflows,
ThreeLeggedLaptopWoman,
House,
Tag,
};
10 changes: 10 additions & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,7 @@ export default {
settings: 'Settings',
reimburse: 'Reimbursements',
categories: 'Categories',
tags: 'Tags',
bills: 'Bills',
invoices: 'Invoices',
travel: 'Travel',
Expand Down Expand Up @@ -1768,6 +1769,15 @@ export default {
},
genericFailureMessage: 'An error occurred while updating the category, please try again.',
},
tags: {
requiresTag: 'Members must tag all spend',
enableTag: 'Enable tag',
subtitle: 'Tags add more detailed ways to classify costs.',
emptyTags: {
title: "You haven't created any tags",
subtitle: 'Add a tag to track projects, locations, departments, and more.',
},
},
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
10 changes: 10 additions & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,7 @@ export default {
settings: 'Configuración',
reimburse: 'Reembolsos',
categories: 'Categorías',
tags: 'Etiquetas',
bills: 'Pagar facturas',
invoices: 'Enviar facturas',
travel: 'Viajes',
Expand Down Expand Up @@ -1792,6 +1793,15 @@ export default {
},
genericFailureMessage: 'Se ha producido un error al intentar eliminar la categoría. Por favor, inténtalo más tarde.',
},
tags: {
requiresTag: 'Los miembros deben etiquetar todos los gastos',
enableTag: 'Habilitar etiqueta',
subtitle: 'Las etiquetas añaden formas más detalladas de clasificar los costos.',
emptyTags: {
title: 'No has creado ninguna etiqueta',
subtitle: 'Añade una etiqueta para realizar el seguimiento de proyectos, ubicaciones, departamentos y otros.',
},
},
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 @@ -24,6 +24,7 @@ const workspaceSettingsScreens = {
[SCREENS.WORKSPACE.TRAVEL]: () => require('../../../../../pages/workspace/travel/WorkspaceTravelPage').default as React.ComponentType,
[SCREENS.WORKSPACE.MEMBERS]: () => require('../../../../../pages/workspace/WorkspaceMembersPage').default as React.ComponentType,
[SCREENS.WORKSPACE.CATEGORIES]: () => require('../../../../../pages/workspace/categories/WorkspaceCategoriesPage').default as React.ComponentType,
[SCREENS.WORKSPACE.TAGS]: () => require('../../../../../pages/workspace/tags/WorkspaceTagsPage').default as React.ComponentType,
} satisfies Screens;

function BaseCentralPaneNavigator() {
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 @@ -67,6 +67,9 @@ const config: LinkingOptions<RootStackParamList>['config'] = {
[SCREENS.WORKSPACE.CATEGORIES]: {
path: ROUTES.WORKSPACE_CATEGORIES.route,
},
[SCREENS.WORKSPACE.TAGS]: {
path: ROUTES.WORKSPACE_TAGS.route,
},
},
},
[SCREENS.NOT_FOUND]: '*',
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 @@ -91,6 +91,9 @@ type CentralPaneNavigatorParamList = {
[SCREENS.WORKSPACE.CATEGORIES]: {
policyID: string;
};
[SCREENS.WORKSPACE.TAGS]: {
policyID: string;
};
};

type WorkspaceSwitcherNavigatorParamList = {
Expand Down
6 changes: 6 additions & 0 deletions src/pages/workspace/WorkspaceInitialPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,12 @@ function WorkspaceInitialPage({policyDraft, policy: policyProp, policyMembers, r
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_CATEGORIES.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.CATEGORIES,
},
{
translationKey: 'workspace.common.tags',
icon: Expensicons.Tag,
action: singleExecution(waitForNavigate(() => Navigation.navigate(ROUTES.WORKSPACE_TAGS.getRoute(policyID)))),
routeName: SCREENS.WORKSPACE.TAGS,
},
];

const menuItems: WorkspaceMenuItem[] = [
Expand Down
144 changes: 144 additions & 0 deletions src/pages/workspace/tags/WorkspaceTagsPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useMemo, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
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 Text from '@components/Text';
import WorkspaceEmptyStateSection from '@components/WorkspaceEmptyStateSection';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as PolicyUtils from '@libs/PolicyUtils';
import type {CentralPaneNavigatorParamList} from '@navigation/types';
import AdminPolicyAccessOrNotFoundWrapper from '@pages/workspace/AdminPolicyAccessOrNotFoundWrapper';
import PaidPolicyAccessOrNotFoundWrapper from '@pages/workspace/PaidPolicyAccessOrNotFoundWrapper';
import ONYXKEYS from '@src/ONYXKEYS';
import type SCREENS from '@src/SCREENS';
import type * as OnyxTypes from '@src/types/onyx';

type PolicyForList = {
value: string;
text: string;
keyForList: string;
isSelected: boolean;
rightElement: React.ReactNode;
};

type WorkspaceTagsOnyxProps = {
/** Collection of tags attached to a policy */
policyTags: OnyxEntry<OnyxTypes.PolicyTagList>;
};

type WorkspaceTagsPageProps = WorkspaceTagsOnyxProps & StackScreenProps<CentralPaneNavigatorParamList, typeof SCREENS.WORKSPACE.TAGS>;

function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) {
const {isSmallScreenWidth} = useWindowDimensions();
const styles = useThemeStyles();
const theme = useTheme();
const {translate} = useLocalize();
const [selectedTags, setSelectedTags] = useState<Record<string, boolean>>({});
const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]);
const tagList = useMemo<PolicyForList[]>(
() =>
policyTagLists
.map((policyTagList) =>
Object.values(policyTagList.tags).map((value) => ({
value: value.name,
text: value.name,
keyForList: value.name,
isSelected: !!selectedTags[value.name],
rightElement: (
<View style={styles.flexRow}>
<Text style={[styles.disabledText, styles.alignSelfCenter]}>
{value.enabled ? translate('workspace.common.enabled') : translate('workspace.common.disabled')}
</Text>
<View style={[styles.p1, styles.pl2]}>
<Icon
src={Expensicons.ArrowRight}
fill={theme.icon}
/>
</View>
</View>
),
})),
)
.flat(),
[policyTagLists, selectedTags, styles.alignSelfCenter, styles.disabledText, styles.flexRow, styles.p1, styles.pl2, theme.icon, translate],
);

const toggleTag = (tag: PolicyForList) => {
setSelectedTags((prev) => ({
...prev,
[tag.value]: !prev[tag.value],
}));
};

const toggleAllTags = () => {
const isAllSelected = tagList.every((tag) => !!selectedTags[tag.value]);
setSelectedTags(isAllSelected ? {} : Object.fromEntries(tagList.map((item) => [item.value, true])));
};

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>
);

return (
<AdminPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<PaidPolicyAccessOrNotFoundWrapper policyID={route.params.policyID}>
<ScreenWrapper
includeSafeAreaPaddingBottom={false}
style={[styles.defaultModalContainer]}
testID={WorkspaceTagsPage.displayName}
shouldShowOfflineIndicatorInWideScreen
>
<HeaderWithBackButton
icon={Illustrations.Tag}
title={translate('workspace.common.tags')}
shouldShowBackButton={isSmallScreenWidth}
/>
<View style={[styles.ph5, styles.pb5]}>
<Text style={[styles.textNormal, styles.colorMuted]}>{translate('workspace.tags.subtitle')}</Text>
</View>
{tagList.length ? (
<SelectionList
canSelectMultiple
sections={[{data: tagList, indexOffset: 0, isDisabled: false}]}
onCheckboxPress={toggleTag}
onSelectRow={() => {}}
onSelectAll={toggleAllTags}
showScrollIndicator
ListItem={TableListItem}
customListHeader={getCustomListHeader()}
listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]}
/>
) : (
<WorkspaceEmptyStateSection
title={translate('workspace.tags.emptyTags.title')}
icon={Illustrations.EmptyStateExpenses}
subtitle={translate('workspace.tags.emptyTags.subtitle')}
/>
)}
</ScreenWrapper>
</PaidPolicyAccessOrNotFoundWrapper>
</AdminPolicyAccessOrNotFoundWrapper>
);
}

WorkspaceTagsPage.displayName = 'WorkspaceTagsPage';

export default withOnyx<WorkspaceTagsPageProps, WorkspaceTagsOnyxProps>({
policyTags: {
key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${route.params.policyID}`,
},
})(WorkspaceTagsPage);

0 comments on commit 4f21d80

Please sign in to comment.