Browse Source

Merge pull request #3425 from jrsupplee/master

My Cards and Due Cards
Lauri Ojansivu 4 years ago
parent
commit
4d71848360

+ 68 - 0
client/components/main/dueCards.jade

@@ -0,0 +1,68 @@
+template(name="dueCardsHeaderBar")
+  h1
+    i.fa.fa-calendar
+    | {{_ 'dueCards-title'}}
+
+  .board-header-btns.left
+    a.board-header-btn.js-due-cards-view-change(title="{{_ 'dueCardsViewChange-title'}}")
+      i.fa.fa-caret-down
+      if $eq dueCardsView 'me'
+        i.fa.fa-user
+        | {{_ 'dueCardsViewChange-choice-me'}}
+      if $eq dueCardsView 'all'
+        i.fa.fa-users
+        | {{_ 'dueCardsViewChange-choice-all'}}
+
+template(name="dueCardsModalTitle")
+  h2
+    i.fa.fa-keyboard-o
+    | {{_ 'dueCards-title'}}
+
+template(name="dueCards")
+  if isPageReady.get
+    .wrapper
+      .due-cards-dueat-list-wrapper
+        each card in dueCardsList
+          .due-cards-card-wrapper
+            a.minicard-wrapper.card-title(href=card.absoluteUrl)
+              +minicard(card)
+            ul.due-cards-context-list
+              li.due-cards-context(title="{{_ 'board'}}")
+                +viewer
+                  = card.getBoard.title
+              li.due-cards-context.due-cards-context-separator
+                = ' '
+                | {{_ 'context-separator'}}
+                = ' '
+              li.due-cards-context(title="{{_ 'swimlane'}}")
+                +viewer
+                  = card.getSwimlane.title
+              li.due-cards-context
+                = ' '
+                | {{_ 'context-separator'}}
+                = ' '
+              li.due-cards-context(title="{{_ 'list'}}")
+                +viewer
+                  = card.getList.title
+  else
+    +spinner
+
+template(name="dueCardsViewChangePopup")
+  ul.pop-over-list
+    li
+      with "dueCardsViewChange-choice-me"
+        a.js-due-cards-view-me
+          i.fa.fa-user.colorful
+          | {{_ 'dueCardsViewChange-choice-me'}}
+          if $eq Utils.dueCardsView "me"
+            i.fa.fa-check
+    li
+      with "dueCardsViewChange-choice-all"
+        a.js-due-cards-view-all
+          i.fa.fa-users.colorful
+          | {{_ 'dueCardsViewChange-choice-all'}}
+          span.sub-name
+            +viewer
+              | {{_ 'dueCardsViewChange-choice-all-description' }}
+          if $eq Utils.dueCardsView "all"
+            i.fa.fa-check

+ 154 - 0
client/components/main/dueCards.js

