From ae11e80bde79d9ad412d185f20e5a7f802685260 Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Wed, 22 Oct 2025 23:31:36 +0300 Subject: [PATCH] Fix Regression - unable to view cards by due date v8.11. Thanks to xet7 ! Fixes #5964 --- client/components/boards/boardBody.js | 10 +- client/components/main/dueCards.jade | 8 ++ client/components/main/dueCards.js | 126 ++++++++++++++++++++++++-- imports/i18n/data/en.i18n.json | 2 + server/methods/fixDuplicateLists.js | 26 +++--- server/publications/cards.js | 39 +++++++- 6 files changed, 184 insertions(+), 27 deletions(-) diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index d4afe0bb2..f1f6b9a5a 100644 --- a/client/components/boards/boardBody.js +++ b/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) { diff --git a/client/components/main/dueCards.jade b/client/components/main/dueCards.jade index 3a7dce08b..f482c9233 100644 --- a/client/components/main/dueCards.jade +++ b/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 diff --git a/client/components/main/dueCards.js b/client/components/main/dueCards.js index ef77f9467..f17bc9a74 100644 --- a/client/components/main/dueCards.js +++ b/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; } } diff --git a/imports/i18n/data/en.i18n.json b/imports/i18n/data/en.i18n.json index 12e76c28d..dd01adcd0 100644 --- a/imports/i18n/data/en.i18n.json +++ b/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.", diff --git a/server/methods/fixDuplicateLists.js b/server/methods/fixDuplicateLists.js index f5e79bab5..b673021ec 100644 --- a/server/methods/fixDuplicateLists.js +++ b/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'); diff --git a/server/publications/cards.js b/server/publications/cards.js index f3619e489..b0b0cb8c8 100644 --- a/server/publications/cards.js +++ b/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) {