Commit 50151226 authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '66-crops-list-and-details' into 'master'

Resolve "Crops: list and details"

Closes #66

See merge request genesys-pgr/genesys-ui!67
parents 4b9de9d1 452cb413
import Crop from 'model/Crop';
import {CropService} from 'service/CropService';
import {LOAD_CROPS_CACHE_IDLE, RECEIVE_CROPS} from 'constants/crop';
import {LOAD_CROPS_CACHE_IDLE, RECEIVE_CROPS} from 'crop/constants';
import {IReducerAction} from 'model/common.model';
import {log} from 'utilities/debug';
......@@ -10,7 +10,8 @@ const receiveCrops = (crops: Crop[]): IReducerAction => ({
});
export const loadCrops = (forceReload: boolean = false) => (dispatch, getState) => {
const timeSinceLastFetch = getState().crop.lastFetched;
const timeSinceLastFetch = getState().crop.public.lastFetched;
// perform the API call if the data is older than the allowed limit
const isDataStale = Date.now() - timeSinceLastFetch > LOAD_CROPS_CACHE_IDLE;
......
import { combineReducers } from 'redux';
// import admin from './admin';
// import dashboard from './dashboard';
import root from './public';
const rootReducer = combineReducers({
// admin,
// dashboard,
public: root,
});
export default rootReducer;
import update from 'immutability-helper';
import { IReducerAction } from 'model/common.model';
import { RECEIVE_CROPS } from 'constants/crop';
import { RECEIVE_CROPS } from 'crop/constants';
const INITIAL_STATE = {
crops: null,
list: null,
lastFetched: 0,
};
......@@ -13,7 +13,7 @@ export default function crop(state = INITIAL_STATE, action: IReducerAction = {ty
switch (action.type) {
case RECEIVE_CROPS: {
return update(state, {
crops: {$set: action.payload},
list: {$set: action.payload},
lastFetched: {$set: Date.now()},
});
}
......
// Root
import CropBrowsePage from 'crop/ui/BrowsePage';
import CropDisplayPage from 'crop/ui/DisplayPage';
// Admin
// Dashboard
// Root routes
const rootRoutes = [
// Crops
{
path: '/c/',
component: CropBrowsePage,
exact: true,
},
{
path: '/c/:shortName',
component: CropDisplayPage,
exact: true,
},
];
export {rootRoutes as rootCropRoutes};
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
// Models
import Crop from 'model/Crop';
// UI
import PageLayout, { PageContents } from 'ui/layout/PageLayout';
import ContentHeader from 'ui/common/heading/ContentHeader';
import CropCard from 'ui/genesys/crop/CropCard';
import GridLayout from 'ui/layout/GridLayout';
interface IBrowsePageProps extends React.ClassAttributes<any> {
crops: Crop[];
}
class BrowsePage extends React.Component<IBrowsePageProps> {
public render() {
const {crops} = this.props;
return (
<PageLayout>
<ContentHeader title="Crop list" subTitle="Genesys crops directory"/>
<PageContents>
<GridLayout>
{ crops && crops.map((crop) => <CropCard key={ crop.shortName } crop={ crop } compact/>) }
</GridLayout>
</PageContents>
</PageLayout>
);
}
}
const mapStateToProps = (state, ownProps) => ({
crops: state.crop.public.list || undefined,
filterCode: ownProps.match.params.filterCode,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(BrowsePage);
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
// Models
import Crop from 'model/Crop';
// UI
import PageLayout, {PageContents} from 'ui/layout/PageLayout';
import CropCard from 'ui/genesys/crop/CropCard';
import ContentHeader from 'ui/common/heading/ContentHeader';
interface IDisplayPageProps extends React.ClassAttributes<any> {
crops: Crop[];
shortName: string;
}
class DisplayPage extends React.Component<IDisplayPageProps, any> {
public state = {
crop: null,
};
public componentWillMount() {
const { crops, shortName } = this.props;
if (crops && shortName) {
const crop = crops.find((item) => item.shortName === shortName);
this.setState({crop});
}
}
public componentWillReceiveProps(nextProps) {
const { crops, shortName } = nextProps;
if (crops && shortName) {
const crop = crops.find((item) => item.shortName === shortName);
this.setState({crop});
}
}
public render() {
const { crop } = this.state;
return !crop ? null : (
<PageLayout>
<ContentHeader title="Crop details"/>
<PageContents>
<CropCard crop={ crop }/>
</PageContents>
</PageLayout>
);
}
}
const mapStateToProps = (state, ownProps) => ({
shortName: ownProps.match.params.shortName,
crops: state.crop.public.list,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(DisplayPage);
......@@ -12,7 +12,7 @@ import subsets from './subsets';
import accessions from './accessions';
import institutes from './institute';
import applicationConfig from './applicationConfig';
import crop from './crop';
import crop from 'crop/reducers';
import user from 'user/reducers';
import requests from './requests';
......
......@@ -3,7 +3,7 @@ import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {updateHistory} from 'actions/history';
import {loadCrops} from 'actions/crop';
import {loadCrops} from 'crop/actions/public';
import {initMyList} from 'user/actions/root';
import { serverInfoRequest } from 'actions/serverInfo';
......@@ -44,7 +44,7 @@ class App extends React.Component<IAppProps, any> {
public componentWillMount() {
const { crops, loadCrops, serverInfo, serverInfoRequest } = this.props;
if (!crops || crops.length === 0) {
if (! crops || crops.length === 0) {
loadCrops();
}
if (! serverInfo || ! serverInfo.revision) {
......@@ -78,7 +78,7 @@ class App extends React.Component<IAppProps, any> {
}
const mapStateToProps = (state) => ({
crops: state.crop.list,
crops: state.crop.public.list || null,
serverInfo: state.serverInfo.data,
// accessToken: state.login.access_token,
});
......
import * as React from 'react';
import {Link} from 'react-router-dom';
// Models
import Crop from 'model/Crop';
// UI
import Card, {CardHeader, CardContent} from 'ui/common/Card';
import {Properties, PropertiesItem} from 'ui/common/Properties';
import PrettyDate from 'ui/common/time/PrettyDate';
import Grid from '@material-ui/core/Grid';
import withStyles from '@material-ui/core/styles/withStyles';
const style = (theme) => ({
root: {
height: '100%',
flexDirection: 'column' as 'column',
justifyContent: 'space-between',
display: 'flex',
},
});
const CropCard = ({crop, classes, compact = false, edit = false, ...other}: { crop: Crop, classes?: any, compact?: boolean, edit?: boolean }) => {
if (!crop) {
return null;
}
// @ts-ignore
const name = window.initialLanguage && crop.i18n && JSON.parse(crop.i18n).name[window.initialLanguage] || crop.name;
return compact ? (
<Grid item xs={ 12 } sm={ 6 } md={ 4 }>
<Card className={ classes.root }>
<CardHeader title={
<Link to={ `/c/${crop.shortName}${edit ? '/edit' : '' }` }>
{ name }
</Link>
}/>
<CardContent>
{ crop.shortName }
</CardContent>
</Card>
</Grid>
)
:
(
<Grid item xs={ 12 }>
<Card className={ classes.root }>
<CardHeader title={
<Link to={ `/c/${crop.shortName}${edit ? '/edit' : '' }` }>{ name }</Link>
}/>
<CardContent>
<Properties>
<PropertiesItem title="Original name">{ crop.name }</PropertiesItem>
<PropertiesItem title="Localized name">{ name }</PropertiesItem>
<PropertiesItem title="Registered"><PrettyDate value={ new Date(crop.createdDate) }/></PropertiesItem>
<PropertiesItem title="Last modified"><PrettyDate value={ new Date(crop.lastModifiedDate) }/></PropertiesItem>
</Properties>
</CardContent>
</Card>
</Grid>
);
};
export default withStyles(style)(CropCard);
......@@ -101,6 +101,9 @@ class LeftMenu extends React.Component<ILeftMenuProps, any> {
<ListItem button>
<NavLink activeClassName="active" to="/wiews">{ t('menu.Institutes') }</NavLink>
</ListItem>
<ListItem button>
<NavLink activeClassName="active" to="/c">{ t('menu.Crops') }</NavLink>
</ListItem>
</div>
</Drawer>
</div>
......
......@@ -176,6 +176,7 @@ class Header extends React.Component<IHeaderProps | any, any> {
<NavLink activeClassName="active" to="/a">{ t('menu.Accessions') }</NavLink>
<NavLink activeClassName="active" to="/subsets">{ t('menu.Subsets') }</NavLink>
<NavLink activeClassName="active" to="/wiews">{ t('menu.Institutes') }</NavLink>
<NavLink activeClassName="active" to="/c">{ t('menu.Crops') }</NavLink>
</div>
</div>
<div className="float-right">{ this.renderLogin([ROLE_USER, ROLE_ADMINISTRATOR]) }</div>
......
......@@ -6,6 +6,8 @@ import AdminLayout from 'ui/layout/AdminLayout';
// User
import {userAdminRoutes, userRoutes, userDashboardRoutes} from 'user/routes';
// Crops
import {rootCropRoutes} from 'crop/routes';
import DashboardPage from 'ui/pages/dashboard/DashboardPage';
// import AccessionsBrowsePage from 'ui/pages/genesys/BrowsePage';
......@@ -45,6 +47,7 @@ const routes = [
component: App,
routes: [
...userRoutes,
...rootCropRoutes,
{
path: '/',
exact: true,
......
Supports Markdown
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