Commit 5e1a9fbc authored by Valeriy Panov's avatar Valeriy Panov
Browse files

#161 Controlled Vocabulary pages

parent 3ddea8b8
Pipeline #4076 passed with stages
in 4 minutes and 7 seconds
import * as React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { withStyles } from 'material-ui/styles';
import Grid from 'material-ui/Grid';
import Button from 'material-ui/Button';
import PaginationComponent from 'ui/common/pagination';
import { listVocabularies, createVocabulary } from 'actions/vocabulary';
import { Vocabulary, IVocabularyFilter } from 'model/vocabulary.model';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
import VocabularyCard from './c/VocabularyCard';
import { Page, Pagination } from 'model/common.model';
const styles = (theme) => ({
root: {
backgroundColor: '#E8E5E0',
},
card: {
margin: '20px',
},
});
interface IBrowsePageProps extends React.ClassAttributes<any> {
router: any;
classes: any;
pagination?: Pagination<IVocabularyFilter>;
paged?: Page<Vocabulary>;
listVocabularies: any;
createVocabulary: any;
}
const sortOptions = {
title: 'Title',
lastModifiedDate: { label: 'Last modified', dir: 'DESC' },
};
class BrowsePage extends React.Component<IBrowsePageProps, any> {
protected static needs = [
({ location: { query: { p: pageCurrent, l: pageSize, s: pageSort, d: pageDir } } }) => listVocabularies(pageCurrent, pageSize, pageSort, {}, pageDir),
];
public componentWillMount() {
const {pagination, paged, listVocabularies} = this.props;
if (!paged) {
listVocabularies(pagination.page, pagination.size, pagination.sort, pagination.filter, pagination.dir);
}
}
public componentWillReceiveProps(nextProps) {
const {listVocabularies, pagination: oldPagination} = this.props;
const {pagination} = nextProps;
if (!oldPagination.equals(pagination)) {
listVocabularies(pagination.page, pagination.size, pagination.sort, pagination.filter, pagination.dir);
}
}
protected onPaginationChange = (page, results, sortBy, dir) => {
const {router, router: { location }} = this.props;
location.query.p = page;
location.query.l = results;
location.query.s = sortBy;
location.query.d = dir;
router.push(location);
}
public render() {
const {classes, paged, createVocabulary} = this.props;
return paged && (
<div className={ classes.root }>
<ContentHeaderWithButton title="What do you want to do?" buttons={ <Button raised onClick={ createVocabulary }>Create vocabulary</Button> } />
<Grid container>
<Grid item xs={ 12 }>
<PaginationComponent pageObj={ paged }
onChange={ this.onPaginationChange }
displayName="vocabularies"
sortOptions={ sortOptions }
/>
</Grid>
{ paged && paged.content.map((vocabulary: Vocabulary, index) => (
<Grid key={ index } item xs={ 12 }>
<VocabularyCard className={ classes.card } vocabulary={ vocabulary } />
</Grid>
)) }
<Grid item xs={ 12 }>
<PaginationComponent pageObj={ paged }
onChange={ this.onPaginationChange }
displayName="vocabularies"
sortOptions={ sortOptions }
/>
</Grid>
</Grid>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => ({
pagination: new Pagination<IVocabularyFilter>({
page: +ownProps.location.query.p || 0, // current page
size: +ownProps.location.query.l || 20, // page size
sort: ownProps.location.query.s, // page sorts
dir: ownProps.location.query.d, // page sort directions
}),
paged: state.vocabulary.paged,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
listVocabularies,
createVocabulary,
}, dispatch);
const styled = withStyles(styles)(BrowsePage);
export default connect(mapStateToProps, mapDispatchToProps)(styled);
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {withStyles} from 'material-ui/styles';
import Grid from 'material-ui/Grid';
import Card, {CardHeader, CardContent} from 'material-ui/Card';
import Divider from 'material-ui/Divider';
import Paper from 'material-ui/Paper';
import {loadVocabulary} from 'actions/vocabulary';
import {Vocabulary} from 'model/vocabulary.model';
import Markdown from 'ui/common/markdown';
import ContentHeaderWithButton from 'ui/common/heading/ContentHeaderWithButton';
interface IDisplayPageProps extends React.ClassAttributes<any> {
classes: any;
uuid?: string;
loadVocabulary: (uuid: string) => void;
vocabulary?: Vocabulary;
}
const styles = (theme) => ({
contentContainer: {
backgroundColor: '#E8E5E0',
padding: '1rem',
},
form: {
padding: '20px',
margin: '0 0 1rem 0',
},
});
class DisplayPage extends React.Component<IDisplayPageProps, any> {
protected static needs = [
({params: {uuid}}) => loadVocabulary(uuid),
];
public componentDidMount() {
const {vocabulary, loadVocabulary, uuid} = this.props;
if (uuid && (!vocabulary || vocabulary.uuid !== uuid)) {
loadVocabulary(uuid);
}
}
public render() {
const {classes, vocabulary} = this.props;
return vocabulary && (
<div>
<ContentHeaderWithButton title="Vocabulary details" buttonName="BACK" buttonUrl="/vocabulary"/>
<Grid container className={ classes.contentContainer }>
<Grid item xs={ 12 }>
<Card className={ classes.card } square>
<CardHeader className={ classes.cardHeader } title={ (
<span>{ vocabulary.title }
<small>{ vocabulary.versionTag }</small></span>
) }/>
<Divider/>
<CardContent className={ classes.cardContent }>
{ vocabulary.description && <Markdown source={ vocabulary.description }/> }
<div>Record version: <b>{ vocabulary.version }</b> UUID: <b>{ vocabulary.uuid }</b></div>
</CardContent>
</Card>
</Grid>
{ vocabulary.terms && (
<Paper className={ classes.form }>
{ vocabulary.terms.map((term) => (
<div key={ term.code }>{ term.code } &rarr; { term.title }</div>
)) }
</Paper>
) }
</Grid>
</div>
);
}
}
const mapStateToProps = (state, ownProps) => ({
uuid: ownProps.params.uuid,
vocabulary: state.vocabulary.currentVocabulary,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadVocabulary,
}, dispatch);
const styled = withStyles(styles)(DisplayPage);
export default connect(mapStateToProps, mapDispatchToProps)(styled);
import * as React from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {withStyles} from 'material-ui/styles';
import Grid from 'material-ui/Grid';
import Paper from 'material-ui/Paper';
import {loadMyPartners} from 'actions/partner';
import {loadVocabulary, saveVocabulary} from 'actions/vocabulary';
import {Vocabulary} from 'model/vocabulary.model';
import {Partner} from 'model/partner.model';
import VocabularyForm from './c/VocabularyForm';
interface IVocabularyEditPageProps extends React.ClassAttributes<any> {
classes: any;
uuid?: string;
loadMyPartners: () => void;
myPartners: Partner[];
vocabulary?: Vocabulary;
loadVocabulary: (uuid: string) => void;
saveVocabulary: (vocabulary: Vocabulary) => void;
}
const styles = (theme) => ({
contentContainer: {
backgroundColor: '#E8E5E0',
padding: '1rem',
},
form: {
padding: '20px',
margin: '0 0 1rem 0',
},
});
class VocabularyEditPage extends React.Component<IVocabularyEditPageProps, any> {
protected static needs = [
({params: {uuid}}) => loadVocabulary(uuid),
loadMyPartners,
];
public componentDidMount() {
const {vocabulary, myPartners, loadMyPartners, loadVocabulary, uuid} = this.props;
if (!myPartners || myPartners.length === 0) {
loadMyPartners();
}
if (uuid && (!vocabulary || vocabulary.uuid !== uuid)) {
loadVocabulary(uuid);
}
}
public onSave = (updatedVocabulary: Vocabulary) => {
const {saveVocabulary} = this.props;
if (updatedVocabulary.uuid && updatedVocabulary.version) {
delete updatedVocabulary.owner;
}
saveVocabulary(updatedVocabulary);
}
public render() {
const {classes, uuid, myPartners} = this.props;
let {vocabulary} = this.props;
if (!vocabulary && !uuid) {
vocabulary = new Vocabulary();
if (myPartners && myPartners.length > 0) {
vocabulary.owner = myPartners.length[0];
}
}
return vocabulary && (
<Grid container className={ classes.contentContainer }>
<Grid item xs={ 12 }>
<Paper className={ classes.form }>
<VocabularyForm initialValues={ vocabulary } onSubmit={ this.onSave }/>
</Paper>
</Grid>
</Grid>
);
}
}
const mapStateToProps = (state, ownProps) => ({
uuid: ownProps.params.uuid,
myPartners: state.partner.myPartners,
vocabulary: state.vocabulary.currentVocabulary,
});
const mapDispatchToProps = (dispatch) => bindActionCreators({
loadMyPartners,
loadVocabulary,
saveVocabulary,
}, dispatch);
const styled = withStyles(styles)(VocabularyEditPage);
export default connect(mapStateToProps, mapDispatchToProps)(styled);
import * as React from 'react';
import ContentHeader from 'ui/common/heading/ContentHeader';
const Wrapper = ({children}: any) => (
<div>
<ContentHeader title="Vocabulary" subTitle="Controlled vocabulary" />
{ children }
</div>
);
export default Wrapper;
import * as React from 'react';
import {Link} from 'react-router';
import Card, {CardHeader, CardContent} from 'material-ui/Card';
import Divider from 'material-ui/Divider';
import {Vocabulary} from 'model/vocabulary.model';
import Markdown from 'ui/common/markdown';
const VocabularyCard = ({vocabulary, className = ''}: { vocabulary: Vocabulary, className: string }) => vocabulary && (
<Card className={ className }>
<CardHeader title={
vocabulary._permissions.write ?
<Link to={ `/vocabulary/${vocabulary.uuid}/edit` }>{ vocabulary.title }</Link>
: <span>{ vocabulary.title }</span>
}/>
<Divider/>
<CardContent>
{ vocabulary.description && vocabulary.description.substring(0, 200) !== '' && (
<CardContent>
<Markdown source={ vocabulary.description.substring(0, 200) + '...' } />
</CardContent>
) }
</CardContent>
</Card>
);
export default VocabularyCard;
import * as React from 'react';
import { connect } from 'react-redux';
import {Field, reduxForm} from 'redux-form';
import {VOCABULARY_FORM} from 'constants/vocabulary';
import { VocabularyTerm } from 'model/vocabulary.model';
import { TextField } from 'ui/common/text-field';
import {MarkdownField} from 'ui/common/markdown';
import Heading from 'ui/common/heading';
import ItemsEditor from 'ui/common/ItemsEditor';
import Button from 'material-ui/Button';
import Grid from 'material-ui/Grid';
const TermEditor = (member, index, fields) => (
<Grid container key={ index } justify="space-between" alignItems="flex-end">
<Grid item xs={ 2 } md={ 2 } lg={ 1 }><Field name={ `${member}.code` } type="text" component={ TextField } label="Code" /></Grid>
<Grid item xs={ 10 } md={ 10 } lg={ 5 }><Field name={ `${member}.title` } type="text" component={ TextField } label="Title" /></Grid>
<Grid item xs={ 12 } lg={ 6 }><Field name={ `${member}.description` } type="text" component={ TextField } multiline label="Description" /></Grid>
</Grid>
);
class VocabularyForm extends React.Component<any, any> {
public render() {
const {error, handleSubmit, initialValues} = this.props;
const onAddMember = () => {
console.log('Adding new term');
return new VocabularyTerm();
};
const onRemoveMember = (item) => {
console.log('Removing term', item);
};
if (! initialValues) {
return null;
}
return (
<div>
<form onSubmit={ handleSubmit }>
<Heading title="Vocabulary details" level={ 2 } />
<Field required name="title"
label="Vocabulary title" placeholder="Color of magic"
component={ TextField }
/>
<Field required name="description"
label="Description" placeholder="Full description of the vocabulary"
component={ MarkdownField }
/>
<Field required name="versionTag"
label="Version tag" placeholder="1.0"
component={ TextField }
/>
<Field name="url"
label="URL to definition" placeholder="https://"
component={ TextField }
/>
<Field name="termUrlPrefix"
label="Term URL prefix" placeholder="???"
component={ TextField }
/>
<Heading title="Vocabulary terms" level={ 3 }/>
<ItemsEditor name="terms" itemLabel="Vocabulary term" addItem={ onAddMember } removeItem={ onRemoveMember } component={ TermEditor } />
<div>{ error && <strong>{ error }</strong> }</div>
<Button raised type="submit" >Save changes</Button>
</form>
</div>
);
}
}
export default connect((state) => ({}), null)(reduxForm({
form: VOCABULARY_FORM,
enableReinitialize: true,
})(VocabularyForm));
......@@ -42,6 +42,11 @@ import CropWrapper from './pages/crop/Wrapper';
import CropBrowsePage from './pages/crop/BrowsePage';
import CropEditPage from './pages/crop/EditPage';
import VocabularyWrapper from './pages/vocabulary/Wrapper';
import VocabularyBrowsePage from './pages/vocabulary/BrowsePage';
import VocabularyEditPage from './pages/vocabulary/EditPage';
import VocabularyDisplayPage from './pages/vocabulary/DisplayPage';
import guiTestingRoutes from './pages/ui-design';
export default(
......@@ -111,6 +116,17 @@ export default(
</Route>
</Route>
<Route path="/vocabulary">
<Route component={ VocabularyWrapper }>
<IndexRoute component={ VocabularyBrowsePage }/>
<Route component={ AuthorizedRouteComponent } authorities={ [ROLE_ADMINISTRATOR] }>
<Route path="create" component={ VocabularyEditPage }/>
<Route path=":uuid/edit" component={ VocabularyEditPage }/>
<Route path=":uuid" component={ VocabularyDisplayPage }/>
</Route>
</Route>
</Route>
<Route path="/login" component={ LoginPage }/>
{ guiTestingRoutes() }
<Route path="*" component={ NotFound }/>
......
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