Commit c469a6b5 authored by Maxym Borodenko's avatar Maxym Borodenko
Browse files

Merge branch 'server-432-descriptor-lists-overview' into 'master'

Added ui part for descriptor lists overview

See merge request genesys-pgr/genesys-ui!269
parents 2f977b52 f8b25ac3
......@@ -1178,7 +1178,13 @@
"modelName": "Descriptor list",
"modelName_plural": "Descriptor lists",
"menu": "Descriptor lists",
"subtitle": "Compilations of crop descriptors"
"subtitle": "Compilations of crop descriptors",
"overview": {
"owner": {
"name": "Maintainer"
},
"crop": "Crop"
}
},
"public": {
"common": {
......@@ -1272,6 +1278,10 @@
"listDescriptorlists": "List descriptor lists",
"createDescriptorlist": "Create descriptor list"
}
},
"tab": {
"data": "Descriptor lists",
"overview": "Overview"
}
},
"descriptors": {
......
......@@ -6,13 +6,18 @@ import { showSnackbar } from 'actions/snackbar';
import {createApiCaller, createPureApiCaller} from 'actions/ApiCall';
// Constants
import { RECEIVE_DESCRIPTORLIST, APPEND_DESCRIPTORLISTS } from 'descriptors/constants';
import {
RECEIVE_DESCRIPTORLIST,
APPEND_DESCRIPTORLISTS,
RECEIVE_DESCRIPTORLISTS_OVERVIEW,
} from 'descriptors/constants';
// Model
import DescriptorList from 'model/catalog/DescriptorList';
import DescriptorListFilter from 'model/catalog/DescriptorListFilter';
import Page from 'model/Page';
import FilteredPage, { IPageRequest } from 'model/FilteredPage';
import DescriptorListOverview from 'model/catalog/DescriptorListOverview';
// Service
import DescriptorListService from 'service/catalog/DescriptorListService';
......@@ -25,6 +30,7 @@ import { log } from 'utilities/debug';
const apiLoadDescriptorList = createApiCaller(DescriptorListService.getDescriptorList, RECEIVE_DESCRIPTORLIST);
const apiPureLoadDescriptorList = createPureApiCaller(DescriptorListService.getDescriptorList);
const apiListDescriptorLists = createApiCaller(DescriptorListService.listDescriptorLists, APPEND_DESCRIPTORLISTS);
const apiOverviewDescriptorLists = createApiCaller(DescriptorListService.overview, RECEIVE_DESCRIPTORLISTS_OVERVIEW);
const apiAutocomplete = createPureApiCaller(DescriptorListService.autocomplete);
// Just load a descriptor list
......@@ -91,3 +97,18 @@ export const applyFilters = (filters: string | DescriptorListFilter, page: IPage
dispatch(showSnackbar(`Filters applied.`));
});
};
export const updateOverviewRoute = (overview: DescriptorListOverview) => (dispatch) => {
dispatch(navigateTo(overview && overview.filterCode ? `/descriptorlists/overview/${overview.filterCode}` : '/descriptorlists/overview'));
};
export const applyOverviewFilters = (filters: string | DescriptorListFilter) => (dispatch) => {
dispatch(showSnackbar('Applying filters...'));
return dispatch(apiOverviewDescriptorLists(filters))
.then((overview) => {
dispatch(updateOverviewRoute(overview));
dispatch(showSnackbar(`Filters applied.`));
});
};
......@@ -7,10 +7,12 @@ import {
RECEIVE_DESCRIPTORLIST,
REMOVE_DESCRIPTORLIST,
APPEND_DESCRIPTORLISTS,
RECEIVE_DESCRIPTORLISTS_OVERVIEW,
} from 'descriptors/constants';
import { LOGIN_USER, LOGIN_APP, LOGOUT } from 'constants/login';
import DescriptorList from 'model/catalog/DescriptorList';
import DescriptorListOverview from 'model/catalog/DescriptorListOverview';
import FilteredPage from 'model/FilteredPage';
import ApiCall from 'model/ApiCall';
......@@ -19,10 +21,12 @@ import {dereferenceReferences} from 'utilities';
const INITIAL_STATE: {
descriptorList: ApiCall<DescriptorList>;
paged: ApiCall<Page<DescriptorList>>;
overview: ApiCall<DescriptorListOverview>
pagedQuery: any;
} = {
descriptorList: null,
paged: null,
overview: null,
pagedQuery: null,
};
......@@ -91,6 +95,20 @@ function descriptorListsPublic(state = INITIAL_STATE, action: { type: string, pa
});
}
case RECEIVE_DESCRIPTORLISTS_OVERVIEW: {
const { apiCall: { loading, error, timestamp, data } } = action.payload;
return update(state, {
overview: {
$set: {
loading,
error,
timestamp,
data: data !== undefined ? data : state.overview && state.overview.data,
},
},
});
}
default:
return state;
}
......
......@@ -6,6 +6,13 @@ import {ROLE_ADMINISTRATOR, ROLE_VETTEDUSER} from 'constants/userRoles';
const publicRoutes = [
{
path: '/descriptorlists/overview/:filterCode(v.+)?',
component: Loadable({
loader: () => import(/* webpackMode:"lazy", webpackChunkName: "descriptorlists" */'descriptorlists/ui/OverviewPage'),
}),
exact: true,
},
{
path: '/descriptorlists/:filterCode(v.+)?',
component: Loadable({
......
......@@ -3,7 +3,13 @@
"modelName": "Descriptor list",
"modelName_plural": "Descriptor lists",
"menu": "Descriptor lists",
"subtitle": "Compilations of crop descriptors"
"subtitle": "Compilations of crop descriptors",
"overview": {
"owner": {
"name": "Maintainer"
},
"crop": "Crop"
}
},
"public": {
"common": {
......@@ -98,5 +104,9 @@
"listDescriptorlists": "List descriptor lists",
"createDescriptorlist": "Create descriptor list"
}
},
"tab": {
"data": "Descriptor lists",
"overview": "Overview"
}
}
\ No newline at end of file
......@@ -21,6 +21,7 @@ import PageTitle from 'ui/common/PageTitle';
import PageLayout, { PageContents } from 'ui/layout/PageLayout';
import ContentHeader from 'ui/common/heading/ContentHeader';
import BrowsePageTemplate from 'ui/pages/_base/BrowsePage';
import Tabs, { Tab } from 'ui/common/Tabs';
// Page to browse and filter descriptor lists
class BrowsePage extends BrowsePageTemplate<DescriptorList> {
......@@ -30,7 +31,7 @@ class BrowsePage extends BrowsePageTemplate<DescriptorList> {
)
public render() {
const { paged, t, loadMoreData, loading} = this.props;
const { paged, t, loadMoreData, filterCode, currentTab, loading} = this.props;
return (
<PageLayout sidebar={
......@@ -42,6 +43,12 @@ class BrowsePage extends BrowsePageTemplate<DescriptorList> {
title={ t('descriptorlists.common.modelName_plural') }
subtitle={ t('descriptorlists.public.p.browse.subtitle') }
/>
<Tabs
tab={ currentTab }
>
<Tab name="overview" to={ `/descriptorlists/overview/${filterCode || ''}` }>{ t('descriptorlists.tab.overview') }</Tab>
<Tab name="data" to={ `/descriptorlists/${filterCode || ''}` }>{ t('descriptorlists.tab.data') }</Tab>
</Tabs>
<PaginationComponent
pageObj={ paged }
onSortChange={ this.onSortChange }
......@@ -72,6 +79,7 @@ const mapStateToProps = (state, ownProps) => ({
paged: state.descriptorList.public.paged ? state.descriptorList.public.paged.data : undefined,
loading: state.descriptorList.public.paged ? state.descriptorList.public.paged.loading : false,
filterCode: ownProps.match.params.filterCode,
currentTab: ownProps.match.params.tab || 'data', // current tab, or ownProps.location.pathname
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
......
......@@ -59,9 +59,6 @@ class DescriptorListPage extends React.Component<IDescriptorListPageProps, any>
const { classes, loading, deleteDescriptorList, setDescriptorsToDescriptorList, copyDescriptor, descriptorList, t } = this.props;
const { approveDescriptorList, rejectDescriptorList, publishDescriptorList } = this.props;
console.log('=============================', loading);
console.log('-----------------------------', descriptorList);
return (
<PageLayout>
<PageTitle title={ !loading && descriptorList ? `${descriptorList.title} ${descriptorList.versionTag}` : t('common:label.loading', {what: t('descriptorlists.public.p.display.title')}) }/>
......
import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { translate } from 'react-i18next';
// actions
import { applyOverviewFilters, updateOverviewRoute } from 'descriptorlists/actions/public';
import { loadPartners } from 'partners/actions/public';
import { showSnackbar } from 'actions/snackbar';
// model
import ApiCall from 'model/ApiCall';
import DescriptorListOverview from 'model/catalog/DescriptorListOverview';
import DescriptorListFilter from 'model/catalog/DescriptorListFilter';
// UI
import PageLayout, { PageContents } from 'ui/layout/PageLayout';
import Tabs, { Tab } from 'ui/common/Tabs';
import ContentHeader from 'ui/common/heading/ContentHeader';
import Number from 'ui/common/Number';
import Loading from 'ui/common/Loading';
import PageTitle from 'ui/common/PageTitle';
import PropertiesCard from 'ui/common/PropertiesCard';
import GridContainer from 'ui/layout/GridContainer';
import { PartnerLink } from 'ui/catalog/Links';
import PrettyFilters from 'ui/common/filter/PrettyFilters';
import DescriptorListFilters from 'descriptorlists/ui/c/Filters';
interface IOverviewPageProps extends React.ClassAttributes<any> {
overview: ApiCall<DescriptorListOverview>;
applyOverviewFilters: (filter: string | DescriptorListFilter) => void;
updateOverviewRoute: (overview: DescriptorListOverview) => void;
showSnackbar: (message: string) => void;
filterCode: string;
currentTab: string;
partnerNames: any;
t: any;
}
class OverviewPage extends React.Component<IOverviewPageProps> {
protected static needs = [
({search, params: {filterCode}}) => {
return applyOverviewFilters(filterCode || '');
},
() => loadPartners({page: 0, size: 50}),
];
public componentWillMount() {
const {overview: apiCall, filterCode, applyOverviewFilters} = this.props;
const {data: overview} = apiCall || {data: undefined};
if (!overview || filterCode !== overview.filterCode) {
console.log('Applying filters', filterCode);
applyOverviewFilters(filterCode || '');
}
}
private addTerm = (property, term) => {
const {overview: apiCall, applyOverviewFilters, showSnackbar} = this.props;
const {data: overview} = apiCall;
const updatedFilter: DescriptorListFilter = {...overview.filter};
switch (property) {
case 'crop':
case 'owner.uuid':
_.set(updatedFilter, property, _.concat(_.get(updatedFilter, property), term).filter((x) => x != null));
break;
// set
default:
_.set(updatedFilter, property, term);
}
console.log(`Updated filter for ${ property } +${ term }`, updatedFilter);
showSnackbar('Applying filters...');
applyOverviewFilters(updatedFilter);
}
public render() {
const {filterCode, currentTab, overview: apiCall, applyOverviewFilters, t} = this.props;
const partnerNames = new Map(this.props.partnerNames);
const {data: overviewWrapper, loading} = apiCall || {data: undefined, loading: true};
const overview = overviewWrapper && overviewWrapper.overview;
const overviewsTerms = new Map();
if (overview) {
Object.keys(overview).map((key) => {
const overviewEl = overview[key];
const terms = [].concat(overviewEl.terms,
{
term: 'Other',
count: overviewEl.other,
},
{
term: 'Not specified',
count: overviewEl.missing,
},
);
overviewsTerms.set(key, terms);
});
}
const filterByTerm = (property, term, count) => {
const skipTerms = ['Other', 'Missing', 'Not specified'];
return (
skipTerms.indexOf(term.term) === -1
? (<a onClick={ () => this.addTerm(property, term.term) }><Number value={ count }/></a>)
: (count)
);
};
return (
<PageLayout
sidebar={
<DescriptorListFilters initialValues={ overviewWrapper && overviewWrapper.filter || {} } onSubmit={ applyOverviewFilters }/>
}
withFooter
>
<PageTitle title={ t('descriptorlists.common.modelName_plural') }/>
<ContentHeader
title={ t('descriptorlists.common.modelName_plural') }
subtitle={ t('descriptorlists.public.p.browse.subtitle') }
/>
<Tabs tab={ currentTab }>
<Tab name="overview" to={ `/descriptorlists/overview/${ filterCode || '' }` }>{ t('descriptorlists.tab.overview') }</Tab>
<Tab name="data" to={ `/descriptorlists/${ filterCode || '' }` }>{ t('descriptorlists.tab.data') }</Tab>
</Tabs>
<PrettyFilters
prefix="descriptorlists"
filterObj={ overviewWrapper && overviewWrapper.filter || {} }
onSubmit={ applyOverviewFilters }
/>
{ loading && <Loading/> }
{ overview &&
<div>
<PageContents className="pt-1rem">
<GridContainer>
{ overviewsTerms && overviewsTerms.get('owner.uuid') && overviewsTerms.get('owner.uuid').length > 2 &&
<PropertiesCard
propertiesList={ overviewsTerms.get('owner.uuid').map((term) => (
{
title: partnerNames && partnerNames.size > 0 && partnerNames.get(term.term) ? <PartnerLink uuid={ term.term }>{ partnerNames.get(term.term) }</PartnerLink> : term.term,
value: filterByTerm('owner.uuid', term, term.count),
}
)) }
title={ t(`descriptorlists.common.overview.owner.name`) }
propertyItemProps={ {numeric: true} }
small
/>
}
{ overviewsTerms && overviewsTerms.get('crop') && overviewsTerms.get('crop').length > 2 &&
<PropertiesCard
propertiesList={ overviewsTerms.get('crop').map((term) => ({ title: term.term, value: filterByTerm('crop', term, term.count) })) }
title={ t(`descriptorlists.common.overview.crop`) }
propertyItemProps={ {numeric: true} }
small
/>
}
</GridContainer>
</PageContents>
</div>
}
</PageLayout>
);
}
}
const mapStateToProps = (state, ownProps) => ({
overview: state.descriptorList.public.overview,
partnerNames: state.uuidDecoder.labels,
filterCode: ownProps.match.params.filterCode || '',
currentTab: ownProps.match.params.tab || 'overview', // current tab, or ownProps.location.pathname
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
applyOverviewFilters,
updateOverviewRoute,
showSnackbar,
}, dispatch);
export default translate()(connect(mapStateToProps, mapDispatchToProps)(OverviewPage));
......@@ -26,6 +26,7 @@ const DescriptorListFilters = ({ handleSubmit, initialize, t, ...other }) => (
);
export default translate()(reduxForm({
destroyOnUnmount: false,
enableReinitialize: true,
form: DESCRIPTORLIST_FILTERFORM,
})(DescriptorListFilters));
......@@ -19,6 +19,7 @@ export const DASHBOARD_CLEAR_DESCRIPTORS = 'descriptor/dashboard/DASHBOARD_CLEAR
// Descriptor lists
export const RECEIVE_DESCRIPTORLIST = 'App/RECEIVE_DESCRIPTORLIST';
export const RECEIVE_DESCRIPTORLISTS_OVERVIEW = 'App/RECEIVE_DESCRIPTORLISTS_OVERVIEW';
// Descriptor lists: dashboard
export const DASHBOARD_APPEND_DESCRIPTORLISTS = 'descriptorList/dashboard/APPEND_DESCRIPTORLISTS';
......
import DescriptorListFilter from 'model/catalog/DescriptorListFilter';
/*
* Defined in Swagger as '#/definitions/DescriptorListOverview'
*/
class DescriptorListOverview {
public descriptorListCount: number;
public filter: DescriptorListFilter;
public filterCode: string;
public overview: any;
public suggestions: any;
}
export default DescriptorListOverview;
......@@ -7,6 +7,7 @@ import DescriptorList from 'model/catalog/DescriptorList';
import DescriptorListFilter from 'model/catalog/DescriptorListFilter';
import FilteredPage from 'model/FilteredPage';
import { IPageRequest } from 'model/Page';
import DescriptorListOverview from 'model/catalog/DescriptorListOverview';
const URL_ADD_DESCRIPTOR = UrlTemplate.parse(`/api/v1/descriptorlist/add-descriptors/{uuid},{version}`);
const URL_APPROVE_DESCRIPTOR_LIST = `/api/v1/descriptorlist/approve`;
......@@ -14,6 +15,7 @@ const URL_AUTOCOMPLETE = `/api/v1/descriptorlist/autocomplete`;
const URL_CREATE_DESCRIPTOR_LIST = `/api/v1/descriptorlist/create`;
const URL_REVIEW_DESCRIPTOR_LIST = `/api/v1/descriptorlist/for-review`;
const URL_LIST_DESCRIPTOR_LISTS = `/api/v1/descriptorlist/list`;
const URL_OVERVIEW = `/api/v1/descriptorlist/overview`;
const URL_MY_DESCRIPTOR_LISTS = `/api/v1/descriptorlist/list-mine`;
const URL_LIST_DESCRIPTOR_LISTS_BY_CODE = UrlTemplate.parse(`/api/v1/descriptorlist/list/{filterCode}`);
const URL_REJECT_DESCRIPTOR_LIST = `/api/v1/descriptorlist/reject`;
......@@ -166,6 +168,30 @@ class DescriptorListService {
}).then(({ data }) => data as FilteredPage<DescriptorList>);
}
/**
* overview at /api/v1/descriptorlist/overview
*
* @param filter filter
* @param f f
* @param xhrConfig additional xhr config
*/
public static overview(filter?: string | DescriptorListFilter, xhrConfig?: any): Promise<DescriptorListOverview> {
const qs = QueryString.stringify({
f: typeof filter === 'string' ? filter : undefined,
}, {});
const apiUrl = URL_OVERVIEW + (qs ? `?${qs}` : '');
// console.log(`Fetching from ${apiUrl}`);
const content = { data: typeof filter === 'string' ? null : { ...filter } };
return axiosBackend.request({
...xhrConfig,
url: apiUrl,
method: 'POST',
...content,
}).then(({ data }) => data as DescriptorListOverview);
}
/**
* myDescriptorLists at /api/v1/descriptorlist/list-mine
*
......
......@@ -126,15 +126,15 @@ const ServerLink = ({ link, children, classes = '' }) =>
const FaoWiewsLink = ({ wiewsCode, children }) =>
<ExternalLink link={ `http://www.fao.org/wiews/data/organizations/en/?instcode=${wiewsCode}` }>{ children ? children : wiewsCode }</ExternalLink>;
const PartnerLink = ({ to: partner, edit = false, children }: { to: Partner, edit?: boolean, children?: any } & any) => {
if (edit) {
const PartnerLink = ({ to: partner, uuid, edit = false, children }: { to?: Partner, uuid?: string, edit?: boolean, children?: any } & any) => {
if (partner) {
return (
<Link to={ `/dashboard/partners/${partner.uuid}/edit` }>{ children || partner.name }</Link>
<Link to={ `${edit ? '/dashboard' : ''}/partners/${partner && partner.uuid}${edit ? '/edit' : ''}` }>{ children || partner.name }</Link>
);
} else {
return (
<Link to={ `/partners/${partner.uuid}` }>{ children || partner.name }</Link>
);
return uuid && children ? (
<Link to={ `${edit ? '/dashboard' : ''}/partners/${uuid}${edit ? '/edit' : ''}` }>{ children }</Link>
) : null;
}
};
......
......@@ -230,7 +230,8 @@ class PrettyFilters extends React.Component<IPrettyFiltersProps, any> {
}
public render() {
const { classes, lookups, prefix, t, labels, amount, displayName } = this.props;
const { classes, lookups, prefix, t, amount, displayName } = this.props;
const labels = new Map(this.props.labels);
const { chipData } = this.state;
const dataArr = Object.getOwnPropertyNames(chipData);
const withAmount = amount !== undefined && amount !== null;
......
......@@ -59,7 +59,7 @@ const PUBLIC_MENUS = [
label: 'public.menu.exploreDatasets',
},
{
to: '/descriptorlists',
to: '/descriptorlists/overview',
label: 'public.menu.exploreDescriptorLists',
},
{
......
......@@ -9,7 +9,6 @@ import GoogleLogin from 'react-google-login';
import { ActionMenuItem } from 'ui/layout/headers/v1/MenuItem';
const styles = (theme) => {
console.log(theme);
return {
/*tslint:disable*/
googleBtn: {
......
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