cardDate.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. import { TAPi18n } from '/imports/i18n';
  2. import { DatePicker } from '/client/lib/datepicker';
  3. import {
  4. formatDateTime,
  5. formatDate,
  6. formatDateByUserPreference,
  7. formatTime,
  8. getISOWeek,
  9. isValidDate,
  10. isBefore,
  11. isAfter,
  12. isSame,
  13. add,
  14. subtract,
  15. startOf,
  16. endOf,
  17. format,
  18. parseDate,
  19. now,
  20. createDate,
  21. fromNow,
  22. calendar,
  23. diff
  24. } from '/imports/lib/dateUtils';
  25. // editCardReceivedDatePopup
  26. (class extends DatePicker {
  27. onCreated() {
  28. super.onCreated(formatDateTime(now()));
  29. this.data().getReceived() &&
  30. this.date.set(new Date(this.data().getReceived()));
  31. }
  32. _storeDate(date) {
  33. this.card.setReceived(formatDateTime(date));
  34. }
  35. _deleteDate() {
  36. this.card.unsetReceived();
  37. }
  38. }.register('editCardReceivedDatePopup'));
  39. // editCardStartDatePopup
  40. (class extends DatePicker {
  41. onCreated() {
  42. super.onCreated(formatDateTime(now()));
  43. this.data().getStart() && this.date.set(new Date(this.data().getStart()));
  44. }
  45. onRendered() {
  46. super.onRendered();
  47. // DatePicker base class handles initialization with native HTML inputs
  48. }
  49. _storeDate(date) {
  50. this.card.setStart(formatDateTime(date));
  51. }
  52. _deleteDate() {
  53. this.card.unsetStart();
  54. }
  55. }.register('editCardStartDatePopup'));
  56. // editCardDueDatePopup
  57. (class extends DatePicker {
  58. onCreated() {
  59. super.onCreated('1970-01-01 17:00:00');
  60. this.data().getDue() && this.date.set(new Date(this.data().getDue()));
  61. }
  62. onRendered() {
  63. super.onRendered();
  64. // DatePicker base class handles initialization with native HTML inputs
  65. }
  66. _storeDate(date) {
  67. this.card.setDue(formatDateTime(date));
  68. }
  69. _deleteDate() {
  70. this.card.unsetDue();
  71. }
  72. }.register('editCardDueDatePopup'));
  73. // editCardEndDatePopup
  74. (class extends DatePicker {
  75. onCreated() {
  76. super.onCreated(formatDateTime(now()));
  77. this.data().getEnd() && this.date.set(new Date(this.data().getEnd()));
  78. }
  79. onRendered() {
  80. super.onRendered();
  81. // DatePicker base class handles initialization with native HTML inputs
  82. }
  83. _storeDate(date) {
  84. this.card.setEnd(formatDateTime(date));
  85. }
  86. _deleteDate() {
  87. this.card.unsetEnd();
  88. }
  89. }.register('editCardEndDatePopup'));
  90. // Display received, start, due & end dates
  91. const CardDate = BlazeComponent.extendComponent({
  92. template() {
  93. return 'dateBadge';
  94. },
  95. onCreated() {
  96. const self = this;
  97. self.date = ReactiveVar();
  98. self.now = ReactiveVar(now());
  99. window.setInterval(() => {
  100. self.now.set(now());
  101. }, 60000);
  102. },
  103. showWeek() {
  104. return getISOWeek(this.date.get()).toString();
  105. },
  106. showWeekOfYear() {
  107. const user = ReactiveCache.getCurrentUser();
  108. if (!user) {
  109. // For non-logged-in users, week of year is not shown
  110. return false;
  111. }
  112. return user.isShowWeekOfYear();
  113. },
  114. showDate() {
  115. const currentUser = ReactiveCache.getCurrentUser();
  116. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  117. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  118. },
  119. showISODate() {
  120. return this.date.get().toISOString();
  121. },
  122. });
  123. class CardReceivedDate extends CardDate {
  124. onCreated() {
  125. super.onCreated();
  126. const self = this;
  127. self.autorun(() => {
  128. self.date.set(new Date(self.data().getReceived()));
  129. });
  130. }
  131. classes() {
  132. let classes = 'received-date ';
  133. const dueAt = this.data().getDue();
  134. const endAt = this.data().getEnd();
  135. const startAt = this.data().getStart();
  136. const theDate = this.date.get();
  137. const now = this.now.get();
  138. // Received date logic: if received date is after start, due, or end dates, it's overdue
  139. if (
  140. (startAt && isAfter(theDate, startAt)) ||
  141. (endAt && isAfter(theDate, endAt)) ||
  142. (dueAt && isAfter(theDate, dueAt))
  143. ) {
  144. classes += 'overdue';
  145. } else {
  146. classes += 'not-due';
  147. }
  148. return classes;
  149. }
  150. showTitle() {
  151. const currentUser = ReactiveCache.getCurrentUser();
  152. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  153. const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
  154. return `${TAPi18n.__('card-received-on')} ${formattedDate}`;
  155. }
  156. events() {
  157. return super.events().concat({
  158. 'click .js-edit-date': Popup.open('editCardReceivedDate'),
  159. });
  160. }
  161. }
  162. CardReceivedDate.register('cardReceivedDate');
  163. class CardStartDate extends CardDate {
  164. onCreated() {
  165. super.onCreated();
  166. const self = this;
  167. self.autorun(() => {
  168. self.date.set(new Date(self.data().getStart()));
  169. });
  170. }
  171. classes() {
  172. let classes = 'start-date ';
  173. const dueAt = this.data().getDue();
  174. const endAt = this.data().getEnd();
  175. const theDate = this.date.get();
  176. const now = this.now.get();
  177. // Start date logic: if start date is after due or end dates, it's overdue
  178. if ((endAt && isAfter(theDate, endAt)) || (dueAt && isAfter(theDate, dueAt))) {
  179. classes += 'overdue';
  180. } else if (isAfter(theDate, now)) {
  181. // Start date is in the future - not due yet
  182. classes += 'not-due';
  183. } else {
  184. // Start date is today or in the past - current/active
  185. classes += 'current';
  186. }
  187. return classes;
  188. }
  189. showTitle() {
  190. const currentUser = ReactiveCache.getCurrentUser();
  191. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  192. const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
  193. return `${TAPi18n.__('card-start-on')} ${formattedDate}`;
  194. }
  195. events() {
  196. return super.events().concat({
  197. 'click .js-edit-date': Popup.open('editCardStartDate'),
  198. });
  199. }
  200. }
  201. CardStartDate.register('cardStartDate');
  202. class CardDueDate extends CardDate {
  203. onCreated() {
  204. super.onCreated();
  205. const self = this;
  206. self.autorun(() => {
  207. self.date.set(new Date(self.data().getDue()));
  208. });
  209. }
  210. classes() {
  211. let classes = 'due-date ';
  212. const endAt = this.data().getEnd();
  213. const theDate = this.date.get();
  214. const now = this.now.get();
  215. // If there's an end date and it's before the due date, task is completed early
  216. if (endAt && isBefore(endAt, theDate)) {
  217. classes += 'completed-early';
  218. }
  219. // If there's an end date, don't show due date status since task is completed
  220. else if (endAt) {
  221. classes += 'completed';
  222. }
  223. // Due date logic based on current time
  224. else {
  225. const daysDiff = diff(theDate, now, 'days');
  226. if (daysDiff < 0) {
  227. // Due date is in the past - overdue
  228. classes += 'overdue';
  229. } else if (daysDiff <= 1) {
  230. // Due today or tomorrow - due soon
  231. classes += 'due-soon';
  232. } else {
  233. // Due date is more than 1 day away - not due yet
  234. classes += 'not-due';
  235. }
  236. }
  237. return classes;
  238. }
  239. showTitle() {
  240. const currentUser = ReactiveCache.getCurrentUser();
  241. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  242. const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
  243. return `${TAPi18n.__('card-due-on')} ${formattedDate}`;
  244. }
  245. events() {
  246. return super.events().concat({
  247. 'click .js-edit-date': Popup.open('editCardDueDate'),
  248. });
  249. }
  250. }
  251. CardDueDate.register('cardDueDate');
  252. class CardEndDate extends CardDate {
  253. onCreated() {
  254. super.onCreated();
  255. const self = this;
  256. self.autorun(() => {
  257. self.date.set(new Date(self.data().getEnd()));
  258. });
  259. }
  260. classes() {
  261. let classes = 'end-date ';
  262. const dueAt = this.data().getDue();
  263. const theDate = this.date.get();
  264. if (!dueAt) {
  265. // No due date set - just show as completed
  266. classes += 'completed';
  267. } else if (isBefore(theDate, dueAt)) {
  268. // End date is before due date - completed early
  269. classes += 'completed-early';
  270. } else if (isAfter(theDate, dueAt)) {
  271. // End date is after due date - completed late
  272. classes += 'completed-late';
  273. } else {
  274. // End date equals due date - completed on time
  275. classes += 'completed-on-time';
  276. }
  277. return classes;
  278. }
  279. showTitle() {
  280. return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`;
  281. }
  282. events() {
  283. return super.events().concat({
  284. 'click .js-edit-date': Popup.open('editCardEndDate'),
  285. });
  286. }
  287. }
  288. CardEndDate.register('cardEndDate');
  289. class CardCustomFieldDate extends CardDate {
  290. template() {
  291. return 'dateCustomField';
  292. }
  293. onCreated() {
  294. super.onCreated();
  295. const self = this;
  296. self.autorun(() => {
  297. self.date.set(new Date(self.data().value));
  298. });
  299. }
  300. showWeek() {
  301. return getISOWeek(this.date.get()).toString();
  302. }
  303. showWeekOfYear() {
  304. const user = ReactiveCache.getCurrentUser();
  305. if (!user) {
  306. // For non-logged-in users, week of year is not shown
  307. return false;
  308. }
  309. return user.isShowWeekOfYear();
  310. }
  311. showDate() {
  312. // this will start working once mquandalle:moment
  313. // is updated to at least moment.js 2.10.5
  314. // until then, the date is displayed in the "L" format
  315. return this.date.get().calendar(null, {
  316. sameElse: 'llll',
  317. });
  318. }
  319. showTitle() {
  320. const currentUser = ReactiveCache.getCurrentUser();
  321. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  322. const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
  323. return `${formattedDate}`;
  324. }
  325. classes() {
  326. return 'customfield-date';
  327. }
  328. events() {
  329. return [];
  330. }
  331. }
  332. CardCustomFieldDate.register('cardCustomFieldDate');
  333. (class extends CardReceivedDate {
  334. template() {
  335. return 'minicardReceivedDate';
  336. }
  337. showDate() {
  338. const currentUser = ReactiveCache.getCurrentUser();
  339. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  340. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  341. }
  342. }.register('minicardReceivedDate'));
  343. (class extends CardStartDate {
  344. template() {
  345. return 'minicardStartDate';
  346. }
  347. showDate() {
  348. const currentUser = ReactiveCache.getCurrentUser();
  349. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  350. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  351. }
  352. }.register('minicardStartDate'));
  353. (class extends CardDueDate {
  354. template() {
  355. return 'minicardDueDate';
  356. }
  357. showDate() {
  358. const currentUser = ReactiveCache.getCurrentUser();
  359. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  360. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  361. }
  362. }.register('minicardDueDate'));
  363. (class extends CardEndDate {
  364. template() {
  365. return 'minicardEndDate';
  366. }
  367. showDate() {
  368. const currentUser = ReactiveCache.getCurrentUser();
  369. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  370. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  371. }
  372. }.register('minicardEndDate'));
  373. (class extends CardCustomFieldDate {
  374. template() {
  375. return 'minicardCustomFieldDate';
  376. }
  377. showDate() {
  378. const currentUser = ReactiveCache.getCurrentUser();
  379. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  380. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  381. }
  382. }.register('minicardCustomFieldDate'));
  383. class VoteEndDate extends CardDate {
  384. onCreated() {
  385. super.onCreated();
  386. const self = this;
  387. self.autorun(() => {
  388. self.date.set(new Date(self.data().getVoteEnd()));
  389. });
  390. }
  391. classes() {
  392. const classes = 'end-date' + ' ';
  393. return classes;
  394. }
  395. showDate() {
  396. const currentUser = ReactiveCache.getCurrentUser();
  397. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  398. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  399. }
  400. showTitle() {
  401. return `${TAPi18n.__('card-end-on')} ${this.date.get().toLocaleString()}`;
  402. }
  403. events() {
  404. return super.events().concat({
  405. 'click .js-edit-date': Popup.open('editVoteEndDate'),
  406. });
  407. }
  408. }
  409. VoteEndDate.register('voteEndDate');
  410. class PokerEndDate extends CardDate {
  411. onCreated() {
  412. super.onCreated();
  413. const self = this;
  414. self.autorun(() => {
  415. self.date.set(new Date(self.data().getPokerEnd()));
  416. });
  417. }
  418. classes() {
  419. const classes = 'end-date' + ' ';
  420. return classes;
  421. }
  422. showDate() {
  423. const currentUser = ReactiveCache.getCurrentUser();
  424. const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
  425. return formatDateByUserPreference(this.date.get(), dateFormat, true);
  426. }
  427. showTitle() {
  428. return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`;
  429. }
  430. events() {
  431. return super.events().concat({
  432. 'click .js-edit-date': Popup.open('editPokerEndDate'),
  433. });
  434. }
  435. }
  436. PokerEndDate.register('pokerEndDate');