From 2f072d13baa9c1486bc91a6590f52a9090493c30 Mon Sep 17 00:00:00 2001 From: jadmsaadaot <91914654+jadmsaadaot@users.noreply.github.com> Date: Thu, 6 Jul 2023 09:41:47 -0700 Subject: [PATCH 1/3] update help page url and paths (#1803) --- .../layout/SideNav/UserGuideNav.tsx | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/met-web/src/components/layout/SideNav/UserGuideNav.tsx b/met-web/src/components/layout/SideNav/UserGuideNav.tsx index 16aae9598..b08dbc157 100644 --- a/met-web/src/components/layout/SideNav/UserGuideNav.tsx +++ b/met-web/src/components/layout/SideNav/UserGuideNav.tsx @@ -6,31 +6,31 @@ import { MetHeader4 } from 'components/common'; import { levenshteinDistance } from 'helper'; const THRESHOLD_SIMILARITY_SCORE = 10; -const DEFAULT_HELP_PATH = 'https://www.example.com/help/dashboard'; +const HELP_URL = 'https://bcgov.github.io/met-guide'; const UserGuideNav = () => { const { pathname } = useLocation(); const helpPaths: { [key: string]: string } = { - '/': 'https://www.example.com/help/dashboard', - '/engagements': 'https://www.example.com/help/engagements', - '/surveys': 'https://www.example.com/help/surveys', - '/surveys/create': 'https://www.example.com/help/create-survey', - '/surveys/1/build': 'https://www.example.com/help/survey-builder', - '/surveys/1/submit': 'https://www.example.com/help/survey-submission', - '/surveys/1/comments': 'https://www.example.com/help/survey-comments', - '/surveys/1/comments/all': 'https://www.example.com/help/all-comments', - '/surveys/1/submissions/1/review': 'https://www.example.com/help/review-submission', - '/engagements/create/form': 'https://www.example.com/help/create-engagement', - '/engagements/1/form': 'https://www.example.com/help/edit-engagement', - '/engagements/1/view': 'https://www.example.com/help/view-engagement', - '/engagements/1/comments': 'https://www.example.com/help/engagement-comments', - '/engagements/1/dashboard': 'https://www.example.com/help/engagement-dashboard', - '/feedback': 'https://www.example.com/help/feedback', - '/calendar': 'https://www.example.com/help/calendar', - '/reporting': 'https://www.example.com/help/reporting', - '/usermanagement': 'https://www.example.com/help/user-management', - '/usermanagement/1/details': 'https://www.example.com/help/user-details', + '/': HELP_URL, + '/engagements': `${HELP_URL}/posts/engagement-listing/`, + '/surveys': `${HELP_URL}/posts/survey-listing/`, + '/surveys/create': `${HELP_URL}/posts/create-survey/`, + '/surveys/1/build': `${HELP_URL}/posts/survey-builder/`, + '/surveys/1/submit': `${HELP_URL}/posts/survey-builder/`, + '/surveys/1/comments': `${HELP_URL}/posts/comment-review/`, + '/surveys/1/comments/all': `${HELP_URL}/posts/comment-review/`, + '/surveys/1/submissions/1/review': `${HELP_URL}/posts/comment-review/`, + '/engagements/create/form': `${HELP_URL}/posts/create-engagement/`, + '/engagements/1/form': `${HELP_URL}/posts/edit-engagement/`, + '/engagements/1/view': `${HELP_URL}/posts/preview-engagement/`, + '/engagements/1/comments': `${HELP_URL}/posts/preview-engagement/`, + '/engagements/1/dashboard': `${HELP_URL}/posts/manage-engagement/`, + '/feedback': `${HELP_URL}/posts/feedback-tool/`, + '/calendar': HELP_URL, + '/reporting': `${HELP_URL}/posts/report/`, + '/usermanagement': `${HELP_URL}/posts/user-management/`, + '/usermanagement/1/details': `${HELP_URL}/posts/user-details/`, }; const handleSimilarityScore = () => { @@ -55,10 +55,10 @@ const UserGuideNav = () => { const openHelpPage = () => { const key = handleSimilarityScore(); if (!key) { - window.open(DEFAULT_HELP_PATH, '_blank', 'noopener'); + window.open(HELP_URL, '_blank', 'noopener'); return; } - const helpPagePath = key ? helpPaths[key] : DEFAULT_HELP_PATH; + const helpPagePath = key ? helpPaths[key] : HELP_URL; window.open(helpPagePath, '_blank', 'noopener'); }; From 00960a31b7b455f14d31b08f58c64e811f406e97 Mon Sep 17 00:00:00 2001 From: jadmsaadaot <91914654+jadmsaadaot@users.noreply.github.com> Date: Thu, 6 Jul 2023 12:07:42 -0700 Subject: [PATCH 2/3] Add advanced search for survey listing page (#1802) --- met-api/src/met_api/models/survey.py | 65 ++- .../met_api/models/survey_search_options.py | 14 +- met-api/src/met_api/resources/survey.py | 9 +- .../components/survey/create/LinkOptions.tsx | 2 +- .../survey/listing/AdvancedSearch.tsx | 201 ++++++++ .../survey/listing/SurveyListingContext.tsx | 171 +++++++ .../src/components/survey/listing/Surveys.tsx | 458 +++++++++++++++++ .../src/components/survey/listing/index.tsx | 464 +----------------- met-web/src/services/surveyService/index.ts | 10 +- .../unit/components/surveyListing.test.tsx | 9 + 10 files changed, 926 insertions(+), 477 deletions(-) create mode 100644 met-web/src/components/survey/listing/AdvancedSearch.tsx create mode 100644 met-web/src/components/survey/listing/SurveyListingContext.tsx create mode 100644 met-web/src/components/survey/listing/Surveys.tsx diff --git a/met-api/src/met_api/models/survey.py b/met-api/src/met_api/models/survey.py index 96a93f4c3..72cdef593 100644 --- a/met-api/src/met_api/models/survey.py +++ b/met-api/src/met_api/models/survey.py @@ -59,15 +59,47 @@ def get_surveys_paginated(cls, pagination_options: PaginationOptions, query = db.session.query(Survey).join(Engagement, isouter=True).join(EngagementStatus, isouter=True) query = cls._add_tenant_filter(query) + query = cls.filter_by_search_options(survey_search_options, query) + + sort = asc(text(pagination_options.sort_key)) if pagination_options.sort_order == 'asc'\ + else desc(text(pagination_options.sort_key)) + + query = query.order_by(sort) + + no_pagination_options = not pagination_options.page or not pagination_options.size + if no_pagination_options: + items = query.all() + return items, len(items) + + page = query.paginate(page=pagination_options.page, per_page=pagination_options.size) + + return page.items, page.total + + @classmethod + def filter_by_search_options(cls, survey_search_options: SurveySearchOptions, query): + """Filter by search options.""" if survey_search_options.exclude_hidden: query = query.filter(Survey.is_hidden.is_(False)) if survey_search_options.exclude_template: query = query.filter(Survey.is_template.is_(False)) - if survey_search_options.unlinked: + if survey_search_options.is_unlinked: query = query.filter(Survey.engagement_id.is_(None)) + if survey_search_options.is_linked: + query = query.filter(Survey.engagement_id.isnot(None)) + + if survey_search_options.is_hidden: + query = query.filter(Survey.is_hidden.is_(True)) + + if survey_search_options.is_template: + query = query.filter(Survey.is_template.is_(True)) + + query = cls._filter_by_created_date(query, survey_search_options) + + query = cls._filter_by_published_date(query, survey_search_options) + # if role has access to view all engagements then include all surveys which are in ready status or # surveys linked to draft and assigned engagements if survey_search_options.can_view_all_engagements: @@ -79,20 +111,7 @@ def get_surveys_paginated(cls, pagination_options: PaginationOptions, if survey_search_options.search_text: query = query.filter(Survey.name.ilike('%' + survey_search_options.search_text + '%')) - - sort = asc(text(pagination_options.sort_key)) if pagination_options.sort_order == 'asc'\ - else desc(text(pagination_options.sort_key)) - - query = query.order_by(sort) - - no_pagination_options = not pagination_options.page or not pagination_options.size - if no_pagination_options: - items = query.all() - return items, len(items) - - page = query.paginate(page=pagination_options.page, per_page=pagination_options.size) - - return page.items, page.total + return query @classmethod def create_survey(cls, survey: SurveySchema) -> Survey: @@ -166,3 +185,19 @@ def _filter_accessible_surveys(query, assigned_engagements: list[int]): Survey.engagement_id.is_(None)] query = query.filter(or_(*filter_conditions)) return query + + @staticmethod + def _filter_by_created_date(query, search_options: SurveySearchOptions): + if search_options.created_date_from: + query = query.filter(Survey.created_date >= search_options.created_date_from) + if search_options.created_date_to: + query = query.filter(Survey.created_date <= search_options.created_date_to) + return query + + @classmethod + def _filter_by_published_date(cls, query, search_options: SurveySearchOptions): + if search_options.published_date_from: + query = query.filter(Engagement.published_date >= search_options.published_date_from) + if search_options.published_date_to: + query = query.filter(Engagement.published_date <= search_options.published_date_to) + return query diff --git a/met-api/src/met_api/models/survey_search_options.py b/met-api/src/met_api/models/survey_search_options.py index 5793e2f6d..ff68ff7cf 100644 --- a/met-api/src/met_api/models/survey_search_options.py +++ b/met-api/src/met_api/models/survey_search_options.py @@ -1,6 +1,7 @@ """This module holds data classes.""" -from typing import List +from typing import List, Optional + from attr import dataclass @@ -10,7 +11,14 @@ class SurveySearchOptions: # pylint: disable=too-many-instance-attributes exclude_hidden: bool exclude_template: bool - assigned_engagements: List[int] = None + assigned_engagements: Optional[List[int]] = None search_text: str = '' - unlinked: bool = False can_view_all_engagements: bool = True + is_unlinked: bool = False + is_linked: bool = False + is_hidden: bool = False + is_template: bool = False + created_date_from: Optional[str] = None + created_date_to: Optional[str] = None + published_date_from: Optional[str] = None + published_date_to: Optional[str] = None diff --git a/met-api/src/met_api/resources/survey.py b/met-api/src/met_api/resources/survey.py index 147ad7e8a..f726b1afd 100644 --- a/met-api/src/met_api/resources/survey.py +++ b/met-api/src/met_api/resources/survey.py @@ -87,7 +87,14 @@ def get(): exclude_hidden=args.get('exclude_hidden', False, bool), exclude_template=args.get('exclude_template', False, bool), search_text=args.get('search_text', '', str), - unlinked=args.get('unlinked', False, bool), + is_unlinked=args.get('is_unlinked', default=False, type=lambda v: v.lower() == 'true'), + is_linked=args.get('is_linked', default=False, type=lambda v: v.lower() == 'true'), + is_hidden=args.get('is_hidden', default=False, type=lambda v: v.lower() == 'true'), + is_template=args.get('is_template', default=False, type=lambda v: v.lower() == 'true'), + created_date_from=args.get('created_date_from', None, type=str), + created_date_to=args.get('created_date_to', None, type=str), + published_date_from=args.get('published_date_from', None, type=str), + published_date_to=args.get('published_date_to', None, type=str), ) survey_records = SurveyService()\ diff --git a/met-web/src/components/survey/create/LinkOptions.tsx b/met-web/src/components/survey/create/LinkOptions.tsx index b39fd191a..bfaaaaf16 100644 --- a/met-web/src/components/survey/create/LinkOptions.tsx +++ b/met-web/src/components/survey/create/LinkOptions.tsx @@ -23,7 +23,7 @@ const LinkOptions = () => { const handleFetchSurveys = async () => { try { const fetchedSurveys = await fetchSurveys({ - unlinked: true, + is_unlinked: true, exclude_hidden: true, exclude_template: true, }); diff --git a/met-web/src/components/survey/listing/AdvancedSearch.tsx b/met-web/src/components/survey/listing/AdvancedSearch.tsx new file mode 100644 index 000000000..18169094f --- /dev/null +++ b/met-web/src/components/survey/listing/AdvancedSearch.tsx @@ -0,0 +1,201 @@ +import React, { useContext, useState } from 'react'; +import { + Checkbox, + FormControl, + FormControlLabel, + FormGroup, + Grid, + Stack, + TextField, + Theme, + useMediaQuery, +} from '@mui/material'; +import { MetLabel, PrimaryButton, SecondaryButton } from 'components/common'; +import { AdvancedSearchFilters, SurveyListingContext } from './SurveyListingContext'; + +export const AdvancedSearch = () => { + const isMediumScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down('lg')); + const [searchFilters, setSearchFilters] = useState({ + status: { + linked: false, + ready: false, + template: false, + hidden: false, + }, + createdDateFrom: '', + createdDateTo: '', + publishedDateFrom: '', + publishedDateTo: '', + }); + const { setAdvancedSearchFilters, initialSearchFilters } = useContext(SurveyListingContext); + + const handleChange = (event: React.ChangeEvent) => { + const { name, value } = event.target; + const newFilters = { ...searchFilters, [name]: value }; + setSearchFilters(newFilters); + }; + + const handleStatusChange = (event: React.ChangeEvent) => { + const { name, checked } = event.target; + const newFilters = { ...searchFilters, status: { ...searchFilters.status, [name]: checked } }; + setSearchFilters(newFilters); + }; + + const { status, createdDateFrom, createdDateTo, publishedDateFrom, publishedDateTo } = searchFilters; + + return ( + + + + Status + + } + label="Hidden" + /> + + } + label="Template" + /> + } + label="Ready" + /> + } + label="Linked" + /> + + + + + + + Date Created - From + + + + Date Created - To + + + + + + Date Published - From + + + + Date Published - To + + + + + + + { + setAdvancedSearchFilters(initialSearchFilters); + setSearchFilters(initialSearchFilters); + }} + > + Reset All Filters + + { + setAdvancedSearchFilters(searchFilters); + }} + > + Search + + + + + ); +}; diff --git a/met-web/src/components/survey/listing/SurveyListingContext.tsx b/met-web/src/components/survey/listing/SurveyListingContext.tsx new file mode 100644 index 000000000..3a42c6e6d --- /dev/null +++ b/met-web/src/components/survey/listing/SurveyListingContext.tsx @@ -0,0 +1,171 @@ +import React, { createContext, useEffect, useState } from 'react'; +import { PageInfo, PaginationOptions, createDefaultPageInfo } from 'components/common/Table/types'; +import { useAppDispatch } from 'hooks'; +import { Survey } from 'models/survey'; +import { openNotification } from 'services/notificationService/notificationSlice'; +import { getSurveysPage } from 'services/surveyService'; + +interface SurveyFilterStatus { + linked: boolean; + ready: boolean; + template: boolean; + hidden: boolean; +} +export interface AdvancedSearchFilters { + status: SurveyFilterStatus; + createdDateFrom: string; + createdDateTo: string; + publishedDateFrom: string; + publishedDateTo: string; +} +export const initialSearchFilters: AdvancedSearchFilters = { + status: { + linked: false, + ready: false, + template: false, + hidden: false, + }, + createdDateFrom: '', + createdDateTo: '', + publishedDateFrom: '', + publishedDateTo: '', +}; + +export interface SurveyListingContextState { + searchFilter: { + key: string; + value: string; + }; + advancedSearchFilters: AdvancedSearchFilters; + setAdvancedSearchFilters: (value: AdvancedSearchFilters) => void; + setSearchFilter: (value: { key: string; value: string }) => void; + searchText: string; + setSearchText: (value: string) => void; + paginationOptions: PaginationOptions; + setPaginationOptions: (value: PaginationOptions) => void; + pageInfo: PageInfo; + setPageInfo: (value: PageInfo) => void; + tableLoading: boolean; + surveys: Survey[]; + initialSearchFilters: AdvancedSearchFilters; +} + +export const SurveyListingContext = createContext({ + searchFilter: { + key: 'id', + value: '', + }, + advancedSearchFilters: initialSearchFilters, + setAdvancedSearchFilters: () => { + throw new Error('setAdvancedSearchFilters not implemented'); + }, + setSearchFilter: () => { + throw new Error('setSearchFilter not implemented'); + }, + searchText: '', + setSearchText: () => { + throw new Error('setSearchText not implemented'); + }, + paginationOptions: { + page: 1, + size: 10, + sort_key: 'created_date', + nested_sort_key: 'survey.created_date', + sort_order: 'desc', + }, + setPaginationOptions: () => { + throw new Error('setPaginationOptions not implemented'); + }, + pageInfo: createDefaultPageInfo(), + setPageInfo: () => { + throw new Error('setPageInfo not implemented'); + }, + tableLoading: false, + surveys: [], + initialSearchFilters: initialSearchFilters, +}); + +interface SurveyListingContextProviderProps { + children: React.ReactNode; +} +export const SurveyListingContextProvider = ({ children }: SurveyListingContextProviderProps) => { + const [searchFilter, setSearchFilter] = useState({ + key: 'name', + value: '', + }); + const [searchText, setSearchText] = useState(''); + const [surveys, setSurveys] = useState([]); + const [paginationOptions, setPaginationOptions] = useState>({ + page: 1, + size: 10, + sort_key: 'created_date', + nested_sort_key: 'survey.created_date', + sort_order: 'desc', + }); + const [pageInfo, setPageInfo] = useState(createDefaultPageInfo()); + + const [tableLoading, setTableLoading] = useState(true); + + const [advancedSearchFilters, setAdvancedSearchFilters] = useState({ + ...initialSearchFilters, + }); + + const dispatch = useAppDispatch(); + + const { page, size, sort_key, nested_sort_key, sort_order } = paginationOptions; + + const loadSurveys = async () => { + try { + setTableLoading(true); + const response = await getSurveysPage({ + page, + size, + sort_key: nested_sort_key || sort_key, + sort_order, + search_text: searchFilter.value, + is_unlinked: advancedSearchFilters.status.ready, + is_template: advancedSearchFilters.status.template, + is_hidden: advancedSearchFilters.status.hidden, + is_linked: advancedSearchFilters.status.linked, + created_date_from: advancedSearchFilters.createdDateFrom, + created_date_to: advancedSearchFilters.createdDateTo, + published_date_from: advancedSearchFilters.publishedDateFrom, + published_date_to: advancedSearchFilters.publishedDateTo, + }); + setSurveys(response.items); + setPageInfo({ + total: response.total, + }); + setTableLoading(false); + } catch (error) { + dispatch(openNotification({ severity: 'error', text: 'Error occurred while fetching surveys' })); + setTableLoading(false); + } + }; + + useEffect(() => { + loadSurveys(); + }, [paginationOptions, searchFilter, advancedSearchFilters]); + + return ( + + {children} + + ); +}; diff --git a/met-web/src/components/survey/listing/Surveys.tsx b/met-web/src/components/survey/listing/Surveys.tsx new file mode 100644 index 000000000..3f8ef7611 --- /dev/null +++ b/met-web/src/components/survey/listing/Surveys.tsx @@ -0,0 +1,458 @@ +import React, { useContext, useState } from 'react'; +import MetTable from 'components/common/Table'; +import Grid from '@mui/material/Grid'; +import { Link, useNavigate } from 'react-router-dom'; +import { MetPageGridContainer, MetTooltip, PrimaryButton, SecondaryButton } from 'components/common'; +import { Survey } from 'models/survey'; +import { HeadCell, PaginationOptions } from 'components/common/Table/types'; +import { formatDate } from 'components/common/dateHelper'; +import { Collapse, Link as MuiLink, Theme, useMediaQuery } from '@mui/material'; +import TextField from '@mui/material/TextField'; +import SearchIcon from '@mui/icons-material/Search'; +import Stack from '@mui/material/Stack'; +import { useAppSelector } from 'hooks'; +import { SubmissionStatus } from 'constants/engagementStatus'; +import PriorityHighRoundedIcon from '@mui/icons-material/PriorityHighRounded'; +import { ApprovedIcon, NewIcon, NFRIcon, RejectedIcon } from 'components/engagement/listing/Icons'; +import CloseRounded from '@mui/icons-material/CloseRounded'; +import FiberNewOutlined from '@mui/icons-material/FiberNewOutlined'; +import { PermissionsGate } from 'components/permissionsGate'; +import { CommentStatus } from 'constants/commentStatus'; +import { ActionsDropDown } from './ActionsDropDown'; +import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; +import CheckIcon from '@mui/icons-material/Check'; +import LinkIcon from '@mui/icons-material/Link'; +import DashboardIcon from '@mui/icons-material/Dashboard'; +import { Palette } from 'styles/Theme'; +import { AdvancedSearch } from './AdvancedSearch'; +import { SurveyListingContext } from './SurveyListingContext'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; +import { USER_ROLES } from 'services/userService/constants'; + +const Surveys = () => { + const { + paginationOptions, + setPaginationOptions, + setSearchFilter, + pageInfo, + tableLoading, + surveys, + searchText, + setSearchText, + searchFilter, + } = useContext(SurveyListingContext); + const navigate = useNavigate(); + + const isMediumScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down('md')); + + const [isAdvancedSearchOpen, setIsAdvancedSearchOpen] = useState(false); + + const { roles, assignedEngagements } = useAppSelector((state) => state.user); + + const canViewPrivateEngagements = roles.includes(USER_ROLES.VIEW_PRIVATE_ENGAGEMENTS); + + const submissionHasBeenOpened = (survey: Survey) => { + return ( + !!survey.engagement && + [SubmissionStatus.Open, SubmissionStatus.Closed].includes(survey.engagement.submission_status) + ); + }; + + const handleSearchBarClick = (surveyNameFilter: string) => { + setSearchFilter({ + ...searchFilter, + value: surveyNameFilter, + }); + }; + + const headCells: HeadCell[] = [ + { + key: 'name', + nestedSortKey: 'survey.name', + numeric: false, + disablePadding: true, + label: 'Survey Name', + allowSort: true, + renderCell: (row: Survey) => ( + + {row.name} + + ), + }, + { + key: 'created_date', + nestedSortKey: 'survey.created_date', + numeric: true, + disablePadding: false, + label: 'Date Created', + allowSort: true, + renderCell: (row: Survey) => formatDate(row.created_date), + }, + { + key: 'engagement', + nestedSortKey: 'engagement.published_date', + numeric: true, + disablePadding: false, + label: 'Date Published', + allowSort: true, + renderCell: (row: Survey) => formatDate(row.engagement?.published_date ?? ''), + }, + { + key: 'engagement', + nestedSortKey: 'engagement.status_id', + numeric: true, + disablePadding: false, + label: 'Status', + allowSort: true, + renderCell: (row: Survey) => { + if (row.is_hidden) { + return ( + + Hidden +

+ This survey is only visible to Superusers. Toggle off on the survey edit page to + make it ready and available. +

+ + } + placement="right" + arrow + > + +
+ ); + } + + if (row.is_template) { + return ( + + Template +

Templates can be cloned and then edited.

+ + } + placement="right" + arrow + > + +
+ ); + } + + if (row.engagement_id) { + return ( + + Linked +

+ This survey is attached to an engagement. It can still be cloned and then + edited. +

+ + } + placement="right" + arrow + > + +
+ ); + } + + return ( + + Ready +

This survey is ready to be cloned or attached to an engagement.

+ + } + placement="right" + arrow + > + +
+ ); + }, + }, + { + key: 'engagement', + nestedSortKey: 'engagement.name', + numeric: true, + disablePadding: false, + label: 'Engagement Name', + allowSort: true, + renderCell: (row: Survey) => { + if (!row.engagement) { + return <>; + } + + return ( + + {row.engagement.name} + + ); + }, + }, + { + key: 'comments_meta_data', + numeric: true, + disablePadding: false, + label: '', + customStyle: { padding: 2 }, + hideSorticon: true, + align: 'left', + icon: ( + + + + ), + allowSort: false, + renderCell: (row: Survey) => { + if (!submissionHasBeenOpened(row)) { + return <>; + } + const { approved } = row.comments_meta_data; + return ( + + + { + navigate(`/surveys/${row.id}/comments`, { + state: { + status: CommentStatus.Approved, + }, + }); + }} + > + {approved || 0} + + + + ); + }, + }, + { + key: 'comments_meta_data', + numeric: true, + disablePadding: false, + label: '', + + customStyle: { padding: 2 }, + hideSorticon: true, + align: 'left', + icon: ( + + + + ), + allowSort: false, + renderCell: (row: Survey) => { + if ( + !submissionHasBeenOpened(row) || + (!canViewPrivateEngagements && !assignedEngagements.includes(Number(row.id))) + ) { + return <>; + } + const { needs_further_review } = row.comments_meta_data; + return ( + + + { + navigate(`/surveys/${row.id}/comments`, { + state: { + status: CommentStatus.NeedsFurtherReview, + }, + }); + }} + > + {needs_further_review || 0} + + + + ); + }, + }, + { + key: 'comments_meta_data', + numeric: true, + disablePadding: false, + label: '', + + customStyle: { padding: 2 }, + hideSorticon: true, + align: 'left', + icon: ( + + + + ), + allowSort: false, + renderCell: (row: Survey) => { + if ( + !submissionHasBeenOpened(row) || + (!canViewPrivateEngagements && !assignedEngagements.includes(Number(row.id))) + ) { + return <>; + } + const { rejected } = row.comments_meta_data; + return ( + + + { + navigate(`/surveys/${row.id}/comments`, { + state: { + status: CommentStatus.Rejected, + }, + }); + }} + > + {rejected || 0} + + + + ); + }, + }, + { + key: 'comments_meta_data', + numeric: true, + disablePadding: false, + label: '', + + customStyle: { padding: 2 }, + hideSorticon: true, + align: 'left', + icon: ( + + + + ), + allowSort: false, + renderCell: (row: Survey) => { + if ( + !submissionHasBeenOpened(row) || + (!canViewPrivateEngagements && !assignedEngagements.includes(Number(row.id))) + ) { + return <>; + } + const { pending } = row.comments_meta_data; + return ( + + + { + navigate(`/surveys/${row.id}/comments`, { + state: { + status: CommentStatus.Pending, + }, + }); + }} + > + {pending || 0} + + + + ); + }, + }, + { + key: 'id', + numeric: true, + disablePadding: false, + label: 'Actions', + allowSort: false, + renderCell: (row: Survey) => { + return ; + }, + customStyle: { + minWidth: '200px', + }, + }, + ]; + + return ( + + + + + + setSearchText(e.target.value)} + size="small" + /> + handleSearchBarClick(searchText)} + > + + + + setIsAdvancedSearchOpen(!isAdvancedSearchOpen)} + startIcon={ + + theme.transitions.create('transform', { + duration: theme.transitions.duration.shortest, + }), + transform: isAdvancedSearchOpen ? 'rotate(180deg)' : 'rotate(0deg)', + }} + /> + } + fullWidth={isMediumScreen ? true : false} + > + Advanced Search + + + + + + Create Survey + + + + + + + + + + + + + ) => + setPaginationOptions(paginationOptions) + } + paginationOptions={paginationOptions} + loading={tableLoading} + pageInfo={pageInfo} + /> + + + ); +}; + +export default Surveys; diff --git a/met-web/src/components/survey/listing/index.tsx b/met-web/src/components/survey/listing/index.tsx index d9fc3ab3c..4242660e7 100644 --- a/met-web/src/components/survey/listing/index.tsx +++ b/met-web/src/components/survey/listing/index.tsx @@ -1,461 +1,13 @@ -import React, { useState, useEffect } from 'react'; -import MetTable from 'components/common/Table'; -import Grid from '@mui/material/Grid'; -import { Link, useNavigate } from 'react-router-dom'; -import { MetPageGridContainer, MetTooltip, PrimaryButton } from 'components/common'; -import { Survey } from 'models/survey'; -import { createDefaultPageInfo, HeadCell, PageInfo, PaginationOptions } from 'components/common/Table/types'; -import { formatDate } from 'components/common/dateHelper'; -import { Link as MuiLink } from '@mui/material'; -import TextField from '@mui/material/TextField'; -import SearchIcon from '@mui/icons-material/Search'; -import Stack from '@mui/material/Stack'; -import { getSurveysPage } from 'services/surveyService'; -import { useAppDispatch, useAppSelector } from 'hooks'; -import { openNotification } from 'services/notificationService/notificationSlice'; -import { SubmissionStatus } from 'constants/engagementStatus'; -import { USER_ROLES } from 'services/userService/constants'; -import PriorityHighRoundedIcon from '@mui/icons-material/PriorityHighRounded'; -import { ApprovedIcon, NewIcon, NFRIcon, RejectedIcon } from 'components/engagement/listing/Icons'; -import CloseRounded from '@mui/icons-material/CloseRounded'; -import FiberNewOutlined from '@mui/icons-material/FiberNewOutlined'; -import { PermissionsGate } from 'components/permissionsGate'; -import { CommentStatus } from 'constants/commentStatus'; -import { ActionsDropDown } from './ActionsDropDown'; -import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; -import CheckIcon from '@mui/icons-material/Check'; -import LinkIcon from '@mui/icons-material/Link'; -import DashboardIcon from '@mui/icons-material/Dashboard'; -import { Palette } from 'styles/Theme'; - -const SurveyListing = () => { - const navigate = useNavigate(); - const [searchFilter, setSearchFilter] = useState({ - key: 'name', - value: '', - }); - const [searchText, setSearchText] = useState(''); - const [surveys, setSurveys] = useState([]); - const [paginationOptions, setPaginationOptions] = useState>({ - page: 1, - size: 10, - sort_key: 'created_date', - nested_sort_key: 'survey.created_date', - sort_order: 'desc', - }); - const [pageInfo, setPageInfo] = useState(createDefaultPageInfo()); - - const [tableLoading, setTableLoading] = useState(true); - - const { roles, assignedEngagements } = useAppSelector((state) => state.user); - - const canViewPrivateEngagements = roles.includes(USER_ROLES.VIEW_PRIVATE_ENGAGEMENTS); - - const dispatch = useAppDispatch(); - - const { page, size, sort_key, nested_sort_key, sort_order } = paginationOptions; - - const loadSurveys = async () => { - try { - setTableLoading(true); - const response = await getSurveysPage({ - page, - size, - sort_key: nested_sort_key || sort_key, - sort_order, - search_text: searchFilter.value, - }); - setSurveys(response.items); - setPageInfo({ - total: response.total, - }); - setTableLoading(false); - } catch (error) { - dispatch(openNotification({ severity: 'error', text: 'Error occurred while fetching surveys' })); - setTableLoading(false); - } - }; - - const submissionHasBeenOpened = (survey: Survey) => { - return ( - !!survey.engagement && - [SubmissionStatus.Open, SubmissionStatus.Closed].includes(survey.engagement.submission_status) - ); - }; - - useEffect(() => { - loadSurveys(); - }, [paginationOptions, searchFilter]); - - const handleSearchBarClick = (surveyNameFilter: string) => { - setSearchFilter({ - ...searchFilter, - value: surveyNameFilter, - }); - }; - - const headCells: HeadCell[] = [ - { - key: 'name', - nestedSortKey: 'survey.name', - numeric: false, - disablePadding: true, - label: 'Survey Name', - allowSort: true, - renderCell: (row: Survey) => ( - - {row.name} - - ), - }, - { - key: 'created_date', - nestedSortKey: 'survey.created_date', - numeric: true, - disablePadding: false, - label: 'Date Created', - allowSort: true, - renderCell: (row: Survey) => formatDate(row.created_date), - }, - { - key: 'engagement', - nestedSortKey: 'engagement.published_date', - numeric: true, - disablePadding: false, - label: 'Date Published', - allowSort: true, - renderCell: (row: Survey) => formatDate(row.engagement?.published_date || ''), - }, - { - key: 'engagement', - nestedSortKey: 'engagement.status_id', - numeric: true, - disablePadding: false, - label: 'Status', - allowSort: true, - renderCell: (row: Survey) => { - if (row.is_hidden) { - return ( - - Hidden -

- This survey is only visible to Superusers. Toggle off on the survey edit page to - make it ready and available. -

- - } - placement="right" - arrow - > - -
- ); - } - - if (row.is_template) { - return ( - - Template -

Templates can be cloned and then edited.

- - } - placement="right" - arrow - > - -
- ); - } - - if (row.engagement_id) { - return ( - - Linked -

- This survey is attached to an engagement. It can still be cloned and then - edited. -

- - } - placement="right" - arrow - > - -
- ); - } - - return ( - - Ready -

This survey is ready to be cloned or attached to an engagement.

- - } - placement="right" - arrow - > - -
- ); - }, - }, - { - key: 'engagement', - nestedSortKey: 'engagement.name', - numeric: true, - disablePadding: false, - label: 'Engagement Name', - allowSort: true, - renderCell: (row: Survey) => { - if (!row.engagement) { - return <>; - } - - return ( - - {row.engagement.name} - - ); - }, - }, - { - key: 'comments_meta_data', - numeric: true, - disablePadding: false, - label: '', - customStyle: { padding: 2 }, - hideSorticon: true, - align: 'left', - icon: ( - - - - ), - allowSort: false, - renderCell: (row: Survey) => { - if (!submissionHasBeenOpened(row)) { - return <>; - } - const { approved } = row.comments_meta_data; - return ( - - - { - navigate(`/surveys/${row.id}/comments`, { - state: { - status: CommentStatus.Approved, - }, - }); - }} - > - {approved || 0} - - - - ); - }, - }, - { - key: 'comments_meta_data', - numeric: true, - disablePadding: false, - label: '', - - customStyle: { padding: 2 }, - hideSorticon: true, - align: 'left', - icon: ( - - - - ), - allowSort: false, - renderCell: (row: Survey) => { - if ( - !submissionHasBeenOpened(row) || - (!canViewPrivateEngagements && !assignedEngagements.includes(Number(row.id))) - ) { - return <>; - } - const { needs_further_review } = row.comments_meta_data; - return ( - - - { - navigate(`/surveys/${row.id}/comments`, { - state: { - status: CommentStatus.NeedsFurtherReview, - }, - }); - }} - > - {needs_further_review || 0} - - - - ); - }, - }, - { - key: 'comments_meta_data', - numeric: true, - disablePadding: false, - label: '', - - customStyle: { padding: 2 }, - hideSorticon: true, - align: 'left', - icon: ( - - - - ), - allowSort: false, - renderCell: (row: Survey) => { - if ( - !submissionHasBeenOpened(row) || - (!canViewPrivateEngagements && !assignedEngagements.includes(Number(row.id))) - ) { - return <>; - } - const { rejected } = row.comments_meta_data; - return ( - - - { - navigate(`/surveys/${row.id}/comments`, { - state: { - status: CommentStatus.Rejected, - }, - }); - }} - > - {rejected || 0} - - - - ); - }, - }, - { - key: 'comments_meta_data', - numeric: true, - disablePadding: false, - label: '', - - customStyle: { padding: 2 }, - hideSorticon: true, - align: 'left', - icon: ( - - - - ), - allowSort: false, - renderCell: (row: Survey) => { - if ( - !submissionHasBeenOpened(row) || - (!canViewPrivateEngagements && !assignedEngagements.includes(Number(row.id))) - ) { - return <>; - } - const { pending } = row.comments_meta_data; - return ( - - - { - navigate(`/surveys/${row.id}/comments`, { - state: { - status: CommentStatus.Pending, - }, - }); - }} - > - {pending || 0} - - - - ); - }, - }, - { - key: 'id', - numeric: true, - disablePadding: false, - label: 'Actions', - allowSort: false, - renderCell: (row: Survey) => { - return ; - }, - customStyle: { - minWidth: '200px', - }, - }, - ]; +import React from 'react'; +import { SurveyListingContextProvider } from './SurveyListingContext'; +import Surveys from './Surveys'; +const SubmissionListing = () => { return ( - - - - - setSearchText(e.target.value)} - size="small" - /> - handleSearchBarClick(searchText)} - > - - - - - - + Create Survey - - - - - - - ) => - setPaginationOptions(paginationOptions) - } - paginationOptions={paginationOptions} - loading={tableLoading} - pageInfo={pageInfo} - /> - - + + + ); }; -export default SurveyListing; +export default SubmissionListing; diff --git a/met-web/src/services/surveyService/index.ts b/met-web/src/services/surveyService/index.ts index 7663ba1a6..08b6c390f 100644 --- a/met-web/src/services/surveyService/index.ts +++ b/met-web/src/services/surveyService/index.ts @@ -5,7 +5,7 @@ import { replaceAllInURL, replaceUrl } from 'helper'; import { Page } from 'services/type'; interface FetchSurveyParams { - unlinked?: boolean; + is_unlinked?: boolean; exclude_hidden?: boolean; exclude_template?: boolean; } @@ -22,6 +22,14 @@ interface GetSurveysParams { search_text?: string; exclude_hidden?: boolean; exclude_template?: boolean; + is_unlinked?: boolean; + is_linked?: boolean; + is_template?: boolean; + is_hidden?: boolean; + created_date_from?: string; + created_date_to?: string; + published_date_from?: string; + published_date_to?: string; } export const getSurveysPage = async (params: GetSurveysParams = {}): Promise> => { const response = await http.GetRequest>(Endpoints.Survey.GET_LIST, params); diff --git a/met-web/tests/unit/components/surveyListing.test.tsx b/met-web/tests/unit/components/surveyListing.test.tsx index 569622cc2..820d7ddb8 100644 --- a/met-web/tests/unit/components/surveyListing.test.tsx +++ b/met-web/tests/unit/components/surveyListing.test.tsx @@ -79,6 +79,7 @@ jest.mock('@mui/material', () => ({ Link: ({ children }: { children: ReactNode }) => { return {children}; }, + useMediaQuery: () => false, })); jest.mock('components/common', () => ({ @@ -166,6 +167,14 @@ describe('Survey form page tests', () => { sort_key: 'survey.created_date', sort_order: 'desc', search_text: 'Survey One', + is_unlinked: false, + is_template: false, + is_hidden: false, + is_linked: false, + created_date_from: '', + created_date_to: '', + published_date_from: '', + published_date_to: '', }); }); }); From 0aba80e80141d1292bc50d404584baebc38328a2 Mon Sep 17 00:00:00 2001 From: djnunez-aot <103138766+djnunez-aot@users.noreply.github.com> Date: Fri, 7 Jul 2023 12:11:25 -0400 Subject: [PATCH 3/3] Update user link in engagement user management tab (#1808) * setup consistent logout * remove get baseurl * remove unused import * update link --- .../engagement/form/EngagementFormTabs/TeamMemberListing.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/met-web/src/components/engagement/form/EngagementFormTabs/TeamMemberListing.tsx b/met-web/src/components/engagement/form/EngagementFormTabs/TeamMemberListing.tsx index d03f7b948..1eec0ecf4 100644 --- a/met-web/src/components/engagement/form/EngagementFormTabs/TeamMemberListing.tsx +++ b/met-web/src/components/engagement/form/EngagementFormTabs/TeamMemberListing.tsx @@ -23,7 +23,7 @@ const TeamMemberListing = () => { label: 'Team Members', allowSort: false, renderCell: (row: EngagementTeamMember) => ( - + {row.user?.last_name + ', ' + row.user?.first_name} ),