Commit 4a0cad19 authored by Maksym Tishchenko's avatar Maksym Tishchenko
Browse files

Merge branch '49-similarity-search' into 'main'

Resolve "Similarity search"

Closes #49

See merge request !46
parents bc7aca34 a85af9e7
......@@ -16,10 +16,16 @@ import Loading from 'ui/common/Loading';
import PageTitle from 'ui/common/PageTitle';
import Markdown from 'ui/common/Markdown';
import McpdDate from "ui/common/McpdDate";
import GenericHit from '@genesys-pgr/client/model/GenericHit';
import Accession from '@genesys-pgr/client/model/accession/Accession';
import SimilarAccessionsTable from "accession/c/SimilarAccessionsTable";
interface AccessionDetailsPageState {
accession: AccessionDetails;
cartItems: string[];
similar: GenericHit<Accession>[];
selectedAccessions: string[];
loadingSimilar: boolean;
}
interface AccessionDetailsPageProps {
......@@ -31,17 +37,18 @@ class AccessionDetailsPage extends React.Component<AccessionDetailsPageProps & W
super(props);
}
public state: {
accession: AccessionDetails,
cartItems: string[],
} = {
public state = {
accession: null,
cartItems: LocalStorageCart.getCartItemsLS(),
similar: null,
selectedAccessions: [],
loadingSimilar: false
};
public componentDidUpdate(prevProps) {
public componentDidUpdate(prevProps, prevState) {
if (prevProps.match.params.uuid !== this.props.match.params.uuid) {
console.log('Loading data');
this.setState({ selectedAccessions: [], similar: null, accession: null })
this.loadData(this.props.match.params.uuid);
}
}
......@@ -91,6 +98,30 @@ class AccessionDetailsPage extends React.Component<AccessionDetailsPageProps & W
this.setState( { cartItems: LocalStorageCart.getCartItemsLS() } )
}
private findSimilar = (e: React.MouseEvent<HTMLButtonElement>) => {
const { appConfig } = this.props;
const { loadingSimilar } = this.state;
const uuid = e.currentTarget.getAttribute('data-uuid')
if (loadingSimilar) {
return;
}
this.setState({ loadingSimilar: true })
AccessionService.getSimilarAccessionsForUUID(uuid, appConfig.filter).then((similar) => {
if (similar && similar.length > 0) {
this.setState({
similar,
loadingSimilar: false
});
console.log(`Genesys returned ${similar.length} matches.`);
} else {
console.log('No matching accessions were found.');
}
}).catch((e) => {
this.setState({ loadingSimilar: false })
console.log(e);
})
}
private renderAddToCart = () => {
const { accession } = this.state;
const { t } = this.props;
......@@ -109,13 +140,26 @@ class AccessionDetailsPage extends React.Component<AccessionDetailsPageProps & W
</button>
}
private onSelectedAccessions = (selectedAccessions) => {
this.setState({ selectedAccessions })
}
private addSelectedToCart = () => {
const { selectedAccessions } = this.state;
LocalStorageCart.addToCart(selectedAccessions);
}
private closeSimilar = () => {
this.setState({ similar: null })
}
public render() {
const { accession, cartItems } = this.state;
const { accession, cartItems, selectedAccessions, similar, loadingSimilar } = this.state;
const { t, appConfig: { apiUrl, shoppingCart, map }, appConfig } = this.props;
let propertyIndex = 0;
if (accession === null) {
if (accession === null || loadingSimilar) {
return (
<>
<PageTitle title={ t('loading') } />
......@@ -126,27 +170,37 @@ class AccessionDetailsPage extends React.Component<AccessionDetailsPageProps & W
const details = accession.details;
const pdci = accession.pdci;
return (
<>
!similar ? <>
<PageTitle title={ t('pagetitle.accession', { accessionNumber: details.accessionNumber }) } />
<div className="d-flex justify-content-between align-items-center">
<h1>{ details.accessionNumber }</h1>
{ shoppingCart.enabled &&
<div>
{ cartItems.includes(this.props.match.params.uuid) ?
<button
type="button"
name={ `button-remove-${accession.details.uuid}` }
data-uuid={ accession.details.uuid }
className="btn btn-primary"
onClick={ this.removeFromCart }
>
{ t('cart.removeFromCart') }
</button>
:
this.renderAddToCart()
}
</div>
}
<div>
<button
type="button"
data-uuid={ accession.details.uuid }
className="btn btn-primary mr-2"
onClick={ this.findSimilar }
>
{ t('accession.details.findAlternatives') }
</button>
{ shoppingCart.enabled &&
<>
{ cartItems.includes(this.props.match.params.uuid) ?
<button
type="button"
name={ `button-remove-${accession.details.uuid}` }
data-uuid={ accession.details.uuid }
className="btn btn-primary"
onClick={ this.removeFromCart }
>
{ t('cart.removeFromCart') }
</button>
:
this.renderAddToCart()
}
</>
}
</div>
</div>
{ details.historic && <div className="bg-warning p-2 mb-2">{ t('accession.historicalNote') }</div> }
......@@ -262,16 +316,16 @@ class AccessionDetailsPage extends React.Component<AccessionDetailsPageProps & W
<Property title={ t('accession.coll.collName') } value={ details.coll.collName } index={ propertyIndex++ }/>
</>
}
{ details.geo &&
{ (details.latitude || details.longitude || details.coordinateDatum || details.coordinateUncertainty || details.elevation) &&
<>
<h3 className="mt-4">{ t('accession.details.geo') }</h3>
<Property title={ t('accession.geo.latitude') } value={ details.geo.latitude } index={ propertyIndex++ }/>
<Property title={ t('accession.geo.longitude') } value={ details.geo.longitude } index={ propertyIndex++ }/>
<Property title={ t('accession.geo.datum') } value={ details.geo.datum } index={ propertyIndex++ }/>
<Property title={ t('accession.geo.uncertainty') } value={ details.geo.uncertainty } index={ propertyIndex++ }/>
<Property title={ t('accession.geo.elevation') } value={ details.geo.elevation } index={ propertyIndex++ }/>
{ map.enabled && details.institute && details.geo.latitude && details.geo.longitude &&
<LocationMap locations={ [{ lat: details.geo.latitude, lng: details.geo.longitude }] }/>
<Property title={ t('accession.geo.latitude') } value={ details.latitude } index={ propertyIndex++ }/>
<Property title={ t('accession.geo.longitude') } value={ details.longitude } index={ propertyIndex++ }/>
<Property title={ t('accession.geo.datum') } value={ details.coordinateDatum } index={ propertyIndex++ }/>
<Property title={ t('accession.geo.uncertainty') } value={ details.coordinateUncertainty } index={ propertyIndex++ }/>
<Property title={ t('accession.geo.elevation') } value={ details.elevation } index={ propertyIndex++ }/>
{ map.enabled && details.institute && details.latitude && details.longitude &&
<LocationMap locations={ [{ lat: details.latitude, lng: details.longitude }] }/>
}
</>
}
......@@ -330,6 +384,24 @@ class AccessionDetailsPage extends React.Component<AccessionDetailsPageProps & W
<Property title={ t('accession.details.created') } value={ new Date(details.createdDate).toLocaleString() } index={ propertyIndex++ }/>
}
</>
:
<>
<PageTitle title={ t('pagetitle.accession', { accessionNumber: details.accessionNumber }) } />
<div className="d-flex justify-content-between align-items-center">
<h1>{ t('accession.details.similarTo', { accNumber: details.accessionNumber }) }</h1>
<div>
{ selectedAccessions?.length !== 0 &&
<button type="button" className="btn btn-primary mr-2" onClick={ this.addSelectedToCart }>
{ t('cart.addToCart') }
</button>
}
<button type="button" className="btn" onClick={ this.closeSimilar }>
{ t('action.close') }
</button>
</div>
</div>
<SimilarAccessionsTable accessions={ similar } onSelectedModified={ this.onSelectedAccessions } noRedirect/>
</>
);
}
}
......
import React from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
// hook
import useAccessionCart from 'accession/hook/useAccessionCart';
// util
import { canAddToCart } from 'utilities';
// ui
import { CountryName } from 'ui/common/CountryName';
import Loading from 'ui/common/Loading';
import GenericHit from '@genesys-pgr/client/model/GenericHit';
import Accession from '@genesys-pgr/client/model/accession/Accession';
interface IAccessionTable {
accessions: GenericHit<Accession>[];
onSelectedModified?: (selected: string[]) => void;
noRedirect?: boolean
}
const AccessionTable = ({ accessions, onSelectedModified, noRedirect }: IAccessionTable) => {
// util
const { t } = useTranslation();
// custom
const { shoppingCartEnabled, addToCart, removeFromCart, cartItems } = useAccessionCart();
// state
const [{ isAllSelected, selectedUUIDs }, setSelected]
= React.useState<{ isAllSelected: boolean, selectedUUIDs: string[] }>({ isAllSelected: false, selectedUUIDs: [] });
const toggleRowSelect = React.useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
const { uuid } = (e.currentTarget as HTMLElement).dataset;
const updated = selectedUUIDs.filter((selectedUuid) => selectedUuid !== uuid);
if (updated.length === selectedUUIDs.length) {
updated.push(uuid);
}
setSelected({ selectedUUIDs: updated, isAllSelected: updated.length === accessions?.length });
}, [accessions?.length, selectedUUIDs]);
const onToggleAll = React.useCallback(() => {
setSelected({
selectedUUIDs: isAllSelected
? []
: accessions?.filter((a) => canAddToCart(a as any) && a.uuid).map((a) => a.uuid),
isAllSelected: !isAllSelected,
});
}, [accessions, isAllSelected]);
React.useEffect(() => {
if (onSelectedModified) {
onSelectedModified(selectedUUIDs);
}
}, [onSelectedModified, selectedUUIDs, isAllSelected]);
if (!accessions) {
return <Loading/>;
}
return (
<>
<table className="table table-striped">
<thead className="thead-dark">
<tr>
{ shoppingCartEnabled && (
<th>
<input
type="checkbox"
name="select-all"
checked={ isAllSelected }
onChange={ onToggleAll }
className="align-middle"
/>
</th>
) }
<th>{ t(['accession.crop', '_.crop']) }</th>
<th>{ t('accession.acceNumb') }</th>
<th>{ t('accession.accessionName') }</th>
<th>{ t('accession.taxonomy.scientificName') }</th>
<th>{ t('accession.countryOfOrigin') }</th>
<th>{ t('accession.sampStat') }</th>
{ shoppingCartEnabled && (<th>{ t('list.availability') }</th>) }
</tr>
</thead>
<tbody>
{ accessions
.map((a) => ({
...a,
canAdd: canAddToCart(a as any),
included: selectedUUIDs.includes(a.uuid),
inCart: cartItems.includes(a.uuid)
}))
.map((a, i) => (
<tr key={ a.id }
className={ a.historic ? 'table-historical' : a.included ? 'table-primary' : '' }>
{ shoppingCartEnabled && (
<td>
{ a.canAdd &&
<input
type="checkbox"
name={ `checkbox-${ a.uuid }-${ i }` }
data-uuid={ a.uuid }
checked={ a.included }
onChange={ toggleRowSelect }
className="align-middle"
/>
}
</td>
) }
<td>{ a.crop && a.crop.name || a.cropName }</td>
<td><Link to={ `/a/${ a.uuid }` }>{ a.accessionNumber }</Link></td>
<td>{ a.accessionName && <b>{ a.accessionName }</b> }</td>
<td><span dangerouslySetInnerHTML={ { __html: a.taxonomy.taxonNameHtml } }/></td>
<td>{ a.countryOfOrigin && (
<CountryName code3={ a.countryOfOrigin.code3 }/> || a.countryOfOrigin.name) }</td>
<td>{ a.sampStat && t(`accession.sampleStatus.${ a.sampStat }`) }</td>
{ shoppingCartEnabled &&
<td>
{ a.canAdd &&
<button
type="button"
name={ `button-add-${ a.uuid }-${ i }` }
data-uuid={ a.uuid }
onClick={ a.inCart ? removeFromCart : addToCart }
className="btn btn-primary"
>
{ a.inCart ? t('cart.removeFromCart') : t('cart.addToCart') }
</button>
}
</td>
}
</tr>
)) }
</tbody>
</table>
</>
);
};
export default AccessionTable;
......@@ -14,7 +14,8 @@
},
"action": {
"submit": "Submit",
"download": "Download"
"download": "Download",
"close": "Close"
},
"pag": {
"first": "First",
......@@ -81,6 +82,8 @@
"datasets": "Datasets",
"subsets": "Subsets",
"details": {
"findAlternatives": "Find alternatives",
"similarTo": "Similar to {{accNumber}}",
"providedTaxonomy": "Taxonomy",
"metadata": "Metadata",
"collSite": "Collecting site",
......
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