dueCards.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import { BlazeComponent } from 'meteor/peerlibrary:blaze-components';
  3. import { TAPi18n } from '/imports/i18n';
  4. // const subManager = new SubsManager();
  5. BlazeComponent.extendComponent({
  6. dueCardsView() {
  7. // eslint-disable-next-line no-console
  8. // console.log('sort:', Utils.dueCardsView());
  9. return Utils && Utils.dueCardsView ? Utils.dueCardsView() : 'me';
  10. },
  11. events() {
  12. return [
  13. {
  14. 'click .js-due-cards-view-change': Popup.open('dueCardsViewChange'),
  15. },
  16. ];
  17. },
  18. }).register('dueCardsHeaderBar');
  19. Template.dueCards.helpers({
  20. userId() {
  21. return Meteor.userId();
  22. },
  23. dueCardsList() {
  24. const component = BlazeComponent.getComponentForElement(this.firstNode);
  25. if (component && component.dueCardsList) {
  26. return component.dueCardsList();
  27. }
  28. return [];
  29. },
  30. hasResults() {
  31. const component = BlazeComponent.getComponentForElement(this.firstNode);
  32. if (component && component.hasResults) {
  33. return component.hasResults.get();
  34. }
  35. return false;
  36. },
  37. searching() {
  38. const component = BlazeComponent.getComponentForElement(this.firstNode);
  39. if (component && component.isLoading) {
  40. return component.isLoading.get();
  41. }
  42. return true; // Show loading by default
  43. },
  44. hasQueryErrors() {
  45. return false; // No longer using search, so always false
  46. },
  47. errorMessages() {
  48. return []; // No longer using search, so always empty
  49. },
  50. cardsCount() {
  51. const component = BlazeComponent.getComponentForElement(this.firstNode);
  52. if (component && component.cardsCount) {
  53. return component.cardsCount();
  54. }
  55. return 0;
  56. },
  57. resultsText() {
  58. const component = BlazeComponent.getComponentForElement(this.firstNode);
  59. if (component && component.resultsText) {
  60. return component.resultsText();
  61. }
  62. return '';
  63. },
  64. });
  65. BlazeComponent.extendComponent({
  66. events() {
  67. return [
  68. {
  69. 'click .js-due-cards-view-me'() {
  70. if (Utils && Utils.setDueCardsView) {
  71. Utils.setDueCardsView('me');
  72. }
  73. Popup.back();
  74. },
  75. 'click .js-due-cards-view-all'() {
  76. if (Utils && Utils.setDueCardsView) {
  77. Utils.setDueCardsView('all');
  78. }
  79. Popup.back();
  80. },
  81. },
  82. ];
  83. },
  84. }).register('dueCardsViewChangePopup');
  85. class DueCardsComponent extends BlazeComponent {
  86. onCreated() {
  87. super.onCreated();
  88. this._cachedCards = null;
  89. this._cachedTimestamp = null;
  90. this.subscriptionHandle = null;
  91. this.isLoading = new ReactiveVar(true);
  92. this.hasResults = new ReactiveVar(false);
  93. this.searching = new ReactiveVar(false);
  94. // Subscribe to the optimized due cards publication
  95. this.autorun(() => {
  96. const allUsers = this.dueCardsView() === 'all';
  97. if (this.subscriptionHandle) {
  98. this.subscriptionHandle.stop();
  99. }
  100. this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers);
  101. // Update loading state based on subscription
  102. this.autorun(() => {
  103. if (this.subscriptionHandle && this.subscriptionHandle.ready()) {
  104. if (process.env.DEBUG === 'true') {
  105. console.log('dueCards: subscription ready, loading data...');
  106. }
  107. this.isLoading.set(false);
  108. const cards = this.dueCardsList();
  109. this.hasResults.set(cards && cards.length > 0);
  110. } else {
  111. if (process.env.DEBUG === 'true') {
  112. console.log('dueCards: subscription not ready, showing loading...');
  113. }
  114. this.isLoading.set(true);
  115. this.hasResults.set(false);
  116. }
  117. });
  118. });
  119. }
  120. onDestroyed() {
  121. super.onDestroyed();
  122. if (this.subscriptionHandle) {
  123. this.subscriptionHandle.stop();
  124. }
  125. }
  126. dueCardsView() {
  127. // eslint-disable-next-line no-console
  128. //console.log('sort:', Utils.dueCardsView());
  129. return Utils && Utils.dueCardsView ? Utils.dueCardsView() : 'me';
  130. }
  131. sortByBoard() {
  132. return this.dueCardsView() === 'board';
  133. }
  134. hasResults() {
  135. return this.hasResults.get();
  136. }
  137. cardsCount() {
  138. const cards = this.dueCardsList();
  139. return cards ? cards.length : 0;
  140. }
  141. resultsText() {
  142. const count = this.cardsCount();
  143. if (count === 1) {
  144. return TAPi18n.__('one-card-found');
  145. } else {
  146. // Get the translated text and manually replace %s with the count
  147. const baseText = TAPi18n.__('n-cards-found');
  148. const result = baseText.replace('%s', count);
  149. if (process.env.DEBUG === 'true') {
  150. console.log('dueCards: base text:', baseText, 'count:', count, 'result:', result);
  151. }
  152. return result;
  153. }
  154. }
  155. dueCardsList() {
  156. // Check if subscription is ready
  157. if (!this.subscriptionHandle || !this.subscriptionHandle.ready()) {
  158. if (process.env.DEBUG === 'true') {
  159. console.log('dueCards client: subscription not ready');
  160. }
  161. return [];
  162. }
  163. // Use cached results if available to avoid expensive re-sorting
  164. if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) {
  165. if (process.env.DEBUG === 'true') {
  166. console.log('dueCards client: using cached results,', this._cachedCards.length, 'cards');
  167. }
  168. return this._cachedCards;
  169. }
  170. // Get cards directly from the subscription (already sorted by the publication)
  171. const cards = ReactiveCache.getCards({
  172. type: 'cardType-card',
  173. archived: false,
  174. dueAt: { $exists: true, $nin: [null, ''] }
  175. });
  176. if (process.env.DEBUG === 'true') {
  177. console.log('dueCards client: found', cards.length, 'cards with due dates');
  178. console.log('dueCards client: cards details:', cards.map(c => ({
  179. id: c._id,
  180. title: c.title,
  181. dueAt: c.dueAt,
  182. boardId: c.boardId,
  183. members: c.members,
  184. assignees: c.assignees,
  185. userId: c.userId
  186. })));
  187. }
  188. // Filter cards based on user view preference
  189. const allUsers = this.dueCardsView() === 'all';
  190. const currentUser = ReactiveCache.getCurrentUser();
  191. let filteredCards = cards;
  192. if (process.env.DEBUG === 'true') {
  193. console.log('dueCards client: current user:', currentUser ? currentUser._id : 'none');
  194. console.log('dueCards client: showing all users:', allUsers);
  195. }
  196. if (!allUsers && currentUser) {
  197. filteredCards = cards.filter(card => {
  198. const isMember = card.members && card.members.includes(currentUser._id);
  199. const isAssignee = card.assignees && card.assignees.includes(currentUser._id);
  200. const isAuthor = card.userId === currentUser._id;
  201. const matches = isMember || isAssignee || isAuthor;
  202. if (process.env.DEBUG === 'true' && matches) {
  203. console.log('dueCards client: card matches user:', card.title, { isMember, isAssignee, isAuthor });
  204. }
  205. return matches;
  206. });
  207. }
  208. if (process.env.DEBUG === 'true') {
  209. console.log('dueCards client: filtered to', filteredCards.length, 'cards');
  210. }
  211. // Cache the results for 5 seconds to avoid re-filtering on every render
  212. this._cachedCards = filteredCards;
  213. this._cachedTimestamp = Date.now();
  214. // Update reactive variables
  215. this.hasResults.set(filteredCards && filteredCards.length > 0);
  216. this.isLoading.set(false);
  217. return filteredCards;
  218. }
  219. }
  220. DueCardsComponent.register('dueCards');