diff --git a/lib/DonorsList/DonorsContainer.js b/lib/DonorsList/DonorsContainer.js new file mode 100644 index 00000000..37765fe4 --- /dev/null +++ b/lib/DonorsList/DonorsContainer.js @@ -0,0 +1,65 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import PropTypes from 'prop-types'; +import { FieldArray } from 'react-final-form-arrays'; + +import { + Col, + Loading, + Row, +} from '@folio/stripes/components'; + +import DonorsList from './DonorsList'; +import { useFetchDonors } from './hooks'; + +function DonorsContainer({ name, donorOrganizationIds }) { + const [donors, setDonors] = useState([]); + const { fetchDonorsMutation, isLoading } = useFetchDonors(); + + const handleFetchDonors = useCallback(ids => { + fetchDonorsMutation({ donorOrganizationIds: ids }) + .then((data) => { + setDonors(data); + }); + }, [fetchDonorsMutation]); + + useEffect(() => { + if (donorOrganizationIds.length) { + handleFetchDonors(donorOrganizationIds); + } + }, [donorOrganizationIds, handleFetchDonors]); + + const donorsMap = donors.reduce((acc, contact) => { + acc[contact.id] = contact; + + return acc; + }, {}); + + if (isLoading) { + return ; + } + + return ( + + + + + + ); +} + +DonorsContainer.propTypes = { + name: PropTypes.string.isRequired, + donorOrganizationIds: PropTypes.arrayOf(PropTypes.string), +}; + +DonorsContainer.defaultProps = { + donorOrganizationIds: [], +}; + +export default DonorsContainer; diff --git a/lib/DonorsList/DonorsList.js b/lib/DonorsList/DonorsList.js new file mode 100644 index 00000000..a9855ada --- /dev/null +++ b/lib/DonorsList/DonorsList.js @@ -0,0 +1,165 @@ +import React, { useMemo } from 'react'; +import { + map, + sortBy, +} from 'lodash'; +import PropTypes from 'prop-types'; +import { + FormattedMessage, + useIntl, +} from 'react-intl'; + +import { + Button, + Icon, + MultiColumnList, +} from '@folio/stripes/components'; +import { + Pluggable, + useStripes, +} from '@folio/stripes/core'; + +import { acqRowFormatter } from '../utils'; + +const columnMapping = { + name: , + code: , + unassignDonor: null, +}; + +const visibleColumns = [ + 'name', + 'code', + 'unassignDonor', +]; + +const getResultsFormatter = ({ + intl, + fields, +}) => ({ + name: donor => donor.name, + code: donor => donor.code, + unassignDonor: (donor) => ( + + ), +}); + +const getDonorUrl = (orgId) => { + if (orgId) { + return `/organizations/view/${orgId}`; + } + + return undefined; +}; + +const AddDonorButton = ({ fetchDonors, fields, stripes, name }) => { + const addDonors = (contacts = []) => { + const addedContactIds = new Set(fields.value); + const newContactsIds = map(contacts.filter(({ id }) => !addedContactIds.has(id)), 'id'); + + if (newContactsIds.length) { + fetchDonors([...addedContactIds, ...newContactsIds]); + newContactsIds.forEach(contactId => fields.push(contactId)); + } + }; + + return ( + } + searchButtonStyle="default" + disableRecordCreation + stripes={stripes} + selectVendor={addDonors} + isDonorsEnabled + > + + + + + ); +}; + +AddDonorButton.propTypes = { + fetchDonors: PropTypes.func.isRequired, + fields: PropTypes.object, + stripes: PropTypes.object, + name: PropTypes.string.isRequired, +}; + +const alignRowProps = { alignLastColToEnd: true }; + +const DonorsList = ({ fetchDonors, fields, donorsMap, id }) => { + const intl = useIntl(); + const stripes = useStripes(); + const canViewOrganizations = stripes.hasPerm('ui-organizations.view'); + const donors = (fields.value || []) + .map((contactId, _index) => { + const contact = donorsMap?.[contactId]; + + return { + ...(contact || { isDeleted: true }), + _index, + }; + }); + const contentData = sortBy(donors, [({ lastName }) => lastName?.toLowerCase()]); + + const anchoredRowFormatter = ({ rowProps, ...rest }) => { + return acqRowFormatter({ + ...rest, + rowProps: { + ...rowProps, + to: getDonorUrl(canViewOrganizations && rest.rowData.id), + }, + }); + }; + + const resultsFormatter = useMemo(() => { + return getResultsFormatter({ intl, fields }); + }, [fields, intl]); + + return ( + <> + +
+ + + ); +}; + +DonorsList.propTypes = { + fetchDonors: PropTypes.func.isRequired, + fields: PropTypes.object, + donorsMap: PropTypes.object, + id: PropTypes.string.isRequired, +}; + +export default DonorsList; diff --git a/lib/DonorsList/hooks/index.js b/lib/DonorsList/hooks/index.js new file mode 100644 index 00000000..eed71fe5 --- /dev/null +++ b/lib/DonorsList/hooks/index.js @@ -0,0 +1 @@ +export { useFetchDonors } from './useFetchDonors'; diff --git a/lib/DonorsList/hooks/useFetchDonors/index.js b/lib/DonorsList/hooks/useFetchDonors/index.js new file mode 100644 index 00000000..eed71fe5 --- /dev/null +++ b/lib/DonorsList/hooks/useFetchDonors/index.js @@ -0,0 +1 @@ +export { useFetchDonors } from './useFetchDonors'; diff --git a/lib/DonorsList/hooks/useFetchDonors/useFetchDonors.js b/lib/DonorsList/hooks/useFetchDonors/useFetchDonors.js new file mode 100644 index 00000000..ee08a023 --- /dev/null +++ b/lib/DonorsList/hooks/useFetchDonors/useFetchDonors.js @@ -0,0 +1,36 @@ +import { useMutation } from 'react-query'; + +import { useOkapiKy } from '@folio/stripes/core'; + +import { VENDORS_API } from '../../../constants'; +import { batchRequest } from '../../../utils'; + +const buildQueryByIds = (itemsChunk) => { + const query = itemsChunk + .map(chunkId => `id==${chunkId}`) + .join(' or '); + + return query || ''; +}; + +export const useFetchDonors = () => { + const ky = useOkapiKy(); + + const { isLoading, mutateAsync } = useMutation( + ({ donorOrganizationIds }) => { + return batchRequest( + ({ params: searchParams }) => ky + .get(VENDORS_API, { searchParams }) + .json() + .then(({ organizations }) => organizations), + donorOrganizationIds, + buildQueryByIds, + ); + }, + ); + + return ({ + fetchDonorsMutation: mutateAsync, + isLoading, + }); +}; diff --git a/lib/DonorsList/index.js b/lib/DonorsList/index.js new file mode 100644 index 00000000..66ae435f --- /dev/null +++ b/lib/DonorsList/index.js @@ -0,0 +1 @@ +export { default as DonorsList } from './DonorsContainer'; diff --git a/lib/index.js b/lib/index.js index ea8509f8..7c0140a3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -16,6 +16,7 @@ export * from './Currency'; export * from './CurrencyExchangeRateFields'; export * from './CurrencySymbol'; export * from './DeleteHoldingsModal'; +export * from './DonorsList'; export * from './DragDropMCL'; export * from './DynamicSelection'; export * from './DynamicSelectionFilter'; diff --git a/translations/stripes-acq-components/en.json b/translations/stripes-acq-components/en.json index 5b2d35a1..6d8cf536 100644 --- a/translations/stripes-acq-components/en.json +++ b/translations/stripes-acq-components/en.json @@ -187,5 +187,8 @@ "acquisition_method.other": "Other", "acquisition_method.purchase": "Purchase", "acquisition_method.purchaseAtVendorSystem": "Purchase at vendor system", - "acquisition_method.technical": "Technical" + "acquisition_method.technical": "Technical", + "donors.button.addDonor": "Add donor", + "donors.column.code": "Code", + "donors.column.name": "Name" }