From dd413518f51c3aab206450bdd9e28d2246b7a22c Mon Sep 17 00:00:00 2001 From: Kalista Payne Date: Tue, 12 May 2026 16:28:40 -0500 Subject: [PATCH] Fiz/summaries (#15653) * dailies monthly weeks of month summary update monthly day-of-week scheduling summary, add scheduling summary to task form, and add 5th week warning * formatting * scheduling warning and summary UI updates * fix(svg): use extant icon --------- Co-authored-by: Hafiz --- .../client/src/components/tasks/taskModal.vue | 136 ++++++++++++++++++ .../src/components/tasks/taskSummary.vue | 44 +++--- website/common/locales/en/tasks.json | 10 ++ 3 files changed, 166 insertions(+), 24 deletions(-) diff --git a/website/client/src/components/tasks/taskModal.vue b/website/client/src/components/tasks/taskModal.vue index 6702bb6270..2aa41da203 100644 --- a/website/client/src/components/tasks/taskModal.vue +++ b/website/client/src/components/tasks/taskModal.vue @@ -412,6 +412,25 @@ +

+ {{ schedulingSummary }} +

+
+ + +
task.repeat[d]); + if (activeDays.length > 0) { + details = ` on ${activeDays.map(d => dayNames[d]).join(', ')}`; + } + } else if (task.frequency === 'monthly' && task.startDate) { + const dayOfMonth = moment(task.startDate).date(); + if (task.weeksOfMonth && task.weeksOfMonth.length > 0) { + const weekNum = task.weeksOfMonth[0] + 1; + const weekStr = String(weekNum); + const lastDigit = weekStr.slice(-1); + let suffix = 'th'; + if (lastDigit === '1' && weekStr !== '11') suffix = 'st'; + if (lastDigit === '2' && weekStr !== '12') suffix = 'nd'; + if (lastDigit === '3' && weekStr !== '13') suffix = 'rd'; + const dayName = moment(task.startDate).format('dddd'); + details = ` on the ${weekNum}${suffix} ${dayName} of the month`; + } else if (task.daysOfMonth && task.daysOfMonth.length > 0) { + const dom = task.daysOfMonth[0]; + const domStr = String(dom); + const lastDigit = domStr.slice(-1); + let suffix = 'th'; + if (lastDigit === '1' && domStr !== '11') suffix = 'st'; + if (lastDigit === '2' && domStr !== '12') suffix = 'nd'; + if (lastDigit === '3' && domStr !== '13') suffix = 'rd'; + details = ` on the ${dom}${suffix}`; + } else { + const domStr = String(dayOfMonth); + const lastDigit = domStr.slice(-1); + let suffix = 'th'; + if (lastDigit === '1' && domStr !== '11') suffix = 'st'; + if (lastDigit === '2' && domStr !== '12') suffix = 'nd'; + if (lastDigit === '3' && domStr !== '13') suffix = 'rd'; + details = ` on the ${dayOfMonth}${suffix}`; + } + } else if (task.frequency === 'yearly' && task.startDate) { + details = ` on ${moment(task.startDate).format('MMMM Do')}`; + } + + return `${this.$t('repeats')} ${interval}${details}`; + }, + schedulingWarning () { + if (!this.task || this.task.type !== 'daily') return ''; + const { task } = this; + if (task.frequency === 'monthly' + && task.weeksOfMonth && task.weeksOfMonth.length > 0 + && task.weeksOfMonth[0] === 4 + && task.startDate) { + const dayName = moment(task.startDate).format('dddd'); + return this.$t('fifthWeekWarning', { day: dayName }); + } + return ''; + }, repeatsOn: { get () { let repeatsOn = 'dayOfMonth'; diff --git a/website/client/src/components/tasks/taskSummary.vue b/website/client/src/components/tasks/taskSummary.vue index fce5f5e579..b92176c0a5 100644 --- a/website/client/src/components/tasks/taskSummary.vue +++ b/website/client/src/components/tasks/taskSummary.vue @@ -222,14 +222,22 @@ export default { return usernames; }, summarySentence () { + let fifthWeekWarning = ''; + if (this.task.type === 'daily' && this.task.frequency === 'monthly' + && this.task.weeksOfMonth && this.task.weeksOfMonth.length > 0 + && this.task.weeksOfMonth[0] === 4) { + const activeDays = keys(pickBy(this.task.repeat, value => value === true)); + const dayName = this.expandDayString[activeDays[0]]; + fifthWeekWarning = ` ${this.$t('fifthWeekWarning', { day: dayName })}`; + } if (this.task.type === 'daily' && moment().isBefore(this.task.startDate)) { return `This is ${this.formattedDifficulty(this.task.priority)} task that will repeat ${this.formattedRepeatInterval(this.task.frequency, this.task.everyX)}${this.formattedDays(this.task.frequency, this.task.repeat, this.task.daysOfMonth, this.task.weeksOfMonth, this.task.startDate)} - starting on ${moment(this.task.startDate).format('MM/DD/YYYY')}.`; + starting on ${moment(this.task.startDate).format('MM/DD/YYYY')}.${fifthWeekWarning}`; } if (this.task.type === 'daily') { return `This is ${this.formattedDifficulty(this.task.priority)} task that repeats - ${this.formattedRepeatInterval(this.task.frequency, this.task.everyX)}${this.formattedDays(this.task.frequency, this.task.repeat, this.task.daysOfMonth, this.task.weeksOfMonth, this.task.startDate)}.`; + ${this.formattedRepeatInterval(this.task.frequency, this.task.everyX)}${this.formattedDays(this.task.frequency, this.task.repeat, this.task.daysOfMonth, this.task.weeksOfMonth, this.task.startDate)}.${fifthWeekWarning}`; } if (this.task.date) { return `This is ${this.formattedDifficulty(this.task.priority)} task that is due ${moment(this.task.date).format('MM/DD/YYYY')}.`; @@ -287,25 +295,14 @@ export default { }); dayStringArray.push(''); } else if (weeksOfMonth.length > 0) { - switch (weeksOfMonth[0]) { - case 0: - dayStringArray.push('first'); - break; - case 1: - dayStringArray.push('second'); - break; - case 2: - dayStringArray.push('third'); - break; - case 3: - dayStringArray.push('fourth'); - break; - case 4: - dayStringArray.push('fifth'); - break; - default: - break; - } + const weekNum = weeksOfMonth[0] + 1; + const weekNumStr = String(weekNum); + const lastDigit = weekNumStr.slice(-1); + let ordinalSuffix = 'th'; + if (lastDigit === '1' && weekNumStr !== '11') ordinalSuffix = 'st'; + if (lastDigit === '2' && weekNumStr !== '12') ordinalSuffix = 'nd'; + if (lastDigit === '3' && weekNumStr !== '13') ordinalSuffix = 'rd'; + dayStringArray.push(`${weekNum}${ordinalSuffix}`); activeDays = keys(pickBy(repeat, value => value === true)); dayStringArray.push(` ${this.expandDayString[activeDays[0]]} of the month`); } @@ -343,9 +340,8 @@ export default { if (numericX === 2) return 'every other week'; return `every ${numericX} weeks`; case 'monthly': - if (numericX === 1) return 'every month'; - if (numericX === 2) return 'every other month'; - return `every ${numericX} months`; + if (numericX === 1) return `${this.$t('everyMonth')}`; + return `${this.$t('everyXMonths', { count: numericX })}`; case 'yearly': if (numericX === 1) return 'every year'; return `every ${everyX} years`; diff --git a/website/common/locales/en/tasks.json b/website/common/locales/en/tasks.json index 58cf93f8d6..f5c1e24992 100644 --- a/website/common/locales/en/tasks.json +++ b/website/common/locales/en/tasks.json @@ -123,6 +123,16 @@ "dayOfMonth": "Day of the Month", "month": "Month", "months": "Months", + "every": "every", + "everyDay": "every day", + "everyXDays": "every <%= count %> days", + "everyWeek": "every week", + "everyXWeeks": "every <%= count %> weeks", + "everyMonth": "every month", + "everyXMonths": "every <%= count %> months", + "everyYear": "every year", + "everyXYears": "every <%= count %> years", + "fifthWeekWarning": "This task will not appear due during months with fewer <%= day %>s", "week": "Week", "weeks": "Weeks", "year": "Year",