From 143a4a61b3fd43d670dc269a6b4bc052717b6379 Mon Sep 17 00:00:00 2001 From: "Keith J. Grant" Date: Tue, 25 May 2021 16:48:38 -0700 Subject: [PATCH 1/3] clear list selection on navigate (first batch) --- .../DataListToolbar/DataListToolbar.jsx | 1 - .../src/components/JobList/JobList.jsx | 30 ++++++-------- .../PaginatedTable/PaginatedTable.jsx | 14 +++++-- .../Schedule/ScheduleList/ScheduleList.jsx | 31 ++++++--------- .../components/TemplateList/TemplateList.jsx | 37 +++++++----------- .../CredentialList/CredentialList.jsx | 17 ++++---- .../src/screens/Host/HostList/HostList.jsx | 31 +++++++-------- .../InventoryHosts/InventoryHostList.jsx | 25 ++++++------ .../Inventory/InventoryList/InventoryList.jsx | 39 +++++++++---------- .../NotificationTemplateDetail.jsx | 2 +- .../OrganizationList/OrganizationList.jsx | 31 +++++++-------- .../Project/ProjectList/ProjectList.jsx | 35 +++++++---------- .../src/screens/Team/TeamList/TeamList.jsx | 35 +++++++---------- .../screens/Template/Survey/SurveyList.jsx | 22 ++++++----- awx/ui_next/src/util/useSelected.jsx | 26 ++++++++++++- 15 files changed, 182 insertions(+), 194 deletions(-) diff --git a/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx b/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx index fceaa60ed3..c87d8cbc92 100644 --- a/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx +++ b/awx/ui_next/src/components/DataListToolbar/DataListToolbar.jsx @@ -37,7 +37,6 @@ function DataListToolbar({ onExpand, onSelectAll, additionalControls, - qsConfig, pagination, }) { diff --git a/awx/ui_next/src/components/JobList/JobList.jsx b/awx/ui_next/src/components/JobList/JobList.jsx index eaaa26095c..f0be9e05e4 100644 --- a/awx/ui_next/src/components/JobList/JobList.jsx +++ b/awx/ui_next/src/components/JobList/JobList.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import { t, Plural } from '@lingui/macro'; @@ -13,7 +13,7 @@ import useRequest, { useDismissableError, } from '../../util/useRequest'; import { useConfig } from '../../contexts/Config'; - +import useSelected from '../../util/useSelected'; import { isJobRunning, getJobModel } from '../../util/jobs'; import { getQSConfig, parseQueryString } from '../../util/qs'; import JobListItem from './JobListItem'; @@ -35,8 +35,6 @@ function JobList({ defaultParams, showTypeColumn = false }) { ); const { me } = useConfig(); - - const [selected, setSelected] = useState([]); const location = useLocation(); const { result: { results, count, relatedSearchableKeys, searchableKeys }, @@ -88,7 +86,14 @@ function JobList({ defaultParams, showTypeColumn = false }) { const jobs = useWsJobs(results, fetchJobsById, qsConfig); - const isAllSelected = selected.length === jobs.length && selected.length > 0; + const { + selected, + isAllSelected, + handleSelect, + setSelected, + selectAll, + clearSelected, + } = useSelected(jobs); const { error: cancelJobsError, @@ -143,18 +148,6 @@ function JobList({ defaultParams, showTypeColumn = false }) { setSelected([]); }; - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...jobs] : []); - }; - - const handleSelect = item => { - if (selected.some(s => s.id === item.id)) { - setSelected(selected.filter(s => s.id !== item.id)); - } else { - setSelected(selected.concat(item)); - } - }; - const cannotDeleteItems = selected.filter(job => isJobRunning(job.status)); return ( @@ -226,6 +219,7 @@ function JobList({ defaultParams, showTypeColumn = false }) { {t`Actions`} } + clearSelected={clearSelected} toolbarSearchableKeys={searchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys} renderToolbar={props => ( @@ -233,7 +227,7 @@ function JobList({ defaultParams, showTypeColumn = false }) { {...props} showSelectAll isAllSelected={isAllSelected} - onSelectAll={handleSelectAll} + onSelectAll={selectAll} qsConfig={qsConfig} additionalControls={[ { + clearSelected(); + }, [location.search, clearSelected]); const pushHistoryState = qs => { history.push(qs ? `${pathname}?${qs}` : pathname); @@ -127,7 +133,7 @@ function PaginatedTable({ ); return ( - + <> ) : null} - + ); } @@ -182,6 +188,7 @@ PaginatedTable.propTypes = { renderToolbar: PropTypes.func, hasContentLoading: PropTypes.bool, contentError: PropTypes.shape(), + clearSelected: PropTypes.func, ouiaId: PropTypes.string, }; @@ -195,6 +202,7 @@ PaginatedTable.defaultProps = { showPageSizeOptions: true, renderToolbar: props => , ouiaId: null, + clearSelected: () => {}, }; export { PaginatedTable as _PaginatedTable }; diff --git a/awx/ui_next/src/components/Schedule/ScheduleList/ScheduleList.jsx b/awx/ui_next/src/components/Schedule/ScheduleList/ScheduleList.jsx index 60592f9e99..4a1a28e509 100644 --- a/awx/ui_next/src/components/Schedule/ScheduleList/ScheduleList.jsx +++ b/awx/ui_next/src/components/Schedule/ScheduleList/ScheduleList.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import { bool, func } from 'prop-types'; @@ -10,6 +10,7 @@ import PaginatedTable, { HeaderRow, HeaderCell } from '../../PaginatedTable'; import DataListToolbar from '../../DataListToolbar'; import { ToolbarAddButton, ToolbarDeleteButton } from '../../PaginatedDataList'; import useRequest, { useDeleteItems } from '../../../util/useRequest'; +import useSelected from '../../../util/useSelected'; import { getQSConfig, parseQueryString } from '../../../util/qs'; import ScheduleListItem from './ScheduleListItem'; @@ -27,8 +28,6 @@ function ScheduleList({ launchConfig, surveyConfig, }) { - const [selected, setSelected] = useState([]); - const location = useLocation(); const { @@ -76,8 +75,13 @@ function ScheduleList({ fetchSchedules(); }, [fetchSchedules]); - const isAllSelected = - selected.length === schedules.length && selected.length > 0; + const { + selected, + isAllSelected, + handleSelect, + selectAll, + clearSelected, + } = useSelected(schedules); const { isLoading: isDeleteLoading, @@ -95,21 +99,9 @@ function ScheduleList({ } ); - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...schedules] : []); - }; - - const handleSelect = row => { - if (selected.some(s => s.id === row.id)) { - setSelected(selected.filter(s => s.id !== row.id)); - } else { - setSelected(selected.concat(row)); - } - }; - const handleDelete = async () => { await deleteJobs(); - setSelected([]); + clearSelected(); }; const canAdd = @@ -183,6 +175,7 @@ function ScheduleList({ isMissingSurvey={isTemplate && hasMissingSurveyValue(item)} /> )} + clearSelected={clearSelected} toolbarSearchColumns={[ { name: t`Name`, @@ -209,7 +202,7 @@ function ScheduleList({ {...props} showSelectAll isAllSelected={isAllSelected} - onSelectAll={handleSelectAll} + onSelectAll={selectAll} qsConfig={QS_CONFIG} additionalControls={[ ...(canAdd diff --git a/awx/ui_next/src/components/TemplateList/TemplateList.jsx b/awx/ui_next/src/components/TemplateList/TemplateList.jsx index 9ab0905306..69a0e6b7f6 100644 --- a/awx/ui_next/src/components/TemplateList/TemplateList.jsx +++ b/awx/ui_next/src/components/TemplateList/TemplateList.jsx @@ -1,4 +1,4 @@ -import React, { Fragment, useEffect, useState, useCallback } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useLocation, Link } from 'react-router-dom'; import { t, Plural } from '@lingui/macro'; import { Card, DropdownItem } from '@patternfly/react-core'; @@ -13,6 +13,7 @@ import ErrorDetail from '../ErrorDetail'; import { ToolbarDeleteButton } from '../PaginatedDataList'; import PaginatedTable, { HeaderRow, HeaderCell } from '../PaginatedTable'; import useRequest, { useDeleteItems } from '../../util/useRequest'; +import useSelected from '../../util/useSelected'; import { getQSConfig, parseQueryString } from '../../util/qs'; import useWsTemplates from '../../util/useWsTemplates'; import AddDropDownButton from '../AddDropDownButton'; @@ -35,8 +36,6 @@ function TemplateList({ defaultParams }) { ); const location = useLocation(); - const [selected, setSelected] = useState([]); - const { result: { results, @@ -87,8 +86,14 @@ function TemplateList({ defaultParams }) { const templates = useWsTemplates(results); - const isAllSelected = - selected.length === templates.length && selected.length > 0; + const { + selected, + isAllSelected, + handleSelect, + selectAll, + clearSelected, + } = useSelected(templates); + const { isLoading: isDeleteLoading, deleteItems: deleteTemplates, @@ -117,19 +122,7 @@ function TemplateList({ defaultParams }) { const handleTemplateDelete = async () => { await deleteTemplates(); - setSelected([]); - }; - - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...templates] : []); - }; - - const handleSelect = template => { - if (selected.some(s => s.id === template.id)) { - setSelected(selected.filter(s => s.id !== template.id)); - } else { - setSelected(selected.concat(template)); - } + clearSelected(); }; const canAddJT = @@ -179,7 +172,7 @@ function TemplateList({ defaultParams }) { ); return ( - + <> - + ); } diff --git a/awx/ui_next/src/screens/Credential/CredentialList/CredentialList.jsx b/awx/ui_next/src/screens/Credential/CredentialList/CredentialList.jsx index 6f80e18ceb..bd78478cdd 100644 --- a/awx/ui_next/src/screens/Credential/CredentialList/CredentialList.jsx +++ b/awx/ui_next/src/screens/Credential/CredentialList/CredentialList.jsx @@ -76,9 +76,14 @@ function CredentialList() { fetchCredentials(); }, [fetchCredentials]); - const { selected, isAllSelected, handleSelect, setSelected } = useSelected( - credentials - ); + const { + selected, + isAllSelected, + handleSelect, + setSelected, + selectAll, + clearSelected, + } = useSelected(credentials); const { isLoading: isDeleteLoading, @@ -115,7 +120,7 @@ function CredentialList() { items={credentials} itemCount={credentialCount} qsConfig={QS_CONFIG} - onRowClick={handleSelect} + clearSelected={clearSelected} toolbarSearchableKeys={searchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarSearchColumns={[ @@ -160,9 +165,7 @@ function CredentialList() { {...props} showSelectAll isAllSelected={isAllSelected} - onSelectAll={isSelected => - setSelected(isSelected ? [...credentials] : []) - } + onSelectAll={selectAll} qsConfig={QS_CONFIG} additionalControls={[ ...(canAdd diff --git a/awx/ui_next/src/screens/Host/HostList/HostList.jsx b/awx/ui_next/src/screens/Host/HostList/HostList.jsx index 5c2bbd8670..f9ed610f8a 100644 --- a/awx/ui_next/src/screens/Host/HostList/HostList.jsx +++ b/awx/ui_next/src/screens/Host/HostList/HostList.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useHistory, useLocation, useRouteMatch } from 'react-router-dom'; import { t } from '@lingui/macro'; @@ -17,6 +17,7 @@ import PaginatedTable, { HeaderCell, } from '../../../components/PaginatedTable'; import useRequest, { useDeleteItems } from '../../../util/useRequest'; +import useSelected from '../../../util/useSelected'; import { encodeQueryString, getQSConfig, @@ -36,7 +37,6 @@ function HostList() { const history = useHistory(); const location = useLocation(); const match = useRouteMatch(); - const [selected, setSelected] = useState([]); const parsedQueryStrings = parseQueryString(QS_CONFIG, location.search); const nonDefaultSearchParams = {}; @@ -86,7 +86,14 @@ function HostList() { fetchHosts(); }, [fetchHosts]); - const isAllSelected = selected.length === hosts.length && selected.length > 0; + const { + selected, + isAllSelected, + handleSelect, + selectAll, + clearSelected, + } = useSelected(hosts); + const { isLoading: isDeleteLoading, deleteItems: deleteHosts, @@ -105,19 +112,7 @@ function HostList() { const handleHostDelete = async () => { await deleteHosts(); - setSelected([]); - }; - - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...hosts] : []); - }; - - const handleSelect = host => { - if (selected.some(h => h.id === host.id)) { - setSelected(selected.filter(h => h.id !== host.id)); - } else { - setSelected(selected.concat(host)); - } + clearSelected(); }; const handleSmartInventoryClick = () => { @@ -141,7 +136,7 @@ function HostList() { itemCount={count} pluralizedItemName={t`Hosts`} qsConfig={QS_CONFIG} - onRowClick={handleSelect} + clearSelected={clearSelected} toolbarSearchColumns={[ { name: t`Name`, @@ -175,7 +170,7 @@ function HostList() { {...props} showSelectAll isAllSelected={isAllSelected} - onSelectAll={handleSelectAll} + onSelectAll={selectAll} qsConfig={QS_CONFIG} additionalControls={[ ...(canAdd diff --git a/awx/ui_next/src/screens/Inventory/InventoryHosts/InventoryHostList.jsx b/awx/ui_next/src/screens/Inventory/InventoryHosts/InventoryHostList.jsx index 9b8bd79873..44a52af3b2 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryHosts/InventoryHostList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryHosts/InventoryHostList.jsx @@ -15,6 +15,7 @@ import { ToolbarAddButton, ToolbarDeleteButton, } from '../../../components/PaginatedDataList'; +import useSelected from '../../../util/useSelected'; import AdHocCommands from '../../../components/AdHocCommands/AdHocCommands'; import InventoryHostItem from './InventoryHostItem'; @@ -25,7 +26,6 @@ const QS_CONFIG = getQSConfig('host', { }); function InventoryHostList() { - const [selected, setSelected] = useState([]); const [isAdHocLaunchLoading, setIsAdHocLaunchLoading] = useState(false); const { id } = useParams(); const { search } = useLocation(); @@ -74,17 +74,14 @@ function InventoryHostList() { fetchData(); }, [fetchData]); - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...hosts] : []); - }; + const { + selected, + isAllSelected, + handleSelect, + selectAll, + clearSelected, + } = useSelected(hosts); - const handleSelect = row => { - if (selected.some(s => s.id === row.id)) { - setSelected(selected.filter(s => s.id !== row.id)); - } else { - setSelected(selected.concat(row)); - } - }; const { isLoading: isDeleteLoading, deleteItems: deleteHosts, @@ -99,12 +96,11 @@ function InventoryHostList() { const handleDeleteHosts = async () => { await deleteHosts(); - setSelected([]); + clearSelected(); }; const canAdd = actions && Object.prototype.hasOwnProperty.call(actions, 'POST'); - const isAllSelected = selected.length > 0 && selected.length === hosts.length; return ( <> @@ -115,6 +111,7 @@ function InventoryHostList() { itemCount={hostCount} pluralizedItemName={t`Hosts`} qsConfig={QS_CONFIG} + clearSelected={clearSelected} toolbarSearchableKeys={searchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys} headerRow={ @@ -128,7 +125,7 @@ function InventoryHostList() { {...props} showSelectAll isAllSelected={isAllSelected} - onSelectAll={handleSelectAll} + onSelectAll={selectAll} qsConfig={QS_CONFIG} additionalControls={[ ...(canAdd diff --git a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryList.jsx b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryList.jsx index 6b674fe973..1ca1b7511a 100644 --- a/awx/ui_next/src/screens/Inventory/InventoryList/InventoryList.jsx +++ b/awx/ui_next/src/screens/Inventory/InventoryList/InventoryList.jsx @@ -1,9 +1,10 @@ -import React, { useState, useCallback, useEffect } from 'react'; +import React, { useCallback, useEffect } from 'react'; import { useLocation, useRouteMatch, Link } from 'react-router-dom'; import { t, Plural } from '@lingui/macro'; import { Card, PageSection, DropdownItem } from '@patternfly/react-core'; import { InventoriesAPI } from '../../../api'; import useRequest, { useDeleteItems } from '../../../util/useRequest'; +import useSelected from '../../../util/useSelected'; import AlertModal from '../../../components/AlertModal'; import DatalistToolbar from '../../../components/DataListToolbar'; import ErrorDetail from '../../../components/ErrorDetail'; @@ -27,7 +28,6 @@ const QS_CONFIG = getQSConfig('inventory', { function InventoryList() { const location = useLocation(); const match = useRouteMatch(); - const [selected, setSelected] = useState([]); const { result: { @@ -89,8 +89,14 @@ function InventoryList() { QS_CONFIG ); - const isAllSelected = - selected.length === inventories.length && selected.length > 0; + const { + selected, + isAllSelected, + handleSelect, + selectAll, + clearSelected, + } = useSelected(inventories); + const { isLoading: isDeleteLoading, deleteItems: deleteInventories, @@ -107,26 +113,12 @@ function InventoryList() { const handleInventoryDelete = async () => { await deleteInventories(); - setSelected([]); + clearSelected(); }; const hasContentLoading = isDeleteLoading || isLoading; const canAdd = actions && actions.POST; - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...inventories] : []); - }; - - const handleSelect = row => { - if (!row.pending_deletion) { - if (selected.some(s => s.id === row.id)) { - setSelected(selected.filter(s => s.id !== row.id)); - } else { - setSelected(selected.concat(row)); - } - } - }; - const deleteDetailsRequests = relatedResourceDeleteRequests.inventory( selected[0] ); @@ -197,6 +189,7 @@ function InventoryList() { ]} toolbarSearchableKeys={searchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys} + clearSelected={clearSelected} headerRow={ {t`Name`} @@ -211,7 +204,7 @@ function InventoryList() { {...props} showSelectAll isAllSelected={isAllSelected} - onSelectAll={handleSelectAll} + onSelectAll={selectAll} qsConfig={QS_CONFIG} additionalControls={[ ...(canAdd ? [addButton] : []), @@ -251,7 +244,11 @@ function InventoryList() { ? `${match.url}/smart_inventory/${inventory.id}/details` : `${match.url}/inventory/${inventory.id}/details` } - onSelect={() => handleSelect(inventory)} + onSelect={() => { + if (!inventory.pending_deletion) { + handleSelect(inventory); + } + }} isSelected={selected.some(row => row.id === inventory.id)} /> )} diff --git a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx index 554660b15b..236ec0b656 100644 --- a/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx +++ b/awx/ui_next/src/screens/NotificationTemplate/NotificationTemplateDetail/NotificationTemplateDetail.jsx @@ -306,7 +306,7 @@ function NotificationTemplateDetail({ template, defaultMessages }) { label={t`HTTP Headers`} value={JSON.stringify(configuration.headers)} mode="json" - rows="6" + rows={6} dataCy="nt-detail-webhook-headers" /> diff --git a/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationList.jsx b/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationList.jsx index 4b5907dad9..1217c6e0b7 100644 --- a/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationList.jsx +++ b/awx/ui_next/src/screens/Organization/OrganizationList/OrganizationList.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useLocation, useRouteMatch } from 'react-router-dom'; import { t, Plural } from '@lingui/macro'; import { Card, PageSection } from '@patternfly/react-core'; @@ -17,6 +17,7 @@ import PaginatedTable, { HeaderCell, } from '../../../components/PaginatedTable'; import { getQSConfig, parseQueryString } from '../../../util/qs'; +import useSelected from '../../../util/useSelected'; import OrganizationListItem from './OrganizationListItem'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; @@ -30,8 +31,6 @@ function OrganizationsList() { const location = useLocation(); const match = useRouteMatch(); - const [selected, setSelected] = useState([]); - const addUrl = `${match.url}/add`; const { @@ -77,8 +76,14 @@ function OrganizationsList() { fetchOrganizations(); }, [fetchOrganizations]); - const isAllSelected = - selected.length === organizations.length && selected.length > 0; + const { + selected, + isAllSelected, + handleSelect, + selectAll, + clearSelected, + } = useSelected(organizations); + const { isLoading: isDeleteLoading, deleteItems: deleteOrganizations, @@ -99,23 +104,12 @@ function OrganizationsList() { const handleOrgDelete = async () => { await deleteOrganizations(); - setSelected([]); + clearSelected(); }; const hasContentLoading = isDeleteLoading || isOrgsLoading; const canAdd = actions && actions.POST; - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...organizations] : []); - }; - - const handleSelect = row => { - if (selected.some(s => s.id === row.id)) { - setSelected(selected.filter(s => s.id !== row.id)); - } else { - setSelected(selected.concat(row)); - } - }; const deleteDetailsRequests = relatedResourceDeleteRequests.organization( selected[0] ); @@ -131,6 +125,7 @@ function OrganizationsList() { itemCount={organizationCount} pluralizedItemName={t`Organizations`} qsConfig={QS_CONFIG} + clearSelected={clearSelected} toolbarSearchColumns={[ { name: t`Name`, @@ -165,7 +160,7 @@ function OrganizationsList() { {...props} showSelectAll isAllSelected={isAllSelected} - onSelectAll={handleSelectAll} + onSelectAll={selectAll} qsConfig={QS_CONFIG} additionalControls={[ ...(canAdd diff --git a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx index eae938776d..66b9dff1f9 100644 --- a/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx +++ b/awx/ui_next/src/screens/Project/ProjectList/ProjectList.jsx @@ -1,4 +1,4 @@ -import React, { Fragment, useState, useEffect, useCallback } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useLocation, useRouteMatch } from 'react-router-dom'; import { t, Plural } from '@lingui/macro'; import { Card, PageSection } from '@patternfly/react-core'; @@ -17,6 +17,7 @@ import PaginatedTable, { HeaderCell, } from '../../../components/PaginatedTable'; import useWsProjects from './useWsProjects'; +import useSelected from '../../../util/useSelected'; import { relatedResourceDeleteRequests } from '../../../util/getRelatedResourceDeleteDetails'; import { getQSConfig, parseQueryString } from '../../../util/qs'; @@ -31,7 +32,6 @@ const QS_CONFIG = getQSConfig('project', { function ProjectList() { const location = useLocation(); const match = useRouteMatch(); - const [selected, setSelected] = useState([]); const { result: { @@ -78,8 +78,15 @@ function ProjectList() { const projects = useWsProjects(results); - const isAllSelected = - selected.length === projects.length && selected.length > 0; + const { + selected, + isAllSelected, + handleSelect, + setSelected, + selectAll, + clearSelected, + } = useSelected(projects); + const { isLoading: isDeleteLoading, deleteItems: deleteProjects, @@ -104,24 +111,12 @@ function ProjectList() { const hasContentLoading = isDeleteLoading || isLoading; const canAdd = actions && actions.POST; - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...projects] : []); - }; - - const handleSelect = row => { - if (selected.some(s => s.id === row.id)) { - setSelected(selected.filter(s => s.id !== row.id)); - } else { - setSelected(selected.concat(row)); - } - }; - const deleteDetailsRequests = relatedResourceDeleteRequests.project( selected[0] ); return ( - + <> - + ); } diff --git a/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx b/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx index 6bfbe89005..36c43a1525 100644 --- a/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx +++ b/awx/ui_next/src/screens/Team/TeamList/TeamList.jsx @@ -1,4 +1,4 @@ -import React, { Fragment, useState, useEffect, useCallback } from 'react'; +import React, { useEffect, useCallback } from 'react'; import { useLocation, useRouteMatch } from 'react-router-dom'; import { t } from '@lingui/macro'; @@ -17,6 +17,7 @@ import { ToolbarAddButton, ToolbarDeleteButton, } from '../../../components/PaginatedDataList'; +import useSelected from '../../../util/useSelected'; import { getQSConfig, parseQueryString } from '../../../util/qs'; import TeamListItem from './TeamListItem'; @@ -30,7 +31,6 @@ const QS_CONFIG = getQSConfig('team', { function TeamList() { const location = useLocation(); const match = useRouteMatch(); - const [selected, setSelected] = useState([]); const { result: { @@ -75,7 +75,14 @@ function TeamList() { fetchTeams(); }, [fetchTeams]); - const isAllSelected = selected.length === teams.length && selected.length > 0; + const { + selected, + isAllSelected, + handleSelect, + selectAll, + clearSelected, + } = useSelected(teams); + const { isLoading: isDeleteLoading, deleteItems: deleteTeams, @@ -94,26 +101,14 @@ function TeamList() { const handleTeamDelete = async () => { await deleteTeams(); - setSelected([]); + clearSelected(); }; const hasContentLoading = isDeleteLoading || isLoading; const canAdd = actions && actions.POST; - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...teams] : []); - }; - - const handleSelect = row => { - if (selected.some(s => s.id === row.id)) { - setSelected(selected.filter(s => s.id !== row.id)); - } else { - setSelected(selected.concat(row)); - } - }; - return ( - + <> - + ); } diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx index 55a6e9a68d..ff86fa7b7d 100644 --- a/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx +++ b/awx/ui_next/src/screens/Template/Survey/SurveyList.jsx @@ -19,6 +19,7 @@ import { ToolbarAddButton } from '../../../components/PaginatedDataList'; import SurveyListItem from './SurveyListItem'; import SurveyToolbar from './SurveyToolbar'; import SurveyPreviewModal from './SurveyPreviewModal'; +import useSelected from '../../../util/useSelected'; const Button = styled(_Button)` margin: 20px; @@ -36,15 +37,16 @@ function SurveyList({ const match = useRouteMatch(); const questions = survey?.spec || []; - const [selected, setSelected] = useState([]); const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false); - const isAllSelected = - selected.length === questions?.length && selected.length > 0; - const handleSelectAll = isSelected => { - setSelected(isSelected ? [...questions] : []); - }; + const { + selected, + isAllSelected, + setSelected, + selectAll, + clearSelected, + } = useSelected(questions); const handleSelect = item => { if (selected.some(q => q.variable === item.variable)) { @@ -61,7 +63,7 @@ function SurveyList({ await updateSurvey(questions.filter(q => !selected.includes(q))); } setIsDeleteModalOpen(false); - setSelected([]); + clearSelected(); }; const moveUp = question => { @@ -91,7 +93,7 @@ function SurveyList({ isOpen={isDeleteModalOpen} onClose={() => { setIsDeleteModalOpen(false); - setSelected([]); + clearSelected(); }} actions={[