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 @@ ...@@ -77,6 +77,7 @@
"list": "List", "list": "List",
"list_plural": "Lists", "list_plural": "Lists",
"created": "Created", "created": "Created",
"createdBy": "Created by {{who}}",
"dateNotProvided": "Date not provided", "dateNotProvided": "Date not provided",
"description": "Description", "description": "Description",
"either": "Either", "either": "Either",
......
...@@ -218,7 +218,9 @@ ...@@ -218,7 +218,9 @@
"submitSelected": "Submit selected", "submitSelected": "Submit selected",
"unpublishSelected": "Unpublish selected", "unpublishSelected": "Unpublish selected",
"approveSelected": "Approve selected", "approveSelected": "Approve selected",
"deleteSelected": "Delete selected" "deleteSelected": "Delete selected",
"selectAll": "Select all",
"unselectAll": "Unselect all"
}, },
"c": { "c": {
"topSection": { "topSection": {
......
...@@ -24,8 +24,16 @@ const styles = (theme) => ({ ...@@ -24,8 +24,16 @@ const styles = (theme) => ({
}, },
}); });
const AccessionCard = ({ accession, classes, index, addAccessionToMyList, removeAccessionFromMyList, accessions, t, ...other }: 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, t: any} & React.ClassAttributes<any>) => { { 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); const isChecked = accession && accessions && accessions.includes(accession.uuid);
let touchTimer; let touchTimer;
...@@ -44,7 +52,7 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove ...@@ -44,7 +52,7 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove
return ( 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> <CardContent>
<div className={ classes.firstRow }> <div className={ classes.firstRow }>
<b> <b>
...@@ -57,9 +65,9 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove ...@@ -57,9 +65,9 @@ const AccessionCard = ({ accession, classes, index, addAccessionToMyList, remove
</b> </b>
{ /* { accession.crop && <CropChips crops={ accession.crop.shortName } /> } */ } { /* { accession.crop && <CropChips crops={ accession.crop.shortName } /> } */ }
{ accession.countryOfOrigin && ` • ${accession.countryOfOrigin.name}` } { 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')}` } { isChecked ? t('common:action.remove') : `+ ${t('accessions.public.c.accessionCard.addToMyList')}` }
</a> </a> }
</div> </div>
<div> <div>
{ accession.institute.code } { accession.institute.code }
......
...@@ -17,13 +17,13 @@ const styles = (theme) => ({ ...@@ -17,13 +17,13 @@ const styles = (theme) => ({
}, },
}); });
const AccessionRefCard = ({ accessionRef, classes, index }: const AccessionRefCard = ({ accessionRef, classes, index, editMode }:
{ accessionRef: AccessionRef, classes: any, index?: number} & React.ClassAttributes<any>) => { { accessionRef: AccessionRef, classes: any, index?: number, editMode?: boolean } & React.ClassAttributes<any>) => {
const accession: Accession = accessionRef.accession; const accession: Accession = accessionRef.accession;
if (accession) { if (accession) {
return <AccessionCard accession={ accession } index={ index } />; return <AccessionCard accession={ accession } index={ index } editMode={ editMode }/>;
} }
return ( return (
......
...@@ -8,8 +8,12 @@ import FaoInstitute from 'model/genesys/FaoInstitute'; ...@@ -8,8 +8,12 @@ import FaoInstitute from 'model/genesys/FaoInstitute';
import Crop from 'model/genesys/Crop'; import Crop from 'model/genesys/Crop';
import {AccessionRef} from 'model/accession/AccessionRef'; import {AccessionRef} from 'model/accession/AccessionRef';
import { SortDirection } from 'model/Page'; 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 accessionCount: number;
public accessionRefs: AccessionRef[]; public accessionRefs: AccessionRef[];
public active: boolean; public active: boolean;
......
...@@ -11,28 +11,89 @@ import { ExternalLink } from 'ui/common/Links'; ...@@ -11,28 +11,89 @@ import { ExternalLink } from 'ui/common/Links';
import { Properties, PropertiesItem } from 'ui/common/Properties'; import { Properties, PropertiesItem } from 'ui/common/Properties';
import CropChips from 'crop/ui/c/CropChips'; import CropChips from 'crop/ui/c/CropChips';
import McpdDate from 'ui/common/time/McpdDate'; 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>) => { const SubsetCard = (
return ( { subset,
<Card> complete = false,
<CardHeader title={ <SubsetLink to={ subset } /> } /> compact = false,
<CardContent> actions,
<Markdown className="mb-20" firstParagraph={ !complete } source={ subset.description } /> index,
<Properties> dataClassName,
{ subset.crops && subset.crops.length > 0 && <PropertiesItem title={ t('subsets.public.c.subsetCard.crops') }><CropChips crops={ subset.crops }/></PropertiesItem> } t,
<PropertiesItem title={ t('subsets.public.c.subsetCard.numberOfAccessions') }><Number value={ subset.accessionCount } /></PropertiesItem> ...other
<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> } subset: Subset,
{ complete && subset.source && <PropertiesItem title={ t('subsets.public.c.subsetCard.source') }><ExternalLink link={ subset.source } /></PropertiesItem> } complete?: boolean,
</Properties> compact?: boolean,
actions?: any,
index: number,
dataClassName: string;
t: any
} & React.ClassAttributes<any>) => {
</CardContent> return (
{ actions && compact ? (
<CardActions> <Card>
{ actions } <CardContent>
</CardActions> <div className="mb-15">
} <b>
</Card> { 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'; ...@@ -16,9 +16,12 @@ import {SortDirection} from 'model/Page';
// UI // UI
import SubsetFilters from './c/SubsetFilters'; import SubsetFilters from './c/SubsetFilters';
import CreateSubsetButton from './c/CreateSubsetButton'; import CreateSubsetButton from './c/CreateSubsetButton';
import SubsetDashboardCard from './c/SubsetDashboardRow';
import MyDataTable from 'ui/common/tables/MyDataTable';
import ContentLayout from 'ui/layout/ContentLayout'; 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> { interface IDashboardPageProps extends React.ClassAttributes<any> {
paged: FilteredPage<Subset>; paged: FilteredPage<Subset>;
...@@ -27,6 +30,7 @@ interface IDashboardPageProps extends React.ClassAttributes<any> { ...@@ -27,6 +30,7 @@ interface IDashboardPageProps extends React.ClassAttributes<any> {
t: any; t: any;
filterCode: string; filterCode: string;
applyFilters: any; applyFilters: any;
dataClassName: string;
} }
const sortOptions = { const sortOptions = {
...@@ -35,15 +39,6 @@ const sortOptions = { ...@@ -35,15 +39,6 @@ const sortOptions = {
wiewsCode: 'Publisher', 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> { class DashboardPage extends React.Component<IDashboardPageProps> {
protected static needs = [ protected static needs = [
...@@ -95,23 +90,39 @@ class DashboardPage extends React.Component<IDashboardPageProps> { ...@@ -95,23 +90,39 @@ class DashboardPage extends React.Component<IDashboardPageProps> {
public render() { public render() {
const {paged, login: { authorities: userRoles }, loadMoreSubsets, t} = this.props; const {paged, login: { authorities: userRoles }, loadMoreSubsets, t} = this.props;
const renderSubset = (s: Subset, index: number) => { 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 ( return (
<ContentLayout left={ <ContentLayout left={
<SubsetFilters initialValues={ paged && paged.filter || {} } onSubmit={ this.myApplyFilters } t={ t }/> <SubsetFilters initialValues={ paged && paged.filter || {} } onSubmit={ this.myApplyFilters } t={ t }/>
} customHeaderHeight> } customHeaderHeight>
<MyDataTable <PaginationComponent
loadMoreData={ loadMoreSubsets } pageObj={ paged }
paged={ paged }
renderTableRow={ renderSubset }
sortOptions={ sortOptions }
headerProps={ tableHeaderProps }
onSortChange={ this.onSortChange } 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 /> <CreateSubsetButton />
</ContentLayout> </ContentLayout>
); );
...@@ -122,6 +133,7 @@ const mapStateToProps = (state, ownProps) => ({ ...@@ -122,6 +133,7 @@ const mapStateToProps = (state, ownProps) => ({
paged: state.subsets.dashboard.paged || undefined, paged: state.subsets.dashboard.paged || undefined,
login: state.login, login: state.login,
filterCode: ownProps.match.params.filterCode, filterCode: ownProps.match.params.filterCode,
dataClassName: Subset.clazz,
}); });
const mapDispatchToProps = (dispatch) => bindActionCreators({ 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 @@ ...@@ -217,7 +217,9 @@
"submitSelected": "Submit selected", "submitSelected": "Submit selected",
"unpublishSelected": "Unpublish selected", "unpublishSelected": "Unpublish selected",
"approveSelected": "Approve selected", "approveSelected": "Approve selected",
"deleteSelected": "Delete selected" "deleteSelected": "Delete selected",
"selectAll": "Select all",
"unselectAll": "Unselect all"
}, },
"c": { "c": {
"topSection": { "topSection": {
......
...@@ -3,65 +3,17 @@ import {translate} from 'react-i18next'; ...@@ -3,65 +3,17 @@ import {translate} from 'react-i18next';
import { withStyles } from '@material-ui/core/styles'; import { withStyles } from '@material-ui/core/styles';
import { AccessionRef } from 'model/accession/AccessionRef'; 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 Page from 'model/Page';
import Loading from 'ui/common/Loading'; import Loading from 'ui/common/Loading';
import TableInfiniteLoader from 'ui/common/TableInfiniteLoader'; import PagedLoader from 'ui/common/PagedLoader';
import { AccessionLink } from 'ui/genesys/Links'; import AccessionRefCard from 'accessions/ui/c/AccessionRefCard';
const styles = (theme) => ({ const styles = (theme) => ({
/*tslint:disable*/ /*tslint:disable*/
root: { root: {
'& > table': { maxHeight: '500px',
display: 'flex' as 'flex', overflowY: 'auto' as 'auto',
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',
},
},
},
} }
/*tslint:enable*/ /*tslint:enable*/
}); });
...@@ -80,36 +32,20 @@ class AccessionRefsTable extends React.Component<IAccessionRefsTableProps, any> ...@@ -80,36 +32,20 @@ class AccessionRefsTable extends React.Component<IAccessionRefsTableProps, any>
} }
public render() { public render() {
const { paged, t, loadNextPage } = this.props; const { paged, loadNextPage } = this.props;
const widths = ['15%', '20%', '20%', '20%', '25%'];
const renderRowWithData = (acce: AccessionRef) => ( const renderRowWithData = (accessionRef: AccessionRef, index: number) => (
<TableRow key={ acce.acceNumb }> <AccessionRefCard accessionRef={ accessionRef } index={ index } key={ index } editMode/>
<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>
); );
return paged ? paged.content && ( return paged ? paged.content && (
<div className={ this.props.classes.root }> <div className={ this.props.classes.root }>
<Table maxHeight="500px" widths={ widths } headers={ [ <PagedLoader
t('institutes.common.instCode'), paged={ paged }
t('accessions.common.acceNumb'), loadMore={ loadNextPage }
t('accessions.common.genus'), roughItemHeight={ 80 }
t('accessions.common.species'), itemRenderer={ renderRowWithData }
t('accessions.common.doi'), />
] }>
<TableInfiniteLoader
paged={ paged }
loadingIndicator={ <Loading /> }
loadMore={ loadNextPage }
colSpan={ 5 }
itemRenderer={ renderRowWithData } />
</Table>
</div> </div>
) : <Loading/>; ) : <Loading/>;
} }
......