Skip to content

Commit

Permalink
UISACQCOMP-166: view list of donors
Browse files Browse the repository at this point in the history
  • Loading branch information
alisher-epam committed Oct 31, 2023
1 parent 4a25f88 commit 44033b8
Show file tree
Hide file tree
Showing 8 changed files with 274 additions and 1 deletion.
65 changes: 65 additions & 0 deletions lib/DonorsList/DonorsContainer.js
Original file line number Diff line number Diff line change
@@ -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 <Loading />;
}

return (
<Row>
<Col xs={12}>
<FieldArray
name={name}
id={name}
component={DonorsList}
fetchDonors={handleFetchDonors}
donorsMap={donorsMap}
/>
</Col>
</Row>
);
}

DonorsContainer.propTypes = {
name: PropTypes.string.isRequired,
donorOrganizationIds: PropTypes.arrayOf(PropTypes.string),
};

DonorsContainer.defaultProps = {
donorOrganizationIds: [],
};

export default DonorsContainer;
165 changes: 165 additions & 0 deletions lib/DonorsList/DonorsList.js
Original file line number Diff line number Diff line change
@@ -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: <FormattedMessage id="stripes-acq-components.donors.column.name" />,
code: <FormattedMessage id="stripes-acq-components.donors.column.code" />,
unassignDonor: null,
};

const visibleColumns = [
'name',
'code',
'unassignDonor',
];

const getResultsFormatter = ({
intl,
fields,
}) => ({
name: donor => donor.name,
code: donor => donor.code,
unassignDonor: (donor) => (
<Button
align="end"
aria-label={intl.formatMessage({ id: 'stripes-acq-components.donors.button.unassign' })}
buttonStyle="fieldControl"
data-test-unassign-donor
type="button"
onClick={(e) => {
e.preventDefault();
fields.remove(donor._index);
}}
>
<Icon icon="times-circle" />
</Button>
),
});

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 (
<Pluggable
id={`${name}-plugin`}
aria-haspopup="true"
type="find-organization"
dataKey="organization"
searchLabel={<FormattedMessage id="stripes-acq-components.donors.button.addDonor" />}
searchButtonStyle="default"
disableRecordCreation
stripes={stripes}
selectVendor={addDonors}
isDonorsEnabled
>
<span data-test-add-donor>
<FormattedMessage id="stripes-acq-components.donors.noFindDonorPlugin" />
</span>
</Pluggable>
);
};

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 (
<>
<MultiColumnList
id={id}
columnMapping={columnMapping}
contentData={contentData}
formatter={resultsFormatter}
rowFormatter={anchoredRowFormatter}
rowProps={alignRowProps}
visibleColumns={visibleColumns}
/>
<br />
<AddDonorButton
fetchDonors={fetchDonors}
fields={fields}
stripes={stripes}
name={id}
/>
</>
);
};

DonorsList.propTypes = {
fetchDonors: PropTypes.func.isRequired,
fields: PropTypes.object,
donorsMap: PropTypes.object,
id: PropTypes.string.isRequired,
};

export default DonorsList;
1 change: 1 addition & 0 deletions lib/DonorsList/hooks/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useFetchDonors } from './useFetchDonors';
1 change: 1 addition & 0 deletions lib/DonorsList/hooks/useFetchDonors/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useFetchDonors } from './useFetchDonors';
36 changes: 36 additions & 0 deletions lib/DonorsList/hooks/useFetchDonors/useFetchDonors.js
Original file line number Diff line number Diff line change
@@ -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,
});
};
1 change: 1 addition & 0 deletions lib/DonorsList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as DonorsList } from './DonorsContainer';
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
5 changes: 4 additions & 1 deletion translations/stripes-acq-components/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}

0 comments on commit 44033b8

Please sign in to comment.