diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index ca136a906e..58b03bae2b 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -200,6 +200,24 @@ "organization": "Organization", "invalidDetailsMessage": "Please enter valid details." }, + "organizationTags": { + "title": "Organization Tags", + "createTag": "Create a new tag", + "manageTag": "Manage", + "editTag": "Edit", + "removeTag": "Remove", + "tagDetails": "Tag Details", + "tagName": "Name", + "tagType": "Type", + "tagNamePlaceholder": "Write the name of the tag", + "tagCreationSuccess": "New tag created successfully", + "tagUpdationSuccess": "Tag updated successfully", + "tagRemovalSuccess": "Tag deleted successfully", + "noTagsFound": "No tags found", + "removeUserTag": "Delete Tag", + "removeUserTagMessage": "Do you want to delete this tag?", + "addChildTag": "Add a Sub Tag" + }, "userListCard": { "addAdmin": "Add Admin", "addedAsAdmin": "User is added as admin." diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json index 78903d160d..f047e41541 100644 --- a/public/locales/fr/translation.json +++ b/public/locales/fr/translation.json @@ -200,6 +200,24 @@ "organization": "Organisation", "invalidDetailsMessage": "Veuillez saisir des informations valides." }, + "organizationTags": { + "title": "Étiquettes d'Organisation", + "createTag": "Créer une nouvelle étiquette", + "manageTag": "Gérer", + "editTag": "Modifier", + "removeTag": "Supprimer", + "tagDetails": "Détails de l'Étiquette", + "tagName": "Nom", + "tagType": "Type", + "tagNamePlaceholder": "Écrire le nom de l'étiquette", + "tagCreationSuccess": "Nouvelle étiquette créée avec succès", + "tagUpdationSuccess": "Étiquette mise à jour avec succès", + "tagRemovalSuccess": "Étiquette supprimée avec succès", + "noTagsFound": "Aucune étiquette trouvée", + "removeUserTag": "Supprimer l'Étiquette", + "removeUserTagMessage": "Voulez-vous supprimer cette étiquette ?", + "addChildTag": "Ajouter une Sous-Étiquette" + }, "userListCard": { "addAdmin": "Ajouter un administrateur", "addedAsAdmin": "L'utilisateur est ajouté en tant qu'administrateur." diff --git a/public/locales/hi/translation.json b/public/locales/hi/translation.json index 4bcb6ac04e..31033786a2 100644 --- a/public/locales/hi/translation.json +++ b/public/locales/hi/translation.json @@ -200,6 +200,24 @@ "organization": "संगठन", "invalidDetailsMessage": "कृपया वैध विवरण दर्ज करें." }, + "organizationTags": { + "title": "संस्थान टैग", + "createTag": "नया टैग बनाएँ", + "manageTag": "प्रबंधित करें", + "editTag": "संपादित करें", + "removeTag": "हटाएँ", + "tagDetails": "टैग विवरण", + "tagName": "नाम", + "tagType": "प्रकार", + "tagNamePlaceholder": "टैग का नाम लिखें", + "tagCreationSuccess": "नई टैग सफलतापूर्वक बनाई गई", + "tagUpdationSuccess": "टैग सफलतापूर्वक अपडेट की गई", + "tagRemovalSuccess": "टैग सफलतापूर्वक हटाई गई", + "noTagsFound": "कोई टैग नहीं मिला", + "removeUserTag": "टैग हटाएँ", + "removeUserTagMessage": "क्या आप इस टैग को हटाना चाहते हैं?", + "addChildTag": "उप-टैग जोड़ें" + }, "userListCard": { "addAdmin": "व्यवस्थापक जोड़ें", "addedAsAdmin": "उपयोगकर्ता को व्यवस्थापक के रूप में जोड़ा गया है." diff --git a/public/locales/sp/translation.json b/public/locales/sp/translation.json index 029d04ee5a..d6aa6b738a 100644 --- a/public/locales/sp/translation.json +++ b/public/locales/sp/translation.json @@ -294,6 +294,24 @@ "cancel": "Cancelar", "invalidDetailsMessage": "Ingrese detalles válidos." }, + "organizationTags": { + "title": "Etiquetas de Organización", + "createTag": "Crear una nueva etiqueta", + "manageTag": "Gestionar", + "editTag": "Editar", + "removeTag": "Eliminar", + "tagDetails": "Detalles de la Etiqueta", + "tagName": "Nombre", + "tagType": "Tipo", + "tagNamePlaceholder": "Escribe el nombre de la etiqueta", + "tagCreationSuccess": "Nueva etiqueta creada con éxito", + "tagUpdationSuccess": "Etiqueta actualizada con éxito", + "tagRemovalSuccess": "Etiqueta eliminada con éxito", + "noTagsFound": "No se encontraron etiquetas", + "removeUserTag": "Eliminar Etiqueta", + "removeUserTagMessage": "¿Desea eliminar esta etiqueta?", + "addChildTag": "Agregar una Sub Etiqueta" + }, "userListCard": { "joined": "Unido", "addAdmin": "Agregar administrador", diff --git a/public/locales/zh/translation.json b/public/locales/zh/translation.json index 02bc6f4850..b8d17ffd6e 100644 --- a/public/locales/zh/translation.json +++ b/public/locales/zh/translation.json @@ -200,6 +200,24 @@ "organization": "组织", "invalidDetailsMessage": "请输入有效的详细信息。" }, + "organizationTags": { + "title": "组织标签", + "createTag": "创建新标签", + "manageTag": "管理", + "editTag": "编辑", + "removeTag": "删除", + "tagDetails": "标签详情", + "tagName": "名称", + "tagType": "类型", + "tagNamePlaceholder": "输入标签名称", + "tagCreationSuccess": "新标签创建成功", + "tagUpdationSuccess": "标签更新成功", + "tagRemovalSuccess": "标签删除成功", + "noTagsFound": "未找到标签", + "removeUserTag": "删除标签", + "removeUserTagMessage": "您确定要删除此标签吗?", + "addChildTag": "添加子标签" + }, "userListCard": { "addAdmin": "添加管理员", "addedAsAdmin": "用户被添加为管理员。" diff --git a/src/App.tsx b/src/App.tsx index 7e63dda528..e4118a6f88 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,6 +20,9 @@ import OrganizationEvents from 'screens/OrganizationEvents/OrganizationEvents'; import OrganizaitionFundCampiagn from 'screens/OrganizationFundCampaign/OrganizationFundCampagins'; import OrganizationFunds from 'screens/OrganizationFunds/OrganizationFunds'; import OrganizationPeople from 'screens/OrganizationPeople/OrganizationPeople'; +import OrganizationTags from 'screens/OrganizationTags/OrganizationTags'; +// import OrganizationTagDetails from 'screens/OrgnanizationTagDetails/OrganizationTagDetails'; +// import OrganizationTagChildTags from 'screens/OrganizationTagChildTags/OrganizationTagChildTags'; import PageNotFound from 'screens/PageNotFound/PageNotFound'; import Requests from 'screens/Requests/Requests'; import Users from 'screens/Users/Users'; @@ -145,6 +148,15 @@ function app(): JSX.Element { } /> } /> } /> + } /> + {/* } + /> + } + /> */} } /> } /> + + + diff --git a/src/components/IconComponent/IconComponent.test.tsx b/src/components/IconComponent/IconComponent.test.tsx index eda6156b6b..4e31d9980d 100644 --- a/src/components/IconComponent/IconComponent.test.tsx +++ b/src/components/IconComponent/IconComponent.test.tsx @@ -15,6 +15,14 @@ const screenTestIdMap: Record> = { name: 'People', testId: 'Icon-Component-PeopleIcon', }, + Tags: { + name: 'Tags', + testId: 'Icon-Component-TagsIcon', + }, + Tag: { + name: 'Tag', + testId: 'Icon-Component-TagIcon', + }, Requests: { name: 'Requests', testId: 'Icon-Component-RequestsIcon', diff --git a/src/components/IconComponent/IconComponent.tsx b/src/components/IconComponent/IconComponent.tsx index d6e7064d00..7aeeacfad4 100644 --- a/src/components/IconComponent/IconComponent.tsx +++ b/src/components/IconComponent/IconComponent.tsx @@ -14,6 +14,8 @@ import { ReactComponent as FundsIcon } from 'assets/svgs/funds.svg'; import { ReactComponent as ListEventRegistrantsIcon } from 'assets/svgs/listEventRegistrants.svg'; import { ReactComponent as OrganizationsIcon } from 'assets/svgs/organizations.svg'; import { ReactComponent as PeopleIcon } from 'assets/svgs/people.svg'; +import { ReactComponent as TagsIcon } from 'assets/svgs/tags.svg'; +import { ReactComponent as TagIcon } from 'assets/svgs/tag.svg'; import { ReactComponent as PluginsIcon } from 'assets/svgs/plugins.svg'; import { ReactComponent as PostsIcon } from 'assets/svgs/posts.svg'; import { ReactComponent as SettingsIcon } from 'assets/svgs/settings.svg'; @@ -49,6 +51,10 @@ const iconComponent = (props: InterfaceIconComponent): JSX.Element => { ); case 'People': return ; + case 'Tags': + return ; + case 'Tag': + return ; case 'Requests': return ( diff --git a/src/components/OrganizationScreen/OrganizationScreen.tsx b/src/components/OrganizationScreen/OrganizationScreen.tsx index f0cd6ee2ad..e0781d996d 100644 --- a/src/components/OrganizationScreen/OrganizationScreen.tsx +++ b/src/components/OrganizationScreen/OrganizationScreen.tsx @@ -128,6 +128,7 @@ export default OrganizationScreen; const map: InterfaceMapType = { orgdash: 'dashboard', orgpeople: 'organizationPeople', + orgtags: 'organizationTags', requests: 'requests', orgads: 'advertisement', member: 'memberDetail', diff --git a/src/screens/OrganizationTags/OrganizationTags.module.css b/src/screens/OrganizationTags/OrganizationTags.module.css new file mode 100644 index 0000000000..185f65344a --- /dev/null +++ b/src/screens/OrganizationTags/OrganizationTags.module.css @@ -0,0 +1,136 @@ +.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: 1120px) { + .contract { + padding-left: calc(250px + 2rem + 1.5rem); + } + + .listBox .itemCard { + width: 100%; + } +} + +@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; +} + +.subTagsLink { + color: var(--bs-blue); + font-weight: 500; + cursor: pointer; +} + +.subTagsLink i { + visibility: hidden; +} + +.subTagsLink:hover { + font-weight: 600; + text-decoration: underline; +} + +.subTagsLink:hover i { + visibility: visible; +} diff --git a/src/screens/OrganizationTags/OrganizationTags.test.tsx b/src/screens/OrganizationTags/OrganizationTags.test.tsx new file mode 100644 index 0000000000..e4da2a3cc8 --- /dev/null +++ b/src/screens/OrganizationTags/OrganizationTags.test.tsx @@ -0,0 +1,231 @@ +import React from 'react'; +import { MockedProvider } from '@apollo/react-testing'; +import type { RenderResult } from '@testing-library/react'; +import { + act, + cleanup, + render, + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import 'jest-location-mock'; +import { I18nextProvider } from 'react-i18next'; +import { Provider } from 'react-redux'; +import { MemoryRouter, Route, Routes } from 'react-router-dom'; +import { toast } from 'react-toastify'; +import { store } from 'state/store'; +import { StaticMockLink } from 'utils/StaticMockLink'; +import i18n from 'utils/i18nForTest'; +import OrganizationTags from './OrganizationTags'; +import { MOCKS, MOCKS_ERROR } from './OrganizationTagsMocks'; +import type { ApolloLink } from '@apollo/client'; + +const translations = { + ...JSON.parse( + JSON.stringify( + i18n.getDataByLanguage('en')?.translation.organizationTags ?? {}, + ), + ), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.common ?? {})), + ...JSON.parse(JSON.stringify(i18n.getDataByLanguage('en')?.errors ?? {})), +}; + +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 renderOrganizationTags = (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', async () => { + const { getByText } = renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect(getByText(translations.createTag)).toBeInTheDocument(); + }); + }); + + test('render error component on unsuccessful userTags query', async () => { + const { queryByText } = renderOrganizationTags(link2); + + await wait(); + + await waitFor(() => { + expect(queryByText(translations.create)).not.toBeInTheDocument(); + }); + }); + + test('opens and closes the create tag modal', async () => { + renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('createTagBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('createTagBtn')); + + await waitFor(() => { + return expect( + screen.findByTestId('closeCreateTagModal'), + ).resolves.toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('closeCreateTagModal')); + + await waitForElementToBeRemoved(() => + screen.queryByTestId('closeCreateTagModal'), + ); + }); + + 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 manage tag page after clicking manage tag option', async () => { + renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect(screen.getAllByTestId('manageTagBtn')[0]).toBeInTheDocument(); + }); + userEvent.click(screen.getAllByTestId('manageTagBtn')[0]); + + await waitFor(() => { + expect(screen.getByTestId('tagDetailsScreen')).toBeInTheDocument(); + }); + }); + + test('paginates between different pages', async () => { + renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('nextPagBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('nextPagBtn')); + + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent('6'); + }); + + await waitFor(() => { + expect(screen.getByTestId('previousPageBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('previousPageBtn')); + + await waitFor(() => { + expect(screen.getAllByTestId('tagName')[0]).toHaveTextContent('1'); + }); + }); + + test('creates a new user tag', async () => { + renderOrganizationTags(link); + + await wait(); + + await waitFor(() => { + expect(screen.getByTestId('createTagBtn')).toBeInTheDocument(); + }); + userEvent.click(screen.getByTestId('createTagBtn')); + + userEvent.type( + screen.getByPlaceholderText(translations.tagNamePlaceholder), + '7', + ); + + 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); + }); + }); +}); diff --git a/src/screens/OrganizationTags/OrganizationTags.tsx b/src/screens/OrganizationTags/OrganizationTags.tsx new file mode 100644 index 0000000000..a265ccffb4 --- /dev/null +++ b/src/screens/OrganizationTags/OrganizationTags.tsx @@ -0,0 +1,510 @@ +import { useMutation, useQuery, type ApolloError } from '@apollo/client'; +import { Search, WarningAmberRounded } from '@mui/icons-material'; +import SortIcon from '@mui/icons-material/Sort'; +import Loader from 'components/Loader/Loader'; +import { useNavigate, useParams, Link } from 'react-router-dom'; +import type { ChangeEvent } from 'react'; +import React, { useState } from 'react'; +import { 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'; +import type { InterfaceQueryOrganizationUserTags } from 'utils/interfaces'; +import styles from './OrganizationTags.module.css'; +import { DataGrid } from '@mui/x-data-grid'; +import type { GridCellParams, GridColDef } from '@mui/x-data-grid'; +import { Stack } from '@mui/material'; +import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; +import { + CREATE_USER_TAG, + REMOVE_USER_TAG, +} from 'GraphQl/Mutations/TagMutations'; + +const dataGridStyle = { + '&.MuiDataGrid-root .MuiDataGrid-cell:focus-within': { + outline: 'none !important', + }, + '&.MuiDataGrid-root .MuiDataGrid-columnHeader:focus-within': { + outline: 'none', + }, + '& .MuiDataGrid-row:hover': { + backgroundColor: 'transparent', + }, + '& .MuiDataGrid-row.Mui-hovered': { + backgroundColor: 'transparent', + }, + '& .MuiDataGrid-root': { + borderRadius: '0.5rem', + }, + '& .MuiDataGrid-main': { + borderRadius: '0.5rem', + }, +}; + +function OrganizationTags(): JSX.Element { + const { t } = useTranslation('translation', { + keyPrefix: 'organizationTags', + }); + const { t: tCommon } = useTranslation('common'); + + const [createTagModalIsOpen, setCreateTagModalIsOpen] = useState(false); + + const { orgId: currentUrl } = 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(''); + + const [removeUserTagId, setRemoveUserTagId] = useState(null); + const [removeTagModalIsOpen, setRemoveTagModalIsOpen] = useState(false); + + const showCreateTagModal = (): void => { + setTagName(''); + setCreateTagModalIsOpen(true); + }; + + const hideCreateTagModal = (): void => { + setCreateTagModalIsOpen(false); + }; + + const { + data: orgUserTagsData, + loading: orgUserTagsLoading, + error: orgUserTagsError, + refetch: orgUserTagsRefetch, + }: { + data?: { + organizations: InterfaceQueryOrganizationUserTags[]; + }; + loading: boolean; + error?: ApolloError; + refetch: () => void; + } = useQuery(ORGANIZATION_USER_TAGS_LIST, { + variables: { + id: currentUrl, + after: after, + before: before, + first: first, + last: last, + }, + }); + + const [create, { loading: createUserTagLoading }] = + useMutation(CREATE_USER_TAG); + + const createTag = async (e: ChangeEvent): Promise => { + e.preventDefault(); + + try { + const { data } = await create({ + variables: { + name: tagName, + organizationId: currentUrl, + }, + }); + + if (data) { + toast.success(t('tagCreationSuccess')); + orgUserTagsRefetch(); + setTagName(''); + setCreateTagModalIsOpen(false); + } + } catch (error: unknown) { + /* istanbul ignore next */ + if (error instanceof Error) { + toast.error(error.message); + console.log(error.message); + } + } + }; + + const [removeUserTag] = useMutation(REMOVE_USER_TAG); + const handleRemoveUserTag = async (): Promise => { + try { + await removeUserTag({ + variables: { + id: removeUserTagId, + }, + }); + + orgUserTagsRefetch(); + toggleRemoveUserTagModal(); + toast.success(t('tagRemovalSuccess')); + } catch (error: unknown) { + /* istanbul ignore next */ + if (error instanceof Error) { + toast.error(error.message); + console.log(error.message); + } + } + }; + + if (createUserTagLoading || orgUserTagsLoading) { + return ; + } + + if (orgUserTagsError) { + return ( +
+
+ +
+ Error occured while loading Organization Tags Data +
+ {orgUserTagsError.message} +
+
+
+ ); + } + + 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) || + []; + + const handleClick = (tagId: string): void => { + navigate(`/orgtags/${currentUrl}/orgtagdetails/${tagId}`); + }; + + const toggleRemoveUserTagModal = (): void => { + if (removeTagModalIsOpen) setRemoveUserTagId(null); + setRemoveTagModalIsOpen(!removeTagModalIsOpen); + }; + + const columns: GridColDef[] = [ + { + field: 'id', + headerName: '#', + minWidth: 100, + align: 'center', + headerAlign: 'center', + headerClassName: `${styles.tableHeader}`, + sortable: false, + renderCell: (params: GridCellParams) => { + return
{tagSerialNumber * 5 + params.row.id}
; + }, + }, + { + field: 'tagName', + headerName: 'Tag Name', + flex: 1, + minWidth: 100, + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( +
+ {params.row.name} + + +
+ ); + }, + }, + { + field: 'totalSubTags', + headerName: 'Total Sub Tags', + flex: 1, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( + + {params.row.childTags.totalCount} + + ); + }, + }, + { + field: 'totalAssignedUsers', + headerName: 'Total Assigned Users', + flex: 1, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( + + {params.row.usersAssignedTo.totalCount} + + ); + }, + }, + { + field: 'actions', + headerName: 'Actions', + flex: 1, + align: 'center', + minWidth: 100, + headerAlign: 'center', + sortable: false, + headerClassName: `${styles.tableHeader}`, + renderCell: (params: GridCellParams) => { + return ( +
+ + + +
+ ); + }, + }, + ]; + + return ( + <> + +
+
+
+ + +
+
+ +
+ +
+ + 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} + /> +
+ +
+
+ +
+
+ +
+
+
+ + {/* Create Tag Modal */} + + + {t('tagDetails')} + +
+ + {t('tagName')} + { + setTagName(e.target.value); + }} + /> + + + + + + +
+
+ + {/* Remove User Tag Modal */} + + + + {t('removeUserTag')} + + + {t('removeUserTagMessage')} + + + + + + + ); +} + +export default OrganizationTags; diff --git a/src/screens/OrganizationTags/OrganizationTagsMocks.ts b/src/screens/OrganizationTags/OrganizationTagsMocks.ts new file mode 100644 index 0000000000..3700c66c46 --- /dev/null +++ b/src/screens/OrganizationTags/OrganizationTagsMocks.ts @@ -0,0 +1,291 @@ +import { + CREATE_USER_TAG, + REMOVE_USER_TAG, +} from 'GraphQl/Mutations/TagMutations'; +import { ORGANIZATION_USER_TAGS_LIST } from 'GraphQl/Queries/OrganizationQueries'; + +export const MOCKS = [ + { + request: { + query: ORGANIZATION_USER_TAGS_LIST, + variables: { + id: '123', + after: null, + before: null, + first: 5, + last: null, + }, + }, + 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', + 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', + }, + ], + 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, + }, + childTags: { + totalCount: 0, + }, + }, + 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', + 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', + }, + ], + pageInfo: { + startCursor: '1', + endCursor: '5', + hasNextPage: true, + hasPreviousPage: false, + }, + totalCount: 6, + }, + }, + ], + }, + }, + }, + { + request: { + query: CREATE_USER_TAG, + variables: { + name: '7', + organizationId: '123', + }, + }, + result: { + data: { + createUserTag: { + _id: '7', + }, + }, + }, + }, + { + request: { + query: REMOVE_USER_TAG, + variables: { + id: '1', + }, + }, + result: { + data: { + removeUserTag: { + _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/state/reducers/routesReducer.test.ts b/src/state/reducers/routesReducer.test.ts index 84427ee4d0..8d8de8a5dd 100644 --- a/src/state/reducers/routesReducer.test.ts +++ b/src/state/reducers/routesReducer.test.ts @@ -13,6 +13,7 @@ describe('Testing Routes reducer', () => { { name: 'My Organizations', url: '/orglist' }, { name: 'Dashboard', url: '/orgdash/undefined' }, { name: 'People', url: '/orgpeople/undefined' }, + { name: 'Tags', url: '/orgtags/undefined' }, { name: 'Events', url: '/orgevents/undefined' }, { name: 'Venues', url: '/orgvenues/undefined' }, { name: 'Action Items', url: '/orgactionitems/undefined' }, @@ -49,6 +50,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgpeople', component: 'OrganizationPeople', }, + { + name: 'Tags', + comp_id: 'orgtags', + component: 'OrganizationTags', + }, { name: 'Events', comp_id: 'orgevents', @@ -116,6 +122,7 @@ describe('Testing Routes reducer', () => { { name: 'My Organizations', url: '/orglist' }, { name: 'Dashboard', url: '/orgdash/orgId' }, { name: 'People', url: '/orgpeople/orgId' }, + { name: 'Tags', url: '/orgtags/orgId' }, { name: 'Events', url: '/orgevents/orgId' }, { name: 'Venues', url: '/orgvenues/orgId' }, { name: 'Action Items', url: '/orgactionitems/orgId' }, @@ -149,6 +156,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgpeople', component: 'OrganizationPeople', }, + { + name: 'Tags', + comp_id: 'orgtags', + component: 'OrganizationTags', + }, { name: 'Events', comp_id: 'orgevents', @@ -212,6 +224,7 @@ describe('Testing Routes reducer', () => { { name: 'My Organizations', url: '/orglist' }, { name: 'Dashboard', url: '/orgdash/undefined' }, { name: 'People', url: '/orgpeople/undefined' }, + { name: 'Tags', url: '/orgtags/undefined' }, { name: 'Events', url: '/orgevents/undefined' }, { name: 'Venues', url: '/orgvenues/undefined' }, { name: 'Action Items', url: '/orgactionitems/undefined' }, @@ -251,6 +264,11 @@ describe('Testing Routes reducer', () => { comp_id: 'orgpeople', component: 'OrganizationPeople', }, + { + name: 'Tags', + comp_id: 'orgtags', + component: 'OrganizationTags', + }, { name: 'Events', comp_id: 'orgevents', diff --git a/src/state/reducers/routesReducer.ts b/src/state/reducers/routesReducer.ts index bcda9f02d8..878fe73099 100644 --- a/src/state/reducers/routesReducer.ts +++ b/src/state/reducers/routesReducer.ts @@ -69,6 +69,7 @@ const components: ComponentType[] = [ { name: 'My Organizations', comp_id: 'orglist', component: 'OrgList' }, { name: 'Dashboard', comp_id: 'orgdash', component: 'OrganizationDashboard' }, { name: 'People', comp_id: 'orgpeople', component: 'OrganizationPeople' }, + { name: 'Tags', comp_id: 'orgtags', component: 'OrganizationTags' }, { name: 'Events', comp_id: 'orgevents', component: 'OrganizationEvents' }, { name: 'Venues', comp_id: 'orgvenues', component: 'OrganizationVenues' }, { diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index f837054b87..834bd7742d 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -204,6 +204,31 @@ export interface InterfaceQueryOrganizationPostListItem { }; } +export interface InterfaceQueryOrganizationUserTags { + userTags: { + edges: { + node: { + _id: string; + name: string; + usersAssignedTo: { + totalCount: number; + }; + childTags: { + totalCount: number; + }; + }; + cursor: string; + }[]; + pageInfo: { + startCursor: string; + endCursor: string; + hasNextPage: boolean; + hasPreviousPage: boolean; + }; + totalCount: number; + }; +} + export interface InterfaceQueryOrganizationAdvertisementListItem { advertisements: { edges: {