feat(tasks): show deadlines in scheduled list page and planner task

This commit is contained in:
Johannes Millan 2026-03-13 11:44:29 +01:00
parent b1a4d9326f
commit 9e8f45b1f8
7 changed files with 114 additions and 1 deletions

View file

@ -44,6 +44,17 @@
<div class="title">{{ task.title }}</div>
@if (task.deadlineDay || task.deadlineWithTime) {
<div class="deadline-indicator">
<mat-icon>event_busy</mat-icon>
@if (task.deadlineWithTime) {
<span>{{ task.deadlineWithTime | shortDate2 }}</span>
} @else if (task.deadlineDay) {
<span>{{ task.deadlineDay | shortDate2 }}</span>
}
</div>
}
<tag-list
[isShowProjectTagAlways]="true"
[tagsToHide]="tagsToHide()"

View file

@ -234,6 +234,25 @@
margin-left: -6px;
}
.deadline-indicator {
display: flex;
align-items: center;
padding-left: 4px;
font-size: var(--planner-font-size-smaller-mobile);
opacity: 0.7;
@include mq(xs) {
font-size: var(--planner-font-size-smaller);
}
mat-icon {
font-size: 14px;
height: 14px;
width: 14px;
margin-right: 2px;
}
}
:host ::ng-deep .due-date + .planner-time-remaining-shared {
display: none;
}

View file

@ -567,6 +567,26 @@ export const selectAllTasksWithDeadlineReminder = createSelector(
},
);
export const selectAllUndoneTasksWithDeadlineSorted = createSelector(
selectAllTasks,
(tasks: Task[]): Task[] => {
return tasks
.filter(
(task) =>
task &&
!task.isDone &&
(task.deadlineDay || typeof task.deadlineWithTime === 'number'),
)
.sort((a, b) => {
const aTime =
a.deadlineWithTime || new Date(a.deadlineDay + 'T23:59:59').getTime();
const bTime =
b.deadlineWithTime || new Date(b.deadlineDay + 'T23:59:59').getTime();
return aTime - bTime;
});
},
);
export const selectUndoneTasksWithDueDayNoReminder = createSelector(
selectAllTasks,
(tasks: Task[]): Task[] => {

View file

@ -133,4 +133,50 @@
} @else {
<full-page-spinner></full-page-spinner>
}
@if (tasksWithDeadlines$ | async; as tasksWithDeadlines) {
<section
class="component-wrapper"
style="margin-top: 24px"
>
<h2>{{ T.SCHEDULE.TASKS_WITH_DEADLINES | translate }}</h2>
@if (!tasksWithDeadlines?.length) {
<p class="no-scheduled-tasks">{{ T.SCHEDULE.NO_DEADLINES | translate }}</p>
}
<div
[@standardList]="tasksWithDeadlines?.length"
class="tasks"
>
@for (task of tasksWithDeadlines; track task.id) {
<planner-task [task]="task">
<div
(click)="editDeadline(task, $event)"
class="due-date"
mat-ripple
>
<div class="date-and-time-left">
@if (task.deadlineWithTime) {
<div class="date hide-xs">
{{ task.deadlineWithTime | localeDate: 'EE, d MMM, ' }}
{{ task.deadlineWithTime | shortTime }}
</div>
<div class="date show-xs-only">
{{ task.deadlineWithTime | localeDate: 'd MMM, ' }},
{{ task.deadlineWithTime | shortTime }}
</div>
<div class="time-left">
({{ task.deadlineWithTime | humanizeTimestamp }})
</div>
} @else if (task.deadlineDay) {
<div class="date">{{ task.deadlineDay | localeDate: 'EE, d MMM' }}</div>
}
</div>
<mat-icon>event_busy</mat-icon>
</div>
</planner-task>
}
</div>
</section>
} @else {
<full-page-spinner></full-page-spinner>
}
</div>

View file

@ -12,6 +12,7 @@ import { TranslatePipe, TranslateService } from '@ngx-translate/core';
import { DialogEditTaskRepeatCfgComponent } from '../../features/task-repeat-cfg/dialog-edit-task-repeat-cfg/dialog-edit-task-repeat-cfg.component';
import { TaskRepeatCfgService } from '../../features/task-repeat-cfg/task-repeat-cfg.service';
import { DialogScheduleTaskComponent } from '../../features/planner/dialog-schedule-task/dialog-schedule-task.component';
import { DialogDeadlineComponent } from '../../features/tasks/dialog-deadline/dialog-deadline.component';
import { DateTimeFormatService } from '../../core/date-time-format/date-time-format.service';
import { MatCard, MatCardContent } from '@angular/material/card';
import { TaskTitleComponent } from '../../ui/task-title/task-title.component';
@ -26,6 +27,7 @@ import { PlannerTaskComponent } from '../../features/planner/planner-task/planne
import {
selectAllTasksWithDueTimeSorted,
selectAllUndoneTasksWithDueDay,
selectAllUndoneTasksWithDeadlineSorted,
} from '../../features/tasks/store/task.selectors';
import { selectTaskRepeatCfgsSortedByTitleAndProject } from '../../features/task-repeat-cfg/store/task-repeat-cfg.selectors';
import { getNextRepeatOccurrence } from '../../features/task-repeat-cfg/store/get-next-repeat-occurrence.util';
@ -69,6 +71,7 @@ export class ScheduledListPageComponent {
taskRepeatCfgs$ = this._store.select(selectTaskRepeatCfgsSortedByTitleAndProject);
tasksPlannedForDays$ = this._store.select(selectAllUndoneTasksWithDueDay);
tasksPlannedWithTime$ = this._store.select(selectAllTasksWithDueTimeSorted);
tasksWithDeadlines$ = this._store.select(selectAllUndoneTasksWithDeadlineSorted);
editReminder(task: TaskCopy, ev: MouseEvent): void {
ev.preventDefault();
@ -79,6 +82,16 @@ export class ScheduledListPageComponent {
});
}
editDeadline(task: TaskCopy, ev: MouseEvent): void {
ev.preventDefault();
ev.stopPropagation();
this._matDialog.open(DialogDeadlineComponent, {
autoFocus: false,
restoreFocus: true,
data: { task },
});
}
editTaskRepeatCfg(repeatCfg: TaskRepeatCfg): void {
this._matDialog.open(DialogEditTaskRepeatCfgComponent, {
restoreFocus: false,

View file

@ -2579,6 +2579,8 @@ const T = {
SCHEDULED_TASKS: 'SCHEDULE.SCHEDULED_TASKS',
SCHEDULED_TASKS_WITH_TIME: 'SCHEDULE.SCHEDULED_TASKS_WITH_TIME',
START_TASK: 'SCHEDULE.START_TASK',
TASKS_WITH_DEADLINES: 'SCHEDULE.TASKS_WITH_DEADLINES',
NO_DEADLINES: 'SCHEDULE.NO_DEADLINES',
},
'Share Chart': 'Share Chart',
'Share Heatmap': 'Share Heatmap',

View file

@ -2523,7 +2523,9 @@
"REPEATED_TASKS": "Recurring Tasks",
"SCHEDULED_TASKS": "Scheduled Tasks",
"SCHEDULED_TASKS_WITH_TIME": "Scheduled Tasks with Start Time",
"START_TASK": "Start task and remove reminder"
"START_TASK": "Start task and remove reminder",
"TASKS_WITH_DEADLINES": "Tasks with Deadlines",
"NO_DEADLINES": "There are currently no tasks with deadlines. You can set a deadline via the task's context menu or detail panel."
},
"Share Chart": "",
"Share Heatmap": "",