@@ -0,0 +1,154 @@
+const subManager = new SubsManager();
+
+BlazeComponent.extendComponent({
+  dueCardsView() {
+    // eslint-disable-next-line no-console
+    // console.log('sort:', Utils.dueCardsView());
+    return Utils.dueCardsView();
+  },
+
+  events() {
+    return [
+      {
+        'click .js-due-cards-view-change': Popup.open('dueCardsViewChange'),
+      },
+    ];
+  },
+}).register('dueCardsHeaderBar');
+
+Template.dueCards.helpers({
+  userId() {
+    return Meteor.userId();
+  },
+});
+
+BlazeComponent.extendComponent({
+  events() {
+    return [
+      {
+        'click .js-due-cards-view-me'() {
+          Utils.setDueCardsView('me');
+          Popup.close();
+        },
+
+        'click .js-due-cards-view-all'() {
+          Utils.setDueCardsView('all');
+          Popup.close();
+        },
+      },
+    ];
+  },
+}).register('dueCardsViewChangePopup');
+
+BlazeComponent.extendComponent({
+  onCreated() {
+    this.isPageReady = new ReactiveVar(false);
+
+    this.autorun(() => {
+      const handle = subManager.subscribe(
+        'dueCards',
+        Utils.dueCardsView() === 'all',
+      );
+      Tracker.nonreactive(() => {
+        Tracker.autorun(() => {
+          this.isPageReady.set(handle.ready());
+        });
+      });
+    });
+    Meteor.subscribe('setting');
+  },
+
+  dueCardsView() {
+    // eslint-disable-next-line no-console
+    console.log('sort:', Utils.dueCardsView());
+    return Utils.dueCardsView();
+  },
+
+  sortByBoard() {
+    return this.dueCardsView() === 'board';
+  },
+
+  dueCardsList() {
+    const allUsers = Utils.dueCardsView() === 'all';
+
+    const user = Meteor.user();
+
+    const archivedBoards = [];
+    Boards.find({ archived: true }).forEach(board => {
+      archivedBoards.push(board._id);
+    });
+
+    const permiitedBoards = [];
+    let selector = {
+      archived: false,
+    };
+    // if user is not an admin allow her to see cards only from public boards
+    // or those where she is a member
+    if (!user.isAdmin) {
+      selector.$or = [
+        { permission: 'public' },
+        { members: { $elemMatch: { userId: user._id, isActive: true } } },
+      ];
+    }
+    Boards.find(selector).forEach(board => {
+      permiitedBoards.push(board._id);
+    });
+
+    const archivedSwimlanes = [];
+    Swimlanes.find({ archived: true }).forEach(swimlane => {
+      archivedSwimlanes.push(swimlane._id);
+    });
+
+    const archivedLists = [];
+    Lists.find({ archived: true }).forEach(list => {
+      archivedLists.push(list._id);
+    });
+
+    selector = {
+      archived: false,
+      boardId: {
+        $nin: archivedBoards,
+        $in: permiitedBoards,
+      },
+      swimlaneId: { $nin: archivedSwimlanes },
+      listId: { $nin: archivedLists },
+      dueAt: { $ne: null },
+      endAt: null,
+    };
+
+    if (!allUsers) {
+      selector.$or = [{ members: user._id }, { assignees: user._id }];
+    }
+
+    const cards = [];
+
+    // eslint-disable-next-line no-console
+    // console.log('cards selector:', selector);
+    Cards.find(selector).forEach(card => {
+      cards.push(card);
+      // eslint-disable-next-line no-console
+      // console.log(
+      //   'board:',
+      //   card.board(),
+      //   'swimlane:',
+      //   card.swimlane(),
+      //   'list:',
+      //   card.list(),
+      // );
+    });
+
+    cards.sort((a, b) => {
+      const x = a.dueAt === null ? Date('2100-12-31') : a.dueAt;
+      const y = b.dueAt === null ? Date('2100-12-31') : b.dueAt;
+
+      if (x > y) return 1;
+      else if (x < y) return -1;
+
+      return 0;
+    });
+
+    // eslint-disable-next-line no-console
+    // console.log('cards:', cards);
+    return cards;
+  },
+}).register('dueCards');

+ 69 - 0
client/components/main/dueCards.styl

@@ -0,0 +1,69 @@
+.due-cards-board-wrapper
+  border-radius: 8px
+  //padding: 0.5rem
+  min-width: 400px
+  border-width: 8px
+  border-color: grey
+  border-style: solid
+  margin-bottom: 2rem
+  margin-right: auto
+  margin-left: auto
+
+.due-cards-board-title
+  font-size: 1.4rem
+  font-weight: bold
+  padding: 0.5rem
+  background-color: grey
+  color: white
+
+.due-cards-swimlane-title
+  font-size: 1.1rem
+  font-weight: bold
+  padding: 0.5rem
+  padding-bottom: 0.4rem
+  margin-top: 0
+  margin-bottom: 0.5rem
+  //border-top: black 1px solid
+  //border-bottom: black 1px solid
+  text-align: center
+
+.swimlane-default-color
+  background-color: lightgrey
+
+.due-cards-list-title
+  font-weight: bold
+  font-size: 1.1rem
+  //padding-bottom: 0
+  //margin-bottom: 0
+  text-align: center
+  margin-bottom: 0.7rem
+
+.due-cards-list-wrapper
+  margin: 1rem
+  border-radius: 5px
+  padding: 1.5rem
+  padding-top: 0.75rem
+  display: inline-block
+  min-width: 250px
+  max-width: 350px
+
+.due-cards-card-wrapper
+  margin-top: 0
+  margin-bottom: 10px
+
+.due-cards-dueat-list-wrapper
+  max-width: 500px
+  margin-right: auto
+  margin-left: auto
+
+.due-cards-field-name
+  font-weight: bold
+
+.due-cards-context
+  display: inline-block
+
+.due-cards-context-separator
+  font-weight: bold
+
+.due-cards-context-list
+  margin-bottom: 0.7rem

