diff --git a/CHANGELOG.md b/CHANGELOG.md index a64797f3..d83e4694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,7 @@ * [UIBULKED-246](https://issues.folio.org/browse/UIBULKED-246) Enabling Build query button on Query tab. * [UIBULKED-367](https://issues.folio.org/browse/UIBULKED-367) Logs - Provide a link to file with identifiers of the records affected by query. * [UIBULKED-412](https://issues.folio.org/browse/UIBULKED-412) Not all but up to 10 note types are displayed in Bulk edit. +* [UIBULKED-411](https://issues.folio.org/browse/UIBULKED-411) Refactoring of ListFilters component ## [4.0.0](https://github.com/folio-org/ui-bulk-edit/tree/v4.0.0) (2023-10-12) diff --git a/src/components/BulkEditList/BulkEditList.js b/src/components/BulkEditList/BulkEditList.js index d024fe16..364ad065 100644 --- a/src/components/BulkEditList/BulkEditList.js +++ b/src/components/BulkEditList/BulkEditList.js @@ -13,7 +13,6 @@ import { AppIcon } from '@folio/stripes/core'; import { noop } from 'lodash/util'; import { useHistory } from 'react-router'; -import { BulkEditListFilters } from './BulkEditListFilters/BulkEditListFilters'; import { BulkEditListResult } from './BulkEditListResult'; import { BulkEditActionMenu } from '../BulkEditActionMenu'; import { BulkEditManualUploadModal } from './BulkEditListResult/BulkEditManualUploadModal'; @@ -22,7 +21,9 @@ import { CRITERIA, APPROACHES, EDITING_STEPS, - FILTERS, + LOGS_FILTERS, + IDENTIFIER_FILTERS, + QUERY_FILTERS, } from '../../constants'; import { BulkEditInApp } from './BulkEditListResult/BulkEditInApp/BulkEditInApp'; import BulkEditInAppPreviewModal from './BulkEditListResult/BulkEditInAppPreviewModal/BulkEditInAppPreviewModal'; @@ -31,6 +32,8 @@ import { RootContext } from '../../context/RootContext'; import BulkEditLogs from '../BulkEditLogs/BulkEditLogs'; import { useResetAppState } from '../../hooks/useResetAppState'; import BulkEditInAppLayer from './BulkEditListResult/BulkEditInAppLayer/BulkEditInAppLayer'; +import { BulkEditListSidebar } from './BulkEditListSidebar/BulkEditListSidebar'; +import { useSearchParams } from '../../hooks/useSearchParams'; export const BulkEditList = () => { const history = useHistory(); @@ -46,37 +49,35 @@ export const BulkEditList = () => { const [confirmedFileName, setConfirmedFileName] = useState(null); const [inAppCommitted, setInAppCommitted] = useState(false); const [filtersTab, setFiltersTab] = useState({ + identifierTab: [], + queryTab: [], logsTab: [], }); const { isActionMenuShown } = useBulkPermissions(); const { id: bulkOperationId } = usePathParams('/bulk-edit/:id'); - const step = search.get('step'); - const capabilities = search.get('capabilities'); - const criteria = search.get('criteria'); - const logsFilters = Object.values(FILTERS).map((el) => search.getAll(el)); + const { + step, + capabilities, + criteria, + initialFileName + } = useSearchParams(); + const identifierFilters = Object.values(IDENTIFIER_FILTERS).map((el) => search.getAll(el)); + const queryFilters = Object.values(QUERY_FILTERS).map((el) => search.getAll(el)); + const logsFilters = Object.values(LOGS_FILTERS).map((el) => search.getAll(el)); useEffect(() => { if (history.location.search) { setFiltersTab(prevState => ({ ...prevState, + queryTab: queryFilters, + identifierTab: identifierFilters, logsTab: logsFilters, })); } }, [history.location]); - const initialFiltersState = { - criteria: CRITERIA.IDENTIFIER, - capabilities: '', - queryText: '', - recordIdentifier: '', - }; - - const [filters, setFilters] = useState(initialFiltersState); - useResetAppState({ - initialFiltersState, - setFilters, setConfirmedFileName, setCountOfRecords, setVisibleColumns, @@ -133,17 +134,15 @@ export const BulkEditList = () => { }; const paneTitle = useMemo(() => { - const fileUploadedName = search.get('fileName'); - - if (confirmedFileName || fileUploadedName) { + if (confirmedFileName || initialFileName) { return ( ); } else return ; - }, [confirmedFileName, history.location.search]); + }, [confirmedFileName, initialFileName, history.location.search]); const changedPaneSubTitle = useMemo(() => ( step === EDITING_STEPS.UPLOAD ? @@ -176,37 +175,30 @@ export const BulkEditList = () => { confirmedFileName, inAppCommitted, setInAppCommitted, + isFileUploaded, + setIsFileUploaded, }} > - {/* FILTERS PANE */} + {/* LOGS_FILTERS PANE */} } > - + {/* RESULT PANES */} - { - isLogsTab && - } - { - !isLogsTab && ( - - - - ) - } + { isLogsTab && } + + { !isLogsTab && ( + + + + )} {/* IN_APP APPROACH */} { { - const permissions = useBulkPermissions(); - const { - isDropZoneDisabled: isDropZoneDisabledPerm, - hasInAppEditPerms, - isSelectIdentifiersDisabled, - hasLogViewPerms, - hasQueryPerms, - hasUsersViewPerms, - hasInventoryInstanceViewPerms, - hasCsvViewPerms, - hasInAppUsersEditPerms, - hasInAppViewPerms, - hasAnyInventoryWithInAppView, - hasAnyUserWithBulkPerm, - } = permissions; - const showCallout = useShowCallout(); - const history = useHistory(); - const location = useLocation(); - - const search = new URLSearchParams(location.search); - const criteria = search.get('criteria'); - const initialCapabilities = search.get('capabilities'); - const initialFileName = search.get('fileName'); - const initialStep = search.get('step'); - const logFilters = Object.values(FILTERS).map((el) => search.getAll(el)); - - const isQuery = criteria === CRITERIA.QUERY; - const isLogs = criteria === CRITERIA.LOGS; - const isIdentifier = criteria === CRITERIA.IDENTIFIER; - const { capabilities, recordIdentifier } = filters; - - const capabilitiesFilterOptions = getCapabilityOptions(criteria, permissions); - - const isQueryBuilderEnabledForUsers = hasUsersViewPerms && (hasCsvViewPerms || hasInAppUsersEditPerms); - const isQueryBuilderEnabledForItems = hasInventoryInstanceViewPerms && hasInAppViewPerms; - const isQueryBuilderDisabled = (!isQueryBuilderEnabledForUsers && !isQueryBuilderEnabledForItems) || !capabilities; - - const initialFilter = { - capabilities: initialCapabilities, - criteria: CRITERIA.LOGS, - fileName: initialFileName, - step: initialStep, - }; - - const [ - activeFilters, - applyFilters, - resetFilters, - ] = useLocationFilters({ location, history, initialFilter }); - - const applyFiltersAdapter = (callBack) => ({ name, values }) => callBack(name, values); - - const adaptedApplyFilters = useCallback( - applyFiltersAdapter(applyFilters), - [applyFilters], - ); - - const { setVisibleColumns, setInAppCommitted } = useContext(RootContext); - const [isDropZoneActive, setDropZoneActive] = useState(false); - const [isDropZoneDisabled, setIsDropZoneDisabled] = useState(true); - - const { fileUpload, isLoading } = useUpload(); - const { bulkOperationStart } = useBulkOperationStart(); - - const { recordTypes } = useRecordTypes({ enabled: isQuery }); - - const recordTypeId = recordTypes?.find(type => type.label === getRecordType(initialCapabilities))?.id; - - const { - entityTypeDataSource, - queryDetailsDataSource, - testQueryDataSource, - getParamsSource, - cancelQueryDataSource, - } = useQueryPlugin(recordTypeId); - - const handleChange = (value, field) => setFilters(prev => ({ - ...prev, [field]: value, - })); - - const handleCapabilityChange = (e) => { - const value = e.target.value; - - setFilters(prev => ({ - ...prev, - capabilities: value, - recordIdentifier: '', - })); - - history.replace({ - pathname: '/bulk-edit', - search: buildSearch({ - capabilities: value, - identifier: null, - step: null, - fileName: null, - }, location.search), - }); - - setVisibleColumns(null); - setIsFileUploaded(false); - setInAppCommitted(false); - }; - - const handleCriteriaChange = (value) => { - setFilters(prev => ({ ...prev, recordIdentifier: '', criteria: value })); - - history.replace({ - search: buildSearch({ identifier: '', criteria: value }, location.search), - }); - }; - - const handleDragEnter = () => { - setDropZoneActive(true); - }; - - const handleDragLeave = () => { - setDropZoneActive(false); - }; - - const handleRecordIdentifierChange = useCallback((e) => { - handleChange(e.target.value, 'recordIdentifier'); - const [status, entityType, operationType, startTime, endTime] = logFilters; - - history.replace({ - pathname: '/bulk-edit', - search: buildSearch({ - identifier: e.target.value, - capabilities: initialCapabilities, - criteria, - status, - entityType, - endTime, - startTime, - operationType, - step: null, - }), - }); - - setVisibleColumns(null); - setIsFileUploaded(false); - setInAppCommitted(false); - }, [location.search]); - - const uploadFileFlow = async (fileToUpload) => { - try { - const { id } = await fileUpload({ - fileToUpload, - entityType: capabilities, - identifierType: recordIdentifier, - }); - - const { status, errorMessage } = await bulkOperationStart({ - id, - step: EDITING_STEPS.UPLOAD, - }); - - if (errorMessage.includes(ERRORS.TOKEN)) throw Error(ERRORS.TOKEN); - if (status === JOB_STATUSES.FAILED) throw Error(); - - history.replace({ - pathname: `/bulk-edit/${id}/progress`, - search: buildSearch({ fileName: fileToUpload.name }, location.search), - }); - - setIsFileUploaded(true); - } catch ({ message }) { - if (message === ERRORS.TOKEN) { - showCallout({ - message: , - type: 'error', - }); - } else { - showCallout({ - message: , - type: 'error', - }); - } - } - }; - - const handleDrop = async (fileToUpload) => { - if (!fileToUpload) return; - - await uploadFileFlow(fileToUpload); - - setDropZoneActive(false); - }; - - const uploaderSubTitle = useMemo(() => { - const messagePrefix = recordIdentifier ? `.${recordIdentifier}` : ''; - - return ; - }, [recordIdentifier]); - - useEffect(() => { - if (isFileUploaded || !recordIdentifier || initialFileName) { - setIsDropZoneDisabled(true); - } else { - setIsDropZoneDisabled(false); - } - }, [isFileUploaded, recordIdentifier]); - - useEffect(() => { - const identifier = search.get('identifier'); - - if (identifier) { - handleChange(identifier, 'recordIdentifier'); - } - - if (initialCapabilities) { - handleChange(initialCapabilities, 'capabilities'); - } - }, [location.search]); - - const renderCapabilities = () => ( - - ); - - const renderListSelect = () => ( - - ); - - const renderListFileUploader = () => ( - - ); - - const renderLogsFilter = () => ( - - ); - - return ( - <> - - - - - {/* IDENTIFIER FILTER */} - {isIdentifier && ( - <> - {renderCapabilities()} - {renderListSelect()} - {renderListFileUploader()} - - )} - - {/* QUERY FILTER */} - {isQuery && hasQueryPerms && ( - <> - {renderCapabilities()} - {}} - cancelQueryDataSource={cancelQueryDataSource} - /> - - )} - - {/* LOGS FILTER */} - {isLogs && renderLogsFilter()} - - ); -}; - -BulkEditListFilters.propTypes = { - filters: PropTypes.object.isRequired, - setFilters: PropTypes.func.isRequired, - isFileUploaded: PropTypes.bool.isRequired, - setIsFileUploaded: PropTypes.func.isRequired, -}; diff --git a/src/components/BulkEditList/BulkEditListResult/BulkEditManualUploadModal/BulkEditManualUploadModal.js b/src/components/BulkEditList/BulkEditListResult/BulkEditManualUploadModal/BulkEditManualUploadModal.js index 1912f45e..5c36ccaa 100644 --- a/src/components/BulkEditList/BulkEditListResult/BulkEditManualUploadModal/BulkEditManualUploadModal.js +++ b/src/components/BulkEditList/BulkEditListResult/BulkEditManualUploadModal/BulkEditManualUploadModal.js @@ -11,7 +11,6 @@ import { } from '@folio/stripes/components'; import { buildSearch, useShowCallout } from '@folio/stripes-acq-components'; -import { ListFileUploader } from '../../../ListFileUploader'; import { APPROACHES, CAPABILITIES, @@ -24,10 +23,11 @@ import { useBulkOperationStart, } from '../../../../hooks/api'; import { useBulkOperationDelete } from '../../../../hooks/api/useBulkOperationDelete'; +import { ListFileUploader } from '../../../shared/ListFileUploader'; +import { useSearchParams } from '../../../../hooks/useSearchParams'; const BulkEditManualUploadModal = ({ operationId, - identifier, open, onCancel, setCountOfRecords, @@ -37,6 +37,7 @@ const BulkEditManualUploadModal = ({ const intl = useIntl(); const callout = useShowCallout(); const controller = useRef(null); + const { identifier } = useSearchParams(); const { fileUpload } = useUpload(); const { bulkOperationStart } = useBulkOperationStart(); @@ -216,7 +217,6 @@ const BulkEditManualUploadModal = ({ BulkEditManualUploadModal.propTypes = { operationId: PropTypes.string, - identifier: PropTypes.string, open: PropTypes.bool.isRequired, onCancel: PropTypes.func.isRequired, setCountOfRecords: PropTypes.func, diff --git a/src/components/BulkEditList/BulkEditListSidebar/BulkEditListSidebar.js b/src/components/BulkEditList/BulkEditListSidebar/BulkEditListSidebar.js new file mode 100644 index 00000000..3b650fea --- /dev/null +++ b/src/components/BulkEditList/BulkEditListSidebar/BulkEditListSidebar.js @@ -0,0 +1,54 @@ +import React from 'react'; +import { useHistory, useLocation } from 'react-router-dom'; + +import { ButtonGroup } from '@folio/stripes/components'; +import { buildSearch } from '@folio/stripes-acq-components'; + +import { CRITERIA } from '../../../constants'; +import { useBulkPermissions } from '../../../hooks'; +import { TabsFilter } from './TabsFilter/TabsFilter'; +import { IdentifierTab } from './IdentifierTab/IdentifierTab'; +import { QueryTab } from './QueryTab/QueryTab'; +import { LogsTab } from './LogsTab/LogsTab'; +import { useSearchParams } from '../../../hooks/useSearchParams'; + +export const BulkEditListSidebar = () => { + const history = useHistory(); + const location = useLocation(); + const { hasLogViewPerms, hasQueryPerms } = useBulkPermissions(); + const { criteria } = useSearchParams(); + + const isQuery = criteria === CRITERIA.QUERY; + const isLogs = criteria === CRITERIA.LOGS; + const isIdentifier = criteria === CRITERIA.IDENTIFIER; + + const handleCriteriaChange = (value) => { + history.replace({ + search: buildSearch({ criteria: value }, location.search), + }); + }; + + return ( + <> + + + + + {/* IDENTIFIER TAB */} + {isIdentifier && ( + + )} + + {/* QUERY TAB */} + {isQuery && hasQueryPerms && } + + {/* LOGS TAB */} + {isLogs && } + + ); +}; diff --git a/src/components/BulkEditList/BulkEditListSidebar/IdentifierTab/IdentifierTab.js b/src/components/BulkEditList/BulkEditListSidebar/IdentifierTab/IdentifierTab.js new file mode 100644 index 00000000..d521f3b9 --- /dev/null +++ b/src/components/BulkEditList/BulkEditListSidebar/IdentifierTab/IdentifierTab.js @@ -0,0 +1,200 @@ +import React, { + useCallback, + useContext, + useMemo, + useState +} from 'react'; +import { buildSearch, useShowCallout } from '@folio/stripes-acq-components'; +import { FormattedMessage } from 'react-intl'; +import { useHistory, useLocation } from 'react-router-dom'; + +import { getCapabilityOptions, isCapabilityDisabled } from '../../../../utils/helpers'; +import { ListFileUploader } from '../../../shared/ListFileUploader'; +import { Capabilities } from '../../../shared/Capabilities/Capabilities'; +import { ListSelect } from '../../../shared/ListSelect/ListSelect'; +import { + CRITERIA, + EDITING_STEPS, + IDENTIFIER_FILTERS, + JOB_STATUSES, + TRANSLATION_SUFFIX +} from '../../../../constants'; +import { useBulkPermissions, useLocationFilters } from '../../../../hooks'; +import { useSearchParams } from '../../../../hooks/useSearchParams'; +import { useBulkOperationStart, useUpload } from '../../../../hooks/api'; +import { getIsDisabledByPerm } from '../utils/getIsDisabledByPerm'; +import { RootContext } from '../../../../context/RootContext'; + +export const IdentifierTab = () => { + const history = useHistory(); + const location = useLocation(); + const showCallout = useShowCallout(); + const permissions = useBulkPermissions(); + + const { + isFileUploaded, + setIsFileUploaded, + setVisibleColumns, + setInAppCommitted, + } = useContext(RootContext); + + const { + criteria, + initialFileName, + step, + capabilities, + identifier + } = useSearchParams(); + + const [isDropZoneActive, setDropZoneActive] = useState(false); + const { fileUpload, isLoading } = useUpload(); + const { bulkOperationStart } = useBulkOperationStart(); + + const [activeFilters] = useLocationFilters({ + initialFilter: { + step, + capabilities, + identifier, + criteria: CRITERIA.IDENTIFIER, + fileName: initialFileName, + } + }); + + const [recordType] = activeFilters[IDENTIFIER_FILTERS.CAPABILITIES] || []; + const [recordIdentifier] = activeFilters[IDENTIFIER_FILTERS.IDENTIFIER] || []; + const isDropZoneDisabled = isFileUploaded || !recordIdentifier || initialFileName; + const capabilitiesFilterOptions = getCapabilityOptions(criteria, permissions); + + const { + isSelectIdentifiersDisabled, + hasAnyUserWithBulkPerm, + hasAnyInventoryWithInAppView, + isDropZoneDisabledPerm, + hasInAppEditPerms, + } = permissions; + + const isRecordIdentifierSelectDisabled = getIsDisabledByPerm( + recordType, + isSelectIdentifiersDisabled, + hasAnyUserWithBulkPerm, + hasAnyInventoryWithInAppView + ); + + const isFileUploadDisabled = isDropZoneDisabled || getIsDisabledByPerm( + recordType, + isDropZoneDisabledPerm, + hasAnyUserWithBulkPerm, + hasAnyInventoryWithInAppView + ); + + const handleRecordIdentifierChange = useCallback((e) => { + history.replace({ + pathname: '/bulk-edit', + search: buildSearch({ + identifier: e.target.value, + step: null, + }, location.search), + }); + + setIsFileUploaded(false); + setVisibleColumns(null); + setInAppCommitted(false); + }, [location.search]); + + const handleCapabilityChange = (e) => { + history.replace({ + pathname: '/bulk-edit', + search: buildSearch({ + capabilities: e.target.value, + identifier: '', + step: null, + fileName: null, + }, location.search), + }); + + setVisibleColumns(null); + setIsFileUploaded(false); + setInAppCommitted(false); + }; + + const handleDragEnter = () => { + setDropZoneActive(true); + }; + + const handleDragLeave = () => { + setDropZoneActive(false); + }; + + const uploadFileFlow = async (fileToUpload) => { + try { + const { id } = await fileUpload({ + fileToUpload, + entityType: recordType, + identifierType: recordIdentifier, + }); + + const { status } = await bulkOperationStart({ + id, + step: EDITING_STEPS.UPLOAD, + }); + + if (status === JOB_STATUSES.FAILED) throw Error(); + + history.replace({ + pathname: `/bulk-edit/${id}/progress`, + search: buildSearch({ fileName: fileToUpload.name }, location.search), + }); + + setIsFileUploaded(true); + } catch ({ message }) { + showCallout({ + message: , + type: 'error', + }); + } + }; + + const handleDrop = async (fileToUpload) => { + if (!fileToUpload) return; + + await uploadFileFlow(fileToUpload); + + setDropZoneActive(false); + }; + + const uploaderSubTitle = useMemo(() => { + const messagePrefix = recordIdentifier ? `.${recordIdentifier}` : ''; + const key = recordType ?? ''; + + return ; + }, [recordIdentifier, recordType]); + + return ( + <> + + + + + ); +}; diff --git a/src/components/BulkEditList/BulkEditListFilters/LogsFilters/LogsFilters.js b/src/components/BulkEditList/BulkEditListSidebar/LogsTab/LogsTab.js similarity index 57% rename from src/components/BulkEditList/BulkEditListFilters/LogsFilters/LogsFilters.js rename to src/components/BulkEditList/BulkEditListSidebar/LogsTab/LogsTab.js index 593cc0c0..93873c98 100644 --- a/src/components/BulkEditList/BulkEditListFilters/LogsFilters/LogsFilters.js +++ b/src/components/BulkEditList/BulkEditListSidebar/LogsTab/LogsTab.js @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import { FormattedMessage, useIntl, @@ -12,30 +11,52 @@ import { } from '@folio/stripes/components'; import { createClearFilterHandler, - DATE_FORMAT, ResetButton, + DATE_FORMAT, + ResetButton, } from '@folio/stripes-acq-components'; import { CheckboxFilter, DateRangeFilter, } from '@folio/stripes/smart-components'; -import React from 'react'; +import React, { useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import moment from 'moment'; import { - FILTERS, + LOGS_FILTERS, FILTER_OPTIONS, + CRITERIA, } from '../../../../constants'; import { useBulkOperationUsers } from '../../../../hooks/api/useBulkOperationUsers'; import { getFullName } from '../../../../utils/getFullName'; +import { useLocationFilters } from '../../../../hooks'; +import { useSearchParams } from '../../../../hooks/useSearchParams'; -export const LogsFilters = ({ - onChange, - activeFilters, - resetFilter, -}) => { +export const LogsTab = () => { const intl = useIntl(); const location = useLocation(); + const { + step, + initialFileName, + capabilities + } = useSearchParams(); + + const [ + activeFilters, + applyFilters, + resetFilters, + ] = useLocationFilters({ + initialFilter: { + step, + capabilities, + criteria: CRITERIA.LOGS, + fileName: initialFileName, + } + }); + + const applyFiltersAdapter = (callBack) => ({ name, values }) => callBack(name, values); + + const adaptedApplyFilters = useCallback(applyFiltersAdapter(applyFilters), [applyFilters]); const { data } = useBulkOperationUsers(); @@ -74,7 +95,7 @@ export const LogsFilters = ({ return `${startDate}:${endDateCorrected}`; }; - const getIsDisabled = () => Object.values(FILTERS).some((el) => location.search.includes(el)); + const getIsDisabled = () => Object.values(LOGS_FILTERS).some((el) => location.search.includes(el)); return (
@@ -82,93 +103,87 @@ export const LogsFilters = ({ } /> } - onClearFilter={createClearFilterHandler(onChange, FILTERS.STATUS)} + onClearFilter={createClearFilterHandler(adaptedApplyFilters, LOGS_FILTERS.STATUS)} > } - onClearFilter={createClearFilterHandler(onChange, FILTERS.CAPABILITY)} + onClearFilter={createClearFilterHandler(adaptedApplyFilters, LOGS_FILTERS.CAPABILITY)} > } - onClearFilter={createClearFilterHandler(onChange, FILTERS.START_DATE)} + onClearFilter={createClearFilterHandler(adaptedApplyFilters, LOGS_FILTERS.START_DATE)} > } - onClearFilter={createClearFilterHandler(onChange, FILTERS.END_DATE)} + onClearFilter={createClearFilterHandler(adaptedApplyFilters, LOGS_FILTERS.END_DATE)} > } - onClearFilter={createClearFilterHandler(onChange, FILTERS.USER)} + onClearFilter={createClearFilterHandler(adaptedApplyFilters, LOGS_FILTERS.USER)} > onChange({ name: FILTERS.USER, values })} + value={activeFilters[LOGS_FILTERS.USER]?.toString()} + onChange={values => adaptedApplyFilters({ name: LOGS_FILTERS.USER, values })} />
); }; - -LogsFilters.propTypes = { - onChange: PropTypes.func, - activeFilters: PropTypes.object, - resetFilter: PropTypes.func, -}; diff --git a/src/components/BulkEditList/BulkEditListFilters/LogsFilters/LogsFilters.test.js b/src/components/BulkEditList/BulkEditListSidebar/LogsTab/LogsTab.test.js similarity index 97% rename from src/components/BulkEditList/BulkEditListFilters/LogsFilters/LogsFilters.test.js rename to src/components/BulkEditList/BulkEditListSidebar/LogsTab/LogsTab.test.js index e342893e..e8d18531 100644 --- a/src/components/BulkEditList/BulkEditListFilters/LogsFilters/LogsFilters.test.js +++ b/src/components/BulkEditList/BulkEditListSidebar/LogsTab/LogsTab.test.js @@ -16,7 +16,7 @@ import '../../../../../test/jest/__mock__'; import { queryClient } from '../../../../../test/jest/utils/queryClient'; -import { LogsFilters } from './LogsFilters'; +import { LogsTab } from './LogsTab'; const activeFiltersMock = { STATUS: ['New'], @@ -34,7 +34,7 @@ const renderLogsFilters = () => { render( - { + const history = useHistory(); + const { + queryRecordType, + criteria, + step, + initialFileName + } = useSearchParams(); + const { setVisibleColumns } = useContext(RootContext); + const { recordTypes } = useRecordTypes(); + const permissions = useBulkPermissions(); + const { + hasInAppEditPerms, + hasInAppViewPerms, + hasInventoryInstanceViewPerms, + hasUsersViewPerms, + hasCsvViewPerms, + hasInAppUsersEditPerms, + } = permissions; + + const [activeFilters] = useLocationFilters({ + initialFilter: { + step, + queryRecordType, + criteria: CRITERIA.QUERY, + fileName: initialFileName, + } + }); + + const [recordType] = activeFilters[QUERY_FILTERS.RECORD_TYPE] || []; + + const capabilitiesFilterOptions = getCapabilityOptions(criteria, permissions); + const recordTypeId = recordTypes?.find(type => type.label === getRecordType(recordType))?.id; + const isQueryBuilderEnabledForUsers = hasUsersViewPerms && (hasCsvViewPerms || hasInAppUsersEditPerms); + const isQueryBuilderEnabledForItems = hasInventoryInstanceViewPerms && hasInAppViewPerms; + const isQueryBuilderDisabled = (!isQueryBuilderEnabledForUsers && !isQueryBuilderEnabledForItems) || !recordTypeId; + + const { + entityTypeDataSource, + queryDetailsDataSource, + testQueryDataSource, + getParamsSource, + cancelQueryDataSource, + } = useQueryPlugin(recordTypeId); + + const handleCapabilityChange = (e) => { + history.replace({ + pathname: '/bulk-edit', + search: buildSearch({ + queryRecordType: e.target.value, + step: null, + fileName: null, + }, history.location.search), + }); + + setVisibleColumns(null); + }; + + + return ( + <> + + {}} + cancelQueryDataSource={cancelQueryDataSource} + /> + + ); +}; diff --git a/src/components/BulkEditList/BulkEditListFilters/FilterTabs/FilterTabs.js b/src/components/BulkEditList/BulkEditListSidebar/TabsFilter/TabsFilter.js similarity index 91% rename from src/components/BulkEditList/BulkEditListFilters/FilterTabs/FilterTabs.js rename to src/components/BulkEditList/BulkEditListSidebar/TabsFilter/TabsFilter.js index f2cd5464..0ae65b5f 100644 --- a/src/components/BulkEditList/BulkEditListFilters/FilterTabs/FilterTabs.js +++ b/src/components/BulkEditList/BulkEditListSidebar/TabsFilter/TabsFilter.js @@ -5,7 +5,11 @@ import PropTypes from 'prop-types'; import { CRITERIA } from '../../../../constants'; import { useBulkPermissions } from '../../../../hooks'; -const FilterTabs = ({ criteria, hasLogViewPerms, onCriteriaChange }) => { +export const TabsFilter = ({ + criteria, + hasLogViewPerms, + onCriteriaChange +}) => { const buttonStyleActive = (criteriaToCompare) => (criteria === criteriaToCompare ? 'primary' : 'default'); const { hasQueryPerms } = useBulkPermissions(); @@ -38,10 +42,8 @@ const FilterTabs = ({ criteria, hasLogViewPerms, onCriteriaChange }) => { ); }; -FilterTabs.propTypes = { +TabsFilter.propTypes = { criteria: PropTypes.string, hasLogViewPerms: PropTypes.bool, onCriteriaChange: PropTypes.func, }; - -export default FilterTabs; diff --git a/src/components/BulkEditList/BulkEditListFilters/utils/getFileInfo.js b/src/components/BulkEditList/BulkEditListSidebar/utils/getFileInfo.js similarity index 100% rename from src/components/BulkEditList/BulkEditListFilters/utils/getFileInfo.js rename to src/components/BulkEditList/BulkEditListSidebar/utils/getFileInfo.js diff --git a/src/components/BulkEditList/BulkEditListFilters/utils/getFileInfo.test.js b/src/components/BulkEditList/BulkEditListSidebar/utils/getFileInfo.test.js similarity index 100% rename from src/components/BulkEditList/BulkEditListFilters/utils/getFileInfo.test.js rename to src/components/BulkEditList/BulkEditListSidebar/utils/getFileInfo.test.js diff --git a/src/components/BulkEditList/BulkEditListFilters/utils/getIsDisabledByPerm.js b/src/components/BulkEditList/BulkEditListSidebar/utils/getIsDisabledByPerm.js similarity index 100% rename from src/components/BulkEditList/BulkEditListFilters/utils/getIsDisabledByPerm.js rename to src/components/BulkEditList/BulkEditListSidebar/utils/getIsDisabledByPerm.js diff --git a/src/components/BulkEditList/BulkEditListFilters/utils/getIsDisabledByPerm.test.js b/src/components/BulkEditList/BulkEditListSidebar/utils/getIsDisabledByPerm.test.js similarity index 100% rename from src/components/BulkEditList/BulkEditListFilters/utils/getIsDisabledByPerm.test.js rename to src/components/BulkEditList/BulkEditListSidebar/utils/getIsDisabledByPerm.test.js diff --git a/src/components/BulkEditList/BulkEditListFilters/Capabilities/Capabilities.js b/src/components/shared/Capabilities/Capabilities.js similarity index 96% rename from src/components/BulkEditList/BulkEditListFilters/Capabilities/Capabilities.js rename to src/components/shared/Capabilities/Capabilities.js index a4a35d96..58a6b44f 100644 --- a/src/components/BulkEditList/BulkEditListFilters/Capabilities/Capabilities.js +++ b/src/components/shared/Capabilities/Capabilities.js @@ -3,7 +3,7 @@ import { Accordion, FilterAccordionHeader, RadioButton, RadioButtonGroup } from import { FormattedMessage } from 'react-intl'; import PropTypes from 'prop-types'; -const Capabilities = ({ +export const Capabilities = ({ capabilitiesFilterOptions, onCapabilityChange, capabilities, @@ -44,4 +44,3 @@ Capabilities.propTypes = { capabilities: PropTypes.string, hasInAppEditPerms: PropTypes.bool, }; -export default Capabilities; diff --git a/src/components/ListFileUploader/ListFileUploader.css b/src/components/shared/ListFileUploader/ListFileUploader.css similarity index 100% rename from src/components/ListFileUploader/ListFileUploader.css rename to src/components/shared/ListFileUploader/ListFileUploader.css diff --git a/src/components/ListFileUploader/ListFileUploader.js b/src/components/shared/ListFileUploader/ListFileUploader.js similarity index 92% rename from src/components/ListFileUploader/ListFileUploader.js rename to src/components/shared/ListFileUploader/ListFileUploader.js index 29ab5b9b..8bdfc10a 100644 --- a/src/components/ListFileUploader/ListFileUploader.js +++ b/src/components/shared/ListFileUploader/ListFileUploader.js @@ -12,21 +12,19 @@ import { } from '@folio/stripes-data-transfer-components'; import css from './ListFileUploader.css'; -import { getFileInfo } from '../BulkEditList/BulkEditListFilters/utils/getFileInfo'; - -const ListFileUploader = ( - { - isDropZoneActive, - isLoading, - handleDrop, - isDropZoneDisabled, - handleDragEnter, - disableUploader, - handleDragLeave, - uploaderSubTitle, - className, - }, -) => { +import { getFileInfo } from '../../BulkEditList/BulkEditListSidebar/utils/getFileInfo'; + +const ListFileUploader = ({ + uploaderSubTitle, + isDropZoneActive, + isLoading, + handleDrop, + isDropZoneDisabled, + handleDragEnter, + disableUploader, + handleDragLeave, + className, +}) => { const [fileExtensionModalOpen, setFileExtensionModalOpen] = useState(false); const [fileExtensionModalMessage, setFileExtensionModalMessage] = useState(''); diff --git a/src/components/ListFileUploader/ListFileUploader.test.js b/src/components/shared/ListFileUploader/ListFileUploader.test.js similarity index 94% rename from src/components/ListFileUploader/ListFileUploader.test.js rename to src/components/shared/ListFileUploader/ListFileUploader.test.js index 8f58f769..a095f721 100644 --- a/src/components/ListFileUploader/ListFileUploader.test.js +++ b/src/components/shared/ListFileUploader/ListFileUploader.test.js @@ -6,10 +6,16 @@ import { fireEvent, } from '@testing-library/react'; -import '../../../test/jest/__mock__'; +import '../../../../test/jest/__mock__'; import ListFileUploader from './ListFileUploader'; -import { createDtWithFiles, createFile, dispatchEvt, flushPromises, mockData } from '../../../test/jest/utils/fileUpload'; +import { + createDtWithFiles, + createFile, + dispatchEvt, + flushPromises, + mockData +} from '../../../../test/jest/utils/fileUpload'; const onDragEnterMock = jest.fn(); const onDragLeaveMock = jest.fn(); diff --git a/src/components/ListFileUploader/index.js b/src/components/shared/ListFileUploader/index.js similarity index 100% rename from src/components/ListFileUploader/index.js rename to src/components/shared/ListFileUploader/index.js diff --git a/src/components/BulkEditList/BulkEditListFilters/ListSelect/ListSelect.js b/src/components/shared/ListSelect/ListSelect.js similarity index 69% rename from src/components/BulkEditList/BulkEditListFilters/ListSelect/ListSelect.js rename to src/components/shared/ListSelect/ListSelect.js index 065e1c4d..17f656b8 100644 --- a/src/components/BulkEditList/BulkEditListFilters/ListSelect/ListSelect.js +++ b/src/components/shared/ListSelect/ListSelect.js @@ -1,15 +1,19 @@ import { memo } from 'react'; -import { useLocation } from 'react-router-dom'; import PropTypes from 'prop-types'; import { FormattedMessage, useIntl } from 'react-intl'; import { Select } from '@folio/stripes/components'; -import { identifierOptions } from '../../../../constants'; +import { identifierOptions } from '../../../constants'; -export const ListSelect = memo(({ value, disabled, onChange, capabilities }) => { + +export const ListSelect = memo(({ + value = '', + capabilities = '', + disabled, + onChange +}) => { const intl = useIntl(); - const location = useLocation(); const options = identifierOptions[capabilities]?.map((el) => ({ value: el.value, @@ -17,8 +21,6 @@ export const ListSelect = memo(({ value, disabled, onChange, capabilities }) => disabled: el.disabled, })); - const identifier = new URLSearchParams(location.search).get('identifier') || value; - const isDisabled = capabilities === '' ? true : disabled; return ( @@ -26,7 +28,7 @@ export const ListSelect = memo(({ value, disabled, onChange, capabilities }) => dataOptions={options} arial-label={intl.formatMessage({ id: 'ui-bulk-edit.list.filters.recordIdentifier' })} label={} - value={identifier} + value={value} onChange={onChange} disabled={isDisabled} /> @@ -34,8 +36,8 @@ export const ListSelect = memo(({ value, disabled, onChange, capabilities }) => }); ListSelect.propTypes = { - onChange: PropTypes.func.isRequired, - defaultIdentifier: PropTypes.func, + value: PropTypes.string, disabled: PropTypes.bool, capabilities: PropTypes.string, + onChange: PropTypes.func.isRequired, }; diff --git a/src/constants/core.js b/src/constants/core.js index 1ff3d2d1..35b35cd3 100644 --- a/src/constants/core.js +++ b/src/constants/core.js @@ -99,7 +99,16 @@ export const TRANSLATION_SUFFIX = { '': '', }; -export const FILTERS = { +export const IDENTIFIER_FILTERS = { + CAPABILITIES: 'capabilities', + IDENTIFIER: 'identifier', +}; + +export const QUERY_FILTERS = { + RECORD_TYPE: 'queryRecordType', +}; + +export const LOGS_FILTERS = { STATUS: 'status', CAPABILITY: 'entityType', OPERATION_TYPE: 'operationType', diff --git a/src/hooks/api/useBulkEditLogs/useBulkEditLogs.test.js b/src/hooks/api/useBulkEditLogs/useBulkEditLogs.test.js index 0812c999..2843ec5d 100644 --- a/src/hooks/api/useBulkEditLogs/useBulkEditLogs.test.js +++ b/src/hooks/api/useBulkEditLogs/useBulkEditLogs.test.js @@ -5,7 +5,7 @@ import { useOkapiKy } from '@folio/stripes/core'; import { bulkEditLogsData } from '../../../../test/jest/__mock__/fakeData'; -import { FILTERS, JOB_STATUSES } from '../../../constants'; +import { LOGS_FILTERS, JOB_STATUSES } from '../../../constants'; import { useBulkEditLogs } from './useBulkEditLogs'; import { getFullName } from '../../../utils/getFullName'; @@ -68,7 +68,7 @@ describe('useBulkEditLogs', () => { it('should return fetched hydreated logs list', async () => { const { result, waitFor } = renderHook(() => useBulkEditLogs({ pagination: { limit: 5, offset: 0, timestamp: 42 }, - filters: { [FILTERS.STATUS]: JOB_STATUSES.COMPLETED }, + filters: { [LOGS_FILTERS.STATUS]: JOB_STATUSES.COMPLETED }, }), { wrapper }); await waitFor(() => !result.current.isLoading); diff --git a/src/hooks/api/useRecordTypes.js b/src/hooks/api/useRecordTypes.js index 192d30d9..6ff76844 100644 --- a/src/hooks/api/useRecordTypes.js +++ b/src/hooks/api/useRecordTypes.js @@ -12,7 +12,8 @@ export const useRecordTypes = ({ enabled } = {}) => { return response.json(); }, - refetchOnWindowFocus: false, + cacheTime: Infinity, + staleTime: Infinity, enabled }); diff --git a/src/hooks/useLocationFilters.js b/src/hooks/useLocationFilters.js index 2d6d8f4c..e2b82ee9 100644 --- a/src/hooks/useLocationFilters.js +++ b/src/hooks/useLocationFilters.js @@ -3,13 +3,15 @@ import { useCallback, useEffect } from 'react'; import { buildFiltersObj } from '@folio/stripes-acq-components/lib/AcqList/utils'; import { buildSearch } from '@folio/stripes-acq-components'; import { SEARCH_INDEX_PARAMETER } from '@folio/stripes-acq-components/lib/AcqList/constants'; +import { useHistory } from 'react-router-dom'; export const useLocationFilters = ({ resetData = () => {}, - location, - history, initialFilter, }) => { + const history = useHistory(); + const { search } = history.location; + const { filters, applyFilters, @@ -21,32 +23,32 @@ export const useLocationFilters = ({ useEffect( () => { - const initialFilters = buildFiltersObj(location.search); + const initialFilters = buildFiltersObj(search); setFilters(initialFilters); setSearchIndex(initialFilters[SEARCH_INDEX_PARAMETER] || ''); }, // eslint-disable-next-line react-hooks/exhaustive-deps - [], + [search], ); const applyLocationFilters = useCallback( (type, value) => { const newFilters = applyFilters(type, value); - const search = new URLSearchParams(location.search); - const criteria = search.get('criteria'); - const capabilities = search.get('capabilities'); - const fileName = search.get('fileName'); - const step = search.get('step'); + const searchParams = new URLSearchParams(search); + const criteria = searchParams.get('criteria'); + const capabilities = searchParams.get('capabilities'); + const fileName = searchParams.get('fileName'); + const step = searchParams.get('step'); history.replace({ pathname: '', - search: `${buildSearch({ ...newFilters, capabilities, criteria, fileName, step }, location.search)}`, + search: `${buildSearch({ ...newFilters, capabilities, criteria, fileName, step }, search)}`, }); return newFilters; }, - [applyFilters, history, location.search], + [applyFilters, history, search], ); const resetLocationFilters = useCallback( diff --git a/src/hooks/useLogsQueryParams.js b/src/hooks/useLogsQueryParams.js index bdfea414..a82084df 100644 --- a/src/hooks/useLogsQueryParams.js +++ b/src/hooks/useLogsQueryParams.js @@ -6,9 +6,9 @@ import { SORTING_DIRECTION_PARAMETER, } from '@folio/stripes-acq-components'; -import { FILTERS } from '../constants'; +import { LOGS_FILTERS } from '../constants'; -const queryFields = [SORTING_PARAMETER, SORTING_DIRECTION_PARAMETER, ...Object.values(FILTERS)]; +const queryFields = [SORTING_PARAMETER, SORTING_DIRECTION_PARAMETER, ...Object.values(LOGS_FILTERS)]; export const useLogsQueryParams = ({ search }) => { const logsQueryParams = useMemo(() => { diff --git a/src/hooks/useResetAppState.js b/src/hooks/useResetAppState.js index 006b66cb..dbe8a2c1 100644 --- a/src/hooks/useResetAppState.js +++ b/src/hooks/useResetAppState.js @@ -3,16 +3,15 @@ import { buildSearch } from '@folio/stripes-acq-components'; import { useHistory } from 'react-router-dom'; import { useQueryClient } from 'react-query'; import { BULK_OPERATION_DETAILS_KEY } from './api'; +import { CRITERIA } from '../constants'; export const useResetAppState = ({ - setFilters, setConfirmedFileName, - initialFiltersState, setVisibleColumns, setCountOfRecords, filtersTab, setIsBulkEditLayerOpen, - setInAppCommitted, + setInAppCommitted }) => { const history = useHistory(); const queryClient = useQueryClient(); @@ -24,9 +23,6 @@ export const useResetAppState = ({ // reset count of records setCountOfRecords(0); - // reset filters - setFilters(initialFiltersState); - // clear bulkOperation information queryClient.setQueryData(BULK_OPERATION_DETAILS_KEY, () => ({ data: undefined })); @@ -41,9 +37,9 @@ export const useResetAppState = ({ // set user capability by default history.replace({ search: buildSearch({ - criteria: initialFiltersState.criteria, - identifier: initialFiltersState.identifier, - capabilities: initialFiltersState.capabilities, + criteria: CRITERIA.IDENTIFIER, + identifier: '', + capabilities: '', status, entityType, operationType, diff --git a/src/hooks/useSearchParams.js b/src/hooks/useSearchParams.js new file mode 100644 index 00000000..75785aa4 --- /dev/null +++ b/src/hooks/useSearchParams.js @@ -0,0 +1,26 @@ +import { useHistory } from 'react-router-dom'; +import { useMemo } from 'react'; + +export const useSearchParams = () => { + const history = useHistory(); + const { location: { search } } = history; + const searchParams = useMemo(() => new URLSearchParams(search), [search]); + + const criteria = searchParams.get('criteria'); + const identifier = searchParams.get('identifier'); + const step = searchParams.get('step'); + const capabilities = searchParams.get('capabilities'); + const queryRecordType = searchParams.get('queryRecordType'); + const initialFileName = searchParams.get('fileName'); + const processedFileName = searchParams.get('processedFileName'); + + return { + step, + criteria, + identifier, + capabilities, + initialFileName, + queryRecordType, + processedFileName, + }; +}; diff --git a/translations/ui-bulk-edit/cs_CZ.json b/translations/ui-bulk-edit/cs_CZ.json index ca604bb2..81bd6495 100644 --- a/translations/ui-bulk-edit/cs_CZ.json +++ b/translations/ui-bulk-edit/cs_CZ.json @@ -430,13 +430,13 @@ "uploaderSubTitle.user.BARCODE": "Vybrat soubor s uživatelskými čárovými kódy", "uploaderSubTitle.user.EXTERNAL_SYSTEM_ID": "Vyberte soubor s externími ID", "uploaderSubTitle.user.USER_NAME": "Vyberte soubor s uživatelskými jmény", - "uploaderSubTitle": "Select a file with record identifiers", + "uploaderSubTitle": "Vybrat soubor s identifikátory záznamu", "logs.actions.linkToTriggeringCsvFile.QUERY": "File with identifiers of the records affected by bulk update", - "columns.ITEM.Administrative note": "Administrative note", - "columns.HOLDINGS_RECORD.Administrative note": "Administrative note", - "logs.actions.linkToMatchedRecordsCsvFile.QUERY": "File with the matching records", - "logs.actions.linkToMatchedRecordsErrorsCsvFile.QUERY": "File with errors encountered during the record matching", - "logs.actions.linkToModifiedRecordsCsvFile.QUERY": "File with the preview of proposed changes", - "logs.actions.linkToCommittedRecordsCsvFile.QUERY": "File with updated records", - "logs.actions.linkToCommittedRecordsErrorsCsvFile.QUERY": "File with errors encountered when committing the changes" + "columns.ITEM.Administrative note": "Administrativní poznámka", + "columns.HOLDINGS_RECORD.Administrative note": "Administrativní poznámka", + "logs.actions.linkToMatchedRecordsCsvFile.QUERY": "Soubor s odpovídajícími záznamy", + "logs.actions.linkToMatchedRecordsErrorsCsvFile.QUERY": "Soubor s chybami zjištěnými během porovnávání záznamů", + "logs.actions.linkToModifiedRecordsCsvFile.QUERY": "Soubor s náhledem navrhovaných změn", + "logs.actions.linkToCommittedRecordsCsvFile.QUERY": "Soubor s aktualizovanými záznamy", + "logs.actions.linkToCommittedRecordsErrorsCsvFile.QUERY": "Soubor s chybami, ke kterým došlo při provádění změn" } \ No newline at end of file