Commit 0ba6afb7 authored by Oleksii Savran's avatar Oleksii Savran

Dashboard with cards

WIP: Tables replaced by cards at subsets, datasets, descriptors and descriptorlists
Removed TableInfiniteLoader
DashboardCard fix
removed unused table components
fix of vocabulary DisplayPage
some fixes after rebase
Changed styles of dashboard cards
Changed DashboardCards
parent 51d4f2d7
......@@ -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