From eec3c301bc7b0f29d7a7fcfcf59d330ceb604985 Mon Sep 17 00:00:00 2001 From: Thuan Pham Quoc Date: Mon, 20 Nov 2017 22:26:31 +0700 Subject: [PATCH 1/3] Add card spent time to log time what can be overtime or not (will support filtering in future) --- client/components/cards/cardDetails.jade | 9 +++ client/components/cards/cardDetails.js | 1 + client/components/cards/cardTime.jade | 22 +++++++ client/components/cards/cardTime.js | 77 ++++++++++++++++++++++++ client/components/cards/cardTime.styl | 17 ++++++ client/components/cards/minicard.jade | 12 ++-- i18n/en.i18n.json | 6 ++ models/cards.js | 26 +++++++- 8 files changed, 164 insertions(+), 6 deletions(-) create mode 100644 client/components/cards/cardTime.jade create mode 100644 client/components/cards/cardTime.js create mode 100644 client/components/cards/cardTime.styl diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index b65722517..c12559332 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -46,6 +46,14 @@ template(name="cardDetails") h3.card-details-item-title {{_ 'card-due'}} +cardDueDate + .card-details-items + if spentTime + .card-details-item.card-details-item-spent + if isOvertime + h3.card-details-item-title {{_ 'overtime-hours'}} + else + h3.card-details-item-title {{_ 'spent-time-hours'}} + +cardSpentTime //- XXX We should use "editable" to avoid repetiting ourselves if canModifyCard @@ -119,6 +127,7 @@ template(name="cardDetailsActionsPopup") li: a.js-attachments {{_ 'card-edit-attachments'}} li: a.js-start-date {{_ 'editCardStartDatePopup-title'}} li: a.js-due-date {{_ 'editCardDueDatePopup-title'}} + li: a.js-spent-time {{_ 'editCardSpentTimePopup-title'}} hr ul.pop-over-list li: a.js-move-card-to-top {{_ 'moveCardToTop-title'}} diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 3825bda86..800381c9c 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -163,6 +163,7 @@ Template.cardDetailsActionsPopup.events({ 'click .js-attachments': Popup.open('cardAttachments'), 'click .js-start-date': Popup.open('editCardStartDate'), 'click .js-due-date': Popup.open('editCardDueDate'), + 'click .js-spent-time': Popup.open('editCardSpentTime'), 'click .js-move-card': Popup.open('moveCard'), 'click .js-copy-card': Popup.open('copyCard'), 'click .js-move-card-to-top' (evt) { diff --git a/client/components/cards/cardTime.jade b/client/components/cards/cardTime.jade new file mode 100644 index 000000000..dcfc92f01 --- /dev/null +++ b/client/components/cards/cardTime.jade @@ -0,0 +1,22 @@ +template(name="editCardSpentTime") + .edit-card-time + form.edit-time + .fields + label(for="time") {{_ 'time'}} + input.js-time-field#time(type="number" step="0.01" name="time" value="{{card.spentTime}}" placeholder=timeFormat autofocus) + label(for="overtime") {{_ 'overtime'}} + a.js-toggle-overtime + .materialCheckBox#overtime(class="{{#if card.isOvertime}}is-checked{{/if}}" name="overtime") + + if error.get + .warning {{_ error.get}} + button.primary.wide.left.js-submit-time(type="submit") {{_ 'save'}} + button.js-delete-time.negate.wide.right {{_ 'delete'}} + +template(name="timeBadge") + if canModifyCard + a.js-edit-time.card-time(title="{{showTitle}}" class="{{#if isOvertime}}card-label-red{{else}}card-label-green{{/if}}") + | {{showTime}} + else + a.card-time(title="{{showTitle}}" class="{{#if isOvertime}}card-label-red{{else}}card-label-green{{/if}}") + | {{showTime}} diff --git a/client/components/cards/cardTime.js b/client/components/cards/cardTime.js new file mode 100644 index 000000000..233316681 --- /dev/null +++ b/client/components/cards/cardTime.js @@ -0,0 +1,77 @@ +BlazeComponent.extendComponent({ + template() { + return 'editCardSpentTime'; + }, + onCreated() { + this.error = new ReactiveVar(''); + this.card = this.data(); + }, + toggleOvertime() { + this.card.isOvertime = !this.card.isOvertime; + $('#overtime .materialCheckBox').toggleClass('is-checked'); + + $('#overtime').toggleClass('is-checked'); + }, + storeTime(spentTime, isOvertime) { + this.card.setSpentTime(spentTime); + this.card.setOvertime(isOvertime); + }, + deleteTime() { + this.card.unsetSpentTime(); + }, + events() { + return [{ + //TODO : need checking this portion + 'submit .edit-time'(evt) { + evt.preventDefault(); + + const spentTime = parseFloat(evt.target.time.value); + const isOvertime = this.card.isOvertime; + + if (spentTime >= 0) { + this.storeTime(spentTime, isOvertime); + Popup.close(); + } else { + this.error.set('invalid-time'); + evt.target.time.focus(); + } + }, + 'click .js-delete-time'(evt) { + evt.preventDefault(); + this.deleteTime(); + Popup.close(); + }, + 'click a.js-toggle-overtime': this.toggleOvertime, + }]; + }, +}).register('editCardSpentTimePopup'); + +BlazeComponent.extendComponent({ + template() { + return 'timeBadge'; + }, + onCreated() { + const self = this; + self.time = ReactiveVar(); + }, + showTitle() { + return `${TAPi18n.__('card-spent')} ${this.data().spentTime}`; + }, + showTime() { + return this.data().spentTime; + }, + isOvertime() { + return this.data().isOvertime; + }, + events() { + return [{ + 'click .js-edit-time': Popup.open('editCardSpentTime'), + }]; + }, +}).register('cardSpentTime'); + +Template.timeBadge.helpers({ + canModifyCard() { + return Meteor.user() && Meteor.user().isBoardMember() && !Meteor.user().isCommentOnly(); + }, +}); diff --git a/client/components/cards/cardTime.styl b/client/components/cards/cardTime.styl new file mode 100644 index 000000000..3c4b43aed --- /dev/null +++ b/client/components/cards/cardTime.styl @@ -0,0 +1,17 @@ +.card-time + display: block + border-radius: 4px + padding: 1px 3px + color: #fff + + background-color: #dbdbdb + &:hover, &.is-active + background-color: #b3b3b3 + + time + &::before + font: normal normal normal 14px/1 FontAwesome + font-size: inherit + -webkit-font-smoothing: antialiased + content: "\f017" // clock symbol + margin-right: 0.3em diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index 3e582b6f0..9fa4dd57e 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -11,11 +11,15 @@ template(name="minicard") = title .dates if startAt - .date - +minicardStartDate + .date + +minicardStartDate if dueAt - .date - +minicardDueDate + .date + +minicardDueDate + if spentTime + .date + +cardSpentTime + if members .minicard-members.js-minicard-members each members diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 5ec6a5f05..8c7dfad60 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -103,6 +103,7 @@ "card-delete-suggest-archive": "You can archive a card to remove it from the board and preserve the activity.", "card-due": "Due", "card-due-on": "Due on", + "card-spent": "Spent Time", "card-edit-attachments": "Edit attachments", "card-edit-labels": "Edit labels", "card-edit-members": "Edit members", @@ -175,6 +176,7 @@ "soft-wip-limit": "Soft WIP Limit", "editCardStartDatePopup-title": "Change start date", "editCardDueDatePopup-title": "Change due date", + "editCardSpentTimePopup-title": "Change spent time", "editLabelPopup-title": "Change Label", "editNotificationPopup-title": "Edit Notification", "editProfilePopup-title": "Edit Profile", @@ -236,6 +238,7 @@ "info": "Version", "initials": "Initials", "invalid-date": "Invalid date", + "invalid-time": "Invalid time", "joined": "joined", "just-invited": "You are just invited to this board", "keyboard-shortcuts": "Keyboard shortcuts", @@ -337,6 +340,9 @@ "team": "Team", "this-board": "this board", "this-card": "this card", + "spent-time-hours": "Spent time (hours)", + "overtime-hours": "Overtime (hours)", + "overtime": "Overtime", "time": "Time", "title": "Title", "tracking": "Tracking", diff --git a/models/cards.js b/models/cards.js index 5de17c6f3..56c3908f0 100644 --- a/models/cards.js +++ b/models/cards.js @@ -64,8 +64,18 @@ Cards.attachSchema(new SimpleSchema({ type: Date, optional: true, }, - // XXX Should probably be called `authorId`. Is it even needed since we have - // the `members` field? + spentTime: { + type: Number, + decimal: true, + optional: true, + }, + isOvertime: { + type: Boolean, + defaultValue: false, + optional: true, + }, + // XXX Should probably be called `authorId`. Is it even needed since we have + // the `members` field? userId: { type: String, autoValue() { // eslint-disable-line consistent-return @@ -269,6 +279,18 @@ Cards.mutations({ unsetDue() { return {$unset: {dueAt: ''}}; }, + + setOvertime(isOvertime) { + return {$set: {isOvertime}}; + }, + + setSpentTime(spentTime) { + return {$set: {spentTime}}; + }, + + unsetSpentTime() { + return {$unset: {spentTime: '', isOvertime: false}}; + }, }); From d38071457ce1ae722d025216fb2bf6ba958697ac Mon Sep 17 00:00:00 2001 From: Thuan Pham Quoc Date: Mon, 20 Nov 2017 22:40:02 +0700 Subject: [PATCH 2/3] Update spent time title to indicate Overtime or normal Spent time --- client/components/cards/cardTime.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/components/cards/cardTime.js b/client/components/cards/cardTime.js index 233316681..eadcc88e1 100644 --- a/client/components/cards/cardTime.js +++ b/client/components/cards/cardTime.js @@ -55,7 +55,11 @@ BlazeComponent.extendComponent({ self.time = ReactiveVar(); }, showTitle() { - return `${TAPi18n.__('card-spent')} ${this.data().spentTime}`; + if (this.data().isOvertime) { + return `${TAPi18n.__('overtime')} ${this.data().spentTime} ${TAPi18n.__('hours')}`; + } else { + return `${TAPi18n.__('card-spent')} ${this.data().spentTime} ${TAPi18n.__('hours')}`; + } }, showTime() { return this.data().spentTime; From 6dba4ccd4d0c8d7443e7d9c39ddafed2b8f1b6ca Mon Sep 17 00:00:00 2001 From: Thuan Pham Quoc Date: Mon, 20 Nov 2017 23:24:27 +0700 Subject: [PATCH 3/3] Added red-green circle to board lists item for indicating board which has overtime logs or normal spent time log --- client/components/boards/boardsList.jade | 6 ++++++ client/components/boards/boardsList.js | 12 ++++++++++++ client/components/boards/boardsList.styl | 17 +++++++++++++++++ i18n/en.i18n.json | 2 ++ models/boards.js | 10 ++++++++++ 5 files changed, 47 insertions(+) diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index ae82dfa97..95ce36781 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -20,6 +20,12 @@ template(name="boardList") i.fa.js-star-board( class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" title="{{_ 'star-board-title'}}") + + if hasSpentTimeCards + i.fa.js-has-spenttime-cards( + class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" + title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") + p.board-list-item-desc= description li.js-add-board a.board-list-item.label {{_ 'add-board'}} diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index e4bb050e9..4ec4b534b 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,3 +1,5 @@ +const subManager = new SubsManager(); + BlazeComponent.extendComponent({ boards() { return Boards.find({ @@ -13,6 +15,16 @@ BlazeComponent.extendComponent({ return user && user.hasStarred(this.currentData()._id); }, + hasOvertimeCards() { + subManager.subscribe('board', this.currentData()._id); + return this.currentData().hasOvertimeCards(); + }, + + hasSpentTimeCards() { + subManager.subscribe('board', this.currentData()._id); + return this.currentData().hasSpentTimeCards(); + }, + isInvited() { const user = Meteor.user(); return user && user.isInvitedTo(this.currentData()._id); diff --git a/client/components/boards/boardsList.styl b/client/components/boards/boardsList.styl index 4b5245f9f..0702b3afb 100644 --- a/client/components/boards/boardsList.styl +++ b/client/components/boards/boardsList.styl @@ -73,6 +73,23 @@ $spaceBetweenTiles = 16px transition-duration: .15s transition-property: color, font-size, background + .fa-circle + bottom: 0; + font-size: 10px; + height: 10px; + line-height: 10px; + padding: 9px 9px; + position: absolute; + right: 0; + transition-duration: .15s + transition-property: color, font-size, background + + .has-overtime-card-active + color: #eb4646 !important + + .no-overtime-card-active + color: #3cb500 !important + .is-star-active color: white diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json index 8c7dfad60..63e5be1c2 100644 --- a/i18n/en.i18n.json +++ b/i18n/en.i18n.json @@ -343,6 +343,8 @@ "spent-time-hours": "Spent time (hours)", "overtime-hours": "Overtime (hours)", "overtime": "Overtime", + "has-overtime-cards": "Has overtime cards", + "has-spenttime-cards": "Has spenttime cards", "time": "Time", "title": "Title", "tracking": "Tracking", diff --git a/models/boards.js b/models/boards.js index 6ae818c6e..594bb7b98 100644 --- a/models/boards.js +++ b/models/boards.js @@ -187,6 +187,16 @@ Boards.helpers({ return Lists.find({ boardId: this._id, archived: false }, { sort: { sort: 1 } }); }, + hasOvertimeCards(){ + const card = Cards.findOne({isOvertime: true, boardId: this._id, archived: false} ); + return card !== undefined; + }, + + hasSpentTimeCards(){ + const card = Cards.findOne({spentTime: { $gt: 0 }, boardId: this._id, archived: false} ); + return card !== undefined; + }, + activities() { return Activities.find({ boardId: this._id }, { sort: { createdAt: -1 } }); },