+ 52 - 61
client/components/main/myCards.jade

@@ -2,18 +2,18 @@ template(name="myCardsHeaderBar")
   h1
     //a.back-btn(href="{{pathFor 'home'}}")
     //  i.fa.fa-chevron-left
+    i.fa.fa-list
     | {{_ 'my-cards'}}
 
   .board-header-btns.left
-    a.board-header-btn.js-toggle-my-cards-choose-sort(title="{{_ 'my-cards-sort'}}")
-      //i.fa.fa-caret-down
-      i.fa.fa-sort
+    a.board-header-btn.js-toggle-my-cards-choose-sort(title="{{_ 'myCardsSortChange-title'}}")
+      i.fa.fa-caret-down
       if $eq myCardsSort 'board'
         i.fa.fa-th-large
-        | {{_ 'my-cards-sort-board'}}
+        | {{_ 'myCardsSortChange-choice-board'}}
       if $eq myCardsSort 'dueAt'
         i.fa.fa-calendar
-        | {{_ 'my-cards-sort-dueat'}}
+        | {{_ 'myCardsSortChange-choice-dueat'}}
 
 template(name="myCardsModalTitle")
   h2
@@ -21,51 +21,53 @@ template(name="myCardsModalTitle")
     | {{_ 'my-cards'}}
 
 template(name="myCards")
-  .wrapper
-    if $eq myCardsSort 'board'
-      each board in myBoards
-        .my-cards-board-wrapper
-          .my-cards-board-title
-            +viewer
-              = board.title
-          each swimlane in board.mySwimlanes
-            .my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
+  if isPageReady.get
+    .wrapper
+      if $eq myCardsSort 'board'
+        each board in myCardsList
+          .my-cards-board-wrapper
+            .my-cards-board-title
               +viewer
-                = swimlane.title
-            each list in swimlane.myLists
-              .my-cards-list-wrapper
-                .my-cards-list-title(class=list.colorClass)
-                  +viewer
-                    = list.title
-                each card in list.myCards
-                  .my-cards-card-wrapper
-                    a.minicard-wrapper(href=card.absoluteUrl)
-                      +minicard(card)
-    else
-      .my-cards-dueat-list-wrapper
-        each card in myCardsList
-          .my-cards-card-wrapper
-            a.minicard-wrapper.card-title(href=card.absoluteUrl)
-              +minicard(card)
-            ul.my-cards-context-list
-              li.my-cards-context(title="{{_ 'board'}}")
-                +viewer
-                  = card.board.title
-              li.my-cards-context.my-cards-context-separator
-                = ' '
-                | {{_ 'context-separator'}}
-                = ' '
-              li.my-cards-context(title="{{_ 'swimlane'}}")
+                = board.title
+            each swimlane in board.mySwimlanes
+              .my-cards-swimlane-title(class="{{#if swimlane.colorClass}}{{ swimlane.colorClass }}{{else}}swimlane-default-color{{/if}}")
                 +viewer
-                  = card.swimlane.title
-              li.my-cards-context
-                = ' '
-                | {{_ 'context-separator'}}
-                = ' '
-              li.my-cards-context(title="{{_ 'list'}}")
-                +viewer
-                  = card.list.title
-
+                  = swimlane.title
+              each list in swimlane.myLists
+                .my-cards-list-wrapper
+                  .my-cards-list-title(class=list.colorClass)
+                    +viewer
+                      = list.title
+                  each card in list.myCards
+                    .my-cards-card-wrapper
+                      a.minicard-wrapper(href=card.absoluteUrl)
+                        +minicard(card)
+      else
+        .my-cards-dueat-list-wrapper
+          each card in myDueCardsList
+            .my-cards-card-wrapper
+              a.minicard-wrapper.card-title(href=card.absoluteUrl)
+                +minicard(card)
+              ul.my-cards-context-list
+                li.my-cards-context(title="{{_ 'board'}}")
+                  +viewer
+                    = card.getBoard.title
+                li.my-cards-context.my-cards-context-separator
+                  = ' '
+                  | {{_ 'context-separator'}}
+                  = ' '
+                li.my-cards-context(title="{{_ 'swimlane'}}")
+                  +viewer
+                    = card.getSwimlane.title
+                li.my-cards-context
+                  = ' '
+                  | {{_ 'context-separator'}}
+                  = ' '
+                li.my-cards-context(title="{{_ 'list'}}")
+                  +viewer
+                    = card.getList.title
+  else
+    +spinner
 
 template(name="myCardsSortChangePopup")
   ul.pop-over-list
