feat(work-view): show day of week in scheduled date group headers

Display weekday alongside date when tasks are grouped by scheduled date
(e.g., "Wed 1/15" instead of "2025-01-15"), making it easier to identify
weekends at a glance.

Closes #5941
This commit is contained in:
Johannes Millan 2026-01-09 13:40:00 +01:00
parent 2194cb952d
commit d475d88da3
4 changed files with 158 additions and 1 deletions

View file

@ -174,7 +174,7 @@
@if (customized.grouped) {
@for (group of customized.grouped | keyvalue; track group.key) {
<collapsible
[title]="group.key"
[title]="group.key | scheduledDateGroup"
[isIconBefore]="true"
[isExpanded]="true"
>

View file

@ -62,6 +62,7 @@ import { TaskSharedActions } from '../../root-store/meta/task-shared.actions';
import { TODAY_TAG } from '../tag/tag.const';
import { LS } from '../../core/persistence/storage-keys.const';
import { FinishDayBtnComponent } from './finish-day-btn/finish-day-btn.component';
import { ScheduledDateGroupPipe } from '../../ui/pipes/scheduled-date-group.pipe';
@Component({
selector: 'work-view',
@ -92,6 +93,7 @@ import { FinishDayBtnComponent } from './finish-day-btn/finish-day-btn.component
CollapsibleComponent,
CommonModule,
FinishDayBtnComponent,
ScheduledDateGroupPipe,
],
})
export class WorkViewComponent implements OnInit, OnDestroy {

View file

@ -0,0 +1,98 @@
import { TestBed } from '@angular/core/testing';
import { ScheduledDateGroupPipe } from './scheduled-date-group.pipe';
import { DateTimeFormatService } from '../../core/date-time-format/date-time-format.service';
import { TranslateService } from '@ngx-translate/core';
import { getDbDateStr } from '../../util/get-db-date-str';
describe('ScheduledDateGroupPipe', () => {
let pipe: ScheduledDateGroupPipe;
let mockDateTimeFormatService: jasmine.SpyObj<DateTimeFormatService>;
let mockTranslateService: jasmine.SpyObj<TranslateService>;
beforeEach(() => {
mockDateTimeFormatService = jasmine.createSpyObj('DateTimeFormatService', [], {
currentLocale: 'en-US',
});
mockTranslateService = jasmine.createSpyObj('TranslateService', ['instant']);
mockTranslateService.instant.and.callFake((key: string) => {
if (key === 'G.TODAY_TAG_TITLE') return 'Today';
return key;
});
TestBed.configureTestingModule({
providers: [
ScheduledDateGroupPipe,
{ provide: DateTimeFormatService, useValue: mockDateTimeFormatService },
{ provide: TranslateService, useValue: mockTranslateService },
],
});
pipe = TestBed.inject(ScheduledDateGroupPipe);
});
it('should create the pipe', () => {
expect(pipe).toBeTruthy();
});
it('should format date string with weekday', () => {
// Wednesday, January 15, 2025
const result = pipe.transform('2025-01-15');
expect(result).toMatch(/Wed/i);
expect(result).toContain('1');
expect(result).toContain('15');
});
it('should return "No date" unchanged', () => {
const result = pipe.transform('No date');
expect(result).toBe('No date');
});
it('should return null for null input', () => {
const result = pipe.transform(null);
expect(result).toBeNull();
});
it('should return null for undefined input', () => {
const result = pipe.transform(undefined);
expect(result).toBeNull();
});
it('should handle "Today" translation for today\'s date', () => {
const todayStr = getDbDateStr();
const result = pipe.transform(todayStr);
expect(result).toBe('Today');
expect(mockTranslateService.instant).toHaveBeenCalled();
});
it('should format weekend dates correctly', () => {
// Saturday, January 18, 2025
const saturdayResult = pipe.transform('2025-01-18');
expect(saturdayResult).toMatch(/Sat/i);
// Sunday, January 19, 2025
const sundayResult = pipe.transform('2025-01-19');
expect(sundayResult).toMatch(/Sun/i);
});
it('should respect configured locale for weekday names', () => {
// Change locale to German
Object.defineProperty(mockDateTimeFormatService, 'currentLocale', {
get: () => 'de-DE',
});
// Wednesday in German is "Mi" (Mittwoch)
const result = pipe.transform('2025-01-15');
expect(result).toMatch(/Mi/i);
});
it('should pass through non-date strings that are not "No date"', () => {
// "No tag", "No project" etc. should pass through unchanged
const result = pipe.transform('No tag');
expect(result).toBe('No tag');
});
it('should handle invalid date strings gracefully', () => {
const result = pipe.transform('invalid-date');
expect(result).toBe('invalid-date');
});
});

View file

@ -0,0 +1,57 @@
import { inject, Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { T } from 'src/app/t.const';
import { getDbDateStr } from '../../util/get-db-date-str';
import { dateStrToUtcDate } from '../../util/date-str-to-utc-date';
import { DateTimeFormatService } from '../../core/date-time-format/date-time-format.service';
const DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;
/**
* Pipe that formats scheduled date group keys with day of week.
* Input: YYYY-MM-DD date string or special strings like "No date"
* Output: "Wed 1/15" or "Today" or passthrough for non-date strings
*/
@Pipe({
name: 'scheduledDateGroup',
standalone: true,
pure: false,
})
export class ScheduledDateGroupPipe implements PipeTransform {
private _dateTimeFormatService = inject(DateTimeFormatService);
private _translateService = inject(TranslateService);
transform(value: unknown): string | null {
if (value === null || value === undefined) {
return null;
}
// Ensure value is a string
if (typeof value !== 'string') {
return String(value);
}
// Check if it's a date string (YYYY-MM-DD format)
if (!DATE_REGEX.test(value)) {
// Pass through non-date strings like "No date", "No tag", etc.
return value;
}
const todayStr = getDbDateStr();
if (value === todayStr) {
return this._translateService.instant(T.G.TODAY_TAG_TITLE);
}
const date = dateStrToUtcDate(value);
const locale = this._dateTimeFormatService.currentLocale;
// Format with weekday and date: "Wed 1/15"
const formatter = new Intl.DateTimeFormat(locale, {
weekday: 'short',
month: 'numeric',
day: 'numeric',
});
return formatter.format(date);
}
}