Diff
checker
Texto
Texto
Imágenes
Documentos
Excel
Carpetas
Legal
Enterprise
Aplicación de escritorio
Precios
Iniciar sesión
Descargar Diffchecker Desktop
Comparar texto
Encuentra la diferencia entre dos archivos de texto
Herramientas
Historial
Editor live
Ocultar sin cambios
Sin ajuste de línea
Vista
Dividido
Unificado
Nivel de detalle
Inteligente
Palabra
Letra
Resaltado de sintaxis
Elegir sintaxis
Ignorar
Transformar texto
Ir al primer cambio
Editar entrada
Diffchecker Desktop
La forma más segura de usar Diffchecker. ¡Obtén la app de Diffchecker Desktop: tus diffs nunca salen de tu computadora!
Obtener Desktop
Search/Browse
Creado
hace 4 años
El diff nunca expira
Borrar
Exportar
Compartir
Explicar
345 eliminaciones
Líneas
Total
Eliminado
Caracteres
Total
Eliminado
Para continuar usando esta función, actualice a
Diff
checker
Pro
Ver precios
700 líneas
Copiar todo
447 adiciones
Líneas
Total
Añadido
Caracteres
Total
Añadido
Para continuar usando esta función, actualice a
Diff
checker
Pro
Ver precios
807 líneas
Copiar todo
import React from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import PropTypes from 'prop-types';
import {
import {
omit,
omit,
get,
get,
set,
set,
flowRight,
flowRight,
size,
size,
Copiar
Copiado
Copiar
Copiado
isEmpty,
noop,
} from 'lodash';
} from 'lodash';
import {
import {
injectIntl,
injectIntl,
FormattedMessage,
FormattedMessage,
} from 'react-intl';
} from 'react-intl';
Copiar
Copiado
Copiar
Copiado
import saveAs from 'file-saver';
import moment from 'moment';
import {
import {
Copiar
Copiado
Copiar
Copiado
Pluggable,
AppIcon,
AppIcon,
Copiar
Copiado
Copiar
Copiado
IfPermission,
IfInterface,
CalloutContext,
CalloutContext,
stripesConnect,
stripesConnect,
withNamespace,
withNamespace,
} from '@folio/stripes/core';
} from '@folio/stripes/core';
import { SearchAndSort } from '@folio/stripes/smart-components';
import { SearchAndSort } from '@folio/stripes/smart-components';
import {
import {
Copiar
Copiado
Copiar
Copiado
Button,
Icon,
Icon,
Checkbox,
Checkbox,
Copiar
Copiado
Copiar
Copiado
MenuSection,
checkScope,
checkScope,
HasCommand,
HasCommand,
} from '@folio/stripes/components';
} from '@folio/stripes/components';
import { pagingTypes } from '@folio/stripes-components/lib/MultiColumnList';
import { pagingTypes } from '@folio/stripes-components/lib/MultiColumnList';
Copiar
Copiado
Copiar
Copiado
import FilterNavigation from '../../components/FilterNavigation';
import packageInfo from '../../../package';
import packageInfo from '../../../package';
import InstanceForm from '../../edit/InstanceForm';
import InstanceForm from '../../edit/InstanceForm';
import ViewInstanceWrapper from '../../ViewInstanceWrapper';
import ViewInstanceWrapper from '../../ViewInstanceWrapper';
import formatters from '../../referenceFormatters';
import formatters from '../../referenceFormatters';
import withLocation from '../../withLocation';
import withLocation from '../../withLocation';
Copiar
Copiado
Copiar
Copiado
import CheckboxColumn from '../../components/InstancesList/CheckboxColumn';
import {
import {
getCurrentFilters,
getCurrentFilters,
getNextSelectedRowsState,
getNextSelectedRowsState,
parseFiltersToStr,
parseFiltersToStr,
marshalInstance,
marshalInstance,
omitFromArray,
omitFromArray,
Copiar
Copiado
Copiar
Copiado
isTestEnv,
handleKeyCommand,
handleKeyCommand,
} from '../../utils';
} from '../../utils';
import {
import {
Copiar
Copiado
Copiar
Copiado
INSTANCES_ID_REPORT_TIMEOUT,
QUICK_EXPORT_LIMIT,
segments,
segments,
Copiar
Copiado
Copiar
Copiado
browseModeOptions,
FACETS,
} from '../../constants';
} from '../../constants';
import {
import {
Copiar
Copiado
Copiar
Copiado
IdReportGenerator,
InTransitItemsReport,
} from '../../reports';
import ErrorModal from '../../components/ErrorModal';
import CheckboxColumn from '../../components/InstancesList/CheckboxColumn';
import SelectedRecordsModal from '../../components/SelectedRecordsModal';
import ImportRecordModal from '../../components/ImportRecordModal';
import { buildQuery } from '../../routes/buildManifestObject';
import {
getItem,
getItem,
setItem,
setItem,
} from '../../storage';
} from '../../storage';
import facetsStore from '../../stores/facetsStore';
import facetsStore from '../../stores/facetsStore';
import css from '../../components/InstancesList/instances.css';
import css from '../../components/InstancesList/instances.css';
Copiar
Copiado
Copiar
Copiado
import { getFilterConfig } from '../../filterConfig';
import { getFilterConfig } from '../../filterConfig';
import SearchBrowseNavigation from '../../components/SearchBrowseNavigation/SearchBrowseNavigation';
import SearchBrowseNavigation from '../../components/SearchBrowseNavigation/SearchBrowseNavigation';
const INITIAL_RESULT_COUNT = 30;
const INITIAL_RESULT_COUNT = 30;
const RESULT_COUNT_INCREMENT = 30;
const RESULT_COUNT_INCREMENT = 30;
const columnSets = {
const columnSets = {
SUBJECTS: ['subject', 'numberOfTitles'],
SUBJECTS: ['subject', 'numberOfTitles'],
CALL_NUMBERS: ['callNumber', 'title', 'numberOfTitles'],
CALL_NUMBERS: ['callNumber', 'title', 'numberOfTitles'],
CONTRIBUTORS: ['contributor', 'contributorType', 'relatorTerm', 'numberOfTitles'],
CONTRIBUTORS: ['contributor', 'contributorType', 'relatorTerm', 'numberOfTitles'],
};
};
const TOGGLEABLE_COLUMNS = ['contributors', 'publishers', 'relation'];
const TOGGLEABLE_COLUMNS = ['contributors', 'publishers', 'relation'];
const NON_TOGGLEABLE_COLUMNS = ['select', 'title'];
const NON_TOGGLEABLE_COLUMNS = ['select', 'title'];
const ALL_COLUMNS = Array.from(new Set([
const ALL_COLUMNS = Array.from(new Set([
...NON_TOGGLEABLE_COLUMNS,
...NON_TOGGLEABLE_COLUMNS,
...TOGGLEABLE_COLUMNS,
...TOGGLEABLE_COLUMNS,
...columnSets.CALL_NUMBERS,
...columnSets.CALL_NUMBERS,
...columnSets.SUBJECTS,
...columnSets.SUBJECTS,
...columnSets.CONTRIBUTORS,
...columnSets.CONTRIBUTORS,
]));
]));
const VISIBLE_COLUMNS_STORAGE_KEY = 'inventory-visible-columns';
const VISIBLE_COLUMNS_STORAGE_KEY = 'inventory-visible-columns';
Copiar
Copiado
Copiar
Copiado
class
InstancesList
extends React.Component {
class
SearchView
extends React.Component {
static defaultProps = {
static defaultProps = {
Copiar
Copiado
Copiar
Copiado
browseOnly: false,
showSingleResult: true,
showSingleResult: true,
};
};
static propTypes = {
static propTypes = {
data: PropTypes.object,
data: PropTypes.object,
parentResources: PropTypes.object,
parentResources: PropTypes.object,
parentMutator: PropTypes.object,
parentMutator: PropTypes.object,
showSingleResult: PropTypes.bool,
showSingleResult: PropTypes.bool,
Copiar
Copiado
Copiar
Copiado
browseOnly: PropTypes.bool,
disableRecordCreation: PropTypes.bool,
disableRecordCreation: PropTypes.bool,
updateLocation: PropTypes.func.isRequired,
updateLocation: PropTypes.func.isRequired,
goTo: PropTypes.func.isRequired,
goTo: PropTypes.func.isRequired,
getParams: PropTypes.func.isRequired,
getParams: PropTypes.func.isRequired,
segment: PropTypes.string,
segment: PropTypes.string,
intl: PropTypes.object,
intl: PropTypes.object,
match: PropTypes.shape({
match: PropTypes.shape({
path: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
params: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
}).isRequired,
}).isRequired,
namespace: PropTypes.string,
namespace: PropTypes.string,
renderFilters: PropTypes.func.isRequired,
renderFilters: PropTypes.func.isRequired,
searchableIndexes: PropTypes.arrayOf(PropTypes.object).isRequired,
searchableIndexes: PropTypes.arrayOf(PropTypes.object).isRequired,
mutator: PropTypes.shape({
mutator: PropTypes.shape({
query: PropTypes.shape({
query: PropTypes.shape({
update: PropTypes.func.isRequired,
update: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired,
replace: PropTypes.func.isRequired,
}).isRequired,
}).isRequired,
}),
}),
location: PropTypes.shape({
location: PropTypes.shape({
search: PropTypes.string
search: PropTypes.string
}),
}),
stripes: PropTypes.object.isRequired,
stripes: PropTypes.object.isRequired,
fetchFacets: PropTypes.func,
fetchFacets: PropTypes.func,
};
};
static contextType = CalloutContext;
static contextType = CalloutContext;
static manifest = Object.freeze({
static manifest = Object.freeze({
query: {},
query: {},
});
});
constructor(props) {
constructor(props) {
super(props);
super(props);
this.state = {
this.state = {
Copiar
Copiado
Copiar
Copiado
showNewFastAddModal: false,
inTransitItemsExportInProgress: false,
instancesIdExportInProgress: false,
holdingsIdExportInProgress: false,
instancesQuickExportInProgress: false,
showErrorModal: false,
selectedRows: {},
selectedRows: {},
Copiar
Copiado
Copiar
Copiado
optionSelected: '',
isSelectedRecordsModalOpened: false,
visibleColumns: this.getInitialToggableColumns(),
isImportRecordModalOpened: false,
searchAndSortKey: 0,
searchAndSortKey: 0,
isSingleResult: this.props.showSingleResult,
isSingleResult: this.props.showSingleResult,
};
};
}
}
Copiar
Copiado
Copiar
Copiado
componentDidMount() {
if (this.getSelectedBrowseOption() || this.getExecutedBrowseQuery()) {
this.setState({
optionSelected: this.getQIndexFromParams(),
isSingleResult: false,
});
} else {
this.setState({
optionSelected: '',
});
Text moved to lines 379-381
}
}
getQIndexFromParams = () => {
getQIndexFromParams = () => {
const params = new URLSearchParams(this.props.location.search);
const params = new URLSearchParams(this.props.location.search);
return params.get('qindex');
return params.get('qindex');
}
}
Copiar
Copiado
Copiar
Copiado
getSelectedBrowseOption = () => {
const isBrowseSelectedBasedOnState = Object.keys(browseModeOptions).filter(k => browseModeOptions[k] === this.state.optionSelected)[0];
return isBrowseSelectedBasedOnState;
}
getExecutedBrowseQuery = () => {
const isBrowseSelectedBasedOnUrl = Object.keys(browseModeOptions).filter(k => browseModeOptions[k] === this.getQIndexFromParams())[0];
return isBrowseSelectedBasedOnUrl;
}
getInitialToggableColumns = () => {
getInitialToggableColumns = () => {
return getItem(VISIBLE_COLUMNS_STORAGE_KEY) || TOGGLEABLE_COLUMNS;
return getItem(VISIBLE_COLUMNS_STORAGE_KEY) || TOGGLEABLE_COLUMNS;
}
}
getVisibleColumns = () => {
getVisibleColumns = () => {
// display particular columnset based on the quindex value in the URL, since the search has been
// display particular columnset based on the quindex value in the URL, since the search has been
// performed.
// performed.
Copiar
Copiado
Copiar
Copiado
const executedBrowseQuery = Object.keys(browseModeOptions).filter(k => browseModeOptions[k] === this.getQIndexFromParams())[0];
const columns = this.state.visibleColumns;
const columns = columnSets[executedBrowseQuery];
const visibleColumns = new Set([...
columns, ...
NON_TOGGLEABLE_COLUMNS]);
if (!executedBrowseQuery) {
return ALL_COLUMNS.filter(key => visibleColumns.has(key));
const visibleColumns = new Set([...
NON_TOGGLEABLE_COLUMNS]);
return ALL_COLUMNS.filter(key => visibleColumns.has(key));
} else {
const visibleColumns = new Set([...columns]);
return Array.from(visibleColumns);
}
}
}
onFilterChangeHandler = ({ name, values }) => {
onFilterChangeHandler = ({ name, values }) => {
const {
const {
data: { query },
data: { query },
match: { path },
match: { path },
goTo,
goTo,
getParams,
getParams,
} = this.props;
} = this.props;
const curFilters = getCurrentFilters(get(query, 'filters', ''));
const curFilters = getCurrentFilters(get(query, 'filters', ''));
const mergedFilters = values.length
const mergedFilters = values.length
? { ...curFilters, [name]: values }
? { ...curFilters, [name]: values }
: omit(curFilters, name);
: omit(curFilters, name);
const filtersStr = parseFiltersToStr(mergedFilters);
const filtersStr = parseFiltersToStr(mergedFilters);
const params = getParams();
const params = getParams();
goTo(path, { ...params, filters: filtersStr });
goTo(path, { ...params, filters: filtersStr });
};
};
closeNewInstance = (e) => {
closeNewInstance = (e) => {
if (e) e.preventDefault();
if (e) e.preventDefault();
this.setState({ copiedInstance: null });
this.setState({ copiedInstance: null });
this.props.updateLocation({ layer: null });
this.props.updateLocation({ layer: null });
};
};
createInstance = (instanceData) => {
createInstance = (instanceData) => {
const { data: { identifierTypesByName } } = this.props;
const { data: { identifierTypesByName } } = this.props;
// Massage record to add preceeding and succeeding title fields
// Massage record to add preceeding and succeeding title fields
const instance = marshalInstance(instanceData, identifierTypesByName);
const instance = marshalInstance(instanceData, identifierTypesByName);
// POST item record
// POST item record
return this.props.parentMutator.records.POST(instance);
return this.props.parentMutator.records.POST(instance);
};
};
onCreate = (instance) => {
onCreate = (instance) => {
return this.createInstance(instance).then(() => this.closeNewInstance());
return this.createInstance(instance).then(() => this.closeNewInstance());
}
}
openCreateInstance = () => {
openCreateInstance = () => {
this.props.updateLocation({ layer: 'create' });
this.props.updateLocation({ layer: 'create' });
}
}
copyInstance = (instance) => {
copyInstance = (instance) => {
const {
const {
precedingTitles,
precedingTitles,
succeedingTitles,
succeedingTitles,
childInstances,
childInstances,
parentInstances,
parentInstances,
} = instance;
} = instance;
let copiedInstance = omit(instance, ['id', 'hrid']);
let copiedInstance = omit(instance, ['id', 'hrid']);
if (precedingTitles?.length) {
if (precedingTitles?.length) {
copiedInstance.precedingTitles = omitFromArray(precedingTitles, 'id');
copiedInstance.precedingTitles = omitFromArray(precedingTitles, 'id');
}
}
if (succeedingTitles?.length) {
if (succeedingTitles?.length) {
copiedInstance.succeedingTitles = omitFromArray(succeedingTitles, 'id');
copiedInstance.succeedingTitles = omitFromArray(succeedingTitles, 'id');
}
}
if (childInstances?.length) {
if (childInstances?.length) {
copiedInstance.childInstances = omitFromArray(childInstances, 'id');
copiedInstance.childInstances = omitFromArray(childInstances, 'id');
}
}
if (parentInstances?.length) {
if (parentInstances?.length) {
copiedInstance.parentInstances = omitFromArray(parentInstances, 'id');
copiedInstance.parentInstances = omitFromArray(parentInstances, 'id');
}
}
copiedInstance = set(copiedInstance, 'source', 'FOLIO');
copiedInstance = set(copiedInstance, 'source', 'FOLIO');
this.setState({ copiedInstance });
this.setState({ copiedInstance });
this.openCreateInstance();
this.openCreateInstance();
}
}
Copiar
Copiado
Copiar
Copiado
refocusOnInputSearch = (
segment
) => {
refocusOnInputSearch = (
) => {
// when navigation button is clicked to change the search segment
// when navigation button is clicked to change the search segment
// the focus stays on the button so refocus back on the input search.
// the focus stays on the button so refocus back on the input search.
// https://issues.folio.org/browse/UIIN-1358
// https://issues.folio.org/browse/UIIN-1358
Copiar
Copiado
Copiar
Copiado
if (segment !== segments.instances) {
this.setState({
optionSelected: ''
});
}
facetsStore.getState().resetFacetSettings();
facetsStore.getState().resetFacetSettings();
document.getElementById('input-inventory-search').focus();
document.getElementById('input-inventory-search').focus();
}
}
renderNavigation = () => (
renderNavigation = () => (
<>
<>
<SearchBrowseNavigation segment={this.props.segment} onChange={this.refocusOnInputSearch} />
<SearchBrowseNavigation segment={this.props.segment} onChange={this.refocusOnInputSearch} />
Copiar
Copiado
Copiar
Copiado
<FilterNavigation segment={this.props.segment} onChange={this.refocusOnInputSearch} />
</>
</>
);
);
Copiar
Copiado
Copiar
Copiado
generateInTransitItemReport = async () => {
const {
intl: {
formatMessage,
},
parentMutator,
} = this.props;
const { sendCallout, removeCallout } = this.context;
const calloutId = sendCallout({
message: <FormattedMessage id="ui-inventory.exportInProgress" />,
timeout: 0,
});
try {
const report = new InTransitItemsReport(parentMutator, formatMessage);
const items = await report.toCSV();
if (!items?.length) {
this.setState({ showErrorModal: true });
}
} catch (error) {
throw new Error(error);
} finally {
removeCallout(calloutId);
this.setState({ inTransitItemsExportInProgress: false });
}
};
startInTransitReportGeneration = () => {
const { inTransitItemsExportInProgress } = this.state;
if (inTransitItemsExportInProgress) {
return;
}
this.setState({ inTransitItemsExportInProgress: true }, this.generateInTransitItemReport);
};
generateInstancesIdReport = async (sendCallout) => {
const { instancesIdExportInProgress } = this.state;
if (instancesIdExportInProgress) return;
this.setState({ instancesIdExportInProgress: true }, async () => {
const {
reset,
GET,
} = this.props.parentMutator.recordsToExportIDs;
let infoCalloutTimer;
try {
reset();
infoCalloutTimer = setTimeout(() => {
sendCallout({
type: 'info',
message: <FormattedMessage id="ui-inventory.saveInstancesUIIDS.info" />,
});
}, INSTANCES_ID_REPORT_TIMEOUT);
const items = await GET();
clearTimeout(infoCalloutTimer);
const report = new IdReportGenerator('SearchInstanceUUIDs');
if (!isEmpty(items)) {
report.toCSV(items, record => record.id);
}
} catch (error) {
clearTimeout(infoCalloutTimer);
sendCallout({
type: 'error',
message: <FormattedMessage id="ui-inventory.saveInstancesUIIDS.error" />,
});
} finally {
this.setState({ instancesIdExportInProgress: false });
}
});
};
triggerQuickExport = async (sendCallout) => {
const { instancesQuickExportInProgress } = this.state;
if (instancesQuickExportInProgress) return;
this.setState({ instancesQuickExportInProgress: true });
try {
const instanceIds = Object.keys(this.state.selectedRows).slice(0, QUICK_EXPORT_LIMIT);
await this.props.parentMutator.quickExport.POST({
uuids: instanceIds,
type: 'uuid',
recordType: 'INSTANCE'
});
new IdReportGenerator('QuickInstanceExport').toCSV(instanceIds);
} catch (error) {
sendCallout({
type: 'error',
message: <FormattedMessage id="ui-inventory.communicationProblem" />,
});
} finally {
this.setState({ instancesQuickExportInProgress: false });
}
};
generateCQLQueryReport = async () => {
if (!isTestEnv()) {
const { data } = this.props;
const query = buildQuery(data.query, {}, data, { log: noop }, this.props);
const fileName = `SearchInstanceCQLQuery${moment().format()}.cql`;
saveAs(new Blob([query], { type: 'text/plain;charset=utf-8;' }), fileName);
Text moved from lines 145-147
}
}
toggleNewFastAddModal = () => {
this.setState((state) => {
return { showNewFastAddModal: !state.showNewFastAddModal };
});
}
generateHoldingsIdReport = async (sendCallout) => {
const { holdingsIdExportInProgress } = this.state;
if (holdingsIdExportInProgress) return;
this.setState({ holdingsIdExportInProgress: true }, async () => {
const {
reset,
GET,
} = this.props.parentMutator.holdingsToExportIDs;
let infoCalloutTimer;
try {
reset();
infoCalloutTimer = setTimeout(() => {
sendCallout({
type: 'info',
message: <FormattedMessage id="ui-inventory.saveHoldingsUIIDS.info" />,
});
}, INSTANCES_ID_REPORT_TIMEOUT);
const items = await GET();
clearTimeout(infoCalloutTimer);
const report = new IdReportGenerator('SearchHoldingsUUIDs');
if (!isEmpty(items)) {
report.toCSV(items, record => record.id);
}
} catch (error) {
clearTimeout(infoCalloutTimer);
sendCallout({
type: 'error',
message: <FormattedMessage id="ui-inventory.saveHoldingsUIIDS.error" />,
});
} finally {
this.setState({ instancesIdExportInProgress: false });
}
});
};
getActionItem = ({ id, icon, messageId, onClickHandler, isDisabled = false }) => {
return (
<Button
buttonStyle="dropdownItem"
id={id}
disabled={isDisabled}
onClick={onClickHandler}
>
<Icon
icon={icon}
size="medium"
iconClassName={css.actionIcon}
/>
<FormattedMessage id={messageId} />
</Button>
);
}
handleToggleColumn = ({ target: { value: key } }) => {
this.setState(({ visibleColumns }) => ({
visibleColumns: visibleColumns.includes(key) ? visibleColumns.filter(k => key !== k) : [...visibleColumns, key]
}), () => {
setItem(VISIBLE_COLUMNS_STORAGE_KEY, this.state.visibleColumns);
});
}
onMarkPosition = (position) => {
onMarkPosition = (position) => {
const { namespace } = this.props;
const { namespace } = this.props;
setItem(`${namespace}.position`, position);
setItem(`${namespace}.position`, position);
}
}
resetMarkedPosition = () => {
resetMarkedPosition = () => {
const { namespace } = this.props;
const { namespace } = this.props;
setItem(`${namespace}.position`, null);
setItem(`${namespace}.position`, null);
}
}
Copiar
Copiado
Copiar
Copiado
getActionMenu = ({ onToggle }) => {
const { parentResources, intl, segment } = this.props;
const { inTransitItemsExportInProgress } = this.state;
const selectedRowsCount = size(this.state.selectedRows);
const isInstancesListEmpty = isEmpty(get(parentResources, ['records', 'records'], []));
const isQuickExportLimitExceeded = selectedRowsCount > QUICK_EXPORT_LIMIT;
const visibleColumns = this.getVisibleColumns();
const columnMapping = this.getColumnMapping();
const buildOnClickHandler = onClickHandler => {
return () => {
onToggle();
onClickHandler(this.context.sendCallout);
};
};
return (
<>
<MenuSection label={intl.formatMessage({ id: 'ui-inventory.actions' })} id="actions-menu-section">
<IfPermission perm="ui-inventory.instance.create">
<Button
buttonStyle="dropdownItem"
id="clickable-newinventory"
onClick={buildOnClickHandler(this.openCreateInstance)}
>
<Icon
icon="plus-sign"
size="medium"
iconClassName={css.actionIcon}
/>
<FormattedMessage id="stripes-smart-components.new" />
</Button>
</IfPermission>
<Pluggable
id="clickable-create-inventory-records"
onClose={this.toggleNewFastAddModal}
open={this.state.showNewFastAddModal} // control the open modal via state var
renderTrigger={() => (
this.getActionItem({
id: 'new-fast-add-record',
icon: 'lightning',
messageId: 'ui-inventory.newFastAddRecord',
onClickHandler: buildOnClickHandler(this.toggleNewFastAddModal),
})
)}
type="create-inventory-records"
/>
{
inTransitItemsExportInProgress ?
this.getActionItem({
id: 'dropdown-clickable-get-report',
icon: 'report',
messageId: 'ui-inventory.exportInProgress',
}) :
this.getActionItem({
id: 'dropdown-clickable-get-report',
icon: 'report',
messageId: 'ui-inventory.inTransitReport',
onClickHandler: buildOnClickHandler(this.startInTransitReportGeneration),
})
}
{this.getActionItem({
id: 'dropdown-clickable-get-instances-uiids',
icon: 'save',
messageId: 'ui-inventory.saveInstancesUIIDS',
onClickHandler: buildOnClickHandler(this.generateInstancesIdReport),
isDisabled: isInstancesListEmpty,
})}
{segment === 'holdings' && this.getActionItem({
id: 'dropdown-clickable-get-holdings-uiids',
icon: 'save',
messageId: 'ui-inventory.saveHoldingsUIIDS',
onClickHandler: buildOnClickHandler(this.generateHoldingsIdReport),
isDisabled: isInstancesListEmpty,
})}
{this.getActionItem({
id: 'dropdown-clickable-get-cql-query',
icon: 'search',
messageId: 'ui-inventory.saveInstancesCQLQuery',
onClickHandler: buildOnClickHandler(this.generateCQLQueryReport),
isDisabled: isInstancesListEmpty,
})}
{this.getActionItem({
id: 'dropdown-clickable-export-marc',
icon: 'download',
messageId: 'ui-inventory.exportInstancesInMARC',
onClickHandler: buildOnClickHandler(this.triggerQuickExport),
isDisabled: !selectedRowsCount || isQuickExportLimitExceeded,
})}
<IfInterface name="copycat-imports">
<IfPermission perm="copycat.profiles.collection.get">
{this.getActionItem({
id: 'dropdown-clickable-import-record',
icon: 'lightning',
messageId: 'ui-inventory.copycat.import',
onClickHandler: buildOnClickHandler(() => this.setState({ isImportRecordModalOpened: true })),
})}
</IfPermission>
</IfInterface>
{isQuickExportLimitExceeded && (
<span
className={css.feedbackError}
data-test-quick-marc-export-limit-exceeded
>
<FormattedMessage
id="ui-inventory.exportInstancesInMARCLimitExceeded"
values={{ count: QUICK_EXPORT_LIMIT }}
/>
</span>
)}
{this.getActionItem({
id: 'dropdown-clickable-export-json',
icon: 'download',
messageId: 'ui-inventory.exportInstancesInJSON',
onClickHandler: buildOnClickHandler(noop),
isDisabled: true,
})}
{this.getActionItem({
id: 'dropdown-clickable-show-selected-records',
icon: 'eye-open',
messageId: 'ui-inventory.instances.showSelectedRecords',
onClickHandler: buildOnClickHandler(() => this.setState({ isSelectedRecordsModalOpened: true })),
isDisabled: !selectedRowsCount,
})}
</MenuSection>
<MenuSection label={intl.formatMessage({ id: 'ui-inventory.showColumns' })} id="columns-menu-section">
{TOGGLEABLE_COLUMNS.map(key => (
<Checkbox
key={key}
name={key}
data-testid={key}
label={columnMapping[key]}
id={`inventory-search-column-checkbox-${key}`}
checked={visibleColumns.includes(key)}
value={key}
onChange={this.handleToggleColumn}
/>
))}
</MenuSection>
</>
);
};
getColumnMapping = () => {
getColumnMapping = () => {
const { intl } = this.props;
const { intl } = this.props;
const columnMapping = {
const columnMapping = {
callNumber: intl.formatMessage({ id: 'ui-inventory.instances.columns.callNumber' }),
callNumber: intl.formatMessage({ id: 'ui-inventory.instances.columns.callNumber' }),
select: '',
select: '',
title: intl.formatMessage({ id: 'ui-inventory.instances.columns.title' }),
title: intl.formatMessage({ id: 'ui-inventory.instances.columns.title' }),
contributors: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributors' }),
contributors: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributors' }),
publishers: intl.formatMessage({ id: 'ui-inventory.instances.columns.publishers' }),
publishers: intl.formatMessage({ id: 'ui-inventory.instances.columns.publishers' }),
relation: intl.formatMessage({ id: 'ui-inventory.instances.columns.relation' }),
relation: intl.formatMessage({ id: 'ui-inventory.instances.columns.relation' }),
numberOfTitles: intl.formatMessage({ id: 'ui-inventory.instances.columns.numberOfTitles' }),
numberOfTitles: intl.formatMessage({ id: 'ui-inventory.instances.columns.numberOfTitles' }),
subject: intl.formatMessage({ id: 'ui-inventory.subject' }),
subject: intl.formatMessage({ id: 'ui-inventory.subject' }),
contributor: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributor' }),
contributor: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributor' }),
contributorType: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributorType' }),
contributorType: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributorType' }),
relatorTerm: intl.formatMessage({ id: 'ui-inventory.instances.columns.relatorTerm' }),
relatorTerm: intl.formatMessage({ id: 'ui-inventory.instances.columns.relatorTerm' }),
};
};
return columnMapping;
return columnMapping;
}
}
Copiar
Copiado
Copiar
Copiado
onErrorModalClose = () => {
this.setState({ showErrorModal: false });
};
toggleRowSelection = row => {
toggleRowSelection = row => {
this.setState(({ selectedRows }) => ({ selectedRows: getNextSelectedRowsState(selectedRows, row) }));
this.setState(({ selectedRows }) => ({ selectedRows: getNextSelectedRowsState(selectedRows, row) }));
};
};
handleResetAll = () => {
handleResetAll = () => {
this.setState({
this.setState({
selectedRows: {},
selectedRows: {},
Copiar
Copiado
Copiar
Copiado
optionSelected: '',
});
});
facetsStore.getState().resetFacetSettings();
facetsStore.getState().resetFacetSettings();
}
}
Copiar
Copiado
Copiar
Copiado
handleSelectedRecordsModalSave = selectedRecords => {
this.setState({
isSelectedRecordsModalOpened: false,
selectedRows: selectedRecords,
});
}
handleSelectedRecordsModalCancel = () => {
this.setState({ isSelectedRecordsModalOpened: false });
}
handleImportRecordModalSubmit = (args) => {
this.setState({ isImportRecordModalOpened: false });
this.props.mutator.query.update({
_path: '/inventory/import',
xidtype: args.externalIdentifierType,
xid: args.externalIdentifier,
});
}
handleImportRecordModalCancel = () => {
this.setState({ isImportRecordModalOpened: false });
}
renderPaneSub() {
renderPaneSub() {
const selectedRowsCount = size(this.state.selectedRows);
const selectedRowsCount = size(this.state.selectedRows);
return selectedRowsCount
return selectedRowsCount
? (
? (
<FormattedMessage
<FormattedMessage
id="ui-inventory.instances.rows.recordsSelected"
id="ui-inventory.instances.rows.recordsSelected"
values={{ count: selectedRowsCount }}
values={{ count: selectedRowsCount }}
/>
/>
)
)
: null;
: null;
}
}
formatCellStyles(defaultCellStyle) {
formatCellStyles(defaultCellStyle) {
return `${defaultCellStyle} ${css.cellAlign}`;
return `${defaultCellStyle} ${css.cellAlign}`;
}
}
Copiar
Copiado
Copiar
Copiado
// handler used for clicking a row in browse mode
onSelectRow = (_, row) => {
const {
parentMutator,
parentResources,
} = this.props;
switch (get(parentResources.query, 'qindex')) {
case browseModeOptions.CALL_NUMBERS:
parentMutator.query.update({
qindex: 'callNumber',
query: row.shelfKey,
filters: '',
});
break;
case browseModeOptions.SUBJECTS:
parentMutator.query.update({
qindex: 'subject',
query: row.subject,
filters: '',
});
break;
case browseModeOptions.CONTRIBUTORS:
if (row.isAnchor && !row.contributorNameTypeId) {
return;
}
parentMutator.query.update({
qindex: 'contributor',
query: row.name,
filters: `${FACETS.SEARCH_CONTRIBUTORS}.${row.contributorNameTypeId}`,
});
break;
default:
}
// the searchAndSortKey state field can be updated to reset SearchAndSort
// to use the app-level selectedIndex
this.setState((curState) => ({
searchAndSortKey: curState.searchAndSortKey + 1,
}));
}
render() {
render() {
const {
const {
Copiar
Copiado
Copiar
Copiado
browseOnly,
disableRecordCreation,
disableRecordCreation,
intl,
intl,
data,
data,
parentResources,
parentResources,
parentMutator,
parentMutator,
renderFilters,
renderFilters,
searchableIndexes,
searchableIndexes,
match: {
match: {
path,
path,
},
},
namespace,
namespace,
stripes,
stripes,
Copiar
Copiado
Copiar
Copiado
fetchFacets,
} = this.props;
} = this.props;
const {
const {
Copiar
Copiado
Copiar
Copiado
isSelectedRecordsModalOpened,
isImportRecordModalOpened,
selectedRows,
selectedRows,
Copiar
Copiado
Copiar
Copiado
optionSelected,
searchAndSortKey,
searchAndSortKey,
isSingleResult
isSingleResult
} = this.state;
} = this.state;
const itemToView = getItem(`${namespace}.position`);
const itemToView = getItem(`${namespace}.position`);
Copiar
Copiado
Copiar
Copiado
const missedMatchItem = () => {
const query = new URLSearchParams(this.props.location.search).get('query');
return (
<div className={css.missedMatchItemWrapper}>
<span className={css.warnIcon}>
<Icon
size="medium"
icon="exclamation-circle"
status="warn"
/>
</span>
<span className={`${css.missingMatchError} ${css.fitContent}`}>
{query}
</span>
<strong className={css.fitContent}>
<FormattedMessage id="ui-inventory.browseCallNumbers.missedMatch" />
</strong>
</div>
);
};
const getFullMatchRecord = (item, isAnchor) => {
const getFullMatchRecord = (item, isAnchor) => {
if (isAnchor) {
if (isAnchor) {
return <strong>{item}</strong>;
return <strong>{item}</strong>;
}
}
return item;
return item;
};
};
Copiar
Copiado
Copiar
Copiado
const handleOnNeedMore = ({ direction, records, source }) => {
const paramByBrowseMode = {
[browseModeOptions.SUBJECTS]: 'subject',
[browseModeOptions.CALL_NUMBERS]: 'callNumber',
[browseModeOptions.CONTRIBUTORS]: 'name',
};
const isSubject = optionSelected === browseModeOptions.SUBJECTS;
const isCallNumber = optionSelected === browseModeOptions.CALL_NUMBERS;
const isContributors = optionSelected === browseModeOptions.CONTRIBUTORS;
const param = paramByBrowseMode[optionSelected];
let anchor;
if (direction === 'prev') {
if (isCallNumber) {
anchor = records.find(i => i.fullCallNumber)?.shelfKey;
} else if (isSubject) {
anchor = records[0].subject;
} else if (isContributors) {
anchor = records[0].name;
}
source.fetchByQuery(`${param} < "${anchor}"`);
} else {
if (isCallNumber) {
anchor = [...records].reverse().find(i => i.fullCallNumber)?.shelfKey;
} else if (isSubject) {
anchor = records[records.length - 1].subject;
} else if (isContributors) {
anchor = records[records.length - 1].name;
}
source.fetchByQuery(`${param} > "${anchor}"`);
}
};
const resultsFormatter = {
const resultsFormatter = {
Copiar
Copiado
Copiar
Copiado
'select': ({
id,
...rowData
}) => (
<CheckboxColumn>
<Checkbox
checked={Boolean(selectedRows[id])}
aria-label={intl.formatMessage({ id: 'ui-inventory.instances.rows.select' })}
onChange={() => this.toggleRowSelection({
id,
...rowData
})}
/>
</CheckboxColumn>
),
'title': ({
'title': ({
title,
title,
Copiar
Copiado
Copiar
Copiado
instance,
discoverySuppress,
discoverySuppress,
isBoundWith,
isBoundWith,
staffSuppress,
staffSuppress,
Copiar
Copiado
Copiar
Copiado
isAnchor,
}) => {
}) => {
Copiar
Copiado
Copiar
Copiado
if (this.getExecutedBrowseQuery()) {
return (
return getFullMatchRecord(instance?.title, isAnchor);
<AppIcon
} else {
size="small"
return (
app="inventory"
iconKey="instance"
iconAlignment="baseline"
>
{title}
{(isBoundWith) &&
<AppIcon
<AppIcon
size="small"
size="small"
Copiar
Copiado
Copiar
Copiado
app="inventory"
app="@folio/inventory"
iconKey="instance"
iconKey="bound-with"
iconAlignment="baseline"
iconClassName={css.boundWithIcon}
>
/>
{title}
}
{(isBoundWith) &&
{(discoverySuppress || staffSuppress) &&
<AppIcon
<span className={css.warnIcon}>
size="small"
<Icon
app="@folio/inventory"
size="medium"
iconKey="bound-with"
icon="exclamation-circle"
iconClassName={css.boundWithIcon}
status="warn"
/>
/>
Copiar
Copiado
Copiar
Copiado
}
</span>
{(discoverySuppress || staffSuppress) &&
<span className={css.warnIcon}>
<Icon
size="medium"
icon="exclamation-circle"
status="warn"
/>
</span>
}
</AppIcon>
);
}
},
'subject': r => {
if (r?.totalRecords) {
return getFullMatchRecord(r?.subject, r.isAnchor);
}
return missedMatchItem();
},
'callNumber': r => {
if (r?.instance || r?.totalRecords) {
return getFullMatchRecord(r?.fullCallNumber, r.isAnchor);
}
return missedMatchItem();
},
'contributor': r => {
if (r?.totalRecords) {
return getFullMatchRecord(r?.name, r.isAnchor);
}
}
Copiar
Copiado
Copiar
Copiado
return missedMatchItem(
);
</AppIcon>
);
},
},
Copiar
Copiado
Copiar
Copiado
'relation': r => formatters.relationsFormatter(r, data.instanceRelationshipTypes),
'publishers': r => (r?.publication ?? []).map(p => (p ? `${p.publisher} ${p.dateOfPublication ? `(${p.dateOfPublication})` : ''}` : '')).join(', '),
'publication date': r => r.publication.map(p => p.dateOfPublication).join(', '),
'contributors': r => formatters.contributorsFormatter(r, data.contributorTypes),
'contributorType': r => data.contributorNameTypes.find(nameType => nameType.id === r.contributorNameTypeId)?.name || '',
'contributorType': r => data.contributorNameTypes.find(nameType => nameType.id === r.contributorNameTypeId)?.name || '',
'relatorTerm': r => {
'relatorTerm': r => {
if (!r.contributorTypeId) {
if (!r.contributorTypeId) {
return '';
return '';
}
}
return r.contributorTypeId.reduce((acc, contributorTypeId) => {
return r.contributorTypeId.reduce((acc, contributorTypeId) => {
return [...acc, data.contributorTypes.find(type => type.id === contributorTypeId)?.name || ''];
return [...acc, data.contributorTypes.find(type => type.id === contributorTypeId)?.name || ''];
}, []).filter(name => !!name).join(', ');
}, []).filter(name => !!name).join(', ');
},
},
'numberOfTitles': r => ((r?.instance || r?.totalRecords) || (r?.subject && r?.totalRecords > 0)) && getFullMatchRecord(r?.totalRecords, r.isAnchor),
'numberOfTitles': r => ((r?.instance || r?.totalRecords) || (r?.subject && r?.totalRecords > 0)) && getFullMatchRecord(r?.totalRecords, r.isAnchor),
};
};
Copiar
Copiado
Copiar
Copiado
const browseQueryExecuted = Boolean(this.getExecutedBrowseQuery());
const visibleColumns = this.getVisibleColumns();
const visibleColumns = this.getVisibleColumns();
const columnMapping = this.getColumnMapping();
const columnMapping = this.getColumnMapping();
Copiar
Copiado
Copiar
Copiado
const isHandleOnNeedMore = Object.values(browseModeOptions).includes(optionSelected) ? handleOnNeedMore : null;
const onChangeIndex = (e) => {
const onChangeIndex = (e) => {
Copiar
Copiado
Copiar
Copiado
this.setState({ optionSelected: e.target.value });
const isBrowseOption = Object.values(browseModeOptions).includes(e.target.value);
parentMutator.query.update({ qindex: e.target.value, filters: '' });
parentMutator.query.update({ qindex: e.target.value, filters: '' });
Copiar
Copiado
Copiar
Copiado
if (isBrowseOption) {
parentMutator.browseModeRecords.reset();
this.setState({ isSingleResult: false });
} else {
this.setState({ isSingleResult: true });
}
};
};
const browseFilter = () => {
const browseFilter = () => {
Copiar
Copiado
Copiar
Copiado
const { renderer } = getFilterConfig('instances
Browse
');
const { renderer } = getFilterConfig('instances
Search
');
if (optionSelected === browseModeOptions.SUBJECTS) {
return renderFilters;
return renderer;
} else if (optionSelected === browseModeOptions.CALL_NUMBERS) {
return renderer({
...data,
onFetchFacets: fetchFacets(data),
parentResources,
browseType: browseModeOptions.CALL_NUMBERS,
});
} else if (optionSelected === browseModeOptions.CONTRIBUTORS) {
return renderer({
...data,
onFetchFacets: fetchFacets(data),
parentResources,
browseType: browseModeOptions.CONTRIBUTORS,
});
} else
return renderFilters;
};
};
Copiar
Copiado
Copiar
Copiado
const browseSelectedString = Object.values(browseModeOptions).some(el => optionSelected.includes(el));
const customPaneSubTextBrowse = browseSelectedString ? <FormattedMessage id="ui-inventory.title.subTitle.browseCall" /> : null;
const searchFieldButtonLabelBrowse = browseSelectedString ? <FormattedMessage id="ui-inventory.browse" /> : null;
const titleBrowse = browseSelectedString ? <FormattedMessage id="ui-inventory.title.browseCall" /> : null;
const notLoadedMessageBrowse = browseSelectedString ? <FormattedMessage id="ui-inventory.notLoadedMessage.browseCall" /> : null;
const regExp = /((callNumber|subject|name) [<|>])|"*/ig;
const formattedSearchableIndexes = searchableIndexes.map(index => {
const formattedSearchableIndexes = searchableIndexes.map(index => {
const { prefix = '' } = index;
const { prefix = '' } = index;
let label = index.label;
let label = index.label;
if (index.label.includes('ui-inventory')) {
if (index.label.includes('ui-inventory')) {
label = prefix + intl.formatMessage({ id: index.label });
label = prefix + intl.formatMessage({ id: index.label });
}
}
Copiar
Copiado
Copiar
Copiado
return { ...index, label };
});
const shortcuts = [
{
name: 'new',
handler: handleKeyCommand(() => {
if (stripes.hasPerm('ui-inventory.instance.create')) {
this.openCreateInstance();
}
}),
},
];
const other = parentResources.records.other;
const pagingCanGoNext = browseQueryExecuted ? !!other?.next : null;
const pagingCanGoPrevious = browseQueryExecuted ? !!other?.prev : null;
return (
<HasCommand
commands={shortcuts}
isWithinScope={checkScope}
scope={document.body}
>
<div data-test-inventory-instances>
<SearchAndSort
key={searchAndSortKey}
packageInfo={packageInfo}
objectName="inventory"
title={titleBrowse}
customPaneSubText={customPaneSubTextBrowse}
searchFieldButtonLabel={searchFieldButtonLabelBrowse}
maxSortKeys={1}
notLoadedMessage={notLoadedMessageBrowse}
renderNavigation={this.renderNavigation}
searchableIndexes={formattedSearchableIndexes}
selectedIndex={get(data.query, 'qindex')}
searchableIndexesPlaceholder={null}
initialResultCount={INITIAL_RESULT_COUNT}
resultCountIncrement={RESULT_COUNT_INCREMENT}
isCountHidden={browseSelectedString}
viewRecordComponent={ViewInstanceWrapper}
editRecordComponent={InstanceForm}
onChangeIndex={onChangeIndex}
regExpForQuery={regExp}
newRecordInitialValues={(this.state && this.state.copiedInstance) ? this.state.copiedInstance : {
discoverySuppress: false,
staffSuppress: false,
previouslyHeld: false,
source: 'FOLIO',
}}
visibleColumns={visibleColumns}
columnMapping={columnMapping}
columnWidths={{
callNumber: '15%',
subject: '50%',
contributor: '50%',
numberOfTitles: '15%',
select: '30px',
title: '40%',
contributorType: '15%',
relatorTerm: '15%',
}}
getCellClass={this.formatCellStyles}
customPaneSub={this.renderPaneSub()}
resultsFormatter={resultsFormatter}
onCreate={this.onCreate}
viewRecordPerms="ui-inventory.instance.view"
newRecordPerms="ui-inventory.instance.create"
disableRecordCreation={disableRecordCreation || false}
parentResources={parentResources}
parentMutator={parentMutator}
detailProps={{
referenceTables: data,
onCopy: this.copyInstance,
}}
basePath={path}
path={`${path}/(view|viewsource)/:id/:holdingsrecordid?/:itemid?`}
showSingleResult={isSingleResult}
browseOnly={browseOnly}
onSelectRow={browseQueryExecuted ? this.onSelectRow : undefined}
renderFilters={browseFilter()}
onFilterChange={this.onFilterChangeHandler}
pageAmount={100}
pagingType={pagingTypes.PREV_NEXT}
hidePageIndices={browseQueryExecuted}
hasNewButton={false}
onResetAll={this.handleResetAll}
sortableColumns={['title', 'contributors', 'publishers']}
syncQueryWithUrl
resultsVirtualize={false}
resultsOnMarkPosition={this.onMarkPosition}
resultsOnResetMarkedPosition={this.resetMarkedPosition}
resultsCachedPosition={itemToView}
resultsOnNeedMore={isHandleOnNeedMore}
pagingCanGoNext={pagingCanGoNext}
pagingCanGoPrevious={pagingCanGoPrevious}
/>
</div>
</HasCommand>
);
}
}
export default withNamespace(flowRight(
injectIntl,
withLocation,
)(stripesConnect(InstancesList)));
Diferencias guardadas
Texto original
Abrir archivo
import React from 'react'; import PropTypes from 'prop-types'; import { omit, get, set, flowRight, size, } from 'lodash'; import { injectIntl, FormattedMessage, } from 'react-intl'; import { AppIcon, CalloutContext, stripesConnect, withNamespace, } from '@folio/stripes/core'; import { SearchAndSort } from '@folio/stripes/smart-components'; import { Icon, Checkbox, checkScope, HasCommand, } from '@folio/stripes/components'; import { pagingTypes } from '@folio/stripes-components/lib/MultiColumnList'; import packageInfo from '../../../package'; import InstanceForm from '../../edit/InstanceForm'; import ViewInstanceWrapper from '../../ViewInstanceWrapper'; import formatters from '../../referenceFormatters'; import withLocation from '../../withLocation'; import CheckboxColumn from '../../components/InstancesList/CheckboxColumn'; import { getCurrentFilters, getNextSelectedRowsState, parseFiltersToStr, marshalInstance, omitFromArray, handleKeyCommand, } from '../../utils'; import { segments, browseModeOptions, FACETS, } from '../../constants'; import { getItem, setItem, } from '../../storage'; import facetsStore from '../../stores/facetsStore'; import css from '../../components/InstancesList/instances.css'; import { getFilterConfig } from '../../filterConfig'; import SearchBrowseNavigation from '../../components/SearchBrowseNavigation/SearchBrowseNavigation'; const INITIAL_RESULT_COUNT = 30; const RESULT_COUNT_INCREMENT = 30; const columnSets = { SUBJECTS: ['subject', 'numberOfTitles'], CALL_NUMBERS: ['callNumber', 'title', 'numberOfTitles'], CONTRIBUTORS: ['contributor', 'contributorType', 'relatorTerm', 'numberOfTitles'], }; const TOGGLEABLE_COLUMNS = ['contributors', 'publishers', 'relation']; const NON_TOGGLEABLE_COLUMNS = ['select', 'title']; const ALL_COLUMNS = Array.from(new Set([ ...NON_TOGGLEABLE_COLUMNS, ...TOGGLEABLE_COLUMNS, ...columnSets.CALL_NUMBERS, ...columnSets.SUBJECTS, ...columnSets.CONTRIBUTORS, ])); const VISIBLE_COLUMNS_STORAGE_KEY = 'inventory-visible-columns'; class InstancesList extends React.Component { static defaultProps = { browseOnly: false, showSingleResult: true, }; static propTypes = { data: PropTypes.object, parentResources: PropTypes.object, parentMutator: PropTypes.object, showSingleResult: PropTypes.bool, browseOnly: PropTypes.bool, disableRecordCreation: PropTypes.bool, updateLocation: PropTypes.func.isRequired, goTo: PropTypes.func.isRequired, getParams: PropTypes.func.isRequired, segment: PropTypes.string, intl: PropTypes.object, match: PropTypes.shape({ path: PropTypes.string.isRequired, params: PropTypes.object.isRequired, }).isRequired, namespace: PropTypes.string, renderFilters: PropTypes.func.isRequired, searchableIndexes: PropTypes.arrayOf(PropTypes.object).isRequired, mutator: PropTypes.shape({ query: PropTypes.shape({ update: PropTypes.func.isRequired, replace: PropTypes.func.isRequired, }).isRequired, }), location: PropTypes.shape({ search: PropTypes.string }), stripes: PropTypes.object.isRequired, fetchFacets: PropTypes.func, }; static contextType = CalloutContext; static manifest = Object.freeze({ query: {}, }); constructor(props) { super(props); this.state = { selectedRows: {}, optionSelected: '', searchAndSortKey: 0, isSingleResult: this.props.showSingleResult, }; } componentDidMount() { if (this.getSelectedBrowseOption() || this.getExecutedBrowseQuery()) { this.setState({ optionSelected: this.getQIndexFromParams(), isSingleResult: false, }); } else { this.setState({ optionSelected: '', }); } } getQIndexFromParams = () => { const params = new URLSearchParams(this.props.location.search); return params.get('qindex'); } getSelectedBrowseOption = () => { const isBrowseSelectedBasedOnState = Object.keys(browseModeOptions).filter(k => browseModeOptions[k] === this.state.optionSelected)[0]; return isBrowseSelectedBasedOnState; } getExecutedBrowseQuery = () => { const isBrowseSelectedBasedOnUrl = Object.keys(browseModeOptions).filter(k => browseModeOptions[k] === this.getQIndexFromParams())[0]; return isBrowseSelectedBasedOnUrl; } getInitialToggableColumns = () => { return getItem(VISIBLE_COLUMNS_STORAGE_KEY) || TOGGLEABLE_COLUMNS; } getVisibleColumns = () => { // display particular columnset based on the quindex value in the URL, since the search has been // performed. const executedBrowseQuery = Object.keys(browseModeOptions).filter(k => browseModeOptions[k] === this.getQIndexFromParams())[0]; const columns = columnSets[executedBrowseQuery]; if (!executedBrowseQuery) { const visibleColumns = new Set([...NON_TOGGLEABLE_COLUMNS]); return ALL_COLUMNS.filter(key => visibleColumns.has(key)); } else { const visibleColumns = new Set([...columns]); return Array.from(visibleColumns); } } onFilterChangeHandler = ({ name, values }) => { const { data: { query }, match: { path }, goTo, getParams, } = this.props; const curFilters = getCurrentFilters(get(query, 'filters', '')); const mergedFilters = values.length ? { ...curFilters, [name]: values } : omit(curFilters, name); const filtersStr = parseFiltersToStr(mergedFilters); const params = getParams(); goTo(path, { ...params, filters: filtersStr }); }; closeNewInstance = (e) => { if (e) e.preventDefault(); this.setState({ copiedInstance: null }); this.props.updateLocation({ layer: null }); }; createInstance = (instanceData) => { const { data: { identifierTypesByName } } = this.props; // Massage record to add preceeding and succeeding title fields const instance = marshalInstance(instanceData, identifierTypesByName); // POST item record return this.props.parentMutator.records.POST(instance); }; onCreate = (instance) => { return this.createInstance(instance).then(() => this.closeNewInstance()); } openCreateInstance = () => { this.props.updateLocation({ layer: 'create' }); } copyInstance = (instance) => { const { precedingTitles, succeedingTitles, childInstances, parentInstances, } = instance; let copiedInstance = omit(instance, ['id', 'hrid']); if (precedingTitles?.length) { copiedInstance.precedingTitles = omitFromArray(precedingTitles, 'id'); } if (succeedingTitles?.length) { copiedInstance.succeedingTitles = omitFromArray(succeedingTitles, 'id'); } if (childInstances?.length) { copiedInstance.childInstances = omitFromArray(childInstances, 'id'); } if (parentInstances?.length) { copiedInstance.parentInstances = omitFromArray(parentInstances, 'id'); } copiedInstance = set(copiedInstance, 'source', 'FOLIO'); this.setState({ copiedInstance }); this.openCreateInstance(); } refocusOnInputSearch = (segment) => { // when navigation button is clicked to change the search segment // the focus stays on the button so refocus back on the input search. // https://issues.folio.org/browse/UIIN-1358 if (segment !== segments.instances) { this.setState({ optionSelected: '' }); } facetsStore.getState().resetFacetSettings(); document.getElementById('input-inventory-search').focus(); } renderNavigation = () => ( <> <SearchBrowseNavigation segment={this.props.segment} onChange={this.refocusOnInputSearch} /> </> ); onMarkPosition = (position) => { const { namespace } = this.props; setItem(`${namespace}.position`, position); } resetMarkedPosition = () => { const { namespace } = this.props; setItem(`${namespace}.position`, null); } getColumnMapping = () => { const { intl } = this.props; const columnMapping = { callNumber: intl.formatMessage({ id: 'ui-inventory.instances.columns.callNumber' }), select: '', title: intl.formatMessage({ id: 'ui-inventory.instances.columns.title' }), contributors: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributors' }), publishers: intl.formatMessage({ id: 'ui-inventory.instances.columns.publishers' }), relation: intl.formatMessage({ id: 'ui-inventory.instances.columns.relation' }), numberOfTitles: intl.formatMessage({ id: 'ui-inventory.instances.columns.numberOfTitles' }), subject: intl.formatMessage({ id: 'ui-inventory.subject' }), contributor: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributor' }), contributorType: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributorType' }), relatorTerm: intl.formatMessage({ id: 'ui-inventory.instances.columns.relatorTerm' }), }; return columnMapping; } toggleRowSelection = row => { this.setState(({ selectedRows }) => ({ selectedRows: getNextSelectedRowsState(selectedRows, row) })); }; handleResetAll = () => { this.setState({ selectedRows: {}, optionSelected: '', }); facetsStore.getState().resetFacetSettings(); } renderPaneSub() { const selectedRowsCount = size(this.state.selectedRows); return selectedRowsCount ? ( <FormattedMessage id="ui-inventory.instances.rows.recordsSelected" values={{ count: selectedRowsCount }} /> ) : null; } formatCellStyles(defaultCellStyle) { return `${defaultCellStyle} ${css.cellAlign}`; } // handler used for clicking a row in browse mode onSelectRow = (_, row) => { const { parentMutator, parentResources, } = this.props; switch (get(parentResources.query, 'qindex')) { case browseModeOptions.CALL_NUMBERS: parentMutator.query.update({ qindex: 'callNumber', query: row.shelfKey, filters: '', }); break; case browseModeOptions.SUBJECTS: parentMutator.query.update({ qindex: 'subject', query: row.subject, filters: '', }); break; case browseModeOptions.CONTRIBUTORS: if (row.isAnchor && !row.contributorNameTypeId) { return; } parentMutator.query.update({ qindex: 'contributor', query: row.name, filters: `${FACETS.SEARCH_CONTRIBUTORS}.${row.contributorNameTypeId}`, }); break; default: } // the searchAndSortKey state field can be updated to reset SearchAndSort // to use the app-level selectedIndex this.setState((curState) => ({ searchAndSortKey: curState.searchAndSortKey + 1, })); } render() { const { browseOnly, disableRecordCreation, intl, data, parentResources, parentMutator, renderFilters, searchableIndexes, match: { path, }, namespace, stripes, fetchFacets, } = this.props; const { selectedRows, optionSelected, searchAndSortKey, isSingleResult } = this.state; const itemToView = getItem(`${namespace}.position`); const missedMatchItem = () => { const query = new URLSearchParams(this.props.location.search).get('query'); return ( <div className={css.missedMatchItemWrapper}> <span className={css.warnIcon}> <Icon size="medium" icon="exclamation-circle" status="warn" /> </span> <span className={`${css.missingMatchError} ${css.fitContent}`}> {query} </span> <strong className={css.fitContent}> <FormattedMessage id="ui-inventory.browseCallNumbers.missedMatch" /> </strong> </div> ); }; const getFullMatchRecord = (item, isAnchor) => { if (isAnchor) { return <strong>{item}</strong>; } return item; }; const handleOnNeedMore = ({ direction, records, source }) => { const paramByBrowseMode = { [browseModeOptions.SUBJECTS]: 'subject', [browseModeOptions.CALL_NUMBERS]: 'callNumber', [browseModeOptions.CONTRIBUTORS]: 'name', }; const isSubject = optionSelected === browseModeOptions.SUBJECTS; const isCallNumber = optionSelected === browseModeOptions.CALL_NUMBERS; const isContributors = optionSelected === browseModeOptions.CONTRIBUTORS; const param = paramByBrowseMode[optionSelected]; let anchor; if (direction === 'prev') { if (isCallNumber) { anchor = records.find(i => i.fullCallNumber)?.shelfKey; } else if (isSubject) { anchor = records[0].subject; } else if (isContributors) { anchor = records[0].name; } source.fetchByQuery(`${param} < "${anchor}"`); } else { if (isCallNumber) { anchor = [...records].reverse().find(i => i.fullCallNumber)?.shelfKey; } else if (isSubject) { anchor = records[records.length - 1].subject; } else if (isContributors) { anchor = records[records.length - 1].name; } source.fetchByQuery(`${param} > "${anchor}"`); } }; const resultsFormatter = { 'title': ({ title, instance, discoverySuppress, isBoundWith, staffSuppress, isAnchor, }) => { if (this.getExecutedBrowseQuery()) { return getFullMatchRecord(instance?.title, isAnchor); } else { return ( <AppIcon size="small" app="inventory" iconKey="instance" iconAlignment="baseline" > {title} {(isBoundWith) && <AppIcon size="small" app="@folio/inventory" iconKey="bound-with" iconClassName={css.boundWithIcon} /> } {(discoverySuppress || staffSuppress) && <span className={css.warnIcon}> <Icon size="medium" icon="exclamation-circle" status="warn" /> </span> } </AppIcon> ); } }, 'subject': r => { if (r?.totalRecords) { return getFullMatchRecord(r?.subject, r.isAnchor); } return missedMatchItem(); }, 'callNumber': r => { if (r?.instance || r?.totalRecords) { return getFullMatchRecord(r?.fullCallNumber, r.isAnchor); } return missedMatchItem(); }, 'contributor': r => { if (r?.totalRecords) { return getFullMatchRecord(r?.name, r.isAnchor); } return missedMatchItem(); }, 'contributorType': r => data.contributorNameTypes.find(nameType => nameType.id === r.contributorNameTypeId)?.name || '', 'relatorTerm': r => { if (!r.contributorTypeId) { return ''; } return r.contributorTypeId.reduce((acc, contributorTypeId) => { return [...acc, data.contributorTypes.find(type => type.id === contributorTypeId)?.name || '']; }, []).filter(name => !!name).join(', '); }, 'numberOfTitles': r => ((r?.instance || r?.totalRecords) || (r?.subject && r?.totalRecords > 0)) && getFullMatchRecord(r?.totalRecords, r.isAnchor), }; const browseQueryExecuted = Boolean(this.getExecutedBrowseQuery()); const visibleColumns = this.getVisibleColumns(); const columnMapping = this.getColumnMapping(); const isHandleOnNeedMore = Object.values(browseModeOptions).includes(optionSelected) ? handleOnNeedMore : null; const onChangeIndex = (e) => { this.setState({ optionSelected: e.target.value }); const isBrowseOption = Object.values(browseModeOptions).includes(e.target.value); parentMutator.query.update({ qindex: e.target.value, filters: '' }); if (isBrowseOption) { parentMutator.browseModeRecords.reset(); this.setState({ isSingleResult: false }); } else { this.setState({ isSingleResult: true }); } }; const browseFilter = () => { const { renderer } = getFilterConfig('instancesBrowse'); if (optionSelected === browseModeOptions.SUBJECTS) { return renderer; } else if (optionSelected === browseModeOptions.CALL_NUMBERS) { return renderer({ ...data, onFetchFacets: fetchFacets(data), parentResources, browseType: browseModeOptions.CALL_NUMBERS, }); } else if (optionSelected === browseModeOptions.CONTRIBUTORS) { return renderer({ ...data, onFetchFacets: fetchFacets(data), parentResources, browseType: browseModeOptions.CONTRIBUTORS, }); } else return renderFilters; }; const browseSelectedString = Object.values(browseModeOptions).some(el => optionSelected.includes(el)); const customPaneSubTextBrowse = browseSelectedString ? <FormattedMessage id="ui-inventory.title.subTitle.browseCall" /> : null; const searchFieldButtonLabelBrowse = browseSelectedString ? <FormattedMessage id="ui-inventory.browse" /> : null; const titleBrowse = browseSelectedString ? <FormattedMessage id="ui-inventory.title.browseCall" /> : null; const notLoadedMessageBrowse = browseSelectedString ? <FormattedMessage id="ui-inventory.notLoadedMessage.browseCall" /> : null; const regExp = /((callNumber|subject|name) [<|>])|"*/ig; const formattedSearchableIndexes = searchableIndexes.map(index => { const { prefix = '' } = index; let label = index.label; if (index.label.includes('ui-inventory')) { label = prefix + intl.formatMessage({ id: index.label }); } return { ...index, label }; }); const shortcuts = [ { name: 'new', handler: handleKeyCommand(() => { if (stripes.hasPerm('ui-inventory.instance.create')) { this.openCreateInstance(); } }), }, ]; const other = parentResources.records.other; const pagingCanGoNext = browseQueryExecuted ? !!other?.next : null; const pagingCanGoPrevious = browseQueryExecuted ? !!other?.prev : null; return ( <HasCommand commands={shortcuts} isWithinScope={checkScope} scope={document.body} > <div data-test-inventory-instances> <SearchAndSort key={searchAndSortKey} packageInfo={packageInfo} objectName="inventory" title={titleBrowse} customPaneSubText={customPaneSubTextBrowse} searchFieldButtonLabel={searchFieldButtonLabelBrowse} maxSortKeys={1} notLoadedMessage={notLoadedMessageBrowse} renderNavigation={this.renderNavigation} searchableIndexes={formattedSearchableIndexes} selectedIndex={get(data.query, 'qindex')} searchableIndexesPlaceholder={null} initialResultCount={INITIAL_RESULT_COUNT} resultCountIncrement={RESULT_COUNT_INCREMENT} isCountHidden={browseSelectedString} viewRecordComponent={ViewInstanceWrapper} editRecordComponent={InstanceForm} onChangeIndex={onChangeIndex} regExpForQuery={regExp} newRecordInitialValues={(this.state && this.state.copiedInstance) ? this.state.copiedInstance : { discoverySuppress: false, staffSuppress: false, previouslyHeld: false, source: 'FOLIO', }} visibleColumns={visibleColumns} columnMapping={columnMapping} columnWidths={{ callNumber: '15%', subject: '50%', contributor: '50%', numberOfTitles: '15%', select: '30px', title: '40%', contributorType: '15%', relatorTerm: '15%', }} getCellClass={this.formatCellStyles} customPaneSub={this.renderPaneSub()} resultsFormatter={resultsFormatter} onCreate={this.onCreate} viewRecordPerms="ui-inventory.instance.view" newRecordPerms="ui-inventory.instance.create" disableRecordCreation={disableRecordCreation || false} parentResources={parentResources} parentMutator={parentMutator} detailProps={{ referenceTables: data, onCopy: this.copyInstance, }} basePath={path} path={`${path}/(view|viewsource)/:id/:holdingsrecordid?/:itemid?`} showSingleResult={isSingleResult} browseOnly={browseOnly} onSelectRow={browseQueryExecuted ? this.onSelectRow : undefined} renderFilters={browseFilter()} onFilterChange={this.onFilterChangeHandler} pageAmount={100} pagingType={pagingTypes.PREV_NEXT} hidePageIndices={browseQueryExecuted} hasNewButton={false} onResetAll={this.handleResetAll} sortableColumns={['title', 'contributors', 'publishers']} syncQueryWithUrl resultsVirtualize={false} resultsOnMarkPosition={this.onMarkPosition} resultsOnResetMarkedPosition={this.resetMarkedPosition} resultsCachedPosition={itemToView} resultsOnNeedMore={isHandleOnNeedMore} pagingCanGoNext={pagingCanGoNext} pagingCanGoPrevious={pagingCanGoPrevious} /> </div> </HasCommand> ); } } export default withNamespace(flowRight( injectIntl, withLocation, )(stripesConnect(InstancesList)));
Texto modificado
Abrir archivo
import React from 'react'; import PropTypes from 'prop-types'; import { omit, get, set, flowRight, size, isEmpty, noop, } from 'lodash'; import { injectIntl, FormattedMessage, } from 'react-intl'; import saveAs from 'file-saver'; import moment from 'moment'; import { Pluggable, AppIcon, IfPermission, IfInterface, CalloutContext, stripesConnect, withNamespace, } from '@folio/stripes/core'; import { SearchAndSort } from '@folio/stripes/smart-components'; import { Button, Icon, Checkbox, MenuSection, checkScope, HasCommand, } from '@folio/stripes/components'; import { pagingTypes } from '@folio/stripes-components/lib/MultiColumnList'; import FilterNavigation from '../../components/FilterNavigation'; import packageInfo from '../../../package'; import InstanceForm from '../../edit/InstanceForm'; import ViewInstanceWrapper from '../../ViewInstanceWrapper'; import formatters from '../../referenceFormatters'; import withLocation from '../../withLocation'; import { getCurrentFilters, getNextSelectedRowsState, parseFiltersToStr, marshalInstance, omitFromArray, isTestEnv, handleKeyCommand, } from '../../utils'; import { INSTANCES_ID_REPORT_TIMEOUT, QUICK_EXPORT_LIMIT, segments, } from '../../constants'; import { IdReportGenerator, InTransitItemsReport, } from '../../reports'; import ErrorModal from '../../components/ErrorModal'; import CheckboxColumn from '../../components/InstancesList/CheckboxColumn'; import SelectedRecordsModal from '../../components/SelectedRecordsModal'; import ImportRecordModal from '../../components/ImportRecordModal'; import { buildQuery } from '../../routes/buildManifestObject'; import { getItem, setItem, } from '../../storage'; import facetsStore from '../../stores/facetsStore'; import css from '../../components/InstancesList/instances.css'; import { getFilterConfig } from '../../filterConfig'; import SearchBrowseNavigation from '../../components/SearchBrowseNavigation/SearchBrowseNavigation'; const INITIAL_RESULT_COUNT = 30; const RESULT_COUNT_INCREMENT = 30; const columnSets = { SUBJECTS: ['subject', 'numberOfTitles'], CALL_NUMBERS: ['callNumber', 'title', 'numberOfTitles'], CONTRIBUTORS: ['contributor', 'contributorType', 'relatorTerm', 'numberOfTitles'], }; const TOGGLEABLE_COLUMNS = ['contributors', 'publishers', 'relation']; const NON_TOGGLEABLE_COLUMNS = ['select', 'title']; const ALL_COLUMNS = Array.from(new Set([ ...NON_TOGGLEABLE_COLUMNS, ...TOGGLEABLE_COLUMNS, ...columnSets.CALL_NUMBERS, ...columnSets.SUBJECTS, ...columnSets.CONTRIBUTORS, ])); const VISIBLE_COLUMNS_STORAGE_KEY = 'inventory-visible-columns'; class SearchView extends React.Component { static defaultProps = { showSingleResult: true, }; static propTypes = { data: PropTypes.object, parentResources: PropTypes.object, parentMutator: PropTypes.object, showSingleResult: PropTypes.bool, disableRecordCreation: PropTypes.bool, updateLocation: PropTypes.func.isRequired, goTo: PropTypes.func.isRequired, getParams: PropTypes.func.isRequired, segment: PropTypes.string, intl: PropTypes.object, match: PropTypes.shape({ path: PropTypes.string.isRequired, params: PropTypes.object.isRequired, }).isRequired, namespace: PropTypes.string, renderFilters: PropTypes.func.isRequired, searchableIndexes: PropTypes.arrayOf(PropTypes.object).isRequired, mutator: PropTypes.shape({ query: PropTypes.shape({ update: PropTypes.func.isRequired, replace: PropTypes.func.isRequired, }).isRequired, }), location: PropTypes.shape({ search: PropTypes.string }), stripes: PropTypes.object.isRequired, fetchFacets: PropTypes.func, }; static contextType = CalloutContext; static manifest = Object.freeze({ query: {}, }); constructor(props) { super(props); this.state = { showNewFastAddModal: false, inTransitItemsExportInProgress: false, instancesIdExportInProgress: false, holdingsIdExportInProgress: false, instancesQuickExportInProgress: false, showErrorModal: false, selectedRows: {}, isSelectedRecordsModalOpened: false, visibleColumns: this.getInitialToggableColumns(), isImportRecordModalOpened: false, searchAndSortKey: 0, isSingleResult: this.props.showSingleResult, }; } getQIndexFromParams = () => { const params = new URLSearchParams(this.props.location.search); return params.get('qindex'); } getInitialToggableColumns = () => { return getItem(VISIBLE_COLUMNS_STORAGE_KEY) || TOGGLEABLE_COLUMNS; } getVisibleColumns = () => { // display particular columnset based on the quindex value in the URL, since the search has been // performed. const columns = this.state.visibleColumns; const visibleColumns = new Set([...columns, ...NON_TOGGLEABLE_COLUMNS]); return ALL_COLUMNS.filter(key => visibleColumns.has(key)); } onFilterChangeHandler = ({ name, values }) => { const { data: { query }, match: { path }, goTo, getParams, } = this.props; const curFilters = getCurrentFilters(get(query, 'filters', '')); const mergedFilters = values.length ? { ...curFilters, [name]: values } : omit(curFilters, name); const filtersStr = parseFiltersToStr(mergedFilters); const params = getParams(); goTo(path, { ...params, filters: filtersStr }); }; closeNewInstance = (e) => { if (e) e.preventDefault(); this.setState({ copiedInstance: null }); this.props.updateLocation({ layer: null }); }; createInstance = (instanceData) => { const { data: { identifierTypesByName } } = this.props; // Massage record to add preceeding and succeeding title fields const instance = marshalInstance(instanceData, identifierTypesByName); // POST item record return this.props.parentMutator.records.POST(instance); }; onCreate = (instance) => { return this.createInstance(instance).then(() => this.closeNewInstance()); } openCreateInstance = () => { this.props.updateLocation({ layer: 'create' }); } copyInstance = (instance) => { const { precedingTitles, succeedingTitles, childInstances, parentInstances, } = instance; let copiedInstance = omit(instance, ['id', 'hrid']); if (precedingTitles?.length) { copiedInstance.precedingTitles = omitFromArray(precedingTitles, 'id'); } if (succeedingTitles?.length) { copiedInstance.succeedingTitles = omitFromArray(succeedingTitles, 'id'); } if (childInstances?.length) { copiedInstance.childInstances = omitFromArray(childInstances, 'id'); } if (parentInstances?.length) { copiedInstance.parentInstances = omitFromArray(parentInstances, 'id'); } copiedInstance = set(copiedInstance, 'source', 'FOLIO'); this.setState({ copiedInstance }); this.openCreateInstance(); } refocusOnInputSearch = () => { // when navigation button is clicked to change the search segment // the focus stays on the button so refocus back on the input search. // https://issues.folio.org/browse/UIIN-1358 facetsStore.getState().resetFacetSettings(); document.getElementById('input-inventory-search').focus(); } renderNavigation = () => ( <> <SearchBrowseNavigation segment={this.props.segment} onChange={this.refocusOnInputSearch} /> <FilterNavigation segment={this.props.segment} onChange={this.refocusOnInputSearch} /> </> ); generateInTransitItemReport = async () => { const { intl: { formatMessage, }, parentMutator, } = this.props; const { sendCallout, removeCallout } = this.context; const calloutId = sendCallout({ message: <FormattedMessage id="ui-inventory.exportInProgress" />, timeout: 0, }); try { const report = new InTransitItemsReport(parentMutator, formatMessage); const items = await report.toCSV(); if (!items?.length) { this.setState({ showErrorModal: true }); } } catch (error) { throw new Error(error); } finally { removeCallout(calloutId); this.setState({ inTransitItemsExportInProgress: false }); } }; startInTransitReportGeneration = () => { const { inTransitItemsExportInProgress } = this.state; if (inTransitItemsExportInProgress) { return; } this.setState({ inTransitItemsExportInProgress: true }, this.generateInTransitItemReport); }; generateInstancesIdReport = async (sendCallout) => { const { instancesIdExportInProgress } = this.state; if (instancesIdExportInProgress) return; this.setState({ instancesIdExportInProgress: true }, async () => { const { reset, GET, } = this.props.parentMutator.recordsToExportIDs; let infoCalloutTimer; try { reset(); infoCalloutTimer = setTimeout(() => { sendCallout({ type: 'info', message: <FormattedMessage id="ui-inventory.saveInstancesUIIDS.info" />, }); }, INSTANCES_ID_REPORT_TIMEOUT); const items = await GET(); clearTimeout(infoCalloutTimer); const report = new IdReportGenerator('SearchInstanceUUIDs'); if (!isEmpty(items)) { report.toCSV(items, record => record.id); } } catch (error) { clearTimeout(infoCalloutTimer); sendCallout({ type: 'error', message: <FormattedMessage id="ui-inventory.saveInstancesUIIDS.error" />, }); } finally { this.setState({ instancesIdExportInProgress: false }); } }); }; triggerQuickExport = async (sendCallout) => { const { instancesQuickExportInProgress } = this.state; if (instancesQuickExportInProgress) return; this.setState({ instancesQuickExportInProgress: true }); try { const instanceIds = Object.keys(this.state.selectedRows).slice(0, QUICK_EXPORT_LIMIT); await this.props.parentMutator.quickExport.POST({ uuids: instanceIds, type: 'uuid', recordType: 'INSTANCE' }); new IdReportGenerator('QuickInstanceExport').toCSV(instanceIds); } catch (error) { sendCallout({ type: 'error', message: <FormattedMessage id="ui-inventory.communicationProblem" />, }); } finally { this.setState({ instancesQuickExportInProgress: false }); } }; generateCQLQueryReport = async () => { if (!isTestEnv()) { const { data } = this.props; const query = buildQuery(data.query, {}, data, { log: noop }, this.props); const fileName = `SearchInstanceCQLQuery${moment().format()}.cql`; saveAs(new Blob([query], { type: 'text/plain;charset=utf-8;' }), fileName); } } toggleNewFastAddModal = () => { this.setState((state) => { return { showNewFastAddModal: !state.showNewFastAddModal }; }); } generateHoldingsIdReport = async (sendCallout) => { const { holdingsIdExportInProgress } = this.state; if (holdingsIdExportInProgress) return; this.setState({ holdingsIdExportInProgress: true }, async () => { const { reset, GET, } = this.props.parentMutator.holdingsToExportIDs; let infoCalloutTimer; try { reset(); infoCalloutTimer = setTimeout(() => { sendCallout({ type: 'info', message: <FormattedMessage id="ui-inventory.saveHoldingsUIIDS.info" />, }); }, INSTANCES_ID_REPORT_TIMEOUT); const items = await GET(); clearTimeout(infoCalloutTimer); const report = new IdReportGenerator('SearchHoldingsUUIDs'); if (!isEmpty(items)) { report.toCSV(items, record => record.id); } } catch (error) { clearTimeout(infoCalloutTimer); sendCallout({ type: 'error', message: <FormattedMessage id="ui-inventory.saveHoldingsUIIDS.error" />, }); } finally { this.setState({ instancesIdExportInProgress: false }); } }); }; getActionItem = ({ id, icon, messageId, onClickHandler, isDisabled = false }) => { return ( <Button buttonStyle="dropdownItem" id={id} disabled={isDisabled} onClick={onClickHandler} > <Icon icon={icon} size="medium" iconClassName={css.actionIcon} /> <FormattedMessage id={messageId} /> </Button> ); } handleToggleColumn = ({ target: { value: key } }) => { this.setState(({ visibleColumns }) => ({ visibleColumns: visibleColumns.includes(key) ? visibleColumns.filter(k => key !== k) : [...visibleColumns, key] }), () => { setItem(VISIBLE_COLUMNS_STORAGE_KEY, this.state.visibleColumns); }); } onMarkPosition = (position) => { const { namespace } = this.props; setItem(`${namespace}.position`, position); } resetMarkedPosition = () => { const { namespace } = this.props; setItem(`${namespace}.position`, null); } getActionMenu = ({ onToggle }) => { const { parentResources, intl, segment } = this.props; const { inTransitItemsExportInProgress } = this.state; const selectedRowsCount = size(this.state.selectedRows); const isInstancesListEmpty = isEmpty(get(parentResources, ['records', 'records'], [])); const isQuickExportLimitExceeded = selectedRowsCount > QUICK_EXPORT_LIMIT; const visibleColumns = this.getVisibleColumns(); const columnMapping = this.getColumnMapping(); const buildOnClickHandler = onClickHandler => { return () => { onToggle(); onClickHandler(this.context.sendCallout); }; }; return ( <> <MenuSection label={intl.formatMessage({ id: 'ui-inventory.actions' })} id="actions-menu-section"> <IfPermission perm="ui-inventory.instance.create"> <Button buttonStyle="dropdownItem" id="clickable-newinventory" onClick={buildOnClickHandler(this.openCreateInstance)} > <Icon icon="plus-sign" size="medium" iconClassName={css.actionIcon} /> <FormattedMessage id="stripes-smart-components.new" /> </Button> </IfPermission> <Pluggable id="clickable-create-inventory-records" onClose={this.toggleNewFastAddModal} open={this.state.showNewFastAddModal} // control the open modal via state var renderTrigger={() => ( this.getActionItem({ id: 'new-fast-add-record', icon: 'lightning', messageId: 'ui-inventory.newFastAddRecord', onClickHandler: buildOnClickHandler(this.toggleNewFastAddModal), }) )} type="create-inventory-records" /> { inTransitItemsExportInProgress ? this.getActionItem({ id: 'dropdown-clickable-get-report', icon: 'report', messageId: 'ui-inventory.exportInProgress', }) : this.getActionItem({ id: 'dropdown-clickable-get-report', icon: 'report', messageId: 'ui-inventory.inTransitReport', onClickHandler: buildOnClickHandler(this.startInTransitReportGeneration), }) } {this.getActionItem({ id: 'dropdown-clickable-get-instances-uiids', icon: 'save', messageId: 'ui-inventory.saveInstancesUIIDS', onClickHandler: buildOnClickHandler(this.generateInstancesIdReport), isDisabled: isInstancesListEmpty, })} {segment === 'holdings' && this.getActionItem({ id: 'dropdown-clickable-get-holdings-uiids', icon: 'save', messageId: 'ui-inventory.saveHoldingsUIIDS', onClickHandler: buildOnClickHandler(this.generateHoldingsIdReport), isDisabled: isInstancesListEmpty, })} {this.getActionItem({ id: 'dropdown-clickable-get-cql-query', icon: 'search', messageId: 'ui-inventory.saveInstancesCQLQuery', onClickHandler: buildOnClickHandler(this.generateCQLQueryReport), isDisabled: isInstancesListEmpty, })} {this.getActionItem({ id: 'dropdown-clickable-export-marc', icon: 'download', messageId: 'ui-inventory.exportInstancesInMARC', onClickHandler: buildOnClickHandler(this.triggerQuickExport), isDisabled: !selectedRowsCount || isQuickExportLimitExceeded, })} <IfInterface name="copycat-imports"> <IfPermission perm="copycat.profiles.collection.get"> {this.getActionItem({ id: 'dropdown-clickable-import-record', icon: 'lightning', messageId: 'ui-inventory.copycat.import', onClickHandler: buildOnClickHandler(() => this.setState({ isImportRecordModalOpened: true })), })} </IfPermission> </IfInterface> {isQuickExportLimitExceeded && ( <span className={css.feedbackError} data-test-quick-marc-export-limit-exceeded > <FormattedMessage id="ui-inventory.exportInstancesInMARCLimitExceeded" values={{ count: QUICK_EXPORT_LIMIT }} /> </span> )} {this.getActionItem({ id: 'dropdown-clickable-export-json', icon: 'download', messageId: 'ui-inventory.exportInstancesInJSON', onClickHandler: buildOnClickHandler(noop), isDisabled: true, })} {this.getActionItem({ id: 'dropdown-clickable-show-selected-records', icon: 'eye-open', messageId: 'ui-inventory.instances.showSelectedRecords', onClickHandler: buildOnClickHandler(() => this.setState({ isSelectedRecordsModalOpened: true })), isDisabled: !selectedRowsCount, })} </MenuSection> <MenuSection label={intl.formatMessage({ id: 'ui-inventory.showColumns' })} id="columns-menu-section"> {TOGGLEABLE_COLUMNS.map(key => ( <Checkbox key={key} name={key} data-testid={key} label={columnMapping[key]} id={`inventory-search-column-checkbox-${key}`} checked={visibleColumns.includes(key)} value={key} onChange={this.handleToggleColumn} /> ))} </MenuSection> </> ); }; getColumnMapping = () => { const { intl } = this.props; const columnMapping = { callNumber: intl.formatMessage({ id: 'ui-inventory.instances.columns.callNumber' }), select: '', title: intl.formatMessage({ id: 'ui-inventory.instances.columns.title' }), contributors: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributors' }), publishers: intl.formatMessage({ id: 'ui-inventory.instances.columns.publishers' }), relation: intl.formatMessage({ id: 'ui-inventory.instances.columns.relation' }), numberOfTitles: intl.formatMessage({ id: 'ui-inventory.instances.columns.numberOfTitles' }), subject: intl.formatMessage({ id: 'ui-inventory.subject' }), contributor: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributor' }), contributorType: intl.formatMessage({ id: 'ui-inventory.instances.columns.contributorType' }), relatorTerm: intl.formatMessage({ id: 'ui-inventory.instances.columns.relatorTerm' }), }; return columnMapping; } onErrorModalClose = () => { this.setState({ showErrorModal: false }); }; toggleRowSelection = row => { this.setState(({ selectedRows }) => ({ selectedRows: getNextSelectedRowsState(selectedRows, row) })); }; handleResetAll = () => { this.setState({ selectedRows: {}, }); facetsStore.getState().resetFacetSettings(); } handleSelectedRecordsModalSave = selectedRecords => { this.setState({ isSelectedRecordsModalOpened: false, selectedRows: selectedRecords, }); } handleSelectedRecordsModalCancel = () => { this.setState({ isSelectedRecordsModalOpened: false }); } handleImportRecordModalSubmit = (args) => { this.setState({ isImportRecordModalOpened: false }); this.props.mutator.query.update({ _path: '/inventory/import', xidtype: args.externalIdentifierType, xid: args.externalIdentifier, }); } handleImportRecordModalCancel = () => { this.setState({ isImportRecordModalOpened: false }); } renderPaneSub() { const selectedRowsCount = size(this.state.selectedRows); return selectedRowsCount ? ( <FormattedMessage id="ui-inventory.instances.rows.recordsSelected" values={{ count: selectedRowsCount }} /> ) : null; } formatCellStyles(defaultCellStyle) { return `${defaultCellStyle} ${css.cellAlign}`; } render() { const { disableRecordCreation, intl, data, parentResources, parentMutator, renderFilters, searchableIndexes, match: { path, }, namespace, stripes, } = this.props; const { isSelectedRecordsModalOpened, isImportRecordModalOpened, selectedRows, searchAndSortKey, isSingleResult } = this.state; const itemToView = getItem(`${namespace}.position`); const getFullMatchRecord = (item, isAnchor) => { if (isAnchor) { return <strong>{item}</strong>; } return item; }; const resultsFormatter = { 'select': ({ id, ...rowData }) => ( <CheckboxColumn> <Checkbox checked={Boolean(selectedRows[id])} aria-label={intl.formatMessage({ id: 'ui-inventory.instances.rows.select' })} onChange={() => this.toggleRowSelection({ id, ...rowData })} /> </CheckboxColumn> ), 'title': ({ title, discoverySuppress, isBoundWith, staffSuppress, }) => { return ( <AppIcon size="small" app="inventory" iconKey="instance" iconAlignment="baseline" > {title} {(isBoundWith) && <AppIcon size="small" app="@folio/inventory" iconKey="bound-with" iconClassName={css.boundWithIcon} /> } {(discoverySuppress || staffSuppress) && <span className={css.warnIcon}> <Icon size="medium" icon="exclamation-circle" status="warn" /> </span> } </AppIcon> ); }, 'relation': r => formatters.relationsFormatter(r, data.instanceRelationshipTypes), 'publishers': r => (r?.publication ?? []).map(p => (p ? `${p.publisher} ${p.dateOfPublication ? `(${p.dateOfPublication})` : ''}` : '')).join(', '), 'publication date': r => r.publication.map(p => p.dateOfPublication).join(', '), 'contributors': r => formatters.contributorsFormatter(r, data.contributorTypes), 'contributorType': r => data.contributorNameTypes.find(nameType => nameType.id === r.contributorNameTypeId)?.name || '', 'relatorTerm': r => { if (!r.contributorTypeId) { return ''; } return r.contributorTypeId.reduce((acc, contributorTypeId) => { return [...acc, data.contributorTypes.find(type => type.id === contributorTypeId)?.name || '']; }, []).filter(name => !!name).join(', '); }, 'numberOfTitles': r => ((r?.instance || r?.totalRecords) || (r?.subject && r?.totalRecords > 0)) && getFullMatchRecord(r?.totalRecords, r.isAnchor), }; const visibleColumns = this.getVisibleColumns(); const columnMapping = this.getColumnMapping(); const onChangeIndex = (e) => { parentMutator.query.update({ qindex: e.target.value, filters: '' }); }; const browseFilter = () => { const { renderer } = getFilterConfig('instancesSearch'); return renderFilters; }; const formattedSearchableIndexes = searchableIndexes.map(index => { const { prefix = '' } = index; let label = index.label; if (index.label.includes('ui-inventory')) { label = prefix + intl.formatMessage({ id: index.label }); } return { ...index, label }; }); const shortcuts = [ { name: 'new', handler: handleKeyCommand(() => { if (stripes.hasPerm('ui-inventory.instance.create')) { this.openCreateInstance(); } }), }, ]; return ( <HasCommand commands={shortcuts} isWithinScope={checkScope} scope={document.body} > <div data-test-inventory-instances> <SearchAndSort key={searchAndSortKey} actionMenu={this.getActionMenu} packageInfo={packageInfo} objectName="inventory" maxSortKeys={1} renderNavigation={this.renderNavigation} searchableIndexes={formattedSearchableIndexes} selectedIndex={get(data.query, 'qindex')} searchableIndexesPlaceholder={null} initialResultCount={INITIAL_RESULT_COUNT} resultCountIncrement={RESULT_COUNT_INCREMENT} viewRecordComponent={ViewInstanceWrapper} editRecordComponent={InstanceForm} onChangeIndex={onChangeIndex} newRecordInitialValues={(this.state && this.state.copiedInstance) ? this.state.copiedInstance : { discoverySuppress: false, staffSuppress: false, previouslyHeld: false, source: 'FOLIO', }} visibleColumns={visibleColumns} columnMapping={columnMapping} columnWidths={{ callNumber: '15%', subject: '50%', contributor: '50%', numberOfTitles: '15%', select: '30px', title: '40%', contributorType: '15%', relatorTerm: '15%', }} getCellClass={this.formatCellStyles} customPaneSub={this.renderPaneSub()} resultsFormatter={resultsFormatter} onCreate={this.onCreate} viewRecordPerms="ui-inventory.instance.view" newRecordPerms="ui-inventory.instance.create" disableRecordCreation={disableRecordCreation || false} parentResources={parentResources} parentMutator={parentMutator} detailProps={{ referenceTables: data, onCopy: this.copyInstance, }} basePath={path} path={`${path}/(view|viewsource)/:id/:holdingsrecordid?/:itemid?`} showSingleResult={isSingleResult} renderFilters={browseFilter()} onFilterChange={this.onFilterChangeHandler} pageAmount={100} pagingType={pagingTypes.PREV_NEXT} hasNewButton={false} onResetAll={this.handleResetAll} sortableColumns={['title', 'contributors', 'publishers']} syncQueryWithUrl resultsVirtualize={false} resultsOnMarkPosition={this.onMarkPosition} resultsOnResetMarkedPosition={this.resetMarkedPosition} resultsCachedPosition={itemToView} /> </div> <ErrorModal isOpen={this.state.showErrorModal} label={<FormattedMessage id="ui-inventory.reports.inTransitItem.emptyReport.label" />} content={<FormattedMessage id="ui-inventory.reports.inTransitItem.emptyReport.message" />} onClose={this.onErrorModalClose} /> <SelectedRecordsModal isOpen={isSelectedRecordsModalOpened} records={selectedRows} columnMapping={columnMapping} formatter={resultsFormatter} onSave={this.handleSelectedRecordsModalSave} onCancel={this.handleSelectedRecordsModalCancel} /> <IfInterface name="copycat-imports"> <IfPermission perm="copycat.profiles.collection.get"> <ImportRecordModal isOpen={isImportRecordModalOpened} currentExternalIdentifier={undefined} handleSubmit={this.handleImportRecordModalSubmit} handleCancel={this.handleImportRecordModalCancel} /> </IfPermission> </IfInterface> </HasCommand> ); } } export default withNamespace(flowRight( injectIntl, withLocation, )(stripesConnect(SearchView)));
Encontrar la diferencia