From c63961da11c8c2039e81c5c27ab6ebe6be11a888 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Fri, 1 Mar 2024 18:51:15 +0100 Subject: [PATCH 1/6] initial workspace tags page --- .../simple-illustration__tag.svg | 16 ++ assets/images/tag.svg | 3 + src/ROUTES.ts | 4 + src/SCREENS.ts | 1 + src/components/Icon/Expensicons.ts | 2 + src/components/Icon/Illustrations.ts | 2 + src/languages/en.ts | 10 ++ src/languages/es.ts | 10 ++ .../BaseCentralPaneNavigator.tsx | 1 + src/libs/Navigation/linkingConfig/config.ts | 3 + src/libs/Navigation/types.ts | 3 + src/pages/workspace/WorkspaceInitialPage.tsx | 6 + .../workspace/tags/WorkspaceTagsPage.tsx | 144 ++++++++++++++++++ 13 files changed, 205 insertions(+) create mode 100644 assets/images/simple-illustrations/simple-illustration__tag.svg create mode 100644 assets/images/tag.svg create mode 100644 src/pages/workspace/tags/WorkspaceTagsPage.tsx diff --git a/assets/images/simple-illustrations/simple-illustration__tag.svg b/assets/images/simple-illustrations/simple-illustration__tag.svg new file mode 100644 index 000000000000..96a1929634b6 --- /dev/null +++ b/assets/images/simple-illustrations/simple-illustration__tag.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/images/tag.svg b/assets/images/tag.svg new file mode 100644 index 000000000000..307edf166349 --- /dev/null +++ b/assets/images/tag.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 22ebffd52eec..bcb99e3c446e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -526,6 +526,10 @@ const ROUTES = { route: 'workspace/:policyID/categories', getRoute: (policyID: string) => `workspace/${policyID}/categories` as const, }, + WORKSPACE_TAGS: { + route: 'workspace/:policyID/tags', + getRoute: (policyID: string) => `workspace/${policyID}/tags` as const, + }, WORKSPACE_CATEGORIES_SETTINGS: { route: 'workspace/:policyID/categories/settings', getRoute: (policyID: string) => `workspace/${policyID}/categories/settings` as const, diff --git a/src/SCREENS.ts b/src/SCREENS.ts index ac75968e68b9..3faa7ea4c7f2 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -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', DESCRIPTION: 'Workspace_Profile_Description', diff --git a/src/components/Icon/Expensicons.ts b/src/components/Icon/Expensicons.ts index 4db22dee8256..e432d8bb6a9d 100644 --- a/src/components/Icon/Expensicons.ts +++ b/src/components/Icon/Expensicons.ts @@ -133,6 +133,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'; @@ -220,6 +221,7 @@ export { FlagLevelThree, Fullscreen, Folder, + Tag, Gallery, Gear, Globe, diff --git a/src/components/Icon/Illustrations.ts b/src/components/Icon/Illustrations.ts index f8c048ebc4c0..7f60ad3867c8 100644 --- a/src/components/Icon/Illustrations.ts +++ b/src/components/Icon/Illustrations.ts @@ -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'; @@ -146,4 +147,5 @@ export { Workflows, ThreeLeggedLaptopWoman, House, + Tag, }; diff --git a/src/languages/en.ts b/src/languages/en.ts index 4018eab57d2b..965ea401ff23 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1711,6 +1711,7 @@ export default { settings: 'Settings', reimburse: 'Reimbursements', categories: 'Categories', + tags: 'Tags', bills: 'Bills', invoices: 'Invoices', travel: 'Travel', @@ -1748,6 +1749,15 @@ export default { subtitle: 'Add a category to organize your spend.', }, }, + 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.', diff --git a/src/languages/es.ts b/src/languages/es.ts index 960b454137ea..3cafb6e2bf11 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1735,6 +1735,7 @@ export default { settings: 'Configuración', reimburse: 'Reembolsos', categories: 'Categorías', + tags: 'Etiquetas', bills: 'Pagar facturas', invoices: 'Enviar facturas', travel: 'Viajes', @@ -1772,6 +1773,15 @@ export default { subtitle: 'Añade una categoría para organizar tu gasto.', }, }, + tags: { + requiresTag: 'Los miembros deben etiquetar todos los gastos', + enableTag: 'Habilitar etiqueta', + subtitle: 'Las etiquetas agregan formas más detalladas de clasificar los costos.', + emptyTags: { + title: 'No has creado ninguna etiqueta', + subtitle: 'Agregue una etiqueta para realizar un seguimiento de proyectos, ubicaciones, departamentos y más.', + }, + }, 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.', diff --git a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx index 1e5d3639a32f..976699e31716 100644 --- a/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx +++ b/src/libs/Navigation/AppNavigator/Navigators/CentralPaneNavigator/BaseCentralPaneNavigator.tsx @@ -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() { diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 7a6211ebd283..60e132182947 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -67,6 +67,9 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.CATEGORIES]: { path: ROUTES.WORKSPACE_CATEGORIES.route, }, + [SCREENS.WORKSPACE.TAGS]: { + path: ROUTES.WORKSPACE_TAGS.route, + }, }, }, [SCREENS.NOT_FOUND]: '*', diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index e0bbbe95802f..41c3ac7f6e34 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -81,6 +81,9 @@ type CentralPaneNavigatorParamList = { [SCREENS.WORKSPACE.CATEGORIES]: { policyID: string; }; + [SCREENS.WORKSPACE.TAGS]: { + policyID: string; + }; }; type WorkspaceSwitcherNavigatorParamList = { diff --git a/src/pages/workspace/WorkspaceInitialPage.tsx b/src/pages/workspace/WorkspaceInitialPage.tsx index 571e4cafce74..a18d2667f1ea 100644 --- a/src/pages/workspace/WorkspaceInitialPage.tsx +++ b/src/pages/workspace/WorkspaceInitialPage.tsx @@ -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[] = [ diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx new file mode 100644 index 000000000000..27e52f2f1a26 --- /dev/null +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -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; +}; + +type WorkspaceTagsPageProps = WorkspaceTagsOnyxProps & StackScreenProps; + +function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { + const {isSmallScreenWidth} = useWindowDimensions(); + const styles = useThemeStyles(); + const theme = useTheme(); + const {translate} = useLocalize(); + const [selectedTags, setSelectedTags] = useState>({}); + const policyTagLists = useMemo(() => PolicyUtils.getTagLists(policyTags), [policyTags]); + const tagList = useMemo( + () => + policyTagLists + .map((tagList) => + Object.values(tagList.tags).map((value) => ({ + value: value.name, + text: value.name, + keyForList: value.name, + isSelected: !!selectedTags[value.name], + rightElement: ( + + + {value.enabled ? translate('workspace.common.enabled') : translate('workspace.common.disabled')} + + + + + + ), + })), + ) + .flat(), + [policyTags, 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 = () => ( + + {translate('common.name')} + {translate('statusPage.status')} + + ); + + return ( + + + + + + {translate('workspace.tags.subtitle')} + + {tagList.length ? ( + {}} + onSelectAll={toggleAllTags} + showScrollIndicator + ListItem={TableListItem} + customListHeader={getCustomListHeader()} + listHeaderWrapperStyle={[styles.ph9, styles.pv3, styles.pb5]} + /> + ) : ( + + )} + + + + ); +} + +WorkspaceTagsPage.displayName = 'WorkspaceTagsPage'; + +export default withOnyx({ + policyTags: { + key: ({route}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${route.params.policyID}`, + }, +})(WorkspaceTagsPage); From 8b52882ec6b508009fc6f30d924d5541cf29a150 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Mon, 4 Mar 2024 13:52:22 +0100 Subject: [PATCH 2/6] updated translations --- src/languages/es.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/es.ts b/src/languages/es.ts index 5d22b52ad401..9e63af9dc982 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1794,10 +1794,10 @@ export default { tags: { requiresTag: 'Los miembros deben etiquetar todos los gastos', enableTag: 'Habilitar etiqueta', - subtitle: 'Las etiquetas agregan formas más detalladas de clasificar los costos.', + subtitle: 'Las etiquetas añaden formas más detalladas de clasificar los costos.', emptyTags: { title: 'No has creado ninguna etiqueta', - subtitle: 'Agregue una etiqueta para realizar un seguimiento de proyectos, ubicaciones, departamentos y más.', + subtitle: 'Añade una etiqueta para realizar el seguimiento de proyectos, ubicaciones, departamentos y otros.', }, }, emptyWorkspace: { From 8454e0f3ae6f1fddbe49c08f0a7e147ac7e9282a Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Mon, 4 Mar 2024 13:53:22 +0100 Subject: [PATCH 3/6] fix lint --- src/pages/workspace/tags/WorkspaceTagsPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 27e52f2f1a26..1f3fc34a7be1 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -49,8 +49,8 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { const tagList = useMemo( () => policyTagLists - .map((tagList) => - Object.values(tagList.tags).map((value) => ({ + .map((policyTagList) => + Object.values(policyTagList.tags).map((value) => ({ value: value.name, text: value.name, keyForList: value.name, From c91e707ab8d96fe7b7342330ac5bd4917f307a13 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Mon, 4 Mar 2024 14:32:33 +0100 Subject: [PATCH 4/6] fix lint --- src/pages/workspace/tags/WorkspaceTagsPage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/tags/WorkspaceTagsPage.tsx b/src/pages/workspace/tags/WorkspaceTagsPage.tsx index 1f3fc34a7be1..c82740eff361 100644 --- a/src/pages/workspace/tags/WorkspaceTagsPage.tsx +++ b/src/pages/workspace/tags/WorkspaceTagsPage.tsx @@ -71,7 +71,7 @@ function WorkspaceTagsPage({policyTags, route}: WorkspaceTagsPageProps) { })), ) .flat(), - [policyTags, selectedTags, styles.alignSelfCenter, styles.disabledText, styles.flexRow, styles.p1, styles.pl2, theme.icon, translate], + [policyTagLists, selectedTags, styles.alignSelfCenter, styles.disabledText, styles.flexRow, styles.p1, styles.pl2, theme.icon, translate], ); const toggleTag = (tag: PolicyForList) => { From 7350a2dd234cd430b3cbe6fdf162b007ec29424c Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Mon, 4 Mar 2024 18:37:36 +0100 Subject: [PATCH 5/6] fixed icon --- assets/images/tag.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/images/tag.svg b/assets/images/tag.svg index 307edf166349..30dcc5a1a1d8 100644 --- a/assets/images/tag.svg +++ b/assets/images/tag.svg @@ -1,3 +1,3 @@ - + From 40ab003a2e1f0c3d6052b7885e618ed9feebe662 Mon Sep 17 00:00:00 2001 From: Artem Makushov Date: Mon, 4 Mar 2024 21:39:51 +0100 Subject: [PATCH 6/6] images update --- .../simple-illustration__tag.svg | 47 +++++++++++++------ assets/images/tag.svg | 13 ++++- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/assets/images/simple-illustrations/simple-illustration__tag.svg b/assets/images/simple-illustrations/simple-illustration__tag.svg index 96a1929634b6..0cac51679a5e 100644 --- a/assets/images/simple-illustrations/simple-illustration__tag.svg +++ b/assets/images/simple-illustrations/simple-illustration__tag.svg @@ -1,16 +1,33 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/assets/images/tag.svg b/assets/images/tag.svg index 30dcc5a1a1d8..f5e13b8135cb 100644 --- a/assets/images/tag.svg +++ b/assets/images/tag.svg @@ -1,3 +1,12 @@ - - + + + + +