Explorar el Código

Merge branch 'card-spent-time' of https://github.com/thuanpq/wekan into thuanpq-card-spent-time

Lauri Ojansivu hace 7 años
padre
commit
e162fe3c0f

+ 6 - 0
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'}}

+ 12 - 0
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);

+ 17 - 0
client/components/boards/boardsList.styl

@@ -74,6 +74,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
 

+ 9 - 0
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'}}

+ 1 - 0
client/components/cards/cardDetails.js

@@ -165,6 +165,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) {

+ 22 - 0
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}}

+ 81 - 0
client/components/cards/cardTime.js

@@ -0,0 +1,81 @@
+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() {
+    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;
+  },
+  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();
+  },
+});

+ 17 - 0
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

+ 8 - 4
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

+ 8 - 0
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,11 @@
     "team": "Team",
     "this-board": "this board",
     "this-card": "this card",
+    "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",

+ 10 - 0
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 } });
   },

+ 24 - 2
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
@@ -273,6 +283,18 @@ Cards.mutations({
   unsetDue() {
     return {$unset: {dueAt: ''}};
   },
+
+  setOvertime(isOvertime) {
+    return {$set: {isOvertime}};
+  },
+
+  setSpentTime(spentTime) {
+    return {$set: {spentTime}};
+  },
+
+  unsetSpentTime() {
+    return {$unset: {spentTime: '', isOvertime: false}};
+  },
 });