Browse Source

Added Date Format setting to Opened Card.

Thanks to xet7 !

Fixes #2011,
fixes #1176
Lauri Ojansivu 2 days ago
parent
commit
2dd3916f7e

+ 5 - 1
client/components/cards/cardCustomFields.js

@@ -1,8 +1,10 @@
 import { TAPi18n } from '/imports/i18n';
 import { DatePicker } from '/client/lib/datepicker';
+import { ReactiveCache } from '/imports/reactiveCache';
 import { 
   formatDateTime, 
   formatDate, 
+  formatDateByUserPreference,
   formatTime, 
   getISOWeek, 
   isValidDate, 
@@ -177,7 +179,9 @@ CardCustomField.register('cardCustomField');
   }
 
   showDate() {
-    return calendar(this.date.get());
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   }
 
   showISODate() {

+ 41 - 12
client/components/cards/cardDate.js

@@ -3,6 +3,7 @@ import { DatePicker } from '/client/lib/datepicker';
 import { 
   formatDateTime, 
   formatDate, 
+  formatDateByUserPreference,
   formatTime, 
   getISOWeek, 
   isValidDate, 
@@ -131,7 +132,9 @@ const CardDate = BlazeComponent.extendComponent({
   },
 
   showDate() {
-    return calendar(this.date.get());
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   },
 
   showISODate() {
@@ -166,7 +169,10 @@ class CardReceivedDate extends CardDate {
   }
 
   showTitle() {
-    return `${TAPi18n.__('card-received-on')} ${format(this.date.get(), 'LLLL')}`;
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
+    return `${TAPi18n.__('card-received-on')} ${formattedDate}`;
   }
 
   events() {
@@ -201,7 +207,10 @@ class CardStartDate extends CardDate {
   }
 
   showTitle() {
-    return `${TAPi18n.__('card-start-on')} ${format(this.date.get(), 'LLLL')}`;
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
+    return `${TAPi18n.__('card-start-on')} ${formattedDate}`;
   }
 
   events() {
@@ -237,7 +246,10 @@ class CardDueDate extends CardDate {
   }
 
   showTitle() {
-    return `${TAPi18n.__('card-due-on')} ${format(this.date.get(), 'LLLL')}`;
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
+    return `${TAPi18n.__('card-due-on')} ${formattedDate}`;
   }
 
   events() {
@@ -315,7 +327,10 @@ class CardCustomFieldDate extends CardDate {
   }
 
   showTitle() {
-    return `${format(this.date.get(), 'LLLL')}`;
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true);
+    return `${formattedDate}`;
   }
 
   classes() {
@@ -334,7 +349,9 @@ CardCustomFieldDate.register('cardCustomFieldDate');
   }
   
   showDate() {
-    return format(this.date.get(), 'L');
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   }
 }.register('minicardReceivedDate'));
 
@@ -344,7 +361,9 @@ CardCustomFieldDate.register('cardCustomFieldDate');
   }
   
   showDate() {
-    return format(this.date.get(), 'YYYY-MM-DD HH:mm');
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   }
 }.register('minicardStartDate'));
 
@@ -354,7 +373,9 @@ CardCustomFieldDate.register('cardCustomFieldDate');
   }
   
   showDate() {
-    return format(this.date.get(), 'YYYY-MM-DD HH:mm');
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   }
 }.register('minicardDueDate'));
 
@@ -364,7 +385,9 @@ CardCustomFieldDate.register('cardCustomFieldDate');
   }
   
   showDate() {
-    return format(this.date.get(), 'YYYY-MM-DD HH:mm');
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   }
 }.register('minicardEndDate'));
 
@@ -374,7 +397,9 @@ CardCustomFieldDate.register('cardCustomFieldDate');
   }
   
   showDate() {
-    return format(this.date.get(), 'L');
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   }
 }.register('minicardCustomFieldDate'));
 
