Commit 17db8fe6 authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza
Browse files

My List and creating requests

parent f847c346
......@@ -40,6 +40,7 @@
"Home": "Home",
"My Dashboard": "My dashboard",
"My profile": "My profile",
"My List": "My List",
"Partners": "Partners",
"Subsets": "Subsets",
"Institutes": "Institutes",
......
......@@ -5,7 +5,7 @@ import { translate } from 'react-i18next';
// Actions
import { loadAccession } from 'accessions/actions/public';
import {addAccessionToMyList, removeAccessionFromMyList} from 'user/actions/public';
import {addAccessionToMyList, removeAccessionFromMyList} from 'list/actions/public';
// Models
import AccessionDetails from 'model/accession/AccessionDetails';
......@@ -271,7 +271,7 @@ const mapStateToProps = (state, ownProps) => ({
error: state.accessions.public.accessionError,
uuid: ownProps.match.params.uuid,
doi: ownProps.match.params.doi,
accessions: state.user.public.myList.accessions,
accessions: state.list.public.myList.accessions,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
......@@ -9,7 +9,7 @@ import Card, {CardContent, CardActions} from 'ui/common/Card';
import DOI from 'ui/common/DOI';
import SciName from 'ui/genesys/SciName';
// import CropChips from 'ui/genesys/crop/CropChips';
import {addAccessionToMyList, removeAccessionFromMyList} from 'user/actions/public';
import {addAccessionToMyList, removeAccessionFromMyList} from 'list/actions/public';
const styles = (theme) => ({
firstRow: {
......@@ -81,7 +81,7 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove
};
const mapStateToProps = (state, ownProps) => ({
accessions: state.user.public.myList.accessions,
accessions: state.list.public.myList.accessions,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
export const MY_LIST_ACCESSION_INIT = 'MY_LIST_ACCESSION_INIT';
export const MY_LIST_ACCESSION_ADD = 'MY_LIST_ACCESSION_ADD';
export const MY_LIST_ACCESSION_REMOVE = 'MY_LIST_ACCESSION_REMOVE';
export const RECEIVE_USER_PROFILE = 'RECEIVE_USER_PROFILE';
export const CHANGE_PASSWORD_FORM = 'Form/User/CHANGE_PASSWORD_FORM';
import {MY_LIST_ACCESSION_ADD, MY_LIST_ACCESSION_INIT, MY_LIST_ACCESSION_REMOVE, MY_LIST_CLEAR} from 'list/constants';
const initAccessions = (accessions: string[]) => ({
type: MY_LIST_ACCESSION_INIT,
payload: accessions,
});
const addAccession = (accUUID: string) => ({
type: MY_LIST_ACCESSION_ADD,
payload: accUUID,
});
const removeAccession = (accUUID: string) => ({
type: MY_LIST_ACCESSION_REMOVE,
payload: accUUID,
});
export const addAccessionToMyList = (accUUID: string) => (dispatch, getState) => {
const myList = getState().list.public.myList;
if (!myList.accessions.includes(accUUID)) {
dispatch(addAccession(accUUID));
window.localStorage.setItem('myList', JSON.stringify(getState().list.public.myList));
} else {
console.log(`Can't add accession ${accUUID}. Already in myList`);
}
};
export const removeAccessionFromMyList = (accUUID: string) => (dispatch, getState) => {
const myList = getState().list.public.myList;
if (myList.accessions.includes(accUUID)) {
dispatch(removeAccession(accUUID));
window.localStorage.setItem('myList', JSON.stringify(getState().list.public.myList));
} else {
console.log(`Can't remove accession ${accUUID}. Not in myList`);
}
};
export const initMyList = () => (dispatch) => {
if (typeof window !== 'undefined') {
const myList = JSON.parse(window.localStorage.getItem('myList'));
if (myList && myList.accessions) {
return dispatch(initAccessions(myList.accessions));
}
}
};
export const clearMyList = () => (dispatch) => {
window.localStorage.setItem('myList', JSON.stringify({count: 0, accessions: []}));
dispatch({type: MY_LIST_CLEAR});
};
export const MY_LIST_ACCESSION_INIT = 'MY_LIST_ACCESSION_INIT';
export const MY_LIST_ACCESSION_ADD = 'MY_LIST_ACCESSION_ADD';
export const MY_LIST_ACCESSION_REMOVE = 'MY_LIST_ACCESSION_REMOVE';
export const MY_LIST_CLEAR = 'MY_LIST_CLEAR';
import { combineReducers } from 'redux';
import listPublic from './public';
const rootReducer = combineReducers({
public: listPublic,
});
export default rootReducer;
import {MY_LIST_ACCESSION_ADD, MY_LIST_ACCESSION_INIT, MY_LIST_ACCESSION_REMOVE, MY_LIST_CLEAR} from 'list/constants';
import update from 'immutability-helper';
const INITIAL_STATE: {
myList: any;
} = {
myList: {
count: 0,
accessions: [],
},
};
export default function listPublic(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
switch (action.type) {
case MY_LIST_ACCESSION_INIT: {
return update(state, {
myList: {
count: {$set: action.payload.length},
accessions: {$push: action.payload},
},
});
}
case MY_LIST_ACCESSION_ADD: {
return update(state, {
myList: {
count: {$apply: (oldCount) => oldCount + 1},
accessions: {$push: [action.payload]},
},
accessions: {$set: []},
});
}
case MY_LIST_ACCESSION_REMOVE: {
const index = state.myList.accessions.indexOf(action.payload);
return update(state, {
myList: {
count: {$apply: (oldCount) => oldCount - 1},
accessions: {$splice: [[index, 1]]},
},
});
}
case MY_LIST_CLEAR: {
return update(state, {
myList: {
count: {$set: 0},
accessions: {$set: []},
},
});
}
default:
return state;
}
}
import MyListPage from 'list/ui/BrowsePage';
const publicRoutes = [
{
path: '/sel',
component: MyListPage,
exact: true,
},
];
export {publicRoutes as listPublicRoutes};
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import AccessionCard from 'accessions/ui/c/AccessionCard';
import PageLayout from 'ui/layout/PageLayout';
import AccessionService from 'service/genesys/AccessionService';
import ContentHeader from 'ui/common/heading/ContentHeader';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import ActionButton from 'ui/common/buttons/ActionButton';
import navigateTo from 'actions/navigation';
interface IMyListPageProps extends React.ClassAttributes<any> {
myList: any[];
navigateTo: any;
}
class MyListPage extends React.Component<IMyListPageProps> {
public state = {
accessions: null,
};
public componentWillMount() {
const {myList} = this.props;
if (myList && myList.length > 0) {
AccessionService.listAllByUuid(myList).then((accessions) => {
this.setState({accessions});
});
}
}
public componentWillReceiveProps(nextProps) {
const {myList} = nextProps;
const {accessions} = this.state;
if (myList && myList.length > 0 && (!accessions || accessions.length !== myList.length)) {
AccessionService.listAllByUuid(myList).then((accessions) => {
this.setState({accessions});
});
}
}
private sendRequestAction = () => {
const {navigateTo} = this.props;
navigateTo('/requests/create');
}
public render() {
const {accessions} = this.state;
return (
<PageLayout>
<ContentHeader
title="Selected accessions"
subTitle="As you explore the millions of accessions held in Genesys, you can create your own list to keep track of the results of your search. Your selections are stored here so you can return to them at any time."
/>
{ accessions && accessions.length > 0
? (
<div>
<ContentHeaderWithButton
title={ `${accessions.length} accessions` }
buttons={ <ActionButton title="Send request" action={ this.sendRequestAction }/> }
/>
{ accessions.map((accession, index) => <div key={ index } className="pr-20 pb-10 pt-10 pl-20"><AccessionCard index={ index } accession={ accession }/></div>) }
</div>
) : (
<ContentHeaderWithButton title="You have not added any accessions to the list."/>
)
}
</PageLayout>
);
}
}
const mapStateToProps = (state, ownProps) => ({
myList: state.list.public.myList.accessions,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
navigateTo,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(MyListPage);
class RequestInfo {
public email: string;
public purposeType: number;
public preacceptSMTA: boolean;
public notes: string;
}
export const PURPOSE_TYPES = [
{value: '1', label: 'Research for food and agriculture'},
{value: '0', label: 'Other (please elaborate in Notes field)'},
];
export default RequestInfo;
......@@ -14,6 +14,7 @@ import institutes from 'institutes/reducers';
import applicationConfig from './applicationConfig';
import crop from 'crop/reducers';
import user from 'user/reducers';
import list from 'list/reducers';
import requests from 'requests/reducers';
import repository from 'repository/reducers';
......@@ -33,6 +34,7 @@ const rootReducer = combineReducers({
crop,
user,
repository,
list,
subsets,
accessions,
......
import RequestService from 'service/genesys/RequestService';
import {RECEIVE_REQUEST_INFO} from 'requests/constants';
import {clearMyList} from 'list/actions/public';
export const receiveRequestInfo = (requestInfo) => (dispatch) => {
dispatch({
type: RECEIVE_REQUEST_INFO,
payload: requestInfo,
});
};
export const initiateRequest = () => (dispatch, getState) => {
const requestInfo = getState().requests.public.requestInfo;
const accessionList = getState().list.public.myList.accessions;
dispatch(clearMyList());
return RequestService.initiateRequest(accessionList, requestInfo);
};
export const RECEIVE_MATERIAL_REQUESTS = 'requests/RECEIVE_MATERIAL_REQUESTS';
export const APPEND_MATERIAL_REQUESTS = 'requests/APPEND_MATERIAL_REQUESTS';
export const RECEIVE_MATERIAL_REQUEST = 'requests/RECEIVE_MATERIAL_REQUEST';
export const RECEIVE_REQUEST_INFO = 'requests/RECEIVE_REQUEST_INFO';
export const REQUEST_INFO_FORM = 'requests/form/REQUEST_INFO_FORM';
export const ADMIN_RECEIVE_MATERIAL_REQUESTS = 'requests/admin/RECEIVE_MATERIAL_REQUESTS';
export const ADMIN_APPEND_MATERIAL_REQUESTS = 'requests/admin/APPEND_MATERIAL_REQUESTS';
......
import { combineReducers } from 'redux';
import admin from './admin';
import publicRequests from './public';
const rootReducer = combineReducers({
admin,
public: publicRequests,
});
export default rootReducer;
import {RECEIVE_REQUEST_INFO} from 'requests/constants';
import {LOGIN_USER} from 'constants/login';
import update from 'immutability-helper';
const INITIAL_STATE: {
requestInfo: RequestInfo;
} = {
requestInfo: null,
};
export default function requestsPublic(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
switch (action.type) {
case LOGIN_USER: {
return update(state, {
requestInfo: {$set: null},
});
}
case RECEIVE_REQUEST_INFO: {
return update(state, {
requestInfo: {$set: action.payload},
});
}
default:
return state;
}
}
import RequestBrowsePage from 'requests/ui/admin/BrowsePage';
import RequestDisplayPage from 'requests/ui/admin/DisplayPage';
import RequestStepperPage from 'requests/ui/StepperPage';
import steps from 'requests/ui/request-stepper/steps';
const publicRoutes = [
{
path: '/requests',
component: RequestStepperPage,
extraProps: {
title: 'Requesting material from holding institutes',
},
routes: [
...steps,
],
},
];
const adminRoutes = [
{
path: '/requests',
......@@ -20,4 +36,4 @@ const adminRoutes = [
},
];
export {adminRoutes as requestsAdminRoutes};
export {adminRoutes as requestsAdminRoutes, publicRoutes as requestPublicRoutes};
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
// actions
import {navigateTo} from 'actions/navigation';
import {setPageTitle} from 'actions/pageTitle';
import {clearMyList} from 'list/actions/public';
import {initiateRequest} from 'requests/actions/public';
// model
import MaterialRequest from 'model/request/MaterialRequest';
// utility
import confirm from 'utilities/confirmAlert';
import {log} from 'utilities/debug';
// ui
import PageLayout from 'ui/layout/PageLayout';
import { BaseStepperPage } from 'ui/pages/_base/StepperPage';
import steps from 'requests/ui/request-stepper/steps';
import ContentHeader from '../../ui/common/heading/ContentHeader';
interface IRequestStepperPageProps extends React.ClassAttributes<any> {
initiateRequest: () => Promise<MaterialRequest>;
clearMyList: () => void;
}
class StepperPage extends BaseStepperPage<MaterialRequest, IRequestStepperPageProps> {
protected gotoStep = (id) => {
const {navigateTo, steps, path} = this.props;
const stepPath = steps.find((e) => e.id === id).path;
log('Go to step child', stepPath);
navigateTo(`${path}${stepPath}`);
}
protected onPublish = (e) => {
const {initiateRequest, navigateTo} = this.props;
if (initiateRequest) {
log('Initiating request');
initiateRequest().then(() => navigateTo(`/sel`));
}
}
protected onDelete = () => {
const {clearMyList, navigateTo} = this.props;
if (clearMyList) {
log('Clearing myList');
confirm(<span>Clear My List?</span>, {
description: `This will remove all records from My List`,
confirmLabel: 'Clear',
abortLabel: 'Cancel',
}).then(() => {
clearMyList();
setTimeout(() => navigateTo(`/sel`));
}).catch(() => {
// don't delete
});
}
}
public render() {
return(
<PageLayout>
<ContentHeader title={ this.props.pageTitle }/>
{ super.render() }
</PageLayout>
);
}
}
const mapStateToProps = (state, ownProps) => ({
steps,
path: '/requests',
needLoading: false,
showHeader: false,
pageTitle: ownProps.route.extraProps.title, // route-configured
location: ownProps.location,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
// none yet
initiateRequest,
clearMyList,
navigateTo,
setPageTitle,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(StepperPage);
import AccessionsListStep from 'requests/ui/request-stepper/steps/accessionsList';
import RequestInfoStep from 'requests/ui/request-stepper/steps/requestInfo';
import ConfirmStep from 'requests/ui/request-stepper/steps/confirm';
const steps = [
{
id: 1,
name: 'Requested accession list',
path: '/create',
component: AccessionsListStep,
exact: true,
},
{
id: 2,
name: 'Personal info',
path: '/create/request-info',
component: RequestInfoStep,
exact: true,
},
{
id: 3,
name: 'Review and submit',
path: '/create/submit',
component: ConfirmStep,
exact: true,
},
];
export default steps;
import * as React from 'react';
import withStyles from '@material-ui/core/styles/withStyles';
// model
import Accession from 'model/accession/Accession';
// ui
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import AccessionCard from 'accessions/ui/c/AccessionCard';
const style = () => ({
selectedUnavailable: {
backgroundColor: '#f3dddd',
},
});
const countAvailable = (accessions: Accession[]) => {
return accessions.filter((accession) => accession.available && accession.institute.allowMaterialRequests).length;
};
const AccessionsList = ({accessions, classes}) => (
<div>
<ContentHeaderWithButton
title={ `Out of ${accessions.length} accessions listed, ${countAvailable(accessions)} are known to be available for distribution.` }
/>
{ accessions.map((accession, index) => (
<div key={ index } className="pr-10 pt-10 pl-10">
<AccessionCard
index={ index }
accession={ accession }
classes={ accession.available && accession.institute.allowMaterialRequests ? {} : {selected: classes.selectedUnavailable} }
/>
</div>
))
}
</div>
);
export default withStyles(style)(AccessionsList);
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment