diff --git a/client/components/cards/cardDate.css b/client/components/cards/cardDate.css index 952cc15b7..3305c6617 100644 --- a/client/components/cards/cardDate.css +++ b/client/components/cards/cardDate.css @@ -94,7 +94,7 @@ /* Generic date badge and custom field date */ .card-date:not(.received-date):not(.start-date):not(.due-date):not(.end-date) time::before { - content: "📅"; /* Calendar - represents generic date */ + /*content: "📅"; // Calendar - represents generic date */ } .card-date time::before { font-size: inherit; diff --git a/client/components/cards/cardDate.jade b/client/components/cards/cardDate.jade index c19f45528..b477ff723 100644 --- a/client/components/cards/cardDate.jade +++ b/client/components/cards/cardDate.jade @@ -24,14 +24,14 @@ template(name="dateCustomField") template(name="minicardReceivedDate") if canModifyCard - a.js-edit-date.card-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") + a.js-edit-date.card-date.received-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") | {{showDate}} if showWeekOfYear b | {{showWeek}} else - a.card-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") + a.card-date.received-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") | {{showDate}} if showWeekOfYear @@ -40,14 +40,14 @@ template(name="minicardReceivedDate") template(name="minicardStartDate") if canModifyCard - a.js-edit-date.card-date(title="{{_ 'card-start'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") + a.js-edit-date.card-date.start-date(title="{{_ 'card-start'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") | {{showDate}} if showWeekOfYear b | {{showWeek}} else - a.card-date(title="{{_ 'card-start'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") + a.card-date.start-date(title="{{_ 'card-start'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") | {{showDate}} if showWeekOfYear @@ -56,14 +56,14 @@ template(name="minicardStartDate") template(name="minicardDueDate") if canModifyCard - a.js-edit-date.card-date(title="{{_ 'card-due'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") + a.js-edit-date.card-date.due-date(title="{{_ 'card-due'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") | {{showDate}} if showWeekOfYear b | {{showWeek}} else - a.card-date(title="{{_ 'card-due'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") + a.card-date.due-date(title="{{_ 'card-due'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") | {{showDate}} if showWeekOfYear @@ -72,14 +72,14 @@ template(name="minicardDueDate") template(name="minicardEndDate") if canModifyCard - a.js-edit-date.card-date(title="{{_ 'card-end'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") + a.js-edit-date.card-date.end-date(title="{{_ 'card-end'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") | {{showDate}} if showWeekOfYear b | {{showWeek}} else - a.card-date(title="{{_ 'card-end'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") + a.card-date.end-date(title="{{_ 'card-end'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") | {{showDate}} if showWeekOfYear diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index 451af183c..685504021 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -18,7 +18,8 @@ import { now, createDate, fromNow, - calendar + calendar, + diff } from '/imports/lib/dateUtils'; // editCardReceivedDatePopup @@ -169,9 +170,7 @@ class CardReceivedDate extends CardDate { } showTitle() { - return `${TAPi18n.__('card-received-on')} ${this.date - .get() - .format('LLLL')}`; + return `${TAPi18n.__('card-received-on')} ${format(this.date.get(), 'LLLL')}`; } events() { @@ -198,15 +197,15 @@ class CardStartDate extends CardDate { const theDate = this.date.get(); const now = this.now.get(); // if dueAt or endAt exist & are > startAt, startAt doesn't need to be flagged - if ((endAt && theDate.isAfter(endAt)) || (dueAt && theDate.isAfter(dueAt))) + if ((endAt && isAfter(theDate, endAt)) || (dueAt && isAfter(theDate, dueAt))) classes += 'long-overdue'; - else if (theDate.isAfter(now)) classes += ''; + else if (isAfter(theDate, now)) classes += ''; else classes += 'current'; return classes; } showTitle() { - return `${TAPi18n.__('card-start-on')} ${this.date.get().format('LLLL')}`; + return `${TAPi18n.__('card-start-on')} ${format(this.date.get(), 'LLLL')}`; } events() { @@ -232,17 +231,17 @@ class CardDueDate extends CardDate { const theDate = this.date.get(); const now = this.now.get(); // if the due date is after the end date, green - done early - if (endAt && theDate.isAfter(endAt)) classes += 'current'; + if (endAt && isAfter(theDate, endAt)) classes += 'current'; // if there is an end date, don't need to flag the due date else if (endAt) classes += ''; - else if (now.diff(theDate, 'days') >= 2) classes += 'long-overdue'; - else if (now.diff(theDate, 'minute') >= 0) classes += 'due'; - else if (now.diff(theDate, 'days') >= -1) classes += 'almost-due'; + else if (diff(now, theDate, 'days') >= 2) classes += 'long-overdue'; + else if (diff(now, theDate, 'minute') >= 0) classes += 'due'; + else if (diff(now, theDate, 'days') >= -1) classes += 'almost-due'; return classes; } showTitle() { - return `${TAPi18n.__('card-due-on')} ${this.date.get().format('LLLL')}`; + return `${TAPi18n.__('card-due-on')} ${format(this.date.get(), 'LLLL')}`; } events() { @@ -267,13 +266,13 @@ class CardEndDate extends CardDate { const dueAt = this.data().getDue(); const theDate = this.date.get(); if (!dueAt) classes += ''; - else if (theDate.isBefore(dueAt)) classes += 'current'; - else if (theDate.isAfter(dueAt)) classes += 'due'; + else if (isBefore(theDate, dueAt)) classes += 'current'; + else if (isAfter(theDate, dueAt)) classes += 'due'; return classes; } showTitle() { - return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`; + return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`; } events() { @@ -315,7 +314,7 @@ class CardCustomFieldDate extends CardDate { } showTitle() { - return `${this.date.get().format('LLLL')}`; + return `${format(this.date.get(), 'LLLL')}`; } classes() { @@ -418,10 +417,10 @@ class PokerEndDate extends CardDate { return classes; } showDate() { - return this.date.get().format('l LT'); + return format(this.date.get(), 'l LT'); } showTitle() { - return `${TAPi18n.__('card-end-on')} ${this.date.get().format('LLLL')}`; + return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`; } events() { diff --git a/client/components/cards/minicard.css b/client/components/cards/minicard.css index 637be7763..b6007c7b3 100644 --- a/client/components/cards/minicard.css +++ b/client/components/cards/minicard.css @@ -169,6 +169,63 @@ margin-right: 0.4vw; } +/* Unicode icons for minicard dates - matching cardDate.css */ +.minicard .card-date.end-date time::before { + content: "🏁"; /* Finish flag - represents end/completion */ +} +.minicard .card-date.due-date time::before { + content: "⏰"; /* Alarm clock - represents due/deadline */ +} +.minicard .card-date.start-date time::before { + content: "🚀"; /* Rocket - represents start/launch */ +} +.minicard .card-date.received-date time::before { + content: "📥"; /* Inbox tray - represents received/incoming */ +} + +.minicard .card-date time::before { + font-size: inherit; + margin-right: 0.3em; + display: inline-block; +} + +/* Date type specific colors for minicards - matching cardDate.css */ +.minicard .card-date.received-date { + background-color: #dbdbdb; /* Grey for received - same as base card-date */ +} + +.minicard .card-date.received-date:hover, +.minicard .card-date.received-date.is-active { + background-color: #b3b3b3; +} + +.minicard .card-date.start-date { + background-color: #3cb500; /* Green for start */ +} + +.minicard .card-date.start-date:hover, +.minicard .card-date.start-date.is-active { + background-color: #2d8f00; +} + +.minicard .card-date.due-date { + background-color: #ff9f19; /* Orange for due */ +} + +.minicard .card-date.due-date:hover, +.minicard .card-date.due-date.is-active { + background-color: #e68a00; +} + +.minicard .card-date.end-date { + background-color: #a632db; /* Purple for end */ +} + +.minicard .card-date.end-date:hover, +.minicard .card-date.end-date.is-active { + background-color: #8a2bb8; +} + /* Font Awesome icons in minicard dates */ .minicard .card-date i.fa { margin-right: 0.3vw; diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index 686fd5eac..402b651c5 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -12,11 +12,8 @@ template(name="minicard") a.minicard-details-menu.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") | ☰ .dates if getReceived - unless getStart - unless getDue - unless getEnd - .date - +minicardReceivedDate + .date + +minicardReceivedDate if getStart .date +minicardStartDate diff --git a/imports/lib/dateUtils.js b/imports/lib/dateUtils.js index de7d95ea7..2e781befc 100644 --- a/imports/lib/dateUtils.js +++ b/imports/lib/dateUtils.js @@ -304,6 +304,10 @@ export function format(date, format = 'L') { return d.toLocaleString(); case 'llll': return d.toLocaleString(); + case 'LLLL': + return d.toLocaleString(); + case 'l LT': + return `${month}/${day}/${year} ${hours}:${minutes}`; case 'YYYY-MM-DD': return `${year}-${month}-${day}`; case 'YYYY-MM-DD HH:mm': @@ -490,3 +494,40 @@ export function calendar(date, now = new Date()) { return format(d, 'L'); } + +/** + * Calculate the difference between two dates in the specified unit + * @param {Date|string} date1 - First date + * @param {Date|string} date2 - Second date + * @param {string} unit - Unit of measurement ('millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year') + * @returns {number} Difference in the specified unit + */ +export function diff(date1, date2, unit = 'millisecond') { + const d1 = new Date(date1); + const d2 = new Date(date2); + + if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return 0; + + const diffMs = d1.getTime() - d2.getTime(); + + switch (unit) { + case 'millisecond': + return diffMs; + case 'second': + return Math.floor(diffMs / 1000); + case 'minute': + return Math.floor(diffMs / (1000 * 60)); + case 'hour': + return Math.floor(diffMs / (1000 * 60 * 60)); + case 'day': + return Math.floor(diffMs / (1000 * 60 * 60 * 24)); + case 'week': + return Math.floor(diffMs / (1000 * 60 * 60 * 24 * 7)); + case 'month': + return Math.floor(diffMs / (1000 * 60 * 60 * 24 * 30)); + case 'year': + return Math.floor(diffMs / (1000 * 60 * 60 * 24 * 365)); + default: + return diffMs; + } +}