diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 5db1512ff..bf40538db 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -246,57 +246,30 @@ class CardDueDate extends CardDate { const theDate = this.date.get(); const now = this.now.get(); - // Debug logging for due date classes - if (process.env.DEBUG === 'true') { - console.log(`CardDueDate classes() - Card: "${this.data().title}", Due: ${theDate}, Now: ${now}, End: ${endAt}`); - } - // If there's an end date and it's before the due date, task is completed early if (endAt && isBefore(endAt, theDate)) { classes += 'completed-early'; - if (process.env.DEBUG === 'true') { - console.log(` -> completed-early (end date ${endAt} is before due date ${theDate})`); - } } // If there's an end date, don't show due date status since task is completed else if (endAt) { classes += 'completed'; - if (process.env.DEBUG === 'true') { - console.log(` -> completed (has end date ${endAt})`); - } } // Due date logic based on current time else { const daysDiff = diff(theDate, now, 'days'); - if (process.env.DEBUG === 'true') { - console.log(` -> daysDiff: ${daysDiff} (due: ${theDate}, now: ${now})`); - } if (daysDiff < 0) { // Due date is in the past - overdue classes += 'overdue'; - if (process.env.DEBUG === 'true') { - console.log(` -> overdue (${Math.abs(daysDiff)} days past due)`); - } } else if (daysDiff <= 1) { // Due today or tomorrow - due soon classes += 'due-soon'; - if (process.env.DEBUG === 'true') { - console.log(` -> due-soon (due in ${daysDiff} days)`); - } } else { // Due date is more than 1 day away - not due yet classes += 'not-due'; - if (process.env.DEBUG === 'true') { - console.log(` -> not-due (due in ${daysDiff} days)`); - } } } - if (process.env.DEBUG === 'true') { - console.log(` -> Final classes: "${classes}"`); - } - return classes; } diff --git a/client/components/main/dueCards.js b/client/components/main/dueCards.js index e60c49287..ef77f9467 100644 --- a/client/components/main/dueCards.js +++ b/client/components/main/dueCards.js @@ -1,13 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import { CardSearchPagedComponent } from '../../lib/cardSearch'; -import { - OPERATOR_HAS, - OPERATOR_SORT, - OPERATOR_USER, - ORDER_ASCENDING, - PREDICATE_DUE_AT, -} from '../../../config/search-const'; -import { QueryParams } from '../../../config/query-classes'; +import { BlazeComponent } from 'meteor/peerlibrary:blaze-components'; // const subManager = new SubsManager(); @@ -38,6 +30,23 @@ Template.dueCards.helpers({ } return []; }, + hasResults() { + const component = BlazeComponent.getComponentForElement(this); + if (component && component.dueCardsList) { + const cards = component.dueCardsList(); + return cards && cards.length > 0; + } + return false; + }, + searching() { + return false; // No longer using search, so always false + }, + hasQueryErrors() { + return false; // No longer using search, so always false + }, + errorMessages() { + return []; // No longer using search, so always empty + }, }); BlazeComponent.extendComponent({ @@ -62,71 +71,29 @@ BlazeComponent.extendComponent({ }, }).register('dueCardsViewChangePopup'); -class DueCardsComponent extends CardSearchPagedComponent { +class DueCardsComponent extends BlazeComponent { onCreated() { super.onCreated(); - // Add a small delay to ensure ReactiveCache is ready - this.searchRetryCount = 0; - this.maxRetries = 3; + this._cachedCards = null; + this._cachedTimestamp = null; + this.subscriptionHandle = null; - // Use a timeout to ensure the search runs after the component is fully initialized - Meteor.setTimeout(() => { - this.performSearch(); - }, 100); - } - - performSearch() { - if (process.env.DEBUG === 'true') { - console.log('Performing due cards search, attempt:', this.searchRetryCount + 1); - } - - // Check if user is authenticated - const currentUser = ReactiveCache.getCurrentUser(); - if (!currentUser) { - if (process.env.DEBUG === 'true') { - console.log('User not authenticated, waiting...'); + // Subscribe to the optimized due cards publication + this.autorun(() => { + const allUsers = this.dueCardsView() === 'all'; + if (this.subscriptionHandle) { + this.subscriptionHandle.stop(); } - Meteor.setTimeout(() => { - this.performSearch(); - }, 1000); - return; - } - - if (process.env.DEBUG === 'true') { - console.log('User authenticated:', currentUser.username); - } - - const queryParams = new QueryParams(); - queryParams.addPredicate(OPERATOR_HAS, { - field: PREDICATE_DUE_AT, - exists: true, - }); - // queryParams[OPERATOR_LIMIT] = 5; - queryParams.addPredicate(OPERATOR_SORT, { - name: PREDICATE_DUE_AT, - order: ORDER_ASCENDING, + this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers); }); + } - // Note: User filtering is handled server-side based on board membership - // The OPERATOR_USER filter is too restrictive as it only shows cards where - // the user is assigned or a member of the card, not the board - // if (Utils && Utils.dueCardsView && Utils.dueCardsView() !== 'all') { - // const currentUser = ReactiveCache.getCurrentUser(); - // if (currentUser && currentUser.username) { - // queryParams.addPredicate(OPERATOR_USER, currentUser.username); - // } - // } - - // Debug: Log the query parameters - if (process.env.DEBUG === 'true') { - console.log('Due cards query params:', queryParams.params); - console.log('Due cards query text:', queryParams.text); - console.log('Due cards has predicates:', queryParams.getPredicates('has')); - console.log('Due cards sort predicates:', queryParams.getPredicates('sort')); + onDestroyed() { + super.onDestroyed(); + if (this.subscriptionHandle) { + this.subscriptionHandle.stop(); } - - this.runGlobalSearch(queryParams); } dueCardsView() { @@ -140,36 +107,36 @@ class DueCardsComponent extends CardSearchPagedComponent { } dueCardsList() { - const results = this.getResults(); - console.log('results:', results); - const cards = []; - if (results) { - results.forEach(card => { - cards.push(card); + // Use cached results if available to avoid expensive re-sorting + if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) { + return this._cachedCards; + } + + // Get cards directly from the subscription (already sorted by the publication) + const cards = ReactiveCache.getCards({ + type: 'cardType-card', + archived: false, + dueAt: { $exists: true, $nin: [null, ''] } + }); + + // Filter cards based on user view preference + const allUsers = this.dueCardsView() === 'all'; + const currentUser = ReactiveCache.getCurrentUser(); + let filteredCards = cards; + + 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; }); } - // Sort by due date: oldest first (ascending order) - cards.sort((a, b) => { - // Handle null/undefined due dates by putting them at the end - const aDueAt = a.dueAt ? new Date(a.dueAt) : new Date('2100-12-31'); - const bDueAt = b.dueAt ? new Date(b.dueAt) : new Date('2100-12-31'); + // Cache the results for 5 seconds to avoid re-filtering on every render + this._cachedCards = filteredCards; + this._cachedTimestamp = Date.now(); - // Debug logging - if (process.env.DEBUG === 'true') { - console.log(`Comparing cards: "${a.title}" (${a.dueAt}) vs "${b.title}" (${b.dueAt})`); - console.log(`Parsed dates: ${aDueAt.toISOString()} vs ${bDueAt.toISOString()}`); - console.log(`Time difference: ${aDueAt.getTime() - bDueAt.getTime()}`); - } - - // Compare dates: if a is earlier than b, return negative (a comes first) - // if a is later than b, return positive (b comes first) - return aDueAt.getTime() - bDueAt.getTime(); - }); - - // eslint-disable-next-line no-console - console.log('cards sorted by due date (oldest first):', cards); - return cards; + return filteredCards; } } diff --git a/client/lib/cardSearch.js b/client/lib/cardSearch.js index 4b9c14ccd..a143965cf 100644 --- a/client/lib/cardSearch.js +++ b/client/lib/cardSearch.js @@ -179,11 +179,16 @@ export class CardSearchPagedComponent extends BlazeComponent { console.log('getResults - no sessionData or no cards array, trying direct card search'); } // Fallback: try to get cards directly from the client-side collection + // Use a more efficient query with limit and sort const selector = { type: 'cardType-card', dueAt: { $exists: true, $nin: [null, ''] } }; - const allCards = Cards.find(selector).fetch(); + const options = { + sort: { dueAt: 1 }, // Sort by due date ascending (oldest first) + limit: 100 // Limit to 100 cards for performance + }; + const allCards = Cards.find(selector, options).fetch(); if (process.env.DEBUG === 'true') { console.log('getResults - direct card search found:', allCards ? allCards.length : 0, 'cards'); } @@ -191,9 +196,6 @@ export class CardSearchPagedComponent extends BlazeComponent { if (allCards && allCards.length > 0) { allCards.forEach(card => { if (card && card._id) { - if (process.env.DEBUG === 'true') { - console.log('getResults - direct card:', card._id, card.title); - } cards.push(card); } }); diff --git a/server/publications/cards.js b/server/publications/cards.js index a47ec5f63..f3619e489 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -121,26 +121,69 @@ Meteor.publish('myCards', function(sessionId) { return ret; }); -// Meteor.publish('dueCards', function(sessionId, allUsers = false) { -// check(sessionId, String); -// check(allUsers, Boolean); -// -// // eslint-disable-next-line no-console -// // console.log('all users:', allUsers); -// -// const queryParams = { -// has: [{ field: 'dueAt', exists: true }], -// limit: 25, -// skip: 0, -// sort: { name: 'dueAt', order: 'des' }, -// }; -// -// if (!allUsers) { -// queryParams.users = [ReactiveCache.getCurrentUser().username]; -// } -// -// return buildQuery(sessionId, queryParams); -// }); +// Optimized due cards publication for better performance +Meteor.publish('dueCards', function(allUsers = false) { + check(allUsers, Boolean); + + const userId = this.userId; + if (!userId) { + return this.ready(); + } + + if (process.env.DEBUG === 'true') { + console.log('dueCards publication called for user:', userId, 'allUsers:', allUsers); + } + + // Get user's board memberships for efficient filtering + const userBoards = ReactiveCache.getBoards({ + members: userId + }).map(board => board._id); + + if (userBoards.length === 0) { + return this.ready(); + } + + // Build optimized selector + const selector = { + type: 'cardType-card', + archived: false, + dueAt: { $exists: true, $nin: [null, ''] }, + boardId: { $in: userBoards } + }; + + // Add user filtering if not showing all users + if (!allUsers) { + selector.$or = [ + { members: userId }, + { assignees: userId }, + { userId: userId } + ]; + } + + const options = { + sort: { dueAt: 1 }, // Sort by due date ascending (oldest first) + limit: 100, // Limit results for performance + fields: { + title: 1, + dueAt: 1, + boardId: 1, + listId: 1, + swimlaneId: 1, + members: 1, + assignees: 1, + userId: 1, + archived: 1, + type: 1 + } + }; + + if (process.env.DEBUG === 'true') { + console.log('dueCards selector:', JSON.stringify(selector, null, 2)); + console.log('dueCards options:', JSON.stringify(options, null, 2)); + } + + return Cards.find(selector, options); +}); Meteor.publish('globalSearch', function(sessionId, params, text) { check(sessionId, String);