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

CMS Admin: Activity posts

added missing translations
fixed add new activity post action
fixed SSR at edit page
changed activityPostDisplay to ActivityPostSection
ActivityPostSection changed to ArticleSection
parent 291f1522
......@@ -248,6 +248,8 @@
"oAuthClients": "OAuth clients",
"requests": "Requests",
"content": "CMS",
"activityPosts": "Activity posts",
"articles": "Articles",
"vocabulary": "Vocabulary",
"fileRepository": "File repository",
"cluster": "Cluster",
......@@ -522,6 +524,10 @@
"slug": "Slug",
"lang": "Language",
"isTemplate": "Is template"
},
"activityPostForm": {
"content": "Content",
"title": "Title"
}
},
"f": {
......@@ -537,8 +543,13 @@
"title": "Browse articles",
"create": "Create new article"
},
"browseActivityPost": {
"title": "Browse activity posts",
"create": "Create new activity post"
},
"edit": {
"articleSaved": "Article saved",
"activityPostSaved": "Activity post saved",
"transifex": {
"fetch": "Fetch translations",
"fetchStarted": "Fetch started",
......@@ -581,7 +592,8 @@
}
},
"common": {
"articleList": "Article list"
"articleList": "Article list",
"activityPostList": "Activity post list"
}
}
......
......@@ -2,10 +2,12 @@
import { showSnackbar } from 'actions/snackbar';
import navigateTo from 'actions/navigation';
// constants
import { ADMIN_APPEND_ARTICLES, ADMIN_RECEIVE_ARTICLE, ADMIN_RECEIVE_ARTICLES, ADMIN_LOADING_ARTICLE } from 'cms/constants';
import { ADMIN_APPEND_ARTICLES, ADMIN_RECEIVE_ARTICLE, ADMIN_RECEIVE_ARTICLES, ADMIN_LOADING_ARTICLE, ADMIN_APPEND_ACTIVITY_POSTS, ADMIN_RECEIVE_ACTIVITY_POSTS, ADMIN_RECEIVE_ACTIVITY_POST, ADMIN_LOADING_ACTIVITY_POST } from 'cms/constants';
// model
import Article from 'model/cms/Article';
import ArticleFilter from 'model/cms/ArticleFilter';
import ActivityPost from 'model/cms/ActivityPost';
import ActivityPostFilter from 'model/cms/ActivityPostFilter';
import Page from 'model/Page';
import FilteredPage, { IPageRequest } from 'model/FilteredPage';
// service
......@@ -32,6 +34,28 @@ const loadingArticle = (slug: string) => ({
payload: {slug},
});
const receiveActivityPosts = (paged: FilteredPage<ActivityPost>, error = null) => ({
type: ADMIN_RECEIVE_ACTIVITY_POSTS,
payload: { paged, error },
});
const appendActivityPosts = (paged: FilteredPage<ActivityPost>, error = null) => ({
type: ADMIN_APPEND_ACTIVITY_POSTS,
payload: { paged, error },
});
const receiveActivityPost = (id: number, activityPost: ActivityPost) => ({
type: ADMIN_RECEIVE_ACTIVITY_POST,
payload: {id, activityPost},
});
const loadingActivityPost = (id: number) => ({
type: ADMIN_LOADING_ACTIVITY_POST,
payload: {id},
});
export const loadArticle = (locale: string, slug: string, targetId: number, clazz: string) => (dispatch) => {
dispatch(loadingArticle(slug));
if (!targetId || !clazz) {
......@@ -55,6 +79,10 @@ export const updateRoute = (paged: FilteredPage<Article>) => (dispatch) => {
dispatch(navigateTo(paged.filterCode ? `/admin/content/${paged.filterCode}` : '/admin/content'));
};
export const activityPostUpdateRoute = (paged: FilteredPage<ActivityPost>) => (dispatch) => {
dispatch(navigateTo(paged.filterCode ? `/admin/content/activity-post/${paged.filterCode}` : '/admin/content/activity-post'));
};
export const applyFilters = (filters: ArticleFilter, page: IPageRequest = { page: 0 }) => (dispatch) => {
console.log('Applying new filter', filters);
dispatch(showSnackbar('Applying filters...'));
......@@ -70,6 +98,21 @@ export const applyFilters = (filters: ArticleFilter, page: IPageRequest = { page
});
};
export const activityPostApplyFilters = (filters: ActivityPostFilter, page: IPageRequest = { page: 0 }) => (dispatch) => {
console.log('Applying new filter', filters);
dispatch(showSnackbar('Applying filters...'));
return CmsService.listActivityPosts(filters, page)
.then((paged) => {
dispatch(receiveActivityPosts(paged));
dispatch(activityPostUpdateRoute(paged));
dispatch(showSnackbar(`Filters applied.`));
}).catch((error) => {
console.log(`API error`, error);
dispatch(receiveArticles(null, error));
dispatch(showSnackbar(`API error: ${error.statusText}`));
});
};
export const loadMoreArticles = (paged?: FilteredPage<Article>) => (dispatch) => {
return CmsService.listArticles(paged && paged.filterCode, Page.nextPage(paged))
.then((paged) => {
......@@ -85,6 +128,43 @@ export const loadMoreArticles = (paged?: FilteredPage<Article>) => (dispatch) =>
});
};
export const loadMoreActivityPosts = (paged?: FilteredPage<ActivityPost>) => (dispatch) => {
return CmsService.listActivityPosts(paged && paged.filterCode, Page.nextPage(paged))
.then((paged) => {
if (paged.number === 0) {
dispatch(receiveActivityPosts(paged));
} else {
dispatch(appendActivityPosts(paged));
}
dispatch(activityPostUpdateRoute(paged));
}).catch((error) => {
console.log(`API error`, error);
dispatch(receiveArticles(null, error));
});
};
export const loadActivityPost = (id: number) => (dispatch) => {
dispatch(loadingActivityPost(id));
return CmsService.getActivityPost(id)
.then((activityPost) => dispatch(receiveActivityPost(id, activityPost)));
};
export const createOrUpdateActivityPost = (activityPost: ActivityPost) => (dispatch) => {
const createOrUpdate = activityPost.id ? CmsService.updateActivityPost : CmsService.createActivityPost;
return createOrUpdate(activityPost)
.then((saved) => {
dispatch(receiveActivityPost(saved.id, saved));
dispatch(showSnackbar('cms.admin.p.edit.activityPostSaved'));
dispatch(navigateTo(`/admin/content/activity-post/${saved.id}/edit`));
}).catch((error) => dispatch(showSnackbar(error.statusText)));
};
export const deleteActivityPost = (id: number) => (dispatch) => {
return CmsService.deleteActivityPost(id)
.then(() => dispatch(navigateTo('/admin/content/activity-post')));
};
export const createGlobalArticle = (article: Article) => (dispatch) => {
article = {
body: '<p></p>',
......
......@@ -10,6 +10,14 @@ export const ADMIN_RECEIVE_ARTICLE = 'cms/admin/RECEIVE_ARTICLE';
export const ADMIN_RECEIVE_ARTICLES = 'cms/admin/RECEIVE_ARTICLES';
export const ADMIN_APPEND_ARTICLES = 'cms/admin/APPEND_ARTICLES';
export const ADMIN_LOADING_ARTICLE = 'cms/admin/LOADING_ARTICLE';
export const ADMIN_RECEIVE_ACTIVITY_POST = 'cms/admin/RECEIVE_ACTIVITY_POST';
export const ADMIN_RECEIVE_ACTIVITY_POSTS = 'cms/admin/RECEIVE_ACTIVITY_POSTS';
export const ADMIN_APPEND_ACTIVITY_POSTS = 'cms/admin/APPEND_ACTIVITY_POSTS';
export const ADMIN_LOADING_ACTIVITY_POST = 'cms/admin/LOADING_ACTIVITY_POST';
export const ARTICLE_FORM = 'form/cms/admin/article';
export const ACTIVITY_POST_FORM = 'form/cms/admin/activityPost';
export const ARTICLE_FILTERS = 'filter/cms/admin/article';
export const ACTIVITY_POST_FILTERS = 'filter/cms/admin/activityPost';
import { ADMIN_RECEIVE_ARTICLE, ADMIN_RECEIVE_ARTICLES, ADMIN_APPEND_ARTICLES, ADMIN_LOADING_ARTICLE } from 'cms/constants';
import { ADMIN_APPEND_ARTICLES, ADMIN_LOADING_ACTIVITY_POST, ADMIN_LOADING_ARTICLE, ADMIN_RECEIVE_ACTIVITY_POST, ADMIN_RECEIVE_ACTIVITY_POSTS, ADMIN_RECEIVE_ARTICLE, ADMIN_RECEIVE_ARTICLES } from 'cms/constants';
import update from 'immutability-helper';
import Page from 'model/Page';
import Article from 'model/cms/Article';
import ActivityPost from 'model/cms/ActivityPost';
const INITIAL_STATE: {
articles: any,
loading: any,
paged: Page<Article>,
pagedError: any,
activityPost: {
paged: Page<ActivityPost>,
pagedError: any,
posts: any,
loading: any,
},
} = {
articles: {},
loading: null,
paged: null,
pagedError: null,
activityPost: {
paged: null,
pagedError: null,
posts: [],
loading: null,
},
};
export default function cmsAdmin(state = INITIAL_STATE, action: { type?: string, payload?: any } = {type: '', payload: {}}) {
......@@ -59,6 +72,43 @@ export default function cmsAdmin(state = INITIAL_STATE, action: { type?: string,
pagedError: { $set: error },
});
}
case ADMIN_RECEIVE_ACTIVITY_POSTS: {
const { paged, error } = action.payload;
return update(state, {
activityPost: {
paged: { $set: paged },
pagedError: { $set: error },
},
});
}
case ADMIN_RECEIVE_ACTIVITY_POST: {
const {id, activityPost } = action.payload;
return update(state, {
activityPost: {
posts: {
[id]: { $set: activityPost },
},
loading: { $set: null },
},
});
}
case ADMIN_LOADING_ACTIVITY_POST: {
const { id } = action.payload;
return update(state, {
activityPost: {
current: { $set: null },
loading: {
$set: {
id: { $set: id },
},
},
},
});
}
default:
return state;
}
......
......@@ -34,6 +34,29 @@ const publicRoutes = [
];
const adminRoutes = [
{
path: '/content/activity-post/:id/edit',
component: Loadable({
loader: () => import(/* webpackMode:"lazy", webpackChunkName: "cms" */'cms/ui/admin/ActivityPostEditPage'),
loading: Loading,
}),
exact: true,
},
{
path: '/content/activity-post/edit',
component: Loadable({
loader: () => import(/* webpackMode:"lazy", webpackChunkName: "cms" */'cms/ui/admin/ActivityPostEditPage'),
loading: Loading,
}),
exact: true,
},
{
path: '/content/activity-post/:filterCode(v.+)?',
component: Loadable({
loader: () => import(/* webpackMode:"lazy", webpackChunkName: "cms" */'cms/ui/admin/ActivityPostBrowsePage'),
loading: Loading,
}),
},
{
path: '/content/:slug/edit',
component: Loadable({
......
......@@ -9,6 +9,10 @@
"slug": "Slug",
"lang": "Language",
"isTemplate": "Is template"
},
"activityPostForm": {
"content": "Content",
"title": "Title"
}
},
"f": {
......@@ -24,8 +28,13 @@
"title": "Browse articles",
"create": "Create new article"
},
"browseActivityPost": {
"title": "Browse activity posts",
"create": "Create new activity post"
},
"edit": {
"articleSaved": "Article saved",
"activityPostSaved": "Activity post saved",
"transifex": {
"fetch": "Fetch translations",
"fetchStarted": "Fetch started",
......@@ -68,6 +77,7 @@
}
},
"common": {
"articleList": "Article list"
"articleList": "Article list",
"activityPostList": "Activity post list"
}
}
import * as React from 'react';
import { Link } from 'react-router-dom';
import { translate } from 'react-i18next';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { parse } from 'query-string';
// Actions
import { activityPostApplyFilters, loadMoreActivityPosts, activityPostUpdateRoute } from 'cms/actions/admin';
// Models
import Page from 'model/Page';
import ActivityPost from 'model/cms/ActivityPost';
// UI
import BrowsePageTemplate, { IBrowsePageProps } from 'ui/pages/_base/BrowsePage';
import PageLayout, { PageContents } from 'ui/layout/PageLayout';
import ContentHeader from 'ui/common/heading/ContentHeader';
import Loading from 'ui/common/Loading';
import PagedLoader from 'ui/common/PagedLoader';
import ActivityPostFilters from './c/ActivityPostFilters';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import Button from '@material-ui/core/Button/Button';
import ActivityPostCard from './c/ActivityPostCard';
class ActivityPostBrowsePage extends BrowsePageTemplate<ActivityPost> {
protected static needs = [
({ search, params: { filterCode } }) => {
const qs = parse(search || '');
return activityPostApplyFilters(filterCode || '', Page.fromQueryString(qs));
},
];
constructor(props: IBrowsePageProps<ActivityPost>, context: any) {
super(props, context);
}
public render() {
const { paged, loadMoreData, t } = this.props;
const renderActivityPost = (aP: ActivityPost, index: number) => {
return <div><ActivityPostCard key={ index } index={ index } post={ aP } edit compact/></div>;
};
return (
<PageLayout sidebar={
<ActivityPostFilters initialValues={ paged && paged.filter || {} } onSubmit={ this.myApplyFilters }/>
}>
<ContentHeader title={ t('cms.admin.p.browseActivityPost.title') }/>
<ContentHeaderWithButton
buttons={
<Link to="/admin/content/activity-post/edit">
<Button variant="contained">{ t('cms.admin.p.browseActivityPost.create') }</Button>
</Link>
}
/>
<PageContents className="pt-1rem container-spacing-horizontal">
{ !paged ? <Loading/> :
<PagedLoader
paged={ paged }
loadMore={ loadMoreData }
roughItemHeight={ 45 }
itemRenderer={ renderActivityPost }
/>
}
</PageContents>
</PageLayout>
);
}
}
const mapStateToProps = (state, ownProps) => ({
paged: state.cms.admin.activityPost.paged || undefined,
filterCode: ownProps.match.params.filterCode,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
applyFilters: activityPostApplyFilters,
loadMoreData: loadMoreActivityPosts,
updateRoute: activityPostUpdateRoute,
}, dispatch);
export default translate()(connect(mapStateToProps, mapDispatchToProps)(ActivityPostBrowsePage));
import * as React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { bindActionCreators } from 'redux';
import { translate } from 'react-i18next';
import { withStyles } from '@material-ui/core';
// actions
import { createOrUpdateActivityPost, deleteActivityPost, loadActivityPost } from 'cms/actions/admin';
import { showSnackbar } from 'actions/snackbar';
// model
import ActivityPost from 'model/cms/ActivityPost';
// utilities
import { log } from 'utilities/debug';
// ui
import Grid from '@material-ui/core/Grid';
import Button from '@material-ui/core/Button';
import { PageContents } from 'ui/layout/PageLayout';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import ActionButton from 'ui/common/buttons/ActionButton';
import ButtonBar from 'ui/common/buttons/ButtonBar';
import ActivityPostForm from 'cms/ui/admin/c/ActivityPostForm';
import ArticleSection from 'cms/ui/c/ArticleSection';
import Footer from 'ui/layout/Footer';
const styles = () => ({
activityPost: {
maxWidth: '100%',
},
alertWarning: {
fontSize: 16,
backgroundColor: '#fcf8e3',
color: '#8a6d3b',
border: '1px solid #faebcc',
marginBottom: '1rem',
padding: '.5rem',
},
previewFooter: {
position: 'fixed' as 'fixed',
width: '100%',
bottom: 0,
},
/*tslint:disable*/
customSubHeader: {
justifyContent: 'space-between' as 'space-between',
'& > div': {
width: 'auto !important' as 'auto !important',
},
},
/*tslint:enable*/
});
interface IArticleEditPageProps extends React.ClassAttributes<any> {
t: any;
classes: any;
loading: any;
id?: number;
activityPost: ActivityPost;
loadActivityPost: (id: number) => void;
deleteActivityPost: (id: number) => void;
createOrUpdateActivityPost: (activityPost: ActivityPost) => void;
showSnackbar: any;
}
class ActivityPostEditPage extends React.Component<IArticleEditPageProps, any> {
protected static needs = [
({ params, state }) => loadActivityPost(params.id),
];
private constructor(props, context) {
super(props, context);
this.state = {
isEdit: true,
};
}
public componentWillMount() {
const { activityPost, loadActivityPost, id } = this.props;
if (id && !activityPost) {
loadActivityPost(id);
}
}
public componentWillReceiveProps(nextProps) {
const { activityPost, loadActivityPost, id, loading } = nextProps;
if (id && !activityPost && !loading) {
loadActivityPost(id);
}
}
public render() {
const { id, t, classes } = this.props;
const { isEdit } = this.state;
let { activityPost } = this.props;
if (!activityPost && !id) {
activityPost = new ActivityPost();
}
if (!activityPost) {
log('No activity post.');
return null;
}
return (
<div>
<ContentHeaderWithButton
title={ <span dangerouslySetInnerHTML={ { __html: activityPost.title } }/> }
classes={ { subHeader: classes.customSubHeader } }
buttons={
<ButtonBar>
<ActionButton
title={ isEdit ? t('common:action.view') : t('common:action.edit') }
action={ () => this.setState({ isEdit: !isEdit }) }
/>
{ id &&
<ActionButton
title={ t('common:action.delete') }
action={ this.onDelete }
/>
}
<Link to={ `/admin/content/activity-post` }> <Button variant="contained" type="button">{ t('common:action.backTo', { where: t('cms.common.activityPostList') }) }</Button></Link>
</ButtonBar>
}
/>
{ isEdit ? (
<PageContents className="pt-1rem pb-1rem">
<Grid item xs={ 12 }>
<ActivityPostForm
initialValues={ activityPost }
onSubmit={ this.onSave }
t={ t }
/>
</Grid>
</PageContents>
) : (
<div>
<ArticleSection title={ <span dangerouslySetInnerHTML={ { __html: activityPost.title } }/> } body={ activityPost.body } />
<div className={ classes.previewFooter }>
<Footer />
</div>
</div>
) }
</div>
);
}
private onSave = (activityPost: ActivityPost) => {
const { createOrUpdateActivityPost } = this.props;
createOrUpdateActivityPost(activityPost);
}
private onDelete = () => {
const { deleteActivityPost, id } = this.props;
deleteActivityPost(id);
}
}
const mapStateToProps = (state, ownProps) => ({
id: ownProps.match.params.id,
loading: state.cms.admin.activityPost.loading,
activityPost: state.cms.admin.activityPost.posts[ownProps.match.params.id] || null,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadActivityPost,
createOrUpdateActivityPost,
deleteActivityPost,
showSnackbar,
}, dispatch);
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(translate()(ActivityPostEditPage)));
......@@ -10,7 +10,7 @@ import { applyFilters, loadMoreArticles, updateRoute } from 'cms/actions/admin';
// Models
import Page from 'model/Page';
import Accession from 'model/accession/Accession';
import Article from 'model/cms/Article';
// UI
import BrowsePageTemplate, { IBrowsePageProps } from 'ui/pages/_base/BrowsePage';
......@@ -18,12 +18,12 @@ import PageLayout, { PageContents } from 'ui/layout/PageLayout';
import ContentHeader from 'ui/common/heading/ContentHeader';
import Loading from 'ui/common/Loading';
import PagedLoader from 'ui/common/PagedLoader';
import AccessionFilters from './c/ArticleFilters';
import ArticleFilters from './c/ArticleFilters';
import ArticleCard from './c/ArticleCard';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import Button from '@material-ui/core/Button/Button';