Commit a3b14df1 authored by David Björkheim's avatar David Björkheim
Browse files

DEICH-5554 Deichman.no: Add publication selector for reservation

parent 05d352c0
......@@ -187,8 +187,8 @@ function topContributorDetails({ contributors, mediaType }) {
}
function _groupDetails(contributorGroup) {
const contributorElements = contributorGroup.items.map(
({ link = null, agent = {} }) =>
const contributorElements =
contributorGroup?.items.map(({ link = null, agent = {} }) =>
link ? (
<Link as={link.as} href={link.href} key={link.as}>
<a>{agent.name}</a>
......@@ -196,8 +196,8 @@ function _groupDetails(contributorGroup) {
) : (
<span>{agent.name}</span>
)
);
const title = translations[contributorGroup.role];
) || [];
const title = translations[(contributorGroup?.role)];
return {
title: contributorElements.length > 1 ? pluraliseString(title) : title,
description: (
......
......@@ -7,6 +7,7 @@ import { Block, Button, Select, Hr } from "@digibib/deichman-ui";
import { translations as TRANSLATIONS } from "../../constants/translations";
import PublicationImage from "../PublicationImage";
import { getWaitingPeriodText } from "../../utilities/datetime";
import { sortBy } from "lodash";
const ReservationForm = ({
isLoggedIn,
......@@ -17,11 +18,10 @@ const ReservationForm = ({
onClose,
completed,
isSubmitting,
recordId,
publication = {},
limitedToBranches,
error,
reservationWork,
workData,
onRedirectClose
}) => {
// Return login form if not authorised
......@@ -34,22 +34,11 @@ const ReservationForm = ({
fetchFavouritesOnLogin
fetchKohaBranchesOnLogin
showCancel
redirectModalData={{
type: "reservation",
recordId
}}
onCancel={onClose}
/>
);
}
const getSelectedPublication = () => {
const biblionumber = reservationWork?.currentItem?.biblionumber?.toString();
// Select the correct publication by matching recordId and biblionumber
return workData?.publications.find(obj => obj.recordId === biblionumber);
};
const renderQueueInformation = () => {
const currentItem = reservationWork?.currentItem;
const priority = currentItem?.pri;
......@@ -66,8 +55,7 @@ const ReservationForm = ({
// Return success message if reservation completed
if (completed && !error) {
const { mainTitle, mediaType, sellingPoint, imageObj } =
getSelectedPublication() || {};
const { mainTitle, mediaType, sellingPoint, imageObj } = publication;
return (
<div className="reservation-success-form">
......@@ -123,17 +111,7 @@ const ReservationForm = ({
);
}
// Sort brances by name (text)
function compareName(a, b) {
if (a.text < b.text) {
return -1;
}
if (a.text > b.text) {
return 1;
}
return 0;
}
const sortedBranches = branches.sort(compareName);
const sortedBranches = sortBy(branches, ["text"]);
return (
<form onSubmit={onSubmit}>
......
import React, { Fragment } from "react";
import ReactDOM from "react-dom";
import PropTypes from "prop-types";
import autoBind from "auto-bind";
import { connect } from "react-redux";
......@@ -10,8 +11,6 @@ import {
remoteReservePublication,
resetReservationError
} from "../../store/reservation";
import { toggleReservations } from "../../store/application";
import { publicationByRecordId } from "../../store/resources";
import {
fetchKohaBranches,
kohaBranchesForDropdownSelector
......@@ -21,22 +20,28 @@ import ReservationForm from "../ReservationForm";
import RemoteReservationForm from "../RemoteReservationForm";
import { Modal } from "@digibib/deichman-ui";
import "./styles.css";
import ReservationSelector from "./ReservationSelector";
import { setBodyFreeze } from "../../utilities/accessibility";
let modalRoot;
/**
* Responsible for reserving items from the catalog
* -> Shows loginform if user is not logged in
* -> Then shows remote/reservation-form
*/
class ReservationsManager extends React.Component {
class ReservationContainer extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.state = {
shouldRender: this.props.isVisible,
selectedBranchId: false,
reservationCompleted: false,
borrowerId: ""
borrowerId: "",
recordId: null,
show: false
};
this.el = document.createElement("div");
}
static getDerivedStateFromProps(nextProps, prevState) {
......@@ -68,19 +73,25 @@ class ReservationsManager extends React.Component {
}
componentDidMount() {
setBodyFreeze(true);
this.props.fetchKohaBranches();
setTimeout(() => this.setState({ show: true }), 100);
modalRoot = document.getElementById("modalRoot");
modalRoot.appendChild(this.el);
}
componentDidUpdate(prevProps) {
// Prevent rendering of contents if not visible, but still allow for
// transition when showing/hiding by adding slight unmount-delay
if (prevProps.isVisible && !this.props.isVisible) {
setTimeout(() => this.setState({ shouldRender: false }), 300);
} else if (!prevProps.isVisible && this.props.isVisible) {
this.setState({ shouldRender: true }); // eslint-disable-line react/no-did-update-set-state
componentDidUpdate() {
if (!this.state.recordId && this.props.publications.length === 1) {
const recordId = this.props.publications[0].recordId;
this.setState({ recordId });
}
}
componentWillUnmount() {
setBodyFreeze(false);
modalRoot && modalRoot.removeChild(this.el);
}
handleChangeBranch(e) {
this.setState({
selectedBranchId: e.target.value
......@@ -96,15 +107,14 @@ class ReservationsManager extends React.Component {
handleCloseModal(redirect) {
return e => {
e.preventDefault();
this.props.toggleReservations();
this.setState({
show: false
});
// Delay error reset until modal has faded out
setTimeout(() => {
this.props.onClose();
this.props.resetReservationError();
this.setState({
reservationCompleted: false,
borrowerId: ""
});
}, 300);
if (redirect) {
......@@ -118,15 +128,18 @@ class ReservationsManager extends React.Component {
async handleSubmit(e) {
e.preventDefault();
const { publication: { recordId, branchcode } = {} } = this.props;
const publication = this.props.publications.find(
pub => pub.recordId === this.state.recordId
);
const branchCode = publication.branchcode;
const isRemoteReservation = this.props.userCategory === "IL";
const onlyAtRiksen = branchcode === "frik";
const onlyAtRiksen = branchCode === "frik";
if (isRemoteReservation) {
const { borrowerId } = this.state;
try {
await this.props.remoteReservePublication(
recordId,
null, // recordId,
borrowerId,
onlyAtRiksen
);
......@@ -141,26 +154,27 @@ class ReservationsManager extends React.Component {
} else {
const { selectedBranchId } = this.state;
await this.props.reservePublication(recordId, selectedBranchId);
await this.props.reservePublication(
publication.recordId,
selectedBranchId
);
this.setState({
reservationCompleted: true
});
}
}
setPublication(recordId) {
this.setState({ recordId });
}
render() {
const { borrowerId, reservationCompleted, selectedBranchId } = this.state;
const {
work,
isVisible,
branches,
publication: {
recordId,
formats = [],
items = [],
numHolds = null,
title
} = {},
copies,
publications,
reservationApi: {
error: reservationError,
inProgress: reservationInProgress
......@@ -171,11 +185,11 @@ class ReservationsManager extends React.Component {
// Check if user is a remote user
const isRemoteReservation = this.props.userCategory === "IL";
// Get number of active holds
let currentPublicationNumHolds = 0;
if (numHolds !== null) {
currentPublicationNumHolds = Number(numHolds);
}
const publication = publications.find(
pub => pub.recordId === this.state.recordId
);
const { recordId, formats = [], title } = publication || {};
const { items = [], numHolds = null } = copies[recordId] || {};
// Check if currentPublicationPickupBranches
let limitedToBranches = [];
......@@ -183,79 +197,91 @@ class ReservationsManager extends React.Component {
limitedToBranches = items.map(item => item.branchcode);
}
return (
<FocusTrap
active={this.state.shouldRender}
focusTrapOptions={{
initialFocus: "#reservation-trap"
}}
>
<Modal
name="Reserver"
visible={isVisible}
onClose={this.handleCloseModal(false)}
sizeW="40rem"
showClose
return ReactDOM.createPortal(
<div className="reservation-wrapper">
<FocusTrap
active={this.state.show}
focusTrapOptions={{
initialFocus: "#reservation-trap"
}}
>
<div
className="focus-trap-target"
id="reservation-trap"
tabIndex="-1"
<Modal
name="Reserver"
visible={this.state.show}
onClose={this.handleCloseModal(false)}
sizeW="40rem"
showClose
>
{this.state.shouldRender ? (
<div
className="focus-trap-target reservation-content"
id="reservation-trap"
tabIndex="-1"
>
<Fragment>
{isRemoteReservation ? (
<RemoteReservationForm
borrowerId={borrowerId}
onChangeBorrowerId={this.handleChangeBorrowerId}
onClose={this.handleCloseModal(false)}
isSubmitting={reservationInProgress}
isLoggedIn={isLoggedIn}
completed={reservationCompleted}
error={reservationError}
recordId={recordId}
numHolds={currentPublicationNumHolds}
publicationTitle={title}
onSubmit={this.handleSubmit}
/>
) : (
<ReservationForm
allBranches={branches}
selectedBranchId={selectedBranchId}
onChangeBranch={this.handleChangeBranch}
onClose={this.handleCloseModal(false)}
isSubmitting={reservationInProgress}
isLoggedIn={isLoggedIn}
completed={reservationCompleted}
error={reservationError}
recordId={recordId}
limitedToBranches={limitedToBranches}
onSubmit={this.handleSubmit}
reservationWork={work}
onRedirectClose={this.handleCloseModal(true)}
{!publication && (
<ReservationSelector
onClick={this.setPublication}
copies={copies}
publications={publications}
currentPublication={this.props.currentPublication}
/>
)}
{publication && (
<>
{isRemoteReservation ? (
<RemoteReservationForm
borrowerId={borrowerId}
onChangeBorrowerId={this.handleChangeBorrowerId}
onClose={this.handleCloseModal(false)}
isSubmitting={reservationInProgress}
isLoggedIn={isLoggedIn}
completed={reservationCompleted}
error={reservationError}
recordId={recordId}
numHolds={`${Number(numHolds)}`}
publicationTitle={title}
onSubmit={this.handleSubmit}
/>
) : (
<ReservationForm
allBranches={branches}
selectedBranchId={selectedBranchId}
onChangeBranch={this.handleChangeBranch}
onClose={this.handleCloseModal(false)}
isSubmitting={reservationInProgress}
isLoggedIn={isLoggedIn}
completed={reservationCompleted}
error={reservationError}
publication={publication}
limitedToBranches={limitedToBranches}
onSubmit={this.handleSubmit}
reservationWork={work}
onRedirectClose={this.handleCloseModal(true)}
/>
)}
</>
)}
</Fragment>
) : (
""
)}
</div>
</Modal>
</FocusTrap>
</div>
</Modal>
</FocusTrap>
</div>,
this.el
);
}
}
ReservationsManager.defaultProps = {
ReservationContainer.defaultProps = {
data: {},
homeBranch: "",
userCategory: ""
};
ReservationsManager.propTypes = {
ReservationContainer.propTypes = {
isVisible: PropTypes.bool.isRequired,
isLoggedIn: PropTypes.bool.isRequired,
data: PropTypes.object,
copies: PropTypes.object,
branches: PropTypes.array.isRequired,
homeBranch: PropTypes.string,
userCategory: PropTypes.string,
......@@ -270,15 +296,11 @@ ReservationsManager.propTypes = {
};
function mapStateToProps(state) {
const { reservations } = state.application;
const { reservationApi, work } = state.reservation;
const { homeBranch, category } = state.auth.userData;
const { isLoggedIn } = state.auth;
return {
isVisible: reservations.visible,
data: reservations.data,
publication: publicationByRecordId(state, reservations.data.recordId),
branches: kohaBranchesForDropdownSelector(state),
reservationApi,
homeBranch,
......@@ -295,12 +317,11 @@ function mapDispatchToProps(dispatch) {
fetchKohaBranches: () => dispatch(fetchKohaBranches()),
remoteReservePublication: (recordId, userId, onlyAtRiksen) =>
dispatch(remoteReservePublication(recordId, userId, onlyAtRiksen)),
resetReservationError: () => dispatch(resetReservationError()),
toggleReservations: () => dispatch(toggleReservations())
resetReservationError: () => dispatch(resetReservationError())
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ReservationsManager);
)(ReservationContainer);
import { Block } from "@digibib/deichman-ui";
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import { toggleDetails, toggleFavourites } from "../../store/application";
import PublicationList from "../PublicationList";
export default function ReservationSelector({
publications,
currentPublication = {},
copies = {},
onClick
}) {
const dispatch = useDispatch();
const favourites = useSelector(state => state.favourites.biblioNumbers);
const onFavourite = recordId => dispatch(toggleFavourites({ recordId }));
const onShowDetails = publication => dispatch(toggleDetails({ publication }));
const userCategory = useSelector(state => state.auth.userData.category);
const tjenesteKatalogUrl = useSelector(
state => state.application.urls.tjenestekatalog
);
const decoratedPublications = publications.map(pub => {
const copyInfo = copies[pub.recordId] || {};
return { ...pub, ...copyInfo };
});
return (
<Block top={4}>
<h2>Velg en utgave</h2>
<PublicationList
items={decoratedPublications}
favourites={favourites}
parent={currentPublication.work}
onReserve={onClick}
onFavourite={onFavourite}
onShowDetails={onShowDetails}
userCategory={userCategory}
tjenesteKatalogUrl={tjenesteKatalogUrl}
/>
</Block>
);
}
.reservation-wrapper {
.modal__inner {
@media (--medium) {
max-height: 50vh;
}
}
}
import ReservationsManager from "./ReservationsManager";
export default ReservationsManager;
import React, { useState } from "react";
import { Button, Select } from "@digibib/deichman-ui";
import {
AUDIO_BOOK,
BOOK,
COMIC_BOOK,
FILM,
GAME,
LANGUAGE_COURSE,
MUSIC_RECORDING
} from "../../constants/mediaTypes";
import { translations as TRANSLATIONS } from "../../constants/translations";
import "./styles.css";
const titles = {
default: "Bestill utgivelsen",
[BOOK]: "Bestill boken",
[AUDIO_BOOK]: "Bestill lydboken",
[COMIC_BOOK]: "Bestill tegneserien",
[MUSIC_RECORDING]: "Bestill musikken (CD)",
[FILM]: "Bestill filmen",
[GAME]: "Bestill spillet"
};
//Since we shall not show all fields even though we have data we need to be able to filter them some how. This map provides us with which field that should be shown
const availableFields = {
default: [],
[BOOK]: ["language"],
[AUDIO_BOOK]: ["language", "format"],
[COMIC_BOOK]: ["language"],
[MUSIC_RECORDING]: [],
[FILM]: ["format"],
[GAME]: ["platform"],
[LANGUAGE_COURSE]: ["format"]
};
import { matchMedia } from "../../utilities/media";
import { uniqBy } from "lodash";
import ReservationContainer from "../ReservationsContainer/ReservationContainer";
import { useEffect } from "react";
export default function ReservePublicationWidget({
publications,
currentPublication
}) {
const { languages, formats, platforms } = currentPublication;
const [language, setLanguage] = useState(languages?.[0]);
const [format, setFormat] = useState(formats?.[0]);
const [platform, setPlatform] = useState(platforms?.[0]);
const [copies, setCopies] = useState({});
const [reservationPublications, setReservationPublications] = useState([]);
const mediaType = currentPublication.mediaType;
const title = matchMedia(titles, mediaType);
const showFields = matchMedia(availableFields, mediaType);
useEffect(
() => {
getCopies(currentPublication).then(newCopies => setCopies(newCopies));
},
[currentPublication]
);
const onReserve = choices => {
setReservationPublications(
publications
.filter(pub => pub.mediaType === mediaType)
.filter(pub =>
choices.every(({ selected: value, choice }) =>
pub[choice].includes(value)
)
)
);
};
const reserveChoices = [
{
selected: language,
choice: "languages",
onChange: e => setLanguage(e.target.value),
alternatives: uniqBy(
publications
.filter(
pub =>