@@ -73,24 +75,13 @@ template(name="myCardsSortChangePopup")
       with "my-cards-sort-board"
         a.js-my-cards-sort-board
           i.fa.fa-th-large.colorful
-          | {{_ 'my-cards-sort-board'}}
+          | {{_ 'myCardsSortChange-choice-board'}}
           if $eq Utils.myCardsSort "board"
             i.fa.fa-check
     li
       with "my-cards-sort-dueat"
         a.js-my-cards-sort-dueat
           i.fa.fa-calendar.colorful
-          | {{_ 'my-cards-sort-dueat'}}
+          | {{_ 'myCardsSortChange-choice-dueat'}}
           if $eq Utils.myCardsSort "dueAt"
             i.fa.fa-check
-
-//template(name="myCardsSortChangePopup")
-//  ul.pop-over-list
-//    li
-//      a.js-my-cards-sort-board
-//        i.fa.fa-th-large.colorful
-//        | {{_ 'my-cards-sort-board'}}
-//    li
-//      a.js-my-cards-sort-dueat
-//        i.fa.fa-calendar.colorful
-//        | {{_ 'my-cards-sort-dueat'}}

+ 27 - 15
client/components/main/myCards.js

@@ -1,3 +1,5 @@
+const subManager = new SubsManager();
+
 BlazeComponent.extendComponent({
   myCardsSort() {
     // eslint-disable-next-line no-console
@@ -8,12 +10,9 @@ BlazeComponent.extendComponent({
   events() {
     return [
       {
-        'click .js-toggle-my-cards-choose-sort'() {
-          // eslint-disable-next-line no-console
-          // console.log('open sort');
-          // Popup.open('myCardsSortChange');
-          Utils.myCardsSortToggle();
-        },
+        'click .js-toggle-my-cards-choose-sort': Popup.open(
+          'myCardsSortChange',
+        ),
       },
     ];
   },
@@ -45,10 +44,17 @@ BlazeComponent.extendComponent({
 
 BlazeComponent.extendComponent({
   onCreated() {
+    this.isPageReady = new ReactiveVar(false);
+
+    this.autorun(() => {
+      const handle = subManager.subscribe('myCards');
+      Tracker.nonreactive(() => {
+        Tracker.autorun(() => {
+          this.isPageReady.set(handle.ready());
+        });
+      });
+    });
     Meteor.subscribe('setting');
-    Meteor.subscribe('myCards');
-    Meteor.subscribe('mySwimlanes');
-    Meteor.subscribe('myLists');
   },
 
   myCardsSort() {
@@ -61,7 +67,7 @@ BlazeComponent.extendComponent({
     return this.myCardsSort() === 'board';
   },
 
-  myBoards() {
+  myCardsList() {
     const userId = Meteor.userId();
     const boards = [];
     let board = null;
@@ -93,7 +99,7 @@ BlazeComponent.extendComponent({
       if (list === null || card.listId !== list._id) {
         // eslint-disable-next-line no-console
         // console.log('new list');
-        list = card.list();
+        list = card.getList();
         if (list.archived) {
           list = null;
           return;
@@ -104,7 +110,7 @@ BlazeComponent.extendComponent({
       if (swimlane === null || card.swimlaneId !== swimlane._id) {
         // eslint-disable-next-line no-console
         // console.log('new swimlane');
-        swimlane = card.swimlane();
+        swimlane = card.getSwimlane();
         if (swimlane.archived) {
           swimlane = null;
           return;
@@ -115,7 +121,7 @@ BlazeComponent.extendComponent({
       if (board === null || card.boardId !== board._id) {
         // eslint-disable-next-line no-console
         // console.log('new board');
-        board = card.board();
+        board = card.getBoard();
         if (board.archived) {
           board = null;
           return;
@@ -176,7 +182,7 @@ BlazeComponent.extendComponent({
     return boards;
   },
 
-  myCardsList() {
+  myDueCardsList() {
     const userId = Meteor.userId();
 
     const cursor = Cards.find(
@@ -200,7 +206,13 @@ BlazeComponent.extendComponent({
 
     const cards = [];
     cursor.forEach(card => {
-      cards.push(card);
+      if (
+        !card.getBoard().archived &&
+        !card.getSwimlane().archived &&
+        !card.getList().archived
+      ) {
+        cards.push(card);
+      }
     });
 
     cards.sort((a, b) => {

+ 4 - 0
client/components/users/userHeader.jade

@@ -17,6 +17,10 @@ template(name="memberMenuPopup")
         a.js-my-cards(href="{{pathFor 'my-cards'}}")
           i.fa.fa-list
           | {{_ 'my-cards'}}
+      li
+        a.js-due-cards(href="{{pathFor 'due-cards'}}")
+          i.fa.fa-calendar
+          | {{_ 'dueCards-title'}}
       li
         a(href="{{pathFor 'home'}}")
           span.fa.fa-home

+ 3 - 0
client/components/users/userHeader.js

@@ -28,6 +28,9 @@ Template.memberMenuPopup.events({
   'click .js-my-cards'() {
     Popup.close();
   },
+  'click .js-due-cards'() {
+    Popup.close();
+  },
   'click .js-open-archived-board'() {
     Modal.open('archivedBoards');
   },

+ 25 - 0
client/lib/utils.js

@@ -69,6 +69,31 @@ Utils = {
     location.reload();
   },
 
+  archivedBoardIds() {
+    const archivedBoards = [];
+    Boards.find({ archived: false }).forEach(board => {
+      archivedBoards.push(board._id);
+    });
+    return archivedBoards;
+  },
+
+  dueCardsView() {
+    let view = window.localStorage.getItem('dueCardsView');
+
+    if (!view || !['me', 'all'].includes(view)) {
+      window.localStorage.setItem('dueCardsView', 'me');
+      location.reload();
+      view = 'me';
+    }
+
+    return view;
+  },
+
+  setDueCardsView(view) {
+    window.localStorage.setItem('dueCardsView', view);
+    location.reload();
+  },
+
   // XXX We should remove these two methods
   goBoardId(_id) {
     const board = Boards.findOne(_id);

+ 19 - 9
config/router.js

@@ -116,8 +116,6 @@ FlowRouter.route('/shortcuts', {
 FlowRouter.route('/my-cards', {
   name: 'my-cards',
   action() {
-    const myCardsTemplate = 'myCards';
-
     Filter.reset();
     // EscapeActions.executeAll();
     EscapeActions.executeUpTo('popup-close');
@@ -125,15 +123,27 @@ FlowRouter.route('/my-cards', {
     Utils.manageCustomUI();
     Utils.manageMatomo();
 
-    // if (previousPath) {
-    //   Modal.open(myCardsTemplate, {
-    //     header: 'myCardsModalTitle',
-    //     onCloseGoTo: previousPath,
-    //   });
-    // } else {
     BlazeLayout.render('defaultLayout', {
       headerBar: 'myCardsHeaderBar',
-      content: myCardsTemplate,
+      content: 'myCards',
+    });
+    // }
+  },
+});
+
+FlowRouter.route('/due-cards', {
+  name: 'due-cards',
+  action() {
+    Filter.reset();
+    // EscapeActions.executeAll();
+    EscapeActions.executeUpTo('popup-close');
+
+    Utils.manageCustomUI();
+    Utils.manageMatomo();
+
+    BlazeLayout.render('defaultLayout', {
+      headerBar: 'dueCardsHeaderBar',
+      content: 'dueCards',
     });
     // }
   },

+ 8 - 3
i18n/en.i18n.json

@@ -854,7 +854,12 @@
   "list": "List",
   "board": "Board",
   "context-separator": "/",
-  "my-cards-sort": "My Cards Sort",
-  "my-cards-sort-board": "By Board",
-  "my-cards-sort-dueat": "By Due Date"
+  "myCardsSortChange-title": "My Cards Sort",
+  "myCardsSortChange-choice-board": "By Board",
+  "myCardsSortChange-choice-dueat": "By Due Date",
+  "dueCards-title": "Due Cards",
+  "dueCardsViewChange-title": "Due Cards View",
+  "dueCardsViewChange-choice-me": "Me",
+  "dueCardsViewChange-choice-all": "All Users",
+  "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission."
 }

+ 39 - 0
models/cards.js

@@ -469,6 +469,45 @@ Cards.helpers({
     return Boards.findOne(this.boardId);
   },
 
+  getList() {
+    const list = this.list();
+    if (!list) {
+      return {
+        _id: this.listId,
+        title: 'Undefined List',
+        archived: false,
+        colorClass: '',
+      };
+    }
+    return list;
+  },
+
+  getSwimlane() {
+    const swimlane = this.swimlane();
+    if (!swimlane) {
+      return {
+        _id: this.swimlaneId,
+        title: 'Undefined Swimlane',
+        archived: false,
+        colorClass: '',
+      };
+    }
+    return swimlane;
+  },
+
+  getBoard() {
+    const board = this.board();
+    if (!board) {
+      return {
+        _id: this.boardId,
+        title: 'Undefined Board',
+        archived: false,
+        colorClass: '',
+      };
+    }
+    return board;
+  },
+
   labels() {
     const boardLabels = this.board().labels;
     const cardLabels = _.filter(boardLabels, label => {

+ 0 - 65
server/publications/boards.js

@@ -43,71 +43,6 @@ Meteor.publish('boards', function() {
   );
 });
 
-Meteor.publish('mySwimlanes', function() {
-  const userId = this.userId;
-  const swimlanes = [];
-
-  Cards.find({
-    archived: false,
-    $or: [{ members: userId }, { assignees: userId }],
-  }).forEach(card => {
-    swimlanes.push(card.swimlaneId);
-  });
-
-  return Swimlanes.find(
-    {
-      // archived: false,
-      _id: { $in: swimlanes },
-    },
-    {
-      fields: {
-        _id: 1,
-        title: 1,
-        boardId: 1,
-        type: 1,
-        color: 1,
-        sort: 1,
-      },
-      // sort: {
-      //   sort: ['boardId', 'listId', 'sort'],
-      // },
-    },
-  );
-});
-
-Meteor.publish('myLists', function() {
-  const userId = this.userId;
-  const lists = [];
-
-  Cards.find({
-    archived: false,
-    $or: [{ members: userId }, { assignees: userId }],
-  }).forEach(card => {
-    lists.push(card.listId);
-  });
-
-  return Lists.find(
-    {
-      // archived: false,
-      _id: { $in: lists },
-    },
-    {
-      fields: {
-        _id: 1,
-        boardId: 1,
-        swimlaneId: 1,
-        title: 1,
-        color: 1,
-        type: 1,
-        sort: 1,
-      },
-      // sort: {
-      //   sort: ['boardId', 'listId', 'sort'],
-      // },
-    },
-  );
-});
-
 Meteor.publish('archivedBoards', function() {
   const userId = this.userId;
   if (!Match.test(userId, String)) return [];

+ 166 - 24
server/publications/cards.js

@@ -4,31 +4,173 @@ Meteor.publish('card', cardId => {
 });
 
 Meteor.publish('myCards', function() {
-  const userId = this.userId;
+  const userId = Meteor.userId();
 
-  return Cards.find(
-    {
-      archived: false,
-      $or: [{ members: userId }, { assignees: userId }],
+  const archivedBoards = [];
+  Boards.find({ archived: true }).forEach(board => {
+    archivedBoards.push(board._id);
+  });
+
+  const archivedSwimlanes = [];
+  Swimlanes.find({ archived: true }).forEach(swimlane => {
+    archivedSwimlanes.push(swimlane._id);
+  });
+
+  const archivedLists = [];
+  Lists.find({ archived: true }).forEach(list => {
+    archivedLists.push(list._id);
+  });
+
+  selector = {
+    archived: false,
+    boardId: { $nin: archivedBoards },
+    swimlaneId: { $nin: archivedSwimlanes },
+    listId: { $nin: archivedLists },
+    $or: [{ members: userId }, { assignees: userId }],
+  };
+
+  const cards = Cards.find(selector, {
+    fields: {
+      _id: 1,
+      archived: 1,
+      boardId: 1,
+      swimlaneId: 1,
+      listId: 1,
+      title: 1,
+      type: 1,
+      sort: 1,
+      members: 1,
+      assignees: 1,
+      colors: 1,
+      dueAt: 1,
     },
-    {
-      fields: {
-        _id: 1,
-        archived: 1,
-        boardId: 1,
-        swimlaneId: 1,
-        listId: 1,
-        title: 1,
-        type: 1,
-        sort: 1,
-        members: 1,
-        assignees: 1,
-        colors: 1,
-        dueAt: 1,
-      },
-      // sort: {
-      //   sort: ['boardId', 'listId', 'sort'],
-      // },
+  });
+
+  const boards = [];
+  const swimlanes = [];
+  const lists = [];
+  const users = [];
+
+  cards.forEach(card => {
+    if (card.boardId) boards.push(card.boardId);
+    if (card.swimlaneId) swimlanes.push(card.swimlaneId);
+    if (card.listId) lists.push(card.listId);
+    if (card.members) {
+      card.members.forEach(userId => {
+        users.push(userId);
+      });
+    }
+    if (card.assignees) {
+      card.assignees.forEach(userId => {
+        users.push(userId);
+      });
+    }
+  });
+
+  return [
+    cards,
+    Boards.find({ _id: { $in: boards } }),
+    Swimlanes.find({ _id: { $in: swimlanes } }),
+    Lists.find({ _id: { $in: lists } }),
+    Users.find({ _id: { $in: users } }),
+  ];
+});
+
+Meteor.publish('dueCards', function(allUsers = false) {
+  check(allUsers, Boolean);
+
+  // eslint-disable-next-line no-console
+  // console.log('all users:', allUsers);
+
+  const user = Users.findOne(this.userId);
+
+  const archivedBoards = [];
+  Boards.find({ archived: true }).forEach(board => {
+    archivedBoards.push(board._id);
+  });
+
+  const permiitedBoards = [];
+  let selector = {
+    archived: false,
+  };
+  // if user is not an admin allow her to see cards only from boards where
+  // she is a member
+  if (!user.isAdmin) {
+    selector.$or = [
+      { permission: 'public' },
+      { members: { $elemMatch: { userId: user._id, isActive: true } } },
+    ];
+  }
+  Boards.find(selector).forEach(board => {
+    permiitedBoards.push(board._id);
+  });
+
+  const archivedSwimlanes = [];
+  Swimlanes.find({ archived: true }).forEach(swimlane => {
+    archivedSwimlanes.push(swimlane._id);
+  });
+
+  const archivedLists = [];
+  Lists.find({ archived: true }).forEach(list => {
+    archivedLists.push(list._id);
+  });
+
+  selector = {
+    archived: false,
+    boardId: { $nin: archivedBoards, $in: permiitedBoards },
+    swimlaneId: { $nin: archivedSwimlanes },
+    listId: { $nin: archivedLists },
+    dueAt: { $ne: null },
+    endAt: null,
+  };
+
+  if (!allUsers) {
+    selector.$or = [{ members: user._id }, { assignees: user._id }];
+  }
+
+  const cards = Cards.find(selector, {
+    fields: {
+      _id: 1,
+      archived: 1,
+      boardId: 1,
+      swimlaneId: 1,
+      listId: 1,
+      title: 1,
+      type: 1,
+      sort: 1,
+      members: 1,
+      assignees: 1,
+      colors: 1,
+      dueAt: 1,
     },
-  );
+  });
+
+  const boards = [];
+  const swimlanes = [];
+  const lists = [];
+  const users = [];
+
+  cards.forEach(card => {
+    if (card.boardId) boards.push(card.boardId);
+    if (card.swimlaneId) swimlanes.push(card.swimlaneId);
+    if (card.listId) lists.push(card.listId);
+    if (card.members) {
+      card.members.forEach(userId => {
+        users.push(userId);
+      });
+    }
+    if (card.assignees) {
+      card.assignees.forEach(userId => {
+        users.push(userId);
+      });
+    }
+  });
+
+  return [
+    cards,
+    Boards.find({ _id: { $in: boards } }),
+    Swimlanes.find({ _id: { $in: swimlanes } }),
+    Lists.find({ _id: { $in: lists } }),
+    Users.find({ _id: { $in: users } }),
+  ];
 });