瀏覽代碼

Fix Regression - unable to view cards by due date v8.11.

Thanks to xet7 !

Fixes #5964
Lauri Ojansivu 23 小時之前
父節點
當前提交
ae11e80bde

+ 5 - 5
client/components/boards/boardBody.js

@@ -187,7 +187,7 @@ BlazeComponent.extendComponent({
           console.log(`Board ${boardId} has no shared lists to convert`);
         }
         // Mark as processed even if no shared lists
-        Meteor.call('boards.update', boardId, { $set: { hasSharedListsConverted: true } });
+        Boards.update(boardId, { $set: { hasSharedListsConverted: true } });
         return;
       }
 
@@ -259,7 +259,7 @@ BlazeComponent.extendComponent({
       }
 
       // Mark board as processed
-      Meteor.call('boards.update', boardId, { $set: { hasSharedListsConverted: true } });
+      Boards.update(boardId, { $set: { hasSharedListsConverted: true } });
 
       if (process.env.DEBUG === 'true') {
         console.log(`Successfully converted ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`);
@@ -361,14 +361,14 @@ BlazeComponent.extendComponent({
         }
         
         // Mark board as processed
-        Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
+        Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
       } else if (process.env.DEBUG === 'true') {
         console.log(`No duplicate lists found for board ${boardId}`);
         // Still mark as processed to avoid repeated checks
-        Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
+        Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
       } else {
         // Still mark as processed to avoid repeated checks
-        Meteor.call('boards.update', boardId, { $set: { fixDuplicateListsCompleted: true } });
+        Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } });
       }
 
     } catch (error) {

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

@@ -32,8 +32,16 @@ template(name="dueCards")
               span.global-search-error-messages
                 = msg
         else
+          .due-cards-results-header
+            h1
+              = resultsText
           each card in dueCardsList
             +resultCard(card)
+    else
+      .global-search-results-list-wrapper
+        .no-results
+          h3 {{_ 'dueCards-noResults-title'}}
+          p {{_ 'dueCards-noResults-description'}}
 
 template(name="dueCardsViewChangePopup")
   if currentUser

+ 117 - 9
client/components/main/dueCards.js

@@ -1,5 +1,6 @@
 import { ReactiveCache } from '/imports/reactiveCache';
 import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
+import { TAPi18n } from '/imports/i18n';
 
 // const subManager = new SubsManager();
 
@@ -24,22 +25,25 @@ Template.dueCards.helpers({
     return Meteor.userId();
   },
   dueCardsList() {
-    const component = BlazeComponent.getComponentForElement(this);
+    const component = BlazeComponent.getComponentForElement(this.firstNode);
     if (component && component.dueCardsList) {
       return component.dueCardsList();
     }
     return [];
   },
   hasResults() {
-    const component = BlazeComponent.getComponentForElement(this);
-    if (component && component.dueCardsList) {
-      const cards = component.dueCardsList();
-      return cards && cards.length > 0;
+    const component = BlazeComponent.getComponentForElement(this.firstNode);
+    if (component && component.hasResults) {
+      return component.hasResults.get();
     }
     return false;
   },
   searching() {
-    return false; // No longer using search, so always false
+    const component = BlazeComponent.getComponentForElement(this.firstNode);
+    if (component && component.isLoading) {
+      return component.isLoading.get();
+    }
+    return true; // Show loading by default
   },
   hasQueryErrors() {
     return false; // No longer using search, so always false
@@ -47,6 +51,20 @@ Template.dueCards.helpers({
   errorMessages() {
     return []; // No longer using search, so always empty
   },
+  cardsCount() {
+    const component = BlazeComponent.getComponentForElement(this.firstNode);
+    if (component && component.cardsCount) {
+      return component.cardsCount();
+    }
+    return 0;
+  },
+  resultsText() {
+    const component = BlazeComponent.getComponentForElement(this.firstNode);
+    if (component && component.resultsText) {
+      return component.resultsText();
+    }
+    return '';
+  },
 });
 
 BlazeComponent.extendComponent({
@@ -78,6 +96,9 @@ class DueCardsComponent extends BlazeComponent {
     this._cachedCards = null;
     this._cachedTimestamp = null;
     this.subscriptionHandle = null;
+    this.isLoading = new ReactiveVar(true);
+    this.hasResults = new ReactiveVar(false);
+    this.searching = new ReactiveVar(false);
     
     // Subscribe to the optimized due cards publication
     this.autorun(() => {
@@ -86,6 +107,24 @@ class DueCardsComponent extends BlazeComponent {
         this.subscriptionHandle.stop();
       }
       this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers);
+      
+      // Update loading state based on subscription
+      this.autorun(() => {
+        if (this.subscriptionHandle && this.subscriptionHandle.ready()) {
+          if (process.env.DEBUG === 'true') {
+            console.log('dueCards: subscription ready, loading data...');
+          }
+          this.isLoading.set(false);
+          const cards = this.dueCardsList();
+          this.hasResults.set(cards && cards.length > 0);
+        } else {
+          if (process.env.DEBUG === 'true') {
+            console.log('dueCards: subscription not ready, showing loading...');
+          }
+          this.isLoading.set(true);
+          this.hasResults.set(false);
+        }
+      });
     });
   }
 
@@ -106,9 +145,45 @@ class DueCardsComponent extends BlazeComponent {
     return this.dueCardsView() === 'board';
   }
 
+  hasResults() {
+    return this.hasResults.get();
+  }
+
+  cardsCount() {
+    const cards = this.dueCardsList();
+    return cards ? cards.length : 0;
+  }
+
+  resultsText() {
+    const count = this.cardsCount();
+    if (count === 1) {
+      return TAPi18n.__('one-card-found');
+    } else {
+      // Get the translated text and manually replace %s with the count
+      const baseText = TAPi18n.__('n-cards-found');
+      const result = baseText.replace('%s', count);
+      
+      if (process.env.DEBUG === 'true') {
+        console.log('dueCards: base text:', baseText, 'count:', count, 'result:', result);
+      }
+      return result;
+    }
+  }
+
   dueCardsList() {
+    // Check if subscription is ready
+    if (!this.subscriptionHandle || !this.subscriptionHandle.ready()) {
+      if (process.env.DEBUG === 'true') {
+        console.log('dueCards client: subscription not ready');
+      }
+      return [];
+    }
+
     // Use cached results if available to avoid expensive re-sorting
     if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) {
+      if (process.env.DEBUG === 'true') {
+        console.log('dueCards client: using cached results,', this._cachedCards.length, 'cards');
+      }
       return this._cachedCards;
     }
 
@@ -119,23 +194,56 @@ class DueCardsComponent extends BlazeComponent {
       dueAt: { $exists: true, $nin: [null, ''] }
     });
 
+    if (process.env.DEBUG === 'true') {
+      console.log('dueCards client: found', cards.length, 'cards with due dates');
+      console.log('dueCards client: cards details:', cards.map(c => ({ 
+        id: c._id, 
+        title: c.title, 
+        dueAt: c.dueAt, 
+        boardId: c.boardId,
+        members: c.members,
+        assignees: c.assignees,
+        userId: c.userId
+      })));
+    }
+
     // Filter cards based on user view preference
     const allUsers = this.dueCardsView() === 'all';
     const currentUser = ReactiveCache.getCurrentUser();
     let filteredCards = cards;
 
+    if (process.env.DEBUG === 'true') {
+      console.log('dueCards client: current user:', currentUser ? currentUser._id : 'none');
+      console.log('dueCards client: showing all users:', allUsers);
+    }
+
     if (!allUsers && currentUser) {
       filteredCards = cards.filter(card => {
-        return card.members && card.members.includes(currentUser._id) ||
-               card.assignees && card.assignees.includes(currentUser._id) ||
-               card.userId === currentUser._id;
+        const isMember = card.members && card.members.includes(currentUser._id);
+        const isAssignee = card.assignees && card.assignees.includes(currentUser._id);
+        const isAuthor = card.userId === currentUser._id;
+        const matches = isMember || isAssignee || isAuthor;
+        
+        if (process.env.DEBUG === 'true' && matches) {
+          console.log('dueCards client: card matches user:', card.title, { isMember, isAssignee, isAuthor });
+        }
+        
+        return matches;
       });
     }
 
+    if (process.env.DEBUG === 'true') {
+      console.log('dueCards client: filtered to', filteredCards.length, 'cards');
+    }
+
     // Cache the results for 5 seconds to avoid re-filtering on every render
     this._cachedCards = filteredCards;
     this._cachedTimestamp = Date.now();
 
+    // Update reactive variables
+    this.hasResults.set(filteredCards && filteredCards.length > 0);
+    this.isLoading.set(false);
+
     return filteredCards;
   }
 }

+ 2 - 0
imports/i18n/data/en.i18n.json

@@ -1015,6 +1015,8 @@
   "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.",
+  "dueCards-noResults-title": "No Due Cards Found",
+  "dueCards-noResults-description": "You don't have any cards with due dates at the moment.",
   "broken-cards": "Broken Cards",
   "board-title-not-found": "Board '%s' not found.",
   "swimlane-title-not-found": "Swimlane '%s' not found.",

+ 15 - 11
server/methods/fixDuplicateLists.js

@@ -25,7 +25,7 @@ Meteor.methods({
 
     for (const board of allBoards) {
       try {
-        const result = this.fixDuplicateListsForBoard(board._id);
+        const result = fixDuplicateListsForBoard(board._id);
         totalFixed += result.fixed;
         totalBoardsProcessed++;
         
@@ -55,19 +55,21 @@ Meteor.methods({
       throw new Meteor.Error('not-authorized');
     }
 
-    return this.fixDuplicateListsForBoard(boardId);
-  },
+    return fixDuplicateListsForBoard(boardId);
+  }
+});
 
-  fixDuplicateListsForBoard(boardId) {
+// Helper functions defined outside of Meteor.methods
+function fixDuplicateListsForBoard(boardId) {
     if (process.env.DEBUG === 'true') {
       console.log(`Fixing duplicate lists for board ${boardId}...`);
     }
     
     // First, fix duplicate swimlanes
-    const swimlaneResult = this.fixDuplicateSwimlanes(boardId);
+    const swimlaneResult = fixDuplicateSwimlanes(boardId);
     
     // Then, fix duplicate lists
-    const listResult = this.fixDuplicateLists(boardId);
+    const listResult = fixDuplicateLists(boardId);
     
     return {
       boardId,
@@ -75,9 +77,10 @@ Meteor.methods({
       fixedLists: listResult.fixed,
       fixed: swimlaneResult.fixed + listResult.fixed
     };
-  },
+}
 
-  fixDuplicateSwimlanes(boardId) {
+// Helper functions defined outside of Meteor.methods
+function fixDuplicateSwimlanes(boardId) {
     const swimlanes = Swimlanes.find({ boardId }).fetch();
     const swimlaneGroups = {};
     let fixed = 0;
@@ -144,9 +147,9 @@ Meteor.methods({
     });
 
     return { fixed };
-  },
+}
 
-  fixDuplicateLists(boardId) {
+function fixDuplicateLists(boardId) {
     const lists = Lists.find({ boardId }).fetch();
     const listGroups = {};
     let fixed = 0;
@@ -192,8 +195,9 @@ Meteor.methods({
     });
 
     return { fixed };
-  },
+}
 
+Meteor.methods({
   'fixDuplicateLists.getReport'() {
     if (!this.userId) {
       throw new Meteor.Error('not-authorized');

+ 37 - 2
server/publications/cards.js

@@ -136,10 +136,29 @@ Meteor.publish('dueCards', function(allUsers = false) {
 
   // Get user's board memberships for efficient filtering
   const userBoards = ReactiveCache.getBoards({
-    members: userId
+    $or: [
+      { permission: 'public' },
+      { members: { $elemMatch: { userId, isActive: true } } }
+    ]
   }).map(board => board._id);
 
+  if (process.env.DEBUG === 'true') {
+    console.log('dueCards userBoards:', userBoards);
+    console.log('dueCards userBoards count:', userBoards.length);
+    
+    // Also check if there are any cards with due dates in the system at all
+    const allCardsWithDueDates = Cards.find({
+      type: 'cardType-card',
+      archived: false,
+      dueAt: { $exists: true, $nin: [null, ''] }
+    }).count();
+    console.log('dueCards: total cards with due dates in system:', allCardsWithDueDates);
+  }
+
   if (userBoards.length === 0) {
+    if (process.env.DEBUG === 'true') {
+      console.log('dueCards: No boards found for user, returning ready');
+    }
     return this.ready();
   }
 
@@ -182,7 +201,23 @@ Meteor.publish('dueCards', function(allUsers = false) {
     console.log('dueCards options:', JSON.stringify(options, null, 2));
   }
 
-  return Cards.find(selector, options);
+  const result = Cards.find(selector, options);
+  
+  if (process.env.DEBUG === 'true') {
+    const count = result.count();
+    console.log('dueCards publication: returning', count, 'cards');
+    if (count > 0) {
+      const sampleCards = result.fetch().slice(0, 3);
+      console.log('dueCards publication: sample cards:', sampleCards.map(c => ({
+        id: c._id,
+        title: c.title,
+        dueAt: c.dueAt,
+        boardId: c.boardId
+      })));
+    }
+  }
+
+  return result;
 });
 
 Meteor.publish('globalSearch', function(sessionId, params, text) {