@@ -391,7 +416,9 @@ class VoteEndDate extends CardDate {
     return classes;
   }
   showDate() {
-    return format(this.date.get(), 'L') + ' ' + format(this.date.get(), 'HH:mm');
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   }
   showTitle() {
     return `${TAPi18n.__('card-end-on')} ${this.date.get().toLocaleString()}`;
@@ -418,7 +445,9 @@ class PokerEndDate extends CardDate {
     return classes;
   }
   showDate() {
-    return format(this.date.get(), 'l LT');
+    const currentUser = ReactiveCache.getCurrentUser();
+    const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
+    return formatDateByUserPreference(this.date.get(), dateFormat, true);
   }
   showTitle() {
     return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`;

+ 28 - 0
client/components/cards/cardDetails.css

@@ -1,3 +1,31 @@
+/* Date Format Selector */
+.card-details-item-date-format {
+  margin-bottom: 10px;
+}
+
+.card-details-item-date-format .card-details-item-title {
+  font-size: 14px;
+  font-weight: bold;
+  margin-bottom: 5px;
+  color: #333;
+}
+
+.card-details-item-date-format .js-date-format-selector {
+  width: 100%;
+  padding: 8px;
+  border: 1px solid #ddd;
+  border-radius: 4px;
+  background-color: #fff;
+  font-size: 14px;
+  cursor: pointer;
+}
+
+.card-details-item-date-format .js-date-format-selector:focus {
+  outline: none;
+  border-color: #007cba;
+  box-shadow: 0 0 0 2px rgba(0, 124, 186, 0.2);
+}
+
 .assignee {
   border-radius: 3px;
   display: block;

+ 10 - 0
client/components/cards/cardDetails.jade

@@ -113,6 +113,16 @@ template(name="cardDetails")
         if currentBoard.hasAnyAllowsDate
           hr
 
+          .card-details-item.card-details-item-date-format
+            h3.card-details-item-title
+              | 📅
+              | {{_ 'date-format'}}
+            .card-details-item-content
+              select.js-date-format-selector
+                option(value="YYYY-MM-DD" selected="{{#if isDateFormat 'YYYY-MM-DD'}}selected{{/if}}") {{_ 'date-format-yyyy-mm-dd'}}
+                option(value="DD-MM-YYYY" selected="{{#if isDateFormat 'DD-MM-YYYY'}}selected{{/if}}") {{_ 'date-format-dd-mm-yyyy'}}
+                option(value="MM-DD-YYYY" selected="{{#if isDateFormat 'MM-DD-YYYY'}}selected{{/if}}") {{_ 'date-format-mm-dd-yyyy'}}
+
         if currentBoard.allowsReceivedDate
           .card-details-item.card-details-item-received
             h3.card-details-item-title

+ 9 - 0
client/components/cards/cardDetails.js

@@ -306,6 +306,10 @@ BlazeComponent.extendComponent({
           const $tooltip = this.$('.card-details-header .copied-tooltip');
           Utils.showCopied(promise, $tooltip);
         },
+        'change .js-date-format-selector'(event) {
+          const dateFormat = event.target.value;
+          Meteor.call('changeDateFormat', dateFormat);
+        },
         'click .js-open-card-details-menu': Popup.open('cardDetailsActions'),
         'submit .js-card-description'(event) {
           event.preventDefault();
@@ -568,6 +572,11 @@ Template.cardDetails.helpers({
     let ret = !!Utils.getPopupCardId();
     return ret;
   },
+  isDateFormat(format) {
+    const currentUser = ReactiveCache.getCurrentUser();
+    if (!currentUser) return format === 'YYYY-MM-DD';
+    return currentUser.getDateFormat() === format;
+  },
   // Upload progress helpers
   hasActiveUploads() {
     return uploadProgressManager.hasActiveUploads(this._id);

+ 4 - 0
imports/i18n/data/en.i18n.json

@@ -354,6 +354,10 @@
   "custom-field-text": "Text",
   "custom-fields": "Custom Fields",
   "date": "Date",
+  "date-format": "Date Format",
+  "date-format-yyyy-mm-dd": "YYYY-MM-DD HH:MM",
+  "date-format-dd-mm-yyyy": "DD-MM-YYYY HH:MM", 
+  "date-format-mm-dd-yyyy": "MM-DD-YYYY HH:MM",
   "decline": "Decline",
   "default-avatar": "Default avatar",
   "delete": "Delete",

+ 38 - 0
imports/lib/dateUtils.js

@@ -36,6 +36,44 @@ export function formatDate(date) {
   return `${year}-${month}-${day}`;
 }
 
+/**
+ * Format a date according to user's preferred format
+ * @param {Date|string} date - Date to format
+ * @param {string} format - Format string (YYYY-MM-DD, DD-MM-YYYY, MM-DD-YYYY)
+ * @param {boolean} includeTime - Whether to include time (HH:MM)
+ * @returns {string} Formatted date string
+ */
+export function formatDateByUserPreference(date, format = 'YYYY-MM-DD', includeTime = true) {
+  const d = new Date(date);
+  if (isNaN(d.getTime())) return '';
+  
+  const year = d.getFullYear();
+  const month = String(d.getMonth() + 1).padStart(2, '0');
+  const day = String(d.getDate()).padStart(2, '0');
+  const hours = String(d.getHours()).padStart(2, '0');
+  const minutes = String(d.getMinutes()).padStart(2, '0');
+  
+  let dateString;
+  switch (format) {
+    case 'DD-MM-YYYY':
+      dateString = `${day}-${month}-${year}`;
+      break;
+    case 'MM-DD-YYYY':
+      dateString = `${month}-${day}-${year}`;
+      break;
+    case 'YYYY-MM-DD':
+    default:
+      dateString = `${year}-${month}-${day}`;
+      break;
+  }
+  
+  if (includeTime) {
+    return `${dateString} ${hours}:${minutes}`;
+  }
+  
+  return dateString;
+}
+
 /**
  * Format a time to HH:mm format
  * @param {Date|string} date - Date to format

+ 26 - 0
models/users.js

@@ -465,6 +465,15 @@ Users.attachSchema(
       type: Boolean,
       defaultValue: true,
     },
+    'profile.dateFormat': {
+      /**
+       * User-specified date format for displaying dates (includes time HH:MM).
+       */
+      type: String,
+      optional: true,
+      allowedValues: ['YYYY-MM-DD', 'DD-MM-YYYY', 'MM-DD-YYYY'],
+      defaultValue: 'YYYY-MM-DD',
+    },
     'profile.zoomLevel': {
       /**
        * User-specified zoom level for board view (1.0 = 100%, 1.5 = 150%, etc.)
@@ -1049,6 +1058,11 @@ Users.helpers({
     return profile.startDayOfWeek;
   },
 
+  getDateFormat() {
+    const profile = this.profile || {};
+    return profile.dateFormat || 'YYYY-MM-DD';
+  },
+
   getTemplatesBoardId() {
     return (this.profile || {}).templatesBoardId;
   },
@@ -1452,6 +1466,14 @@ Users.mutations({
     };
   },
 
+  setDateFormat(dateFormat) {
+    return {
+      $set: {
+        'profile.dateFormat': dateFormat,
+      },
+    };
+  },
+
   setBoardView(view) {
     return {
       $set: {
@@ -1597,6 +1619,10 @@ Meteor.methods({
     check(startDay, Number);
     ReactiveCache.getCurrentUser().setStartDayOfWeek(startDay);
   },
+  changeDateFormat(dateFormat) {
+    check(dateFormat, String);
+    ReactiveCache.getCurrentUser().setDateFormat(dateFormat);
+  },
   applyListWidth(boardId, listId, width, constraint) {
     check(boardId, String);
     check(listId, String);