From 66e1af22673a55cd91fc66e6f25aefe583766a92 Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 26 Oct 2024 00:19:44 +0530 Subject: [PATCH 01/52] add people to tag functaionality --- public/locales/en/translation.json | 5 +- public/locales/fr/translation.json | 5 +- public/locales/hi/translation.json | 5 +- public/locales/sp/translation.json | 5 +- public/locales/zh/translation.json | 5 +- src/GraphQl/Mutations/TagMutations.ts | 15 + src/GraphQl/Queries/userTagQueries.ts | 42 ++ .../AddPeopleToTag/AddPeopleToTag.module.css | 77 ++++ .../AddPeopleToTag/AddPeopleToTag.test.tsx | 194 +++++++++ .../AddPeopleToTag/AddPeopleToTag.tsx | 371 ++++++++++++++++++ .../AddPeopleToTag/AddPeopleToTagsMocks.ts | 169 ++++++++ src/screens/ManageTag/ManageTag.test.tsx | 7 +- src/screens/ManageTag/ManageTag.tsx | 43 +- src/screens/ManageTag/ManageTagMocks.ts | 84 ++++ src/utils/interfaces.ts | 39 +- 15 files changed, 1008 insertions(+), 58 deletions(-) create mode 100644 src/components/AddPeopleToTag/AddPeopleToTag.module.css create mode 100644 src/components/AddPeopleToTag/AddPeopleToTag.test.tsx create mode 100644 src/components/AddPeopleToTag/AddPeopleToTag.tsx create mode 100644 src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index a3415f199f..472af51ebf 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -332,7 +332,10 @@ "successfullyUnassigned": "Tag unassigned from user", "addPeople": "Add People", "add": "Add", - "subTags": "Sub Tags" + "subTags": "Sub Tags", + "assignedToAll": "Tag Assigned to All", + "successfullyAssignedToPeople": "Tag assigned successfully", + "assignPeople": "Assign" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 6f7332057b..4db1b1171f 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -332,7 +332,10 @@ "successfullyUnassigned": "Étiquette retirée de l'utilisateur", "addPeople": "Ajouter des personnes", "add": "Ajouter", - "subTags": "Sous-étiquettes" + "subTags": "Sous-étiquettes", + "assignedToAll": "Étiquette attribuée à tous", + "successfullyAssignedToPeople": "Étiquette attribuée avec succès", + "assignPeople": "Attribuer" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 95d704daca..b0ec38dcbd 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -332,7 +332,10 @@ "successfullyUnassigned": "उपयोगकर्ता से टैग हटा दिया गया", "addPeople": "लोगों को जोड़ें", "add": "जोड़ें", - "subTags": "उप-टैग्स" + "subTags": "उप-टैग्स", + "assignedToAll": "सभी को टैग असाइन किया गया", + "successfullyAssignedToPeople": "टैग सफलतापूर्वक असाइन किया गया", + "assignPeople": "असाइन करें" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index bd2959808d..6aeeeeece9 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -332,7 +332,10 @@ "successfullyUnassigned": "Etiqueta desasignada del usuario", "addPeople": "Agregar Personas", "add": "Agregar", - "subTags": "Subetiquetas" + "subTags": "Subetiquetas", + "assignedToAll": "Etiqueta asignada a todos", + "successfullyAssignedToPeople": "Etiqueta asignada con éxito", + "assignPeople": "Asignar" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index d5610594f6..ecfc2190a8 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -332,7 +332,10 @@ "successfullyUnassigned": "标签已从用户中取消分配", "addPeople": "添加人员", "add": "添加", - "subTags": "子标签" + "subTags": "子标签", + "assignedToAll": "标签分配给所有人", + "successfullyAssignedToPeople": "标签分配成功", + "assignPeople": "分配" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/GraphQl/Mutations/TagMutations.ts b/src/GraphQl/Mutations/TagMutations.ts index 2e92babce4..d97fefc246 100644 --- a/src/GraphQl/Mutations/TagMutations.ts +++ b/src/GraphQl/Mutations/TagMutations.ts @@ -72,3 +72,18 @@ export const REMOVE_USER_TAG = gql` } } `; + +/** + * GraphQL mutation to add people to tag. + * + * @param tagId - Id of the tag to be assigned. + * @param userIds - Ids of the users to assign to. + */ + +export const ADD_PEOPLE_TO_TAG = gql` + mutation AddPeopleToUserTag($tagId: ID!, $userIds: [ID!]!) { + addPeopleToUserTag(input: { tagId: $tagId, userIds: $userIds }) { + _id + } + } +`; diff --git a/src/GraphQl/Queries/userTagQueries.ts b/src/GraphQl/Queries/userTagQueries.ts index e0673d81d7..4dd1d283ac 100644 --- a/src/GraphQl/Queries/userTagQueries.ts +++ b/src/GraphQl/Queries/userTagQueries.ts @@ -84,6 +84,48 @@ export const USER_TAG_SUB_TAGS = gql` } `; +/** + * GraphQL query to retrieve organization members that aren't assigned a certain tag. + * + * @param id - The ID of the tag. + * @returns The list of organization members. + */ + +export const USER_TAGS_MEMBERS_TO_ASSIGN_TO = gql` + query UserTagDetails( + $id: ID! + $after: String + $before: String + $first: PositiveInt + $last: PositiveInt + ) { + getUserTag(id: $id) { + name + usersToAssignTo( + after: $after + before: $before + first: $first + last: $last + ) { + edges { + node { + _id + firstName + lastName + } + } + pageInfo { + startCursor + endCursor + hasNextPage + hasPreviousPage + } + totalCount + } + } + } +`; + /** * GraphQL query to retrieve the ancestor tags of a certain tag. * diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css new file mode 100644 index 0000000000..fb8599d96c --- /dev/null +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -0,0 +1,77 @@ +.errorContainer { + min-height: 100vh; +} + +.errorMessage { + margin-top: 25%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.errorIcon { + transform: scale(1.5); + color: var(--bs-danger); + margin-bottom: 1rem; +} + +.tableHeader { + background-color: var(--bs-primary); + color: var(--bs-white); + font-size: 1rem; +} + +.rowBackground { + background-color: var(--bs-white); + max-height: 120px; +} + +.scrollContainer { + max-height: 100px; /* Adjust as needed */ + overflow-y: auto; + margin-bottom: 1rem; +} + +.memberBadge { + display: flex; + align-items: center; + padding: 5px 10px; + border-radius: 12px; + background-color: #f8f9fa; /* Light background */ + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + max-width: calc(100% - 30px); /* Ensure it fits within the container */ +} + +.removeFilterIcon { + cursor: pointer; +} + +.scrollContainer { + max-height: 350px; /* Set your desired max height */ + overflow-y: auto; /* Enable vertical scrolling */ +} + +/* SimpleLoader.css */ +.simple-loader { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid rgba(0, 0, 0, 0.1); + border-top-color: #3498db; + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx new file mode 100644 index 0000000000..3094ed5f25 --- /dev/null +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -0,0 +1,194 @@ +import React, { act } from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import type { RenderResult } from '@testing-library/react'; +import { + render, + screen, + fireEvent, + cleanup, + waitFor, +} from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; + +import { store } from 'state/store'; +import userEvent from '@testing-library/user-event'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { toast } from 'react-toastify'; +import type { ApolloLink } from '@apollo/client'; +import type { InterfaceAddPeopleToTagProps } from './AddPeopleToTag'; +import AddPeopleToTag from './AddPeopleToTag'; +import i18n from 'utils/i18nForTest'; +import { MOCKS, MOCKS_ERROR } from './AddPeopleToTagsMocks'; + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_ERROR, true); + +async function wait(ms = 500): Promise { + await act(() => { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }); +} + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const translations = { + ...JSON.parse( + JSON.stringify(i18n.getDataByLanguage('en')?.translation.manageTag ?? {}), + ), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), +}; + +const props: InterfaceAddPeopleToTagProps = { + addPeopleToTagModalIsOpen: true, + hideAddPeopleToTagModal: () => {}, + refetchAssignedMembersData: () => {}, + t: (key: string) => translations[key], + tCommon: (key: string) => translations[key], +}; + +const renderAddPeopleToTagModal = ( + props: InterfaceAddPeopleToTagProps, + link: ApolloLink, +): RenderResult => { + return render( + + + + + + } + /> + + + + + , + ); +}; + +describe('Organisation Tags Page', () => { + beforeEach(() => { + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: 'orgId' }), + })); + // cache.reset(); + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('Component loads correctly', async () => { + const { getByText } = renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.addPeople)).toBeInTheDocument(); + }); + }); + + test('Renders error component when when query is unsuccessful', async () => { + const { queryByText } = renderAddPeopleToTagModal(props, link2); + + await wait(); + + await waitFor(() => { + expect(queryByText(translations.addPeople)).not.toBeInTheDocument(); + }); + }); + + test('Selects and deselects members to assign to', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[0]); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[1]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[1]); + + await waitFor(() => { + expect( + screen.getAllByTestId('clearSelectedMember')[0], + ).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('clearSelectedMember')[0]); + + await waitFor(() => { + expect(screen.getAllByTestId('deselectMemberBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('deselectMemberBtn')[0]); + }); + + test('Renders more members with infinite scroll', async () => { + const { getByText } = renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.addPeople)).toBeInTheDocument(); + }); + + // Find the infinite scroll div by test ID or another selector + const scrollableDiv = screen.getByTestId('scrollableDiv'); + + // Set scroll position to the bottom + fireEvent.scroll(scrollableDiv, { + target: { scrollY: scrollableDiv.scrollHeight }, + }); + + await waitFor(() => { + expect(getByText(translations.addPeople)).toBeInTheDocument(); + }); + }); + + test('Assigns tag to multiple people', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[0]); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[1]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[1]); + + await waitFor(() => { + expect(screen.getAllByTestId('selectMemberBtn')[2]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('selectMemberBtn')[2]); + + userEvent.click(screen.getByTestId('assignPeopleBtn')); + + await waitFor(() => { + expect(toast.success).toHaveBeenCalledWith( + translations.successfullyAssignedToPeople, + ); + }); + }); +}); diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx new file mode 100644 index 0000000000..c0dd82a7e1 --- /dev/null +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -0,0 +1,371 @@ +import type { ApolloError } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; +import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; +import { DataGrid } from '@mui/x-data-grid'; +import Loader from 'components/Loader/Loader'; +import { USER_TAGS_MEMBERS_TO_ASSIGN_TO } from 'GraphQl/Queries/userTagQueries'; +import type { ChangeEvent } from 'react'; +import React, { useState } from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; +import { useParams } from 'react-router-dom'; +import type { InterfaceQueryUserTagsMembersToAssignTo } from 'utils/interfaces'; +import styles from './AddPeopleToTag.module.css'; +import { dataGridStyle } from 'utils/organizationTagsUtils'; +import { Stack } from '@mui/material'; +import { toast } from 'react-toastify'; +import { ADD_PEOPLE_TO_TAG } from 'GraphQl/Mutations/TagMutations'; +import InfiniteScroll from 'react-infinite-scroll-component'; +import { WarningAmberRounded } from '@mui/icons-material'; + +/** + * Props for the `AddPeopleToTag` component. + */ +export interface InterfaceAddPeopleToTagProps { + addPeopleToTagModalIsOpen: boolean; + hideAddPeopleToTagModal: () => void; + refetchAssignedMembersData: () => void; + t: (key: string) => string; + tCommon: (key: string) => string; +} + +interface InterfaceMemberData { + _id: string; + firstName: string; + lastName: string; +} + +const AddPeopleToTag: React.FC = ({ + addPeopleToTagModalIsOpen, + hideAddPeopleToTagModal, + refetchAssignedMembersData, + t, + tCommon, +}) => { + const { tagId: currentTagId } = useParams(); + + const [assignToMembers, setAssignToMembers] = useState( + [], + ); + + const { + data: userTagsMembersToAssignToData, + loading: userTagsMembersToAssignToLoading, + error: userTagsMembersToAssignToError, + fetchMore: fetchMoreMembersToAssignTo, + }: { + data?: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }; + loading: boolean; + error?: ApolloError; + fetchMore: (options: { + variables: { + after?: string | null; + first?: number | null; + }; + updateQuery?: ( + previousQueryResult: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }, + options: { + fetchMoreResult: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }; + }, + ) => { getUserTag: InterfaceQueryUserTagsMembersToAssignTo }; + }) => Promise; + } = useQuery(USER_TAGS_MEMBERS_TO_ASSIGN_TO, { + variables: { + id: currentTagId, + first: 7, + }, + skip: !addPeopleToTagModalIsOpen, + }); + + const loadMoreMembersToAssignTo = (): void => { + fetchMoreMembersToAssignTo({ + variables: { + first: 7, // Load 7 more items + after: + userTagsMembersToAssignToData?.getUserTag.usersToAssignTo.pageInfo + .endCursor, // Fetch after the last loaded cursor + }, + updateQuery: ( + prevResult: { getUserTag: InterfaceQueryUserTagsMembersToAssignTo }, + { + fetchMoreResult, + }: { + fetchMoreResult: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }; + }, + ) => { + if (!fetchMoreResult) return prevResult; + + return { + getUserTag: { + ...fetchMoreResult.getUserTag, + usersToAssignTo: { + ...fetchMoreResult.getUserTag.usersToAssignTo, + edges: [ + ...prevResult.getUserTag.usersToAssignTo.edges, + ...fetchMoreResult.getUserTag.usersToAssignTo.edges, + ], + }, + }, + }; + }, + }); + }; + + const userTagMembersToAssignTo = + userTagsMembersToAssignToData?.getUserTag.usersToAssignTo.edges.map( + (edge) => edge.node, + ); + + const handleAddOrRemoveMember = (member: InterfaceMemberData): void => { + setAssignToMembers((prevMembers) => { + const isAssigned = prevMembers.some((m) => m._id === member._id); + if (isAssigned) { + return prevMembers.filter((m) => m._id !== member._id); + } else { + return [...prevMembers, member]; + } + }); + }; + + const removeMember = (id: string): void => { + setAssignToMembers((prevMembers) => + prevMembers.filter((m) => m._id !== id), + ); + }; + + const [addPeople, { loading: addPeopleToTagLoading }] = + useMutation(ADD_PEOPLE_TO_TAG); + + const addPeopleToCurrentTag = async ( + e: ChangeEvent, + ): Promise => { + e.preventDefault(); + + try { + const { data } = await addPeople({ + variables: { + tagId: currentTagId, + userIds: assignToMembers.map((member) => member._id), + }, + }); + + if (data) { + toast.success(t('successfullyAssignedToPeople')); + refetchAssignedMembersData(); + hideAddPeopleToTagModal(); + setAssignToMembers([]); + } + } catch (error: unknown) { + /* istanbul ignore next */ + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + + if (userTagsMembersToAssignToError) { + return ( +
+
+ +
+ Error occured while loading members +
+ {userTagsMembersToAssignToError.message} +
+
+
+ ); + } + + const columns: GridColDef[] = [ + { + field: 'id', + headerName: '#', + minWidth: 100, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return
{params.row.id}
; + }, + }, + { + field: 'userName', + headerName: 'User Name', + flex: 2, + minWidth: 100, + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( +
+ {params.row.firstName + ' ' + params.row.lastName} +
+ ); + }, + }, + { + field: 'actions', + headerName: 'Actions', + flex: 1, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + const isToBeAssigned = assignToMembers.some( + (member) => member._id === params.row._id, + ); + + return ( + + ); + }, + }, + ]; + + return ( + <> + + + {t('addPeople')} + +
+ + {userTagsMembersToAssignToLoading ? ( + + ) : ( + <> +
+ {assignToMembers.length === 0 ? ( +
+ No one selected +
+ ) : ( + assignToMembers.map((member) => ( +
+ {member.firstName} {member.lastName} + removeMember(member._id)} + data-testid="clearSelectedMember" + /> +
+ )) + )} +
+ +
+ +
+
+ } + scrollableTarget="scrollableDiv" + > + row._id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('assignedToAll')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagMembersToAssignTo?.map( + (membersToAssignTo, index) => ({ + id: index + 1, + ...membersToAssignTo, + }), + )} + columns={columns} + isRowSelectable={() => false} + /> +
+
+ + )} +
+ + + + + +
+ + ); +}; + +export default AddPeopleToTag; diff --git a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts new file mode 100644 index 0000000000..ab185bb858 --- /dev/null +++ b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts @@ -0,0 +1,169 @@ +import { ADD_PEOPLE_TO_TAG } from 'GraphQl/Mutations/TagMutations'; +import { USER_TAGS_MEMBERS_TO_ASSIGN_TO } from 'GraphQl/Queries/userTagQueries'; + +export const MOCKS = [ + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: 7, + }, + }, + result: { + data: { + getUserTag: { + name: 'tag1', + usersToAssignTo: { + edges: [ + { + node: { + _id: '1', + firstName: 'member', + lastName: '1', + }, + cursor: '1', + }, + { + node: { + _id: '2', + firstName: 'member', + lastName: '2', + }, + cursor: '2', + }, + { + node: { + _id: '3', + firstName: 'member', + lastName: '3', + }, + cursor: '3', + }, + { + node: { + _id: '4', + firstName: 'member', + lastName: '4', + }, + cursor: '4', + }, + { + node: { + _id: '5', + firstName: 'member', + lastName: '5', + }, + cursor: '5', + }, + { + node: { + _id: '6', + firstName: 'member', + lastName: '6', + }, + cursor: '6', + }, + { + node: { + _id: '7', + firstName: 'member', + lastName: '7', + }, + cursor: '7', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '7', + hasNextPage: true, + hasPreviousPage: false, + }, + totalCount: 10, + }, + }, + }, + }, + }, + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: 7, + after: '7', + }, + }, + result: { + data: { + getUserTag: { + name: 'tag1', + usersToAssignTo: { + edges: [ + { + node: { + _id: '8', + firstName: 'member', + lastName: '8', + }, + cursor: '8', + }, + { + node: { + _id: '9', + firstName: 'member', + lastName: '9', + }, + cursor: '9', + }, + { + node: { + _id: '10', + firstName: 'member', + lastName: '10', + }, + cursor: '10', + }, + ], + pageInfo: { + startCursor: '8', + endCursor: '10', + hasNextPage: false, + hasPreviousPage: true, + }, + totalCount: 10, + }, + }, + }, + }, + }, + { + request: { + query: ADD_PEOPLE_TO_TAG, + variables: { + tagId: '1', + userIds: ['1', '3', '5'], + }, + }, + result: { + data: { + addPeopleToUserTag: { + _id: '1', + }, + }, + }, + }, +]; + +export const MOCKS_ERROR = [ + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: 7, + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index 38ebba7402..d497d2a0a8 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -60,6 +60,7 @@ const cache = new InMemoryCache({ getUserTag: { keyArgs: false, merge(existing = {}, incoming) { + console.log(existing); return incoming; }, }, @@ -99,7 +100,7 @@ const renderManageTag = (link: ApolloLink): RenderResult => { ); }; -describe('Organisation Tags Page', () => { +describe('Manage Tag Page', () => { beforeEach(() => { jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), @@ -290,7 +291,9 @@ describe('Organisation Tags Page', () => { userEvent.click(screen.getByTestId('unassignTagModalSubmitBtn')); await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.successfullyUnassigned); + expect(toast.success).toHaveBeenCalledWith( + translations.successfullyUnassigned, + ); }); }); }); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 86a44fb169..25b39e884d 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -23,6 +23,7 @@ import { USER_TAG_ANCESTORS, USER_TAGS_ASSIGNED_MEMBERS, } from 'GraphQl/Queries/userTagQueries'; +import AddPeopleToTag from 'components/AddPeopleToTag/AddPeopleToTag'; /** * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/managetag/:tagId'. @@ -432,41 +433,13 @@ function ManageTag(): JSX.Element { {/* Add People To Tag Modal */} - - - {t('addPeople')} - -
- - - - - - -
-
+ {/* Unassign Tag Modal */} Date: Sat, 26 Oct 2024 00:45:59 +0530 Subject: [PATCH 02/52] minor change --- src/GraphQl/Queries/userTagQueries.ts | 2 +- src/components/AddPeopleToTag/AddPeopleToTag.test.tsx | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/GraphQl/Queries/userTagQueries.ts b/src/GraphQl/Queries/userTagQueries.ts index 4dd1d283ac..9e1a11daa3 100644 --- a/src/GraphQl/Queries/userTagQueries.ts +++ b/src/GraphQl/Queries/userTagQueries.ts @@ -92,7 +92,7 @@ export const USER_TAG_SUB_TAGS = gql` */ export const USER_TAGS_MEMBERS_TO_ASSIGN_TO = gql` - query UserTagDetails( + query GetMembersToAssignTo( $id: ID! $after: String $before: String diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx index 3094ed5f25..0e630f35bd 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -153,12 +153,17 @@ describe('Organisation Tags Page', () => { // Find the infinite scroll div by test ID or another selector const scrollableDiv = screen.getByTestId('scrollableDiv'); + const initialMemberDataLength = screen.getAllByTestId('memberName').length; + // Set scroll position to the bottom fireEvent.scroll(scrollableDiv, { target: { scrollY: scrollableDiv.scrollHeight }, }); await waitFor(() => { + const finalMemberDataLength = screen.getAllByTestId('memberName').length; + expect(finalMemberDataLength).toBeGreaterThan(initialMemberDataLength); + expect(getByText(translations.addPeople)).toBeInTheDocument(); }); }); From 689ad9063090fa30378ea63e609ddff554f561a6 Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 26 Oct 2024 01:16:21 +0530 Subject: [PATCH 03/52] more translations --- public/locales/en/translation.json | 6 +++++- public/locales/fr/translation.json | 6 +++++- public/locales/hi/translation.json | 6 +++++- public/locales/sp/translation.json | 6 +++++- public/locales/zh/translation.json | 6 +++++- src/components/AddPeopleToTag/AddPeopleToTag.test.tsx | 11 +++++------ src/components/AddPeopleToTag/AddPeopleToTag.tsx | 8 ++++---- 7 files changed, 34 insertions(+), 15 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 472af51ebf..83bb999138 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -335,7 +335,11 @@ "subTags": "Sub Tags", "assignedToAll": "Tag Assigned to All", "successfullyAssignedToPeople": "Tag assigned successfully", - "assignPeople": "Assign" + "assignPeople": "Assign", + "errorOccurredWhileLoadingMembers": "Error occured while loading members", + "userName": "User Name", + "actions": "Actions", + "noOneSelected": "No One Selected" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 4db1b1171f..cd560dcda6 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -335,7 +335,11 @@ "subTags": "Sous-étiquettes", "assignedToAll": "Étiquette attribuée à tous", "successfullyAssignedToPeople": "Étiquette attribuée avec succès", - "assignPeople": "Attribuer" + "assignPeople": "Attribuer", + "errorOccurredWhileLoadingMembers": "Erreur survenue lors du chargement des membres", + "userName": "Nom d'utilisateur", + "actions": "Actions", + "noOneSelected": "Personne sélectionnée" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index b0ec38dcbd..58d0aa5e57 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -335,7 +335,11 @@ "subTags": "उप-टैग्स", "assignedToAll": "सभी को टैग असाइन किया गया", "successfullyAssignedToPeople": "टैग सफलतापूर्वक असाइन किया गया", - "assignPeople": "असाइन करें" + "assignPeople": "असाइन करें", + "errorOccurredWhileLoadingMembers": "सदस्यों को लोड करते समय त्रुटि हुई", + "userName": "उपयोगकर्ता नाम", + "actions": "क्रियाएँ", + "noOneSelected": "कोई चयनित नहीं" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 6aeeeeece9..48c8f9940d 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -335,7 +335,11 @@ "subTags": "Subetiquetas", "assignedToAll": "Etiqueta asignada a todos", "successfullyAssignedToPeople": "Etiqueta asignada con éxito", - "assignPeople": "Asignar" + "assignPeople": "Asignar", + "errorOccurredWhileLoadingMembers": "Error al cargar los miembros", + "userName": "Nombre de usuario", + "actions": "Acciones", + "noOneSelected": "Nadie seleccionado" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index ecfc2190a8..9fb3965fd6 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -335,7 +335,11 @@ "subTags": "子标签", "assignedToAll": "标签分配给所有人", "successfullyAssignedToPeople": "标签分配成功", - "assignPeople": "分配" + "assignPeople": "分配", + "errorOccurredWhileLoadingMembers": "加载成员时出错", + "userName": "用户名", + "actions": "操作", + "noOneSelected": "未选择任何人" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx index 0e630f35bd..4b24694a25 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -1,4 +1,4 @@ -import React, { act } from 'react'; +import React from 'react'; import { MockedProvider } from '@apollo/react-testing'; import type { RenderResult } from '@testing-library/react'; import { @@ -26,11 +26,10 @@ import { MOCKS, MOCKS_ERROR } from './AddPeopleToTagsMocks'; const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_ERROR, true); -async function wait(ms = 500): Promise { - await act(() => { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); +async function wait(): Promise { + await waitFor(() => { + // The waitFor utility automatically uses optimal timing + return Promise.resolve(); }); } diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index c0dd82a7e1..cf023353c6 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -176,7 +176,7 @@ const AddPeopleToTag: React.FC = ({
- Error occured while loading members + {t('errorOccurredWhileLoadingMembers')}
{userTagsMembersToAssignToError.message}
@@ -200,7 +200,7 @@ const AddPeopleToTag: React.FC = ({ }, { field: 'userName', - headerName: 'User Name', + headerName: t('userName'), flex: 2, minWidth: 100, sortable: false, @@ -215,7 +215,7 @@ const AddPeopleToTag: React.FC = ({ }, { field: 'actions', - headerName: 'Actions', + headerName: t('actions'), flex: 1, align: 'center', minWidth: 100, @@ -270,7 +270,7 @@ const AddPeopleToTag: React.FC = ({ > {assignToMembers.length === 0 ? (
- No one selected + {t('noOneSelected')}
) : ( assignToMembers.map((member) => ( From abaf86e6f916f0e364ed0005dde51107b65f9c8d Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 26 Oct 2024 11:10:00 +0530 Subject: [PATCH 04/52] minor change --- src/components/AddPeopleToTag/AddPeopleToTag.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index cf023353c6..e2ca6ca89d 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -16,6 +16,7 @@ import { toast } from 'react-toastify'; import { ADD_PEOPLE_TO_TAG } from 'GraphQl/Mutations/TagMutations'; import InfiniteScroll from 'react-infinite-scroll-component'; import { WarningAmberRounded } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; /** * Props for the `AddPeopleToTag` component. @@ -43,6 +44,8 @@ const AddPeopleToTag: React.FC = ({ }) => { const { tagId: currentTagId } = useParams(); + const { t: tErrors } = useTranslation('error'); + const [assignToMembers, setAssignToMembers] = useState( [], ); @@ -164,9 +167,9 @@ const AddPeopleToTag: React.FC = ({ } } catch (error: unknown) { /* istanbul ignore next */ - if (error instanceof Error) { - toast.error(error.message); - } + const errorMessage = + error instanceof Error ? error.message : tErrors('unknownError'); + toast.error(errorMessage); } }; From 7b7b6bf164aea206b998feb077c882eae6707e2e Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 26 Oct 2024 11:19:26 +0530 Subject: [PATCH 05/52] add a variable for page size --- src/components/AddPeopleToTag/AddPeopleToTag.tsx | 9 ++++++--- src/utils/organizationTagsUtils.ts | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index e2ca6ca89d..73066d2f0f 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -10,7 +10,10 @@ import { Modal, Form, Button } from 'react-bootstrap'; import { useParams } from 'react-router-dom'; import type { InterfaceQueryUserTagsMembersToAssignTo } from 'utils/interfaces'; import styles from './AddPeopleToTag.module.css'; -import { dataGridStyle } from 'utils/organizationTagsUtils'; +import { + ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + dataGridStyle, +} from 'utils/organizationTagsUtils'; import { Stack } from '@mui/material'; import { toast } from 'react-toastify'; import { ADD_PEOPLE_TO_TAG } from 'GraphQl/Mutations/TagMutations'; @@ -80,7 +83,7 @@ const AddPeopleToTag: React.FC = ({ } = useQuery(USER_TAGS_MEMBERS_TO_ASSIGN_TO, { variables: { id: currentTagId, - first: 7, + first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, }, skip: !addPeopleToTagModalIsOpen, }); @@ -88,7 +91,7 @@ const AddPeopleToTag: React.FC = ({ const loadMoreMembersToAssignTo = (): void => { fetchMoreMembersToAssignTo({ variables: { - first: 7, // Load 7 more items + first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, after: userTagsMembersToAssignToData?.getUserTag.usersToAssignTo.pageInfo .endCursor, // Fetch after the last loaded cursor diff --git a/src/utils/organizationTagsUtils.ts b/src/utils/organizationTagsUtils.ts index 5f81497c65..6f33c26df7 100644 --- a/src/utils/organizationTagsUtils.ts +++ b/src/utils/organizationTagsUtils.ts @@ -21,3 +21,5 @@ export const dataGridStyle = { borderRadius: '0.1rem', }, }; + +export const ADD_PEOPLE_TO_TAGS_QUERY_LIMIT = 7; From 72b6fd3d13a36dd82d05b8325a867a363ee33996 Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 26 Oct 2024 17:30:23 +0530 Subject: [PATCH 06/52] add tag actions --- public/locales/en/translation.json | 14 +- public/locales/fr/translation.json | 14 +- public/locales/hi/translation.json | 14 +- public/locales/sp/translation.json | 14 +- public/locales/zh/translation.json | 14 +- src/GraphQl/Mutations/TagMutations.ts | 34 ++ .../AddPeopleToTag/AddPeopleToTag.tsx | 2 +- .../TagActions/TagActions.module.css | 174 +++++++ src/components/TagActions/TagActions.tsx | 434 ++++++++++++++++++ src/components/TagActions/TagNode.tsx | 179 ++++++++ src/screens/ManageTag/ManageTag.tsx | 233 +++++++++- src/utils/interfaces.ts | 34 +- src/utils/organizationTagsUtils.ts | 3 + 13 files changed, 1124 insertions(+), 39 deletions(-) create mode 100644 src/components/TagActions/TagActions.module.css create mode 100644 src/components/TagActions/TagActions.tsx create mode 100644 src/components/TagActions/TagNode.tsx diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 83bb999138..94668f8fed 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -335,11 +335,21 @@ "subTags": "Sub Tags", "assignedToAll": "Tag Assigned to All", "successfullyAssignedToPeople": "Tag assigned successfully", - "assignPeople": "Assign", "errorOccurredWhileLoadingMembers": "Error occured while loading members", "userName": "User Name", "actions": "Actions", - "noOneSelected": "No One Selected" + "noOneSelected": "No One Selected", + "assignToTags": "Assign to Tags", + "removeFromTags": "Remove from Tags", + "assign": "Assign", + "remove": "Remove", + "successfullyAssignedToTags": "Successfully Assigned to Tags", + "successfullyRemovedFromTags": "Successfully Removed from Tags", + "errorOccurredWhileLoadingOrganizationUserTags": "Error occured while loading organization tags", + "removeUserTag": "Delete Tag", + "removeUserTagMessage": "Do you want to delete this tag? It delete all the sub tags and all the associations.", + "tagDetails": "Tag Details", + "tagName": "Name" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index cd560dcda6..9386106268 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -330,7 +330,6 @@ "unassignUserTag": "Désassigner l'étiquette", "unassignUserTagMessage": "Voulez-vous retirer l'étiquette de cet utilisateur?", "successfullyUnassigned": "Étiquette retirée de l'utilisateur", - "addPeople": "Ajouter des personnes", "add": "Ajouter", "subTags": "Sous-étiquettes", "assignedToAll": "Étiquette attribuée à tous", @@ -339,7 +338,18 @@ "errorOccurredWhileLoadingMembers": "Erreur survenue lors du chargement des membres", "userName": "Nom d'utilisateur", "actions": "Actions", - "noOneSelected": "Personne sélectionnée" + "noOneSelected": "Personne sélectionnée", + "assignToTags": "Attribuer aux tags", + "removeFromTags": "Retirer des tags", + "assign": "Attribuer", + "remove": "Retirer", + "successfullyAssignedToTags": "Attribué aux tags avec succès", + "successfullyRemovedFromTags": "Retiré des tags avec succès", + "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des tags de l'organisation", + "removeUserTag": "Supprimer le tag", + "removeUserTagMessage": "Voulez-vous supprimer ce tag ? Cela supprimera tous les sous-tags et toutes les associations.", + "tagDetails": "Détails du tag", + "tagName": "Nom" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 58d0aa5e57..a07e2e4c72 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -335,11 +335,21 @@ "subTags": "उप-टैग्स", "assignedToAll": "सभी को टैग असाइन किया गया", "successfullyAssignedToPeople": "टैग सफलतापूर्वक असाइन किया गया", - "assignPeople": "असाइन करें", "errorOccurredWhileLoadingMembers": "सदस्यों को लोड करते समय त्रुटि हुई", "userName": "उपयोगकर्ता नाम", "actions": "क्रियाएँ", - "noOneSelected": "कोई चयनित नहीं" + "noOneSelected": "कोई चयनित नहीं", + "assignToTags": "टैग्स को असाइन करें", + "removeFromTags": "टैग्स से हटाएं", + "assign": "असाइन करें", + "remove": "हटाएं", + "successfullyAssignedToTags": "सफलतापूर्वक टैग्स को असाइन किया गया", + "successfullyRemovedFromTags": "सफलतापूर्वक टैग्स से हटाया गया", + "errorOccurredWhileLoadingOrganizationUserTags": "संगठन टैग्स को लोड करते समय त्रुटि हुई", + "removeUserTag": "टैग हटाएं", + "removeUserTagMessage": "क्या आप इस टैग को हटाना चाहते हैं? यह सभी उप-टैग्स और सभी संबंधों को हटा देगा।", + "tagDetails": "टैग विवरण", + "tagName": "नाम" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 48c8f9940d..937db99b49 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -335,11 +335,21 @@ "subTags": "Subetiquetas", "assignedToAll": "Etiqueta asignada a todos", "successfullyAssignedToPeople": "Etiqueta asignada con éxito", - "assignPeople": "Asignar", "errorOccurredWhileLoadingMembers": "Error al cargar los miembros", "userName": "Nombre de usuario", "actions": "Acciones", - "noOneSelected": "Nadie seleccionado" + "noOneSelected": "Nadie seleccionado", + "assignToTags": "Asignar a etiquetas", + "removeFromTags": "Eliminar de etiquetas", + "assign": "Asignar", + "remove": "Eliminar", + "successfullyAssignedToTags": "Asignado a etiquetas con éxito", + "successfullyRemovedFromTags": "Eliminado de etiquetas con éxito", + "errorOccurredWhileLoadingOrganizationUserTags": "Error al cargar las etiquetas de la organización", + "removeUserTag": "Eliminar etiqueta", + "removeUserTagMessage": "¿Desea eliminar esta etiqueta? Esto eliminará todas las subetiquetas y todas las asociaciones.", + "tagDetails": "Detalles de la etiqueta", + "tagName": "Nombre" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 9fb3965fd6..55f36947f2 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -335,11 +335,21 @@ "subTags": "子标签", "assignedToAll": "标签分配给所有人", "successfullyAssignedToPeople": "标签分配成功", - "assignPeople": "分配", "errorOccurredWhileLoadingMembers": "加载成员时出错", "userName": "用户名", "actions": "操作", - "noOneSelected": "未选择任何人" + "noOneSelected": "未选择任何人", + "assignToTags": "分配到标签", + "removeFromTags": "从标签中移除", + "assign": "分配", + "remove": "移除", + "successfullyAssignedToTags": "成功分配到标签", + "successfullyRemovedFromTags": "成功从标签中移除", + "errorOccurredWhileLoadingOrganizationUserTags": "加载组织标签时出错", + "removeUserTag": "删除标签", + "removeUserTagMessage": "您要删除此标签吗?这将删除所有子标签和所有关联。", + "tagDetails": "标签详情", + "tagName": "名称" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/GraphQl/Mutations/TagMutations.ts b/src/GraphQl/Mutations/TagMutations.ts index d97fefc246..9f8ed1ec61 100644 --- a/src/GraphQl/Mutations/TagMutations.ts +++ b/src/GraphQl/Mutations/TagMutations.ts @@ -87,3 +87,37 @@ export const ADD_PEOPLE_TO_TAG = gql` } } `; + +/** + * GraphQL mutation to assign people to multiple tags. + * + * @param currentTagId - Id of the current tag. + * @param selectedTagIds - Ids of the selected tags to be assined. + */ + +export const ASSIGN_TO_TAGS = gql` + mutation AssignToUserTags($currentTagId: ID!, $selectedTagIds: [ID!]!) { + assignToUserTags( + input: { currentTagId: $currentTagId, selectedTagIds: $selectedTagIds } + ) { + _id + } + } +`; + +/** + * GraphQL mutation to remove people from multiple tags. + * + * @param currentTagId - Id of the current tag. + * @param selectedTagIds - Ids of the selected tags to be removed from. + */ + +export const REMOVE_FROM_TAGS = gql` + mutation RemoveFromUserTags($currentTagId: ID!, $selectedTagIds: [ID!]!) { + removeFromUserTags( + input: { currentTagId: $currentTagId, selectedTagIds: $selectedTagIds } + ) { + _id + } + } +`; diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index 73066d2f0f..a43a47b006 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -365,7 +365,7 @@ const AddPeopleToTag: React.FC = ({ variant="primary" data-testid="assignPeopleBtn" > - {t('assignPeople')} + {t('assign')} diff --git a/src/components/TagActions/TagActions.module.css b/src/components/TagActions/TagActions.module.css new file mode 100644 index 0000000000..e667adb96e --- /dev/null +++ b/src/components/TagActions/TagActions.module.css @@ -0,0 +1,174 @@ +.btnsContainer { + display: flex; + margin: 2rem 0; +} + +.btnsContainer .btnsBlock { + display: flex; + width: max-content; +} + +.btnsContainer .btnsBlock button { + margin-left: 1rem; + display: flex; + justify-content: center; + align-items: center; +} + +.btnsContainer .input { + flex: 1; + position: relative; + max-width: 60%; + justify-content: space-between; +} + +.btnsContainer input { + outline: 1px solid var(--bs-gray-400); +} + +.btnsContainer .input button { + width: 52px; +} + +@media (max-width: 1020px) { + .btnsContainer { + flex-direction: column; + margin: 1.5rem 0; + } + + .btnsContainer .btnsBlock { + margin: 1.5rem 0 0 0; + justify-content: space-between; + } + + .btnsContainer .btnsBlock button { + margin: 0; + } + + .btnsContainer .btnsBlock div button { + margin-right: 1.5rem; + } +} + +/* For mobile devices */ + +@media (max-width: 520px) { + .btnsContainer { + margin-bottom: 0; + } + + .btnsContainer .btnsBlock { + display: block; + margin-top: 1rem; + margin-right: 0; + } + + .btnsContainer .btnsBlock div { + flex: 1; + } + + .btnsContainer .btnsBlock div[title='Sort organizations'] { + margin-right: 0.5rem; + } + + .btnsContainer .btnsBlock button { + margin-bottom: 1rem; + margin-right: 0; + width: 100%; + } +} + +.errorContainer { + min-height: 100vh; +} + +.errorMessage { + margin-top: 25%; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} + +.errorIcon { + transform: scale(1.5); + color: var(--bs-danger); + margin-bottom: 1rem; +} + +.tableHeader { + background-color: var(--bs-primary); + color: var(--bs-white); + font-size: 1rem; +} + +.rowBackground { + background-color: var(--bs-white); + max-height: 120px; +} + +.tagsBreadCrumbs { + color: var(--bs-gray); + cursor: pointer; +} + +.tagsBreadCrumbs:hover { + color: var(--bs-blue); + font-weight: 600; + text-decoration: underline; +} + +.scrollContainer { + max-height: 100px; /* Adjust as needed */ + overflow-y: auto; + margin-bottom: 1rem; +} + +.tagBadge { + display: flex; + align-items: center; + padding: 5px 10px; + border-radius: 12px; + background-color: #f8f9fa; /* Light background */ + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + max-width: calc(100% - 30px); /* Ensure it fits within the container */ +} + +.removeFilterIcon { + cursor: pointer; +} + +.scrContainer { + max-height: 300px; + overflow: scroll; + /* padding-right: 8px; */ +} + +.allTagsHeading { + color: rgb(77, 76, 76); + font-weight: 600; +} + +/* SimpleLoader.css */ +.simpleLoader { + display: flex; + justify-content: start; + align-items: center; + width: 100%; + height: 100%; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid var(--bs-gray); + border-top-color: var(--bs-gray); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx new file mode 100644 index 0000000000..93f2f071eb --- /dev/null +++ b/src/components/TagActions/TagActions.tsx @@ -0,0 +1,434 @@ +import type { ApolloError } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; +import Loader from 'components/Loader/Loader'; +import { USER_TAG_ANCESTORS } from 'GraphQl/Queries/userTagQueries'; +import type { FormEvent } from 'react'; +import React, { useEffect, useState } from 'react'; +import { Modal, Form, Button } from 'react-bootstrap'; +import { useParams } from 'react-router-dom'; +import type { + InterfaceQueryOrganizationUserTags, + InterfaceTagData, +} from 'utils/interfaces'; +import styles from './TagActions.module.css'; +import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; +import { + ASSIGN_TO_TAGS, + REMOVE_FROM_TAGS, +} from 'GraphQl/Mutations/TagMutations'; +import { toast } from 'react-toastify'; +import type { TagActionType } from 'utils/organizationTagsUtils'; +import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; +import InfiniteScroll from 'react-infinite-scroll-component'; +import { WarningAmberRounded } from '@mui/icons-material'; +import TagNode from './TagNode'; + +interface InterfaceUserTagsAncestorData { + _id: string; + name: string; +} + +/** + * Props for the `AssignToTags` component. + */ +interface InterfaceAssignToTagsProps { + assignToTagsModalIsOpen: boolean; + hideAssignToTagsModal: () => void; + tagActionType: TagActionType; + t: (key: string) => string; + tCommon: (key: string) => string; +} + +const TagActions: React.FC = ({ + assignToTagsModalIsOpen, + hideAssignToTagsModal, + tagActionType, + t, + tCommon, +}) => { + const { orgId, tagId: currentTagId } = useParams(); + + const { + data: orgUserTagsData, + loading: orgUserTagsLoading, + error: orgUserTagsError, + fetchMore: orgUserTagsFetchMore, + }: { + data?: { + organizations: InterfaceQueryOrganizationUserTags[]; + }; + loading: boolean; + error?: ApolloError; + refetch: () => void; + fetchMore: (options: { + variables: { + first: number; + after?: string; + }; + updateQuery: ( + previousResult: { organizations: InterfaceQueryOrganizationUserTags[] }, + options: { + fetchMoreResult?: { + organizations: InterfaceQueryOrganizationUserTags[]; + }; + }, + ) => { organizations: InterfaceQueryOrganizationUserTags[] }; + }) => void; + } = useQuery(ORGANIZATION_USER_TAGS_LIST, { + variables: { + id: orgId, + first: TAGS_QUERY_LIMIT, + }, + skip: !assignToTagsModalIsOpen, + }); + + const userTagsList = orgUserTagsData?.organizations[0].userTags.edges.map( + (edge) => edge.node, + ); + + const [checkedTagId, setCheckedTagId] = useState(null); + const [uncheckedTagId, setUnheckedTagId] = useState(null); + + // tags that we have selected to assigned + const [selectedTags, setSelectedTags] = useState([]); + + // tags that we have checked, it is there to differentiate between the selected tags and all the checked tags + // i.e. selected tags would only be the ones we select, but checked tags will also include the selected tag's ancestors + const [checkedTags, setCheckedTags] = useState>(new Set()); + + // next 3 states are there to keep track of the ancestor tags of the the tags that we have selected + // i.e. when we check a tag, all of it's ancestor tags will be checked too + // indicating that the users will be assigned all of the ancestor tags as well + const [addAncestorTagsData, setAddAncestorTagsData] = useState< + Set + >(new Set()); + const [removeAncestorTagsData, setRemoveAncestorTagsData] = useState< + Set + >(new Set()); + const [ancestorTagsDataMap, setAncestorTagsDataMap] = useState(new Map()); + + useEffect(() => { + const newCheckedTags = new Set(checkedTags); + const newAncestorTagsDataMap = new Map(ancestorTagsDataMap); + addAncestorTagsData.forEach( + (ancestorTag: InterfaceUserTagsAncestorData) => { + const prevAncestorTagValue = ancestorTagsDataMap.get(ancestorTag._id); + newAncestorTagsDataMap.set( + ancestorTag._id, + prevAncestorTagValue ? prevAncestorTagValue + 1 : 1, + ); + newCheckedTags.add(ancestorTag._id); + }, + ); + + setCheckedTags(newCheckedTags); + setAncestorTagsDataMap(newAncestorTagsDataMap); + }, [addAncestorTagsData]); + + useEffect(() => { + const newCheckedTags = new Set(checkedTags); + const newAncestorTagsDataMap = new Map(ancestorTagsDataMap); + removeAncestorTagsData.forEach( + (ancestorTag: InterfaceUserTagsAncestorData) => { + const prevAncestorTagValue = ancestorTagsDataMap.get(ancestorTag._id); + if (prevAncestorTagValue === 1) { + newCheckedTags.delete(ancestorTag._id); + newAncestorTagsDataMap.delete(ancestorTag._id); + } else { + newAncestorTagsDataMap.set(ancestorTag._id, prevAncestorTagValue - 1); + } + }, + ); + + setCheckedTags(newCheckedTags); + setAncestorTagsDataMap(newAncestorTagsDataMap); + }, [removeAncestorTagsData]); + + const addAncestorTags = (tagId: string): void => { + setCheckedTagId(tagId); + setUnheckedTagId(null); + }; + + const removeAncestorTags = (tagId: string): void => { + setUnheckedTagId(tagId); + setCheckedTagId(null); + }; + + const selectTag = (tag: InterfaceTagData): void => { + const newCheckedTags = new Set(checkedTags); + + setSelectedTags((selectedTags) => [...selectedTags, tag]); + newCheckedTags.add(tag._id); + addAncestorTags(tag._id); + + setCheckedTags(newCheckedTags); + }; + + const deSelectTag = (tag: InterfaceTagData): void => { + if (!selectedTags.some((selectedTag) => selectedTag._id === tag._id)) { + return; + } + + const newCheckedTags = new Set(checkedTags); + + setSelectedTags( + selectedTags.filter((selectedTag) => selectedTag._id !== tag._id), + ); + newCheckedTags.delete(tag._id); + removeAncestorTags(tag._id); + + setCheckedTags(newCheckedTags); + }; + + const toggleTagSelection = ( + tag: InterfaceTagData, + isSelected: boolean, + ): void => { + if (isSelected) { + selectTag(tag); + } else { + deSelectTag(tag); + } + }; + + useQuery(USER_TAG_ANCESTORS, { + variables: { id: checkedTagId }, + onCompleted: (data) => { + setAddAncestorTagsData(data.getUserTagAncestors.slice(0, -1)); // Update the ancestor tags data + }, + }); + + useQuery(USER_TAG_ANCESTORS, { + variables: { id: uncheckedTagId }, + onCompleted: (data) => { + setRemoveAncestorTagsData(data.getUserTagAncestors.slice(0, -1)); // Update the ancestor tags data + }, + }); + + const loadMoreUserTags = (): void => { + orgUserTagsFetchMore({ + variables: { + first: TAGS_QUERY_LIMIT, + after: orgUserTagsData?.organizations[0].userTags.pageInfo.endCursor, + }, + updateQuery: ( + prevResult: { organizations: InterfaceQueryOrganizationUserTags[] }, + { + fetchMoreResult, + }: { + fetchMoreResult?: { + organizations: InterfaceQueryOrganizationUserTags[]; + }; + }, + ) => { + if (!fetchMoreResult) return prevResult; + + return { + organizations: [ + { + ...prevResult.organizations[0], + userTags: { + ...prevResult.organizations[0].userTags, + edges: [ + ...prevResult.organizations[0].userTags.edges, + ...fetchMoreResult.organizations[0].userTags.edges, + ], + pageInfo: fetchMoreResult.organizations[0].userTags.pageInfo, + }, + }, + ], + }; + }, + }); + }; + + const [assignToTags] = useMutation(ASSIGN_TO_TAGS); + + const assignToSelectedTags = async ( + e: FormEvent, + ): Promise => { + e.preventDefault(); + + try { + const { data } = await assignToTags({ + variables: { + currentTagId, + selectedTagIds: selectedTags.map((selectedTag) => selectedTag._id), + }, + }); + + if (data) { + toast.success(t('successfullyAssignedToTags')); + hideAssignToTagsModal(); + } + } catch (error: unknown) { + /* istanbul ignore next */ + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + + const [removeFromTags] = useMutation(REMOVE_FROM_TAGS); + + const removeFromSelectedTags = async ( + e: FormEvent, + ): Promise => { + e.preventDefault(); + + try { + const { data } = await removeFromTags({ + variables: { + currentTagId, + selectedTagIds: selectedTags.map((selectedTag) => selectedTag._id), + }, + }); + + if (data) { + toast.success(t('successfullyRemovedFromTags')); + hideAssignToTagsModal(); + } + } catch (error: unknown) { + /* istanbul ignore next */ + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + + if (orgUserTagsError) { + return ( +
+
+ +
+ {t('errorOccurredWhileLoadingOrganizationUserTags')} +
+ {orgUserTagsError.message} +
+
+
+ ); + } + + return ( + <> + + + + {tagActionType === 'assignToTags' + ? t('assignToTags') + : t('removeFromTags')} + + +
+ tagActionType === 'assignToTags' + ? assignToSelectedTags(e) + : removeFromSelectedTags(e) + } + > + + {orgUserTagsLoading ? ( + + ) : ( + <> +
+ {selectedTags.length === 0 ? ( +
+ No tag selected +
+ ) : ( + selectedTags.map((tag: InterfaceTagData) => ( +
+ {tag.name} + deSelectTag(tag)} + data-testid="clearAssignedMember" + /> +
+ )) + )} +
+ +
+ All Tags +
+ +
+ +
+
+ } + scrollableTarget="scrollableDiv" + > + {userTagsList?.map((tag) => ( +
+ +
+ ))} +
+
+ + )} +
+ + + + + + +
+ + ); +}; + +export default TagActions; diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx new file mode 100644 index 0000000000..3eb506006d --- /dev/null +++ b/src/components/TagActions/TagNode.tsx @@ -0,0 +1,179 @@ +import type { ApolloError } from '@apollo/client'; +import { useQuery } from '@apollo/client'; +import { USER_TAG_SUB_TAGS } from 'GraphQl/Queries/userTagQueries'; +import React, { useState } from 'react'; +import type { + InterfaceQueryUserTagChildTags, + InterfaceTagData, +} from 'utils/interfaces'; +import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; +import styles from './TagActions.module.css'; + +interface InterfaceTagNodeProps { + tag: InterfaceTagData; + checkedTags: Set; + toggleTagSelection: (tag: InterfaceTagData, isSelected: boolean) => void; +} + +const TagNode: React.FC = ({ + tag, + checkedTags, + toggleTagSelection, +}) => { + const [expanded, setExpanded] = useState(false); + + const { + data: subTagsData, + loading: subTagsLoading, + fetchMore: fetchMoreSubTags, + }: { + data?: { + getUserTag: InterfaceQueryUserTagChildTags; + }; + loading: boolean; + error?: ApolloError; + refetch: () => void; + fetchMore: (options: { + variables: { + first: number; + after?: string; + }; + updateQuery: ( + previousResult: { getUserTag: InterfaceQueryUserTagChildTags }, + options: { + fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; + }, + ) => { getUserTag: InterfaceQueryUserTagChildTags }; + }) => void; + } = useQuery(USER_TAG_SUB_TAGS, { + variables: { + id: tag._id, + first: TAGS_QUERY_LIMIT, + }, + skip: !expanded, + }); + + const subTagsList = subTagsData?.getUserTag.childTags.edges.map( + (edge) => edge.node, + ); + + const handleTagClick = (): void => { + if (!expanded) { + setExpanded(true); + } else { + setExpanded(false); // collapse on second click + } + }; + + const handleCheckboxChange = ( + e: React.ChangeEvent, + ): void => { + toggleTagSelection(tag, e.target.checked); + }; + + const loadMoreSubTags = (): void => { + if (subTagsData?.getUserTag.childTags.pageInfo.hasNextPage) { + fetchMoreSubTags({ + variables: { + first: TAGS_QUERY_LIMIT, + after: subTagsData.getUserTag.childTags.pageInfo.endCursor, + }, + updateQuery: ( + prevResult: { getUserTag: InterfaceQueryUserTagChildTags }, + { + fetchMoreResult, + }: { + fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; + }, + ) => { + if (!fetchMoreResult) return prevResult; + + return { + getUserTag: { + ...fetchMoreResult.getUserTag, + childTags: { + ...fetchMoreResult.getUserTag.childTags, + edges: [ + ...prevResult.getUserTag.childTags.edges, + ...fetchMoreResult.getUserTag.childTags.edges, + ], + }, + }, + }; + }, + }); + } + }; + + return ( +
+
+ {tag.childTags.totalCount ? ( + <> + + {expanded ? '▼' : '▶'} + + + {' '} + + ) : ( + <> + + + {' '} + + )} + + {tag.name} +
+ + {expanded && subTagsLoading && ( +
+
+
+
+
+ )} + {expanded && subTagsList && ( +
+ {subTagsList.map((tag: InterfaceTagData) => ( + + ))} + {subTagsData?.getUserTag.childTags.pageInfo.hasNextPage && ( +
+ ...fetch more + +
+ )} +
+ )} +
+ ); +}; + +export default TagNode; diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 25b39e884d..7b5735208c 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -1,4 +1,5 @@ -import React, { useState } from 'react'; +import type { FormEvent } from 'react'; +import React, { useEffect, useState } from 'react'; import { useMutation, useQuery, type ApolloError } from '@apollo/client'; import { Search, WarningAmberRounded } from '@mui/icons-material'; import SortIcon from '@mui/icons-material/Sort'; @@ -15,15 +16,21 @@ import { toast } from 'react-toastify'; import type { InterfaceQueryUserTagsAssignedMembers } from 'utils/interfaces'; import styles from './ManageTag.module.css'; import { DataGrid } from '@mui/x-data-grid'; +import type { TagActionType } from 'utils/organizationTagsUtils'; import { dataGridStyle } from 'utils/organizationTagsUtils'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; -import { UNASSIGN_USER_TAG } from 'GraphQl/Mutations/TagMutations'; +import { + REMOVE_USER_TAG, + UNASSIGN_USER_TAG, + UPDATE_USER_TAG, +} from 'GraphQl/Mutations/TagMutations'; import { USER_TAG_ANCESTORS, USER_TAGS_ASSIGNED_MEMBERS, } from 'GraphQl/Queries/userTagQueries'; import AddPeopleToTag from 'components/AddPeopleToTag/AddPeopleToTag'; +import TagActions from 'components/TagActions/TagActions'; /** * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/managetag/:tagId'. @@ -38,9 +45,14 @@ function ManageTag(): JSX.Element { }); const { t: tCommon } = useTranslation('common'); + const [unassignTagModalIsOpen, setUnassignTagModalIsOpen] = useState(false); + const [addPeopleToTagModalIsOpen, setAddPeopleToTagModalIsOpen] = useState(false); - const [unassignTagModalIsOpen, setUnassignTagModalIsOpen] = useState(false); + const [assignToTagsModalIsOpen, setAssignToTagsModalIsOpen] = useState(false); + + const [editTagModalIsOpen, setEditTagModalIsOpen] = useState(false); + const [removeTagModalIsOpen, setRemoveTagModalIsOpen] = useState(false); const { orgId, tagId: currentTagId } = useParams(); const navigate = useNavigate(); @@ -51,6 +63,14 @@ function ManageTag(): JSX.Element { const [unassignUserId, setUnassignUserId] = useState(null); + // a state to specify whether we're assigning to tags or removing from tags + const [tagActionType, setTagActionType] = + useState('assignToTags'); + + const toggleRemoveUserTagModal = (): void => { + setRemoveTagModalIsOpen(!removeTagModalIsOpen); + }; + const showAddPeopleToTagModal = (): void => { setAddPeopleToTagModalIsOpen(true); }; @@ -59,6 +79,18 @@ function ManageTag(): JSX.Element { setAddPeopleToTagModalIsOpen(false); }; + const showAssignToTagsModal = (): void => { + setAssignToTagsModalIsOpen(true); + }; + + const hideAssignToTagsModal = (): void => { + setAssignToTagsModalIsOpen(false); + }; + + const hideEditTagModal = (): void => { + setEditTagModalIsOpen(false); + }; + const { data: userTagAssignedMembersData, loading: userTagAssignedMembersLoading, @@ -84,6 +116,7 @@ function ManageTag(): JSX.Element { const { data: orgUserTagAncestorsData, loading: orgUserTagsAncestorsLoading, + refetch: orgUserTagsAncestorsRefetch, error: orgUserTagsAncestorsError, }: { data?: { @@ -123,6 +156,59 @@ function ManageTag(): JSX.Element { } }; + const [edit] = useMutation(UPDATE_USER_TAG); + + const [newTagName, setNewTagName] = useState(''); + + useEffect(() => { + setNewTagName(userTagAssignedMembersData?.getUserTag.name ?? ''); + }, [userTagAssignedMembersData]); + + const editTag = async (e: FormEvent): Promise => { + e.preventDefault(); + + try { + const { data } = await edit({ + variables: { + tagId: currentTagId, + name: newTagName, + }, + }); + + if (data) { + toast.success(t('tagCreationSuccess') as string); + userTagAssignedMembersRefetch(); + orgUserTagsAncestorsRefetch(); + setEditTagModalIsOpen(false); + } + } catch (error: unknown) { + /* istanbul ignore next */ + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + + const [removeUserTag] = useMutation(REMOVE_USER_TAG); + const handleRemoveUserTag = async (): Promise => { + try { + await removeUserTag({ + variables: { + id: currentTagId, + }, + }); + + navigate(`/orgtags/${orgId}`); + toggleRemoveUserTagModal(); + toast.success(t('tagRemovalSuccess') as string); + } catch (error: unknown) { + /* istanbul ignore next */ + if (error instanceof Error) { + toast.error(error.message); + } + } + }; + if (userTagAssignedMembersLoading || orgUserTagsAncestorsLoading) { return ; } @@ -413,18 +499,43 @@ function ManageTag(): JSX.Element {
{'Actions'}
-
-
- {'Email Users'} +
+
{ + setTagActionType('assignToTags'); + showAssignToTagsModal(); + }} + className="ms-5 mt-2 mb-2 btn btn-primary btn-sm w-75" + > + {t('assignToTags')} +
+
{ + setTagActionType('removeFromTags'); + showAssignToTagsModal(); + }} + className="ms-5 mb-3 btn btn-danger btn-sm w-75" + > + {t('removeFromTags')}
+
-
-
-
- {'Add to tags'} + +
{ + setEditTagModalIsOpen(true); + }} + className="ms-5 mt-3 mb-2 btn btn-primary btn-sm w-75" + > + {tCommon('edit')}
-
- {'Remove from tags'} +
{ + setRemoveTagModalIsOpen(true); + }} + className="ms-5 mb-2 btn btn-danger btn-sm w-75" + > + {tCommon('remove')}
@@ -441,6 +552,15 @@ function ManageTag(): JSX.Element { tCommon={tCommon} /> + {/* Assign People To Tags Modal */} + + {/* Unassign Tag Modal */} + + {/* Edit Tag Modal */} + + + {t('tagDetails')} + +
+ + {t('tagName')} + { + setNewTagName(e.target.value); + }} + /> + + + + + + +
+
+ + {/* Remove User Tag Modal */} + + + + {t('removeUserTag')} + + + {t('removeUserTagMessage')} + + + + + ); } diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index c08c9811eb..df2b112feb 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -209,18 +209,20 @@ export interface InterfaceQueryOrganizationPostListItem { }; } -interface InterfaceTagData { +export interface InterfaceTagData { + _id: string; + name: string; + usersAssignedTo: { + totalCount: number; + }; + childTags: { + totalCount: number; + }; +} + +interface InterfaceTagNodeData { edges: { - node: { - _id: string; - name: string; - usersAssignedTo: { - totalCount: number; - }; - childTags: { - totalCount: number; - }; - }; + node: InterfaceTagData; cursor: string; }[]; pageInfo: { @@ -250,17 +252,17 @@ interface InterfaceTagMembersData { } export interface InterfaceQueryOrganizationUserTags { - userTags: InterfaceTagData; + userTags: InterfaceTagNodeData; } -export interface InterfaceQueryUserTagsAssignedMembers { +export interface InterfaceQueryUserTagChildTags { name: string; - usersAssignedTo: InterfaceTagMembersData; + childTags: InterfaceTagNodeData; } -export interface InterfaceQueryUserTagChildTags { +export interface InterfaceQueryUserTagsAssignedMembers { name: string; - childTags: InterfaceTagData; + usersAssignedTo: InterfaceTagMembersData; } export interface InterfaceQueryUserTagsMembersToAssignTo { diff --git a/src/utils/organizationTagsUtils.ts b/src/utils/organizationTagsUtils.ts index 6f33c26df7..fc987f37b5 100644 --- a/src/utils/organizationTagsUtils.ts +++ b/src/utils/organizationTagsUtils.ts @@ -23,3 +23,6 @@ export const dataGridStyle = { }; export const ADD_PEOPLE_TO_TAGS_QUERY_LIMIT = 7; +export const TAGS_QUERY_LIMIT = 10; + +export type TagActionType = 'assignToTags' | 'removeFromTags'; From 3b97043581b96d0ba873dd5e081451b02c463026 Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 26 Oct 2024 20:11:37 +0530 Subject: [PATCH 07/52] add tests --- public/locales/en/translation.json | 6 +- public/locales/fr/translation.json | 4 +- public/locales/hi/translation.json | 4 +- public/locales/sp/translation.json | 4 +- public/locales/zh/translation.json | 4 +- src/components/TagActions/TagActions.test.tsx | 281 +++++++++++ src/components/TagActions/TagActions.tsx | 36 +- src/components/TagActions/TagActionsMocks.ts | 469 ++++++++++++++++++ src/components/TagActions/TagNode.tsx | 13 +- src/screens/ManageTag/ManageTag.test.tsx | 138 +++++- src/screens/ManageTag/ManageTag.tsx | 14 +- src/screens/ManageTag/ManageTagMocks.ts | 197 +++++++- 12 files changed, 1138 insertions(+), 32 deletions(-) create mode 100644 src/components/TagActions/TagActions.test.tsx create mode 100644 src/components/TagActions/TagActionsMocks.ts diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 94668f8fed..2d8347572a 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -349,7 +349,11 @@ "removeUserTag": "Delete Tag", "removeUserTagMessage": "Do you want to delete this tag? It delete all the sub tags and all the associations.", "tagDetails": "Tag Details", - "tagName": "Name" + "tagName": "Name", + "tagUpdationSuccess": "Tag updated successfully", + "tagRemovalSuccess": "Tag deleted successfully", + "noTagSelected": "No Tag Selected", + "fetchMore": "fetch more" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 9386106268..d104ed1ef8 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -349,7 +349,9 @@ "removeUserTag": "Supprimer le tag", "removeUserTagMessage": "Voulez-vous supprimer ce tag ? Cela supprimera tous les sous-tags et toutes les associations.", "tagDetails": "Détails du tag", - "tagName": "Nom" + "tagName": "Nom", + "tagUpdationSuccess": "Étiquette mise à jour avec succès", + "tagRemovalSuccess": "Étiquette supprimée avec succès" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index a07e2e4c72..ee51f8ed9b 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -349,7 +349,9 @@ "removeUserTag": "टैग हटाएं", "removeUserTagMessage": "क्या आप इस टैग को हटाना चाहते हैं? यह सभी उप-टैग्स और सभी संबंधों को हटा देगा।", "tagDetails": "टैग विवरण", - "tagName": "नाम" + "tagName": "नाम", + "tagUpdationSuccess": "टैग सफलतापूर्वक अपडेट की गई", + "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 937db99b49..1e836cd977 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -349,7 +349,9 @@ "removeUserTag": "Eliminar etiqueta", "removeUserTagMessage": "¿Desea eliminar esta etiqueta? Esto eliminará todas las subetiquetas y todas las asociaciones.", "tagDetails": "Detalles de la etiqueta", - "tagName": "Nombre" + "tagName": "Nombre", + "tagUpdationSuccess": "Etiqueta actualizada con éxito", + "tagRemovalSuccess": "Etiqueta eliminada con éxito" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 55f36947f2..53b9100ed2 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -349,7 +349,9 @@ "removeUserTag": "删除标签", "removeUserTagMessage": "您要删除此标签吗?这将删除所有子标签和所有关联。", "tagDetails": "标签详情", - "tagName": "名称" + "tagName": "名称", + "tagUpdationSuccess": "标签更新成功", + "tagRemovalSuccess": "标签删除成功" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx new file mode 100644 index 0000000000..c583e1d585 --- /dev/null +++ b/src/components/TagActions/TagActions.test.tsx @@ -0,0 +1,281 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import type { RenderResult } from '@testing-library/react'; +import { + render, + screen, + fireEvent, + cleanup, + waitFor, +} from '@testing-library/react'; +import { Provider } from 'react-redux'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; + +import { store } from 'state/store'; +import userEvent from '@testing-library/user-event'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import { toast } from 'react-toastify'; +import type { ApolloLink } from '@apollo/client'; +import type { InterfaceTagActionsProps } from './TagActions'; +import TagActions from './TagActions'; +import i18n from 'utils/i18nForTest'; +import { MOCKS, MOCKS_ERROR } from './TagActionsMocks'; + +const link = new StaticMockLink(MOCKS, true); +const link2 = new StaticMockLink(MOCKS_ERROR, true); + +async function wait(): Promise { + await waitFor(() => { + // The waitFor utility automatically uses optimal timing + return Promise.resolve(); + }); +} + +jest.mock('react-toastify', () => ({ + toast: { + success: jest.fn(), + error: jest.fn(), + }, +})); + +const translations = { + ...JSON.parse( + JSON.stringify(i18n.getDataByLanguage('en')?.translation.manageTag ?? {}), + ), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), +}; + +const props: InterfaceTagActionsProps[] = [ + { + assignToTagsModalIsOpen: true, + hideAssignToTagsModal: () => {}, + tagActionType: 'assignToTags', + t: (key: string) => translations[key], + tCommon: (key: string) => translations[key], + }, + { + assignToTagsModalIsOpen: true, + hideAssignToTagsModal: () => {}, + tagActionType: 'removeFromTags', + t: (key: string) => translations[key], + tCommon: (key: string) => translations[key], + }, +]; + +const renderTagActionsModal = ( + props: InterfaceTagActionsProps, + link: ApolloLink, +): RenderResult => { + return render( + + + + + + } + /> + + + + + , + ); +}; + +describe('Organisation Tags Page', () => { + beforeEach(() => { + jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ orgId: 'orgId' }), + })); + }); + + afterEach(() => { + jest.clearAllMocks(); + cleanup(); + }); + + test('Component loads correctly and opens assignToTags modal', async () => { + const { getByText } = renderTagActionsModal(props[0], link); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.assign)).toBeInTheDocument(); + }); + }); + + test('Component loads correctly and opens removeFromTags modal', async () => { + const { getByText } = renderTagActionsModal(props[1], link); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.remove)).toBeInTheDocument(); + }); + }); + + test('Renders error component when when query is unsuccessful', async () => { + const { queryByText } = renderTagActionsModal(props[0], link2); + + await wait(); + + await waitFor(() => { + expect(queryByText(translations.assign)).not.toBeInTheDocument(); + }); + }); + + test('Renders more members with infinite scroll', async () => { + const { getByText } = renderTagActionsModal(props[0], link); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.assign)).toBeInTheDocument(); + }); + + // Find the infinite scroll div by test ID or another selector + const scrollableDiv = screen.getByTestId('scrollableDiv'); + + const initialTagsDataLength = screen.getAllByTestId('orgUserTag').length; + + // Set scroll position to the bottom + fireEvent.scroll(scrollableDiv, { + target: { scrollY: scrollableDiv.scrollHeight }, + }); + + await waitFor(() => { + const finalTagsDataLength = screen.getAllByTestId('orgUserTag').length; + expect(finalTagsDataLength).toBeGreaterThan(initialTagsDataLength); + + expect(getByText(translations.assign)).toBeInTheDocument(); + }); + }); + + test('Selects and deselects tags', async () => { + renderTagActionsModal(props[0], link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('checkTag1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTag1')); + + await waitFor(() => { + expect(screen.getByTestId('checkTag2')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTag2')); + + await waitFor(() => { + expect(screen.getByTestId('checkTag1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTag1')); + + await waitFor(() => { + expect(screen.getByTestId('clearSelectedTag2')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('clearSelectedTag2')); + }); + + test('fetches and lists the child tags and then selects and deselects them', async () => { + renderTagActionsModal(props[0], link); + + await wait(); + + // expand tag 1 to list its subtags + await waitFor(() => { + expect(screen.getByTestId('expandSubTags1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('expandSubTags1')); + + // fetch more of its subtags + await waitFor(() => { + expect(screen.getByTestId('fetchMoreSubTagsOf1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('fetchMoreSubTagsOf1')); + + // select subtags 1 & 2 + await waitFor(() => { + expect(screen.getByTestId('checkTagsubTag1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTagsubTag1')); + + await waitFor(() => { + expect(screen.getByTestId('checkTagsubTag2')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTagsubTag2')); + + await waitFor(() => { + expect(screen.getByTestId('checkTag1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTag1')); + + // deselect subtags 1 & 2 + await waitFor(() => { + expect(screen.getByTestId('checkTagsubTag1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTagsubTag1')); + + await waitFor(() => { + expect(screen.getByTestId('checkTagsubTag2')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTagsubTag2')); + + // hide subtags of tag 1 + await waitFor(() => { + expect(screen.getByTestId('expandSubTags1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('expandSubTags1')); + }); + + test('Successfully assigns to tags', async () => { + renderTagActionsModal(props[0], link); + + await wait(); + + // select userTags 2 & 3 and assign them + await waitFor(() => { + expect(screen.getByTestId('checkTag2')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTag2')); + + await waitFor(() => { + expect(screen.getByTestId('checkTag3')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTag3')); + + userEvent.click(screen.getByTestId('tagActionSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toHaveBeenCalledWith( + translations.successfullyAssignedToTags, + ); + }); + }); + + test('Successfully removes from tags', async () => { + renderTagActionsModal(props[1], link); + + await wait(); + + // select userTag 2 and remove people from it + await waitFor(() => { + expect(screen.getByTestId('checkTag2')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('checkTag2')); + + userEvent.click(screen.getByTestId('tagActionSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toHaveBeenCalledWith( + translations.successfullyRemovedFromTags, + ); + }); + }); +}); diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index 93f2f071eb..a8c0871053 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -31,7 +31,7 @@ interface InterfaceUserTagsAncestorData { /** * Props for the `AssignToTags` component. */ -interface InterfaceAssignToTagsProps { +export interface InterfaceTagActionsProps { assignToTagsModalIsOpen: boolean; hideAssignToTagsModal: () => void; tagActionType: TagActionType; @@ -39,7 +39,7 @@ interface InterfaceAssignToTagsProps { tCommon: (key: string) => string; } -const TagActions: React.FC = ({ +const TagActions: React.FC = ({ assignToTagsModalIsOpen, hideAssignToTagsModal, tagActionType, @@ -110,6 +110,7 @@ const TagActions: React.FC = ({ useEffect(() => { const newCheckedTags = new Set(checkedTags); const newAncestorTagsDataMap = new Map(ancestorTagsDataMap); + /* istanbul ignore next */ addAncestorTagsData.forEach( (ancestorTag: InterfaceUserTagsAncestorData) => { const prevAncestorTagValue = ancestorTagsDataMap.get(ancestorTag._id); @@ -128,6 +129,7 @@ const TagActions: React.FC = ({ useEffect(() => { const newCheckedTags = new Set(checkedTags); const newAncestorTagsDataMap = new Map(ancestorTagsDataMap); + /* istanbul ignore next */ removeAncestorTagsData.forEach( (ancestorTag: InterfaceUserTagsAncestorData) => { const prevAncestorTagValue = ancestorTagsDataMap.get(ancestorTag._id); @@ -166,6 +168,7 @@ const TagActions: React.FC = ({ const deSelectTag = (tag: InterfaceTagData): void => { if (!selectedTags.some((selectedTag) => selectedTag._id === tag._id)) { + /* istanbul ignore next */ return; } @@ -193,15 +196,15 @@ const TagActions: React.FC = ({ useQuery(USER_TAG_ANCESTORS, { variables: { id: checkedTagId }, - onCompleted: (data) => { - setAddAncestorTagsData(data.getUserTagAncestors.slice(0, -1)); // Update the ancestor tags data + onCompleted: /* istanbul ignore next */ (data) => { + setAddAncestorTagsData(data.getUserTagAncestors.slice(0, -1)); // Update the ancestor tags data, to check the ancestor tags }, }); useQuery(USER_TAG_ANCESTORS, { variables: { id: uncheckedTagId }, - onCompleted: (data) => { - setRemoveAncestorTagsData(data.getUserTagAncestors.slice(0, -1)); // Update the ancestor tags data + onCompleted: /* istanbul ignore next */ (data) => { + setRemoveAncestorTagsData(data.getUserTagAncestors.slice(0, -1)); // Update the ancestor tags data, to uncheck the ancestor tags }, }); @@ -348,7 +351,7 @@ const TagActions: React.FC = ({ > {selectedTags.length === 0 ? (
- No tag selected + {t('noTagSelected')}
) : ( selectedTags.map((tag: InterfaceTagData) => ( @@ -360,7 +363,7 @@ const TagActions: React.FC = ({ deSelectTag(tag)} - data-testid="clearAssignedMember" + data-testid={`clearSelectedTag${tag._id}`} />
)) @@ -381,7 +384,7 @@ const TagActions: React.FC = ({ className={`${styles.scrContainer}`} > = ({ scrollableTarget="scrollableDiv" > {userTagsList?.map((tag) => ( -
+
))} @@ -413,15 +421,11 @@ const TagActions: React.FC = ({ - diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts new file mode 100644 index 0000000000..c7a9c80ee2 --- /dev/null +++ b/src/components/TagActions/TagActionsMocks.ts @@ -0,0 +1,469 @@ +import { + ASSIGN_TO_TAGS, + REMOVE_FROM_TAGS, +} from 'GraphQl/Mutations/TagMutations'; +import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; +import { USER_TAG_SUB_TAGS } from 'GraphQl/Queries/userTagQueries'; +import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; + +export const MOCKS = [ + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_LIMIT, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: '1', + name: 'userTag 1', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 11, + }, + }, + cursor: '1', + }, + { + node: { + _id: '2', + name: 'userTag 2', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: '2', + }, + { + node: { + _id: '3', + name: 'userTag 3', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: '3', + }, + { + node: { + _id: '4', + name: 'userTag 4', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: '4', + }, + { + node: { + _id: '5', + name: 'userTag 5', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: '5', + }, + { + node: { + _id: '6', + name: 'userTag 6', + usersAssignedTo: { + totalCount: 6, + }, + childTags: { + totalCount: 6, + }, + }, + cursor: '6', + }, + { + node: { + _id: '7', + name: 'userTag 7', + usersAssignedTo: { + totalCount: 7, + }, + childTags: { + totalCount: 7, + }, + }, + cursor: '7', + }, + { + node: { + _id: '8', + name: 'userTag 8', + usersAssignedTo: { + totalCount: 8, + }, + childTags: { + totalCount: 8, + }, + }, + cursor: '8', + }, + { + node: { + _id: '9', + name: 'userTag 9', + usersAssignedTo: { + totalCount: 9, + }, + childTags: { + totalCount: 9, + }, + }, + cursor: '9', + }, + { + node: { + _id: '10', + name: 'userTag 10', + usersAssignedTo: { + totalCount: 10, + }, + childTags: { + totalCount: 10, + }, + }, + cursor: '10', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '10', + hasNextPage: true, + hasPreviousPage: false, + }, + totalCount: 12, + }, + }, + ], + }, + }, + }, + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_LIMIT, + after: '10', + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: '11', + name: 'userTag 11', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: '11', + }, + { + node: { + _id: '12', + name: 'userTag 12', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: '12', + }, + ], + pageInfo: { + startCursor: '11', + endCursor: '12', + hasNextPage: false, + hasPreviousPage: true, + }, + totalCount: 12, + }, + }, + ], + }, + }, + }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: '1', + first: TAGS_QUERY_LIMIT, + }, + }, + result: { + data: { + getUserTag: { + name: 'userTag 1', + childTags: { + edges: [ + { + node: { + _id: 'subTag1', + name: 'subTag 1', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag1', + }, + { + node: { + _id: 'subTag2', + name: 'subTag 2', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: 'subTag2', + }, + { + node: { + _id: 'subTag3', + name: 'subTag 3', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag3', + }, + { + node: { + _id: 'subTag4', + name: 'subTag 4', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: 'subTag4', + }, + { + node: { + _id: 'subTag5', + name: 'subTag 5', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag5', + }, + { + node: { + _id: 'subTag6', + name: 'subTag 6', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag6', + }, + { + node: { + _id: 'subTag7', + name: 'subTag 7', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag7', + }, + { + node: { + _id: 'subTag8', + name: 'subTag 8', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag8', + }, + { + node: { + _id: 'subTag9', + name: 'subTag 9', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag9', + }, + { + node: { + _id: 'subTag10', + name: 'subTag 10', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag10', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '10', + hasNextPage: true, + hasPreviousPage: false, + }, + totalCount: 11, + }, + }, + }, + }, + }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: '1', + after: '10', + first: TAGS_QUERY_LIMIT, + }, + }, + result: { + data: { + getUserTag: { + name: 'tag1', + childTags: { + edges: [ + { + node: { + _id: 'subTag11', + name: 'subTag 11', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: 'subTag11', + }, + ], + pageInfo: { + startCursor: '11', + endCursor: '11', + hasNextPage: false, + hasPreviousPage: true, + }, + totalCount: 11, + }, + }, + }, + }, + }, + { + request: { + query: ASSIGN_TO_TAGS, + variables: { + currentTagId: '1', + selectedTagIds: ['2', '3'], + }, + }, + result: { + data: { + assignToUserTags: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: REMOVE_FROM_TAGS, + variables: { + currentTagId: '1', + selectedTagIds: ['2'], + }, + }, + result: { + data: { + removeFromUserTags: { + _id: '1', + }, + }, + }, + }, +]; + +export const MOCKS_ERROR = [ + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + after: null, + before: null, + first: 5, + last: null, + }, + }, + error: new Error('Mock Graphql Error'), + }, +]; diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index 3eb506006d..f4b70a12dc 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -13,12 +13,14 @@ interface InterfaceTagNodeProps { tag: InterfaceTagData; checkedTags: Set; toggleTagSelection: (tag: InterfaceTagData, isSelected: boolean) => void; + t: (key: string) => string; } const TagNode: React.FC = ({ tag, checkedTags, toggleTagSelection, + t, }) => { const [expanded, setExpanded] = useState(false); @@ -114,6 +116,7 @@ const TagNode: React.FC = ({ onClick={handleTagClick} className="me-3" style={{ cursor: 'pointer' }} + data-testid={`expandSubTags${tag._id}`} > {expanded ? '▼' : '▶'} @@ -123,6 +126,7 @@ const TagNode: React.FC = ({ checked={checkedTags.has(tag._id)} className="me-2" onChange={handleCheckboxChange} + data-testid={`checkTag${tag._id}`} /> {' '} @@ -135,6 +139,7 @@ const TagNode: React.FC = ({ checked={checkedTags.has(tag._id)} className="ms-1 me-2" onChange={handleCheckboxChange} + data-testid={`checkTag${tag._id}`} /> {' '} @@ -158,6 +163,7 @@ const TagNode: React.FC = ({ tag={tag} checkedTags={checkedTags} toggleTagSelection={toggleTagSelection} + t={t} /> ))} {subTagsData?.getUserTag.childTags.pageInfo.hasNextPage && ( @@ -166,7 +172,12 @@ const TagNode: React.FC = ({ className="ms-4 mt-0 mb-3" onClick={loadMoreSubTags} > - ...fetch more + + ...{t('fetchMore')} +
)} diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index d497d2a0a8..a27ebad8b4 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -59,8 +59,7 @@ const cache = new InMemoryCache({ fields: { getUserTag: { keyArgs: false, - merge(existing = {}, incoming) { - console.log(existing); + merge(_, incoming) { return incoming; }, }, @@ -188,6 +187,94 @@ describe('Manage Tag Page', () => { ); }); + test('opens and closes the assignToTags modal', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('assignToTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('assignToTags')); + + await waitFor(() => { + return expect( + screen.findByTestId('closeTagActionsModalBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('closeTagActionsModalBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('closeTagActionsModalBtn'), + ); + }); + + test('opens and closes the removeFromTags modal', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('removeFromTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('removeFromTags')); + + await waitFor(() => { + return expect( + screen.findByTestId('closeTagActionsModalBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('closeTagActionsModalBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('closeTagActionsModalBtn'), + ); + }); + + test('opens and closes the edit tag modal', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('editTag')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('editTag')); + + await waitFor(() => { + return expect( + screen.findByTestId('closeEditTagModalBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('closeEditTagModalBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('closeEditTagModalBtn'), + ); + }); + + test('opens and closes the remove tag modal', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('removeTag')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('removeTag')); + + await waitFor(() => { + return expect( + screen.findByTestId('removeUserTagModalCloseBtn'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('removeUserTagModalCloseBtn')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('removeUserTagModalCloseBtn'), + ); + }); + test("navigates to the member's profile after clicking the view option", async () => { renderManageTag(link); @@ -296,4 +383,51 @@ describe('Manage Tag Page', () => { ); }); }); + + test('successfully edits the tag name', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('editTag')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('editTag')); + + const tagNameInput = screen.getByTestId('tagNameInput'); + await userEvent.clear(tagNameInput); + await userEvent.type(tagNameInput, 'tag 1 edited'); + expect(tagNameInput).toHaveValue('tag 1 edited'); + + userEvent.click(screen.getByTestId('editTagSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toHaveBeenCalledWith( + translations.tagUpdationSuccess, + ); + }); + }); + + test('successfully removes the tag and redirects to orgTags page', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('removeTag')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('removeTag')); + + userEvent.click(screen.getByTestId('removeUserTagSubmitBtn')); + + await waitFor(() => { + expect(toast.success).toHaveBeenCalledWith( + translations.tagRemovalSuccess, + ); + }); + + await waitFor(() => { + expect(screen.getByTestId('organizationTagsScreen')).toBeInTheDocument(); + }); + }); }); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 7b5735208c..b861ff962b 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -176,7 +176,7 @@ function ManageTag(): JSX.Element { }); if (data) { - toast.success(t('tagCreationSuccess') as string); + toast.success(t('tagUpdationSuccess')); userTagAssignedMembersRefetch(); orgUserTagsAncestorsRefetch(); setEditTagModalIsOpen(false); @@ -506,6 +506,7 @@ function ManageTag(): JSX.Element { showAssignToTagsModal(); }} className="ms-5 mt-2 mb-2 btn btn-primary btn-sm w-75" + data-testid="assignToTags" > {t('assignToTags')}
@@ -515,6 +516,7 @@ function ManageTag(): JSX.Element { showAssignToTagsModal(); }} className="ms-5 mb-3 btn btn-danger btn-sm w-75" + data-testid="removeFromTags" > {t('removeFromTags')}
@@ -526,6 +528,7 @@ function ManageTag(): JSX.Element { setEditTagModalIsOpen(true); }} className="ms-5 mt-3 mb-2 btn btn-primary btn-sm w-75" + data-testid="editTag" > {tCommon('edit')}
@@ -534,6 +537,7 @@ function ManageTag(): JSX.Element { setRemoveTagModalIsOpen(true); }} className="ms-5 mb-2 btn btn-danger btn-sm w-75" + data-testid="removeTag" > {tCommon('remove')}
@@ -635,15 +639,11 @@ function ManageTag(): JSX.Element { - diff --git a/src/screens/ManageTag/ManageTagMocks.ts b/src/screens/ManageTag/ManageTagMocks.ts index 27de4de676..e90e8c58ed 100644 --- a/src/screens/ManageTag/ManageTagMocks.ts +++ b/src/screens/ManageTag/ManageTagMocks.ts @@ -1,9 +1,15 @@ -import { UNASSIGN_USER_TAG } from 'GraphQl/Mutations/TagMutations'; +import { + REMOVE_USER_TAG, + UNASSIGN_USER_TAG, + UPDATE_USER_TAG, +} from 'GraphQl/Mutations/TagMutations'; +import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; import { USER_TAG_ANCESTORS, USER_TAGS_ASSIGNED_MEMBERS, USER_TAGS_MEMBERS_TO_ASSIGN_TO, } from 'GraphQl/Queries/userTagQueries'; +import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; export const MOCKS = [ { @@ -301,6 +307,195 @@ export const MOCKS = [ }, }, }, + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_LIMIT, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: '1', + name: 'userTag 1', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 11, + }, + }, + cursor: '1', + }, + { + node: { + _id: '2', + name: 'userTag 2', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: '2', + }, + { + node: { + _id: '3', + name: 'userTag 3', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: '3', + }, + { + node: { + _id: '4', + name: 'userTag 4', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: '4', + }, + { + node: { + _id: '5', + name: 'userTag 5', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: '5', + }, + { + node: { + _id: '6', + name: 'userTag 6', + usersAssignedTo: { + totalCount: 6, + }, + childTags: { + totalCount: 6, + }, + }, + cursor: '6', + }, + { + node: { + _id: '7', + name: 'userTag 7', + usersAssignedTo: { + totalCount: 7, + }, + childTags: { + totalCount: 7, + }, + }, + cursor: '7', + }, + { + node: { + _id: '8', + name: 'userTag 8', + usersAssignedTo: { + totalCount: 8, + }, + childTags: { + totalCount: 8, + }, + }, + cursor: '8', + }, + { + node: { + _id: '9', + name: 'userTag 9', + usersAssignedTo: { + totalCount: 9, + }, + childTags: { + totalCount: 9, + }, + }, + cursor: '9', + }, + { + node: { + _id: '10', + name: 'userTag 10', + usersAssignedTo: { + totalCount: 10, + }, + childTags: { + totalCount: 10, + }, + }, + cursor: '10', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '10', + hasNextPage: true, + hasPreviousPage: false, + }, + totalCount: 12, + }, + }, + ], + }, + }, + }, + { + request: { + query: UPDATE_USER_TAG, + variables: { + tagId: '1', + name: 'tag 1 edited', + }, + }, + result: { + data: { + updateUserTag: { + _id: '1', + }, + }, + }, + }, + { + request: { + query: REMOVE_USER_TAG, + variables: { + id: '1', + }, + }, + result: { + data: { + removeUserTag: { + _id: '1', + }, + }, + }, + }, ]; export const MOCKS_ERROR_ASSIGNED_MEMBERS = [ From 6c134847f38b651fcc6e8765e731e517fc23963b Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 26 Oct 2024 20:27:08 +0530 Subject: [PATCH 08/52] translations --- public/locales/fr/translation.json | 4 +++- public/locales/hi/translation.json | 4 +++- public/locales/sp/translation.json | 4 +++- public/locales/zh/translation.json | 4 +++- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index d104ed1ef8..0949a59ccc 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -351,7 +351,9 @@ "tagDetails": "Détails du tag", "tagName": "Nom", "tagUpdationSuccess": "Étiquette mise à jour avec succès", - "tagRemovalSuccess": "Étiquette supprimée avec succès" + "tagRemovalSuccess": "Étiquette supprimée avec succès", + "noTagSelected": "Aucun tag sélectionné", + "fetchMore": "charger plus" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index ee51f8ed9b..1001c1cfda 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -351,7 +351,9 @@ "tagDetails": "टैग विवरण", "tagName": "नाम", "tagUpdationSuccess": "टैग सफलतापूर्वक अपडेट की गई", - "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई" + "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई", + "noTagSelected": "कोई टैग चयनित नहीं", + "fetchMore": "और लोड करें" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 1e836cd977..ebd131a566 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -351,7 +351,9 @@ "tagDetails": "Detalles de la etiqueta", "tagName": "Nombre", "tagUpdationSuccess": "Etiqueta actualizada con éxito", - "tagRemovalSuccess": "Etiqueta eliminada con éxito" + "tagRemovalSuccess": "Etiqueta eliminada con éxito", + "noTagSelected": "Ninguna etiqueta seleccionada", + "fetchMore": "cargar más" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 53b9100ed2..235fc313b9 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -351,7 +351,9 @@ "tagDetails": "标签详情", "tagName": "名称", "tagUpdationSuccess": "标签更新成功", - "tagRemovalSuccess": "标签删除成功" + "tagRemovalSuccess": "标签删除成功", + "noTagSelected": "未选择标签", + "fetchMore": "加载更多" }, "userListCard": { "addAdmin": "添加管理员", From 76f5fbe57f8dde678e0ef5842fe5e220c7e0e07e Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 12:27:30 +0530 Subject: [PATCH 09/52] change to infinite scroll --- src/screens/ManageTag/ManageTag.tsx | 187 ++++++++++-------- .../OrganizationTags/OrganizationTags.tsx | 174 ++++++++-------- src/screens/SubTags/SubTags.tsx | 164 ++++++++------- 3 files changed, 286 insertions(+), 239 deletions(-) diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index b861ff962b..6a39d4634e 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -17,7 +17,10 @@ import type { InterfaceQueryUserTagsAssignedMembers } from 'utils/interfaces'; import styles from './ManageTag.module.css'; import { DataGrid } from '@mui/x-data-grid'; import type { TagActionType } from 'utils/organizationTagsUtils'; -import { dataGridStyle } from 'utils/organizationTagsUtils'; +import { + ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + dataGridStyle, +} from 'utils/organizationTagsUtils'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; import { @@ -31,6 +34,7 @@ import { } from 'GraphQl/Queries/userTagQueries'; import AddPeopleToTag from 'components/AddPeopleToTag/AddPeopleToTag'; import TagActions from 'components/TagActions/TagActions'; +import InfiniteScroll from 'react-infinite-scroll-component'; /** * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/managetag/:tagId'. @@ -56,10 +60,6 @@ function ManageTag(): JSX.Element { const { orgId, tagId: currentTagId } = useParams(); const navigate = useNavigate(); - const [after, setAfter] = useState(null); - const [before, setBefore] = useState(null); - const [first, setFirst] = useState(5); - const [last, setLast] = useState(null); const [unassignUserId, setUnassignUserId] = useState(null); @@ -96,6 +96,7 @@ function ManageTag(): JSX.Element { loading: userTagAssignedMembersLoading, error: userTagAssignedMembersError, refetch: userTagAssignedMembersRefetch, + fetchMore: fetchMoreAssignedMembers, }: { data?: { getUserTag: InterfaceQueryUserTagsAssignedMembers; @@ -103,16 +104,65 @@ function ManageTag(): JSX.Element { loading: boolean; error?: ApolloError; refetch: () => void; + fetchMore: (options: { + variables: { + after?: string | null; + first?: number | null; + }; + updateQuery?: ( + previousQueryResult: { + getUserTag: InterfaceQueryUserTagsAssignedMembers; + }, + options: { + fetchMoreResult: { + getUserTag: InterfaceQueryUserTagsAssignedMembers; + }; + }, + ) => { getUserTag: InterfaceQueryUserTagsAssignedMembers }; + }) => Promise; } = useQuery(USER_TAGS_ASSIGNED_MEMBERS, { variables: { id: currentTagId, - after: after, - before: before, - first: first, - last: last, + first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, }, }); + const loadMoreAssignedMembers = (): void => { + fetchMoreAssignedMembers({ + variables: { + first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + after: + userTagAssignedMembersData?.getUserTag.usersAssignedTo.pageInfo + .endCursor, // Fetch after the last loaded cursor + }, + updateQuery: ( + prevResult: { getUserTag: InterfaceQueryUserTagsAssignedMembers }, + { + fetchMoreResult, + }: { + fetchMoreResult: { + getUserTag: InterfaceQueryUserTagsAssignedMembers; + }; + }, + ) => { + if (!fetchMoreResult) return prevResult; + + return { + getUserTag: { + ...fetchMoreResult.getUserTag, + usersAssignedTo: { + ...fetchMoreResult.getUserTag.usersAssignedTo, + edges: [ + ...prevResult.getUserTag.usersAssignedTo.edges, + ...fetchMoreResult.getUserTag.usersAssignedTo.edges, + ], + }, + }, + }; + }, + }); + }; + const { data: orgUserTagAncestorsData, loading: orgUserTagsAncestorsLoading, @@ -246,24 +296,6 @@ function ManageTag(): JSX.Element { navigate(`/orgtags/${orgId}/managetag/${tagId}`); }; - const handleNextPage = (): void => { - setAfter( - userTagAssignedMembersData?.getUserTag.usersAssignedTo.pageInfo.endCursor, - ); - setBefore(null); - setFirst(5); - setLast(null); - }; - const handlePreviousPage = (): void => { - setBefore( - userTagAssignedMembersData?.getUserTag.usersAssignedTo.pageInfo - .startCursor, - ); - setAfter(null); - setFirst(null); - setLast(5); - }; - const toggleUnassignTagModal = (): void => { if (unassignTagModalIsOpen) { setUnassignUserId(null); @@ -405,7 +437,7 @@ function ManageTag(): JSX.Element {
- +
@@ -437,62 +469,49 @@ function ManageTag(): JSX.Element {
))}
- row._id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noAssignedMembersFound')} - - ), - }} - sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={userTagAssignedMembers?.map((assignedMembers, index) => ({ - id: index + 1, - ...assignedMembers, - }))} - columns={columns} - isRowSelectable={() => false} - /> - -
-
- -
-
- -
-
+ +
+
+ } + > + row._id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noAssignedMembersFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagAssignedMembers?.map( + (assignedMembers, index) => ({ + id: index + 1, + ...assignedMembers, + }), + )} + columns={columns} + isRowSelectable={() => false} + /> +
diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index e4bf0604e9..c65546d6e5 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -16,7 +16,7 @@ import { toast } from 'react-toastify'; import type { InterfaceQueryOrganizationUserTags } from 'utils/interfaces'; import styles from './OrganizationTags.module.css'; import { DataGrid } from '@mui/x-data-grid'; -import { dataGridStyle } from 'utils/organizationTagsUtils'; +import { dataGridStyle, TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; @@ -24,6 +24,7 @@ import { CREATE_USER_TAG, REMOVE_USER_TAG, } from 'GraphQl/Mutations/TagMutations'; +import InfiniteScroll from 'react-infinite-scroll-component'; /** * Component that renders the Organization Tags screen when the app navigates to '/orgtags/:orgId'. @@ -42,11 +43,6 @@ function OrganizationTags(): JSX.Element { const { orgId } = useParams(); const navigate = useNavigate(); - const [after, setAfter] = useState(null); - const [before, setBefore] = useState(null); - const [first, setFirst] = useState(5); - const [last, setLast] = useState(null); - const [tagSerialNumber, setTagSerialNumber] = useState(0); const [tagName, setTagName] = useState(''); @@ -67,6 +63,7 @@ function OrganizationTags(): JSX.Element { loading: orgUserTagsLoading, error: orgUserTagsError, refetch: orgUserTagsRefetch, + fetchMore: orgUserTagsFetchMore, }: { data?: { organizations: InterfaceQueryOrganizationUserTags[]; @@ -74,16 +71,64 @@ function OrganizationTags(): JSX.Element { loading: boolean; error?: ApolloError; refetch: () => void; + fetchMore: (options: { + variables: { + first: number; + after?: string; + }; + updateQuery: ( + previousResult: { organizations: InterfaceQueryOrganizationUserTags[] }, + options: { + fetchMoreResult?: { + organizations: InterfaceQueryOrganizationUserTags[]; + }; + }, + ) => { organizations: InterfaceQueryOrganizationUserTags[] }; + }) => void; } = useQuery(ORGANIZATION_USER_TAGS_LIST, { variables: { id: orgId, - after: after, - before: before, - first: first, - last: last, + first: TAGS_QUERY_LIMIT, }, }); + const loadMoreUserTags = (): void => { + orgUserTagsFetchMore({ + variables: { + first: TAGS_QUERY_LIMIT, + after: orgUserTagsData?.organizations[0].userTags.pageInfo.endCursor, + }, + updateQuery: ( + prevResult: { organizations: InterfaceQueryOrganizationUserTags[] }, + { + fetchMoreResult, + }: { + fetchMoreResult?: { + organizations: InterfaceQueryOrganizationUserTags[]; + }; + }, + ) => { + if (!fetchMoreResult) return prevResult; + + return { + organizations: [ + { + ...prevResult.organizations[0], + userTags: { + ...prevResult.organizations[0].userTags, + edges: [ + ...prevResult.organizations[0].userTags.edges, + ...fetchMoreResult.organizations[0].userTags.edges, + ], + pageInfo: fetchMoreResult.organizations[0].userTags.pageInfo, + }, + }, + ], + }; + }, + }); + }; + const [create, { loading: createUserTagLoading }] = useMutation(CREATE_USER_TAG); @@ -151,21 +196,6 @@ function OrganizationTags(): JSX.Element { ); } - const handleNextPage = (): void => { - setAfter(orgUserTagsData?.organizations[0].userTags.pageInfo.endCursor); - setBefore(null); - setFirst(5); - setLast(null); - setTagSerialNumber(tagSerialNumber + 1); - }; - const handlePreviousPage = (): void => { - setBefore(orgUserTagsData?.organizations[0].userTags.pageInfo.startCursor); - setAfter(null); - setFirst(null); - setLast(5); - setTagSerialNumber(tagSerialNumber - 1); - }; - const userTagsList = orgUserTagsData?.organizations[0].userTags.edges.map( (edge) => edge.node, ); @@ -193,7 +223,7 @@ function OrganizationTags(): JSX.Element { headerClassName: `${styles.tableHeader}`, sortable: false, renderCell: (params: GridCellParams) => { - return
{tagSerialNumber * 5 + params.row.id}
; + return
{params.row.id}
; }, }, { @@ -351,7 +381,7 @@ function OrganizationTags(): JSX.Element { -
+
@@ -361,61 +391,47 @@ function OrganizationTags(): JSX.Element { {'Tags'}
- row._id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noTagsFound')} - - ), - }} - sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={userTagsList?.map((fund, index) => ({ - id: index + 1, - ...fund, - }))} - columns={columns} - isRowSelectable={() => false} - /> -
-
- -
-
- -
-
- + row._id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noTagsFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagsList?.map((fund, index) => ({ + id: index + 1, + ...fund, + }))} + columns={columns} + isRowSelectable={() => false} + /> +
diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index b381c9f816..fd82a9434b 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -16,7 +16,7 @@ import { toast } from 'react-toastify'; import type { InterfaceQueryUserTagChildTags } from 'utils/interfaces'; import styles from './SubTags.module.css'; import { DataGrid } from '@mui/x-data-grid'; -import { dataGridStyle } from 'utils/organizationTagsUtils'; +import { dataGridStyle, TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; import { @@ -27,6 +27,7 @@ import { USER_TAG_ANCESTORS, USER_TAG_SUB_TAGS, } from 'GraphQl/Queries/userTagQueries'; +import InfiniteScroll from 'react-infinite-scroll-component'; /** * Component that renders the SubTags screen when the app navigates to '/orgtags/:orgId/subtags/:tagId'. @@ -47,11 +48,6 @@ function SubTags(): JSX.Element { const navigate = useNavigate(); - const [after, setAfter] = useState(null); - const [before, setBefore] = useState(null); - const [first, setFirst] = useState(5); - const [last, setLast] = useState(null); - const [tagName, setTagName] = useState(''); const [removeUserTagId, setRemoveUserTagId] = useState(null); @@ -69,9 +65,10 @@ function SubTags(): JSX.Element { const { data: subTagsData, - loading: subTagsLoading, error: subTagsError, + loading: subTagsLoading, refetch: subTagsRefetch, + fetchMore: fetchMoreSubTags, }: { data?: { getUserTag: InterfaceQueryUserTagChildTags; @@ -79,16 +76,57 @@ function SubTags(): JSX.Element { loading: boolean; error?: ApolloError; refetch: () => void; + fetchMore: (options: { + variables: { + first: number; + after?: string; + }; + updateQuery: ( + previousResult: { getUserTag: InterfaceQueryUserTagChildTags }, + options: { + fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; + }, + ) => { getUserTag: InterfaceQueryUserTagChildTags }; + }) => void; } = useQuery(USER_TAG_SUB_TAGS, { variables: { id: parentTagId, - after: after, - before: before, - first: first, - last: last, + first: TAGS_QUERY_LIMIT, }, }); + const loadMoreSubTags = (): void => { + fetchMoreSubTags({ + variables: { + first: TAGS_QUERY_LIMIT, + after: subTagsData?.getUserTag.childTags.pageInfo.endCursor, + }, + updateQuery: ( + prevResult: { getUserTag: InterfaceQueryUserTagChildTags }, + { + fetchMoreResult, + }: { + fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; + }, + ) => { + if (!fetchMoreResult) return prevResult; + + return { + getUserTag: { + ...fetchMoreResult.getUserTag, + childTags: { + ...fetchMoreResult.getUserTag.childTags, + edges: [ + ...prevResult.getUserTag.childTags.edges, + ...fetchMoreResult.getUserTag.childTags.edges, + ], + }, + }, + }; + }, + }); + }; + const { data: orgUserTagAncestorsData, loading: orgUserTagsAncestorsLoading, @@ -163,20 +201,6 @@ function SubTags(): JSX.Element { return ; } - const handleNextPage = (): void => { - setAfter(subTagsData?.getUserTag.childTags.pageInfo.endCursor); - setBefore(null); - setFirst(5); - setLast(null); - }; - - const handlePreviousPage = (): void => { - setBefore(subTagsData?.getUserTag.childTags.pageInfo.startCursor); - setAfter(null); - setFirst(null); - setLast(5); - }; - if (subTagsError || orgUserTagsAncestorsError) { return (
@@ -394,7 +418,7 @@ function SubTags(): JSX.Element {
-
+
@@ -424,58 +448,46 @@ function SubTags(): JSX.Element {
))}
- row._id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noTagsFound')} - - ), - }} - sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={userTagsList?.map((fund, index) => ({ - id: index + 1, - ...fund, - }))} - columns={columns} - isRowSelectable={() => false} - /> -
-
- -
-
- -
-
- + row._id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noTagsFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagsList?.map((fund, index) => ({ + id: index + 1, + ...fund, + }))} + columns={columns} + isRowSelectable={() => false} + /> +
From d022c8d536615dded9f002d3bfba16cfcb146ebe Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 13:14:32 +0530 Subject: [PATCH 10/52] add subtags infinite scroll --- public/locales/en/translation.json | 3 +- public/locales/fr/translation.json | 3 +- public/locales/hi/translation.json | 3 +- public/locales/sp/translation.json | 3 +- public/locales/zh/translation.json | 3 +- src/components/TagActions/TagActions.test.tsx | 20 +++- src/components/TagActions/TagNode.tsx | 111 ++++++++++-------- 7 files changed, 82 insertions(+), 64 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 2d8347572a..2485fec3e4 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -352,8 +352,7 @@ "tagName": "Name", "tagUpdationSuccess": "Tag updated successfully", "tagRemovalSuccess": "Tag deleted successfully", - "noTagSelected": "No Tag Selected", - "fetchMore": "fetch more" + "noTagSelected": "No Tag Selected" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 0949a59ccc..9957849399 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -352,8 +352,7 @@ "tagName": "Nom", "tagUpdationSuccess": "Étiquette mise à jour avec succès", "tagRemovalSuccess": "Étiquette supprimée avec succès", - "noTagSelected": "Aucun tag sélectionné", - "fetchMore": "charger plus" + "noTagSelected": "Aucun tag sélectionné" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 1001c1cfda..cdc1d5e52b 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -352,8 +352,7 @@ "tagName": "नाम", "tagUpdationSuccess": "टैग सफलतापूर्वक अपडेट की गई", "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई", - "noTagSelected": "कोई टैग चयनित नहीं", - "fetchMore": "और लोड करें" + "noTagSelected": "कोई टैग चयनित नहीं" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index ebd131a566..fc6d9eb839 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -352,8 +352,7 @@ "tagName": "Nombre", "tagUpdationSuccess": "Etiqueta actualizada con éxito", "tagRemovalSuccess": "Etiqueta eliminada con éxito", - "noTagSelected": "Ninguna etiqueta seleccionada", - "fetchMore": "cargar más" + "noTagSelected": "Ninguna etiqueta seleccionada" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 235fc313b9..a264056547 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -352,8 +352,7 @@ "tagName": "名称", "tagUpdationSuccess": "标签更新成功", "tagRemovalSuccess": "标签删除成功", - "noTagSelected": "未选择标签", - "fetchMore": "加载更多" + "noTagSelected": "未选择标签" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index c583e1d585..3fd98c98c2 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -194,11 +194,25 @@ describe('Organisation Tags Page', () => { }); userEvent.click(screen.getByTestId('expandSubTags1')); - // fetch more of its subtags await waitFor(() => { - expect(screen.getByTestId('fetchMoreSubTagsOf1')).toBeInTheDocument(); + expect(screen.getByTestId('subTagsScrollableDiv')).toBeInTheDocument(); + }); + // Find the infinite scroll div for subtags by test ID or another selector + const subTagsScrollableDiv = screen.getByTestId('subTagsScrollableDiv'); + + const initialTagsDataLength = + screen.getAllByTestId('orgUserSubTags').length; + + // Set scroll position to the bottom + fireEvent.scroll(subTagsScrollableDiv, { + target: { scrollY: subTagsScrollableDiv.scrollHeight }, + }); + + await waitFor(() => { + const finalTagsDataLength = + screen.getAllByTestId('orgUserSubTags').length; + expect(finalTagsDataLength).toBeGreaterThan(initialTagsDataLength); }); - userEvent.click(screen.getByTestId('fetchMoreSubTagsOf1')); // select subtags 1 & 2 await waitFor(() => { diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index f4b70a12dc..7a22a46077 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -8,6 +8,7 @@ import type { } from 'utils/interfaces'; import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; import styles from './TagActions.module.css'; +import InfiniteScroll from 'react-infinite-scroll-component'; interface InterfaceTagNodeProps { tag: InterfaceTagData; @@ -74,37 +75,35 @@ const TagNode: React.FC = ({ }; const loadMoreSubTags = (): void => { - if (subTagsData?.getUserTag.childTags.pageInfo.hasNextPage) { - fetchMoreSubTags({ - variables: { - first: TAGS_QUERY_LIMIT, - after: subTagsData.getUserTag.childTags.pageInfo.endCursor, + fetchMoreSubTags({ + variables: { + first: TAGS_QUERY_LIMIT, + after: subTagsData?.getUserTag.childTags.pageInfo.endCursor, + }, + updateQuery: ( + prevResult: { getUserTag: InterfaceQueryUserTagChildTags }, + { + fetchMoreResult, + }: { + fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; }, - updateQuery: ( - prevResult: { getUserTag: InterfaceQueryUserTagChildTags }, - { - fetchMoreResult, - }: { - fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; - }, - ) => { - if (!fetchMoreResult) return prevResult; + ) => { + if (!fetchMoreResult) return prevResult; - return { - getUserTag: { - ...fetchMoreResult.getUserTag, - childTags: { - ...fetchMoreResult.getUserTag.childTags, - edges: [ - ...prevResult.getUserTag.childTags.edges, - ...fetchMoreResult.getUserTag.childTags.edges, - ], - }, + return { + getUserTag: { + ...fetchMoreResult.getUserTag, + childTags: { + ...fetchMoreResult.getUserTag.childTags, + edges: [ + ...prevResult.getUserTag.childTags.edges, + ...fetchMoreResult.getUserTag.childTags.edges, + ], }, - }; - }, - }); - } + }, + }; + }, + }); }; return ( @@ -157,30 +156,40 @@ const TagNode: React.FC = ({ )} {expanded && subTagsList && (
- {subTagsList.map((tag: InterfaceTagData) => ( - - ))} - {subTagsData?.getUserTag.childTags.pageInfo.hasNextPage && ( -
+ +
+
+ } + scrollableTarget="subTagsScrollableDiv" > - - ...{t('fetchMore')} - - -
- )} + {subTagsList.map((tag: InterfaceTagData) => ( +
+ +
+ ))} + +
)} From d11a6ee94ce3654331355fc086af82e0f404cb5e Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 13:46:58 +0530 Subject: [PATCH 11/52] extract common variables to utils --- .../AddPeopleToTag/AddPeopleToTag.tsx | 41 ++----- .../AddPeopleToTag/AddPeopleToTagsMocks.ts | 63 +++++++---- src/components/TagActions/TagActions.tsx | 105 +++++++---------- src/components/TagActions/TagActionsMocks.ts | 10 +- src/components/TagActions/TagNode.tsx | 65 ++++------- src/screens/ManageTag/ManageTag.tsx | 36 ++---- src/screens/ManageTag/ManageTagMocks.ts | 4 +- .../OrganizationTags/OrganizationTags.tsx | 35 ++---- src/screens/SubTags/SubTags.tsx | 31 ++--- src/utils/organizationTagsUtils.ts | 107 +++++++++++++++++- 10 files changed, 254 insertions(+), 243 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index a43a47b006..e04c757993 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -1,4 +1,3 @@ -import type { ApolloError } from '@apollo/client'; import { useMutation, useQuery } from '@apollo/client'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { DataGrid } from '@mui/x-data-grid'; @@ -10,8 +9,9 @@ import { Modal, Form, Button } from 'react-bootstrap'; import { useParams } from 'react-router-dom'; import type { InterfaceQueryUserTagsMembersToAssignTo } from 'utils/interfaces'; import styles from './AddPeopleToTag.module.css'; +import type { InterfaceTagUsersToAssignToQuery } from 'utils/organizationTagsUtils'; import { - ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + TAGS_QUERY_PAGE_SIZE, dataGridStyle, } from 'utils/organizationTagsUtils'; import { Stack } from '@mui/material'; @@ -58,40 +58,21 @@ const AddPeopleToTag: React.FC = ({ loading: userTagsMembersToAssignToLoading, error: userTagsMembersToAssignToError, fetchMore: fetchMoreMembersToAssignTo, - }: { - data?: { - getUserTag: InterfaceQueryUserTagsMembersToAssignTo; - }; - loading: boolean; - error?: ApolloError; - fetchMore: (options: { + }: InterfaceTagUsersToAssignToQuery = useQuery( + USER_TAGS_MEMBERS_TO_ASSIGN_TO, + { variables: { - after?: string | null; - first?: number | null; - }; - updateQuery?: ( - previousQueryResult: { - getUserTag: InterfaceQueryUserTagsMembersToAssignTo; - }, - options: { - fetchMoreResult: { - getUserTag: InterfaceQueryUserTagsMembersToAssignTo; - }; - }, - ) => { getUserTag: InterfaceQueryUserTagsMembersToAssignTo }; - }) => Promise; - } = useQuery(USER_TAGS_MEMBERS_TO_ASSIGN_TO, { - variables: { - id: currentTagId, - first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + id: currentTagId, + first: TAGS_QUERY_PAGE_SIZE, + }, + skip: !addPeopleToTagModalIsOpen, }, - skip: !addPeopleToTagModalIsOpen, - }); + ); const loadMoreMembersToAssignTo = (): void => { fetchMoreMembersToAssignTo({ variables: { - first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, after: userTagsMembersToAssignToData?.getUserTag.usersToAssignTo.pageInfo .endCursor, // Fetch after the last loaded cursor diff --git a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts index ab185bb858..9c18c45487 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts +++ b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts @@ -1,5 +1,6 @@ import { ADD_PEOPLE_TO_TAG } from 'GraphQl/Mutations/TagMutations'; import { USER_TAGS_MEMBERS_TO_ASSIGN_TO } from 'GraphQl/Queries/userTagQueries'; +import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; export const MOCKS = [ { @@ -7,7 +8,7 @@ export const MOCKS = [ query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, variables: { id: '1', - first: 7, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { @@ -72,14 +73,38 @@ export const MOCKS = [ }, cursor: '7', }, + { + node: { + _id: '8', + firstName: 'member', + lastName: '8', + }, + cursor: '8', + }, + { + node: { + _id: '9', + firstName: 'member', + lastName: '9', + }, + cursor: '9', + }, + { + node: { + _id: '10', + firstName: 'member', + lastName: '10', + }, + cursor: '10', + }, ], pageInfo: { startCursor: '1', - endCursor: '7', + endCursor: '10', hasNextPage: true, hasPreviousPage: false, }, - totalCount: 10, + totalCount: 12, }, }, }, @@ -90,8 +115,8 @@ export const MOCKS = [ query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, variables: { id: '1', - first: 7, - after: '7', + first: TAGS_QUERY_PAGE_SIZE, + after: '10', }, }, result: { @@ -102,36 +127,28 @@ export const MOCKS = [ edges: [ { node: { - _id: '8', - firstName: 'member', - lastName: '8', - }, - cursor: '8', - }, - { - node: { - _id: '9', + _id: '11', firstName: 'member', - lastName: '9', + lastName: '11', }, - cursor: '9', + cursor: '11', }, { node: { - _id: '10', + _id: '12', firstName: 'member', - lastName: '10', + lastName: '12', }, - cursor: '10', + cursor: '12', }, ], pageInfo: { - startCursor: '8', - endCursor: '10', + startCursor: '11', + endCursor: '12', hasNextPage: false, hasPreviousPage: true, }, - totalCount: 10, + totalCount: 12, }, }, }, @@ -161,7 +178,7 @@ export const MOCKS_ERROR = [ query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, variables: { id: '1', - first: 7, + first: TAGS_QUERY_PAGE_SIZE, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index a8c0871053..57ffe7268f 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -1,4 +1,3 @@ -import type { ApolloError } from '@apollo/client'; import { useMutation, useQuery } from '@apollo/client'; import Loader from 'components/Loader/Loader'; import { USER_TAG_ANCESTORS } from 'GraphQl/Queries/userTagQueries'; @@ -17,8 +16,11 @@ import { REMOVE_FROM_TAGS, } from 'GraphQl/Mutations/TagMutations'; import { toast } from 'react-toastify'; -import type { TagActionType } from 'utils/organizationTagsUtils'; -import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; +import type { + InterfaceOrganizationTagsQuery, + TagActionType, +} from 'utils/organizationTagsUtils'; +import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; import InfiniteScroll from 'react-infinite-scroll-component'; import { WarningAmberRounded } from '@mui/icons-material'; import TagNode from './TagNode'; @@ -53,34 +55,50 @@ const TagActions: React.FC = ({ loading: orgUserTagsLoading, error: orgUserTagsError, fetchMore: orgUserTagsFetchMore, - }: { - data?: { - organizations: InterfaceQueryOrganizationUserTags[]; - }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - fetchMore: (options: { + }: InterfaceOrganizationTagsQuery = useQuery(ORGANIZATION_USER_TAGS_LIST, { + variables: { + id: orgId, + first: TAGS_QUERY_PAGE_SIZE, + }, + skip: !assignToTagsModalIsOpen, + }); + + const loadMoreUserTags = (): void => { + orgUserTagsFetchMore({ variables: { - first: number; - after?: string; - }; + first: TAGS_QUERY_PAGE_SIZE, + after: orgUserTagsData?.organizations[0].userTags.pageInfo.endCursor, + }, updateQuery: ( - previousResult: { organizations: InterfaceQueryOrganizationUserTags[] }, - options: { + prevResult: { organizations: InterfaceQueryOrganizationUserTags[] }, + { + fetchMoreResult, + }: { fetchMoreResult?: { organizations: InterfaceQueryOrganizationUserTags[]; }; }, - ) => { organizations: InterfaceQueryOrganizationUserTags[] }; - }) => void; - } = useQuery(ORGANIZATION_USER_TAGS_LIST, { - variables: { - id: orgId, - first: TAGS_QUERY_LIMIT, - }, - skip: !assignToTagsModalIsOpen, - }); + ) => { + if (!fetchMoreResult) return prevResult; + + return { + organizations: [ + { + ...prevResult.organizations[0], + userTags: { + ...prevResult.organizations[0].userTags, + edges: [ + ...prevResult.organizations[0].userTags.edges, + ...fetchMoreResult.organizations[0].userTags.edges, + ], + pageInfo: fetchMoreResult.organizations[0].userTags.pageInfo, + }, + }, + ], + }; + }, + }); + }; const userTagsList = orgUserTagsData?.organizations[0].userTags.edges.map( (edge) => edge.node, @@ -208,43 +226,6 @@ const TagActions: React.FC = ({ }, }); - const loadMoreUserTags = (): void => { - orgUserTagsFetchMore({ - variables: { - first: TAGS_QUERY_LIMIT, - after: orgUserTagsData?.organizations[0].userTags.pageInfo.endCursor, - }, - updateQuery: ( - prevResult: { organizations: InterfaceQueryOrganizationUserTags[] }, - { - fetchMoreResult, - }: { - fetchMoreResult?: { - organizations: InterfaceQueryOrganizationUserTags[]; - }; - }, - ) => { - if (!fetchMoreResult) return prevResult; - - return { - organizations: [ - { - ...prevResult.organizations[0], - userTags: { - ...prevResult.organizations[0].userTags, - edges: [ - ...prevResult.organizations[0].userTags.edges, - ...fetchMoreResult.organizations[0].userTags.edges, - ], - pageInfo: fetchMoreResult.organizations[0].userTags.pageInfo, - }, - }, - ], - }; - }, - }); - }; - const [assignToTags] = useMutation(ASSIGN_TO_TAGS); const assignToSelectedTags = async ( diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index c7a9c80ee2..9a681468d2 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -4,7 +4,7 @@ import { } from 'GraphQl/Mutations/TagMutations'; import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; import { USER_TAG_SUB_TAGS } from 'GraphQl/Queries/userTagQueries'; -import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; +import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; export const MOCKS = [ { @@ -12,7 +12,7 @@ export const MOCKS = [ query: ORGANIZATION_USER_TAGS_LIST, variables: { id: '123', - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { @@ -170,7 +170,7 @@ export const MOCKS = [ query: ORGANIZATION_USER_TAGS_LIST, variables: { id: '123', - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, after: '10', }, }, @@ -225,7 +225,7 @@ export const MOCKS = [ query: USER_TAG_SUB_TAGS, variables: { id: '1', - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { @@ -383,7 +383,7 @@ export const MOCKS = [ variables: { id: '1', after: '10', - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index 7a22a46077..fd90550228 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -1,4 +1,3 @@ -import type { ApolloError } from '@apollo/client'; import { useQuery } from '@apollo/client'; import { USER_TAG_SUB_TAGS } from 'GraphQl/Queries/userTagQueries'; import React, { useState } from 'react'; @@ -6,7 +5,8 @@ import type { InterfaceQueryUserTagChildTags, InterfaceTagData, } from 'utils/interfaces'; -import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; +import type { InterfaceOrganizationSubTagsQuery } from 'utils/organizationTagsUtils'; +import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; import styles from './TagActions.module.css'; import InfiniteScroll from 'react-infinite-scroll-component'; @@ -29,55 +29,18 @@ const TagNode: React.FC = ({ data: subTagsData, loading: subTagsLoading, fetchMore: fetchMoreSubTags, - }: { - data?: { - getUserTag: InterfaceQueryUserTagChildTags; - }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - fetchMore: (options: { - variables: { - first: number; - after?: string; - }; - updateQuery: ( - previousResult: { getUserTag: InterfaceQueryUserTagChildTags }, - options: { - fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; - }, - ) => { getUserTag: InterfaceQueryUserTagChildTags }; - }) => void; - } = useQuery(USER_TAG_SUB_TAGS, { + }: InterfaceOrganizationSubTagsQuery = useQuery(USER_TAG_SUB_TAGS, { variables: { id: tag._id, - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, }, skip: !expanded, }); - const subTagsList = subTagsData?.getUserTag.childTags.edges.map( - (edge) => edge.node, - ); - - const handleTagClick = (): void => { - if (!expanded) { - setExpanded(true); - } else { - setExpanded(false); // collapse on second click - } - }; - - const handleCheckboxChange = ( - e: React.ChangeEvent, - ): void => { - toggleTagSelection(tag, e.target.checked); - }; - const loadMoreSubTags = (): void => { fetchMoreSubTags({ variables: { - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, after: subTagsData?.getUserTag.childTags.pageInfo.endCursor, }, updateQuery: ( @@ -106,6 +69,24 @@ const TagNode: React.FC = ({ }); }; + const subTagsList = subTagsData?.getUserTag.childTags.edges.map( + (edge) => edge.node, + ); + + const handleTagClick = (): void => { + if (!expanded) { + setExpanded(true); + } else { + setExpanded(false); // collapse on second click + } + }; + + const handleCheckboxChange = ( + e: React.ChangeEvent, + ): void => { + toggleTagSelection(tag, e.target.checked); + }; + return (
diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 6a39d4634e..08e0aee77b 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -16,9 +16,12 @@ import { toast } from 'react-toastify'; import type { InterfaceQueryUserTagsAssignedMembers } from 'utils/interfaces'; import styles from './ManageTag.module.css'; import { DataGrid } from '@mui/x-data-grid'; -import type { TagActionType } from 'utils/organizationTagsUtils'; +import type { + InterfaceTagAssignedMembersQuery, + TagActionType, +} from 'utils/organizationTagsUtils'; import { - ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + TAGS_QUERY_PAGE_SIZE, dataGridStyle, } from 'utils/organizationTagsUtils'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; @@ -97,40 +100,17 @@ function ManageTag(): JSX.Element { error: userTagAssignedMembersError, refetch: userTagAssignedMembersRefetch, fetchMore: fetchMoreAssignedMembers, - }: { - data?: { - getUserTag: InterfaceQueryUserTagsAssignedMembers; - }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - fetchMore: (options: { - variables: { - after?: string | null; - first?: number | null; - }; - updateQuery?: ( - previousQueryResult: { - getUserTag: InterfaceQueryUserTagsAssignedMembers; - }, - options: { - fetchMoreResult: { - getUserTag: InterfaceQueryUserTagsAssignedMembers; - }; - }, - ) => { getUserTag: InterfaceQueryUserTagsAssignedMembers }; - }) => Promise; - } = useQuery(USER_TAGS_ASSIGNED_MEMBERS, { + }: InterfaceTagAssignedMembersQuery = useQuery(USER_TAGS_ASSIGNED_MEMBERS, { variables: { id: currentTagId, - first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, }, }); const loadMoreAssignedMembers = (): void => { fetchMoreAssignedMembers({ variables: { - first: ADD_PEOPLE_TO_TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, after: userTagAssignedMembersData?.getUserTag.usersAssignedTo.pageInfo .endCursor, // Fetch after the last loaded cursor diff --git a/src/screens/ManageTag/ManageTagMocks.ts b/src/screens/ManageTag/ManageTagMocks.ts index e90e8c58ed..91b9f2a622 100644 --- a/src/screens/ManageTag/ManageTagMocks.ts +++ b/src/screens/ManageTag/ManageTagMocks.ts @@ -9,7 +9,7 @@ import { USER_TAGS_ASSIGNED_MEMBERS, USER_TAGS_MEMBERS_TO_ASSIGN_TO, } from 'GraphQl/Queries/userTagQueries'; -import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; +import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; export const MOCKS = [ { @@ -312,7 +312,7 @@ export const MOCKS = [ query: ORGANIZATION_USER_TAGS_LIST, variables: { id: '123', - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index c65546d6e5..9df9ea16c3 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery, type ApolloError } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; import { Search, WarningAmberRounded } from '@mui/icons-material'; import SortIcon from '@mui/icons-material/Sort'; import Loader from 'components/Loader/Loader'; @@ -16,7 +16,11 @@ import { toast } from 'react-toastify'; import type { InterfaceQueryOrganizationUserTags } from 'utils/interfaces'; import styles from './OrganizationTags.module.css'; import { DataGrid } from '@mui/x-data-grid'; -import { dataGridStyle, TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; +import type { InterfaceOrganizationTagsQuery } from 'utils/organizationTagsUtils'; +import { + dataGridStyle, + TAGS_QUERY_PAGE_SIZE, +} from 'utils/organizationTagsUtils'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; @@ -64,38 +68,17 @@ function OrganizationTags(): JSX.Element { error: orgUserTagsError, refetch: orgUserTagsRefetch, fetchMore: orgUserTagsFetchMore, - }: { - data?: { - organizations: InterfaceQueryOrganizationUserTags[]; - }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - fetchMore: (options: { - variables: { - first: number; - after?: string; - }; - updateQuery: ( - previousResult: { organizations: InterfaceQueryOrganizationUserTags[] }, - options: { - fetchMoreResult?: { - organizations: InterfaceQueryOrganizationUserTags[]; - }; - }, - ) => { organizations: InterfaceQueryOrganizationUserTags[] }; - }) => void; - } = useQuery(ORGANIZATION_USER_TAGS_LIST, { + }: InterfaceOrganizationTagsQuery = useQuery(ORGANIZATION_USER_TAGS_LIST, { variables: { id: orgId, - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, }, }); const loadMoreUserTags = (): void => { orgUserTagsFetchMore({ variables: { - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, after: orgUserTagsData?.organizations[0].userTags.pageInfo.endCursor, }, updateQuery: ( diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index fd82a9434b..0307785ce2 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -16,7 +16,11 @@ import { toast } from 'react-toastify'; import type { InterfaceQueryUserTagChildTags } from 'utils/interfaces'; import styles from './SubTags.module.css'; import { DataGrid } from '@mui/x-data-grid'; -import { dataGridStyle, TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; +import type { InterfaceOrganizationSubTagsQuery } from 'utils/organizationTagsUtils'; +import { + dataGridStyle, + TAGS_QUERY_PAGE_SIZE, +} from 'utils/organizationTagsUtils'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; import { @@ -69,36 +73,17 @@ function SubTags(): JSX.Element { loading: subTagsLoading, refetch: subTagsRefetch, fetchMore: fetchMoreSubTags, - }: { - data?: { - getUserTag: InterfaceQueryUserTagChildTags; - }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - fetchMore: (options: { - variables: { - first: number; - after?: string; - }; - updateQuery: ( - previousResult: { getUserTag: InterfaceQueryUserTagChildTags }, - options: { - fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; - }, - ) => { getUserTag: InterfaceQueryUserTagChildTags }; - }) => void; - } = useQuery(USER_TAG_SUB_TAGS, { + }: InterfaceOrganizationSubTagsQuery = useQuery(USER_TAG_SUB_TAGS, { variables: { id: parentTagId, - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, }, }); const loadMoreSubTags = (): void => { fetchMoreSubTags({ variables: { - first: TAGS_QUERY_LIMIT, + first: TAGS_QUERY_PAGE_SIZE, after: subTagsData?.getUserTag.childTags.pageInfo.endCursor, }, updateQuery: ( diff --git a/src/utils/organizationTagsUtils.ts b/src/utils/organizationTagsUtils.ts index fc987f37b5..6cac6a79f5 100644 --- a/src/utils/organizationTagsUtils.ts +++ b/src/utils/organizationTagsUtils.ts @@ -1,5 +1,13 @@ // This file will contain the utililities for organization tags +import type { ApolloError } from '@apollo/client'; +import type { + InterfaceQueryOrganizationUserTags, + InterfaceQueryUserTagChildTags, + InterfaceQueryUserTagsAssignedMembers, + InterfaceQueryUserTagsMembersToAssignTo, +} from './interfaces'; + // This is the style object for mui's data grid used to list the data (tags and member data) export const dataGridStyle = { '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { @@ -22,7 +30,102 @@ export const dataGridStyle = { }, }; -export const ADD_PEOPLE_TO_TAGS_QUERY_LIMIT = 7; -export const TAGS_QUERY_LIMIT = 10; +// the page limit for tag related queries +export const TAGS_QUERY_PAGE_SIZE = 10; +// the tag action type export type TagActionType = 'assignToTags' | 'removeFromTags'; + +// interfaces for tag queries +export interface InterfaceOrganizationTagsQuery { + data?: { + organizations: InterfaceQueryOrganizationUserTags[]; + }; + loading: boolean; + error?: ApolloError; + refetch: () => void; + fetchMore: (options: { + variables: { + first: number; + after?: string; + }; + updateQuery: ( + previousResult: { organizations: InterfaceQueryOrganizationUserTags[] }, + options: { + fetchMoreResult?: { + organizations: InterfaceQueryOrganizationUserTags[]; + }; + }, + ) => { organizations: InterfaceQueryOrganizationUserTags[] }; + }) => void; +} + +export interface InterfaceOrganizationSubTagsQuery { + data?: { + getUserTag: InterfaceQueryUserTagChildTags; + }; + loading: boolean; + error?: ApolloError; + refetch: () => void; + fetchMore: (options: { + variables: { + first: number; + after?: string; + }; + updateQuery: ( + previousResult: { getUserTag: InterfaceQueryUserTagChildTags }, + options: { + fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; + }, + ) => { getUserTag: InterfaceQueryUserTagChildTags }; + }) => void; +} + +export interface InterfaceTagAssignedMembersQuery { + data?: { + getUserTag: InterfaceQueryUserTagsAssignedMembers; + }; + loading: boolean; + error?: ApolloError; + refetch: () => void; + fetchMore: (options: { + variables: { + after?: string | null; + first?: number | null; + }; + updateQuery?: ( + previousQueryResult: { + getUserTag: InterfaceQueryUserTagsAssignedMembers; + }, + options: { + fetchMoreResult: { + getUserTag: InterfaceQueryUserTagsAssignedMembers; + }; + }, + ) => { getUserTag: InterfaceQueryUserTagsAssignedMembers }; + }) => Promise; +} + +export interface InterfaceTagUsersToAssignToQuery { + data?: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }; + loading: boolean; + error?: ApolloError; + fetchMore: (options: { + variables: { + after?: string | null; + first?: number | null; + }; + updateQuery?: ( + previousQueryResult: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }, + options: { + fetchMoreResult: { + getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + }; + }, + ) => { getUserTag: InterfaceQueryUserTagsMembersToAssignTo }; + }) => Promise; +} From 01b4b65fd36bdc485e118320eccebd2ccaf4986c Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 14:00:13 +0530 Subject: [PATCH 12/52] minor correction --- src/components/TagActions/TagActions.test.tsx | 8 ++++---- src/components/TagActions/TagNode.tsx | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 3fd98c98c2..3f44b314ce 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -195,17 +195,17 @@ describe('Organisation Tags Page', () => { userEvent.click(screen.getByTestId('expandSubTags1')); await waitFor(() => { - expect(screen.getByTestId('subTagsScrollableDiv')).toBeInTheDocument(); + expect(screen.getByTestId('subTagsScrollableDiv1')).toBeInTheDocument(); }); // Find the infinite scroll div for subtags by test ID or another selector - const subTagsScrollableDiv = screen.getByTestId('subTagsScrollableDiv'); + const subTagsScrollableDiv1 = screen.getByTestId('subTagsScrollableDiv1'); const initialTagsDataLength = screen.getAllByTestId('orgUserSubTags').length; // Set scroll position to the bottom - fireEvent.scroll(subTagsScrollableDiv, { - target: { scrollY: subTagsScrollableDiv.scrollHeight }, + fireEvent.scroll(subTagsScrollableDiv1, { + target: { scrollY: subTagsScrollableDiv1.scrollHeight }, }); await waitFor(() => { diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index 7a22a46077..8bad6c78ec 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -157,8 +157,8 @@ const TagNode: React.FC = ({ {expanded && subTagsList && (
= ({
} - scrollableTarget="subTagsScrollableDiv" + scrollableTarget={`subTagsScrollableDiv${tag._id}`} > {subTagsList.map((tag: InterfaceTagData) => (
From ba59202dcfb86b6747924402cede77c957b954c5 Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 15:03:53 +0530 Subject: [PATCH 13/52] more design adjustments --- src/screens/ManageTag/ManageTag.tsx | 10 +- .../OrganizationTags/OrganizationTags.tsx | 106 ++--------------- src/screens/SubTags/SubTags.tsx | 110 ++---------------- 3 files changed, 29 insertions(+), 197 deletions(-) diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 08e0aee77b..b739809519 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -273,7 +273,7 @@ function ManageTag(): JSX.Element { }; const redirectToManageTag = (tagId: string): void => { - navigate(`/orgtags/${orgId}/managetag/${tagId}`); + navigate(`/orgtags/${orgId}/manageTag/${tagId}`); }; const toggleUnassignTagModal = (): void => { @@ -322,20 +322,20 @@ function ManageTag(): JSX.Element { headerClassName: `${styles.tableHeader}`, renderCell: (params: GridCellParams) => { return ( -
+
-
{t('viewProfile')}
+
+ {t('viewProfile')} +
- - -
+ ); }, }, @@ -470,43 +425,6 @@ function OrganizationTags(): JSX.Element { - - {/* Remove User Tag Modal */} - - - - {t('removeUserTag')} - - - {t('removeUserTagMessage')} - - - - - ); } diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index 0307785ce2..bb6bf5870f 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -23,10 +23,7 @@ import { } from 'utils/organizationTagsUtils'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; -import { - CREATE_USER_TAG, - REMOVE_USER_TAG, -} from 'GraphQl/Mutations/TagMutations'; +import { CREATE_USER_TAG } from 'GraphQl/Mutations/TagMutations'; import { USER_TAG_ANCESTORS, USER_TAG_SUB_TAGS, @@ -54,10 +51,6 @@ function SubTags(): JSX.Element { const [tagName, setTagName] = useState(''); - const [removeUserTagId, setRemoveUserTagId] = useState(null); - const [removeUserTagModalIsOpen, setRemoveUserTagModalIsOpen] = - useState(false); - const showAddSubTagModal = (): void => { setAddSubTagModalIsOpen(true); }; @@ -162,26 +155,6 @@ function SubTags(): JSX.Element { } }; - const [removeUserTag] = useMutation(REMOVE_USER_TAG); - const handleRemoveUserTag = async (): Promise => { - try { - await removeUserTag({ - variables: { - id: removeUserTagId, - }, - }); - - subTagsRefetch(); - toggleRemoveUserTagModal(); - toast.success(t('tagRemovalSuccess') as string); - } catch (error: unknown) { - /* istanbul ignore next */ - if (error instanceof Error) { - toast.error(error.message); - } - } - }; - if (createUserTagLoading || subTagsLoading || orgUserTagsAncestorsLoading) { return ; } @@ -215,14 +188,7 @@ function SubTags(): JSX.Element { }; const redirectToSubTags = (tagId: string): void => { - navigate(`/orgtags/${orgId}/subtags/${tagId}`); - }; - - const toggleRemoveUserTagModal = (): void => { - if (removeUserTagModalIsOpen) { - setRemoveUserTagId(null); - } - setRemoveUserTagModalIsOpen(!removeUserTagModalIsOpen); + navigate(`/orgtags/${orgId}/subTags/${tagId}`); }; const columns: GridColDef[] = [ @@ -272,7 +238,7 @@ function SubTags(): JSX.Element { return ( {params.row.childTags.totalCount} @@ -292,7 +258,7 @@ function SubTags(): JSX.Element { return ( {params.row.usersAssignedTo.totalCount} @@ -310,28 +276,14 @@ function SubTags(): JSX.Element { headerClassName: `${styles.tableHeader}`, renderCell: (params: GridCellParams) => { return ( -
- - - -
+ ); }, }, @@ -524,44 +476,6 @@ function SubTags(): JSX.Element { - - {/* Remove User Tag Modal */} - - - - {t('removeUserTag')} - - - {t('removeUserTagMessage')} - - - - - ); } From a59da700f03b6a157c50b59cbcd4f771622964f9 Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 16:18:17 +0530 Subject: [PATCH 14/52] add tests --- src/screens/ManageTag/ManageTag.test.tsx | 34 ++- src/screens/ManageTag/ManageTagMocks.ts | 175 ++++++----- .../OrganizationTags.test.tsx | 75 ++--- .../OrganizationTags/OrganizationTagsMocks.ts | 201 +++++-------- src/screens/SubTags/SubTags.test.tsx | 79 ++--- src/screens/SubTags/SubTagsMocks.ts | 275 ++++++++---------- 6 files changed, 337 insertions(+), 502 deletions(-) diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index a27ebad8b4..e2c4f889c9 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -4,6 +4,7 @@ import type { RenderResult } from '@testing-library/react'; import { act, cleanup, + fireEvent, render, screen, waitFor, @@ -80,11 +81,11 @@ const renderManageTag = (link: ApolloLink): RenderResult => { element={
} /> } />
} /> { }); }); - test('paginates between different pages', async () => { - renderManageTag(link); + test('Fetches more assigned members with infinite scroll', async () => { + const { getByText } = renderManageTag(link); await wait(); await waitFor(() => { - expect(screen.getByTestId('nextPagBtn')).toBeInTheDocument(); + expect(getByText(translations.addPeopleToTag)).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('nextPagBtn')); - await waitFor(() => { - expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( - 'member 6', - ); - }); + // Get the initial number of tags loaded + const initialAssignedMembersDataLength = + screen.getAllByTestId('viewProfileBtn').length; - await waitFor(() => { - expect(screen.getByTestId('previousPageBtn')).toBeInTheDocument(); + // Set scroll position to the bottom of the window + fireEvent.scroll(window, { + target: { scrollY: document.documentElement.scrollHeight }, }); - userEvent.click(screen.getByTestId('previousPageBtn')); await waitFor(() => { - expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( - 'member 1', + const finalAssignedMembersDataLength = + screen.getAllByTestId('viewProfileBtn').length; + expect(finalAssignedMembersDataLength).toBeGreaterThan( + initialAssignedMembersDataLength, ); + + expect(getByText(translations.addPeopleToTag)).toBeInTheDocument(); }); }); diff --git a/src/screens/ManageTag/ManageTagMocks.ts b/src/screens/ManageTag/ManageTagMocks.ts index 91b9f2a622..18d6e67212 100644 --- a/src/screens/ManageTag/ManageTagMocks.ts +++ b/src/screens/ManageTag/ManageTagMocks.ts @@ -17,10 +17,7 @@ export const MOCKS = [ query: USER_TAGS_ASSIGNED_MEMBERS, variables: { id: '1', - after: null, - before: null, - first: 5, - last: null, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { @@ -69,36 +66,6 @@ export const MOCKS = [ }, cursor: '5', }, - ], - pageInfo: { - startCursor: '1', - endCursor: '5', - hasNextPage: true, - hasPreviousPage: false, - }, - totalCount: 6, - }, - }, - }, - }, - }, - { - request: { - query: USER_TAGS_ASSIGNED_MEMBERS, - variables: { - id: '1', - after: '5', - before: null, - first: 5, - last: null, - }, - }, - result: { - data: { - getUserTag: { - name: 'tag1', - usersAssignedTo: { - edges: [ { node: { _id: '6', @@ -107,14 +74,46 @@ export const MOCKS = [ }, cursor: '6', }, + { + node: { + _id: '7', + firstName: 'member', + lastName: '7', + }, + cursor: '7', + }, + { + node: { + _id: '8', + firstName: 'member', + lastName: '8', + }, + cursor: '8', + }, + { + node: { + _id: '9', + firstName: 'member', + lastName: '9', + }, + cursor: '9', + }, + { + node: { + _id: '10', + firstName: 'member', + lastName: '10', + }, + cursor: '10', + }, ], pageInfo: { - startCursor: '6', - endCursor: '6', - hasNextPage: false, - hasPreviousPage: true, + startCursor: '1', + endCursor: '10', + hasNextPage: true, + hasPreviousPage: false, }, - totalCount: 6, + totalCount: 12, }, }, }, @@ -125,10 +124,8 @@ export const MOCKS = [ query: USER_TAGS_ASSIGNED_MEMBERS, variables: { id: '1', - after: null, - before: '6', - first: null, - last: 5, + first: TAGS_QUERY_PAGE_SIZE, + after: '10', }, }, result: { @@ -139,52 +136,28 @@ export const MOCKS = [ edges: [ { node: { - _id: '1', + _id: '11', firstName: 'member', - lastName: '1', + lastName: '11', }, - cursor: '1', - }, - { - node: { - _id: '2', - firstName: 'member', - lastName: '2', - }, - cursor: '2', - }, - { - node: { - _id: '3', - firstName: 'member', - lastName: '3', - }, - cursor: '3', - }, - { - node: { - _id: '4', - firstName: 'member', - lastName: '4', - }, - cursor: '4', + cursor: '11', }, { node: { - _id: '5', + _id: '12', firstName: 'member', - lastName: '5', + lastName: '12', }, - cursor: '5', + cursor: '12', }, ], pageInfo: { - startCursor: '1', - endCursor: '5', - hasNextPage: true, - hasPreviousPage: false, + startCursor: '11', + endCursor: '12', + hasNextPage: false, + hasPreviousPage: true, }, - totalCount: 6, + totalCount: 12, }, }, }, @@ -195,7 +168,7 @@ export const MOCKS = [ query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, variables: { id: '1', - first: 7, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { @@ -260,11 +233,35 @@ export const MOCKS = [ }, cursor: '7', }, + { + node: { + _id: '8', + firstName: 'member', + lastName: '8', + }, + cursor: '8', + }, + { + node: { + _id: '9', + firstName: 'member', + lastName: '9', + }, + cursor: '9', + }, + { + node: { + _id: '10', + firstName: 'member', + lastName: '10', + }, + cursor: '10', + }, ], pageInfo: { startCursor: '1', - endCursor: '7', - hasNextPage: true, + endCursor: '10', + hasNextPage: false, hasPreviousPage: false, }, totalCount: 10, @@ -504,10 +501,7 @@ export const MOCKS_ERROR_ASSIGNED_MEMBERS = [ query: USER_TAGS_ASSIGNED_MEMBERS, variables: { id: '1', - after: null, - before: null, - first: 5, - last: null, + first: TAGS_QUERY_PAGE_SIZE, }, }, error: new Error('Mock Graphql Error'), @@ -533,10 +527,7 @@ export const MOCKS_ERROR_TAG_ANCESTORS = [ query: USER_TAGS_ASSIGNED_MEMBERS, variables: { id: '1', - after: null, - before: null, - first: 5, - last: null, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { @@ -547,11 +538,11 @@ export const MOCKS_ERROR_TAG_ANCESTORS = [ edges: [], pageInfo: { startCursor: '1', - endCursor: '5', - hasNextPage: true, + endCursor: '1', + hasNextPage: false, hasPreviousPage: false, }, - totalCount: 6, + totalCount: 1, }, }, }, diff --git a/src/screens/OrganizationTags/OrganizationTags.test.tsx b/src/screens/OrganizationTags/OrganizationTags.test.tsx index 923a26d982..5bee9c7a96 100644 --- a/src/screens/OrganizationTags/OrganizationTags.test.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.test.tsx @@ -4,6 +4,7 @@ import type { RenderResult } from '@testing-library/react'; import { act, cleanup, + fireEvent, render, screen, waitFor, @@ -59,11 +60,11 @@ const renderOrganizationTags = (link: ApolloLink): RenderResult => { } />
} />
} /> @@ -103,7 +104,7 @@ describe('Organisation Tags Page', () => { await wait(); await waitFor(() => { - expect(queryByText(translations.create)).not.toBeInTheDocument(); + expect(queryByText(translations.createTag)).not.toBeInTheDocument(); }); }); @@ -129,28 +130,6 @@ describe('Organisation Tags Page', () => { ); }); - test('opens and closes the remove tag modal', async () => { - renderOrganizationTags(link); - - await wait(); - - await waitFor(() => { - expect(screen.getAllByTestId('removeUserTagBtn')[0]).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('removeUserTagBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('removeUserTagModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('removeUserTagModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('removeUserTagModalCloseBtn'), - ); - }); - test('navigates to sub tags screen after clicking on a tag', async () => { renderOrganizationTags(link); @@ -181,27 +160,28 @@ describe('Organisation Tags Page', () => { }); }); - test('paginates between different pages', async () => { - renderOrganizationTags(link); + test('Fetches more tags with infinite scroll', async () => { + const { getByText } = renderOrganizationTags(link); await wait(); await waitFor(() => { - expect(screen.getByTestId('nextPagBtn')).toBeInTheDocument(); + expect(getByText(translations.createTag)).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('nextPagBtn')); - await waitFor(() => { - expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent('6'); - }); + // Get the initial number of tags loaded + const initialTagsDataLength = screen.getAllByTestId('manageTagBtn').length; - await waitFor(() => { - expect(screen.getByTestId('previousPageBtn')).toBeInTheDocument(); + // Set scroll position to the bottom of the window + fireEvent.scroll(window, { + target: { scrollY: document.documentElement.scrollHeight }, }); - userEvent.click(screen.getByTestId('previousPageBtn')); await waitFor(() => { - expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent('1'); + const finalTagsDataLength = screen.getAllByTestId('manageTagBtn').length; + expect(finalTagsDataLength).toBeGreaterThan(initialTagsDataLength); + + expect(getByText(translations.createTag)).toBeInTheDocument(); }); }); @@ -217,30 +197,15 @@ describe('Organisation Tags Page', () => { userEvent.type( screen.getByPlaceholderText(translations.tagNamePlaceholder), - '7', + 'userTag 12', ); userEvent.click(screen.getByTestId('createTagSubmitBtn')); await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.tagCreationSuccess); - }); - }); - - test('removes a user tag', async () => { - renderOrganizationTags(link); - - await wait(); - - await waitFor(() => { - expect(screen.getAllByTestId('removeUserTagBtn')[0]).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('removeUserTagBtn')[0]); - - userEvent.click(screen.getByTestId('removeUserTagSubmitBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.tagRemovalSuccess); + expect(toast.success).toHaveBeenCalledWith( + translations.tagCreationSuccess, + ); }); }); }); diff --git a/src/screens/OrganizationTags/OrganizationTagsMocks.ts b/src/screens/OrganizationTags/OrganizationTagsMocks.ts index 3700c66c46..e9dadc3578 100644 --- a/src/screens/OrganizationTags/OrganizationTagsMocks.ts +++ b/src/screens/OrganizationTags/OrganizationTagsMocks.ts @@ -1,8 +1,6 @@ -import { - CREATE_USER_TAG, - REMOVE_USER_TAG, -} from 'GraphQl/Mutations/TagMutations'; +import { CREATE_USER_TAG } from 'GraphQl/Mutations/TagMutations'; import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; +import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; export const MOCKS = [ { @@ -10,10 +8,7 @@ export const MOCKS = [ query: ORGANIZATION_USER_TAGS_LIST, variables: { id: '123', - after: null, - before: null, - first: 5, - last: null, + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { @@ -30,7 +25,7 @@ export const MOCKS = [ totalCount: 5, }, childTags: { - totalCount: 5, + totalCount: 11, }, }, cursor: '1', @@ -87,154 +82,79 @@ export const MOCKS = [ }, cursor: '5', }, - ], - pageInfo: { - startCursor: '1', - endCursor: '5', - hasNextPage: true, - hasPreviousPage: false, - }, - totalCount: 6, - }, - }, - ], - }, - }, - }, - { - request: { - query: ORGANIZATION_USER_TAGS_LIST, - variables: { - id: '123', - after: '5', - before: null, - first: 5, - last: null, - }, - }, - result: { - data: { - organizations: [ - { - userTags: { - edges: [ { node: { _id: '6', name: 'userTag 6', usersAssignedTo: { - totalCount: 0, + totalCount: 6, }, childTags: { - totalCount: 0, + totalCount: 6, }, }, cursor: '6', }, - ], - pageInfo: { - startCursor: '6', - endCursor: '6', - hasNextPage: false, - hasPreviousPage: true, - }, - totalCount: 6, - }, - }, - ], - }, - }, - }, - { - request: { - query: ORGANIZATION_USER_TAGS_LIST, - variables: { - id: '123', - after: null, - before: '6', - first: null, - last: 5, - }, - }, - result: { - data: { - organizations: [ - { - userTags: { - edges: [ - { - node: { - _id: '1', - name: 'userTag 1', - usersAssignedTo: { - totalCount: 5, - }, - childTags: { - totalCount: 5, - }, - }, - cursor: '1', - }, { node: { - _id: '2', - name: 'userTag 2', + _id: '7', + name: 'userTag 7', usersAssignedTo: { - totalCount: 5, + totalCount: 7, }, childTags: { - totalCount: 0, + totalCount: 7, }, }, - cursor: '2', + cursor: '7', }, { node: { - _id: '3', - name: 'userTag 3', + _id: '8', + name: 'userTag 8', usersAssignedTo: { - totalCount: 0, + totalCount: 8, }, childTags: { - totalCount: 5, + totalCount: 8, }, }, - cursor: '3', + cursor: '8', }, { node: { - _id: '4', - name: 'userTag 4', + _id: '9', + name: 'userTag 9', usersAssignedTo: { - totalCount: 0, + totalCount: 9, }, childTags: { - totalCount: 0, + totalCount: 9, }, }, - cursor: '4', + cursor: '9', }, { node: { - _id: '5', - name: 'userTag 5', + _id: '10', + name: 'userTag 10', usersAssignedTo: { - totalCount: 5, + totalCount: 10, }, childTags: { - totalCount: 5, + totalCount: 10, }, }, - cursor: '5', + cursor: '10', }, ], pageInfo: { startCursor: '1', - endCursor: '5', + endCursor: '10', hasNextPage: true, hasPreviousPage: false, }, - totalCount: 6, + totalCount: 12, }, }, ], @@ -243,31 +163,71 @@ export const MOCKS = [ }, { request: { - query: CREATE_USER_TAG, + query: ORGANIZATION_USER_TAGS_LIST, variables: { - name: '7', - organizationId: '123', + id: '123', + first: TAGS_QUERY_PAGE_SIZE, + after: '10', }, }, result: { data: { - createUserTag: { - _id: '7', - }, + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: '11', + name: 'userTag 11', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: '11', + }, + { + node: { + _id: '12', + name: 'userTag 12', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: '12', + }, + ], + pageInfo: { + startCursor: '11', + endCursor: '12', + hasNextPage: false, + hasPreviousPage: true, + }, + totalCount: 12, + }, + }, + ], }, }, }, { request: { - query: REMOVE_USER_TAG, + query: CREATE_USER_TAG, variables: { - id: '1', + name: 'userTag 12', + organizationId: '123', }, }, result: { data: { - removeUserTag: { - _id: '1', + createUserTag: { + _id: '12', }, }, }, @@ -280,10 +240,7 @@ export const MOCKS_ERROR = [ query: ORGANIZATION_USER_TAGS_LIST, variables: { id: '123', - after: null, - before: null, - first: 5, - last: null, + first: TAGS_QUERY_PAGE_SIZE, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/screens/SubTags/SubTags.test.tsx b/src/screens/SubTags/SubTags.test.tsx index 1780027639..3d0358f93d 100644 --- a/src/screens/SubTags/SubTags.test.tsx +++ b/src/screens/SubTags/SubTags.test.tsx @@ -4,6 +4,7 @@ import type { RenderResult } from '@testing-library/react'; import { act, cleanup, + fireEvent, render, screen, waitFor, @@ -61,7 +62,7 @@ const cache = new InMemoryCache({ fields: { getUserTag: { keyArgs: false, - merge(existing = {}, incoming) { + merge(_, incoming) { return incoming; }, }, @@ -73,7 +74,7 @@ const cache = new InMemoryCache({ const renderSubTags = (link: ApolloLink): RenderResult => { return render( - + @@ -82,11 +83,11 @@ const renderSubTags = (link: ApolloLink): RenderResult => { element={
} />
} /> } /> @@ -163,28 +164,6 @@ describe('Organisation Tags Page', () => { ); }); - test('opens and closes the remove tag modal', async () => { - renderSubTags(link); - - await wait(); - - await waitFor(() => { - expect(screen.getAllByTestId('removeUserTagBtn')[0]).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('removeUserTagBtn')[0]); - - await waitFor(() => { - return expect( - screen.findByTestId('removeUserTagModalCloseBtn'), - ).resolves.toBeInTheDocument(); - }); - userEvent.click(screen.getByTestId('removeUserTagModalCloseBtn')); - - await waitForElementToBeRemoved(() => - screen.queryByTestId('removeUserTagModalCloseBtn'), - ); - }); - test('navigates to manage tag screen after clicking manage tag option', async () => { renderSubTags(link); @@ -260,27 +239,30 @@ describe('Organisation Tags Page', () => { }); }); - test('paginates between different pages', async () => { - renderSubTags(link); + test('Fetches more sub tags with infinite scroll', async () => { + const { getByText } = renderSubTags(link); await wait(); await waitFor(() => { - expect(screen.getByTestId('nextPagBtn')).toBeInTheDocument(); + expect(getByText(translations.addChildTag)).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('nextPagBtn')); - await waitFor(() => { - expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent('subTag 6'); - }); + // Get the initial number of tags loaded + const initialSubTagsDataLength = + screen.getAllByTestId('manageTagBtn').length; - await waitFor(() => { - expect(screen.getByTestId('previousPageBtn')).toBeInTheDocument(); + // Set scroll position to the bottom of the window + fireEvent.scroll(window, { + target: { scrollY: document.documentElement.scrollHeight }, }); - userEvent.click(screen.getByTestId('previousPageBtn')); await waitFor(() => { - expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent('subTag 1'); + const finalSubTagsDataLength = + screen.getAllByTestId('manageTagBtn').length; + expect(finalSubTagsDataLength).toBeGreaterThan(initialSubTagsDataLength); + + expect(getByText(translations.addChildTag)).toBeInTheDocument(); }); }); @@ -296,30 +278,15 @@ describe('Organisation Tags Page', () => { userEvent.type( screen.getByPlaceholderText(translations.tagNamePlaceholder), - 'subTag 7', + 'subTag 12', ); userEvent.click(screen.getByTestId('addSubTagSubmitBtn')); await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.tagCreationSuccess); - }); - }); - - test('removes a sub tag', async () => { - renderSubTags(link); - - await wait(); - - await waitFor(() => { - expect(screen.getAllByTestId('removeUserTagBtn')[0]).toBeInTheDocument(); - }); - userEvent.click(screen.getAllByTestId('removeUserTagBtn')[0]); - - userEvent.click(screen.getByTestId('removeUserTagSubmitBtn')); - - await waitFor(() => { - expect(toast.success).toBeCalledWith(translations.tagRemovalSuccess); + expect(toast.success).toHaveBeenCalledWith( + translations.tagCreationSuccess, + ); }); }); }); diff --git a/src/screens/SubTags/SubTagsMocks.ts b/src/screens/SubTags/SubTagsMocks.ts index 757f3f42ad..2f26ecce7f 100644 --- a/src/screens/SubTags/SubTagsMocks.ts +++ b/src/screens/SubTags/SubTagsMocks.ts @@ -1,33 +1,28 @@ -import { - CREATE_USER_TAG, - REMOVE_USER_TAG, -} from 'GraphQl/Mutations/TagMutations'; +import { CREATE_USER_TAG } from 'GraphQl/Mutations/TagMutations'; import { USER_TAG_ANCESTORS, USER_TAG_SUB_TAGS, } from 'GraphQl/Queries/userTagQueries'; +import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; export const MOCKS = [ { request: { query: USER_TAG_SUB_TAGS, variables: { - id: 'tag1', - after: null, - before: null, - first: 5, - last: null, + id: '1', + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { data: { getUserTag: { - name: 'tag1', + name: 'userTag 1', childTags: { edges: [ { node: { - _id: '1', + _id: 'subTag1', name: 'subTag 1', usersAssignedTo: { totalCount: 5, @@ -36,11 +31,11 @@ export const MOCKS = [ totalCount: 5, }, }, - cursor: '1', + cursor: 'subTag1', }, { node: { - _id: '2', + _id: 'subTag2', name: 'subTag 2', usersAssignedTo: { totalCount: 5, @@ -49,11 +44,11 @@ export const MOCKS = [ totalCount: 0, }, }, - cursor: '2', + cursor: 'subTag2', }, { node: { - _id: '3', + _id: 'subTag3', name: 'subTag 3', usersAssignedTo: { totalCount: 0, @@ -62,11 +57,11 @@ export const MOCKS = [ totalCount: 5, }, }, - cursor: '3', + cursor: 'subTag3', }, { node: { - _id: '4', + _id: 'subTag4', name: 'subTag 4', usersAssignedTo: { totalCount: 0, @@ -75,11 +70,11 @@ export const MOCKS = [ totalCount: 0, }, }, - cursor: '4', + cursor: 'subTag4', }, { node: { - _id: '5', + _id: 'subTag5', name: 'subTag 5', usersAssignedTo: { totalCount: 5, @@ -88,85 +83,12 @@ export const MOCKS = [ totalCount: 5, }, }, - cursor: '5', + cursor: 'subTag5', }, - ], - pageInfo: { - startCursor: '1', - endCursor: '5', - hasNextPage: true, - hasPreviousPage: false, - }, - totalCount: 6, - }, - }, - }, - }, - }, - { - request: { - query: USER_TAG_SUB_TAGS, - variables: { - id: 'tag1', - after: '5', - before: null, - first: 5, - last: null, - }, - }, - result: { - data: { - getUserTag: { - name: 'tag1', - childTags: { - edges: [ { node: { - _id: '6', + _id: 'subTag6', name: 'subTag 6', - usersAssignedTo: { - totalCount: 0, - }, - childTags: { - totalCount: 0, - }, - }, - cursor: '6', - }, - ], - pageInfo: { - startCursor: '6', - endCursor: '6', - hasNextPage: false, - hasPreviousPage: true, - }, - totalCount: 6, - }, - }, - }, - }, - }, - { - request: { - query: USER_TAG_SUB_TAGS, - variables: { - id: 'tag1', - after: null, - before: '6', - first: null, - last: 5, - }, - }, - result: { - data: { - getUserTag: { - name: 'tag1', - childTags: { - edges: [ - { - node: { - _id: '1', - name: 'subTag 1', usersAssignedTo: { totalCount: 5, }, @@ -174,51 +96,51 @@ export const MOCKS = [ totalCount: 5, }, }, - cursor: '1', + cursor: 'subTag6', }, { node: { - _id: '2', - name: 'subTag 2', + _id: 'subTag7', + name: 'subTag 7', usersAssignedTo: { totalCount: 5, }, childTags: { - totalCount: 0, + totalCount: 5, }, }, - cursor: '2', + cursor: 'subTag7', }, { node: { - _id: '3', - name: 'subTag 3', + _id: 'subTag8', + name: 'subTag 8', usersAssignedTo: { - totalCount: 0, + totalCount: 5, }, childTags: { totalCount: 5, }, }, - cursor: '3', + cursor: 'subTag8', }, { node: { - _id: '4', - name: 'subTag 4', + _id: 'subTag9', + name: 'subTag 9', usersAssignedTo: { - totalCount: 0, + totalCount: 5, }, childTags: { - totalCount: 0, + totalCount: 5, }, }, - cursor: '4', + cursor: 'subTag9', }, { node: { - _id: '5', - name: 'subTag 5', + _id: 'subTag10', + name: 'subTag 10', usersAssignedTo: { totalCount: 5, }, @@ -226,16 +148,16 @@ export const MOCKS = [ totalCount: 5, }, }, - cursor: '5', + cursor: 'subTag10', }, ], pageInfo: { startCursor: '1', - endCursor: '5', + endCursor: '10', hasNextPage: true, hasPreviousPage: false, }, - totalCount: 6, + totalCount: 11, }, }, }, @@ -246,25 +168,37 @@ export const MOCKS = [ query: USER_TAG_SUB_TAGS, variables: { id: '1', - after: null, - before: null, - first: 5, - last: null, + after: '10', + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { data: { getUserTag: { - name: 'subTag 1', + name: 'tag1', childTags: { - edges: [], + edges: [ + { + node: { + _id: 'subTag11', + name: 'subTag 11', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: 'subTag11', + }, + ], pageInfo: { - startCursor: null, - endCursor: null, + startCursor: '11', + endCursor: '11', hasNextPage: false, - hasPreviousPage: false, + hasPreviousPage: true, }, - totalCount: 0, + totalCount: 11, }, }, }, @@ -274,7 +208,7 @@ export const MOCKS = [ request: { query: USER_TAG_ANCESTORS, variables: { - id: 'tag1', + id: '1', }, }, result: { @@ -288,22 +222,62 @@ export const MOCKS = [ }, }, }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: 'subTag1', + first: TAGS_QUERY_PAGE_SIZE, + }, + }, + result: { + data: { + getUserTag: { + name: 'subTag 1', + childTags: { + edges: [ + { + node: { + _id: 'subTag1.1', + name: 'subTag 1.1', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + }, + cursor: 'subTag1.1', + }, + ], + pageInfo: { + startCursor: 'subTag1.1', + endCursor: 'subTag1.1', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 1, + }, + }, + }, + }, + }, { request: { query: USER_TAG_ANCESTORS, variables: { - id: '1', + id: 'subTag1', }, }, result: { data: { getUserTagAncestors: [ { - _id: 'tag1', + _id: '1', name: 'tag 1', }, { - _id: '1', + _id: 'subTag1', name: 'subTag 1', }, ], @@ -314,30 +288,15 @@ export const MOCKS = [ request: { query: CREATE_USER_TAG, variables: { - name: 'subTag 7', + name: 'subTag 12', organizationId: '123', - parentTagId: 'tag1', + parentTagId: '1', }, }, result: { data: { createUserTag: { - _id: '7', - }, - }, - }, - }, - { - request: { - query: REMOVE_USER_TAG, - variables: { - id: '1', - }, - }, - result: { - data: { - removeUserTag: { - _id: '1', + _id: 'subTag12', }, }, }, @@ -349,11 +308,8 @@ export const MOCKS_ERROR_SUB_TAGS = [ request: { query: USER_TAG_SUB_TAGS, variables: { - id: 'tag1', - after: null, - before: null, - first: 5, - last: null, + id: '1', + first: TAGS_QUERY_PAGE_SIZE, }, }, error: new Error('Mock Graphql Error'), @@ -362,7 +318,7 @@ export const MOCKS_ERROR_SUB_TAGS = [ request: { query: USER_TAG_ANCESTORS, variables: { - id: 'tag1', + id: '1', }, }, result: { @@ -378,11 +334,8 @@ export const MOCKS_ERROR_TAG_ANCESTORS = [ request: { query: USER_TAG_SUB_TAGS, variables: { - id: 'tag1', - after: null, - before: null, - first: 5, - last: null, + id: '1', + first: TAGS_QUERY_PAGE_SIZE, }, }, result: { @@ -392,12 +345,12 @@ export const MOCKS_ERROR_TAG_ANCESTORS = [ childTags: { edges: [], pageInfo: { - startCursor: '1', - endCursor: '5', - hasNextPage: true, + startCursor: '0', + endCursor: '0', + hasNextPage: false, hasPreviousPage: false, }, - totalCount: 6, + totalCount: 0, }, }, }, @@ -407,7 +360,7 @@ export const MOCKS_ERROR_TAG_ANCESTORS = [ request: { query: USER_TAG_ANCESTORS, variables: { - id: 'tag1', + id: '1', }, }, error: new Error('Mock Graphql Error'), From f77300542d16f7662d3b2d0b1669a6c323d84330 Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 18:23:18 +0530 Subject: [PATCH 15/52] add scrollbar for infinite scroll --- src/screens/ManageTag/ManageTag.module.css | 8 ++ src/screens/ManageTag/ManageTag.test.tsx | 8 +- src/screens/ManageTag/ManageTag.tsx | 91 ++++++++++--------- .../OrganizationTags.module.css | 8 ++ .../OrganizationTags.test.tsx | 10 +- .../OrganizationTags/OrganizationTags.tsx | 87 ++++++++++-------- src/screens/SubTags/SubTags.module.css | 8 ++ src/screens/SubTags/SubTags.test.tsx | 8 +- src/screens/SubTags/SubTags.tsx | 86 ++++++++++-------- 9 files changed, 184 insertions(+), 130 deletions(-) diff --git a/src/screens/ManageTag/ManageTag.module.css b/src/screens/ManageTag/ManageTag.module.css index db1886099a..191d74c048 100644 --- a/src/screens/ManageTag/ManageTag.module.css +++ b/src/screens/ManageTag/ManageTag.module.css @@ -117,3 +117,11 @@ font-weight: 600; text-decoration: underline; } + +.manageTagScrollableDiv { + scrollbar-width: auto; + scrollbar-color: var(--bs-gray-400) var(--bs-white); + + height: calc(100vh - 18rem); + overflow: auto; +} diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index e2c4f889c9..d3476af053 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -347,13 +347,15 @@ describe('Manage Tag Page', () => { expect(getByText(translations.addPeopleToTag)).toBeInTheDocument(); }); + const manageTagScrollableDiv = screen.getByTestId('manageTagScrollableDiv'); + // Get the initial number of tags loaded const initialAssignedMembersDataLength = screen.getAllByTestId('viewProfileBtn').length; - // Set scroll position to the bottom of the window - fireEvent.scroll(window, { - target: { scrollY: document.documentElement.scrollHeight }, + // Set scroll position to the bottom + fireEvent.scroll(manageTagScrollableDiv, { + target: { scrollY: manageTagScrollableDiv.scrollHeight }, }); await waitFor(() => { diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index b739809519..3e210b45af 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -449,49 +449,56 @@ function ManageTag(): JSX.Element {
))}
- -
-
- } +
- row._id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noAssignedMembersFound')} - - ), - }} - sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={userTagAssignedMembers?.map( - (assignedMembers, index) => ({ - id: index + 1, - ...assignedMembers, - }), - )} - columns={columns} - isRowSelectable={() => false} - /> - + +
+
+ } + scrollableTarget="manageTagScrollableDiv" + > + row._id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noAssignedMembersFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagAssignedMembers?.map( + (assignedMembers, index) => ({ + id: index + 1, + ...assignedMembers, + }), + )} + columns={columns} + isRowSelectable={() => false} + /> +
+
diff --git a/src/screens/OrganizationTags/OrganizationTags.module.css b/src/screens/OrganizationTags/OrganizationTags.module.css index 7251a79d0d..f3ce815c26 100644 --- a/src/screens/OrganizationTags/OrganizationTags.module.css +++ b/src/screens/OrganizationTags/OrganizationTags.module.css @@ -139,3 +139,11 @@ color: var(--bs-gray); cursor: pointer; } + +.orgUserTagsScrollableDiv { + scrollbar-width: auto; + scrollbar-color: var(--bs-gray-400) var(--bs-white); + + height: calc(100vh - 18rem); + overflow: auto; +} diff --git a/src/screens/OrganizationTags/OrganizationTags.test.tsx b/src/screens/OrganizationTags/OrganizationTags.test.tsx index 5bee9c7a96..1ab94ce35c 100644 --- a/src/screens/OrganizationTags/OrganizationTags.test.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.test.tsx @@ -169,12 +169,16 @@ describe('Organisation Tags Page', () => { expect(getByText(translations.createTag)).toBeInTheDocument(); }); + const orgUserTagsScrollableDiv = screen.getByTestId( + 'orgUserTagsScrollableDiv', + ); + // Get the initial number of tags loaded const initialTagsDataLength = screen.getAllByTestId('manageTagBtn').length; - // Set scroll position to the bottom of the window - fireEvent.scroll(window, { - target: { scrollY: document.documentElement.scrollHeight }, + // Set scroll position to the bottom + fireEvent.scroll(orgUserTagsScrollableDiv, { + target: { scrollY: orgUserTagsScrollableDiv.scrollHeight }, }); await waitFor(() => { diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index d5379c5319..29819fffbe 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -329,47 +329,54 @@ function OrganizationTags(): JSX.Element { {'Tags'} - -
-
- } +
- row._id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noTagsFound')} - - ), - }} - sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={userTagsList?.map((fund, index) => ({ - id: index + 1, - ...fund, - }))} - columns={columns} - isRowSelectable={() => false} - /> - + +
+
+ } + scrollableTarget="orgUserTagsScrollableDiv" + > + row._id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noTagsFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagsList?.map((fund, index) => ({ + id: index + 1, + ...fund, + }))} + columns={columns} + isRowSelectable={() => false} + /> +
+
diff --git a/src/screens/SubTags/SubTags.module.css b/src/screens/SubTags/SubTags.module.css index 2fed58ec52..ef219e37b4 100644 --- a/src/screens/SubTags/SubTags.module.css +++ b/src/screens/SubTags/SubTags.module.css @@ -135,3 +135,11 @@ font-weight: 600; text-decoration: underline; } + +.subTagsScrollableDiv { + scrollbar-width: auto; + scrollbar-color: var(--bs-gray-400) var(--bs-white); + + height: calc(100vh - 18rem); + overflow: auto; +} diff --git a/src/screens/SubTags/SubTags.test.tsx b/src/screens/SubTags/SubTags.test.tsx index 3d0358f93d..e9382a4fb8 100644 --- a/src/screens/SubTags/SubTags.test.tsx +++ b/src/screens/SubTags/SubTags.test.tsx @@ -248,13 +248,15 @@ describe('Organisation Tags Page', () => { expect(getByText(translations.addChildTag)).toBeInTheDocument(); }); + const subTagsScrollableDiv = screen.getByTestId('subTagsScrollableDiv'); + // Get the initial number of tags loaded const initialSubTagsDataLength = screen.getAllByTestId('manageTagBtn').length; - // Set scroll position to the bottom of the window - fireEvent.scroll(window, { - target: { scrollY: document.documentElement.scrollHeight }, + // Set scroll position to the bottom + fireEvent.scroll(subTagsScrollableDiv, { + target: { scrollY: subTagsScrollableDiv.scrollHeight }, }); await waitFor(() => { diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index bb6bf5870f..cba452a267 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -385,46 +385,54 @@ function SubTags(): JSX.Element { ))} - -
-
- } +
- row._id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noTagsFound')} - - ), - }} - sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={userTagsList?.map((fund, index) => ({ - id: index + 1, - ...fund, - }))} - columns={columns} - isRowSelectable={() => false} - /> - + +
+
+ } + scrollableTarget="subTagsScrollableDiv" + > + row._id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noTagsFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagsList?.map((fund, index) => ({ + id: index + 1, + ...fund, + }))} + columns={columns} + isRowSelectable={() => false} + /> +
+
From e42b8fe284f36520cb8c08b68b05b389d738da92 Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 20:49:41 +0530 Subject: [PATCH 16/52] extract infinite scroll loader --- .../AddPeopleToTag/AddPeopleToTag.module.css | 24 ------------------- .../AddPeopleToTag/AddPeopleToTag.tsx | 9 +++---- .../InfiniteScrollLoader.module.css | 24 +++++++++++++++++++ .../InfiniteScrollLoader.test.tsx | 14 +++++++++++ .../InfiniteScrollLoader.tsx | 15 ++++++++++++ .../TagActions/TagActions.module.css | 24 ------------------- src/components/TagActions/TagActions.tsx | 9 +++---- src/components/TagActions/TagNode.tsx | 9 +++---- src/screens/ManageTag/ManageTag.module.css | 2 +- src/screens/ManageTag/ManageTag.tsx | 7 ++---- .../OrganizationTags.module.css | 2 +- .../OrganizationTags/OrganizationTags.tsx | 7 ++---- src/screens/SubTags/SubTags.module.css | 2 +- src/screens/SubTags/SubTags.tsx | 7 ++---- 14 files changed, 71 insertions(+), 84 deletions(-) create mode 100644 src/components/InfiniteScrollLoader/InfiniteScrollLoader.module.css create mode 100644 src/components/InfiniteScrollLoader/InfiniteScrollLoader.test.tsx create mode 100644 src/components/InfiniteScrollLoader/InfiniteScrollLoader.tsx diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css index fb8599d96c..945f887655 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.module.css +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -51,27 +51,3 @@ max-height: 350px; /* Set your desired max height */ overflow-y: auto; /* Enable vertical scrolling */ } - -/* SimpleLoader.css */ -.simple-loader { - display: flex; - justify-content: center; - align-items: center; - width: 100%; - height: 100%; -} - -.spinner { - width: 40px; - height: 40px; - border: 4px solid rgba(0, 0, 0, 0.1); - border-top-color: #3498db; - border-radius: 50%; - animation: spin 0.6s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index e04c757993..95447fc196 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -20,6 +20,7 @@ import { ADD_PEOPLE_TO_TAG } from 'GraphQl/Mutations/TagMutations'; import InfiniteScroll from 'react-infinite-scroll-component'; import { WarningAmberRounded } from '@mui/icons-material'; import { useTranslation } from 'react-i18next'; +import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScrollLoader'; /** * Props for the `AddPeopleToTag` component. @@ -280,7 +281,7 @@ const AddPeopleToTag: React.FC = ({ id="scrollableDiv" data-testid="scrollableDiv" style={{ - height: 300, + maxHeight: 300, overflow: 'auto', }} > @@ -291,11 +292,7 @@ const AddPeopleToTag: React.FC = ({ userTagsMembersToAssignToData?.getUserTag.usersToAssignTo .pageInfo.hasNextPage ?? false } - loader={ -
-
-
- } + loader={} scrollableTarget="scrollableDiv" > { + test('Component should be rendered properly', () => { + render(); + + expect(screen.getByTestId('infiniteScrollLoader')).toBeInTheDocument(); + expect( + screen.getByTestId('infiniteScrollLoaderSpinner'), + ).toBeInTheDocument(); + }); +}); diff --git a/src/components/InfiniteScrollLoader/InfiniteScrollLoader.tsx b/src/components/InfiniteScrollLoader/InfiniteScrollLoader.tsx new file mode 100644 index 0000000000..1554721e32 --- /dev/null +++ b/src/components/InfiniteScrollLoader/InfiniteScrollLoader.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import styles from './InfiniteScrollLoader.module.css'; + +const InfiniteScrollLoader = (): JSX.Element => { + return ( +
+
+
+ ); +}; + +export default InfiniteScrollLoader; diff --git a/src/components/TagActions/TagActions.module.css b/src/components/TagActions/TagActions.module.css index e667adb96e..d291929137 100644 --- a/src/components/TagActions/TagActions.module.css +++ b/src/components/TagActions/TagActions.module.css @@ -148,27 +148,3 @@ color: rgb(77, 76, 76); font-weight: 600; } - -/* SimpleLoader.css */ -.simpleLoader { - display: flex; - justify-content: start; - align-items: center; - width: 100%; - height: 100%; -} - -.spinner { - width: 40px; - height: 40px; - border: 4px solid var(--bs-gray); - border-top-color: var(--bs-gray); - border-radius: 50%; - animation: spin 0.6s linear infinite; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index 57ffe7268f..534d2089d5 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -24,6 +24,7 @@ import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; import InfiniteScroll from 'react-infinite-scroll-component'; import { WarningAmberRounded } from '@mui/icons-material'; import TagNode from './TagNode'; +import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScrollLoader'; interface InterfaceUserTagsAncestorData { _id: string; @@ -359,7 +360,7 @@ const TagActions: React.FC = ({ id="scrollableDiv" data-testid="scrollableDiv" style={{ - height: 300, + maxHeight: 300, overflow: 'auto', }} className={`${styles.scrContainer}`} @@ -371,11 +372,7 @@ const TagActions: React.FC = ({ orgUserTagsData?.organizations[0].userTags.pageInfo .hasNextPage ?? false } - loader={ -
-
-
- } + loader={} scrollableTarget="scrollableDiv" > {userTagsList?.map((tag) => ( diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index 0feda3799b..50b009ca83 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -9,6 +9,7 @@ import type { InterfaceOrganizationSubTagsQuery } from 'utils/organizationTagsUt import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; import styles from './TagActions.module.css'; import InfiniteScroll from 'react-infinite-scroll-component'; +import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScrollLoader'; interface InterfaceTagNodeProps { tag: InterfaceTagData; @@ -141,7 +142,7 @@ const TagNode: React.FC = ({ id={`subTagsScrollableDiv${tag._id}`} data-testid={`subTagsScrollableDiv${tag._id}`} style={{ - height: 300, + maxHeight: 300, overflow: 'auto', }} className={`${styles.scrContainer}`} @@ -152,11 +153,7 @@ const TagNode: React.FC = ({ hasMore={ subTagsData?.getUserTag.childTags.pageInfo.hasNextPage ?? false } - loader={ -
-
-
- } + loader={} scrollableTarget={`subTagsScrollableDiv${tag._id}`} > {subTagsList.map((tag: InterfaceTagData) => ( diff --git a/src/screens/ManageTag/ManageTag.module.css b/src/screens/ManageTag/ManageTag.module.css index 191d74c048..4607f774cc 100644 --- a/src/screens/ManageTag/ManageTag.module.css +++ b/src/screens/ManageTag/ManageTag.module.css @@ -122,6 +122,6 @@ scrollbar-width: auto; scrollbar-color: var(--bs-gray-400) var(--bs-white); - height: calc(100vh - 18rem); + max-height: calc(100vh - 18rem); overflow: auto; } diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 3e210b45af..c1d8a6686c 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -38,6 +38,7 @@ import { import AddPeopleToTag from 'components/AddPeopleToTag/AddPeopleToTag'; import TagActions from 'components/TagActions/TagActions'; import InfiniteScroll from 'react-infinite-scroll-component'; +import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScrollLoader'; /** * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/managetag/:tagId'. @@ -461,11 +462,7 @@ function ManageTag(): JSX.Element { userTagAssignedMembersData?.getUserTag.usersAssignedTo .pageInfo.hasNextPage ?? false } - loader={ -
-
-
- } + loader={} scrollableTarget="manageTagScrollableDiv" > -
-
- } + loader={} scrollableTarget="orgUserTagsScrollableDiv" > -
-
- } + loader={} scrollableTarget="subTagsScrollableDiv" > Date: Sun, 27 Oct 2024 20:58:03 +0530 Subject: [PATCH 17/52] minor adjustment --- src/screens/ManageTag/ManageTag.tsx | 2 +- src/screens/OrganizationTags/OrganizationTags.tsx | 2 +- src/screens/SubTags/SubTags.tsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index c1d8a6686c..3b07399a4f 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -420,7 +420,7 @@ function ManageTag(): JSX.Element { -
+
diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index f1889df979..75fb421695 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -321,7 +321,7 @@ function OrganizationTags(): JSX.Element {
-
+
diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index ff3c555e8e..4eb431e3b0 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -357,7 +357,7 @@ function SubTags(): JSX.Element {
-
+
From b223e210c125fccb1f11c6514f41e93ff9f28dd9 Mon Sep 17 00:00:00 2001 From: meetul Date: Sun, 27 Oct 2024 23:43:30 +0530 Subject: [PATCH 18/52] refactoring --- src/GraphQl/Queries/userTagQueries.ts | 6 +- .../AddPeopleToTag/AddPeopleToTag.tsx | 32 +- .../AddPeopleToTag/AddPeopleToTagsMocks.ts | 4 +- src/components/TagActions/TagActions.test.tsx | 33 ++- src/components/TagActions/TagActions.tsx | 20 +- src/components/TagActions/TagActionsMocks.ts | 4 +- src/components/TagActions/TagNode.tsx | 21 +- src/screens/ManageTag/ManageTag.test.tsx | 80 ++--- src/screens/ManageTag/ManageTag.tsx | 36 +-- .../MockAddPeopleToTag.tsx | 24 ++ .../MockTagActions.tsx | 24 ++ src/screens/ManageTag/ManageTagMocks.ts | 273 +----------------- src/screens/SubTags/SubTags.test.tsx | 33 +-- src/screens/SubTags/SubTags.tsx | 22 +- src/screens/SubTags/SubTagsMocks.ts | 8 +- src/utils/organizationTagsUtils.ts | 24 +- 16 files changed, 229 insertions(+), 415 deletions(-) create mode 100644 src/screens/ManageTag/ManageTagMockComponents/MockAddPeopleToTag.tsx create mode 100644 src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx diff --git a/src/GraphQl/Queries/userTagQueries.ts b/src/GraphQl/Queries/userTagQueries.ts index 9e1a11daa3..33a9b63174 100644 --- a/src/GraphQl/Queries/userTagQueries.ts +++ b/src/GraphQl/Queries/userTagQueries.ts @@ -15,7 +15,7 @@ export const USER_TAGS_ASSIGNED_MEMBERS = gql` $first: PositiveInt $last: PositiveInt ) { - getUserTag(id: $id) { + getAssignedUsers: getUserTag(id: $id) { name usersAssignedTo( after: $after @@ -57,7 +57,7 @@ export const USER_TAG_SUB_TAGS = gql` $first: PositiveInt $last: PositiveInt ) { - getUserTag(id: $id) { + getChildTags: getUserTag(id: $id) { name childTags(after: $after, before: $before, first: $first, last: $last) { edges { @@ -99,7 +99,7 @@ export const USER_TAGS_MEMBERS_TO_ASSIGN_TO = gql` $first: PositiveInt $last: PositiveInt ) { - getUserTag(id: $id) { + getUsersToAssignTo: getUserTag(id: $id) { name usersToAssignTo( after: $after diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index 95447fc196..08e112b426 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -67,6 +67,7 @@ const AddPeopleToTag: React.FC = ({ first: TAGS_QUERY_PAGE_SIZE, }, skip: !addPeopleToTagModalIsOpen, + fetchPolicy: 'no-cache', }, ); @@ -75,29 +76,31 @@ const AddPeopleToTag: React.FC = ({ variables: { first: TAGS_QUERY_PAGE_SIZE, after: - userTagsMembersToAssignToData?.getUserTag.usersToAssignTo.pageInfo - .endCursor, // Fetch after the last loaded cursor + userTagsMembersToAssignToData?.getUsersToAssignTo.usersToAssignTo + .pageInfo.endCursor, // Fetch after the last loaded cursor }, updateQuery: ( - prevResult: { getUserTag: InterfaceQueryUserTagsMembersToAssignTo }, + prevResult: { + getUsersToAssignTo: InterfaceQueryUserTagsMembersToAssignTo; + }, { fetchMoreResult, }: { fetchMoreResult: { - getUserTag: InterfaceQueryUserTagsMembersToAssignTo; + getUsersToAssignTo: InterfaceQueryUserTagsMembersToAssignTo; }; }, ) => { if (!fetchMoreResult) return prevResult; return { - getUserTag: { - ...fetchMoreResult.getUserTag, + getUsersToAssignTo: { + ...fetchMoreResult.getUsersToAssignTo, usersToAssignTo: { - ...fetchMoreResult.getUserTag.usersToAssignTo, + ...fetchMoreResult.getUsersToAssignTo.usersToAssignTo, edges: [ - ...prevResult.getUserTag.usersToAssignTo.edges, - ...fetchMoreResult.getUserTag.usersToAssignTo.edges, + ...prevResult.getUsersToAssignTo.usersToAssignTo.edges, + ...fetchMoreResult.getUsersToAssignTo.usersToAssignTo.edges, ], }, }, @@ -106,8 +109,13 @@ const AddPeopleToTag: React.FC = ({ }); }; + // const userTagMembersToAssignTo = + // userTagsMembersToAssignToData?.getUsersToAssignTo.usersToAssignTo.edges.map( + // (edge) => edge.node, + // ); + const userTagMembersToAssignTo = - userTagsMembersToAssignToData?.getUserTag.usersToAssignTo.edges.map( + userTagsMembersToAssignToData?.getUsersToAssignTo.usersToAssignTo.edges.map( (edge) => edge.node, ); @@ -289,8 +297,8 @@ const AddPeopleToTag: React.FC = ({ dataLength={userTagMembersToAssignTo?.length ?? 0} // This is important field to render the next data next={loadMoreMembersToAssignTo} hasMore={ - userTagsMembersToAssignToData?.getUserTag.usersToAssignTo - .pageInfo.hasNextPage ?? false + userTagsMembersToAssignToData?.getUsersToAssignTo + .usersToAssignTo.pageInfo.hasNextPage ?? false } loader={} scrollableTarget="scrollableDiv" diff --git a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts index 9c18c45487..67b554e94c 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts +++ b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts @@ -13,7 +13,7 @@ export const MOCKS = [ }, result: { data: { - getUserTag: { + getUsersToAssignTo: { name: 'tag1', usersToAssignTo: { edges: [ @@ -121,7 +121,7 @@ export const MOCKS = [ }, result: { data: { - getUserTag: { + getUsersToAssignTo: { name: 'tag1', usersToAssignTo: { edges: [ diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 3f44b314ce..4014dcfc89 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -50,15 +50,15 @@ const translations = { const props: InterfaceTagActionsProps[] = [ { - assignToTagsModalIsOpen: true, - hideAssignToTagsModal: () => {}, + tagActionsModalIsOpen: true, + hideTagActionsModal: () => {}, tagActionType: 'assignToTags', t: (key: string) => translations[key], tCommon: (key: string) => translations[key], }, { - assignToTagsModalIsOpen: true, - hideAssignToTagsModal: () => {}, + tagActionsModalIsOpen: true, + hideTagActionsModal: () => {}, tagActionType: 'removeFromTags', t: (key: string) => translations[key], tCommon: (key: string) => translations[key], @@ -120,6 +120,31 @@ describe('Organisation Tags Page', () => { }); }); + test('Component calls hideTagActionsModal when modal is closed', async () => { + const hideTagActionsModalMock = jest.fn(); + + const props2: InterfaceTagActionsProps = { + tagActionsModalIsOpen: true, + hideTagActionsModal: hideTagActionsModalMock, + tagActionType: 'assignToTags', + t: (key: string) => key, + tCommon: (key: string) => key, + }; + + renderTagActionsModal(props2, link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('closeTagActionsModalBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('closeTagActionsModalBtn')); + + await waitFor(() => { + expect(hideTagActionsModalMock).toHaveBeenCalled(); + }); + }); + test('Renders error component when when query is unsuccessful', async () => { const { queryByText } = renderTagActionsModal(props[0], link2); diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index 534d2089d5..756c656ba8 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -35,16 +35,16 @@ interface InterfaceUserTagsAncestorData { * Props for the `AssignToTags` component. */ export interface InterfaceTagActionsProps { - assignToTagsModalIsOpen: boolean; - hideAssignToTagsModal: () => void; + tagActionsModalIsOpen: boolean; + hideTagActionsModal: () => void; tagActionType: TagActionType; t: (key: string) => string; tCommon: (key: string) => string; } const TagActions: React.FC = ({ - assignToTagsModalIsOpen, - hideAssignToTagsModal, + tagActionsModalIsOpen, + hideTagActionsModal, tagActionType, t, tCommon, @@ -61,7 +61,7 @@ const TagActions: React.FC = ({ id: orgId, first: TAGS_QUERY_PAGE_SIZE, }, - skip: !assignToTagsModalIsOpen, + skip: !tagActionsModalIsOpen, }); const loadMoreUserTags = (): void => { @@ -244,7 +244,7 @@ const TagActions: React.FC = ({ if (data) { toast.success(t('successfullyAssignedToTags')); - hideAssignToTagsModal(); + hideTagActionsModal(); } } catch (error: unknown) { /* istanbul ignore next */ @@ -271,7 +271,7 @@ const TagActions: React.FC = ({ if (data) { toast.success(t('successfullyRemovedFromTags')); - hideAssignToTagsModal(); + hideTagActionsModal(); } } catch (error: unknown) { /* istanbul ignore next */ @@ -299,8 +299,8 @@ const TagActions: React.FC = ({ return ( <> = ({ +
+ )} + + ); +}; + +export default MockAddPeopleToTag; diff --git a/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx b/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx new file mode 100644 index 0000000000..e122a4f985 --- /dev/null +++ b/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import type { InterfaceTagActionsProps } from '../../../components/TagActions/TagActions'; + +const MockTagActions: React.FC = ({ + tagActionsModalIsOpen, + hideTagActionsModal, +}) => { + return ( + <> + {tagActionsModalIsOpen && ( +
+ +
+ )} + + ); +}; + +export default MockTagActions; diff --git a/src/screens/ManageTag/ManageTagMocks.ts b/src/screens/ManageTag/ManageTagMocks.ts index 18d6e67212..c8f4bba2b3 100644 --- a/src/screens/ManageTag/ManageTagMocks.ts +++ b/src/screens/ManageTag/ManageTagMocks.ts @@ -3,11 +3,9 @@ import { UNASSIGN_USER_TAG, UPDATE_USER_TAG, } from 'GraphQl/Mutations/TagMutations'; -import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; import { USER_TAG_ANCESTORS, USER_TAGS_ASSIGNED_MEMBERS, - USER_TAGS_MEMBERS_TO_ASSIGN_TO, } from 'GraphQl/Queries/userTagQueries'; import { TAGS_QUERY_PAGE_SIZE } from 'utils/organizationTagsUtils'; @@ -22,7 +20,7 @@ export const MOCKS = [ }, result: { data: { - getUserTag: { + getAssignedUsers: { name: 'tag1', usersAssignedTo: { edges: [ @@ -130,7 +128,7 @@ export const MOCKS = [ }, result: { data: { - getUserTag: { + getAssignedUsers: { name: 'tag1', usersAssignedTo: { edges: [ @@ -163,113 +161,6 @@ export const MOCKS = [ }, }, }, - { - request: { - query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, - variables: { - id: '1', - first: TAGS_QUERY_PAGE_SIZE, - }, - }, - result: { - data: { - getUserTag: { - name: 'tag1', - usersToAssignTo: { - edges: [ - { - node: { - _id: '1', - firstName: 'member', - lastName: '1', - }, - cursor: '1', - }, - { - node: { - _id: '2', - firstName: 'member', - lastName: '2', - }, - cursor: '2', - }, - { - node: { - _id: '3', - firstName: 'member', - lastName: '3', - }, - cursor: '3', - }, - { - node: { - _id: '4', - firstName: 'member', - lastName: '4', - }, - cursor: '4', - }, - { - node: { - _id: '5', - firstName: 'member', - lastName: '5', - }, - cursor: '5', - }, - { - node: { - _id: '6', - firstName: 'member', - lastName: '6', - }, - cursor: '6', - }, - { - node: { - _id: '7', - firstName: 'member', - lastName: '7', - }, - cursor: '7', - }, - { - node: { - _id: '8', - firstName: 'member', - lastName: '8', - }, - cursor: '8', - }, - { - node: { - _id: '9', - firstName: 'member', - lastName: '9', - }, - cursor: '9', - }, - { - node: { - _id: '10', - firstName: 'member', - lastName: '10', - }, - cursor: '10', - }, - ], - pageInfo: { - startCursor: '1', - endCursor: '10', - hasNextPage: false, - hasPreviousPage: false, - }, - totalCount: 10, - }, - }, - }, - }, - }, { request: { query: USER_TAG_ANCESTORS, @@ -304,164 +195,6 @@ export const MOCKS = [ }, }, }, - { - request: { - query: ORGANIZATION_USER_TAGS_LIST, - variables: { - id: '123', - first: TAGS_QUERY_PAGE_SIZE, - }, - }, - result: { - data: { - organizations: [ - { - userTags: { - edges: [ - { - node: { - _id: '1', - name: 'userTag 1', - usersAssignedTo: { - totalCount: 5, - }, - childTags: { - totalCount: 11, - }, - }, - cursor: '1', - }, - { - node: { - _id: '2', - name: 'userTag 2', - usersAssignedTo: { - totalCount: 5, - }, - childTags: { - totalCount: 0, - }, - }, - cursor: '2', - }, - { - node: { - _id: '3', - name: 'userTag 3', - usersAssignedTo: { - totalCount: 0, - }, - childTags: { - totalCount: 5, - }, - }, - cursor: '3', - }, - { - node: { - _id: '4', - name: 'userTag 4', - usersAssignedTo: { - totalCount: 0, - }, - childTags: { - totalCount: 0, - }, - }, - cursor: '4', - }, - { - node: { - _id: '5', - name: 'userTag 5', - usersAssignedTo: { - totalCount: 5, - }, - childTags: { - totalCount: 5, - }, - }, - cursor: '5', - }, - { - node: { - _id: '6', - name: 'userTag 6', - usersAssignedTo: { - totalCount: 6, - }, - childTags: { - totalCount: 6, - }, - }, - cursor: '6', - }, - { - node: { - _id: '7', - name: 'userTag 7', - usersAssignedTo: { - totalCount: 7, - }, - childTags: { - totalCount: 7, - }, - }, - cursor: '7', - }, - { - node: { - _id: '8', - name: 'userTag 8', - usersAssignedTo: { - totalCount: 8, - }, - childTags: { - totalCount: 8, - }, - }, - cursor: '8', - }, - { - node: { - _id: '9', - name: 'userTag 9', - usersAssignedTo: { - totalCount: 9, - }, - childTags: { - totalCount: 9, - }, - }, - cursor: '9', - }, - { - node: { - _id: '10', - name: 'userTag 10', - usersAssignedTo: { - totalCount: 10, - }, - childTags: { - totalCount: 10, - }, - }, - cursor: '10', - }, - ], - pageInfo: { - startCursor: '1', - endCursor: '10', - hasNextPage: true, - hasPreviousPage: false, - }, - totalCount: 12, - }, - }, - ], - }, - }, - }, { request: { query: UPDATE_USER_TAG, @@ -532,7 +265,7 @@ export const MOCKS_ERROR_TAG_ANCESTORS = [ }, result: { data: { - getUserTag: { + getAssignedUsers: { name: 'tag1', usersAssignedTo: { edges: [], diff --git a/src/screens/SubTags/SubTags.test.tsx b/src/screens/SubTags/SubTags.test.tsx index e9382a4fb8..9cd778b858 100644 --- a/src/screens/SubTags/SubTags.test.tsx +++ b/src/screens/SubTags/SubTags.test.tsx @@ -25,7 +25,7 @@ import { MOCKS_ERROR_SUB_TAGS, MOCKS_ERROR_TAG_ANCESTORS, } from './SubTagsMocks'; -import { InMemoryCache, type ApolloLink } from '@apollo/client'; +import { type ApolloLink } from '@apollo/client'; const translations = { ...JSON.parse( @@ -56,24 +56,24 @@ jest.mock('react-toastify', () => ({ }, })); -const cache = new InMemoryCache({ - typePolicies: { - Query: { - fields: { - getUserTag: { - keyArgs: false, - merge(_, incoming) { - return incoming; - }, - }, - }, - }, - }, -}); +// const cache = new InMemoryCache({ +// typePolicies: { +// Query: { +// fields: { +// getUserTag: { +// keyArgs: false, +// merge(_, incoming) { +// return incoming; +// }, +// }, +// }, +// }, +// }, +// }); const renderSubTags = (link: ApolloLink): RenderResult => { return render( - + @@ -104,7 +104,6 @@ describe('Organisation Tags Page', () => { ...jest.requireActual('react-router-dom'), useParams: () => ({ orgId: 'orgId' }), })); - cache.reset(); }); afterEach(() => { diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index 4eb431e3b0..4201c14b53 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -78,26 +78,26 @@ function SubTags(): JSX.Element { fetchMoreSubTags({ variables: { first: TAGS_QUERY_PAGE_SIZE, - after: subTagsData?.getUserTag.childTags.pageInfo.endCursor, + after: subTagsData?.getChildTags.childTags.pageInfo.endCursor, }, updateQuery: ( - prevResult: { getUserTag: InterfaceQueryUserTagChildTags }, + prevResult: { getChildTags: InterfaceQueryUserTagChildTags }, { fetchMoreResult, }: { - fetchMoreResult?: { getUserTag: InterfaceQueryUserTagChildTags }; + fetchMoreResult?: { getChildTags: InterfaceQueryUserTagChildTags }; }, ) => { if (!fetchMoreResult) return prevResult; return { - getUserTag: { - ...fetchMoreResult.getUserTag, + getChildTags: { + ...fetchMoreResult.getChildTags, childTags: { - ...fetchMoreResult.getUserTag.childTags, + ...fetchMoreResult.getChildTags.childTags, edges: [ - ...prevResult.getUserTag.childTags.edges, - ...fetchMoreResult.getUserTag.childTags.edges, + ...prevResult.getChildTags.childTags.edges, + ...fetchMoreResult.getChildTags.childTags.edges, ], }, }, @@ -178,7 +178,7 @@ function SubTags(): JSX.Element { ); } - const userTagsList = subTagsData?.getUserTag.childTags.edges.map( + const userTagsList = subTagsData?.getChildTags.childTags.edges.map( (edge) => edge.node, ); @@ -341,7 +341,7 @@ function SubTags(): JSX.Element { data-testid="manageCurrentTagBtn" className="mx-4" > - {`${t('manageTag')} ${subTagsData?.getUserTag.name}`} + {`${t('manageTag')} ${subTagsData?.getChildTags.name}`}
@@ -337,8 +318,8 @@ const TagActions: React.FC = ({
tagActionType === 'assignToTags' - ? assignToSelectedTags(e) - : removeFromSelectedTags(e) + ? handleTagAction(e) + : handleTagAction(e) } > diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index c7a9c80ee2..46ef0b85db 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -366,8 +366,8 @@ export const MOCKS = [ }, ], pageInfo: { - startCursor: '1', - endCursor: '10', + startCursor: 'subTag1', + endCursor: 'subTag10', hasNextPage: true, hasPreviousPage: false, }, @@ -382,7 +382,7 @@ export const MOCKS = [ query: USER_TAG_SUB_TAGS, variables: { id: '1', - after: '10', + after: 'subTag10', first: TAGS_QUERY_LIMIT, }, }, @@ -407,8 +407,8 @@ export const MOCKS = [ }, ], pageInfo: { - startCursor: '11', - endCursor: '11', + startCursor: 'subTag11', + endCursor: 'subTag11', hasNextPage: false, hasPreviousPage: true, }, @@ -458,10 +458,7 @@ export const MOCKS_ERROR = [ query: ORGANIZATION_USER_TAGS_LIST, variables: { id: '123', - after: null, - before: null, - first: 5, - last: null, + first: TAGS_QUERY_LIMIT, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index a27ebad8b4..5de5e97c88 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -49,6 +49,7 @@ async function wait(ms = 500): Promise { jest.mock('react-toastify', () => ({ toast: { success: jest.fn(), + info: jest.fn(), error: jest.fn(), }, })); @@ -394,6 +395,12 @@ describe('Manage Tag Page', () => { }); userEvent.click(screen.getByTestId('editTag')); + userEvent.click(screen.getByTestId('editTagSubmitBtn')); + + await waitFor(() => { + expect(toast.info).toHaveBeenCalledWith(translations.changeNameToEdit); + }); + const tagNameInput = screen.getByTestId('tagNameInput'); await userEvent.clear(tagNameInput); await userEvent.type(tagNameInput, 'tag 1 edited'); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index b861ff962b..8099ca8754 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -87,6 +87,10 @@ function ManageTag(): JSX.Element { setAssignToTagsModalIsOpen(false); }; + const showEditTagModal = (): void => { + setEditTagModalIsOpen(true); + }; + const hideEditTagModal = (): void => { setEditTagModalIsOpen(false); }; @@ -159,6 +163,7 @@ function ManageTag(): JSX.Element { const [edit] = useMutation(UPDATE_USER_TAG); const [newTagName, setNewTagName] = useState(''); + const currentTagName = userTagAssignedMembersData?.getUserTag.name ?? ''; useEffect(() => { setNewTagName(userTagAssignedMembersData?.getUserTag.name ?? ''); @@ -167,6 +172,11 @@ function ManageTag(): JSX.Element { const editTag = async (e: FormEvent): Promise => { e.preventDefault(); + if (newTagName === currentTagName) { + toast.info(t('changeNameToEdit')); + return; + } + try { const { data } = await edit({ variables: { @@ -524,18 +534,14 @@ function ManageTag(): JSX.Element {
{ - setEditTagModalIsOpen(true); - }} + onClick={showEditTagModal} className="ms-5 mt-3 mb-2 btn btn-primary btn-sm w-75" data-testid="editTag" > {tCommon('edit')}
{ - setRemoveTagModalIsOpen(true); - }} + onClick={toggleRemoveUserTagModal} className="ms-5 mb-2 btn btn-danger btn-sm w-75" data-testid="removeTag" > From 9bc25d75e83803564832cb83561756ce6ced1aa1 Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 13:34:32 +0530 Subject: [PATCH 24/52] more changes --- public/locales/en/translation.json | 6 +++++- public/locales/fr/translation.json | 6 +++++- public/locales/hi/translation.json | 6 +++++- public/locales/sp/translation.json | 6 +++++- public/locales/zh/translation.json | 6 +++++- src/components/TagActions/TagActions.tsx | 19 +++++++------------ src/components/TagActions/TagActionsMocks.ts | 2 +- src/components/TagActions/TagNode.tsx | 5 ++++- src/screens/ManageTag/ManageTag.tsx | 2 +- 9 files changed, 38 insertions(+), 20 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 19bea8e4b4..3b202bd593 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "Tag updated successfully", "tagRemovalSuccess": "Tag deleted successfully", "noTagSelected": "No Tag Selected", - "changeNameToEdit": "Change the name to make an update" + "changeNameToEdit": "Change the name to make an update", + "selectTag": "Select Tag", + "collapse": "Collapse", + "expand": "Expand", + "tagNamePlaceholder": "Write the name of the tag" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index b961c587ff..7b11e35f56 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "Étiquette mise à jour avec succès", "tagRemovalSuccess": "Étiquette supprimée avec succès", "noTagSelected": "Aucun tag sélectionné", - "changeNameToEdit": "Modifiez le nom pour faire une mise à jour" + "changeNameToEdit": "Modifiez le nom pour faire une mise à jour", + "selectTag": "Sélectionner le tag", + "collapse": "Réduire", + "expand": "Développer", + "tagNamePlaceholder": "Écrire le nom du tag" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 940be5a73c..bab222175c 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "टैग सफलतापूर्वक अपडेट की गई", "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई", "noTagSelected": "कोई टैग चयनित नहीं", - "changeNameToEdit": "अपडेट करने के लिए नाम बदलें" + "changeNameToEdit": "अपडेट करने के लिए नाम बदलें", + "selectTag": "टैग चुनें", + "collapse": "संक्षिप्त करें", + "expand": "विस्तारित करें", + "tagNamePlaceholder": "टैग का नाम लिखें" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 4e65b4f8d2..e301bde4d7 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "Etiqueta actualizada con éxito", "tagRemovalSuccess": "Etiqueta eliminada con éxito", "noTagSelected": "Ninguna etiqueta seleccionada", - "changeNameToEdit": "Cambia el nombre para hacer una actualización" + "changeNameToEdit": "Cambia el nombre para hacer una actualización", + "selectTag": "Seleccionar etiqueta", + "collapse": "Colapsar", + "expand": "Expandir", + "tagNamePlaceholder": "Escribe el nombre de la etiqueta" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index d6fe494a04..07d1905ca8 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -353,7 +353,11 @@ "tagUpdationSuccess": "标签更新成功", "tagRemovalSuccess": "标签删除成功", "noTagSelected": "未选择标签", - "changeNameToEdit": "更改名称以进行更新" + "changeNameToEdit": "更改名称以进行更新", + "selectTag": "选择标签", + "collapse": "收起", + "expand": "展开", + "tagNamePlaceholder": "输入标签名称" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index b5c219aa3d..c1006c12a9 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -87,7 +87,7 @@ const TagActions: React.FC = ({ ); const [checkedTagId, setCheckedTagId] = useState(null); - const [uncheckedTagId, setUnheckedTagId] = useState(null); + const [uncheckedTagId, setUncheckedTagId] = useState(null); // tags that we have selected to assigned const [selectedTags, setSelectedTags] = useState([]); @@ -148,11 +148,11 @@ const TagActions: React.FC = ({ const addAncestorTags = (tagId: string): void => { setCheckedTagId(tagId); - setUnheckedTagId(null); + setUncheckedTagId(null); }; const removeAncestorTags = (tagId: string): void => { - setUnheckedTagId(tagId); + setUncheckedTagId(tagId); setCheckedTagId(null); }; @@ -315,13 +315,7 @@ const TagActions: React.FC = ({ : t('removeFromTags')} - - tagActionType === 'assignToTags' - ? handleTagAction(e) - : handleTagAction(e) - } - > + {orgUserTagsLoading ? ( @@ -341,10 +335,11 @@ const TagActions: React.FC = ({ className={`badge bg-dark-subtle text-secondary-emphasis lh-lg my-2 ms-2 d-flex align-items-center ${styles.tagBadge}`} > {tag.name} - deSelectTag(tag)} data-testid={`clearSelectedTag${tag._id}`} + aria-label={t('remove')} />
)) diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index 46ef0b85db..30fff78e3e 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -389,7 +389,7 @@ export const MOCKS = [ result: { data: { getUserTag: { - name: 'tag1', + name: 'userTag 1', childTags: { edges: [ { diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index e419e22e05..4d2f6dbadd 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -11,7 +11,7 @@ import styles from './TagActions.module.css'; import InfiniteScroll from 'react-infinite-scroll-component'; /** - * Props for the `AssignToTags` component. + * Props for the `TagNode` component. */ interface InterfaceTagNodeProps { tag: InterfaceTagData; @@ -122,6 +122,7 @@ const TagNode: React.FC = ({ className="me-3" style={{ cursor: 'pointer' }} data-testid={`expandSubTags${tag._id}`} + aria-label={expanded ? t('collapse') : t('expand')} > {expanded ? '▼' : '▶'} @@ -132,6 +133,7 @@ const TagNode: React.FC = ({ className="me-2" onChange={handleCheckboxChange} data-testid={`checkTag${tag._id}`} + aria-label={t('selectTag')} /> {' '} @@ -145,6 +147,7 @@ const TagNode: React.FC = ({ className="ms-1 me-2" onChange={handleCheckboxChange} data-testid={`checkTag${tag._id}`} + aria-label={tag.name} /> {' '} diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 8099ca8754..05b45b1205 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -628,7 +628,7 @@ function ManageTag(): JSX.Element { {t('tagName')} Date: Mon, 28 Oct 2024 13:57:13 +0530 Subject: [PATCH 25/52] more changes --- public/locales/en/translation.json | 5 +++-- public/locales/fr/translation.json | 3 ++- public/locales/hi/translation.json | 3 ++- public/locales/sp/translation.json | 3 ++- public/locales/zh/translation.json | 3 ++- src/components/TagActions/TagActions.tsx | 4 ++-- src/components/TagActions/TagNode.tsx | 6 +----- src/screens/ManageTag/ManageTag.tsx | 2 +- 8 files changed, 15 insertions(+), 14 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 3b202bd593..0aaf9bbc03 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -345,7 +345,7 @@ "remove": "Remove", "successfullyAssignedToTags": "Successfully Assigned to Tags", "successfullyRemovedFromTags": "Successfully Removed from Tags", - "errorOccurredWhileLoadingOrganizationUserTags": "Error occured while loading organization tags", + "errorOccurredWhileLoadingOrganizationUserTags": "Error occurred while loading organization tags", "removeUserTag": "Delete Tag", "removeUserTagMessage": "Do you want to delete this tag? It delete all the sub tags and all the associations.", "tagDetails": "Tag Details", @@ -357,7 +357,8 @@ "selectTag": "Select Tag", "collapse": "Collapse", "expand": "Expand", - "tagNamePlaceholder": "Write the name of the tag" + "tagNamePlaceholder": "Write the name of the tag", + "allTags": "All Tags" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 7b11e35f56..bcaa5e8333 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -357,7 +357,8 @@ "selectTag": "Sélectionner le tag", "collapse": "Réduire", "expand": "Développer", - "tagNamePlaceholder": "Écrire le nom du tag" + "tagNamePlaceholder": "Écrire le nom du tag", + "allTags": "Tous les tags" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index bab222175c..2d47e00566 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -357,7 +357,8 @@ "selectTag": "टैग चुनें", "collapse": "संक्षिप्त करें", "expand": "विस्तारित करें", - "tagNamePlaceholder": "टैग का नाम लिखें" + "tagNamePlaceholder": "टैग का नाम लिखें", + "allTags": "सभी टैग" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index e301bde4d7..e47ee49105 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -357,7 +357,8 @@ "selectTag": "Seleccionar etiqueta", "collapse": "Colapsar", "expand": "Expandir", - "tagNamePlaceholder": "Escribe el nombre de la etiqueta" + "tagNamePlaceholder": "Escribe el nombre de la etiqueta", + "allTags": "Todas las etiquetas" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 07d1905ca8..c3a69273bb 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -357,7 +357,8 @@ "selectTag": "选择标签", "collapse": "收起", "expand": "展开", - "tagNamePlaceholder": "输入标签名称" + "tagNamePlaceholder": "输入标签名称", + "allTags": "所有标签" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index c1006c12a9..0c3246f16c 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -315,7 +315,7 @@ const TagActions: React.FC = ({ : t('removeFromTags')} - + {orgUserTagsLoading ? ( @@ -347,7 +347,7 @@ const TagActions: React.FC = ({
- All Tags + {t('allTags')}
= ({ ); const handleTagClick = (): void => { - if (!expanded) { - setExpanded(true); - } else { - setExpanded(false); // collapse on second click - } + setExpanded(!expanded); }; const handleCheckboxChange = ( diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 05b45b1205..884b402a7c 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -627,7 +627,7 @@ function ManageTag(): JSX.Element { {t('tagName')} Date: Mon, 28 Oct 2024 14:16:11 +0530 Subject: [PATCH 26/52] minor correction --- src/components/TagActions/TagNode.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index ecc6514f3c..9a97efc62c 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -159,7 +159,7 @@ const TagNode: React.FC = ({
)} - {expanded && subTagsList && ( + {expanded && subTagsList?.length && (
Date: Mon, 28 Oct 2024 14:39:58 +0530 Subject: [PATCH 27/52] add error component for tagNode subtags query --- public/locales/en/translation.json | 1 + public/locales/fr/translation.json | 1 + public/locales/hi/translation.json | 1 + public/locales/sp/translation.json | 1 + public/locales/zh/translation.json | 1 + src/components/TagActions/TagActions.test.tsx | 27 ++++++- src/components/TagActions/TagActionsMocks.ts | 71 ++++++++++++++++++- src/components/TagActions/TagNode.tsx | 15 ++++ 8 files changed, 114 insertions(+), 4 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 0aaf9bbc03..7d2108bc06 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "Successfully Assigned to Tags", "successfullyRemovedFromTags": "Successfully Removed from Tags", "errorOccurredWhileLoadingOrganizationUserTags": "Error occurred while loading organization tags", + "errorOccurredWhileLoadingSubTags": "Error occurred while loading subTags tags", "removeUserTag": "Delete Tag", "removeUserTagMessage": "Do you want to delete this tag? It delete all the sub tags and all the associations.", "tagDetails": "Tag Details", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index bcaa5e8333..f01c2f8ad7 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "Attribué aux tags avec succès", "successfullyRemovedFromTags": "Retiré des tags avec succès", "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des tags de l'organisation", + "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-tags", "removeUserTag": "Supprimer le tag", "removeUserTagMessage": "Voulez-vous supprimer ce tag ? Cela supprimera tous les sous-tags et toutes les associations.", "tagDetails": "Détails du tag", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 2d47e00566..4384648ca3 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "सफलतापूर्वक टैग्स को असाइन किया गया", "successfullyRemovedFromTags": "सफलतापूर्वक टैग्स से हटाया गया", "errorOccurredWhileLoadingOrganizationUserTags": "संगठन टैग्स को लोड करते समय त्रुटि हुई", + "errorOccurredWhileLoadingSubTags": "उप-टैग लोड करते समय त्रुटि हुई", "removeUserTag": "टैग हटाएं", "removeUserTagMessage": "क्या आप इस टैग को हटाना चाहते हैं? यह सभी उप-टैग्स और सभी संबंधों को हटा देगा।", "tagDetails": "टैग विवरण", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index e47ee49105..24ce0dbdec 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "Asignado a etiquetas con éxito", "successfullyRemovedFromTags": "Eliminado de etiquetas con éxito", "errorOccurredWhileLoadingOrganizationUserTags": "Error al cargar las etiquetas de la organización", + "errorOccurredWhileLoadingSubTags": "Ocurrió un error al cargar las subetiquetas", "removeUserTag": "Eliminar etiqueta", "removeUserTagMessage": "¿Desea eliminar esta etiqueta? Esto eliminará todas las subetiquetas y todas las asociaciones.", "tagDetails": "Detalles de la etiqueta", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index c3a69273bb..0c070bcf8b 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -346,6 +346,7 @@ "successfullyAssignedToTags": "成功分配到标签", "successfullyRemovedFromTags": "成功从标签中移除", "errorOccurredWhileLoadingOrganizationUserTags": "加载组织标签时出错", + "errorOccurredWhileLoadingSubTags": "加载子标签时发生错误", "removeUserTag": "删除标签", "removeUserTagMessage": "您要删除此标签吗?这将删除所有子标签和所有关联。", "tagDetails": "标签详情", diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 014748a913..39e287395b 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -22,10 +22,15 @@ import type { ApolloLink } from '@apollo/client'; import type { InterfaceTagActionsProps } from './TagActions'; import TagActions from './TagActions'; import i18n from 'utils/i18nForTest'; -import { MOCKS, MOCKS_ERROR } from './TagActionsMocks'; +import { + MOCKS, + MOCKS_ERROR_ORGANIZATION_TAGS_QUERY, + MOCKS_ERROR_SUBTAGS_QUERY, +} from './TagActionsMocks'; const link = new StaticMockLink(MOCKS, true); -const link2 = new StaticMockLink(MOCKS_ERROR, true); +const link2 = new StaticMockLink(MOCKS_ERROR_ORGANIZATION_TAGS_QUERY, true); +const link3 = new StaticMockLink(MOCKS_ERROR_SUBTAGS_QUERY, true); async function wait(ms = 500): Promise { await act(() => { @@ -132,6 +137,24 @@ describe('Organisation Tags Page', () => { }); }); + test('Renders error component when when subTags query is unsuccessful', async () => { + const { getByText } = renderTagActionsModal(props[0], link3); + + await wait(); + + // expand tag 1 to list its subtags + await waitFor(() => { + expect(screen.getByTestId('expandSubTags1')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('expandSubTags1')); + + await waitFor(() => { + expect( + getByText(translations.errorOccurredWhileLoadingSubTags), + ).toBeInTheDocument(); + }); + }); + test('Renders more members with infinite scroll', async () => { const { getByText } = renderTagActionsModal(props[0], link); diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index 30fff78e3e..4e3a08f184 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -452,7 +452,7 @@ export const MOCKS = [ }, ]; -export const MOCKS_ERROR = [ +export const MOCKS_ERROR_ORGANIZATION_TAGS_QUERY = [ { request: { query: ORGANIZATION_USER_TAGS_LIST, @@ -461,6 +461,73 @@ export const MOCKS_ERROR = [ first: TAGS_QUERY_LIMIT, }, }, - error: new Error('Mock Graphql Error'), + error: new Error('Mock Graphql Error for organization root tags query'), + }, +]; + +export const MOCKS_ERROR_SUBTAGS_QUERY = [ + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_LIMIT, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: '1', + name: 'userTag 1', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 11, + }, + }, + cursor: '1', + }, + { + node: { + _id: '2', + name: 'userTag 2', + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + ], + }, + }, + }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: '1', + first: TAGS_QUERY_LIMIT, + }, + }, + error: new Error('Mock Graphql Error for subTags query'), }, ]; diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index 9a97efc62c..db08c3b451 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -9,6 +9,7 @@ import type { import { TAGS_QUERY_LIMIT } from 'utils/organizationTagsUtils'; import styles from './TagActions.module.css'; import InfiniteScroll from 'react-infinite-scroll-component'; +import { WarningAmberRounded } from '@mui/icons-material'; /** * Props for the `TagNode` component. @@ -34,6 +35,7 @@ const TagNode: React.FC = ({ const { data: subTagsData, loading: subTagsLoading, + error: subTagsError, fetchMore: fetchMoreSubTags, }: { data?: { @@ -62,6 +64,19 @@ const TagNode: React.FC = ({ skip: !expanded, }); + if (subTagsError) { + return ( +
+
+ +
+ {t('errorOccurredWhileLoadingSubTags')} +
+
+
+ ); + } + const subTagsList = subTagsData?.getUserTag.childTags.edges.map( (edge) => edge.node, ); From 052aead3c914341465f0f394cd297a3ac4bd3f0b Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 14:52:20 +0530 Subject: [PATCH 28/52] fix translation --- public/locales/fr/translation.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index f01c2f8ad7..74689bc209 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -347,19 +347,19 @@ "successfullyRemovedFromTags": "Retiré des tags avec succès", "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des tags de l'organisation", "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-tags", - "removeUserTag": "Supprimer le tag", - "removeUserTagMessage": "Voulez-vous supprimer ce tag ? Cela supprimera tous les sous-tags et toutes les associations.", - "tagDetails": "Détails du tag", - "tagName": "Nom", + "removeUserTag": "Supprimer l'étiquette", + "removeUserTagMessage": "Voulez-vous supprimer cette étiquette ? Cela supprimera toutes les sous-étiquettes et toutes les associations.", + "tagDetails": "Détails de l'étiquette", + "tagName": "Nom de l'étiquette", "tagUpdationSuccess": "Étiquette mise à jour avec succès", "tagRemovalSuccess": "Étiquette supprimée avec succès", - "noTagSelected": "Aucun tag sélectionné", + "noTagSelected": "Aucune étiquette sélectionnée", "changeNameToEdit": "Modifiez le nom pour faire une mise à jour", - "selectTag": "Sélectionner le tag", + "selectTag": "Sélectionner l'étiquette", "collapse": "Réduire", "expand": "Développer", - "tagNamePlaceholder": "Écrire le nom du tag", - "allTags": "Tous les tags" + "tagNamePlaceholder": "Écrire le nom de l'étiquette", + "allTags": "Toutes les étiquettes" }, "userListCard": { "addAdmin": "Ajouter un administrateur", From d8d657a472e9d9c34c99485f9ead847cd54fb532 Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 15:14:57 +0530 Subject: [PATCH 29/52] fix translation --- public/locales/fr/translation.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 74689bc209..7f74826272 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -339,14 +339,14 @@ "userName": "Nom d'utilisateur", "actions": "Actions", "noOneSelected": "Personne sélectionnée", - "assignToTags": "Attribuer aux tags", - "removeFromTags": "Retirer des tags", + "assignToTags": "Attribuer aux étiquettes", + "removeFromTags": "Retirer des étiquettes", "assign": "Attribuer", "remove": "Retirer", - "successfullyAssignedToTags": "Attribué aux tags avec succès", - "successfullyRemovedFromTags": "Retiré des tags avec succès", - "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des tags de l'organisation", - "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-tags", + "successfullyAssignedToTags": "Attribué aux étiquettes avec succès", + "successfullyRemovedFromTags": "Retiré des étiquettes avec succès", + "errorOccurredWhileLoadingOrganizationUserTags": "Erreur lors du chargement des étiquettes de l'organisation", + "errorOccurredWhileLoadingSubTags": "Une erreur s'est produite lors du chargement des sous-étiquettes", "removeUserTag": "Supprimer l'étiquette", "removeUserTagMessage": "Voulez-vous supprimer cette étiquette ? Cela supprimera toutes les sous-étiquettes et toutes les associations.", "tagDetails": "Détails de l'étiquette", From 4ae2507ffb547f5ba41eebb0b4b3c7f099010463 Mon Sep 17 00:00:00 2001 From: meetul Date: Mon, 28 Oct 2024 18:31:10 +0530 Subject: [PATCH 30/52] remove unused css classes --- .../AddPeopleToTag/AddPeopleToTag.module.css | 12 +- .../InfiniteScrollLoader.module.css | 5 +- .../TagActions/TagActions.module.css | 120 +----------------- src/components/TagActions/TagActions.tsx | 3 +- src/components/TagActions/TagNode.tsx | 1 - 5 files changed, 9 insertions(+), 132 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css index 945f887655..c7c5c4582b 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.module.css +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -28,7 +28,7 @@ } .scrollContainer { - max-height: 100px; /* Adjust as needed */ + max-height: 100px; overflow-y: auto; margin-bottom: 1rem; } @@ -38,16 +38,10 @@ align-items: center; padding: 5px 10px; border-radius: 12px; - background-color: #f8f9fa; /* Light background */ - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - max-width: calc(100% - 30px); /* Ensure it fits within the container */ + box-shadow: 0 1px 3px var(--bs-gray-400); + max-width: calc(100% - 2rem); } .removeFilterIcon { cursor: pointer; } - -.scrollContainer { - max-height: 350px; /* Set your desired max height */ - overflow-y: auto; /* Enable vertical scrolling */ -} diff --git a/src/components/InfiniteScrollLoader/InfiniteScrollLoader.module.css b/src/components/InfiniteScrollLoader/InfiniteScrollLoader.module.css index 33fe847a20..a5b609ae75 100644 --- a/src/components/InfiniteScrollLoader/InfiniteScrollLoader.module.css +++ b/src/components/InfiniteScrollLoader/InfiniteScrollLoader.module.css @@ -1,4 +1,3 @@ -/* InfiniteScrollLoader.module.css */ .simpleLoader { display: flex; justify-content: center; @@ -11,8 +10,8 @@ width: 2rem; height: 2rem; margin: 1rem 0; - border: 4px solid transparent; /* Set all sides transparent */ - border-top-color: var(--bs-gray-400); /* Color only the top border */ + border: 4px solid transparent; + border-top-color: var(--bs-gray-400); border-radius: 50%; animation: spin 0.6s linear infinite; } diff --git a/src/components/TagActions/TagActions.module.css b/src/components/TagActions/TagActions.module.css index d291929137..62c5855981 100644 --- a/src/components/TagActions/TagActions.module.css +++ b/src/components/TagActions/TagActions.module.css @@ -1,83 +1,3 @@ -.btnsContainer { - display: flex; - margin: 2rem 0; -} - -.btnsContainer .btnsBlock { - display: flex; - width: max-content; -} - -.btnsContainer .btnsBlock button { - margin-left: 1rem; - display: flex; - justify-content: center; - align-items: center; -} - -.btnsContainer .input { - flex: 1; - position: relative; - max-width: 60%; - justify-content: space-between; -} - -.btnsContainer input { - outline: 1px solid var(--bs-gray-400); -} - -.btnsContainer .input button { - width: 52px; -} - -@media (max-width: 1020px) { - .btnsContainer { - flex-direction: column; - margin: 1.5rem 0; - } - - .btnsContainer .btnsBlock { - margin: 1.5rem 0 0 0; - justify-content: space-between; - } - - .btnsContainer .btnsBlock button { - margin: 0; - } - - .btnsContainer .btnsBlock div button { - margin-right: 1.5rem; - } -} - -/* For mobile devices */ - -@media (max-width: 520px) { - .btnsContainer { - margin-bottom: 0; - } - - .btnsContainer .btnsBlock { - display: block; - margin-top: 1rem; - margin-right: 0; - } - - .btnsContainer .btnsBlock div { - flex: 1; - } - - .btnsContainer .btnsBlock div[title='Sort organizations'] { - margin-right: 0.5rem; - } - - .btnsContainer .btnsBlock button { - margin-bottom: 1rem; - margin-right: 0; - width: 100%; - } -} - .errorContainer { min-height: 100vh; } @@ -96,30 +16,8 @@ margin-bottom: 1rem; } -.tableHeader { - background-color: var(--bs-primary); - color: var(--bs-white); - font-size: 1rem; -} - -.rowBackground { - background-color: var(--bs-white); - max-height: 120px; -} - -.tagsBreadCrumbs { - color: var(--bs-gray); - cursor: pointer; -} - -.tagsBreadCrumbs:hover { - color: var(--bs-blue); - font-weight: 600; - text-decoration: underline; -} - .scrollContainer { - max-height: 100px; /* Adjust as needed */ + max-height: 100px; overflow-y: auto; margin-bottom: 1rem; } @@ -129,22 +27,10 @@ align-items: center; padding: 5px 10px; border-radius: 12px; - background-color: #f8f9fa; /* Light background */ - box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); - max-width: calc(100% - 30px); /* Ensure it fits within the container */ + box-shadow: 0 1px 3px var(--bs-gray-400); + max-width: calc(100% - 2rem); } .removeFilterIcon { cursor: pointer; } - -.scrContainer { - max-height: 300px; - overflow: scroll; - /* padding-right: 8px; */ -} - -.allTagsHeading { - color: rgb(77, 76, 76); - font-weight: 600; -} diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index ece511e6ec..008b25c5e2 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -328,7 +328,7 @@ const TagActions: React.FC = ({ )}
-
+
{t('allTags')}
@@ -339,7 +339,6 @@ const TagActions: React.FC = ({ maxHeight: 300, overflow: 'auto', }} - className={`${styles.scrContainer}`} > = ({ maxHeight: 300, overflow: 'auto', }} - className={`${styles.scrContainer}`} > Date: Wed, 30 Oct 2024 14:27:19 +0530 Subject: [PATCH 31/52] refactor ManageTag file to reduce its size --- src/screens/ManageTag/EditUserTagModal.tsx | 80 ++++++++ src/screens/ManageTag/ManageTag.test.tsx | 8 +- src/screens/ManageTag/ManageTag.tsx | 185 ++++-------------- .../MockAddPeopleToTag.tsx | 4 + .../MockTagActions.tsx | 4 + src/screens/ManageTag/RemoveUserTagModal.tsx | 64 ++++++ .../ManageTag/UnasisgnUserTagModal.tsx | 64 ++++++ 7 files changed, 260 insertions(+), 149 deletions(-) create mode 100644 src/screens/ManageTag/EditUserTagModal.tsx create mode 100644 src/screens/ManageTag/RemoveUserTagModal.tsx create mode 100644 src/screens/ManageTag/UnasisgnUserTagModal.tsx diff --git a/src/screens/ManageTag/EditUserTagModal.tsx b/src/screens/ManageTag/EditUserTagModal.tsx new file mode 100644 index 0000000000..7381e8723f --- /dev/null +++ b/src/screens/ManageTag/EditUserTagModal.tsx @@ -0,0 +1,80 @@ +import type { FormEvent } from 'react'; +import React from 'react'; +import { Button, Form, Modal } from 'react-bootstrap'; + +/** + * Edit UserTag Modal component for the Manage Tag screen. + */ + +export interface InterfaceEditUserTagModalProps { + editTagModalIsOpen: boolean; + hideEditTagModal: () => void; + newTagName: string; + setNewTagName: (state: React.SetStateAction) => void; + handleEditUserTag: (e: FormEvent) => Promise; + t: (key: string) => string; + tCommon: (key: string) => string; +} + +const EditUserTagModal: React.FC = ({ + editTagModalIsOpen, + hideEditTagModal, + newTagName, + handleEditUserTag, + setNewTagName, + t, + tCommon, +}) => { + return ( + <> + + + {t('tagDetails')} + + + + {t('tagName')} + { + setNewTagName(e.target.value); + }} + /> + + + + + + + + + + ); +}; + +export default EditUserTagModal; diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index 7be7f97318..c4c1375e0e 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -239,9 +239,9 @@ describe('Manage Tag Page', () => { await wait(); await waitFor(() => { - expect(screen.getByTestId('editTag')).toBeInTheDocument(); + expect(screen.getByTestId('editUserTag')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('editTag')); + userEvent.click(screen.getByTestId('editUserTag')); await waitFor(() => { return expect( @@ -395,9 +395,9 @@ describe('Manage Tag Page', () => { await wait(); await waitFor(() => { - expect(screen.getByTestId('editTag')).toBeInTheDocument(); + expect(screen.getByTestId('editUserTag')).toBeInTheDocument(); }); - userEvent.click(screen.getByTestId('editTag')); + userEvent.click(screen.getByTestId('editUserTag')); userEvent.click(screen.getByTestId('editTagSubmitBtn')); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index f94bbfc72d..89a053002c 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -9,7 +9,6 @@ import { useNavigate, useParams, Link } from 'react-router-dom'; import { Col, Form } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; import Dropdown from 'react-bootstrap/Dropdown'; -import Modal from 'react-bootstrap/Modal'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; @@ -39,6 +38,9 @@ import AddPeopleToTag from 'components/AddPeopleToTag/AddPeopleToTag'; import TagActions from 'components/TagActions/TagActions'; import InfiniteScroll from 'react-infinite-scroll-component'; import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScrollLoader'; +import EditUserTagModal from './EditUserTagModal'; +import RemoveUserTagModal from './RemoveUserTagModal'; +import UnassignUserTagModal from './UnasisgnUserTagModal'; /** * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/managetag/:tagId'. @@ -53,18 +55,16 @@ function ManageTag(): JSX.Element { }); const { t: tCommon } = useTranslation('common'); - const [unassignTagModalIsOpen, setUnassignTagModalIsOpen] = useState(false); - + const [unassignUserTagModalIsOpen, setUnassignTagModalIsOpen] = + useState(false); const [addPeopleToTagModalIsOpen, setAddPeopleToTagModalIsOpen] = useState(false); const [tagActionsModalIsOpen, setTagActionsModalIsOpen] = useState(false); - const [editTagModalIsOpen, setEditTagModalIsOpen] = useState(false); const [removeTagModalIsOpen, setRemoveTagModalIsOpen] = useState(false); const { orgId, tagId: currentTagId } = useParams(); const navigate = useNavigate(); - const [unassignUserId, setUnassignUserId] = useState(null); // a state to specify whether we're assigning to tags or removing from tags @@ -171,7 +171,7 @@ function ManageTag(): JSX.Element { const [unassignUserTag] = useMutation(UNASSIGN_USER_TAG); - const handleUnassignTag = async (): Promise => { + const handleUnassignUserTag = async (): Promise => { try { await unassignUserTag({ variables: { @@ -181,7 +181,7 @@ function ManageTag(): JSX.Element { }); userTagAssignedMembersRefetch(); - toggleUnassignTagModal(); + toggleUnassignUserTagModal(); toast.success(t('successfullyUnassigned') as string); } catch (error: unknown) { /* istanbul ignore next */ @@ -201,7 +201,9 @@ function ManageTag(): JSX.Element { setNewTagName(userTagAssignedMembersData?.getAssignedUsers.name ?? ''); }, [userTagAssignedMembersData]); - const editTag = async (e: FormEvent): Promise => { + const handleEditUserTag = async ( + e: FormEvent, + ): Promise => { e.preventDefault(); if (newTagName === currentTagName) { @@ -288,11 +290,11 @@ function ManageTag(): JSX.Element { navigate(`/orgtags/${orgId}/manageTag/${tagId}`); }; - const toggleUnassignTagModal = (): void => { - if (unassignTagModalIsOpen) { + const toggleUnassignUserTagModal = (): void => { + if (unassignUserTagModalIsOpen) { setUnassignUserId(null); } - setUnassignTagModalIsOpen(!unassignTagModalIsOpen); + setUnassignTagModalIsOpen(!unassignUserTagModalIsOpen); }; const columns: GridColDef[] = [ @@ -350,7 +352,7 @@ function ManageTag(): JSX.Element { variant="danger" onClick={() => { setUnassignUserId(params.row._id); - toggleUnassignTagModal(); + toggleUnassignUserTagModal(); }} data-testid="unassignTagBtn" > @@ -384,7 +386,6 @@ function ManageTag(): JSX.Element {
-
- -
-
navigate(`/orgtags/${orgId}`)} className={`fs-6 ms-3 my-1 ${styles.tagsBreadCrumbs}`} @@ -444,7 +442,6 @@ function ManageTag(): JSX.Element { {'Tags'}
- {orgUserTagAncestors?.map((tag, index) => (
{tag.name} - {orgUserTagAncestors.length - 1 !== index && ( /* istanbul ignore next */ @@ -508,7 +504,6 @@ function ManageTag(): JSX.Element {
-
{'Actions'}
@@ -534,13 +529,11 @@ function ManageTag(): JSX.Element { > {t('removeFromTags')}
-
-
{tCommon('edit')}
@@ -565,7 +558,6 @@ function ManageTag(): JSX.Element { t={t} tCommon={tCommon} /> - {/* Assign People To Tags Modal */} - - {/* Unassign Tag Modal */} - - - - {t('unassignUserTag')} - - - {t('unassignUserTagMessage')} - - - - - - - {/* Edit Tag Modal */} - - - {t('tagDetails')} - -
- - {t('tagName')} - { - setNewTagName(e.target.value); - }} - /> - - - - - - -
-
- + {/* Unassign User Tag Modal */} + + {/* Edit User Tag Modal */} + {/* Remove User Tag Modal */} - - - - {t('removeUserTag')} - - - {t('removeUserTagMessage')} - - - - - + ); } - export default ManageTag; diff --git a/src/screens/ManageTag/ManageTagMockComponents/MockAddPeopleToTag.tsx b/src/screens/ManageTag/ManageTagMockComponents/MockAddPeopleToTag.tsx index e54eda1aec..70b194c289 100644 --- a/src/screens/ManageTag/ManageTagMockComponents/MockAddPeopleToTag.tsx +++ b/src/screens/ManageTag/ManageTagMockComponents/MockAddPeopleToTag.tsx @@ -1,6 +1,10 @@ import React from 'react'; import type { InterfaceAddPeopleToTagProps } from '../../../components/AddPeopleToTag/AddPeopleToTag'; +/** + * Component that mocks the AddPeopleToTag component for the Manage Tag screen. + */ + const MockAddPeopleToTag: React.FC = ({ addPeopleToTagModalIsOpen, hideAddPeopleToTagModal, diff --git a/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx b/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx index e122a4f985..1c7afbc712 100644 --- a/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx +++ b/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx @@ -1,6 +1,10 @@ import React from 'react'; import type { InterfaceTagActionsProps } from '../../../components/TagActions/TagActions'; +/** + * Component that mocks the TagActions component for the Manage Tag screen. + */ + const MockTagActions: React.FC = ({ tagActionsModalIsOpen, hideTagActionsModal, diff --git a/src/screens/ManageTag/RemoveUserTagModal.tsx b/src/screens/ManageTag/RemoveUserTagModal.tsx new file mode 100644 index 0000000000..dd80581911 --- /dev/null +++ b/src/screens/ManageTag/RemoveUserTagModal.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Button, Modal } from 'react-bootstrap'; + +/** + * Remove UserTag Modal component for the Manage Tag screen. + */ + +export interface InterfaceRemoveUserTagModalProps { + removeTagModalIsOpen: boolean; + toggleRemoveUserTagModal: () => void; + handleRemoveUserTag: () => Promise; + t: (key: string) => string; + tCommon: (key: string) => string; +} + +const RemoveUserTagModal: React.FC = ({ + removeTagModalIsOpen, + toggleRemoveUserTagModal, + handleRemoveUserTag, + t, + tCommon, +}) => { + return ( + <> + + + + {t('removeUserTag')} + + + {t('removeUserTagMessage')} + + + + + + + ); +}; + +export default RemoveUserTagModal; diff --git a/src/screens/ManageTag/UnasisgnUserTagModal.tsx b/src/screens/ManageTag/UnasisgnUserTagModal.tsx new file mode 100644 index 0000000000..47b6bd07a2 --- /dev/null +++ b/src/screens/ManageTag/UnasisgnUserTagModal.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Button, Modal } from 'react-bootstrap'; + +/** + * Unassign UserTag Modal component for the Manage Tag screen. + */ + +export interface InterfaceUnassignUserTagModalProps { + unassignUserTagModalIsOpen: boolean; + toggleUnassignUserTagModal: () => void; + handleUnassignUserTag: () => Promise; + t: (key: string) => string; + tCommon: (key: string) => string; +} + +const UnassignUserTagModal: React.FC = ({ + unassignUserTagModalIsOpen, + toggleUnassignUserTagModal, + handleUnassignUserTag, + t, + tCommon, +}) => { + return ( + <> + + + + {t('unassignUserTag')} + + + {t('unassignUserTagMessage')} + + + + + + + ); +}; + +export default UnassignUserTagModal; From f4c7814d2dbd5276a434b7a2f545441b78b7e028 Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 30 Oct 2024 14:29:23 +0530 Subject: [PATCH 32/52] update pull-request check --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index c423143e6f..75588cc4df 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -38,7 +38,7 @@ jobs: - name: Count number of lines run: | chmod +x ./.github/workflows/countline.py - ./.github/workflows/countline.py --lines 600 --exclude_files src/screens/LoginPage/LoginPage.tsx src/GraphQl/Queries/Queries.ts src/screens/OrgList/OrgList.tsx src/GraphQl/Mutations/mutations.ts src/components/EventListCard/EventListCardModals.tsx src/screens/ManageTag/ManageTag.tsx + ./.github/workflows/countline.py --lines 600 --exclude_files src/screens/LoginPage/LoginPage.tsx src/GraphQl/Queries/Queries.ts src/screens/OrgList/OrgList.tsx src/GraphQl/Mutations/mutations.ts src/components/EventListCard/EventListCardModals.tsx - name: Get changed TypeScript files id: changed-files From 95a113880d02dcf0dcfa48127315da9c2dd810c8 Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 30 Oct 2024 15:16:18 +0530 Subject: [PATCH 33/52] minor adjustments --- src/screens/ManageTag/ManageTag.tsx | 40 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 89a053002c..42423d42a9 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -416,16 +416,16 @@ function ManageTag(): JSX.Element { > {t('subTags')} -
+
@@ -463,7 +463,7 @@ function ManageTag(): JSX.Element { className={styles.manageTagScrollableDiv} >
{'Actions'}
-
+
{ setTagActionType('assignToTags'); showAssignToTagsModal(); }} - className="ms-5 mt-2 mb-2 btn btn-primary btn-sm w-75" + className="my-2 btn btn-primary btn-sm w-75" data-testid="assignToTags" > {t('assignToTags')} @@ -524,22 +524,30 @@ function ManageTag(): JSX.Element { setTagActionType('removeFromTags'); showAssignToTagsModal(); }} - className="ms-5 mb-3 btn btn-danger btn-sm w-75" + className="mb-1 btn btn-danger btn-sm w-75" data-testid="removeFromTags" > {t('removeFromTags')}
-
+ +
+
{tCommon('edit')}
{tCommon('remove')} From 4efe79e4cb2281dc9e67ebfb8a0b7a7312ea89fa Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 30 Oct 2024 15:30:28 +0530 Subject: [PATCH 34/52] minor correction --- src/screens/ManageTag/EditUserTagModal.tsx | 14 ++++++------- src/screens/ManageTag/ManageTag.tsx | 21 ++++++++++---------- src/screens/ManageTag/RemoveUserTagModal.tsx | 6 +++--- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/src/screens/ManageTag/EditUserTagModal.tsx b/src/screens/ManageTag/EditUserTagModal.tsx index 7381e8723f..8e29c99111 100644 --- a/src/screens/ManageTag/EditUserTagModal.tsx +++ b/src/screens/ManageTag/EditUserTagModal.tsx @@ -7,8 +7,8 @@ import { Button, Form, Modal } from 'react-bootstrap'; */ export interface InterfaceEditUserTagModalProps { - editTagModalIsOpen: boolean; - hideEditTagModal: () => void; + editUserTagModalIsOpen: boolean; + hideEditUserTagModal: () => void; newTagName: string; setNewTagName: (state: React.SetStateAction) => void; handleEditUserTag: (e: FormEvent) => Promise; @@ -17,8 +17,8 @@ export interface InterfaceEditUserTagModalProps { } const EditUserTagModal: React.FC = ({ - editTagModalIsOpen, - hideEditTagModal, + editUserTagModalIsOpen, + hideEditUserTagModal, newTagName, handleEditUserTag, setNewTagName, @@ -28,8 +28,8 @@ const EditUserTagModal: React.FC = ({ return ( <> = ({ diff --git a/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx b/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx index 1c7afbc712..3d2d6cc880 100644 --- a/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx +++ b/src/screens/ManageTag/ManageTagMockComponents/MockTagActions.tsx @@ -12,9 +12,18 @@ const MockTagActions: React.FC = ({ return ( <> {tagActionsModalIsOpen && ( -
+
+

+ Tag Actions +

diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index 170d072d79..42d0d25fd7 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -74,7 +74,9 @@ function OrganizationTags(): JSX.Element { orgUserTagsFetchMore({ variables: { first: TAGS_QUERY_DATA_CHUNK_SIZE, - after: orgUserTagsData?.organizations[0].userTags.pageInfo.endCursor, + after: + orgUserTagsData?.organizations?.[0]?.userTags?.pageInfo?.endCursor ?? + null, }, updateQuery: ( prevResult: { organizations: InterfaceQueryOrganizationUserTags[] }, @@ -339,8 +341,8 @@ function OrganizationTags(): JSX.Element { dataLength={userTagsList?.length ?? 0} next={loadMoreUserTags} hasMore={ - orgUserTagsData?.organizations[0].userTags.pageInfo - .hasNextPage ?? /* istanbul ignore next */ false + orgUserTagsData?.organizations?.[0]?.userTags?.pageInfo + ?.hasNextPage ?? /* istanbul ignore next */ false } loader={} scrollableTarget="orgUserTagsScrollableDiv" @@ -349,7 +351,7 @@ function OrganizationTags(): JSX.Element { disableColumnMenu columnBufferPx={7} hideFooter={true} - getRowId={(row) => row._id} + getRowId={(row) => row.id} slots={{ noRowsOverlay: /* istanbul ignore next */ () => ( row._id} + getRowId={(row) => row.id} slots={{ noRowsOverlay: /* istanbul ignore next */ () => ( void; +} + +// 2. Generic pagination options +interface InterfacePaginationVariables { + after?: string | null; + first?: number | null; +} + +// 3. Generic fetch more options +interface InterfaceBaseFetchMoreOptions { + variables: InterfacePaginationVariables; + updateQuery?: (prev: T, options: { fetchMoreResult: T }) => T; +} + +// 4. Query interfaces +export interface InterfaceOrganizationTagsQuery + extends InterfaceBaseQueryResult { data?: { organizations: InterfaceQueryOrganizationUserTags[]; }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - fetchMore: (options: { - variables: { - first: number; - after?: string; - }; - updateQuery: ( - previousResult: { organizations: InterfaceQueryOrganizationUserTags[] }, - options: { - fetchMoreResult?: { - organizations: InterfaceQueryOrganizationUserTags[]; - }; - }, - ) => { organizations: InterfaceQueryOrganizationUserTags[] }; - }) => void; + fetchMore: ( + options: InterfaceBaseFetchMoreOptions<{ + organizations: InterfaceQueryOrganizationUserTags[]; + }>, + ) => void; } -export interface InterfaceOrganizationSubTagsQuery { +export interface InterfaceOrganizationSubTagsQuery + extends InterfaceBaseQueryResult { data?: { getChildTags: InterfaceQueryUserTagChildTags; }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - fetchMore: (options: { - variables: { - first: number; - after?: string; - }; - updateQuery: ( - previousResult: { getChildTags: InterfaceQueryUserTagChildTags }, - options: { - fetchMoreResult?: { getChildTags: InterfaceQueryUserTagChildTags }; - }, - ) => { getChildTags: InterfaceQueryUserTagChildTags }; - }) => void; + fetchMore: ( + options: InterfaceBaseFetchMoreOptions<{ + getChildTags: InterfaceQueryUserTagChildTags; + }>, + ) => void; } -export interface InterfaceTagAssignedMembersQuery { +export interface InterfaceTagAssignedMembersQuery + extends InterfaceBaseQueryResult { data?: { getAssignedUsers: InterfaceQueryUserTagsAssignedMembers; }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - fetchMore: (options: { - variables: { - after?: string | null; - first?: number | null; - }; - updateQuery?: ( - previousQueryResult: { - getAssignedUsers: InterfaceQueryUserTagsAssignedMembers; - }, - options: { - fetchMoreResult: { - getAssignedUsers: InterfaceQueryUserTagsAssignedMembers; - }; - }, - ) => { getAssignedUsers: InterfaceQueryUserTagsAssignedMembers }; - }) => Promise; + fetchMore: ( + options: InterfaceBaseFetchMoreOptions<{ + getAssignedUsers: InterfaceQueryUserTagsAssignedMembers; + }>, + ) => void; } -export interface InterfaceTagUsersToAssignToQuery { +export interface InterfaceTagUsersToAssignToQuery + extends InterfaceBaseQueryResult { data?: { getUsersToAssignTo: InterfaceQueryUserTagsMembersToAssignTo; }; - loading: boolean; - error?: ApolloError; - fetchMore: (options: { - variables: { - after?: string | null; - first?: number | null; - }; - updateQuery?: ( - previousQueryResult: { - getUsersToAssignTo: InterfaceQueryUserTagsMembersToAssignTo; - }, - options: { - fetchMoreResult: { - getUsersToAssignTo: InterfaceQueryUserTagsMembersToAssignTo; - }; - }, - ) => { getUsersToAssignTo: InterfaceQueryUserTagsMembersToAssignTo }; - }) => Promise; + fetchMore: ( + options: InterfaceBaseFetchMoreOptions<{ + getUsersToAssignTo: InterfaceQueryUserTagsMembersToAssignTo; + }>, + ) => void; } From 9be6fa8f0b8dd68f44f0db83a6d87f59f6a2c66e Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 30 Oct 2024 21:02:31 +0530 Subject: [PATCH 40/52] more changes --- .../AddPeopleToTag/AddPeopleToTag.module.css | 3 +- .../AddPeopleToTag/AddPeopleToTag.test.tsx | 2 +- .../AddPeopleToTag/AddPeopleToTag.tsx | 2 +- src/components/TagActions/TagActions.test.tsx | 2 +- src/components/TagActions/TagActions.tsx | 7 +++-- src/components/TagActions/TagNode.tsx | 6 ++-- src/screens/ManageTag/EditUserTagModal.tsx | 9 +++++- src/screens/ManageTag/ManageTag.tsx | 2 +- src/screens/ManageTag/RemoveUserTagModal.tsx | 7 +++-- .../OrganizationTags.module.css | 1 + .../OrganizationTags/OrganizationTags.tsx | 4 +-- src/screens/SubTags/SubTags.tsx | 31 +++++++++---------- src/utils/organizationTagsUtils.ts | 8 +++++ 13 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css index c7c5c4582b..d025435c3a 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.module.css +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -30,7 +30,8 @@ .scrollContainer { max-height: 100px; overflow-y: auto; - margin-bottom: 1rem; + margin-top: 0.5rem; + margin-bottom: 1.7rem; } .memberBadge { diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx index d6749ad23b..e21ccd1a52 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -74,7 +74,7 @@ const renderAddPeopleToTagModal = ( } /> diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index 2ff78d9121..915452c210 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -113,7 +113,7 @@ const AddPeopleToTag: React.FC = ({ const userTagMembersToAssignTo = userTagsMembersToAssignToData?.getUsersToAssignTo.usersToAssignTo.edges.map( (edge) => edge.node, - ); + ) ?? /* istanbul ignore next */ []; const handleAddOrRemoveMember = (member: InterfaceMemberData): void => { setAssignToMembers((prevMembers) => { diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 03a955d9d0..140504d044 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -96,7 +96,7 @@ const renderTagActionsModal = ( } /> diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index 29f1105cf2..60099e855d 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -102,9 +102,10 @@ const TagActions: React.FC = ({ }); }; - const userTagsList = orgUserTagsData?.organizations[0]?.userTags.edges.map( - (edge) => edge.node, - ); + const userTagsList = + orgUserTagsData?.organizations[0]?.userTags.edges.map( + (edge) => edge.node, + ) ?? /* istanbul ignore next */ []; const [checkedTagId, setCheckedTagId] = useState(null); const [uncheckedTagId, setUncheckedTagId] = useState(null); diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index 38949dd01f..e05d572f30 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -92,9 +92,9 @@ const TagNode: React.FC = ({ ); } - const subTagsList = subTagsData?.getChildTags.childTags.edges.map( - (edge) => edge.node, - ); + const subTagsList = + subTagsData?.getChildTags.childTags.edges.map((edge) => edge.node) ?? + /* istanbul ignore next */ []; const handleTagClick = (): void => { setExpanded(!expanded); diff --git a/src/screens/ManageTag/EditUserTagModal.tsx b/src/screens/ManageTag/EditUserTagModal.tsx index e686375f49..5fa8ae2771 100644 --- a/src/screens/ManageTag/EditUserTagModal.tsx +++ b/src/screens/ManageTag/EditUserTagModal.tsx @@ -43,7 +43,14 @@ const EditUserTagModal: React.FC = ({ > {t('tagDetails')} -
+ ): void => { + e.preventDefault(); + if (newTagName.trim()) { + handleEditUserTag(e); + } + }} + > {t('tagName')} edge.node, - ); + ) ?? /* istanbul ignore next */ []; const orgUserTagAncestors = orgUserTagAncestorsData?.getUserTagAncestors; const redirectToSubTags = (tagId: string): void => { diff --git a/src/screens/ManageTag/RemoveUserTagModal.tsx b/src/screens/ManageTag/RemoveUserTagModal.tsx index eeb84e1abe..dc000f443c 100644 --- a/src/screens/ManageTag/RemoveUserTagModal.tsx +++ b/src/screens/ManageTag/RemoveUserTagModal.tsx @@ -26,6 +26,7 @@ const RemoveUserTagModal: React.FC = ({ = ({ centered > - + {t('removeUserTag')} - {t('removeUserTagMessage')} + + {t('removeUserTagMessage')} + - -
+
@@ -392,7 +391,7 @@ function SubTags(): JSX.Element { className={styles.subTagsScrollableDiv} > `${styles.rowBackground}`} autoHeight rowHeight={65} - rows={userTagsList?.map((fund, index) => ({ + rows={subTagsList?.map((subTag, index) => ({ id: index + 1, - ...fund, + ...subTag, }))} columns={columns} isRowSelectable={() => false} diff --git a/src/utils/organizationTagsUtils.ts b/src/utils/organizationTagsUtils.ts index c58c09efe4..75c001816e 100644 --- a/src/utils/organizationTagsUtils.ts +++ b/src/utils/organizationTagsUtils.ts @@ -28,6 +28,14 @@ export const dataGridStyle = { '& .MuiDataGrid-main': { borderRadius: '0.1rem', }, + '& .MuiDataGrid-topContainer': { + position: 'fixed', + top: 259, + zIndex: 1, + }, + '& .MuiDataGrid-virtualScrollerContent': { + marginTop: 6.5, + }, }; // the data chunk size for tag related queries From 63febd0815bbf92dcd7a472ff35c1ffbf7ac1936 Mon Sep 17 00:00:00 2001 From: meetul Date: Wed, 30 Oct 2024 21:57:43 +0530 Subject: [PATCH 41/52] fix dataGrid classes --- .../AddPeopleToTag/AddPeopleToTag.module.css | 3 +-- src/components/AddPeopleToTag/AddPeopleToTag.tsx | 10 +++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css index d025435c3a..c7c5c4582b 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.module.css +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -30,8 +30,7 @@ .scrollContainer { max-height: 100px; overflow-y: auto; - margin-top: 0.5rem; - margin-bottom: 1.7rem; + margin-bottom: 1rem; } .memberBadge { diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index 915452c210..f5c90be096 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -315,7 +315,15 @@ const AddPeopleToTag: React.FC = ({ ), }} - sx={dataGridStyle} + sx={{ + ...dataGridStyle, + '& .MuiDataGrid-topContainer': { + position: 'static', + }, + '& .MuiDataGrid-virtualScrollerContent': { + marginTop: '0', + }, + }} getRowClassName={() => `${styles.rowBackground}`} autoHeight rowHeight={65} From cec740b5cd5c6b1b7330858dee797f24175b055d Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 1 Nov 2024 10:22:40 +0530 Subject: [PATCH 42/52] remove getUserTagAncestors query --- src/GraphQl/Queries/OrganizationQueries.ts | 7 + src/GraphQl/Queries/userTagQueries.ts | 28 ++- .../AddPeopleToTag/AddPeopleToTag.test.tsx | 8 +- .../AddPeopleToTag/AddPeopleToTag.tsx | 7 +- src/components/TagActions/TagActions.tsx | 34 +--- src/components/TagActions/TagActionsMocks.ts | 96 +++++++++ src/screens/ManageTag/ManageTag.test.tsx | 17 +- src/screens/ManageTag/ManageTag.tsx | 52 ++--- src/screens/ManageTag/ManageTagMocks.ts | 76 +------- .../OrganizationTags/OrganizationTagsMocks.ts | 24 +++ src/screens/SubTags/SubTags.test.tsx | 36 +--- src/screens/SubTags/SubTags.tsx | 50 ++--- src/screens/SubTags/SubTagsMocks.ts | 182 +++++++++--------- src/utils/interfaces.ts | 18 +- 14 files changed, 289 insertions(+), 346 deletions(-) diff --git a/src/GraphQl/Queries/OrganizationQueries.ts b/src/GraphQl/Queries/OrganizationQueries.ts index 315c50ebf4..574dbe48d1 100644 --- a/src/GraphQl/Queries/OrganizationQueries.ts +++ b/src/GraphQl/Queries/OrganizationQueries.ts @@ -96,12 +96,19 @@ export const ORGANIZATION_USER_TAGS_LIST = gql` node { _id name + parentTag { + _id + } usersAssignedTo(first: $first, last: $last) { totalCount } childTags(first: $first, last: $last) { totalCount } + ancestorTags { + _id + name + } } cursor } diff --git a/src/GraphQl/Queries/userTagQueries.ts b/src/GraphQl/Queries/userTagQueries.ts index 33a9b63174..827cca28fe 100644 --- a/src/GraphQl/Queries/userTagQueries.ts +++ b/src/GraphQl/Queries/userTagQueries.ts @@ -38,6 +38,10 @@ export const USER_TAGS_ASSIGNED_MEMBERS = gql` } totalCount } + ancestorTags { + _id + name + } } } `; @@ -70,6 +74,10 @@ export const USER_TAG_SUB_TAGS = gql` childTags(first: $first, last: $last) { totalCount } + ancestorTags { + _id + name + } } } pageInfo { @@ -80,6 +88,10 @@ export const USER_TAG_SUB_TAGS = gql` } totalCount } + ancestorTags { + _id + name + } } } `; @@ -125,19 +137,3 @@ export const USER_TAGS_MEMBERS_TO_ASSIGN_TO = gql` } } `; - -/** - * GraphQL query to retrieve the ancestor tags of a certain tag. - * - * @param id - The ID of the current tag. - * @returns The list of ancestor tags. - */ - -export const USER_TAG_ANCESTORS = gql` - query GetUserTagAncestors($id: ID!) { - getUserTagAncestors(id: $id) { - _id - name - } - } -`; diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx index e21ccd1a52..ef9977eb2f 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -157,13 +157,15 @@ describe('Organisation Tags Page', () => { }); // Find the infinite scroll div by test ID or another selector - const scrollableDiv = screen.getByTestId('scrollableDiv'); + const addPeopleToTagScrollableDiv = screen.getByTestId( + 'addPeopleToTagScrollableDiv', + ); const initialMemberDataLength = screen.getAllByTestId('memberName').length; // Set scroll position to the bottom - fireEvent.scroll(scrollableDiv, { - target: { scrollY: scrollableDiv.scrollHeight }, + fireEvent.scroll(addPeopleToTagScrollableDiv, { + target: { scrollY: addPeopleToTagScrollableDiv.scrollHeight }, }); await waitFor(() => { diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index f5c90be096..ed43061410 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -68,7 +68,6 @@ const AddPeopleToTag: React.FC = ({ first: TAGS_QUERY_DATA_CHUNK_SIZE, }, skip: !addPeopleToTagModalIsOpen, - fetchPolicy: 'no-cache', }, ); @@ -281,8 +280,8 @@ const AddPeopleToTag: React.FC = ({
= ({ /* istanbul ignore next */ false } loader={} - scrollableTarget="scrollableDiv" + scrollableTarget="addPeopleToTagScrollableDiv" > = ({ (edge) => edge.node, ) ?? /* istanbul ignore next */ []; - const [checkedTagId, setCheckedTagId] = useState(null); - const [uncheckedTagId, setUncheckedTagId] = useState(null); - // tags that we have selected to assigned const [selectedTags, setSelectedTags] = useState([]); @@ -167,22 +163,13 @@ const TagActions: React.FC = ({ setAncestorTagsDataMap(newAncestorTagsDataMap); }, [removeAncestorTagsData]); - const addAncestorTags = (tagId: string): void => { - setCheckedTagId(tagId); - setUncheckedTagId(null); - }; - - const removeAncestorTags = (tagId: string): void => { - setUncheckedTagId(tagId); - setCheckedTagId(null); - }; - const selectTag = (tag: InterfaceTagData): void => { const newCheckedTags = new Set(checkedTags); setSelectedTags((selectedTags) => [...selectedTags, tag]); newCheckedTags.add(tag._id); - addAncestorTags(tag._id); + + setAddAncestorTagsData(new Set(tag.ancestorTags)); setCheckedTags(newCheckedTags); }; @@ -199,7 +186,8 @@ const TagActions: React.FC = ({ selectedTags.filter((selectedTag) => selectedTag._id !== tag._id), ); newCheckedTags.delete(tag._id); - removeAncestorTags(tag._id); + + setRemoveAncestorTagsData(new Set(tag.ancestorTags)); setCheckedTags(newCheckedTags); }; @@ -215,20 +203,6 @@ const TagActions: React.FC = ({ } }; - useQuery(USER_TAG_ANCESTORS, { - variables: { id: checkedTagId }, - onCompleted: /* istanbul ignore next */ (data) => { - setAddAncestorTagsData(data.getUserTagAncestors.slice(0, -1)); // Update the ancestor tags data, to check the ancestor tags - }, - }); - - useQuery(USER_TAG_ANCESTORS, { - variables: { id: uncheckedTagId }, - onCompleted: /* istanbul ignore next */ (data) => { - setRemoveAncestorTagsData(data.getUserTagAncestors.slice(0, -1)); // Update the ancestor tags data, to uncheck the ancestor tags - }, - }); - const [assignToTags] = useMutation(ASSIGN_TO_TAGS); const [removeFromTags] = useMutation(REMOVE_FROM_TAGS); diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index a6ed89ab8b..20ae3e5349 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -25,12 +25,14 @@ export const MOCKS = [ node: { _id: '1', name: 'userTag 1', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 11, }, + ancestorTags: [], }, cursor: '1', }, @@ -38,12 +40,14 @@ export const MOCKS = [ node: { _id: '2', name: 'userTag 2', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 0, }, + ancestorTags: [], }, cursor: '2', }, @@ -51,12 +55,14 @@ export const MOCKS = [ node: { _id: '3', name: 'userTag 3', + parentTag: null, usersAssignedTo: { totalCount: 0, }, childTags: { totalCount: 5, }, + ancestorTags: [], }, cursor: '3', }, @@ -64,12 +70,14 @@ export const MOCKS = [ node: { _id: '4', name: 'userTag 4', + parentTag: null, usersAssignedTo: { totalCount: 0, }, childTags: { totalCount: 0, }, + ancestorTags: [], }, cursor: '4', }, @@ -77,12 +85,14 @@ export const MOCKS = [ node: { _id: '5', name: 'userTag 5', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 5, }, + ancestorTags: [], }, cursor: '5', }, @@ -90,12 +100,14 @@ export const MOCKS = [ node: { _id: '6', name: 'userTag 6', + parentTag: null, usersAssignedTo: { totalCount: 6, }, childTags: { totalCount: 6, }, + ancestorTags: [], }, cursor: '6', }, @@ -103,12 +115,14 @@ export const MOCKS = [ node: { _id: '7', name: 'userTag 7', + parentTag: null, usersAssignedTo: { totalCount: 7, }, childTags: { totalCount: 7, }, + ancestorTags: [], }, cursor: '7', }, @@ -116,12 +130,14 @@ export const MOCKS = [ node: { _id: '8', name: 'userTag 8', + parentTag: null, usersAssignedTo: { totalCount: 8, }, childTags: { totalCount: 8, }, + ancestorTags: [], }, cursor: '8', }, @@ -129,12 +145,14 @@ export const MOCKS = [ node: { _id: '9', name: 'userTag 9', + parentTag: null, usersAssignedTo: { totalCount: 9, }, childTags: { totalCount: 9, }, + ancestorTags: [], }, cursor: '9', }, @@ -142,12 +160,14 @@ export const MOCKS = [ node: { _id: '10', name: 'userTag 10', + parentTag: null, usersAssignedTo: { totalCount: 10, }, childTags: { totalCount: 10, }, + ancestorTags: [], }, cursor: '10', }, @@ -184,12 +204,14 @@ export const MOCKS = [ node: { _id: '11', name: 'userTag 11', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 5, }, + ancestorTags: [], }, cursor: '11', }, @@ -197,12 +219,14 @@ export const MOCKS = [ node: { _id: '12', name: 'userTag 12', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 0, }, + ancestorTags: [], }, cursor: '12', }, @@ -244,6 +268,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag1', }, @@ -257,6 +287,12 @@ export const MOCKS = [ childTags: { totalCount: 0, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag2', }, @@ -270,6 +306,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag3', }, @@ -283,6 +325,12 @@ export const MOCKS = [ childTags: { totalCount: 0, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag4', }, @@ -296,6 +344,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag5', }, @@ -309,6 +363,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag6', }, @@ -322,6 +382,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag7', }, @@ -335,6 +401,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag8', }, @@ -348,6 +420,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag9', }, @@ -361,6 +439,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag10', }, @@ -373,6 +457,7 @@ export const MOCKS = [ }, totalCount: 11, }, + ancestorTags: [], }, }, }, @@ -402,6 +487,12 @@ export const MOCKS = [ childTags: { totalCount: 0, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag11', }, @@ -414,6 +505,7 @@ export const MOCKS = [ }, totalCount: 11, }, + ancestorTags: [], }, }, }, @@ -484,12 +576,14 @@ export const MOCKS_ERROR_SUBTAGS_QUERY = [ node: { _id: '1', name: 'userTag 1', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 11, }, + ancestorTags: [], }, cursor: '1', }, @@ -497,12 +591,14 @@ export const MOCKS_ERROR_SUBTAGS_QUERY = [ node: { _id: '2', name: 'userTag 2', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 0, }, + ancestorTags: [], }, cursor: '2', }, diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index c4c1375e0e..c660e441db 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -20,11 +20,7 @@ import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18n from 'utils/i18nForTest'; import ManageTag from './ManageTag'; -import { - MOCKS, - MOCKS_ERROR_ASSIGNED_MEMBERS, - MOCKS_ERROR_TAG_ANCESTORS, -} from './ManageTagMocks'; +import { MOCKS, MOCKS_ERROR_ASSIGNED_MEMBERS } from './ManageTagMocks'; import { type ApolloLink } from '@apollo/client'; const translations = { @@ -37,7 +33,6 @@ const translations = { const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_ERROR_ASSIGNED_MEMBERS, true); -const link3 = new StaticMockLink(MOCKS_ERROR_TAG_ANCESTORS, true); async function wait(ms = 500): Promise { await act(() => { @@ -129,16 +124,6 @@ describe('Manage Tag Page', () => { }); }); - test('renders error component on unsuccessful userTag ancestors query', async () => { - const { queryByText } = renderManageTag(link3); - - await wait(); - - await waitFor(() => { - expect(queryByText(translations.addPeopleToTag)).not.toBeInTheDocument(); - }); - }); - test('opens and closes the add people to tag modal', async () => { renderManageTag(link); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index f04965ba4d..b93fbe6887 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -1,6 +1,6 @@ import type { FormEvent } from 'react'; import React, { useEffect, useState } from 'react'; -import { useMutation, useQuery, type ApolloError } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; import { Search, WarningAmberRounded } from '@mui/icons-material'; import SortIcon from '@mui/icons-material/Sort'; import Loader from 'components/Loader/Loader'; @@ -30,10 +30,7 @@ import { UNASSIGN_USER_TAG, UPDATE_USER_TAG, } from 'GraphQl/Mutations/TagMutations'; -import { - USER_TAG_ANCESTORS, - USER_TAGS_ASSIGNED_MEMBERS, -} from 'GraphQl/Queries/userTagQueries'; +import { USER_TAGS_ASSIGNED_MEMBERS } from 'GraphQl/Queries/userTagQueries'; import AddPeopleToTag from 'components/AddPeopleToTag/AddPeopleToTag'; import TagActions from 'components/TagActions/TagActions'; import InfiniteScroll from 'react-infinite-scroll-component'; @@ -108,6 +105,7 @@ function ManageTag(): JSX.Element { id: currentTagId, first: TAGS_QUERY_DATA_CHUNK_SIZE, }, + fetchPolicy: 'no-cache', }); const loadMoreAssignedMembers = (): void => { @@ -146,27 +144,6 @@ function ManageTag(): JSX.Element { }); }; - const { - data: orgUserTagAncestorsData, - loading: orgUserTagsAncestorsLoading, - refetch: orgUserTagsAncestorsRefetch, - error: orgUserTagsAncestorsError, - }: { - data?: { - getUserTagAncestors: { - _id: string; - name: string; - }[]; - }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - } = useQuery(USER_TAG_ANCESTORS, { - variables: { - id: currentTagId, - }, - }); - const [unassignUserTag] = useMutation(UNASSIGN_USER_TAG); const handleUnassignUserTag = async (): Promise => { @@ -220,7 +197,6 @@ function ManageTag(): JSX.Element { if (data) { toast.success(t('tagUpdationSuccess')); userTagAssignedMembersRefetch(); - orgUserTagsAncestorsRefetch(); setEditUserTagModalIsOpen(false); } } catch (error: unknown) { @@ -251,22 +227,17 @@ function ManageTag(): JSX.Element { } }; - if (userTagAssignedMembersLoading || orgUserTagsAncestorsLoading) { + if (userTagAssignedMembersLoading) { return ; } - if (userTagAssignedMembersError || orgUserTagsAncestorsError) { + if (userTagAssignedMembersError) { return (
- Error occured while loading{' '} - {userTagAssignedMembersError ? 'assigned users' : 'tag ancestors'} -
- {userTagAssignedMembersError - ? userTagAssignedMembersError.message - : orgUserTagsAncestorsError?.message} + Error occured while loading assigned users
@@ -277,7 +248,16 @@ function ManageTag(): JSX.Element { userTagAssignedMembersData?.getAssignedUsers.usersAssignedTo.edges.map( (edge) => edge.node, ) ?? /* istanbul ignore next */ []; - const orgUserTagAncestors = orgUserTagAncestorsData?.getUserTagAncestors; + + // get the ancestorTags array and push the current tag in it + // used for the tag breadcrumbs + const orgUserTagAncestors = [ + ...(userTagAssignedMembersData?.getAssignedUsers.ancestorTags ?? []), + { + _id: currentTagId, + name: currentTagName, + }, + ]; const redirectToSubTags = (tagId: string): void => { navigate(`/orgtags/${orgId}/subTags/${tagId}`); diff --git a/src/screens/ManageTag/ManageTagMocks.ts b/src/screens/ManageTag/ManageTagMocks.ts index 4e9c64badd..b7ef4cef46 100644 --- a/src/screens/ManageTag/ManageTagMocks.ts +++ b/src/screens/ManageTag/ManageTagMocks.ts @@ -3,10 +3,7 @@ import { UNASSIGN_USER_TAG, UPDATE_USER_TAG, } from 'GraphQl/Mutations/TagMutations'; -import { - USER_TAG_ANCESTORS, - USER_TAGS_ASSIGNED_MEMBERS, -} from 'GraphQl/Queries/userTagQueries'; +import { USER_TAGS_ASSIGNED_MEMBERS } from 'GraphQl/Queries/userTagQueries'; import { TAGS_QUERY_DATA_CHUNK_SIZE } from 'utils/organizationTagsUtils'; export const MOCKS = [ @@ -113,6 +110,7 @@ export const MOCKS = [ }, totalCount: 12, }, + ancestorTags: [], }, }, }, @@ -157,28 +155,11 @@ export const MOCKS = [ }, totalCount: 12, }, + ancestorTags: [], }, }, }, }, - { - request: { - query: USER_TAG_ANCESTORS, - variables: { - id: '1', - }, - }, - result: { - data: { - getUserTagAncestors: [ - { - _id: '1', - name: 'tag1', - }, - ], - }, - }, - }, { request: { query: UNASSIGN_USER_TAG, @@ -239,55 +220,4 @@ export const MOCKS_ERROR_ASSIGNED_MEMBERS = [ }, error: new Error('Mock Graphql Error'), }, - { - request: { - query: USER_TAG_ANCESTORS, - variables: { - id: '1', - }, - }, - result: { - data: { - getUserTagAncestors: [], - }, - }, - }, -]; - -export const MOCKS_ERROR_TAG_ANCESTORS = [ - { - request: { - query: USER_TAGS_ASSIGNED_MEMBERS, - variables: { - id: '1', - first: TAGS_QUERY_DATA_CHUNK_SIZE, - }, - }, - result: { - data: { - getAssignedUsers: { - name: 'tag1', - usersAssignedTo: { - edges: [], - pageInfo: { - startCursor: '1', - endCursor: '1', - hasNextPage: false, - hasPreviousPage: false, - }, - totalCount: 1, - }, - }, - }, - }, - }, - { - request: { - query: USER_TAG_ANCESTORS, - variables: { - id: '1', - }, - }, - error: new Error('Mock Graphql Error'), - }, ]; diff --git a/src/screens/OrganizationTags/OrganizationTagsMocks.ts b/src/screens/OrganizationTags/OrganizationTagsMocks.ts index a8e186b393..c3c54c138a 100644 --- a/src/screens/OrganizationTags/OrganizationTagsMocks.ts +++ b/src/screens/OrganizationTags/OrganizationTagsMocks.ts @@ -21,12 +21,14 @@ export const MOCKS = [ node: { _id: '1', name: 'userTag 1', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 11, }, + ancestorTags: [], }, cursor: '1', }, @@ -34,12 +36,14 @@ export const MOCKS = [ node: { _id: '2', name: 'userTag 2', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 0, }, + ancestorTags: [], }, cursor: '2', }, @@ -47,12 +51,14 @@ export const MOCKS = [ node: { _id: '3', name: 'userTag 3', + parentTag: null, usersAssignedTo: { totalCount: 0, }, childTags: { totalCount: 5, }, + ancestorTags: [], }, cursor: '3', }, @@ -60,12 +66,14 @@ export const MOCKS = [ node: { _id: '4', name: 'userTag 4', + parentTag: null, usersAssignedTo: { totalCount: 0, }, childTags: { totalCount: 0, }, + ancestorTags: [], }, cursor: '4', }, @@ -73,12 +81,14 @@ export const MOCKS = [ node: { _id: '5', name: 'userTag 5', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 5, }, + ancestorTags: [], }, cursor: '5', }, @@ -86,12 +96,14 @@ export const MOCKS = [ node: { _id: '6', name: 'userTag 6', + parentTag: null, usersAssignedTo: { totalCount: 6, }, childTags: { totalCount: 6, }, + ancestorTags: [], }, cursor: '6', }, @@ -99,12 +111,14 @@ export const MOCKS = [ node: { _id: '7', name: 'userTag 7', + parentTag: null, usersAssignedTo: { totalCount: 7, }, childTags: { totalCount: 7, }, + ancestorTags: [], }, cursor: '7', }, @@ -112,12 +126,14 @@ export const MOCKS = [ node: { _id: '8', name: 'userTag 8', + parentTag: null, usersAssignedTo: { totalCount: 8, }, childTags: { totalCount: 8, }, + ancestorTags: [], }, cursor: '8', }, @@ -125,12 +141,14 @@ export const MOCKS = [ node: { _id: '9', name: 'userTag 9', + parentTag: null, usersAssignedTo: { totalCount: 9, }, childTags: { totalCount: 9, }, + ancestorTags: [], }, cursor: '9', }, @@ -138,12 +156,14 @@ export const MOCKS = [ node: { _id: '10', name: 'userTag 10', + parentTag: null, usersAssignedTo: { totalCount: 10, }, childTags: { totalCount: 10, }, + ancestorTags: [], }, cursor: '10', }, @@ -180,12 +200,14 @@ export const MOCKS = [ node: { _id: '11', name: 'userTag 11', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 5, }, + ancestorTags: [], }, cursor: '11', }, @@ -193,12 +215,14 @@ export const MOCKS = [ node: { _id: '12', name: 'userTag 12', + parentTag: null, usersAssignedTo: { totalCount: 5, }, childTags: { totalCount: 0, }, + ancestorTags: [], }, cursor: '12', }, diff --git a/src/screens/SubTags/SubTags.test.tsx b/src/screens/SubTags/SubTags.test.tsx index 9cd778b858..6445d259e8 100644 --- a/src/screens/SubTags/SubTags.test.tsx +++ b/src/screens/SubTags/SubTags.test.tsx @@ -20,11 +20,7 @@ import { store } from 'state/store'; import { StaticMockLink } from 'utils/StaticMockLink'; import i18n from 'utils/i18nForTest'; import SubTags from './SubTags'; -import { - MOCKS, - MOCKS_ERROR_SUB_TAGS, - MOCKS_ERROR_TAG_ANCESTORS, -} from './SubTagsMocks'; +import { MOCKS, MOCKS_ERROR_SUB_TAGS } from './SubTagsMocks'; import { type ApolloLink } from '@apollo/client'; const translations = { @@ -39,7 +35,6 @@ const translations = { const link = new StaticMockLink(MOCKS, true); const link2 = new StaticMockLink(MOCKS_ERROR_SUB_TAGS, true); -const link3 = new StaticMockLink(MOCKS_ERROR_TAG_ANCESTORS, true); async function wait(ms = 500): Promise { await act(() => { @@ -56,25 +51,10 @@ jest.mock('react-toastify', () => ({ }, })); -// const cache = new InMemoryCache({ -// typePolicies: { -// Query: { -// fields: { -// getUserTag: { -// keyArgs: false, -// merge(_, incoming) { -// return incoming; -// }, -// }, -// }, -// }, -// }, -// }); - const renderSubTags = (link: ApolloLink): RenderResult => { return render( - - + + @@ -131,16 +111,6 @@ describe('Organisation Tags Page', () => { }); }); - test('renders error component on unsuccessful userTag ancestors query', async () => { - const { queryByText } = renderSubTags(link3); - - await wait(); - - await waitFor(() => { - expect(queryByText(translations.addChildTag)).not.toBeInTheDocument(); - }); - }); - test('opens and closes the create tag modal', async () => { renderSubTags(link); diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index 193670ac59..74789ff0df 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery, type ApolloError } from '@apollo/client'; +import { useMutation, useQuery } from '@apollo/client'; import { Search, WarningAmberRounded } from '@mui/icons-material'; import SortIcon from '@mui/icons-material/Sort'; import Loader from 'components/Loader/Loader'; @@ -24,10 +24,7 @@ import { import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { Stack } from '@mui/material'; import { CREATE_USER_TAG } from 'GraphQl/Mutations/TagMutations'; -import { - USER_TAG_ANCESTORS, - USER_TAG_SUB_TAGS, -} from 'GraphQl/Queries/userTagQueries'; +import { USER_TAG_SUB_TAGS } from 'GraphQl/Queries/userTagQueries'; import InfiniteScroll from 'react-infinite-scroll-component'; import InfiniteScrollLoader from 'components/InfiniteScrollLoader/InfiniteScrollLoader'; @@ -106,26 +103,6 @@ function SubTags(): JSX.Element { }); }; - const { - data: orgUserTagAncestorsData, - loading: orgUserTagsAncestorsLoading, - error: orgUserTagsAncestorsError, - }: { - data?: { - getUserTagAncestors: { - _id: string; - name: string; - }[]; - }; - loading: boolean; - error?: ApolloError; - refetch: () => void; - } = useQuery(USER_TAG_ANCESTORS, { - variables: { - id: parentTagId, - }, - }); - const [create, { loading: createUserTagLoading }] = useMutation(CREATE_USER_TAG); @@ -156,22 +133,17 @@ function SubTags(): JSX.Element { } }; - if (createUserTagLoading || subTagsLoading || orgUserTagsAncestorsLoading) { + if (createUserTagLoading || subTagsLoading) { return ; } - if (subTagsError || orgUserTagsAncestorsError) { + if (subTagsError) { return (
- Error occured while loading{' '} - {subTagsError ? 'sub tags' : 'tag ancestors'} -
- {subTagsError - ? subTagsError.message - : orgUserTagsAncestorsError?.message} + Error occured while loading sub tags
@@ -182,7 +154,17 @@ function SubTags(): JSX.Element { subTagsData?.getChildTags.childTags.edges.map((edge) => edge.node) ?? /* istanbul ignore next */ []; - const orgUserTagAncestors = orgUserTagAncestorsData?.getUserTagAncestors; + const parentTagName = subTagsData?.getChildTags.name; + + // get the ancestorTags array and push the current tag in it + // used for the tag breadcrumbs + const orgUserTagAncestors = [ + ...(subTagsData?.getChildTags.ancestorTags ?? []), + { + _id: parentTagId, + name: parentTagName, + }, + ]; const redirectToManageTag = (tagId: string): void => { navigate(`/orgtags/${orgId}/manageTag/${tagId}`); diff --git a/src/screens/SubTags/SubTagsMocks.ts b/src/screens/SubTags/SubTagsMocks.ts index b51541a5e7..250c13253d 100644 --- a/src/screens/SubTags/SubTagsMocks.ts +++ b/src/screens/SubTags/SubTagsMocks.ts @@ -1,8 +1,5 @@ import { CREATE_USER_TAG } from 'GraphQl/Mutations/TagMutations'; -import { - USER_TAG_ANCESTORS, - USER_TAG_SUB_TAGS, -} from 'GraphQl/Queries/userTagQueries'; +import { USER_TAG_SUB_TAGS } from 'GraphQl/Queries/userTagQueries'; import { TAGS_QUERY_DATA_CHUNK_SIZE } from 'utils/organizationTagsUtils'; export const MOCKS = [ @@ -30,6 +27,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag1', }, @@ -43,6 +46,12 @@ export const MOCKS = [ childTags: { totalCount: 0, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag2', }, @@ -56,6 +65,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag3', }, @@ -69,6 +84,12 @@ export const MOCKS = [ childTags: { totalCount: 0, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag4', }, @@ -82,6 +103,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag5', }, @@ -95,6 +122,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag6', }, @@ -108,6 +141,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag7', }, @@ -121,6 +160,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag8', }, @@ -134,6 +179,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag9', }, @@ -147,6 +198,12 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag10', }, @@ -159,6 +216,7 @@ export const MOCKS = [ }, totalCount: 11, }, + ancestorTags: [], }, }, }, @@ -175,7 +233,7 @@ export const MOCKS = [ result: { data: { getChildTags: { - name: 'tag1', + name: 'userTag 1', childTags: { edges: [ { @@ -188,6 +246,12 @@ export const MOCKS = [ childTags: { totalCount: 0, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, cursor: 'subTag11', }, @@ -200,28 +264,11 @@ export const MOCKS = [ }, totalCount: 11, }, + ancestorTags: [], }, }, }, }, - { - request: { - query: USER_TAG_ANCESTORS, - variables: { - id: '1', - }, - }, - result: { - data: { - getUserTagAncestors: [ - { - _id: '1', - name: 'tag1', - }, - ], - }, - }, - }, { request: { query: USER_TAG_SUB_TAGS, @@ -246,6 +293,16 @@ export const MOCKS = [ childTags: { totalCount: 5, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + { + _id: 'subTag1', + name: 'subTag 1', + }, + ], }, cursor: 'subTag1.1', }, @@ -258,32 +315,16 @@ export const MOCKS = [ }, totalCount: 1, }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], }, }, }, }, - { - request: { - query: USER_TAG_ANCESTORS, - variables: { - id: 'subTag1', - }, - }, - result: { - data: { - getUserTagAncestors: [ - { - _id: '1', - name: 'tag 1', - }, - { - _id: 'subTag1', - name: 'subTag 1', - }, - ], - }, - }, - }, { request: { query: CREATE_USER_TAG, @@ -314,55 +355,4 @@ export const MOCKS_ERROR_SUB_TAGS = [ }, error: new Error('Mock Graphql Error'), }, - { - request: { - query: USER_TAG_ANCESTORS, - variables: { - id: '1', - }, - }, - result: { - data: { - getUserTagAncestors: [], - }, - }, - }, -]; - -export const MOCKS_ERROR_TAG_ANCESTORS = [ - { - request: { - query: USER_TAG_SUB_TAGS, - variables: { - id: '1', - first: TAGS_QUERY_DATA_CHUNK_SIZE, - }, - }, - result: { - data: { - getChildTags: { - name: 'tag1', - childTags: { - edges: [], - pageInfo: { - startCursor: '0', - endCursor: '0', - hasNextPage: false, - hasPreviousPage: false, - }, - totalCount: 0, - }, - }, - }, - }, - }, - { - request: { - query: USER_TAG_ANCESTORS, - variables: { - id: '1', - }, - }, - error: new Error('Mock Graphql Error'), - }, ]; diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index 3d3af2ac64..234b36262e 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -212,12 +212,17 @@ export interface InterfaceQueryOrganizationPostListItem { export interface InterfaceTagData { _id: string; name: string; + parentTag: { _id: string }; usersAssignedTo: { totalCount: number; }; childTags: { totalCount: number; }; + ancestorTags: { + _id: string; + name: string; + }[]; } interface InterfaceTagNodeData { @@ -258,16 +263,19 @@ export interface InterfaceQueryOrganizationUserTags { export interface InterfaceQueryUserTagChildTags { name: string; childTags: InterfaceTagNodeData; + ancestorTags: { + _id: string; + name: string; + }[]; } export interface InterfaceQueryUserTagsAssignedMembers { name: string; usersAssignedTo: InterfaceTagMembersData; -} - -export interface InterfaceQueryUserTagsMembersToAssignTo { - name: string; - usersToAssignTo: InterfaceTagMembersData; + ancestorTags: { + _id: string; + name: string; + }[]; } export interface InterfaceQueryUserTagsMembersToAssignTo { From b96e7e4961500c1fefa10d5a41783e5b0f847d0d Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 1 Nov 2024 15:18:46 +0530 Subject: [PATCH 43/52] add search and sort --- src/GraphQl/Queries/OrganizationQueries.ts | 11 +- src/GraphQl/Queries/userTagQueries.ts | 17 +- .../AddPeopleToTag/AddPeopleToTag.module.css | 8 + .../AddPeopleToTag/AddPeopleToTag.tsx | 97 ++++-- src/screens/ManageTag/ManageTag.tsx | 289 ++++++++++-------- .../OrganizationTags/OrganizationTags.tsx | 143 +++++---- src/screens/SubTags/SubTags.tsx | 180 +++++------ src/utils/organizationTagsUtils.ts | 3 + 8 files changed, 439 insertions(+), 309 deletions(-) diff --git a/src/GraphQl/Queries/OrganizationQueries.ts b/src/GraphQl/Queries/OrganizationQueries.ts index 574dbe48d1..4dc7dd7a09 100644 --- a/src/GraphQl/Queries/OrganizationQueries.ts +++ b/src/GraphQl/Queries/OrganizationQueries.ts @@ -89,9 +89,18 @@ export const ORGANIZATION_USER_TAGS_LIST = gql` $before: String $first: PositiveInt $last: PositiveInt + $where: UserTagWhereInput + $sortedBy: UserTagSortedByInput ) { organizations(id: $id) { - userTags(after: $after, before: $before, first: $first, last: $last) { + userTags( + after: $after + before: $before + first: $first + last: $last + where: $where + sortedBy: $sortedBy + ) { edges { node { _id diff --git a/src/GraphQl/Queries/userTagQueries.ts b/src/GraphQl/Queries/userTagQueries.ts index 827cca28fe..d58da19e1b 100644 --- a/src/GraphQl/Queries/userTagQueries.ts +++ b/src/GraphQl/Queries/userTagQueries.ts @@ -14,6 +14,8 @@ export const USER_TAGS_ASSIGNED_MEMBERS = gql` $before: String $first: PositiveInt $last: PositiveInt + $where: UserTagUsersAssignedToWhereInput + $sortedBy: UserTagUsersAssignedToSortedByInput ) { getAssignedUsers: getUserTag(id: $id) { name @@ -22,6 +24,8 @@ export const USER_TAGS_ASSIGNED_MEMBERS = gql` before: $before first: $first last: $last + where: $where + sortedBy: $sortedBy ) { edges { node { @@ -60,10 +64,19 @@ export const USER_TAG_SUB_TAGS = gql` $before: String $first: PositiveInt $last: PositiveInt + $where: UserTagWhereInput + $sortedBy: UserTagSortedByInput ) { getChildTags: getUserTag(id: $id) { name - childTags(after: $after, before: $before, first: $first, last: $last) { + childTags( + after: $after + before: $before + first: $first + last: $last + where: $where + sortedBy: $sortedBy + ) { edges { node { _id @@ -110,6 +123,7 @@ export const USER_TAGS_MEMBERS_TO_ASSIGN_TO = gql` $before: String $first: PositiveInt $last: PositiveInt + $where: UserTagUsersToAssignToWhereInput ) { getUsersToAssignTo: getUserTag(id: $id) { name @@ -118,6 +132,7 @@ export const USER_TAGS_MEMBERS_TO_ASSIGN_TO = gql` before: $before first: $first last: $last + where: $where ) { edges { node { diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css index c7c5c4582b..d4f24626ee 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.module.css +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -28,6 +28,7 @@ } .scrollContainer { + min-height: 100px; max-height: 100px; overflow-y: auto; margin-bottom: 1rem; @@ -45,3 +46,10 @@ .removeFilterIcon { cursor: pointer; } + +.loadingDiv { + min-height: 300px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index ed43061410..e8dcd279af 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -1,7 +1,6 @@ import { useMutation, useQuery } from '@apollo/client'; import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { DataGrid } from '@mui/x-data-grid'; -import Loader from 'components/Loader/Loader'; import { USER_TAGS_MEMBERS_TO_ASSIGN_TO } from 'GraphQl/Queries/userTagQueries'; import type { ChangeEvent } from 'react'; import React, { useState } from 'react'; @@ -55,6 +54,11 @@ const AddPeopleToTag: React.FC = ({ [], ); + const [memberToAssignToSearchFirstName, setMemberToAssignToSearchFirstName] = + useState(''); + const [memberToAssignToSearchLastName, setMemberToAssignToSearchLastName] = + useState(''); + const { data: userTagsMembersToAssignToData, loading: userTagsMembersToAssignToLoading, @@ -66,6 +70,10 @@ const AddPeopleToTag: React.FC = ({ variables: { id: currentTagId, first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: memberToAssignToSearchFirstName }, + lastName: { starts_with: memberToAssignToSearchLastName }, + }, }, skip: !addPeopleToTagModalIsOpen, }, @@ -251,39 +259,74 @@ const AddPeopleToTag: React.FC = ({ +
+ {assignToMembers.length === 0 ? ( +
+ {t('noOneSelected')} +
+ ) : ( + assignToMembers.map((member) => ( +
+ {member.firstName} {member.lastName} + removeMember(member._id)} + data-testid="clearSelectedMember" + /> +
+ )) + )} +
+ +
+
+ + + setMemberToAssignToSearchFirstName(e.target.value.trim()) + } + data-testid="searchByName" + autoComplete="off" + required + /> +
+
+ + + setMemberToAssignToSearchLastName(e.target.value.trim()) + } + data-testid="searchByName" + autoComplete="off" + required + /> +
+
+ {userTagsMembersToAssignToLoading ? ( - +
+ +
) : ( <> -
- {assignToMembers.length === 0 ? ( -
- {t('noOneSelected')} -
- ) : ( - assignToMembers.map((member) => ( -
- {member.firstName} {member.lastName} - removeMember(member._id)} - data-testid="clearSelectedMember" - /> -
- )) - )} -
-
diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index b93fbe6887..2c6892d4f8 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -1,7 +1,7 @@ import type { FormEvent } from 'react'; import React, { useEffect, useState } from 'react'; import { useMutation, useQuery } from '@apollo/client'; -import { Search, WarningAmberRounded } from '@mui/icons-material'; +import { WarningAmberRounded } from '@mui/icons-material'; import SortIcon from '@mui/icons-material/Sort'; import Loader from 'components/Loader/Loader'; import IconComponent from 'components/IconComponent/IconComponent'; @@ -17,6 +17,7 @@ import styles from './ManageTag.module.css'; import { DataGrid } from '@mui/x-data-grid'; import type { InterfaceTagAssignedMembersQuery, + SortedByType, TagActionType, } from 'utils/organizationTagsUtils'; import { @@ -65,6 +66,16 @@ function ManageTag(): JSX.Element { const navigate = useNavigate(); const [unassignUserId, setUnassignUserId] = useState(null); + const [assignedMemberSearchInput, setAssignedMemberSearchInput] = + useState(''); + const [assignedMemberSearchFirstName, setAssignedMemberSearchFirstName] = + useState(''); + const [assignedMemberSearchLastName, setAssignedMemberSearchLastName] = + useState(''); + + const [assignedMemberSortOrder, setAssignedMemberSortOrder] = + useState('DESCENDING'); + // a state to specify whether we're assigning to tags or removing from tags const [tagActionType, setTagActionType] = useState('assignToTags'); @@ -104,6 +115,11 @@ function ManageTag(): JSX.Element { variables: { id: currentTagId, first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: assignedMemberSearchFirstName }, + lastName: { starts_with: assignedMemberSearchLastName }, + }, + sortedBy: { id: assignedMemberSortOrder }, }, fetchPolicy: 'no-cache', }); @@ -144,6 +160,15 @@ function ManageTag(): JSX.Element { }); }; + useEffect(() => { + const [firstName, ...lastNameParts] = assignedMemberSearchInput + .trim() + .split(/\s+/); + const lastName = lastNameParts.join(' '); // Joins everything after the first word + setAssignedMemberSearchFirstName(firstName); + setAssignedMemberSearchLastName(lastName); + }, [assignedMemberSearchInput]); + const [unassignUserTag] = useMutation(UNASSIGN_USER_TAG); const handleUnassignUserTag = async (): Promise => { @@ -227,10 +252,6 @@ function ManageTag(): JSX.Element { } }; - if (userTagAssignedMembersLoading) { - return ; - } - if (userTagAssignedMembersError) { return (
@@ -346,21 +367,19 @@ function ManageTag(): JSX.Element {
+ + setAssignedMemberSearchInput(e.target.value.trim()) + } data-testid="searchByName" autoComplete="off" required /> -
- + setAssignedMemberSortOrder('DESCENDING')} + > {tCommon('Latest')} - + setAssignedMemberSortOrder('ASCENDING')} + > {tCommon('Oldest')} @@ -403,131 +428,137 @@ function ManageTag(): JSX.Element { {t('addPeopleToTag')}
- - -
-
- + + {userTagAssignedMembersLoading ? ( + + ) : ( + + +
+
+ +
+
navigate(`/orgtags/${orgId}`)} + className={`fs-6 ms-3 my-1 ${styles.tagsBreadCrumbs}`} + data-testid="allTagsBtn" + > + {'Tags'} + +
+ {orgUserTagAncestors?.map((tag, index) => ( +
redirectToManageTag(tag._id as string)} + data-testid="redirectToManageTag" + > + {tag.name} + {orgUserTagAncestors.length - 1 !== index && ( + /* istanbul ignore next */ + + )} +
+ ))}
navigate(`/orgtags/${orgId}`)} - className={`fs-6 ms-3 my-1 ${styles.tagsBreadCrumbs}`} - data-testid="allTagsBtn" + id="manageTagScrollableDiv" + data-testid="manageTagScrollableDiv" + className={styles.manageTagScrollableDiv} > - {'Tags'} - + } + scrollableTarget="manageTagScrollableDiv" + > + row.id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noAssignedMembersFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagAssignedMembers?.map( + (assignedMembers, index) => ({ + id: index + 1, + ...assignedMembers, + }), + )} + columns={columns} + isRowSelectable={() => false} + /> +
- {orgUserTagAncestors?.map((tag, index) => ( + + +
+
{'Actions'}
+
+
redirectToManageTag(tag._id as string)} - data-testid="redirectToManageTag" + onClick={() => { + setTagActionType('assignToTags'); + showTagActionsModal(); + }} + className="my-2 btn btn-primary btn-sm w-75" + data-testid="assignToTags" > - {tag.name} - {orgUserTagAncestors.length - 1 !== index && ( - /* istanbul ignore next */ - - )} + {t('assignToTags')}
- ))} -
-
- } - scrollableTarget="manageTagScrollableDiv" - > - row.id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noAssignedMembersFound')} - - ), +
{ + setTagActionType('removeFromTags'); + showTagActionsModal(); + }} + className="mb-1 btn btn-danger btn-sm w-75" + data-testid="removeFromTags" + > + {t('removeFromTags')} +
+
`${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={userTagAssignedMembers?.map( - (assignedMembers, index) => ({ - id: index + 1, - ...assignedMembers, - }), - )} - columns={columns} - isRowSelectable={() => false} /> -
-
- - -
-
{'Actions'}
-
-
-
{ - setTagActionType('assignToTags'); - showTagActionsModal(); - }} - className="my-2 btn btn-primary btn-sm w-75" - data-testid="assignToTags" - > - {t('assignToTags')} -
-
{ - setTagActionType('removeFromTags'); - showTagActionsModal(); - }} - className="mb-1 btn btn-danger btn-sm w-75" - data-testid="removeFromTags" - > - {t('removeFromTags')} -
-
-
- {tCommon('edit')} -
-
- {tCommon('remove')} +
+ {tCommon('edit')} +
+
+ {tCommon('remove')} +
-
- -
+ + + )}
diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index ce37528cc1..d239bac7a3 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery } from '@apollo/client'; -import { Search, WarningAmberRounded } from '@mui/icons-material'; +import { WarningAmberRounded } from '@mui/icons-material'; import SortIcon from '@mui/icons-material/Sort'; import Loader from 'components/Loader/Loader'; import IconComponent from 'components/IconComponent/IconComponent'; @@ -16,7 +16,10 @@ import { toast } from 'react-toastify'; import type { InterfaceQueryOrganizationUserTags } from 'utils/interfaces'; import styles from './OrganizationTags.module.css'; import { DataGrid } from '@mui/x-data-grid'; -import type { InterfaceOrganizationTagsQuery } from 'utils/organizationTagsUtils'; +import type { + InterfaceOrganizationTagsQuery, + SortedByType, +} from 'utils/organizationTagsUtils'; import { dataGridStyle, TAGS_QUERY_DATA_CHUNK_SIZE, @@ -43,6 +46,9 @@ function OrganizationTags(): JSX.Element { const [createTagModalIsOpen, setCreateTagModalIsOpen] = useState(false); + const [tagSearchName, setTagSearchName] = useState(''); + const [tagSortOrder, setTagSortOrder] = useState('DESCENDING'); + const { orgId } = useParams(); const navigate = useNavigate(); @@ -67,6 +73,8 @@ function OrganizationTags(): JSX.Element { variables: { id: orgId, first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: tagSearchName } }, + sortedBy: { id: tagSortOrder }, }, }); @@ -137,10 +145,6 @@ function OrganizationTags(): JSX.Element { } }; - if (createUserTagLoading || orgUserTagsLoading) { - return ; - } - if (orgUserTagsError) { return (
@@ -272,21 +276,17 @@ function OrganizationTags(): JSX.Element {
+ setTagSearchName(e.target.value.trim())} autoComplete="off" required /> -
- + setTagSortOrder('DESCENDING')} + > {tCommon('Latest')} - + setTagSortOrder('ASCENDING')} + > {tCommon('Oldest')} @@ -322,61 +328,66 @@ function OrganizationTags(): JSX.Element {
-
-
-
- -
+ {orgUserTagsLoading || createUserTagLoading ? ( + + ) : ( +
+
+
+ +
-
- {'Tags'} +
+ {'Tags'} +
-
-
- } - scrollableTarget="orgUserTagsScrollableDiv" + +
- row.id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noTagsFound')} - - ), - }} - sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={userTagsList?.map((userTag, index) => ({ - id: index + 1, - ...userTag, - }))} - columns={columns} - isRowSelectable={() => false} - /> - + } + scrollableTarget="orgUserTagsScrollableDiv" + > + row.id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noTagsFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={userTagsList?.map((userTag, index) => ({ + id: index + 1, + ...userTag, + }))} + columns={columns} + isRowSelectable={() => false} + /> + +
-
+ )}
diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index 74789ff0df..7d49c69047 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -1,5 +1,5 @@ import { useMutation, useQuery } from '@apollo/client'; -import { Search, WarningAmberRounded } from '@mui/icons-material'; +import { WarningAmberRounded } from '@mui/icons-material'; import SortIcon from '@mui/icons-material/Sort'; import Loader from 'components/Loader/Loader'; import IconComponent from 'components/IconComponent/IconComponent'; @@ -16,7 +16,10 @@ import { toast } from 'react-toastify'; import type { InterfaceQueryUserTagChildTags } from 'utils/interfaces'; import styles from './SubTags.module.css'; import { DataGrid } from '@mui/x-data-grid'; -import type { InterfaceOrganizationSubTagsQuery } from 'utils/organizationTagsUtils'; +import type { + InterfaceOrganizationSubTagsQuery, + SortedByType, +} from 'utils/organizationTagsUtils'; import { dataGridStyle, TAGS_QUERY_DATA_CHUNK_SIZE, @@ -49,6 +52,9 @@ function SubTags(): JSX.Element { const [tagName, setTagName] = useState(''); + const [tagSearchName, setTagSearchName] = useState(''); + const [tagSortOrder, setTagSortOrder] = useState('DESCENDING'); + const showAddSubTagModal = (): void => { setAddSubTagModalIsOpen(true); }; @@ -68,6 +74,8 @@ function SubTags(): JSX.Element { variables: { id: parentTagId, first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: tagSearchName } }, + sortedBy: { id: tagSortOrder }, }, }); @@ -133,10 +141,6 @@ function SubTags(): JSX.Element { } }; - if (createUserTagLoading || subTagsLoading) { - return ; - } - if (subTagsError) { return (
@@ -278,21 +282,17 @@ function SubTags(): JSX.Element {
+ setTagSearchName(e.target.value.trim())} data-testid="searchByName" autoComplete="off" required /> -
- + setTagSortOrder('DESCENDING')} + > {tCommon('Latest')} - + setTagSortOrder('ASCENDING')} + > {tCommon('Oldest')} @@ -337,82 +343,86 @@ function SubTags(): JSX.Element {
-
-
-
- -
- -
navigate(`/orgtags/${orgId}`)} - className={`fs-6 ms-3 my-1 ${styles.tagsBreadCrumbs}`} - data-testid="allTagsBtn" - > - {'Tags'} - -
+ {subTagsLoading || createUserTagLoading ? ( + + ) : ( +
+
+
+ +
- {orgUserTagAncestors?.map((tag, index) => (
redirectToSubTags(tag._id as string)} - data-testid="redirectToSubTags" + onClick={() => navigate(`/orgtags/${orgId}`)} + className={`fs-6 ms-3 my-1 ${styles.tagsBreadCrumbs}`} + data-testid="allTagsBtn" > - {tag.name} - - {orgUserTagAncestors.length - 1 !== index && ( - - )} + {'Tags'} +
- ))} -
-
- } - scrollableTarget="subTagsScrollableDiv" + + {orgUserTagAncestors?.map((tag, index) => ( +
redirectToSubTags(tag._id as string)} + data-testid="redirectToSubTags" + > + {tag.name} + + {orgUserTagAncestors.length - 1 !== index && ( + + )} +
+ ))} +
+
- row.id} - slots={{ - noRowsOverlay: /* istanbul ignore next */ () => ( - - {t('noTagsFound')} - - ), - }} - sx={dataGridStyle} - getRowClassName={() => `${styles.rowBackground}`} - autoHeight - rowHeight={65} - rows={subTagsList?.map((subTag, index) => ({ - id: index + 1, - ...subTag, - }))} - columns={columns} - isRowSelectable={() => false} - /> - + } + scrollableTarget="subTagsScrollableDiv" + > + row.id} + slots={{ + noRowsOverlay: /* istanbul ignore next */ () => ( + + {t('noTagsFound')} + + ), + }} + sx={dataGridStyle} + getRowClassName={() => `${styles.rowBackground}`} + autoHeight + rowHeight={65} + rows={subTagsList?.map((subTag, index) => ({ + id: index + 1, + ...subTag, + }))} + columns={columns} + isRowSelectable={() => false} + /> + +
-
+ )}
diff --git a/src/utils/organizationTagsUtils.ts b/src/utils/organizationTagsUtils.ts index 75c001816e..99faca3bf2 100644 --- a/src/utils/organizationTagsUtils.ts +++ b/src/utils/organizationTagsUtils.ts @@ -44,6 +44,9 @@ export const TAGS_QUERY_DATA_CHUNK_SIZE = 10; // the tag action type export type TagActionType = 'assignToTags' | 'removeFromTags'; +// the sortedByType +export type SortedByType = 'ASCENDING' | 'DESCENDING'; + // Interfaces for tag queries: // 1. Base interface for Apollo query results interface InterfaceBaseQueryResult { From 12793a167aa89213250e9c595c05510200cdb730 Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 1 Nov 2024 21:03:34 +0530 Subject: [PATCH 44/52] add tests --- .../AddPeopleToTag/AddPeopleToTag.module.css | 3 +- .../AddPeopleToTag/AddPeopleToTag.test.tsx | 99 ++++++++++- .../AddPeopleToTag/AddPeopleToTag.tsx | 10 +- .../AddPeopleToTag/AddPeopleToTagsMocks.ts | 106 ++++++++++++ .../TagActions/TagActions.module.css | 10 +- src/components/TagActions/TagActions.test.tsx | 21 +++ src/components/TagActions/TagActions.tsx | 123 +++++++++----- src/components/TagActions/TagActionsMocks.ts | 77 +++++++++ src/components/TagActions/TagNode.tsx | 2 +- src/screens/ManageTag/ManageTag.test.tsx | 78 +++++++++ src/screens/ManageTag/ManageTag.tsx | 3 +- src/screens/ManageTag/ManageTagMocks.ts | 113 +++++++++++++ .../OrganizationTags.test.tsx | 82 +++++++++- .../OrganizationTags/OrganizationTags.tsx | 38 +++-- .../OrganizationTags/OrganizationTagsMocks.ts | 154 ++++++++++++++++++ src/screens/SubTags/SubTags.test.tsx | 110 ++++++++++++- src/screens/SubTags/SubTags.tsx | 5 +- src/screens/SubTags/SubTagsMocks.ts | 144 ++++++++++++++++ 18 files changed, 1100 insertions(+), 78 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.module.css b/src/components/AddPeopleToTag/AddPeopleToTag.module.css index d4f24626ee..5dd04ffed5 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.module.css +++ b/src/components/AddPeopleToTag/AddPeopleToTag.module.css @@ -28,8 +28,7 @@ } .scrollContainer { - min-height: 100px; - max-height: 100px; + height: 100px; overflow-y: auto; margin-bottom: 1rem; } diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx index ef9977eb2f..99bdbd67e3 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -17,7 +17,7 @@ import { store } from 'state/store'; import userEvent from '@testing-library/user-event'; import { StaticMockLink } from 'utils/StaticMockLink'; import { toast } from 'react-toastify'; -import type { ApolloLink } from '@apollo/client'; +import { InMemoryCache, type ApolloLink } from '@apollo/client'; import type { InterfaceAddPeopleToTagProps } from './AddPeopleToTag'; import AddPeopleToTag from './AddPeopleToTag'; import i18n from 'utils/i18nForTest'; @@ -63,12 +63,39 @@ const props: InterfaceAddPeopleToTagProps = { >, }; +const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + getUserTag: { + merge(existing = {}, incoming) { + const merged = { + ...existing, + ...incoming, + usersToAssignTo: { + ...existing.usersToAssignTo, + ...incoming.usersToAssignTo, + edges: [ + ...(existing.usersToAssignTo?.edges || []), + ...(incoming.usersToAssignTo?.edges || []), + ], + }, + }; + + return merged; + }, + }, + }, + }, + }, +}); + const renderAddPeopleToTagModal = ( props: InterfaceAddPeopleToTagProps, link: ApolloLink, ): RenderResult => { return render( - + @@ -91,7 +118,7 @@ describe('Organisation Tags Page', () => { ...jest.requireActual('react-router-dom'), useParams: () => ({ orgId: 'orgId' }), })); - // cache.reset(); + cache.reset(); }); afterEach(() => { @@ -147,6 +174,72 @@ describe('Organisation Tags Page', () => { userEvent.click(screen.getAllByTestId('deselectMemberBtn')[0]); }); + test('searchs for tags where the firstName matches the provided firstName search input', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.firstName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.firstName); + fireEvent.change(input, { target: { value: 'usersToAssignTo' } }); + + // should render the two users from the mock data + // where firstName starts with "usersToAssignTo" + await waitFor(() => { + const members = screen.getAllByTestId('memberName'); + expect(members.length).toEqual(2); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'usersToAssignTo user1', + ); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[1]).toHaveTextContent( + 'usersToAssignTo user2', + ); + }); + }); + + test('searchs for tags where the lastName matches the provided lastName search input', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.lastName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.lastName); + fireEvent.change(input, { target: { value: 'userToAssignTo' } }); + + // should render the two users from the mock data + // where lastName starts with "usersToAssignTo" + await waitFor(() => { + const members = screen.getAllByTestId('memberName'); + expect(members.length).toEqual(2); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'first userToAssignTo', + ); + }); + + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[1]).toHaveTextContent( + 'second userToAssignTo', + ); + }); + }); + test('Renders more members with infinite scroll', async () => { const { getByText } = renderAddPeopleToTagModal(props, link); diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index e8dcd279af..ec0d779f7a 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -99,7 +99,7 @@ const AddPeopleToTag: React.FC = ({ }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { getUsersToAssignTo: { @@ -263,7 +263,7 @@ const AddPeopleToTag: React.FC = ({ className={`d-flex flex-wrap align-items-center border border-2 border-dark-subtle bg-light-subtle rounded-3 p-2 ${styles.scrollContainer}`} > {assignToMembers.length === 0 ? ( -
+
{t('noOneSelected')}
) : ( @@ -294,9 +294,8 @@ const AddPeopleToTag: React.FC = ({ onChange={(e) => setMemberToAssignToSearchFirstName(e.target.value.trim()) } - data-testid="searchByName" + data-testid="searchByFirstName" autoComplete="off" - required />
@@ -309,9 +308,8 @@ const AddPeopleToTag: React.FC = ({ onChange={(e) => setMemberToAssignToSearchLastName(e.target.value.trim()) } - data-testid="searchByName" + data-testid="searchByLastName" autoComplete="off" - required />
diff --git a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts index 223fcd3064..fbaf812186 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts +++ b/src/components/AddPeopleToTag/AddPeopleToTagsMocks.ts @@ -9,6 +9,10 @@ export const MOCKS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, }, }, result: { @@ -117,6 +121,10 @@ export const MOCKS = [ id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, after: '10', + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, }, }, result: { @@ -154,6 +162,100 @@ export const MOCKS = [ }, }, }, + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: 'usersToAssignTo' }, + lastName: { starts_with: '' }, + }, + }, + }, + result: { + data: { + getUsersToAssignTo: { + name: 'tag1', + usersToAssignTo: { + edges: [ + { + node: { + _id: '1', + firstName: 'usersToAssignTo', + lastName: 'user1', + }, + cursor: '1', + }, + { + node: { + _id: '2', + firstName: 'usersToAssignTo', + lastName: 'user2', + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + }, + }, + }, + { + request: { + query: USER_TAGS_MEMBERS_TO_ASSIGN_TO, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: 'userToAssignTo' }, + }, + }, + }, + result: { + data: { + getUsersToAssignTo: { + name: 'tag1', + usersToAssignTo: { + edges: [ + { + node: { + _id: '1', + firstName: 'first', + lastName: 'userToAssignTo', + }, + cursor: '1', + }, + { + node: { + _id: '2', + firstName: 'second', + lastName: 'userToAssignTo', + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + }, + }, + }, { request: { query: ADD_PEOPLE_TO_TAG, @@ -179,6 +281,10 @@ export const MOCKS_ERROR = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/components/TagActions/TagActions.module.css b/src/components/TagActions/TagActions.module.css index 62c5855981..079dffea65 100644 --- a/src/components/TagActions/TagActions.module.css +++ b/src/components/TagActions/TagActions.module.css @@ -17,9 +17,8 @@ } .scrollContainer { - max-height: 100px; + height: 100px; overflow-y: auto; - margin-bottom: 1rem; } .tagBadge { @@ -34,3 +33,10 @@ .removeFilterIcon { cursor: pointer; } + +.loadingDiv { + min-height: 300px; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 140504d044..da6e6528d9 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -199,6 +199,27 @@ describe('Organisation Tags Page', () => { }); }); + test('searchs for tags where the name matches the provided search input', async () => { + renderTagActionsModal(props[0], link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchUserTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + const tags = screen.getAllByTestId('orgUserTag'); + expect(tags.length).toEqual(2); + }); + }); + test('Renders more members with infinite scroll', async () => { const { getByText } = renderTagActionsModal(props[0], link); diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index aec6ccc494..1c954c4b0d 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -1,5 +1,4 @@ import { useMutation, useQuery } from '@apollo/client'; -import Loader from 'components/Loader/Loader'; import type { FormEvent } from 'react'; import React, { useEffect, useState } from 'react'; import { Modal, Form, Button } from 'react-bootstrap'; @@ -51,6 +50,8 @@ const TagActions: React.FC = ({ }) => { const { orgId, tagId: currentTagId } = useParams(); + const [tagSearchName, setTagSearchName] = useState(''); + const { data: orgUserTagsData, loading: orgUserTagsLoading, @@ -60,6 +61,7 @@ const TagActions: React.FC = ({ variables: { id: orgId, first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: tagSearchName } }, }, skip: !tagActionsModalIsOpen, }); @@ -80,7 +82,7 @@ const TagActions: React.FC = ({ }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { organizations: [ @@ -275,44 +277,58 @@ const TagActions: React.FC = ({ +
+ {selectedTags.length === 0 ? ( +
+ {t('noTagSelected')} +
+ ) : ( + selectedTags.map((tag: InterfaceTagData) => ( +
+ {tag.name} +
+ )) + )} +
+ +
+ + setTagSearchName(e.target.value.trim())} + data-testid="searchByName" + autoComplete="off" + /> +
+ +
+ {t('allTags')} +
{orgUserTagsLoading ? ( - +
+ +
) : ( <> -
- {selectedTags.length === 0 ? ( -
- {t('noTagSelected')} -
- ) : ( - selectedTags.map((tag: InterfaceTagData) => ( -
- {tag.name} -
- )) - )} -
- -
- {t('allTags')} -
-
@@ -327,17 +343,36 @@ const TagActions: React.FC = ({ scrollableTarget="scrollableDiv" > {userTagsList?.map((tag) => ( -
- +
+
+ +
+ + {/* Ancestor tags breadcrumbs positioned at the end of TagNode */} + {tag.parentTag && ( +
+ <>{'('} + {tag.ancestorTags?.map((ancestorTag) => ( + + {ancestorTag.name} + + + ))} + <>{')'} +
+ )}
))} diff --git a/src/components/TagActions/TagActionsMocks.ts b/src/components/TagActions/TagActionsMocks.ts index 20ae3e5349..f22458fa53 100644 --- a/src/components/TagActions/TagActionsMocks.ts +++ b/src/components/TagActions/TagActionsMocks.ts @@ -13,6 +13,7 @@ export const MOCKS = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, }, }, result: { @@ -192,6 +193,7 @@ export const MOCKS = [ id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, after: '10', + where: { name: { starts_with: '' } }, }, }, result: { @@ -244,6 +246,79 @@ export const MOCKS = [ }, }, }, + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchUserTag' } }, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: '1', + name: 'searchUserTag 1', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: '1', + }, + { + node: { + _id: '2', + name: 'searchUserTag 2', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + ], + }, + }, + }, { request: { query: USER_TAG_SUB_TAGS, @@ -551,6 +626,7 @@ export const MOCKS_ERROR_ORGANIZATION_TAGS_QUERY = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, }, }, error: new Error('Mock Graphql Error for organization root tags query'), @@ -564,6 +640,7 @@ export const MOCKS_ERROR_SUBTAGS_QUERY = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, }, }, result: { diff --git a/src/components/TagActions/TagNode.tsx b/src/components/TagActions/TagNode.tsx index e05d572f30..4a085ecaf9 100644 --- a/src/components/TagActions/TagNode.tsx +++ b/src/components/TagActions/TagNode.tsx @@ -61,7 +61,7 @@ const TagNode: React.FC = ({ fetchMoreResult?: { getChildTags: InterfaceQueryUserTagChildTags }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { getChildTags: { diff --git a/src/screens/ManageTag/ManageTag.test.tsx b/src/screens/ManageTag/ManageTag.test.tsx index c660e441db..598a15cc9a 100644 --- a/src/screens/ManageTag/ManageTag.test.tsx +++ b/src/screens/ManageTag/ManageTag.test.tsx @@ -324,6 +324,84 @@ describe('Manage Tag Page', () => { }); }); + test('searchs for tags where the name matches the provided search input', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'assigned user' } }); + + // should render the two users from the mock data + // where firstName starts with "assigned" and lastName starts with "user" + await waitFor(() => { + const buttons = screen.getAllByTestId('viewProfileBtn'); + expect(buttons.length).toEqual(2); + }); + }); + + test('fetches the tags by the sort order, i.e. latest or oldest first', async () => { + renderManageTag(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'assigned user' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'assigned user1', + ); + }); + + // now change the sorting order + await waitFor(() => { + expect(screen.getByTestId('sortPeople')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortPeople')); + + await waitFor(() => { + expect(screen.getByTestId('oldest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('oldest')); + + // returns the tags in reverse order + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'assigned user2', + ); + }); + + await waitFor(() => { + expect(screen.getByTestId('sortPeople')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortPeople')); + + await waitFor(() => { + expect(screen.getByTestId('latest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('latest')); + + // reverse the order again + await waitFor(() => { + expect(screen.getAllByTestId('memberName')[0]).toHaveTextContent( + 'assigned user1', + ); + }); + }); + test('Fetches more assigned members with infinite scroll', async () => { const { getByText } = renderManageTag(link); diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 2c6892d4f8..39e97e2b76 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -142,7 +142,7 @@ function ManageTag(): JSX.Element { }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { getAssignedUsers: { @@ -378,7 +378,6 @@ function ManageTag(): JSX.Element { } data-testid="searchByName" autoComplete="off" - required />
diff --git a/src/screens/ManageTag/ManageTagMocks.ts b/src/screens/ManageTag/ManageTagMocks.ts index b7ef4cef46..5ce1e62595 100644 --- a/src/screens/ManageTag/ManageTagMocks.ts +++ b/src/screens/ManageTag/ManageTagMocks.ts @@ -13,6 +13,11 @@ export const MOCKS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -122,6 +127,11 @@ export const MOCKS = [ id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, after: '10', + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -160,6 +170,104 @@ export const MOCKS = [ }, }, }, + { + request: { + query: USER_TAGS_ASSIGNED_MEMBERS, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: 'assigned' }, + lastName: { starts_with: 'user' }, + }, + sortedBy: { id: 'DESCENDING' }, + }, + }, + result: { + data: { + getAssignedUsers: { + name: 'tag1', + usersAssignedTo: { + edges: [ + { + node: { + _id: '1', + firstName: 'assigned', + lastName: 'user1', + }, + cursor: '1', + }, + { + node: { + _id: '2', + firstName: 'assigned', + lastName: 'user2', + }, + cursor: '2', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + ancestorTags: [], + }, + }, + }, + }, + { + request: { + query: USER_TAGS_ASSIGNED_MEMBERS, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: 'assigned' }, + lastName: { starts_with: 'user' }, + }, + sortedBy: { id: 'ASCENDING' }, + }, + }, + result: { + data: { + getAssignedUsers: { + name: 'tag1', + usersAssignedTo: { + edges: [ + { + node: { + _id: '2', + firstName: 'assigned', + lastName: 'user2', + }, + cursor: '2', + }, + { + node: { + _id: '1', + firstName: 'assigned', + lastName: 'user1', + }, + cursor: '1', + }, + ], + pageInfo: { + startCursor: '2', + endCursor: '1', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + ancestorTags: [], + }, + }, + }, + }, { request: { query: UNASSIGN_USER_TAG, @@ -216,6 +324,11 @@ export const MOCKS_ERROR_ASSIGNED_MEMBERS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { + firstName: { starts_with: '' }, + lastName: { starts_with: '' }, + }, + sortedBy: { id: 'DESCENDING' }, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/screens/OrganizationTags/OrganizationTags.test.tsx b/src/screens/OrganizationTags/OrganizationTags.test.tsx index 1ab94ce35c..723eef3b88 100644 --- a/src/screens/OrganizationTags/OrganizationTags.test.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.test.tsx @@ -88,7 +88,7 @@ describe('Organisation Tags Page', () => { cleanup(); }); - test('Component loads correctly', async () => { + test('component loads correctly', async () => { const { getByText } = renderOrganizationTags(link); await wait(); @@ -160,7 +160,85 @@ describe('Organisation Tags Page', () => { }); }); - test('Fetches more tags with infinite scroll', async () => { + test('searchs for tags where the name matches the provided search input', async () => { + renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchUserTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + const buttons = screen.getAllByTestId('manageTagBtn'); + expect(buttons.length).toEqual(2); + }); + }); + + test('fetches the tags by the sort order, i.e. latest or oldest first', async () => { + renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchUserTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchUserTag 1', + ); + }); + + // now change the sorting order + await waitFor(() => { + expect(screen.getByTestId('sortTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortTags')); + + await waitFor(() => { + expect(screen.getByTestId('oldest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('oldest')); + + // returns the tags in reverse order + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchUserTag 2', + ); + }); + + await waitFor(() => { + expect(screen.getByTestId('sortTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortTags')); + + await waitFor(() => { + expect(screen.getByTestId('latest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('latest')); + + // reverse the order again + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchUserTag 1', + ); + }); + }); + + test('fetches more tags with infinite scroll', async () => { const { getByText } = renderOrganizationTags(link); await wait(); diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index d239bac7a3..27e8cff9c8 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -13,7 +13,10 @@ import Modal from 'react-bootstrap/Modal'; import Row from 'react-bootstrap/Row'; import { useTranslation } from 'react-i18next'; import { toast } from 'react-toastify'; -import type { InterfaceQueryOrganizationUserTags } from 'utils/interfaces'; +import type { + InterfaceQueryOrganizationUserTags, + InterfaceTagData, +} from 'utils/interfaces'; import styles from './OrganizationTags.module.css'; import { DataGrid } from '@mui/x-data-grid'; import type { @@ -84,6 +87,7 @@ function OrganizationTags(): JSX.Element { first: TAGS_QUERY_DATA_CHUNK_SIZE, after: orgUserTagsData?.organizations?.[0]?.userTags?.pageInfo?.endCursor ?? + /* istanbul ignore next */ null, }, updateQuery: ( @@ -96,7 +100,7 @@ function OrganizationTags(): JSX.Element { }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { organizations: [ @@ -192,16 +196,29 @@ function OrganizationTags(): JSX.Element { minWidth: 100, sortable: false, headerClassName: `${styles.tableHeader}`, - renderCell: (params: GridCellParams) => { + renderCell: (params: GridCellParams) => { return ( -
redirectToSubTags(params.row._id)} - > - {params.row.name} +
+ {params.row.parentTag && + params.row.ancestorTags?.map((tag) => ( +
+ {tag.name} + +
+ ))} - +
redirectToSubTags(params.row._id)} + > + {params.row.name} + +
); }, @@ -285,7 +302,6 @@ function OrganizationTags(): JSX.Element { data-testid="searchByName" onChange={(e) => setTagSearchName(e.target.value.trim())} autoComplete="off" - required />
diff --git a/src/screens/OrganizationTags/OrganizationTagsMocks.ts b/src/screens/OrganizationTags/OrganizationTagsMocks.ts index c3c54c138a..0fe48ca97f 100644 --- a/src/screens/OrganizationTags/OrganizationTagsMocks.ts +++ b/src/screens/OrganizationTags/OrganizationTagsMocks.ts @@ -9,6 +9,8 @@ export const MOCKS = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -188,6 +190,8 @@ export const MOCKS = [ id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, after: '10', + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -240,6 +244,154 @@ export const MOCKS = [ }, }, }, + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchUserTag' } }, + sortedBy: { id: 'DESCENDING' }, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: 'searchUserTag1', + name: 'searchUserTag 1', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchUserTag1', + }, + { + node: { + _id: 'searchUserTag2', + name: 'searchUserTag 2', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchUserTag2', + }, + ], + pageInfo: { + startCursor: 'searchUserTag1', + endCursor: 'searchUserTag2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + ], + }, + }, + }, + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchUserTag' } }, + sortedBy: { id: 'ASCENDING' }, + }, + }, + result: { + data: { + organizations: [ + { + userTags: { + edges: [ + { + node: { + _id: 'searchUserTag2', + name: 'searchUserTag 2', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 5, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchUserTag2', + }, + { + node: { + _id: 'searchUserTag1', + name: 'searchUserTag 1', + parentTag: { + _id: '1', + }, + usersAssignedTo: { + totalCount: 5, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchUserTag1', + }, + ], + pageInfo: { + startCursor: 'searchUserTag2', + endCursor: 'searchUserTag1', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + }, + ], + }, + }, + }, { request: { query: CREATE_USER_TAG, @@ -265,6 +417,8 @@ export const MOCKS_ERROR = [ variables: { id: '123', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, error: new Error('Mock Graphql Error'), diff --git a/src/screens/SubTags/SubTags.test.tsx b/src/screens/SubTags/SubTags.test.tsx index 6445d259e8..145d31109d 100644 --- a/src/screens/SubTags/SubTags.test.tsx +++ b/src/screens/SubTags/SubTags.test.tsx @@ -21,7 +21,7 @@ import { StaticMockLink } from 'utils/StaticMockLink'; import i18n from 'utils/i18nForTest'; import SubTags from './SubTags'; import { MOCKS, MOCKS_ERROR_SUB_TAGS } from './SubTagsMocks'; -import { type ApolloLink } from '@apollo/client'; +import { InMemoryCache, type ApolloLink } from '@apollo/client'; const translations = { ...JSON.parse( @@ -51,9 +51,36 @@ jest.mock('react-toastify', () => ({ }, })); +const cache = new InMemoryCache({ + typePolicies: { + Query: { + fields: { + getUserTag: { + merge(existing = {}, incoming) { + const merged = { + ...existing, + ...incoming, + childTags: { + ...existing.childTags, + ...incoming.childTags, + edges: [ + ...(existing.childTags?.edges || []), + ...(incoming.childTags?.edges || []), + ], + }, + }; + + return merged; + }, + }, + }, + }, + }, +}); + const renderSubTags = (link: ApolloLink): RenderResult => { return render( - + @@ -84,6 +111,7 @@ describe('Organisation Tags Page', () => { ...jest.requireActual('react-router-dom'), useParams: () => ({ orgId: 'orgId' }), })); + cache.reset(); }); afterEach(() => { @@ -208,6 +236,84 @@ describe('Organisation Tags Page', () => { }); }); + test('searchs for tags where the name matches the provided search input', async () => { + renderSubTags(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchSubTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + const buttons = screen.getAllByTestId('manageTagBtn'); + expect(buttons.length).toEqual(2); + }); + }); + + test('fetches the tags by the sort order, i.e. latest or oldest first', async () => { + renderSubTags(link); + + await wait(); + + await waitFor(() => { + expect( + screen.getByPlaceholderText(translations.searchByName), + ).toBeInTheDocument(); + }); + const input = screen.getByPlaceholderText(translations.searchByName); + fireEvent.change(input, { target: { value: 'searchSubTag' } }); + + // should render the two searched tags from the mock data + // where name starts with "searchUserTag" + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchSubTag 1', + ); + }); + + // now change the sorting order + await waitFor(() => { + expect(screen.getByTestId('sortTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortTags')); + + await waitFor(() => { + expect(screen.getByTestId('oldest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('oldest')); + + // returns the tags in reverse order + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchSubTag 2', + ); + }); + + await waitFor(() => { + expect(screen.getByTestId('sortTags')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('sortTags')); + + await waitFor(() => { + expect(screen.getByTestId('latest')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('latest')); + + // reverse the order again + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent( + 'searchSubTag 1', + ); + }); + }); + test('Fetches more sub tags with infinite scroll', async () => { const { getByText } = renderSubTags(link); diff --git a/src/screens/SubTags/SubTags.tsx b/src/screens/SubTags/SubTags.tsx index 7d49c69047..95aca87c5b 100644 --- a/src/screens/SubTags/SubTags.tsx +++ b/src/screens/SubTags/SubTags.tsx @@ -93,7 +93,7 @@ function SubTags(): JSX.Element { fetchMoreResult?: { getChildTags: InterfaceQueryUserTagChildTags }; }, ) => { - if (!fetchMoreResult) return prevResult; + if (!fetchMoreResult) /* istanbul ignore next */ return prevResult; return { getChildTags: { @@ -291,7 +291,6 @@ function SubTags(): JSX.Element { onChange={(e) => setTagSearchName(e.target.value.trim())} data-testid="searchByName" autoComplete="off" - required />
@@ -302,7 +301,7 @@ function SubTags(): JSX.Element { > {tCommon('sort')} diff --git a/src/screens/SubTags/SubTagsMocks.ts b/src/screens/SubTags/SubTagsMocks.ts index 250c13253d..5165ea3a53 100644 --- a/src/screens/SubTags/SubTagsMocks.ts +++ b/src/screens/SubTags/SubTagsMocks.ts @@ -9,6 +9,8 @@ export const MOCKS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -228,6 +230,8 @@ export const MOCKS = [ id: '1', after: '10', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -275,6 +279,8 @@ export const MOCKS = [ variables: { id: 'subTag1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, result: { @@ -325,6 +331,142 @@ export const MOCKS = [ }, }, }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchSubTag' } }, + sortedBy: { id: 'DESCENDING' }, + }, + }, + result: { + data: { + getChildTags: { + name: 'userTag 1', + childTags: { + edges: [ + { + node: { + _id: 'searchSubTag1', + name: 'searchSubTag 1', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchSubTag1', + }, + { + node: { + _id: 'searchSubTag2', + name: 'searchSubTag 2', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchSubTag2', + }, + ], + pageInfo: { + startCursor: 'searchSubTag1', + endCursor: 'searchSubTag2', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + ancestorTags: [], + }, + }, + }, + }, + { + request: { + query: USER_TAG_SUB_TAGS, + variables: { + id: '1', + first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: 'searchSubTag' } }, + sortedBy: { id: 'ASCENDING' }, + }, + }, + result: { + data: { + getChildTags: { + name: 'userTag 1', + childTags: { + edges: [ + { + node: { + _id: 'searchSubTag2', + name: 'searchSubTag 2', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchSubTag2', + }, + { + node: { + _id: 'searchSubTag1', + name: 'searchSubTag 1', + usersAssignedTo: { + totalCount: 0, + }, + childTags: { + totalCount: 0, + }, + ancestorTags: [ + { + _id: '1', + name: 'userTag 1', + }, + ], + }, + cursor: 'searchSubTag1', + }, + ], + pageInfo: { + startCursor: 'searchSubTag2', + endCursor: 'searchSubTag1', + hasNextPage: false, + hasPreviousPage: false, + }, + totalCount: 2, + }, + ancestorTags: [], + }, + }, + }, + }, { request: { query: CREATE_USER_TAG, @@ -351,6 +493,8 @@ export const MOCKS_ERROR_SUB_TAGS = [ variables: { id: '1', first: TAGS_QUERY_DATA_CHUNK_SIZE, + where: { name: { starts_with: '' } }, + sortedBy: { id: 'DESCENDING' }, }, }, error: new Error('Mock Graphql Error'), From cc56edfed4749eea1a6ad3d9f295a8c27a703fa3 Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 1 Nov 2024 21:10:14 +0530 Subject: [PATCH 45/52] error toast on no seletion tag/user --- src/components/AddPeopleToTag/AddPeopleToTag.test.tsx | 10 ++++++++++ src/components/AddPeopleToTag/AddPeopleToTag.tsx | 5 +++++ src/components/TagActions/TagActions.test.tsx | 10 ++++++++++ src/components/TagActions/TagActions.tsx | 5 +++++ 4 files changed, 30 insertions(+) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx index 99bdbd67e3..019f9f5d7a 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -274,6 +274,16 @@ describe('Organisation Tags Page', () => { await wait(); + // gives error if no user is selected + await waitFor(() => { + expect(screen.getByTestId('assignPeopleBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('assignPeopleBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith(translations.noOneSelected); + }); + await waitFor(() => { expect(screen.getAllByTestId('selectMemberBtn')[0]).toBeInTheDocument(); }); diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index ec0d779f7a..deeb1f7b65 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -147,6 +147,11 @@ const AddPeopleToTag: React.FC = ({ ): Promise => { e.preventDefault(); + if (!assignToMembers.length) { + toast.error(t('noOneSelected')); + return; + } + try { const { data } = await addPeople({ variables: { diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index da6e6528d9..416f642abd 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -343,6 +343,16 @@ describe('Organisation Tags Page', () => { await wait(); + // gives error if no tag is selected + await waitFor(() => { + expect(screen.getByTestId('tagActionSubmitBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('tagActionSubmitBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith(translations.noTagSelected); + }); + // select userTags 2 & 3 and assign them await waitFor(() => { expect(screen.getByTestId('checkTag2')).toBeInTheDocument(); diff --git a/src/components/TagActions/TagActions.tsx b/src/components/TagActions/TagActions.tsx index 1c954c4b0d..083341372f 100644 --- a/src/components/TagActions/TagActions.tsx +++ b/src/components/TagActions/TagActions.tsx @@ -213,6 +213,11 @@ const TagActions: React.FC = ({ ): Promise => { e.preventDefault(); + if (!selectedTags.length) { + toast.error(t('noTagSelected')); + return; + } + const mutationObject = { variables: { currentTagId, From 3742f1d94ddfcf2e45733fd52d9fcfdbc1e7358a Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 1 Nov 2024 21:44:30 +0530 Subject: [PATCH 46/52] minor correction --- src/screens/OrganizationTags/OrganizationTags.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index 27e8cff9c8..4d46040942 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -435,7 +435,7 @@ function OrganizationTags(): JSX.Element { required value={tagName} onChange={(e): void => { - setTagName(e.target.value); + setTagName(e.target.value.trim()); }} /> From ae1afea347250d0dc0ae61c8727ecbb8754ec464 Mon Sep 17 00:00:00 2001 From: meetul Date: Fri, 1 Nov 2024 22:38:35 +0530 Subject: [PATCH 47/52] fix test --- public/locales/en/translation.json | 3 ++- public/locales/fr/translation.json | 3 ++- public/locales/hi/translation.json | 3 ++- public/locales/sp/translation.json | 3 ++- public/locales/zh/translation.json | 3 ++- src/screens/OrganizationTags/OrganizationTags.test.tsx | 6 ++++++ src/screens/OrganizationTags/OrganizationTags.tsx | 9 +++++++-- 7 files changed, 23 insertions(+), 7 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 7d2108bc06..074b339b71 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -320,7 +320,8 @@ "noTagsFound": "No tags found", "removeUserTag": "Delete Tag", "removeUserTagMessage": "Do you want to delete this tag?", - "addChildTag": "Add a Sub Tag" + "addChildTag": "Add a Sub Tag", + "enterTagName": "Enter Tag Name" }, "manageTag": { "title": "Tag Details", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 7f74826272..082a098460 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -320,7 +320,8 @@ "noTagsFound": "Aucune étiquette trouvée", "removeUserTag": "Supprimer l'Étiquette", "removeUserTagMessage": "Voulez-vous supprimer cette étiquette ?", - "addChildTag": "Ajouter une Sous-Étiquette" + "addChildTag": "Ajouter une Sous-Étiquette", + "enterTagName": "Entrez le nom du étiquette" }, "manageTag": { "title": "Détails de l'étiquette", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 4384648ca3..20c0e11a4a 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -320,7 +320,8 @@ "noTagsFound": "कोई टैग नहीं मिला", "removeUserTag": "टैग हटाएँ", "removeUserTagMessage": "क्या आप इस टैग को हटाना चाहते हैं?", - "addChildTag": "उप-टैग जोड़ें" + "addChildTag": "उप-टैग जोड़ें", + "enterTagName": "टैग का नाम दर्ज करें" }, "manageTag": { "title": "टैग विवरण", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 24ce0dbdec..12fdbe3b1c 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -320,7 +320,8 @@ "noTagsFound": "No se encontraron etiquetas", "removeUserTag": "Eliminar Etiqueta", "removeUserTagMessage": "¿Desea eliminar esta etiqueta?", - "addChildTag": "Agregar una Sub Etiqueta" + "addChildTag": "Agregar una Sub Etiqueta", + "enterTagName": "Ingrese el nombre de la etiqueta" }, "manageTag": { "title": "Detalles de la Etiqueta", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 0c070bcf8b..b5a61aa3af 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -320,7 +320,8 @@ "noTagsFound": "未找到标签", "removeUserTag": "删除标签", "removeUserTagMessage": "您确定要删除此标签吗?", - "addChildTag": "添加子标签" + "addChildTag": "添加子标签", + "enterTagName": "输入标签名称" }, "manageTag": { "title": "标签详情", diff --git a/src/screens/OrganizationTags/OrganizationTags.test.tsx b/src/screens/OrganizationTags/OrganizationTags.test.tsx index 723eef3b88..0d426d20ac 100644 --- a/src/screens/OrganizationTags/OrganizationTags.test.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.test.tsx @@ -277,6 +277,12 @@ describe('Organisation Tags Page', () => { }); userEvent.click(screen.getByTestId('createTagBtn')); + userEvent.click(screen.getByTestId('createTagSubmitBtn')); + + await waitFor(() => { + expect(toast.error).toHaveBeenCalledWith(translations.enterTagName); + }); + userEvent.type( screen.getByPlaceholderText(translations.tagNamePlaceholder), 'userTag 12', diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index 4d46040942..93838c25bc 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -127,6 +127,11 @@ function OrganizationTags(): JSX.Element { const createTag = async (e: ChangeEvent): Promise => { e.preventDefault(); + if (!tagName.trim()) { + toast.error(t('enterTagName')); + return; + } + try { const { data } = await create({ variables: { @@ -136,7 +141,7 @@ function OrganizationTags(): JSX.Element { }); if (data) { - toast.success(t('tagCreationSuccess') as string); + toast.success(t('tagCreationSuccess')); orgUserTagsRefetch(); setTagName(''); setCreateTagModalIsOpen(false); @@ -435,7 +440,7 @@ function OrganizationTags(): JSX.Element { required value={tagName} onChange={(e): void => { - setTagName(e.target.value.trim()); + setTagName(e.target.value); }} /> From ba50762560751bf09904f581388406b40cfc2ecf Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 2 Nov 2024 08:01:08 +0530 Subject: [PATCH 48/52] minor change --- src/screens/ManageTag/ManageTag.tsx | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 39e97e2b76..14183fc0c6 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -41,10 +41,7 @@ import RemoveUserTagModal from './RemoveUserTagModal'; import UnassignUserTagModal from './UnassignUserTagModal'; /** - * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/managetag/:tagId'. - * - * This component does not accept any props and is responsible for displaying - * the content associated with the corresponding route. + * Component that renders the Manage Tag screen when the app navigates to '/orgtags/:orgId/manageTag/:tagId'. */ function ManageTag(): JSX.Element { @@ -52,6 +49,8 @@ function ManageTag(): JSX.Element { keyPrefix: 'manageTag', }); const { t: tCommon } = useTranslation('common'); + const { orgId, tagId: currentTagId } = useParams(); + const navigate = useNavigate(); const [unassignUserTagModalIsOpen, setUnassignUserTagModalIsOpen] = useState(false); @@ -61,21 +60,15 @@ function ManageTag(): JSX.Element { const [editUserTagModalIsOpen, setEditUserTagModalIsOpen] = useState(false); const [removeUserTagModalIsOpen, setRemoveUserTagModalIsOpen] = useState(false); - - const { orgId, tagId: currentTagId } = useParams(); - const navigate = useNavigate(); const [unassignUserId, setUnassignUserId] = useState(null); - const [assignedMemberSearchInput, setAssignedMemberSearchInput] = useState(''); const [assignedMemberSearchFirstName, setAssignedMemberSearchFirstName] = useState(''); const [assignedMemberSearchLastName, setAssignedMemberSearchLastName] = useState(''); - const [assignedMemberSortOrder, setAssignedMemberSortOrder] = useState('DESCENDING'); - // a state to specify whether we're assigning to tags or removing from tags const [tagActionType, setTagActionType] = useState('assignToTags'); @@ -83,21 +76,18 @@ function ManageTag(): JSX.Element { const toggleRemoveUserTagModal = (): void => { setRemoveUserTagModalIsOpen(!removeUserTagModalIsOpen); }; - const showAddPeopleToTagModal = (): void => { setAddPeopleToTagModalIsOpen(true); }; const hideAddPeopleToTagModal = (): void => { setAddPeopleToTagModalIsOpen(false); }; - const showTagActionsModal = (): void => { setTagActionsModalIsOpen(true); }; const hideTagActionsModal = (): void => { setTagActionsModalIsOpen(false); }; - const showEditUserTagModal = (): void => { setEditUserTagModalIsOpen(true); }; @@ -286,7 +276,6 @@ function ManageTag(): JSX.Element { const redirectToManageTag = (tagId: string): void => { navigate(`/orgtags/${orgId}/manageTag/${tagId}`); }; - const toggleUnassignUserTagModal = (): void => { if (unassignUserTagModalIsOpen) { setUnassignUserId(null); From cbe81df74809068e11a92f0b881f855e5f4beb5d Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 2 Nov 2024 08:37:56 +0530 Subject: [PATCH 49/52] add useEffect --- src/components/AddPeopleToTag/AddPeopleToTag.tsx | 9 ++++++++- src/screens/OrganizationTags/OrganizationTags.tsx | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index deeb1f7b65..6c8e966cf5 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -3,7 +3,7 @@ import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; import { DataGrid } from '@mui/x-data-grid'; import { USER_TAGS_MEMBERS_TO_ASSIGN_TO } from 'GraphQl/Queries/userTagQueries'; import type { ChangeEvent } from 'react'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Modal, Form, Button } from 'react-bootstrap'; import { useParams } from 'react-router-dom'; import type { InterfaceQueryUserTagsMembersToAssignTo } from 'utils/interfaces'; @@ -63,6 +63,7 @@ const AddPeopleToTag: React.FC = ({ data: userTagsMembersToAssignToData, loading: userTagsMembersToAssignToLoading, error: userTagsMembersToAssignToError, + refetch: userTagsMembersToAssignToRefetch, fetchMore: fetchMoreMembersToAssignTo, }: InterfaceTagUsersToAssignToQuery = useQuery( USER_TAGS_MEMBERS_TO_ASSIGN_TO, @@ -79,6 +80,12 @@ const AddPeopleToTag: React.FC = ({ }, ); + useEffect(() => { + setMemberToAssignToSearchFirstName(''); + setMemberToAssignToSearchLastName(''); + userTagsMembersToAssignToRefetch(); + }, [addPeopleToTagModalIsOpen]); + const loadMoreMembersToAssignTo = (): void => { fetchMoreMembersToAssignTo({ variables: { diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx index 93838c25bc..b8de059235 100644 --- a/src/screens/OrganizationTags/OrganizationTags.tsx +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -5,7 +5,7 @@ import Loader from 'components/Loader/Loader'; import IconComponent from 'components/IconComponent/IconComponent'; import { useNavigate, useParams, Link } from 'react-router-dom'; import type { ChangeEvent } from 'react'; -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { Form } from 'react-bootstrap'; import Button from 'react-bootstrap/Button'; import Dropdown from 'react-bootstrap/Dropdown'; @@ -121,6 +121,10 @@ function OrganizationTags(): JSX.Element { }); }; + useEffect(() => { + orgUserTagsRefetch(); + }, []); + const [create, { loading: createUserTagLoading }] = useMutation(CREATE_USER_TAG); From 23fea573b1bfa8addc8d8b1581bf8ed0d45010a5 Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 2 Nov 2024 08:59:40 +0530 Subject: [PATCH 50/52] exclude TagActionsMocks from countlines check --- .github/workflows/pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index a0a542f3cf..27f18e28ab 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -38,7 +38,7 @@ jobs: - name: Count number of lines run: | chmod +x ./.github/workflows/countline.py - ./.github/workflows/countline.py --lines 600 --exclude_files src/screens/LoginPage/LoginPage.tsx src/GraphQl/Queries/Queries.ts src/screens/OrgList/OrgList.tsx src/GraphQl/Mutations/mutations.ts src/components/EventListCard/EventListCardModals.tsx + ./.github/workflows/countline.py --lines 600 --exclude_files src/screens/LoginPage/LoginPage.tsx src/GraphQl/Queries/Queries.ts src/screens/OrgList/OrgList.tsx src/GraphQl/Mutations/mutations.ts src/components/EventListCard/EventListCardModals.tsx src/components/TagActions/TagActionsMocks.ts - name: Get changed TypeScript files id: changed-files From 3d801caf7a329bc7fcf464ec8af07e8a4661ed6a Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 2 Nov 2024 13:12:18 +0530 Subject: [PATCH 51/52] coderabbitai suggested changes --- public/locales/fr/translation.json | 2 +- src/components/AddPeopleToTag/AddPeopleToTag.test.tsx | 10 ++++++++-- src/components/AddPeopleToTag/AddPeopleToTag.tsx | 4 ++-- src/components/TagActions/TagActions.test.tsx | 9 +++++++-- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 082a098460..8129ca3346 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -321,7 +321,7 @@ "removeUserTag": "Supprimer l'Étiquette", "removeUserTagMessage": "Voulez-vous supprimer cette étiquette ?", "addChildTag": "Ajouter une Sous-Étiquette", - "enterTagName": "Entrez le nom du étiquette" + "enterTagName": "Entrez le nom de l'étiquette" }, "manageTag": { "title": "Détails de l'étiquette", diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx index 019f9f5d7a..824bc25654 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.test.tsx @@ -269,12 +269,11 @@ describe('Organisation Tags Page', () => { }); }); - test('Assigns tag to multiple people', async () => { + test('Toasts error when no one is selected while assigning', async () => { renderAddPeopleToTagModal(props, link); await wait(); - // gives error if no user is selected await waitFor(() => { expect(screen.getByTestId('assignPeopleBtn')).toBeInTheDocument(); }); @@ -283,7 +282,14 @@ describe('Organisation Tags Page', () => { await waitFor(() => { expect(toast.error).toHaveBeenCalledWith(translations.noOneSelected); }); + }); + + test('Assigns tag to multiple people', async () => { + renderAddPeopleToTagModal(props, link); + + await wait(); + // select members and assign them await waitFor(() => { expect(screen.getAllByTestId('selectMemberBtn')[0]).toBeInTheDocument(); }); diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index 6c8e966cf5..73b384888e 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -300,7 +300,7 @@ const AddPeopleToTag: React.FC = ({ @@ -314,7 +314,7 @@ const AddPeopleToTag: React.FC = ({ diff --git a/src/components/TagActions/TagActions.test.tsx b/src/components/TagActions/TagActions.test.tsx index 416f642abd..d27f177ebe 100644 --- a/src/components/TagActions/TagActions.test.tsx +++ b/src/components/TagActions/TagActions.test.tsx @@ -338,12 +338,11 @@ describe('Organisation Tags Page', () => { userEvent.click(screen.getByTestId('expandSubTags1')); }); - test('Successfully assigns to tags', async () => { + test('Toasts error when no tag is selected while assigning', async () => { renderTagActionsModal(props[0], link); await wait(); - // gives error if no tag is selected await waitFor(() => { expect(screen.getByTestId('tagActionSubmitBtn')).toBeInTheDocument(); }); @@ -352,6 +351,12 @@ describe('Organisation Tags Page', () => { await waitFor(() => { expect(toast.error).toHaveBeenCalledWith(translations.noTagSelected); }); + }); + + test('Successfully assigns to tags', async () => { + renderTagActionsModal(props[0], link); + + await wait(); // select userTags 2 & 3 and assign them await waitFor(() => { From 5085b7f7c4eee93b2434010490337120d896b7c3 Mon Sep 17 00:00:00 2001 From: meetul Date: Sat, 2 Nov 2024 13:51:53 +0530 Subject: [PATCH 52/52] show sort order --- public/locales/en/translation.json | 4 ++-- public/locales/fr/translation.json | 4 ++-- public/locales/hi/translation.json | 4 ++-- public/locales/sp/translation.json | 4 ++-- public/locales/zh/translation.json | 4 ++-- src/components/AddPeopleToTag/AddPeopleToTag.tsx | 2 +- src/screens/ManageTag/ManageTag.tsx | 4 +++- src/screens/OrganizationTags/OrganizationTags.tsx | 4 +++- src/screens/SubTags/SubTags.tsx | 4 +++- 9 files changed, 20 insertions(+), 14 deletions(-) diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 074b339b71..55865167d5 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -334,7 +334,6 @@ "addPeople": "Add People", "add": "Add", "subTags": "Sub Tags", - "assignedToAll": "Tag Assigned to All", "successfullyAssignedToPeople": "Tag assigned successfully", "errorOccurredWhileLoadingMembers": "Error occured while loading members", "userName": "User Name", @@ -360,7 +359,8 @@ "collapse": "Collapse", "expand": "Expand", "tagNamePlaceholder": "Write the name of the tag", - "allTags": "All Tags" + "allTags": "All Tags", + "noMoreMembersFound": "No more members found" }, "userListCard": { "addAdmin": "Add Admin", diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 8129ca3346..8bdb3e854a 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -334,7 +334,6 @@ "addPeople": "Ajouter des personnes", "add": "Ajouter", "subTags": "Sous-étiquettes", - "assignedToAll": "Étiquette attribuée à tous", "successfullyAssignedToPeople": "Étiquette attribuée avec succès", "errorOccurredWhileLoadingMembers": "Erreur survenue lors du chargement des membres", "userName": "Nom d'utilisateur", @@ -360,7 +359,8 @@ "collapse": "Réduire", "expand": "Développer", "tagNamePlaceholder": "Écrire le nom de l'étiquette", - "allTags": "Toutes les étiquettes" + "allTags": "Toutes les étiquettes", + "noMoreMembersFound": "Aucun autre membre trouvé" }, "userListCard": { "addAdmin": "Ajouter un administrateur", diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 20c0e11a4a..b3b00bd989 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -334,7 +334,6 @@ "addPeople": "लोगों को जोड़ें", "add": "जोड़ें", "subTags": "उप-टैग्स", - "assignedToAll": "सभी को टैग असाइन किया गया", "successfullyAssignedToPeople": "टैग सफलतापूर्वक असाइन किया गया", "errorOccurredWhileLoadingMembers": "सदस्यों को लोड करते समय त्रुटि हुई", "userName": "उपयोगकर्ता नाम", @@ -360,7 +359,8 @@ "collapse": "संक्षिप्त करें", "expand": "विस्तारित करें", "tagNamePlaceholder": "टैग का नाम लिखें", - "allTags": "सभी टैग" + "allTags": "सभी टैग", + "noMoreMembersFound": "कोई और सदस्य नहीं मिला" }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 12fdbe3b1c..f698238e56 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -334,7 +334,6 @@ "addPeople": "Agregar Personas", "add": "Agregar", "subTags": "Subetiquetas", - "assignedToAll": "Etiqueta asignada a todos", "successfullyAssignedToPeople": "Etiqueta asignada con éxito", "errorOccurredWhileLoadingMembers": "Error al cargar los miembros", "userName": "Nombre de usuario", @@ -360,7 +359,8 @@ "collapse": "Colapsar", "expand": "Expandir", "tagNamePlaceholder": "Escribe el nombre de la etiqueta", - "allTags": "Todas las etiquetas" + "allTags": "Todas las etiquetas", + "noMoreMembersFound": "No se encontraron más miembros" }, "userListCard": { "joined": "Unido", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index b5a61aa3af..5d8cfc4826 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -334,7 +334,6 @@ "addPeople": "添加人员", "add": "添加", "subTags": "子标签", - "assignedToAll": "标签分配给所有人", "successfullyAssignedToPeople": "标签分配成功", "errorOccurredWhileLoadingMembers": "加载成员时出错", "userName": "用户名", @@ -360,7 +359,8 @@ "collapse": "收起", "expand": "展开", "tagNamePlaceholder": "输入标签名称", - "allTags": "所有标签" + "allTags": "所有标签", + "noMoreMembersFound": "未找到更多成员" }, "userListCard": { "addAdmin": "添加管理员", diff --git a/src/components/AddPeopleToTag/AddPeopleToTag.tsx b/src/components/AddPeopleToTag/AddPeopleToTag.tsx index 73b384888e..51d21a6a1c 100644 --- a/src/components/AddPeopleToTag/AddPeopleToTag.tsx +++ b/src/components/AddPeopleToTag/AddPeopleToTag.tsx @@ -363,7 +363,7 @@ const AddPeopleToTag: React.FC = ({ alignItems="center" justifyContent="center" > - {t('assignedToAll')} + {t('noMoreMembersFound')} ), }} diff --git a/src/screens/ManageTag/ManageTag.tsx b/src/screens/ManageTag/ManageTag.tsx index 14183fc0c6..e8eb9bb8df 100644 --- a/src/screens/ManageTag/ManageTag.tsx +++ b/src/screens/ManageTag/ManageTag.tsx @@ -380,7 +380,9 @@ function ManageTag(): JSX.Element { data-testid="sortPeople" > - {tCommon('sort')} + {assignedMemberSortOrder === 'DESCENDING' + ? tCommon('Latest') + : tCommon('Oldest')} - {tCommon('sort')} + {tagSortOrder === 'DESCENDING' + ? tCommon('Latest') + : tCommon('Oldest')} - {tCommon('sort')} + {tagSortOrder === 'DESCENDING' + ? tCommon('Latest') + : tCommon('Oldest')}