Commit 9237ecbe authored by Matija Obreza's avatar Matija Obreza
Browse files

Merge branch '174-dashboard-with-cards' into 'master'

Resolve "Dashboard with cards"

Closes #147 and #174

See merge request genesys-pgr/genesys-ui!180
parents 4af2df33 0ba6afb7
......@@ -77,6 +77,7 @@
"list": "List",
"list_plural": "Lists",
"created": "Created",
"createdBy": "Created by {{who}}",
"dateNotProvided": "Date not provided",
"description": "Description",
"either": "Either",
......
......@@ -218,7 +218,9 @@
"submitSelected": "Submit selected",
"unpublishSelected": "Unpublish selected",
"approveSelected": "Approve selected",
"deleteSelected": "Delete selected"
"deleteSelected": "Delete selected",
"selectAll": "Select all",
"unselectAll": "Unselect all"
},
"c": {
"topSection": {
......
......@@ -24,8 +24,16 @@ const styles = (theme) => ({
},
});
const AccessionCard = ({ accession, classes, index, addAccessionToMyList, removeAccessionFromMyList, accessions, t, ...other }:
{ accession: Accession, classes: any, index?: number, addAccessionToMyList: (uuid: string) => void , removeAccessionFromMyList: (uuid: string) => void, accessions: any, t: any} & React.ClassAttributes<any>) => {
const AccessionCard = ({ accession, classes, index, addAccessionToMyList, removeAccessionFromMyList, accessions, editMode, t, ...other }:
{ accession: Accession,
classes: any,
index?: number,
addAccessionToMyList: (uuid: string) => void,
removeAccessionFromMyList: (uuid: string) => void,
accessions: any,
editMode?: boolean,
t: any,
} & React.ClassAttributes<any>) => {
const isChecked = accession && accessions && accessions.includes(accession.uuid);
let touchTimer;
......@@ -44,7 +52,7 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove
return (
<Card className={ isChecked ? classes.selected : accession.historic ? classes.historic : '' } onTouchStart={ onTouchStart } onTouchEnd={ onTouchEnd }>
<Card className={ !editMode && (isChecked ? classes.selected : accession.historic ? classes.historic : '') } onTouchStart={ onTouchStart } onTouchEnd={ onTouchEnd }>
<CardContent>
<div className={ classes.firstRow }>
<b>
......@@ -57,9 +65,9 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove
</b>
{ /* { accession.crop && <CropChips crops={ accession.crop.shortName } /> } */ }
{ accession.countryOfOrigin && ` • ${accession.countryOfOrigin.name}` }
<a className="float-right" onClick={ onCheckedLinkClick }>
{ !editMode && <a className="float-right" onClick={ onCheckedLinkClick }>
{ isChecked ? t('common:action.remove') : `+ ${t('accessions.public.c.accessionCard.addToMyList')}` }
</a>
</a> }
</div>
<div>
{ accession.institute.code }
......
......@@ -17,13 +17,13 @@ const styles = (theme) => ({
},
});
const AccessionRefCard = ({ accessionRef, classes, index }:
{ accessionRef: AccessionRef, classes: any, index?: number} & React.ClassAttributes<any>) => {
const AccessionRefCard = ({ accessionRef, classes, index, editMode }:
{ accessionRef: AccessionRef, classes: any, index?: number, editMode?: boolean } & React.ClassAttributes<any>) => {
const accession: Accession = accessionRef.accession;
if (accession) {
return <AccessionCard accession={ accession } index={ index } />;
return <AccessionCard accession={ accession } index={ index } editMode={ editMode }/>;
}
return (
......
......@@ -8,8 +8,12 @@ import FaoInstitute from 'model/genesys/FaoInstitute';
import Crop from 'model/genesys/Crop';
import {AccessionRef} from 'model/accession/AccessionRef';
import { SortDirection } from 'model/Page';
import { Permissions, IUserPermissions } from 'model/acl/ACL';
class Subset implements IUserPermissions {
public static clazz: string = 'org.genesys2.server.model.impl.Subset';
public _permissions: Permissions;
class Subset {
public accessionCount: number;
public accessionRefs: AccessionRef[];
public active: boolean;
......
......@@ -11,28 +11,89 @@ import { ExternalLink } from 'ui/common/Links';
import { Properties, PropertiesItem } from 'ui/common/Properties';
import CropChips from 'crop/ui/c/CropChips';
import McpdDate from 'ui/common/time/McpdDate';
import DashboardCardDates from 'ui/catalog/dashboard/c/DashboardCardDates';
import DashboardCardStatus from 'ui/catalog/dashboard/c/DashboardCardStatus';
import Permissions from 'ui/common/permission/Permissions';
const SubsetCard = ({subset, complete = false, actions, t, ...other}: { subset: Subset, complete?: boolean, actions?: any, t: any } & React.ClassAttributes<any>) => {
return (
<Card>
<CardHeader title={ <SubsetLink to={ subset } /> } />
<CardContent>
<Markdown className="mb-20" firstParagraph={ !complete } source={ subset.description } />
<Properties>
{ subset.crops && subset.crops.length > 0 && <PropertiesItem title={ t('subsets.public.c.subsetCard.crops') }><CropChips crops={ subset.crops }/></PropertiesItem> }
<PropertiesItem title={ t('subsets.public.c.subsetCard.numberOfAccessions') }><Number value={ subset.accessionCount } /></PropertiesItem>
<PropertiesItem title={ t('subsets.public.c.subsetCard.institute') }><InstituteLink to={ subset.institute }>{ subset.institute.fullName }</InstituteLink></PropertiesItem>
{ complete && <PropertiesItem title={ t('subsets.public.c.subsetCard.creationDate') }><McpdDate value={ subset.date }/></PropertiesItem> }
{ complete && subset.source && <PropertiesItem title={ t('subsets.public.c.subsetCard.source') }><ExternalLink link={ subset.source } /></PropertiesItem> }
</Properties>
const SubsetCard = (
{ subset,
complete = false,
compact = false,
actions,
index,
dataClassName,
t,
...other
}: {
subset: Subset,
complete?: boolean,
compact?: boolean,
actions?: any,
index: number,
dataClassName: string;
t: any
} & React.ClassAttributes<any>) => {
</CardContent>
{ actions &&
<CardActions>
{ actions }
</CardActions>
}
</Card>
return (
compact ? (
<Card>
<CardContent>
<div className="mb-15">
<b>
{ index !== undefined && `${index + 1}. ` }
<SubsetLink to={ subset }/>
</b>
<b></b>
<span>{ subset.wiewsCode }</span>
<b></b>
<Number value={ subset.accessionCount }/>
{ ` ${t('accessions.common.modelName', {count: subset.accessionCount})}` }
<DashboardCardDates item={ subset }/>
</div>
<div>
{ subset.state !== undefined && <DashboardCardStatus item={ subset }/> }
</div>
</CardContent>
{ subset._permissions.manage &&
<CardActions>
<div style={ {width: '100%', display: 'flex', flexDirection: 'row-reverse'} }>
<Permissions clazz={ dataClassName } id={ subset.id }/>
</div>
</CardActions>
}
</Card>
) : (
<Card>
<CardHeader title={ <SubsetLink to={ subset }/> }/>
<CardContent>
<Markdown className="mb-20" firstParagraph={ !complete } source={ subset.description }/>
<Properties>
{ subset.crops && subset.crops.length > 0 &&
<PropertiesItem title={ t('subsets.public.c.subsetCard.crops') }>
<CropChips crops={ subset.crops }/>
</PropertiesItem> }
<PropertiesItem title={ t('subsets.public.c.subsetCard.numberOfAccessions') }>
<Number value={ subset.accessionCount }/>
</PropertiesItem>
<PropertiesItem title={ t('subsets.public.c.subsetCard.institute') }>
<InstituteLink to={ subset.institute }>{ subset.institute.fullName }</InstituteLink>
</PropertiesItem>
{ complete && <PropertiesItem title={ t('subsets.public.c.subsetCard.creationDate') }>
<McpdDate value={ subset.date }/>
</PropertiesItem> }
{ complete && subset.source &&
<PropertiesItem title={ t('subsets.public.c.subsetCard.source') }>
<ExternalLink link={ subset.source }/>
</PropertiesItem> }
</Properties>
</CardContent>
{ actions &&
<CardActions>
{ actions }
</CardActions>
}
</Card>
)
);
};
......
......@@ -16,9 +16,12 @@ import {SortDirection} from 'model/Page';
// UI
import SubsetFilters from './c/SubsetFilters';
import CreateSubsetButton from './c/CreateSubsetButton';
import SubsetDashboardCard from './c/SubsetDashboardRow';
import MyDataTable from 'ui/common/tables/MyDataTable';
import ContentLayout from 'ui/layout/ContentLayout';
import { PageContents } from 'ui/layout/PageLayout';
import Loading from 'ui/common/Loading';
import PagedLoader from 'ui/common/PagedLoader';
import SubsetCard from 'subsets/ui/c/SubsetCard';
import PaginationComponent from 'ui/common/pagination';
interface IDashboardPageProps extends React.ClassAttributes<any> {
paged: FilteredPage<Subset>;
......@@ -27,6 +30,7 @@ interface IDashboardPageProps extends React.ClassAttributes<any> {
t: any;
filterCode: string;
applyFilters: any;
dataClassName: string;
}
const sortOptions = {
......@@ -35,15 +39,6 @@ const sortOptions = {
wiewsCode: 'Publisher',
};
const tableHeaderProps = [
{title: 'subsets.dashboard.c.dashboard.no', width: '10px'},
{title: 'common:label.title', width: '40%'},
{title: 'subsets.dashboard.c.dashboard.publisher', width: null},
{title: 'common:label.created', width: null},
{title: 'common:label.modified', width: null},
{title: 'subsets.dashboard.c.dashboard.status', width: '90px'},
];
class DashboardPage extends React.Component<IDashboardPageProps> {
protected static needs = [
......@@ -95,23 +90,39 @@ class DashboardPage extends React.Component<IDashboardPageProps> {
public render() {
const {paged, login: { authorities: userRoles }, loadMoreSubsets, t} = this.props;
const renderSubset = (s: Subset, index: number) => {
return <SubsetDashboardCard key={ s.uuid } subset={ s } index={ index } isAdmin={ userRoles.findIndex((role) => role === 'ROLE_ADMINISTRATOR') !== -1 }/>;
return (
<SubsetCard
key={ s.uuid }
subset={ s }
index={ index }
isAdmin={ userRoles.findIndex((role) => role === 'ROLE_ADMINISTRATOR') !== -1 }
dataClassName={ this.props.dataClassName }
compact
/>
);
};
return (
<ContentLayout left={
<SubsetFilters initialValues={ paged && paged.filter || {} } onSubmit={ this.myApplyFilters } t={ t }/>
} customHeaderHeight>
<MyDataTable
loadMoreData={ loadMoreSubsets }
paged={ paged }
renderTableRow={ renderSubset }
sortOptions={ sortOptions }
headerProps={ tableHeaderProps }
<PaginationComponent
pageObj={ paged }
onSortChange={ this.onSortChange }
displayName={ t('subsets.common.modelName') }
sortOptions={ sortOptions }
/>
<PageContents className="pt-1rem container-spacing-horizontal">
{ ! paged ? <Loading /> :
<PagedLoader
paged={ paged }
loadMore={ loadMoreSubsets }
roughItemHeight={ 80 }
itemRenderer={ renderSubset } />
}
</PageContents>
<CreateSubsetButton />
</ContentLayout>
);
......@@ -122,6 +133,7 @@ const mapStateToProps = (state, ownProps) => ({
paged: state.subsets.dashboard.paged || undefined,
login: state.login,
filterCode: ownProps.match.params.filterCode,
dataClassName: Subset.clazz,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
import * as React from 'react';
import {withStyles} from '@material-ui/core/styles';
// models
import Subset from 'model/subset/Subset';
import {PublishState} from 'model/common.model';
// ui
import Markdown from 'ui/common/markdown';
import PrettyDate from 'ui/common/time/PrettyDate';
import {TableCell, TableRow} from 'ui/common/tables';
import {SubsetLink} from 'ui/genesys/Links';
const styles = (theme) => ({
/*tslint:disable*/
subsetTableRow: {
'& > * ': {
fontSize: '1rem',
}
},
/*tslint:enable*/
firstRow: {
marginBottom: '1em',
},
});
const StatusCell = ({state}: { state: PublishState }) => {
switch (state) {
case PublishState.PUBLISHED: {
return <TableCell style={ {color: '#88bb41', fontSize: '1rem'} }>{ 'Published' }</TableCell>;
}
case PublishState.DRAFT: {
return <TableCell style={ {color: '#ed9506', fontSize: '1rem'} }>{ 'In progress' }</TableCell>;
}
case PublishState.REVIEWING: {
return <TableCell style={ {color: '#06a7ed', fontSize: '1rem'} }>{ 'In review' }</TableCell>;
}
}
};
const SubsetDashboardRow = ({subset, classes, index, isAdmin, ...other}: { subset: Subset, classes: any, index?: number, isAdmin: boolean } & React.ClassAttributes<any>) => {
return (
<TableRow key={ subset.uuid } className={ classes.subsetTableRow }>
<TableCell>
<b style={ {paddingRight: '8px', paddingLeft: '8px', fontSize: '1rem'} }>{ index + 1 }</b>
</TableCell>
<TableCell>
<b style={ {fontSize: '1rem'} }>
<SubsetLink edit={ (isAdmin && subset.state !== PublishState.PUBLISHED) || subset.state === PublishState.DRAFT } to={ subset }>{ <Markdown basic source={ subset.title }/> || (<i>Untitled</i>) }</SubsetLink>
</b>
</TableCell>
<TableCell style={ {fontSize: '1rem'} }>{ subset.wiewsCode }</TableCell>
<TableCell style={ {fontSize: '1rem'} }>{ subset.createdDate && <PrettyDate value={ subset.createdDate }/> }</TableCell>
<TableCell style={ {fontSize: '1rem'} }>{ subset.lastModifiedDate && <PrettyDate value={ subset.lastModifiedDate }/> }</TableCell>
<StatusCell state={ subset.state }/>
</TableRow>
);
};
export default withStyles(styles)(SubsetDashboardRow);
......@@ -217,7 +217,9 @@
"submitSelected": "Submit selected",
"unpublishSelected": "Unpublish selected",
"approveSelected": "Approve selected",
"deleteSelected": "Delete selected"
"deleteSelected": "Delete selected",
"selectAll": "Select all",
"unselectAll": "Unselect all"
},
"c": {
"topSection": {
......
......@@ -3,65 +3,17 @@ import {translate} from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
import { AccessionRef } from 'model/accession/AccessionRef';
import { Table, TableRow, TableCell } from 'ui/common/tables';
import DOI from 'ui/common/DOI';
import Page from 'model/Page';
import Loading from 'ui/common/Loading';
import TableInfiniteLoader from 'ui/common/TableInfiniteLoader';
import { AccessionLink } from 'ui/genesys/Links';
import PagedLoader from 'ui/common/PagedLoader';
import AccessionRefCard from 'accessions/ui/c/AccessionRefCard';
const styles = (theme) => ({
/*tslint:disable*/
root: {
'& > table': {
display: 'flex' as 'flex',
flexDirection: 'column' as 'column',
'& > thead': {
width: '100%',
height: '2rem',
'& > tr': {
fontWeight: '700',
height: '2rem',
'& > th': {
backgroundColor: '#88ba42',
color: '#fff',
padding: '0 10px',
'&:first-child': {
borderLeft: 0,
},
'&:last-child': {
borderRight: 0,
}
},
width: '100%',
display: 'table' as 'table',
},
},
'& > tbody': {
width: '100%',
display: 'block' as 'block',
overflowX: 'hidden' as 'hidden',
overflowY: 'overlay' as 'overlay',
'& > tr': {
'& > td': {
padding: '0 10px',
'&:first-child': {
borderLeft: 0,
},
'&:last-child': {
borderRight: 0,
},
},
height: '2rem',
width: '100%',
display: 'table' as 'table',
},
'& > tr:not(#pagedLoaderLastItem) > td': {
fontSize: '14px',
fontStyle: 'italic',
},
},
},
maxHeight: '500px',
overflowY: 'auto' as 'auto',
}
/*tslint:enable*/
});
......@@ -80,36 +32,20 @@ class AccessionRefsTable extends React.Component<IAccessionRefsTableProps, any>
}
public render() {
const { paged, t, loadNextPage } = this.props;
const widths = ['15%', '20%', '20%', '20%', '25%'];
const { paged, loadNextPage } = this.props;
const renderRowWithData = (acce: AccessionRef) => (
<TableRow key={ acce.acceNumb }>
<TableCell style={ { width: widths[0] } }>{ acce.instCode }</TableCell>
<TableCell style={ { width: widths[1] } }>{ acce.accession ? <AccessionLink to={ acce.accession } /> : acce.acceNumb }</TableCell>
<TableCell style={ { width: widths[2] } }>{ acce.accession && acce.accession.taxonomy.genus || acce.genus }</TableCell>
<TableCell style={ { width: widths[3] } }>{ acce.accession && acce.accession.taxonomy.species || acce.species }</TableCell>
<TableCell style={ { width: widths[4] } }><DOI noPrefix value={ acce.accession && acce.accession.doi || acce.doi } /></TableCell>
</TableRow>
const renderRowWithData = (accessionRef: AccessionRef, index: number) => (
<AccessionRefCard accessionRef={ accessionRef } index={ index } key={ index } editMode/>
);
return paged ? paged.content && (
<div className={ this.props.classes.root }>
<Table maxHeight="500px" widths={ widths } headers={ [
t('institutes.common.instCode'),
t('accessions.common.acceNumb'),
t('accessions.common.genus'),
t('accessions.common.species'),
t('accessions.common.doi'),
] }>
<TableInfiniteLoader
paged={ paged }
loadingIndicator={ <Loading /> }
loadMore={ loadNextPage }
colSpan={ 5 }
itemRenderer={ renderRowWithData } />
</Table>
<PagedLoader
paged={ paged }
loadMore={ loadNextPage }
roughItemHeight={ 80 }
itemRenderer={ renderRowWithData }
/>
</div>
) : <Loading/>;
}
......
......@@ -15,12 +15,10 @@ import {
selectAll, unselectAll,
} from 'actions/dashboard';
// import PageLayout from 'ui/layout/PageLayout';
import MyDataTable from './c/MyDataTable';
import MyDataCards from './c/MyDataCards';
import DashboardActionsButton from './c/DashboardActionsButton';
import ContentLayout from 'ui/layout/ContentLayout';
import DashboardFilters from './c/Filters';
// import ContentHeader from 'ui/common/heading/ContentHeader';
// TODO fix props
interface IDataPublishedContainerProps extends React.ClassAttributes<any> {
......@@ -122,7 +120,7 @@ class BaseMyDataPage<T> extends React.Component<T & IDataPublishedContainerProps
return (
<ContentLayout customHeaderHeight left={ <Filters initialValues={ paged && paged.filter } onSubmit={ this.onFilter }/> }>
<MyDataTable
<MyDataCards
tab={ tab }
filterComponent={ filterComponent }
basePath={ basePath }
......
import * as React from 'react';
import { translate } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
// ui
import Checkbox from '@material-ui/core/Checkbox';
import CropChips from 'crop/ui/c/CropChips';
import Card, { CardContent, CardActions } from 'ui/common/Card';
import DashboardCardDates from 'ui/catalog/dashboard/c/DashboardCardDates';
import DashboardCardStatus from 'ui/catalog/dashboard/c/DashboardCardStatus';
import Permissions from 'ui/common/permission/Permissions';
import Markdown from 'ui/common/markdown';
import Number from 'ui/common/Number';
const styles = () => ({
checkbox: {
padding: '0',
marginLeft: '8px',
},
flexContainer: {
display: 'flex',
flexDirection: 'row' as 'row',
justifyContent: 'space-between' as 'space-between',
alignItems: 'center' as 'center',
},
infoWrapper: {
/*tslint:disable*/
flexWrap: 'wrap' as 'wrap',
justifyContent: 'flex-start' as ' flex-start',
'& > div': {
height: 'auto' as 'auto',
'& > div': {
top: 0,
},
},
/*tslint:enable*/
},
});
class DashboardCard extends React.Component<any> {
public state = {
inEditList: false,
};
private onCheckboxChange = (e, isChecked) => {
const { item, addToEditAction, removeFromEditAction } = this.props;
isChecked ? addToEditAction(item) : removeFromEditAction(item);
this.setState({ ...this.state, inEditList: isChecked });
}
public componentWillMount() {
const { inEditList } = this.props;
this.setState({ ...this.state, inEditList });
}
public componentWillReceiveProps(nextProps) {
const { inEditList } = nextProps;
this.setState({ ...this.state, inEditList });
}
public render() {
const { item, tab, index, isEditMode, DataLink, dataClassName, t, classes } = this.props;
const { inEditList } = this.state;
return (