onion/js/components/whitelabel/prize/components/ascribe_detail/prize_piece_container.js

453 lines
18 KiB
JavaScript
Raw Normal View History

'use strict';
import React from 'react';
import { Link } from 'react-router';
2015-08-26 09:50:38 +02:00
import Moment from 'moment';
2015-08-10 17:02:10 +02:00
import StarRating from 'react-star-rating';
import PieceActions from '../../../../../actions/piece_actions';
import PieceStore from '../../../../../stores/piece_store';
2015-08-11 17:12:12 +02:00
import PieceListStore from '../../../../../stores/piece_list_store';
import PieceListActions from '../../../../../actions/piece_list_actions';
2015-08-10 17:02:10 +02:00
import PrizeRatingActions from '../../actions/prize_rating_actions';
import PrizeRatingStore from '../../stores/prize_rating_store';
2015-08-12 13:21:05 +02:00
import UserStore from '../../../../../stores/user_store';
import Piece from '../../../../../components/ascribe_detail/piece';
2015-08-24 15:33:36 +02:00
import Note from '../../../../../components/ascribe_detail/note';
import AscribeSpinner from '../../../../ascribe_spinner';
import Form from '../../../../../components/ascribe_forms/form';
import Property from '../../../../../components/ascribe_forms/property';
import InputTextAreaToggable from '../../../../../components/ascribe_forms/input_textarea_toggable';
import CollapsibleParagraph from '../../../../../components/ascribe_collapsible/collapsible_paragraph';
import InputCheckbox from '../../../../ascribe_forms/input_checkbox';
2015-08-26 09:50:38 +02:00
import LoanForm from '../../../../ascribe_forms/form_loan';
import ListRequestActions from '../../../../ascribe_forms/list_form_request_actions';
2015-08-26 09:50:38 +02:00
import ModalWrapper from '../../../../ascribe_modal/modal_wrapper';
2015-08-12 13:21:05 +02:00
import GlobalNotificationModel from '../../../../../models/global_notification_model';
import GlobalNotificationActions from '../../../../../actions/global_notification_actions';
2015-08-11 17:12:12 +02:00
import DetailProperty from '../../../../ascribe_detail/detail_property';
2015-08-12 13:21:05 +02:00
import ApiUrls from '../../../../../constants/api_urls';
2015-08-11 17:12:12 +02:00
import { mergeOptions } from '../../../../../utils/general_utils';
import { getLangText } from '../../../../../utils/lang_utils';
2015-10-13 17:02:03 +02:00
import { setDocumentTitle } from '../../../../../utils/dom_utils';
2015-08-12 16:47:16 +02:00
/**
* This is the component that implements resource/data specific functionality
*/
let PieceContainer = React.createClass({
getInitialState() {
2015-08-12 16:47:16 +02:00
return mergeOptions(
PieceStore.getState(),
2015-09-03 17:25:22 +02:00
UserStore.getState()
2015-08-12 16:47:16 +02:00
);
},
componentDidMount() {
PieceStore.listen(this.onChange);
PieceActions.fetchOne(this.props.params.pieceId);
2015-08-12 16:47:16 +02:00
UserStore.listen(this.onChange);
},
2015-08-24 15:33:36 +02:00
// This is done to update the container when the user clicks on the prev or next
// button to update the URL parameter (and therefore to switch pieces)
componentWillReceiveProps(nextProps) {
if(this.props.params.pieceId !== nextProps.params.pieceId) {
PieceActions.updatePiece({});
PieceActions.fetchOne(nextProps.params.pieceId);
}
},
componentWillUnmount() {
// Every time we're leaving the piece detail page,
// just reset the piece that is saved in the piece store
// as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PieceActions.updatePiece({});
PieceStore.unlisten(this.onChange);
2015-08-12 16:47:16 +02:00
UserStore.unlisten(this.onChange);
},
2015-08-11 17:12:12 +02:00
onChange(state) {
this.setState(state);
},
loadPiece() {
PieceActions.fetchOne(this.props.params.pieceId);
},
getActions() {
if (this.state.piece &&
2015-09-03 17:25:22 +02:00
this.state.piece.notifications &&
this.state.piece.notifications.length > 0) {
return (
<ListRequestActions
pieceOrEditions={this.state.piece}
currentUser={this.state.currentUser}
handleSuccess={this.loadPiece}
2015-09-03 17:25:22 +02:00
notifications={this.state.piece.notifications}/>);
}
},
render() {
if(this.state.piece && this.state.piece.title) {
/*
This really needs a refactor!
- Tim
*/
2015-08-27 10:10:39 +02:00
// Only show the artist name if you are the participant or if you are a judge and the piece is shortlisted
let artistName = ((this.state.currentUser.is_jury && !this.state.currentUser.is_judge) ||
(this.state.currentUser.is_judge && !this.state.piece.selected )) ?
2015-10-13 17:02:03 +02:00
null : this.state.piece.artist_name;
2015-08-27 10:10:39 +02:00
// Only show the artist email if you are a judge and the piece is shortlisted
let artistEmail = (this.state.currentUser.is_judge && this.state.piece.selected ) ?
<DetailProperty label={getLangText('REGISTREE')} value={ this.state.piece.user_registered } /> : null;
2015-10-13 17:02:03 +02:00
if (artistName === null) {
setDocumentTitle(this.state.piece.title);
} else {
setDocumentTitle([artistName, this.state.piece.title].join(', '));
}
if (artistName === null) {
artistName = <span className="glyphicon glyphicon-eye-close" aria-hidden="true"/>;
}
return (
<Piece
piece={this.state.piece}
2015-08-11 17:12:12 +02:00
loadPiece={this.loadPiece}
header={
<div className="ascribe-detail-header">
2015-08-12 16:47:16 +02:00
<NavigationHeader
piece={this.state.piece}
currentUser={this.state.currentUser}/>
2015-08-11 17:12:12 +02:00
<hr/>
<h1 className="ascribe-detail-title">{this.state.piece.title}</h1>
<DetailProperty label={getLangText('BY')} value={artistName} />
2015-11-03 10:39:01 +01:00
<DetailProperty label={getLangText('DATE')} value={new Date(this.state.piece.date_created).getFullYear()} />
{artistEmail}
{this.getActions()}
2015-08-11 17:12:12 +02:00
<hr/>
</div>
}
subheader={
2015-08-12 16:47:16 +02:00
<PrizePieceRatings
loadPiece={this.loadPiece}
2015-08-12 16:47:16 +02:00
piece={this.state.piece}
currentUser={this.state.currentUser}/>
2015-08-11 17:12:12 +02:00
}>
<PrizePieceDetails piece={this.state.piece}/>
</Piece>
);
} else {
return (
<div className="fullpage-spinner">
<AscribeSpinner color='dark-blue' size='lg' />
</div>
);
}
}
});
2015-08-12 13:21:05 +02:00
let NavigationHeader = React.createClass({
propTypes: {
2015-08-12 16:47:16 +02:00
piece: React.PropTypes.object,
currentUser: React.PropTypes.object
2015-08-12 13:21:05 +02:00
},
render() {
2015-08-24 11:22:44 +02:00
if (this.props.currentUser && this.props.currentUser.email && this.props.piece && this.props.piece.navigation) {
let nav = this.props.piece.navigation;
2015-08-24 11:22:44 +02:00
return (
<div style={{marginBottom: '1em'}}>
<div className="row no-margin">
<Link className="disable-select" to={`/pieces/${ nav.prev_index || this.props.piece.id }`}>
<span className="glyphicon glyphicon-chevron-left pull-left link-ascribe" aria-hidden="true">
2015-08-24 11:22:44 +02:00
{getLangText('Previous')}
</span>
</Link>
<Link className="disable-select" to={`/pieces/${ nav.next_index || this.props.piece.id }`}>
<span className="pull-right link-ascribe">
2015-08-24 11:22:44 +02:00
{getLangText('Next')}
<span className="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
</span>
</Link>
</div>
2015-08-12 16:47:16 +02:00
</div>
);
}
return null;
2015-08-12 13:21:05 +02:00
}
});
2015-08-11 17:12:12 +02:00
let PrizePieceRatings = React.createClass({
propTypes: {
loadPiece: React.PropTypes.func,
2015-08-12 16:47:16 +02:00
piece: React.PropTypes.object,
currentUser: React.PropTypes.object
},
2015-08-10 15:23:13 +02:00
2015-08-10 17:02:10 +02:00
getInitialState() {
2015-08-11 17:12:12 +02:00
return mergeOptions(
PieceListStore.getState(),
PrizeRatingStore.getState()
2015-08-11 17:12:12 +02:00
);
2015-08-10 15:23:13 +02:00
},
2015-08-10 17:02:10 +02:00
componentDidMount() {
PrizeRatingStore.listen(this.onChange);
PrizeRatingActions.fetchOne(this.props.piece.id);
2015-08-24 15:33:36 +02:00
PrizeRatingActions.fetchAverage(this.props.piece.id);
2015-08-11 17:12:12 +02:00
PieceListStore.listen(this.onChange);
2015-08-10 17:02:10 +02:00
},
componentWillUnmount() {
// Every time we're leaving the piece detail page,
// just reset the piece that is saved in the piece store
// as it will otherwise display wrong/old data once the user loads
// the piece detail a second time
PrizeRatingActions.updateRating({});
PrizeRatingStore.unlisten(this.onChange);
2015-08-11 17:12:12 +02:00
PieceListStore.unlisten(this.onChange);
},
2015-08-24 11:22:44 +02:00
// The StarRating component does not have a property that lets us set
// a default value at initialization. Since the ratingCache would otherwise on
// every mouseover be overridden, we need to set it ourselves initially to deal
// with the problem.
2015-08-11 17:12:12 +02:00
onChange(state) {
this.setState(state);
if (this.refs.rating) {
this.refs.rating.state.ratingCache = {
pos: this.refs.rating.state.pos,
rating: this.state.currentRating,
caption: this.refs.rating.props.caption,
name: this.refs.rating.props.name
};
}
2015-08-10 17:02:10 +02:00
},
onRatingClick(event, args) {
event.preventDefault();
2015-08-11 17:12:12 +02:00
PrizeRatingActions.createRating(this.props.piece.id, args.rating).then(
this.refreshPieceData()
2015-08-11 17:12:12 +02:00
);
},
2015-08-24 11:22:44 +02:00
handleLoanRequestSuccess(message){
let notification = new GlobalNotificationModel(message, 'success', 4000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
2015-08-26 09:50:38 +02:00
getLoanButton(){
let today = new Moment();
let endDate = new Moment();
endDate.add(6, 'months');
return (
<ModalWrapper
trigger={
<button className='btn btn-default btn-sm'>
{getLangText('SEND LOAN REQUEST')}
2015-08-26 09:50:38 +02:00
</button>
}
handleSuccess={this.handleLoanRequestSuccess}
title='REQUEST LOAN'>
<LoanForm
loanHeading={null}
message={getLangText('Congratulations,\nYou have been selected for the prize.\n' +
'Please accept the loan request to proceed.')}
2015-08-26 09:50:38 +02:00
id={{piece_id: this.props.piece.id}}
url={ApiUrls.ownership_loans_pieces_request}
email={this.props.currentUser.email}
2015-08-26 09:50:38 +02:00
gallery={this.props.piece.prize.name}
startdate={today}
enddate={endDate}
showPersonalMessage={true}
showPassword={false}
handleSuccess={this.handleLoanSuccess} />
</ModalWrapper>);
},
2015-08-26 14:50:16 +02:00
handleShortlistSuccess(message){
let notification = new GlobalNotificationModel(message, 'success', 2000);
GlobalNotificationActions.appendGlobalNotification(notification);
},
2015-08-26 09:50:38 +02:00
refreshPieceData() {
this.props.loadPiece();
2015-08-26 14:50:16 +02:00
PieceListActions.fetchPieceList(this.state.page, this.state.pageSize, this.state.search,
this.state.orderBy, this.state.orderAsc, this.state.filterBy);
2015-08-24 15:33:36 +02:00
},
2015-08-27 10:10:39 +02:00
onSelectChange() {
PrizeRatingActions.toggleShortlist(this.props.piece.id)
.then(
(res) => {
this.refreshPieceData();
return res;
})
.then(
(res) => {
this.handleShortlistSuccess(res.notification);
}
);
},
2015-08-11 17:12:12 +02:00
render(){
if (this.props.piece && this.props.currentUser && this.props.currentUser.is_judge && this.state.average) {
2015-08-27 10:10:39 +02:00
// Judge sees shortlisting, average and per-jury notes
2015-08-24 15:33:36 +02:00
return (
<div>
<CollapsibleParagraph
2015-08-27 10:10:39 +02:00
title={getLangText('Shortlisting')}
defaultExpanded={true}>
<div className="row no-margin">
<span className="ascribe-checkbox-wrapper" style={{marginLeft: '1.5em'}}>
<InputCheckbox
defaultChecked={this.props.piece.selected}
2015-08-27 10:10:39 +02:00
onChange={this.onSelectChange}>
<span>
2015-08-27 10:10:39 +02:00
{getLangText('Select for the prize')}
</span>
</InputCheckbox>
</span>
<span className="pull-right">
2015-08-26 09:50:38 +02:00
{this.props.piece.selected ? this.getLoanButton() : null}
</span>
</div>
<hr />
</CollapsibleParagraph>
<CollapsibleParagraph
2015-08-27 10:10:39 +02:00
title={getLangText('Average Rating')}
defaultExpanded={true}>
2015-08-25 10:47:38 +02:00
<div id="list-rating" style={{marginLeft: '1.5em', marginBottom: '1em'}}>
<StarRating
ref='average-rating'
name="average-rating"
caption=""
size='md'
step={0.5}
rating={this.state.average}
ratingAmount={5}/>
2015-08-24 15:33:36 +02:00
</div>
<hr />
{this.state.ratings.map((item, i) => {
let note = item.note ?
<div className="rating-note">
note: {item.note}
</div> : null;
return (
<div className="rating-list">
<div id="list-rating" className="row no-margin">
<span className="pull-right">
<StarRating
ref={'rating' + i}
name={'rating' + i}
caption=""
size='sm'
step={0.5}
rating={item.rating}
ratingAmount={5}/>
</span>
<span> {item.user}</span>
{note}
</div>
2015-08-25 10:47:38 +02:00
</div>
);
})}
<hr />
</CollapsibleParagraph>
</div>);
2015-08-24 15:33:36 +02:00
}
else if (this.props.currentUser && this.props.currentUser.is_jury) {
2015-08-27 10:10:39 +02:00
// Jury can set rating and note
2015-08-12 13:21:05 +02:00
return (
<CollapsibleParagraph
2015-08-27 10:18:50 +02:00
title={getLangText('Rating')}
defaultExpanded={true}>
<div style={{marginLeft: '1.5em', marginBottom: '1em'}}>
2015-08-12 13:21:05 +02:00
<StarRating
ref='rating'
name="prize-rating"
caption=""
step={1}
size='md'
rating={this.state.currentRating}
onRatingClick={this.onRatingClick}
ratingAmount={5} />
</div>
2015-08-24 15:33:36 +02:00
<Note
id={() => {return {'piece_id': this.props.piece.id}; }}
2015-08-24 15:33:36 +02:00
label={getLangText('Jury note')}
defaultValue={this.props.piece && this.props.piece.note_from_user ? this.props.piece.note_from_user.note : null}
placeholder={getLangText('Enter your comments ...')}
editable={true}
successMessage={getLangText('Jury note saved')}
url={ApiUrls.notes}
2015-08-12 16:47:16 +02:00
currentUser={this.props.currentUser}/>
</CollapsibleParagraph>);
2015-08-12 13:21:05 +02:00
}
return null;
}
});
2015-08-11 17:12:12 +02:00
let PrizePieceDetails = React.createClass({
propTypes: {
piece: React.PropTypes.object
2015-08-10 17:02:10 +02:00
},
render() {
2015-08-24 11:22:44 +02:00
if (this.props.piece
&& this.props.piece.prize
2015-07-15 14:36:25 +02:00
&& this.props.piece.prize.name
&& Object.keys(this.props.piece.extra_data).length !== 0){
return (
<CollapsibleParagraph
2015-08-24 11:22:44 +02:00
title={getLangText('Prize Details')}
2015-07-15 14:36:25 +02:00
defaultExpanded={true}>
<Form ref='form'>
{Object.keys(this.props.piece.extra_data).map((data) => {
let label = data.replace('_', ' ');
return (
<Property
name={data}
label={label}
editable={false}
overrideForm={true}>
2015-07-15 14:36:25 +02:00
<InputTextAreaToggable
rows={1}
defaultValue={this.props.piece.extra_data[data]}/>
</Property>);
}
)}
<hr />
</Form>
</CollapsibleParagraph>
);
}
return null;
}
});
export default PieceContainer;