Commit f8b25ac3 authored by Viacheslav Pavlov's avatar Viacheslav Pavlov
Browse files

Added ui part for descriptor lists overview

Fixed displaying of partner overview

Now partner names loaded in SSR

Fixed Map deserializing after SSR

Changed order of Overview and Browse pages
parent 2f977b52
......@@ -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