diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index e815dbc89..6bd46d5a5 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -354,3 +354,30 @@ class VoteEndDate extends CardDate { } } VoteEndDate.register('voteEndDate'); + +class PokerEndDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(moment(self.data().getPokerEnd())); + }); + } + classes() { + const classes = 'end-date' + ' '; + return classes; + } + showDate() { + return this.date.get().format('l LT'); + } + showTitle() { + return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`; + } + + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editPokerEndDate'), + }); + } +} +PokerEndDate.register('pokerEndDate'); diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 341d85eea..8edf34da0 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -243,6 +243,205 @@ template(name="cardDetails") i.fa.fa-thumbs-down | {{_ 'vote-against'}} + if getPokerQuestion + hr + .poker-title + div.flex + h3 + i.fa.fa-thumbs-up + | {{_ 'poker-question'}} + if getPokerEnd + +pokerEndDate + div.flex + .poker-result + if expiredPoker + unless ($and currentBoard.isPublic pokerAllowNonBoardMembers ) + .card-label.card-label-gray {{ pokerCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }} + if showPlanningPokerButtons + .poker-result + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-one(class="{{#if $eq pokerState 'one'}}poker-voted{{/if}}") {{_ 'poker-one'}} + if $eq pokerState "one" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-two(class="{{#if $eq pokerState 'two'}}poker-voted{{/if}}") {{_ 'poker-two'}} + if $eq pokerState "two" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-three(class="{{#if $eq pokerState 'three'}}poker-voted{{/if}}") {{_ 'poker-three'}} + if $eq pokerState "three" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-five(class="{{#if $eq pokerState 'five'}}poker-voted{{/if}}") {{_ 'poker-five'}} + if $eq pokerState "five" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-eight(class="{{#if $eq pokerState 'eight'}}poker-voted{{/if}}") {{_ 'poker-eight'}} + if $eq pokerState "eight" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-thirteen(class="{{#if $eq pokerState 'thirteen'}}poker-voted{{/if}}") {{_ 'poker-thirteen'}} + if $eq pokerState "thirteen" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-twenty(class="{{#if $eq pokerState 'twenty'}}poker-voted{{/if}}") {{_ 'poker-twenty'}} + if $eq pokerState "twenty" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-forty(class="{{#if $eq pokerState 'forty'}}poker-voted{{/if}}") {{_ 'poker-forty'}} + if $eq pokerState "forty" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-one-hundred(class="{{#if $eq pokerState 'oneHundred'}}poker-voted{{/if}}") {{_ 'poker-oneHundred'}} + if $eq pokerState "oneHundred" + i.fa.fa-check + .poker-deck + .poker-card + span.inner.js-poker.js-poker-vote-unsure(class="{{#if $eq pokerState 'unsure'}}poker-voted{{/if}}") {{_ 'poker-unsure'}} + if $eq pokerState "unsure" + i.fa.fa-check + + if currentUser.isBoardAdmin + button.card-details-blue.js-poker-finish(class="{{#if $eq voteState false}}poker-voted{{/if}}") {{_ 'poker-finish'}} + + if expiredPoker + .poker-table + .poker-table-side-left + .poker-table-heading-left + .poker-table-row + .poker-table-cell + .poker-table-cell + | {{_ 'poker-result-votes' }} + .poker-table-cell.poker-table-cell-who + | {{_ 'poker-result-who' }} + .poker-table-body + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 1}}winner{{else}}loser{{/if}}") {{_ 'poker-one'}} + .poker-table-cell {{ pokerCountOne }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberOne + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 2}}winner{{else}}loser{{/if}}") {{_ 'poker-two'}} + .poker-table-cell {{ pokerCountTwo }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberTwo + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 3}}winner{{else}}loser{{/if}}") {{_ 'poker-three'}} + .poker-table-cell {{ pokerCountThree }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberThree + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 5}}winner{{else}}loser{{/if}}") {{_ 'poker-five'}} + .poker-table-cell {{ pokerCountFive }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberFive + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 8}}winner{{else}}loser{{/if}}") {{_ 'poker-eight'}} + .poker-table-cell {{ pokerCountEight }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberEight + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-side-right + .poker-table-heading-right + .poker-table-row + .poker-table-cell + .poker-table-cell + | {{_ 'poker-result-votes' }} + .poker-table-cell.poker-table-cell-who + | {{_ 'poker-result-who' }} + .poker-table-body + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 13}}winner{{else}}loser{{/if}}") {{_ 'poker-thirteen'}} + .poker-table-cell {{ pokerCountThirteen }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberThirteen + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 20}}winner{{else}}loser{{/if}}") {{_ 'poker-twenty'}} + .poker-table-cell {{ pokerCountTwenty }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberTwenty + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 40}}winner{{else}}loser{{/if}}") {{_ 'poker-forty'}} + .poker-table-cell {{ pokerCountForty }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberForty + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 100}}winner{{else}}loser{{/if}}") {{_ 'poker-oneHundred'}} + .poker-table-cell {{ pokerCountOneHundred }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberOneHundred + a.name + +userAvatar(userId=m._id noRemove=true) + + .poker-table-row + .poker-table-cell + button.card-details-gray.js-poker.poker-card-result(class="{{#if $eq pokerWinner 'unsure'}}winner{{else}}loser{{/if}}") {{_ 'poker-unsure'}} + .poker-table-cell {{ pokerCountUnsure }} + .poker-table-cell.poker-table-cell-who + .poker-result + each m in pokerMemberUnsure + a.name + +userAvatar(userId=m._id noRemove=true) + + if currentUser.isBoardAdmin + div.estimation-add + button.card-details-red.js-poker-replay(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'poker-replay'}} + div.estimation-add + button.js-poker-estimation + i.fa.fa-plus + | {{_ 'set-estimation'}} + input(type=text,autofocus value=getPokerEstimation,id="pokerEstimation") + //- XXX We should use "editable" to avoid repetiting ourselves if canModifyCard unless currentUser.isWorker @@ -362,6 +561,10 @@ template(name="cardDetailsActionsPopup") a.js-start-voting i.fa.fa-thumbs-up | {{_ 'card-edit-voting'}} + li + a.js-start-planning-poker + i.fa.fa-thumbs-up + | {{_ 'card-edit-planning-poker'}} if currentUser.isBoardAdmin li a.js-custom-fields @@ -621,3 +824,29 @@ template(name="negativeVoteMembersPopup") span.full-name = m.profile.fullname | ({{ m.username }}) + +template(name="deletePokerPopup") + p {{_ "poker-delete-pop"}} + button.js-confirm.negate.full(type="submit") {{_ 'delete'}} + +template(name="cardStartPlanningPokerPopup") + form.edit-poker-question + .fields + .check-div + a.flex(class="{{#if getPokerQuestion}}is-disabled{{else}}js-toggle-poker-allow-non-members{{/if}}") + .materialCheckBox#poker-allow-non-members(name="poker-allow-non-members" class="{{#if pokerAllowNonBoardMembers}}is-checked{{/if}}") + span {{_ 'allowNonBoardMembers'}} + .check-div.flex + i.fa.fa-hourglass-end + a.js-end-date + span + | {{_ 'card-end'}} + unless getPokerEnd + i.fa.fa-plus + if getPokerEnd + +pokerEndDate + + button.primary.js-submit {{_ 'save'}} + if getPokerQuestion + if currentUser.isBoardAdmin + button.js-remove-poker.negate.wide.right {{_ 'delete'}} diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 6abd8b2dc..a973df075 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -140,6 +140,15 @@ BlazeComponent.extendComponent({ ); }, + showPlanningPokerButtons() { + const card = this.currentData(); + return ( + (currentUser.isBoardMember() || + (currentUser && card.pokerAllowNonBoardMembers())) && + !card.expiredPoker() + ); + }, + onRendered() { if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) { // Send Webhook but not create Activities records --- @@ -407,6 +416,80 @@ BlazeComponent.extendComponent({ } this.data().setVote(Meteor.userId(), newState); }, + 'click .js-poker'(e) { + let newState = null; + if ($(e.target).hasClass('js-poker-vote-one')) { + newState = 'one'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-two')) { + newState = 'two'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-three')) { + newState = 'three'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-five')) { + newState = 'five'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-eight')) { + newState = 'eight'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-thirteen')) { + newState = 'thirteen'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-twenty')) { + newState = 'twenty'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-forty')) { + newState = 'forty'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-one-hundred')) { + newState = 'oneHundred'; + this.data().setPoker(Meteor.userId(), newState); + } + if ($(e.target).hasClass('js-poker-vote-unsure')) { + newState = 'unsure'; + this.data().setPoker(Meteor.userId(), newState); + } + }, + 'click .js-poker-finish'(e) { + if ($(e.target).hasClass('js-poker-finish')) { + e.preventDefault(); + const now = moment().format('YYYY-MM-DD HH:mm'); + this.data().setPokerEnd(now); + } + }, + + 'click .js-poker-replay'(e) { + if ($(e.target).hasClass('js-poker-replay')) { + e.preventDefault(); + this.currentCard = this.currentData(); + this.currentCard.replayPoker(); + this.data().unsetPokerEnd(); + this.data().unsetPokerEstimation(); + } + }, + 'click .js-poker-estimation'(event) { + event.preventDefault(); + + const ruleTitle = this.find('#pokerEstimation').value; + if (ruleTitle !== undefined && ruleTitle !== '') { + this.find('#pokerEstimation').value = ''; + + if (ruleTitle) { + this.data().setPokerEstimation(parseInt(ruleTitle, 10)); + } else { + this.data().setPokerEstimation(''); + } + } + }, }, ]; }, @@ -477,6 +560,7 @@ Template.cardDetailsActionsPopup.events({ 'click .js-labels': Popup.open('cardLabels'), 'click .js-attachments': Popup.open('cardAttachments'), 'click .js-start-voting': Popup.open('cardStartVoting'), + 'click .js-start-planning-poker': Popup.open('cardStartPlanningPoker'), 'click .js-custom-fields': Popup.open('cardCustomFields'), 'click .js-received-date': Popup.open('editCardReceivedDate'), 'click .js-start-date': Popup.open('editCardStartDate'), @@ -976,6 +1060,96 @@ BlazeComponent.extendComponent({ } }.register('editVoteEndDatePopup')); +BlazeComponent.extendComponent({ + onCreated() { + this.currentCard = this.currentData(); + this.pokerQuestion = new ReactiveVar(this.currentCard.pokerQuestion); + }, + + events() { + return [ + { + 'click .js-end-date': Popup.open('editPokerEndDate'), + 'submit .edit-poker-question'(evt) { + evt.preventDefault(); + const pokerQuestion = true; + const allowNonBoardMembers = $('#poker-allow-non-members').hasClass( + 'is-checked', + ); + const endString = this.currentCard.getPokerEnd(); + + this.currentCard.setPokerQuestion( + pokerQuestion, + allowNonBoardMembers, + ); + if (endString) { + this.currentCard.setPokerEnd(endString); + } + Popup.close(); + }, + 'click .js-remove-poker': Popup.afterConfirm('deletePoker', () => { + event.preventDefault(); + this.currentCard.unsetPoker(); + Popup.close(); + }), + 'click a.js-toggle-poker-allow-non-members'(event) { + event.preventDefault(); + $('#poker-allow-non-members').toggleClass('is-checked'); + }, + }, + ]; + }, +}).register('cardStartPlanningPokerPopup'); + +// editPokerEndDatePopup +(class extends DatePicker { + onCreated() { + super.onCreated(moment().format('YYYY-MM-DD HH:mm')); + this.data().getPokerEnd() && this.date.set(moment(this.data().getPokerEnd())); + } + events() { + return [ + { + 'submit .edit-date'(evt) { + evt.preventDefault(); + + // if no time was given, init with 12:00 + const time = + evt.target.time.value || + moment(new Date().setHours(12, 0, 0)).format('LT'); + + const dateString = `${evt.target.date.value} ${time}`; + const newDate = moment(dateString, 'L LT', true); + if (newDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(newDate.toDate()); + Popup.close(); + } else { + this.currentData().poker = { end: newDate.toDate() }; // set poker end temp + Popup.back(); + } + } else { + this.error.set('invalid-date'); + evt.target.date.focus(); + } + }, + 'click .js-delete-date'(evt) { + evt.preventDefault(); + this._deleteDate(); + Popup.close(); + }, + }, + ]; + } + _storeDate(newDate) { + this.card.setPokerEnd(newDate); + } + _deleteDate() { + this.card.unsetPokerEnd(); + } +}.register('editPokerEndDatePopup')); + // Close the card details pane by pressing escape EscapeActions.register( 'detailsPane', diff --git a/client/components/cards/cardDetails.styl b/client/components/cards/cardDetails.styl index 5e57d49bc..fe5c50bf6 100644 --- a/client/components/cards/cardDetails.styl +++ b/client/components/cards/cardDetails.styl @@ -357,3 +357,131 @@ card-details-color(background, color...) display: flex .js-show-positive-votes cursor: pointer + +.poker-voted + opacity: .7 + +.poker-title + display: flex + justify-content: space-between + + .js-edit-date + align-self: baseline + margin-left: 5px + +.poker-result + display: flex + flex-flow: row wrap +.js-show-positive-poker-votes + cursor: pointer + +.poker-deck + display: grid + flex-direction: column + text-align: center + +.poker-card-result + width: 32px + font-size: 1em + font-weight: bold + padding: 4px 2px 4px 2px + cursor: default + +.winner + font-weight: bold + outline: #2d2d2d solid 2px + +.loser + opacity: .5 + +.responsive-table + overflow-x: auto + +.poker-table + display: table + width: 100% + padding-top: 10px + +.poker-table-row + display: table-row + +.poker-table-heading + background-color: #EEE + display: table-header-group + +.poker-table-cell + display: table-cell + padding: 0 0 5px 2px + border-bottom: 1px solid #d2d0d0 + text-align: center + min-width: 45px + +.poker-table-cell-who + width: 150px + vertical-align: middle + +.poker-table-heading-left, +.poker-table-heading-right + display: table-header-group + font-weight: bold + border-top: 1px solid #808080 + +@media (max-width: 400px) + .poker-table-heading-right + display: none + +.poker-table-body + display: table-row-group + +.poker-table-side-left, +.poker-table-side-right + display: inline-block + +.poker-table-side-right + padding-left: 10px + +@media (max-width: 400px) + .poker-table-side-right + padding-left: 0px + +.estimation-add + display: block + overflow: auto + margin-top: 15px + margin-bottom: 5px + input + display: inline-block + float: right + margin: auto + margin-right: 10px + width: 100px + button + display: inline-block + float: right + margin: auto + +.poker-card + width:48px + height:72px + float:left + background:#fff + border-radius:5px + display:table + box-sizing:border-box + padding:5px + margin:3px + font-size:20px + font-weight: bold + text-shadow: #2d2d2d 1px 1px 0 + box-shadow:0 0 5px #aaaaaa + text-align:center + position:relative + cursor: pointer + + .inner + display:table-cell + vertical-align:middle + border-radius:5px + overflow:hidden + background-color: #cecece + diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index eab49a011..60d240a7c 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -121,6 +121,11 @@ template(name="minicard") span.badge-text {{ voteCountPositive }} span.badge-icon.fa.fa-thumbs-down(class="{{#if $eq voteState false}}text-red{{/if}}") span.badge-text {{ voteCountNegative }} + if getPokerQuestion + .badge.badge-state-image-only(title=getPokerQuestion) + span.badge-icon.fa.fa-check(class="{{#if pokerState}}text-green{{/if}}") + if expiredPoker + span.badge-text {{ getPokerEstimation }} if attachments.count .badge span.badge-icon.fa.fa-paperclip diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 14fb076a0..97d45d460 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -178,6 +178,27 @@ "vote-against": "against", "deleteVotePopup-title": "Delete vote?", "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", + "cardStartPlanningPokerPopup-title": "Start a Planning Poker", + "card-edit-planning-poker": "Edit Planning Poker", + "editPokerEndDatePopup-title": "Change Planning Poker vote end date", + "poker-question": "Planning Poker", + "poker-one": "1", + "poker-two": "2", + "poker-three": "3", + "poker-five": "5", + "poker-eight": "8", + "poker-thirteen": "13", + "poker-twenty": "20", + "poker-forty": "40", + "poker-oneHundred": "100", + "poker-unsure": "?", + "poker-finish": "Finish", + "poker-result-votes": "Votes", + "poker-result-who": "Who", + "poker-replay": "Replay", + "set-estimation": "Set Estimation", + "deletePokerPopup-title": "Delete planning poker?", + "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", "cardDeletePopup-title": "Delete Card?", "cardDetailsActionsPopup-title": "Card Actions", "cardLabelsPopup-title": "Labels", @@ -186,6 +207,7 @@ "cardTemplatePopup-title": "Create template", "cards": "Cards", "cards-count": "Cards", + "cards-count-one": "Card", "casSignIn": "Sign In with CAS", "cardType-card": "Card", "cardType-linkedCard": "Linked Card", diff --git a/models/cards.js b/models/cards.js index b72293cfa..54732897e 100644 --- a/models/cards.js +++ b/models/cards.js @@ -338,6 +338,113 @@ Cards.attachSchema( type: Boolean, defaultValue: false, }, + poker: { + /** + * poker object, see below + */ + type: Object, + optional: true, + }, + 'poker.question': { + type: Boolean, + defaultValue: false, + }, + 'poker.one': { + /** + * poker card one + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.two': { + /** + * poker card two + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.three': { + /** + * poker card three + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.five': { + /** + * poker card five + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.eight': { + /** + * poker card eight + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.thirteen': { + /** + * poker card thirteen + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.twenty': { + /** + * poker card twenty + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.forty': { + /** + * poker card forty + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.oneHundred': { + /** + * poker card oneHundred + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.unsure': { + /** + * poker card unsure + */ + type: [String], + optional: true, + defaultValue: [], + }, + 'poker.end': { + type: Date, + optional: true, + defaultValue: null, + }, + 'poker.allowNonBoardMembers': { + type: Boolean, + defaultValue: false, + }, + 'poker.estimation': { + /** + * poker estimation value + */ + type: Number, + optional: true, + }, }), ); @@ -1279,6 +1386,191 @@ Cards.helpers({ return null; }, + getPokerQuestion() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card === undefined) { + return null; + } else if (card && card.poker) { + return card.poker.question; + } else { + return null; + } + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board === undefined) { + return null; + } else if (board && board.poker) { + return board.poker.question; + } else { + return null; + } + } else if (this.poker) { + return this.poker.question; + } else { + return null; + } + }, + + getPokerEstimation() { + if (this.poker) { + return this.poker.estimation; + } else { + return null; + } + }, + + getPokerEnd() { + if (this.isLinkedCard()) { + const card = Cards.findOne({ _id: this.linkedId }); + if (card === undefined) { + return null; + } else if (card && card.poker) { + return card.poker.end; + } else { + return null; + } + } else if (this.isLinkedBoard()) { + const board = Boards.findOne({ _id: this.linkedId }); + if (board === undefined) { + return null; + } else if (board && board.poker) { + return board.poker.end; + } else { + return null; + } + } else if (this.poker) { + return this.poker.end; + } else { + return null; + } + }, + expiredPoker() { + let end = this.getPokerEnd(); + if (end) { + end = moment(end); + return end.isBefore(new Date()); + } + return false; + }, + pokerMemberOne() { + if (this.poker && this.poker.one) + return Users.find({ _id: { $in: this.poker.one } }); + return []; + }, + pokerMemberTwo() { + if (this.poker && this.poker.two) + return Users.find({ _id: { $in: this.poker.two } }); + return []; + }, + pokerMemberThree() { + if (this.poker && this.poker.three) + return Users.find({ _id: { $in: this.poker.three } }); + return []; + }, + pokerMemberFive() { + if (this.poker && this.poker.five) + return Users.find({ _id: { $in: this.poker.five } }); + return []; + }, + pokerMemberEight() { + if (this.poker && this.poker.eight) + return Users.find({ _id: { $in: this.poker.eight } }); + return []; + }, + pokerMemberThirteen() { + if (this.poker && this.poker.thirteen) + return Users.find({ _id: { $in: this.poker.thirteen } }); + return []; + }, + pokerMemberTwenty() { + if (this.poker && this.poker.twenty) + return Users.find({ _id: { $in: this.poker.twenty } }); + return []; + }, + pokerMemberForty() { + if (this.poker && this.poker.forty) + return Users.find({ _id: { $in: this.poker.forty } }); + return []; + }, + pokerMemberOneHundred() { + if (this.poker && this.poker.oneHundred) + return Users.find({ _id: { $in: this.poker.oneHundred } }); + return []; + }, + pokerMemberUnsure() { + if (this.poker && this.poker.unsure) + return Users.find({ _id: { $in: this.poker.unsure } }); + return []; + }, + pokerState() { + const userId = Meteor.userId(); + let state; + if (this.poker) { + if (this.poker.one) { + state = _.contains(this.poker.one, userId); + if (state === true) { + return 'one'; + } + } + if (this.poker.two) { + state = _.contains(this.poker.two, userId); + if (state === true) { + return 'two'; + } + } + if (this.poker.three) { + state = _.contains(this.poker.three, userId); + if (state === true) { + return 'three'; + } + } + if (this.poker.five) { + state = _.contains(this.poker.five, userId); + if (state === true) { + return 'five'; + } + } + if (this.poker.eight) { + state = _.contains(this.poker.eight, userId); + if (state === true) { + return 'eight'; + } + } + if (this.poker.thirteen) { + state = _.contains(this.poker.thirteen, userId); + if (state === true) { + return 'thirteen'; + } + } + if (this.poker.twenty) { + state = _.contains(this.poker.twenty, userId); + if (state === true) { + return 'twenty'; + } + } + if (this.poker.forty) { + state = _.contains(this.poker.forty, userId); + if (state === true) { + return 'forty'; + } + } + if (this.poker.oneHundred) { + state = _.contains(this.poker.oneHundred, userId); + if (state === true) { + return 'oneHundred'; + } + } + if (this.poker.unsure) { + state = _.contains(this.poker.unsure, userId); + if (state === true) { + return 'unsure'; + } + } + } + return null; + }, + getId() { if (this.isLinked()) { return this.linkedId; @@ -1433,6 +1725,101 @@ Cards.helpers({ voteCount() { return this.voteCountPositive() + this.voteCountNegative(); }, + + pokerAllowNonBoardMembers() { + if (this.poker) return this.poker.allowNonBoardMembers; + return null; + }, + pokerCountOne() { + if (this.poker && this.poker.one) return this.poker.one.length; + return null; + }, + pokerCountTwo() { + if (this.poker && this.poker.two) return this.poker.two.length; + return null; + }, + pokerCountThree() { + if (this.poker && this.poker.three) return this.poker.three.length; + return null; + }, + pokerCountFive() { + if (this.poker && this.poker.five) return this.poker.five.length; + return null; + }, + pokerCountEight() { + if (this.poker && this.poker.eight) return this.poker.eight.length; + return null; + }, + pokerCountThirteen() { + if (this.poker && this.poker.thirteen) return this.poker.thirteen.length; + return null; + }, + pokerCountTwenty() { + if (this.poker && this.poker.twenty) return this.poker.twenty.length; + return null; + }, + pokerCountForty() { + if (this.poker && this.poker.forty) return this.poker.forty.length; + return null; + }, + pokerCountOneHundred() { + if (this.poker && this.poker.oneHundred) return this.poker.oneHundred.length; + return null; + }, + pokerCountUnsure() { + if (this.poker && this.poker.unsure) return this.poker.unsure.length; + return null; + }, + pokerCount() { + return ( + this.pokerCountOne() + + this.pokerCountTwo() + + this.pokerCountThree() + + this.pokerCountFive() + + this.pokerCountEight() + + this.pokerCountThirteen() + + this.pokerCountTwenty() + + this.pokerCountForty() + + this.pokerCountOneHundred() + + this.pokerCountUnsure() + ); + }, + pokerWinner() { + const pokerListMaps = []; + let pokerWinnersListMap = []; + if (this.expiredPoker()) { + const one = { count: this.pokerCountOne(), pokerCard: 1 }; + const two = { count: this.pokerCountTwo(), pokerCard: 2 }; + const three = { count: this.pokerCountThree(), pokerCard: 3 }; + const five = { count: this.pokerCountFive(), pokerCard: 5 }; + const eight = { count: this.pokerCountEight(), pokerCard: 8 }; + const thirteen = { count: this.pokerCountThirteen(), pokerCard: 13 }; + const twenty = { count: this.pokerCountTwenty(), pokerCard: 20 }; + const forty = { count: this.pokerCountForty(), pokerCard: 40 }; + const oneHundred = { count: this.pokerCountOneHundred(), pokerCard: 100 }; + const unsure = { count: this.pokerCountUnsure(), pokerCard: 'Unsure' }; + pokerListMaps.push(one); + pokerListMaps.push(two); + pokerListMaps.push(three); + pokerListMaps.push(five); + pokerListMaps.push(eight); + pokerListMaps.push(thirteen); + pokerListMaps.push(twenty); + pokerListMaps.push(forty); + pokerListMaps.push(oneHundred); + pokerListMaps.push(unsure); + + pokerListMaps.sort(function(a, b) { + return b.count - a.count; + }); + const max = pokerListMaps[0].count; + pokerWinnersListMap = pokerListMaps.filter(task => task.count >= max); + pokerWinnersListMap.sort(function(a, b) { + return b.pokerCard - a.pokerCard; + }); + } + return pokerWinnersListMap[0].pokerCard; + }, }); Cards.mutations({ @@ -1870,6 +2257,279 @@ Cards.mutations({ }; } }, + + setPokerQuestion(question, allowNonBoardMembers) { + return { + $set: { + poker: { + question, + allowNonBoardMembers, + one: [], + two: [], + three: [], + five: [], + eight: [], + thirteen: [], + twenty: [], + forty: [], + oneHundred: [], + unsure: [], + }, + }, + }; + }, + setPokerEstimation(estimation) { + return { + $set: { 'poker.estimation': estimation }, + }; + }, + unsetPokerEstimation() { + return { + $unset: { 'poker.estimation': '' }, + }; + }, + unsetPoker() { + return { + $unset: { + poker: '', + }, + }; + }, + setPokerEnd(end) { + return { + $set: { 'poker.end': end }, + }; + }, + unsetPokerEnd() { + return { + $unset: { 'poker.end': '' }, + }; + }, + setPoker(userId, state) { + switch (state) { + case 'one': + // poker one + return { + $pull: { + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.one': userId, + }, + }; + case 'two': + // poker two + return { + $pull: { + 'poker.one': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.two': userId, + }, + }; + + case 'three': + // poker three + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.three': userId, + }, + }; + + case 'five': + // poker five + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.five': userId, + }, + }; + + case 'eight': + // poker eight + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.eight': userId, + }, + }; + + case 'thirteen': + // poker thirteen + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.thirteen': userId, + }, + }; + + case 'twenty': + // poker twenty + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.twenty': userId, + }, + }; + + case 'forty': + // poker forty + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.forty': userId, + }, + }; + + case 'oneHundred': + // poker one hundred + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.oneHundred': userId, + }, + }; + + case 'unsure': + // poker unsure + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + }, + $addToSet: { + 'poker.unsure': userId, + }, + }; + + default: + // Remove pokers + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + }; + } + }, + replayPoker() { + return { + $set: { + 'poker.one': [], + 'poker.two': [], + 'poker.three': [], + 'poker.five': [], + 'poker.eight': [], + 'poker.thirteen': [], + 'poker.twelve': [], + 'poker.forty': [], + 'poker.oneHundred': [], + 'poker.unsure': [], + }, + }; + }, }); //FUNCTIONS FOR creation of Activities @@ -2593,6 +3253,9 @@ if (Meteor.isServer) { * @param {string} vote.question the vote question * @param {boolean} vote.public show who voted what * @param {boolean} vote.allowNonBoardMembers allow all logged in users to vote? + * @param {Object} [poker] the poker object + * @param {string} poker.question the vote question + * @param {boolean} poker.allowNonBoardMembers allow all logged in users to vote? * @return_type {_id: string} */ JsonRoutes.add( @@ -2698,6 +3361,31 @@ if (Meteor.isServer) { { $set: { vote: newVote } }, ); } + if (req.body.hasOwnProperty('poker')) { + const newPoker = req.body.poker; + newPoker.one = []; + newPoker.two = []; + newPoker.three = []; + newPoker.five = []; + newPoker.eight = []; + newPoker.thirteen = []; + newPoker.twenty = []; + newPoker.forty = []; + newPoker.oneHundred = []; + newPoker.unsure = []; + if (!newPoker.hasOwnProperty('allowNonBoardMembers')) + newPoker.allowNonBoardMembers = false; + + Cards.direct.update( + { + _id: paramCardId, + listId: paramListId, + boardId: paramBoardId, + archived: false, + }, + { $set: { poker: newPoker } }, + ); + } if (req.body.hasOwnProperty('labelIds')) { let newlabelIds = req.body.labelIds; if (_.isString(newlabelIds)) {