Commit 17713dbb authored by Maksym Tishchenko's avatar Maksym Tishchenko
Browse files

Merge branch '406-link-overviews-to-accession-inventory' into 'main'

Resolve "Link Overviews to Accession/Inventory"

Closes #406

See merge request grin-global/grin-global-ui!403
parents 7d069c61 48a51a3c
......@@ -78,6 +78,7 @@
"fromDate": "From date",
"toDate": "To date",
"toDateExclusive": "To date (exclusive)",
"browse": "Browse {{what, lowercase}}",
"true": "True",
"tick": "Tick",
"untick": "Untick",
......
......@@ -5,8 +5,10 @@ import StringFilter from '@gringlobal-ce/client/model/common/StringFilter';
import NumberFilter from '@gringlobal-ce/client/model/common/NumberFilter';
import SiteFilter from '@gringlobal-ce/client/model/gringlobal/SiteFilter';
import TaxonomySpeciesFilter from '@gringlobal-ce/client/model/gringlobal/TaxonomySpeciesFilter';
import InventoryFilter from './InventoryFilter';
import AccessionSourceFilter from './AccessionSourceFilter';
import InventoryFilter from '@gringlobal-ce/client/model/gringlobal/InventoryFilter';
import AccessionSourceFilter from '@gringlobal-ce/client/model/gringlobal/AccessionSourceFilter';
import AccessionActionFilter from "@gringlobal-ce/client/model/gringlobal/AccessionActionFilter";
import AccessionIprFilter from "@gringlobal-ce/client/model/gringlobal/AccessionIprFilter";
/**
* AccessionFilter
......@@ -45,6 +47,8 @@ class AccessionFilter {
public taxonomySpecies?: TaxonomySpeciesFilter;
public inventories?: InventoryFilter;
public sources?: AccessionSourceFilter;
public accessionActions?: AccessionActionFilter;
public accessionIprs?: AccessionIprFilter;
public _text?: string;
}
......
import DateFilter from '@gringlobal-ce/client/model/gringlobal/DateFilter';
import InventoryFilter from '@gringlobal-ce/client/model/gringlobal/InventoryFilter';
import NameGroupFilter from '@gringlobal-ce/client/model/gringlobal/NameGroupFilter';
import StringFilter from "@gringlobal-ce/client/model/common/StringFilter";
/**
* AccessionInvNameFilter
*
* GRIN-Global CE API
*/
class AccessionInvNameFilter {
public NOT: AccessionInvNameFilter;
public NULL: string[];
public NOTNULL: string[];
public AND: AccessionInvNameFilter;
public OR: AccessionInvNameFilter;
public id: number[];
public createdBy: number[];
public createdDate: DateFilter;
public modifiedBy: number[];
public modifiedDate: DateFilter;
public ownedBy: number[];
public ownedDate: DateFilter;
public inventory: InventoryFilter;
public categoryCode: string[];
public plantName: StringFilter;
public plantNameRank: number;
public webVisible: boolean;
public nameGroup: NameGroupFilter;
}
export default AccessionInvNameFilter;
import AccessionFilter from '@gringlobal-ce/client/model/gringlobal/AccessionFilter';
import DateFilter from '@gringlobal-ce/client/model/gringlobal/DateFilter';
import StringFilter from "@gringlobal-ce/client/model/common/StringFilter";
/**
* AccessionIprFilter
*
* GRIN-Global CE API
*/
class AccessionIprFilter {
public NOT: AccessionIprFilter;
public NULL: string[];
public NOTNULL: string[];
public AND: AccessionIprFilter;
public OR: AccessionIprFilter;
public id: number[];
public createdBy: number[];
public createdDate: DateFilter;
public modifiedBy: number[];
public modifiedDate: DateFilter;
public ownedBy: number[];
public ownedDate: DateFilter;
public acceptedDate: DateFilter;
public accession: AccessionFilter;
public expectedDate: DateFilter;
public expiredDate: DateFilter;
public iprCropName: StringFilter;
public iprFullName: StringFilter;
public iprNumber: StringFilter;
public issuedDate: DateFilter;
public typeCode: string[];
}
export default AccessionIprFilter;
......@@ -4,6 +4,8 @@ import DateFilter from '@gringlobal-ce/client/model/gringlobal/DateFilter';
import NumberFilter from '@gringlobal-ce/client/model/common/NumberFilter';
import StringFilter from '@gringlobal-ce/client/model/common/StringFilter';
import SiteFilter from '@gringlobal-ce/client/model/gringlobal/SiteFilter';
import InventoryActionFilter from "@gringlobal-ce/client/model/gringlobal/InventoryActionFilter";
import AccessionInvNameFilter from "@gringlobal-ce/client/model/gringlobal/AccessionInvNameFilter";
/**
* InventoryFilter
......@@ -60,6 +62,9 @@ class InventoryFilter {
public storageLocationPart3?: StringFilter;
public storageLocationPart4?: StringFilter;
public webAvailabilityNote?: StringFilter;
public accessionInvGroup?: number[];
public actions?: InventoryActionFilter;
public names?: AccessionInvNameFilter;
public _text?: string;
public barcode?: string[];
......
......@@ -173,6 +173,20 @@
}
},
"public": {
"prettyF": {
"NOT": "Excluding {{what}}",
"le": "{{what}} ≤",
"ge": "{{what}} ≥",
"lt": "{{what}} <",
"gt": "{{what}} >",
"less": "Less filters...",
"more": "More filters...",
"_text": "Full-text",
"dataExists": "Data exists",
"dataNotProvided": "Data not provided",
"applyingFilters": "Applying filters...",
"includeSystem": "Include system"
},
"c": {
"printLabel": {
"noTemplateSelected": "Please select a template",
......
......@@ -128,6 +128,7 @@
"eslint-webpack-plugin": "^2.0.0",
"fetch-mock": "^9.0.0",
"file-loader": "^6.0.0",
"flattenjs": "^2.0.0",
"git-revision-webpack-plugin": "^3.0.0",
"html-webpack-plugin": "^4.0.0",
"html-webpack-skip-assets-plugin": "^0.0.2",
......
......@@ -3,7 +3,8 @@ export const SAGA_LIST_ACCESSIONS = 'saga/accession/public/LIST';
export const SAGA_LIST_ACCESSIONS_MCPD = 'saga/accession/public/LISTMCPD';
export const SAGA_RECEIVE_ACCESSION_INVENTORIES = 'saga/accession/public/RECEIVE_INVENTORIES';
export const SAGA_UPLOAD_ACCESSION_ATTACHMENTS = 'saga/accession/public/UPLOAD_ACCESSION_ATTACHMENTS';
export const SAGA_RECEIVE_ACCESSION_SUCCESS = 'saga/INVENTORY_GROUP/public/RECEIVE_INVENTORY_SUCCESS';
export const SAGA_RECEIVE_ACCESSION_SUCCESS = 'saga/accession/public/RECEIVE_ACCESSION_SUCCESS';
export const SAGA_RECEIVE_ACCESSION_LIST_SUCCESS = 'saga/accession/public/RECEIVE_ACCESSION_LIST_SUCCESS';
export const RECEIVE_ACCESSION = 'success/accession/public/RECEIVE_ACCESSION';
export const RECEIVE_ACCESSIONS_LIST = 'success/accession/public/LIST';
......
......@@ -3,12 +3,13 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { withTranslation, WithTranslation } from 'react-i18next';
import memoize from 'memoize-one';
import { set, get, isEmpty, merge } from 'lodash';
// Actions
import navigateTo from '@gringlobal-ce/client/action/navigation';
// Layout
import { createStyles, Paper, Card, CardContent } from '@material-ui/core';
import { Button, Card, CardContent, createStyles, IconButton, Paper, WithWidth, withWidth } from '@material-ui/core';
import SlotLayout from '@gringlobal-ce/client/ui/common/layout/SlotLayout';
import { withStyles, WithStyles } from '@material-ui/core/styles';
......@@ -27,23 +28,42 @@ import { Properties, PropertiesItem } from '@gringlobal-ce/client/ui/common/Prop
import Number from '@gringlobal-ce/client/ui/common/Number';
import FiltersButton from 'accession/ui/c/FiltersButton';
import Filters from 'accession/ui/c/Filters';
import { Geography } from "@gringlobal-ce/client/model/gringlobal";
import { YesNoToBoolean } from "@gringlobal-ce/client/utilities";
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
import PrettyFilters from "ui/common/filter/PrettyFilters";
import ListIcon from '@material-ui/icons/List';
import { Breakpoint } from "@material-ui/core/styles/createBreakpoints";
import { listAccessionsAction } from "accession/action/public";
const styles = () => createStyles({
groupByContainer: {
'display': 'flex' as const,
'flexDirection': 'row' as const,
'justifyContent': 'flex-start' as const,
display: 'flex' as const,
flexDirection: 'row' as const,
justifyContent: 'flex-start' as const,
height: '5rem',
gap: '4px',
whiteSpace: 'nowrap',
'@media only screen and (max-height: 500px)': {
flexDirection: 'column' as const,
flexWrap: 'wrap' as const,
},
},
groupBy: {
padding: '0.5em 1em',
margin: '0.5em 0',
padding: '1em 1em 0',
cursor: 'pointer' as const,
width: '160px',
fontSize: '0.8em',
height: '100%',
boxSizing: 'border-box',
'&:nth-child(even)': {
backgroundColor: '#f8f7f5',
},
'&:nth-child(odd)': {
backgroundColor: '#f3f2ee',
},
},
groupBySelected: {
borderBottom: '4px solid #e240a3',
},
blowUp: {
height: '100%',
......@@ -58,36 +78,53 @@ const styles = () => createStyles({
flexShrink: 0,
},
},
actionsWrapper: {
position: 'absolute',
right: 0
},
listIcon: {
marginBottom: '2px'
}
});
const ORIGINONLY = { sources: { origin: true } };
const WITHOUTSYSTEM = { inventories: { includeSystem: false } };
class SummaryPage extends React.Component<PropsFromRedux & WithTranslation & WithStyles> {
const FILTER_TYPES = {
stringArray: 'STRING_ARRAY', // string[]
stringFilter: 'STRING_FILTER', // StringFilter = { eq, contains, sw }
boolean: 'BOOLEAN',
codeValue: 'CODE_VALUE'
}
const mobile = [ 'xs', 'sm' ] as Breakpoint[];
class SummaryPage extends React.Component<PropsFromRedux & WithTranslation & WithStyles & WithWidth> {
private wrapperRef = null;
private static SUMMARIES = [
{ groupBy: 'site.siteShortName', title: [ 'client:model.Accession.site', 'client:model._.site' ] },
{ groupBy: 'accessionNumberPart1', title: 'client:model.Accession.accessionNumberPart1' },
{ groupBy: 'statusCode', title: 'client:model.Accession.statusCode', codeGroup: Accession.CodeGroup.statusCode },
{ groupBy: 'mlsStatus', title: 'client:model.Accession.mlsStatus', codeGroup: Accession.CodeGroup.mlsStatus },
{ groupBy: 'improvementStatusCode', title: 'client:model.Accession.improvementStatusCode', codeGroup: Accession.CodeGroup.improvementStatusCode },
{ groupBy: 'lifeFormCode', title: 'client:model.Accession.lifeFormCode' },
{ groupBy: 'reproductiveUniformityCode', title: 'client:model.Accession.reproductiveUniformityCode' },
{ groupBy: 'taxonomySpecies.taxonomyGenus.genusName', title: 'client:model.TaxonomyGenus.genusName' },
{ groupBy: 'taxonomySpecies.name', title: 'client:model.Accession.taxonomySpecies' },
{ groupBy: 'accessionSources.sourceTypeCode', title: 'accession.public.p.summary.originSourceTypeCode', codeGroup: AccessionSource.CodeGroup.sourceTypeCode, filter: { ...ORIGINONLY } },
{ groupBy: 'accessionSources.acquisitionSource', title: 'client:model.AccessionSource.acquisitionSource', codeGroup: AccessionSource.CodeGroup.acquisitionSource, filter: { ...ORIGINONLY } },
{ groupBy: 'accessionSources.geography.countryCode', title: 'client:model.Geography.countryCode', codeGroup: 'GEOGRAPHY_COUNTRY_CODE', filter: { ...ORIGINONLY } },
{ groupBy: 'accessionActions.actionNameCode', title: 'client:model.AccessionAction.actionNameCode', codeGroup: AccessionAction.CodeGroup.actionNameCode },
{ groupBy: 'accessionIprs.typeCode', title: 'client:model.AccessionIpr.typeCode', codeGroup: AccessionIpr.CodeGroup.typeCode },
{ groupBy: 'inventories.formTypeCode', title: 'client:model.Inventory.formTypeCode', codeGroup: Inventory.CodeGroup.formTypeCode, filter: { ...WITHOUTSYSTEM } },
{ groupBy: 'inventories.inventoryMaintenancePolicy.maintenanceName', title: 'client:model.Inventory.inventoryMaintenancePolicy', filter: { ...WITHOUTSYSTEM } },
{ groupBy: 'inventories.availabilityStatusCode', title: 'client:model.Inventory.availabilityStatusCode', codeGroup: Inventory.CodeGroup.availabilityStatusCode, filter: { ...WITHOUTSYSTEM } },
{ groupBy: 'inventories.isAvailable', title: 'client:model.Inventory.isAvailable', filter: { ...WITHOUTSYSTEM } },
{ groupBy: 'inventories.storageLocationPart1', title: 'client:model.Inventory.storageLocationPart1', filter: { ...WITHOUTSYSTEM } },
{ groupBy: 'inventories.names.categoryCode', title: 'client:model.AccessionInvName.categoryCode', codeGroup: AccessionInvName.CodeGroup.categoryCode },
{ groupBy: 'inventories.names.nameGroup.groupName', title: 'client:model.AccessionInvGroup.groupName' },
{ groupBy: 'inventories.actions.actionNameCode', title: 'client:model.InventoryAction.actionNameCode', codeGroup: InventoryAction.CodeGroup.actionNameCode },
{ groupBy: 'site.siteShortName', title: [ 'client:model.Accession.site', 'client:model._.site' ], filterType: FILTER_TYPES.stringFilter },
{ groupBy: 'accessionNumberPart1', title: 'client:model.Accession.accessionNumberPart1', filterType: FILTER_TYPES.stringFilter },
{ groupBy: 'statusCode', title: 'client:model.Accession.statusCode', codeGroup: Accession.CodeGroup.statusCode, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'mlsStatus', title: 'client:model.Accession.mlsStatus', codeGroup: Accession.CodeGroup.mlsStatus, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'improvementStatusCode', title: 'client:model.Accession.improvementStatusCode', codeGroup: Accession.CodeGroup.improvementStatusCode, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'lifeFormCode', title: 'client:model.Accession.lifeFormCode', codeGroup: Accession.CodeGroup.lifeFormCode, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'reproductiveUniformityCode', title: 'client:model.Accession.reproductiveUniformityCode', codeGroup: Accession.CodeGroup.reproductiveUniformityCode, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'taxonomySpecies.taxonomyGenus.genusName', title: 'client:model.TaxonomyGenus.genusName', filterType: FILTER_TYPES.stringFilter },
{ groupBy: 'taxonomySpecies.name', title: 'client:model.Accession.taxonomySpecies', filterType: FILTER_TYPES.stringFilter },
{ groupBy: 'accessionSources.sourceTypeCode', title: 'accession.public.p.summary.originSourceTypeCode', codeGroup: AccessionSource.CodeGroup.sourceTypeCode, filterPath: 'sources.sourceTypeCode', filter: { ...ORIGINONLY }, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'accessionSources.acquisitionSource', title: 'client:model.AccessionSource.acquisitionSource', codeGroup: AccessionSource.CodeGroup.acquisitionSource, filterPath: 'sources.acquisitionSource', filter: { ...ORIGINONLY }, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'accessionSources.geography.countryCode', title: 'client:model.Geography.countryCode', codeGroup: Geography.CodeGroup.countryCode, filterPath: 'sources.geography.countryCode', filter: { ...ORIGINONLY }, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'accessionActions.actionNameCode', title: 'client:model.AccessionAction.actionNameCode', codeGroup: AccessionAction.CodeGroup.actionNameCode , filterType: FILTER_TYPES.codeValue },
{ groupBy: 'accessionIprs.typeCode', title: 'client:model.AccessionIpr.typeCode', codeGroup: AccessionIpr.CodeGroup.typeCode, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'inventories.formTypeCode', title: 'client:model.Inventory.formTypeCode', codeGroup: Inventory.CodeGroup.formTypeCode, filter: { ...WITHOUTSYSTEM }, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'inventories.inventoryMaintenancePolicy.maintenanceName', title: 'client:model.Inventory.inventoryMaintenancePolicy', filter: { ...WITHOUTSYSTEM }, filterType: FILTER_TYPES.stringFilter },
{ groupBy: 'inventories.availabilityStatusCode', title: 'client:model.Inventory.availabilityStatusCode', codeGroup: Inventory.CodeGroup.availabilityStatusCode, filter: { ...WITHOUTSYSTEM }, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'inventories.isAvailable', title: 'client:model.Inventory.isAvailable', filter: { ...WITHOUTSYSTEM }, filterType: FILTER_TYPES.boolean, filterPath: 'inventories.available' },
{ groupBy: 'inventories.storageLocationPart1', title: 'client:model.Inventory.storageLocationPart1', filter: { ...WITHOUTSYSTEM }, filterType: FILTER_TYPES.stringFilter },
{ groupBy: 'inventories.names.categoryCode', title: 'client:model.AccessionInvName.categoryCode', codeGroup: AccessionInvName.CodeGroup.categoryCode, filterType: FILTER_TYPES.codeValue },
{ groupBy: 'inventories.names.nameGroup.groupName', title: 'client:model.AccessionInvGroup.groupName', filterType: FILTER_TYPES.stringFilter },
{ groupBy: 'inventories.actions.actionNameCode', title: 'client:model.InventoryAction.actionNameCode', codeGroup: InventoryAction.CodeGroup.actionNameCode, filterType: FILTER_TYPES.codeValue },
];
public state = {
......@@ -98,6 +135,7 @@ class SummaryPage extends React.Component<PropsFromRedux & WithTranslation & Wit
public constructor(props) {
super(props);
this.wrapperRef = React.createRef();
}
public static getDerivedStateFromProps(props, state) {
......@@ -133,6 +171,15 @@ class SummaryPage extends React.Component<PropsFromRedux & WithTranslation & Wit
// }
}
private onCopy = () => {
const rawText = this.wrapperRef.current.innerText;
const formattedText = rawText.split('\n').reduce((acc, el, i) => {
return acc + el + (i % 2 ? '\n' : '\t'); // --> first cell \t second cell \n
}, '');
const formattedTextWithLink = formattedText + `\n${window.location}`;
(navigator as any).clipboard.writeText(formattedTextWithLink)
};
private decodeCodeValue = (groupName: string, value: string): string => {
if (value === 'NULL') { // API uses this key
return value;
......@@ -152,7 +199,7 @@ class SummaryPage extends React.Component<PropsFromRedux & WithTranslation & Wit
const summary = SummaryPage.SUMMARIES.filter((summary) => summary.groupBy === group)[0];
// Get overview statistics
const customizedFilter = { ...accessionFilter, ...summary.filter };
const customizedFilter = merge({}, summary.filter, accessionFilter);
console.log(`Getting overview for ${group}`, summary, customizedFilter);
AccessionService.getOverview(group, customizedFilter).then((overview) => {
......@@ -201,13 +248,61 @@ class SummaryPage extends React.Component<PropsFromRedux & WithTranslation & Wit
console.log(`Selected group ${group}`);
this.loadNumbers(group);
}
private filterByNumber = (name, group) => {
const { accessionFilter } = this.state;
const summary = SummaryPage.SUMMARIES.filter((summary) => summary.groupBy === group)[0]
const filter = {}
const filterPath = summary.filterPath ?? group
const arrayOfKeys = filterPath.split('.')
const lastPathKey = arrayOfKeys.pop();
const withoutLastKey = arrayOfKeys.join('.');
if (name === "NULL") {
set(filter, `${withoutLastKey ? `${withoutLastKey}.` : ''}NULL`, [...(get(accessionFilter, `${filterPath}.NULL`) ?? []), lastPathKey])
this.setState({ accessionFilter: merge({}, accessionFilter, filter) })
this.loadNumbers(group);
return
}
switch (summary.filterType) {
case FILTER_TYPES.stringFilter: {
set(filter, `${ filterPath }.eq`, [name])
break;
}
case FILTER_TYPES.stringArray: {
set(filter, filterPath, [name])
break;
}
case FILTER_TYPES.boolean: {
set(filter, filterPath, YesNoToBoolean(name))
break;
}
case FILTER_TYPES.codeValue: {
set(filter, filterPath, name)
break;
}
}
this.setState({ accessionFilter: merge({}, accessionFilter, filter) })
this.loadNumbers(group);
}
private getSortedValues = memoize((values: any[]) => [ ...values ].sort((a, b) => b.value - a.value));
public render() {
const { t, classes } = this.props;
private browseList = () => {
const { navigateTo, listAccessionsAction } = this.props;
const { accessionFilter } = this.state;
console.log('Rendering summary. This is important!');
listAccessionsAction(accessionFilter)
navigateTo('/a')
}
public render() {
const { t, width, classes } = this.props;
const { accessionFilter, group } = this.state;
const isMobile = mobile.indexOf(width) !== -1;
console.log('Rendering summary. This is important!', accessionFilter);
return (
<>
......@@ -223,27 +318,44 @@ class SummaryPage extends React.Component<PropsFromRedux & WithTranslation & Wit
<SlotLayout
fixedContent={
<>
<Paper square style={ { flexGrow: 1, overflow: 'scroll' } }>
<Paper square style={ { flexGrow: 1, overflowX: 'auto', overflowY: 'hidden' } }>
<div className={ classes.groupByContainer }>
{ SummaryPage.SUMMARIES.map((summary) => (
<div key={ summary.groupBy } className={ classes.groupBy } onClick={ this.selectGroup(summary.groupBy, t(summary.title)) }>{ t(summary.title) }</div>
<div key={ summary.groupBy } className={ `${ classes.groupBy } ${ summary.groupBy === group ? classes.groupBySelected : '' }` } onClick={ this.selectGroup(summary.groupBy, t(summary.title)) }>{ t(summary.title) }</div>
)) }
</div>
</Paper>
{ ! isEmpty(accessionFilter) &&
<PrettyFilters
prefix="Accession"
filterObj={ accessionFilter }
onSubmit={ (filter) => this.setState({ accessionFilter: filter }) }
labels={ SummaryPage.SUMMARIES.map((summary) => [ summary.filterPath ?? summary.groupBy, { title: summary.title, codeGroup: summary.codeGroup } ]) }
/>
}
</>
}
>
<div className={ classes.mainWrapper }>
<div className={ classes.diagramWrapper }>
<TreeMap className={ classes.blowUp } data={ this.state.data } />
<div className={ classes.actionsWrapper }>
<Button type="button" onClick={ this.browseList }>
{ ! isMobile && <span className="mr-5">{ `${ t('common:label.browse', { what: t('client:model.name.Accession_plural') }) }` }</span> }
<ListIcon className={ classes.listIcon }/>
</Button>
<IconButton type="button" onClick={ this.onCopy } title={ t('common:action.copyToClipboard') }>
<FileCopyOutlinedIcon/>
</IconButton>
</div>
<TreeMap onClick={ (name) => this.filterByNumber(name, group) } className={ classes.blowUp } data={ this.state.data } />
</div>
{ this.state.data && this.state.data.children && (
<Card>
<CardContent>
<CardContent ref={ this.wrapperRef }>
<Properties>
{ this.getSortedValues(this.state.data.children).map((item) =>
<PropertiesItem key={ `${item.name}-${item.value}` } title={ item.missingData ? <i>{ item.label || item.name }</i> : item.label || item.name } numeric>
<Number value={ item.value } />
<a onClick={ () => this.filterByNumber(item.name, group) }><Number value={ item.value } /></a>
</PropertiesItem>
) }
</Properties>
......@@ -265,6 +377,7 @@ const mapStateToProps = (state) => ({
const mapDispatch = {
navigateTo,
listAccessionsAction,
}
type PropsFromRedux = ReturnType<typeof mapStateToProps> & typeof mapDispatch;
......@@ -273,4 +386,5 @@ export default compose(
connect(mapStateToProps, mapDispatch),
withStyles(styles),
withTranslation(),
withWidth(),
)(SummaryPage);
......@@ -7,12 +7,13 @@ interface IProps {
className: string;
data: any;
colors?: any;
onClick?: (name: string) => void;
}
/**
* TreeMap based on https://observablehq.com/@d3/zoomable-treemap
*/
export const TreeMap = ({ nullLabel = 'NULL', className, data, colors }: IProps) => {
export const TreeMap = ({ nullLabel = 'NULL', className, data, colors, onClick }: IProps) => {
const chartRef = React.useRef(null);
// useEffect
......@@ -87,7 +88,9 @@ export const TreeMap = ({ nullLabel = 'NULL', className, data, colors }: IProps)
node.append('rect')
.attr('id', (d) => (d.leafUid = DOM.uid('leaf')).id)
.attr('fill', (d) => d === root ? '#fff' : d.children ? '#ccc' : color(d.data.name))
.attr('stroke', '#fff');
.attr('stroke', '#fff')
.attr('cursor', onClick ? (d) => d !== root ? 'pointer' : null : null)
.on('click', onClick ? (d) => d !== root ? onClick(d.data.name) : null : null);
node.append('clipPath')
.attr('id', (d) => (d.clipUid = DOM.uid('clip')).id)
......
......@@ -3,12 +3,13 @@ import { connect } from 'react-redux';
import { compose } from 'redux';
import { withTranslation, WithTranslation } from 'react-i18next';
import memoize from 'memoize-one';
import { set, isEmpty, merge, get } from 'lodash';
// Actions
import navigateTo from '@gringlobal-ce/client/action/navigation';
// Layout
import { Card, CardContent, createStyles, Paper } from '@material-ui/core';
import { Button, Card, CardContent, createStyles, IconButton, Paper, WithWidth, withWidth } from '@material-ui/core';
import SlotLayout from '@gringlobal-ce/client/ui/common/layout/SlotLayout';
import { withStyles, WithStyles } from '@material-ui/core/styles';
......@@ -27,23 +28,41 @@ import { Properties, PropertiesItem } from '@gringlobal-ce/client/ui/common/Prop
import Number from '@gringlobal-ce/client/ui/common/Number';
import FiltersButton from 'accession/ui/c/FiltersButton';
import Filters from 'inventory/ui/c/Filters';
import { YesNoToBoolean } from "@gringlobal-ce/client/utilities";
import FileCopyOutlinedIcon from '@material-ui/icons/FileCopyOutlined';
import PrettyFilters from "ui/common/filter/PrettyFilters";
import ListIcon from '@material-ui/icons/List';
import { Breakpoint } from "@material-ui/core/styles/createBreakpoints";
import { listInventoryAction } from "inventory/action/public";
const styles = () => createStyles({
groupByContainer: {
'display': 'flex' as const,
'flexDirection': 'row' as const,
'justifyContent': 'flex-start' as const,
display: 'flex' as const,
flexDirection: 'row' as const,
justifyContent: 'flex-start' as const,
height: '5rem',
gap: '4px',
whiteSpace: 'nowrap',
'@media only screen and (max-height: 500px)': {
flexDirection: 'column' as const,
flexWrap: 'wrap' as const,
},
},
groupBy: {
padding: '0.5em 1em',
margin: '0.5em 0',
padding: '1em 1em 0',
cursor: 'pointer' as const,
width: '160px',
fontSize: '0.8em',
height: '100%',
boxSizing: 'border-box',
'&:nth-child(even)': {
backgroundColor: '#f8f7f5',
},
'&:nth-child(odd)': {
backgroundColor: '#f3f2ee',
},
},
groupBySelected: {
borderBottom: '4px solid #e240a3',
},
blowUp: {
height: '100%',
......@@ -58,31 +77,48 @@ const styles = () => createStyles({
flexShrink: 0,
},