Commit f45a1cf9 authored by Viacheslav Pavlov's avatar Viacheslav Pavlov Committed by Matija Obreza
Browse files

Filters on map page

- Fixed button position
- Extracted BIO climate data to BioClimateComponent
- Added climate search by lat-lng to map
- fixed dialog overflow
parent 486562f1
......@@ -410,7 +410,11 @@
},
"map": {
"andMore": "And {{otherMore}} more",
"kml": "KML"
"kml": "KML",
"filterAccessions": "Filter accessions",
"pick": "Show climate",
"stopPick": "Cancel",
"noClimateData": "No climate data available for selected location"
}
}
......
......@@ -6,6 +6,7 @@ export const RECEIVE_ACCESSION_AUDIT_LOG = 'accessions/RECEIVE_ACCESSION_AUDIT_L
export const RECEIVE_ACCESSION = 'accessions/RECEIVE_ACCESSION';
export const RECEIVE_ACCESSION_MAPINFO = 'accessions/RECEIVE_ACCESSION_MAPINFO';
export const ACCESSION_FILTERFORM = 'Form/Accession/ACCESSION_FILTERFORM';
export const ACCESSION_MAP_FILTERFORM = 'Form/Accession/ACCESSION_MAP_FILTERFORM';
export const ACCESSION_FORM = 'Form/Accession/ACCESSION_FORM';
// Dashboard
......
......@@ -67,7 +67,11 @@
},
"map": {
"andMore": "And {{otherMore}} more",
"kml": "KML"
"kml": "KML",
"filterAccessions": "Filter accessions",
"pick": "Show climate",
"stopPick": "Cancel",
"noClimateData": "No climate data available for selected location"
}
}
......
......@@ -39,9 +39,7 @@ import ImageGalleryView from 'repository/ui/c/ImageGalleryView';
import Button from '@material-ui/core/Button/Button';
import AuditedInfo from 'ui/common/AuditedInfo';
import Authorize from 'ui/common/authorized/Authorize';
import TemperatureChart from './c/TemperatureChart';
import PrecipitationChart from './c/PrecipitationChart';
import ClimateTable from './c/ClimateTable';
import BioClimateDisplay from 'accessions/ui/c/BioClimateDisplay';
import PageTitle from 'ui/common/PageTitle';
import ActionButton from 'ui/common/buttons/ActionButton';
......@@ -411,15 +409,7 @@ class BrowsePage extends React.Component<IBrowsePageProps, any> {
}
{ accession.geo && accession.geo.climate &&
<PageSection title={ t('accessions.public.p.display.climateAtCollection') }>
<GridContainer className="container-spacing-vertical">
<TemperatureChart halfWidth climate={ accession.geo.climate }/>
<PrecipitationChart halfWidth climate={ accession.geo.climate }/>
</GridContainer>
<div>
<ClimateTable climate={ accession.geo.climate }/>
</div>
</PageSection>
<BioClimateDisplay climateData={ accession.geo.climate }/>
}
{ accession.geo && accession.geo.climate &&
......
......@@ -4,6 +4,7 @@ import { Link } from 'react-router-dom';
import { translate } from 'react-i18next';
import { withStyles } from '@material-ui/core/styles';
import { bindActionCreators } from 'redux';
import {showSnackbar} from 'actions/snackbar';
import { loadAccessionsMapInfo } from 'accessions/actions/public';
import AccessionFilter from 'model/accession/AccessionFilter';
import Loading from 'ui/common/Loading';
......@@ -17,7 +18,11 @@ import ButtonBar from 'ui/common/buttons/ButtonBar';
import ContentLayout from 'ui/layout/ContentLayout';
import MapConfigSection from './c/MapConfigSection';
import ClimateService from 'service/genesys/ClimateService';
import AccessionService from 'service/genesys/AccessionService';
import MapAccessionsFilters from 'accessions/ui/c/MapAccessionsFilters';
import BioClimateDisplay from 'accessions/ui/c/BioClimateDisplay';
import {Dialog, Drawer} from '@material-ui/core';
import PageTitle from 'ui/common/PageTitle';
let Map;
......@@ -37,6 +42,7 @@ interface IMapPageProps {
filterCode: string;
loadAccessionsMapInfo: any;
mapLayers: MapLayer[];
showSnackbar: (message: string) => void;
}
const styles = (theme) => ({
......@@ -45,6 +51,27 @@ const styles = (theme) => ({
minHeight: '500px',
height: 'calc(100vh - 186px)',
},
crosshair: {
'& > div': {
cursor: 'crosshair !important' as 'crosshair !important',
},
},
climateDialog: {
marginBottom: 0,
overflow: 'auto' as 'auto',
},
filterAccessionsButton: {
position: 'absolute' as 'absolute',
zIndex: 1000,
top: '8px',
left: '48px',
},
pickPositionButton: {
position: 'absolute' as 'absolute',
zIndex: 1000,
top: '48px',
left: '48px',
},
});
class BrowsePage extends React.Component<IMapPageProps, any> {
......@@ -62,6 +89,10 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
searchBox: null,
geoData: [],
otherCount: 0,
sidebarOpened: false,
trackClickPos: false,
dialogOpened: false,
climateData: null,
};
constructor(props, context) {
......@@ -86,8 +117,56 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
}
}
private openSidebar = () => {
return this.setState({sidebarOpened: true});
}
private closeSidebar = () => {
return this.setState({sidebarOpened: false});
}
private setPositionPick = (e) => {
e.preventDefault();
e.stopPropagation();
this.setState({trackClickPos: !this.state.trackClickPos});
return false;
}
private handleTrackPosition = (e) => {
const {showSnackbar, t} = this.props;
const { lat, lng } = e.latlng;
this.setState({climateData: null});
ClimateService.getCurrentClimate(lat, lng)
.then((res) => {
// @ts-ignore
if (!res || res === '') {
return showSnackbar(t('accessions.public.p.map.noClimateData'));
}
return this.setState({climateData: res, dialogOpened: true});
})
.catch((error) => {
if (error.code === 404) {
return showSnackbar(t('accessions.public.p.map.noClimateData'));
}
});
}
private hideDialog = () => {
this.setState({dialogOpened: false});
}
private onMapClick = (e) => {
if (e.originalEvent.target.className.indexOf('leaflet-touch') === -1) {
return;
}
if (this.state.trackClickPos) {
return this.handleTrackPosition(e);
}
if (this.clickTimeout) {
console.log('prevented');
clearTimeout(this.clickTimeout);
......@@ -125,6 +204,7 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
ge: searchBounds[0][0],
le: searchBounds[1][0],
},
climate: filter && filter.geo && filter.geo.climate,
},
};
AccessionService.geoJson(filterWithGeo, popupContentLimit)
......@@ -140,10 +220,10 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
}
public render() {
const { searchBox, geoData, otherCount } = this.state;
const { searchBox, geoData, otherCount, sidebarOpened, trackClickPos, dialogOpened, climateData} = this.state;
const position = [30, 0];
const { mapInfo, mapLayers, currentTab, classes, filterCode, t } = this.props;
const { mapInfo, mapLayers, currentTab, classes, filterCode, t, loadAccessionsMapInfo } = this.props;
if (! mapInfo) {
return <Loading />;
......@@ -160,7 +240,17 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
>
<PageTitle title={ t('accessions.public.p.browse.title') } />
<ContentHeader title={ t('accessions.public.p.browse.title') } subTitle={ t('accessions.public.p.browse.subTitle') } />
<Tabs
<Drawer variant="temporary" open={ sidebarOpened } onClose={ this.closeSidebar }>
<MapAccessionsFilters onSubmit={ loadAccessionsMapInfo } initialValues={ mapInfo && mapInfo.filter }/>
</Drawer>
<Dialog open={ dialogOpened } onClose={ this.hideDialog } maxWidth="md" fullWidth>
<BioClimateDisplay classes={ {section: classes.climateDialog} } climateData={ climateData }/>
</Dialog>
<Tabs
tab={ currentTab }
actions={
<ButtonBar>
......@@ -182,14 +272,17 @@ class BrowsePage extends React.Component<IMapPageProps, any> {
filterObj={ mapInfo && mapInfo.filter || {} }
onSubmit={ this.myApplyFilters }
/>
<div className={ classes.leafletContainer }>
{ mapInfo && typeof window !== 'undefined' &&
<div className={ `${classes.leafletContainer} ${trackClickPos && classes.crosshair}` }>
{ mapInfo && typeof window !== 'undefined' &&
<Map
onClick={ this.onMapClick }
center={ position }
zoom={ 3 } minZoom={ 2 } maxZoom={ 14 }
bounds={ mapInfo.bounds }>
<TileLayer
bounds={ mapInfo.bounds }
>
<Button variant="contained" className={ classes.filterAccessionsButton } onClick={ this.openSidebar }>{ t(`accessions.public.p.map.filterAccessions`) }</Button>
<Button variant="contained" className={ classes.pickPositionButton } onClick={ this.setPositionPick }>{ t(`accessions.public.p.map.${ trackClickPos ? 'stopPick' : 'pick'}`) }</Button>
<TileLayer
zIndex={ 0 }
opacity={ 0.50 }
attribution={ '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' }
......@@ -236,6 +329,7 @@ const mapStateToProps = (state, ownProps) => ({
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadAccessionsMapInfo,
showSnackbar,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(translate()(BrowsePage)));
import * as React from 'react';
import {translate} from 'react-i18next';
// model
import TileClimate from 'model/genesys/TileClimate';
// ui
import {PageSection} from 'ui/layout/PageLayout';
import GridContainer from 'ui/layout/GridContainer';
import ClimateTable from 'accessions/ui/c/ClimateTable';
import PrecipitationChart from 'accessions/ui/c/PrecipitationChart';
import TemperatureChart from 'accessions/ui/c/TemperatureChart';
const BioClimateDisplay = ({climateData, t, ...rest}: {climateData: TileClimate, t: any}) => (
<PageSection title={ t('accessions.public.p.display.climateAtCollection') } { ...rest }>
<GridContainer className="container-spacing-vertical">
<TemperatureChart halfWidth climate={ climateData }/>
<PrecipitationChart halfWidth climate={ climateData }/>
</GridContainer>
<div>
<ClimateTable climate={ climateData }/>
</div>
</PageSection>
);
export default translate()(BioClimateDisplay);
import * as React from 'react';
import { reduxForm } from 'redux-form';
import {translate} from 'react-i18next';
import { ACCESSION_MAP_FILTERFORM } from 'accessions/constants';
import FiltersBlock from 'ui/common/filter/FiltersBlock';
import CollapsibleComponentSearch from 'ui/common/filter/CollapsibleComponentSearch';
import BooleanFilter from 'ui/common/filter/BooleanFilter';
import NumberFilter from 'ui/common/filter/NumberFilter';
import StringFilter from 'ui/common/filter/StringFilter';
import StringArrFilter from 'ui/common/filter/StringArrFilter';
import Accession from 'model/accession/Accession';
import DateFilter from 'ui/common/filter/DateFilter';
import CropFilter from 'crop/ui/c/CropFilter';
const MapAccessionsFilters = ({handleSubmit, initialValues, initialize, t, ...other}) => {
// console.log('AccessionFilters', initialValues);
return (
<FiltersBlock title={ t('accessions.public.f.filtersTitle') } handleSubmit={ handleSubmit } initialize={ initialize } { ...other }>
<CollapsibleComponentSearch title={ t('accessions.public.f.historic') }>
<BooleanFilter name="historic"/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('common:f.textSearch') }>
<StringArrFilter name="holder.code" label={ t('accessions.common.instituteCode') } placeholder="NGA039"/>
<StringFilter name="acceNumb" searchType="contains" label={ t('accessions.common.acceNumb') } placeholder="IRGC"/>
<NumberFilter name="seqNo" label={ t('accessions.public.f.seqNumber') } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('common:f.dateSearch') }>
<DateFilter name="lastModifiedDate" label={ t('common:f.lastModifiedDate') }/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.crop') }>
<CropFilter/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.common.taxonomy') }>
<StringArrFilter name="taxa.genus" label={ t('accessions.common.genus') } placeholder="Hordeum"/>
<StringArrFilter name="taxa.species" label={ t('accessions.common.species') } placeholder="vulgare"/>
<StringFilter name="taxa.subtaxa" searchType="contains" label={ t('accessions.public.f.subtaxon') } placeholder=""/>
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.originOfMaterial') }>
<StringArrFilter name="origin.iso3" label={ t('accessions.common.countryOfOrigin') } placeholder="SVN"/>
<NumberFilter name="geo.latitude" label={ t('geo.common.latitude') } />
<NumberFilter name="geo.longitude" label={ t('geo.common.longitude') } />
<NumberFilter name="geo.elevation" label={ t('accessions.public.f.elevation') } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.climate') }>
<NumberFilter name="geo.climate.bio1" label={ t('accessions.climate.bio1') } />
<NumberFilter name="geo.climate.bio2" label={ t('accessions.climate.bio2') } />
<NumberFilter name="geo.climate.bio3" label={ t('accessions.climate.bio3') } />
<NumberFilter name="geo.climate.bio4" label={ t('accessions.climate.bio4') } />
<NumberFilter name="geo.climate.bio5" label={ t('accessions.climate.bio5') } />
<NumberFilter name="geo.climate.bio6" label={ t('accessions.climate.bio6') } />
<NumberFilter name="geo.climate.bio7" label={ t('accessions.climate.bio7') } />
<NumberFilter name="geo.climate.bio8" label={ t('accessions.climate.bio8') } />
<NumberFilter name="geo.climate.bio9" label={ t('accessions.climate.bio9') } />
<NumberFilter name="geo.climate.bio10" label={ t('accessions.climate.bio10') } />
<NumberFilter name="geo.climate.bio11" label={ t('accessions.climate.bio11') } />
<NumberFilter name="geo.climate.bio12" label={ t('accessions.climate.bio12') } />
<NumberFilter name="geo.climate.bio13" label={ t('accessions.climate.bio13') } />
<NumberFilter name="geo.climate.bio14" label={ t('accessions.climate.bio14') } />
<NumberFilter name="geo.climate.bio15" label={ t('accessions.climate.bio15') } />
<NumberFilter name="geo.climate.bio16" label={ t('accessions.climate.bio16') } />
<NumberFilter name="geo.climate.bio17" label={ t('accessions.climate.bio17') } />
<NumberFilter name="geo.climate.bio18" label={ t('accessions.climate.bio18') } />
<NumberFilter name="geo.climate.bio19" label={ t('accessions.climate.bio19') } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.common.sampStat') }>
<StringArrFilter name="sampStat" options={ Accession.SAMPSTAT } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.common.storageType') }>
<StringArrFilter name="storage" options={ Accession.STORAGE } />
</CollapsibleComponentSearch>
<CollapsibleComponentSearch title={ t('accessions.public.f.status') }>
<BooleanFilter name="historic" label={ t('accessions.public.f.historic') } />
<BooleanFilter name="available" label={ t('accessions.public.f.available') } />
<BooleanFilter name="mlsStatus" label={ t('accessions.public.f.mlsStatus') } />
<BooleanFilter name="sgsv" label={ t('accessions.public.f.sgsv') } />
<BooleanFilter name="images" label={ t('accessions.public.f.images') } />
</CollapsibleComponentSearch>
</FiltersBlock>
);
};
export default translate()(reduxForm({
enableReinitialize: true,
destroyOnUnmount: false,
form: ACCESSION_MAP_FILTERFORM,
})(MapAccessionsFilters));
import * as UrlTemplate from 'url-template';
import { axiosBackend } from 'utilities/requestUtils';
import TileClimate from 'model/genesys/TileClimate';
const URL_GET_CURRENT_CLIMATE = UrlTemplate.parse(`/api/v1/climate/current/{latitude},{longitude}`);
const URL_GET_FUTURE_CLIMATE = UrlTemplate.parse(`/api/v1/climate/future/{latitude},{longitude}`);
const URL_GET_PAST_CLIMATE = UrlTemplate.parse(`/api/v1/climate/past/{latitude},{longitude}`);
/*
* Defined in Swagger as 'climate'
*/
class ClimateService {
/**
* getCurrentClimate at /api/v1/climate/current/{latitude},{longitude}
*
* @param latitude latitude
* @param longitude longitude
*/
public static getCurrentClimate(latitude: number, longitude: number): Promise<TileClimate> {
const apiUrl = URL_GET_CURRENT_CLIMATE.expand({ latitude, longitude });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as TileClimate);
}
/**
* getFutureClimate at /api/v1/climate/future/{latitude},{longitude}
*
* @param latitude latitude
* @param longitude longitude
*/
public static getFutureClimate(latitude: number, longitude: number): Promise<TileClimate> {
const apiUrl = URL_GET_FUTURE_CLIMATE.expand({ latitude, longitude });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as TileClimate);
}
/**
* getPastClimate at /api/v1/climate/past/{latitude},{longitude}
*
* @param latitude latitude
* @param longitude longitude
*/
public static getPastClimate(latitude: number, longitude: number): Promise<TileClimate> {
const apiUrl = URL_GET_PAST_CLIMATE.expand({ latitude, longitude });
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as TileClimate);
}
}
export default ClimateService;
Markdown is supported
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