diff --git a/src/app/components/settings/settings-form/settings-form.component.html b/src/app/components/settings/settings-form/settings-form.component.html index 87a1805..fc6bf37 100644 --- a/src/app/components/settings/settings-form/settings-form.component.html +++ b/src/app/components/settings/settings-form/settings-form.component.html @@ -1,4 +1,4 @@ -
+
@if (workHoursPerWeek$ | async; as workHoursPerWeek) { @@ -6,6 +6,11 @@ }
+
+ +
Will set the end time to "now" each time you create a new entry in the dashboard.
+ +
diff --git a/src/app/components/settings/settings-form/settings-form.component.ts b/src/app/components/settings/settings-form/settings-form.component.ts index 3c5b055..85356d2 100644 --- a/src/app/components/settings/settings-form/settings-form.component.ts +++ b/src/app/components/settings/settings-form/settings-form.component.ts @@ -11,6 +11,7 @@ type SettingsModel = FormModel< Settings, { workPerDay: Replace>; + preFillEndTime: Replace>; } >; @@ -28,6 +29,7 @@ export class SettingsFormComponent implements OnInit { private readonly formBuilder = inject(FormBuilder); protected formGroup = this.formBuilder.group({ workPerDay: new FormControl('', { nonNullable: true }), + preFillEndTime: new FormControl(true, { nonNullable: true }), }); protected readonly workHoursPerWeek$ = this.formGroup.controls.workPerDay.valueChanges.pipe( map(workPerDay => multiplyDuration(parseTimeToDuration(workPerDay))), @@ -39,6 +41,7 @@ export class SettingsFormComponent implements OnInit { void Promise.resolve().then(() => this.formGroup.setValue({ workPerDay: millisecondsToHumanReadable(this.settings.workPerDay), + preFillEndTime: this.settings.preFillEndTime, }), ); } @@ -52,6 +55,7 @@ export class SettingsFormComponent implements OnInit { this.settingsChange.next({ workPerDay: parseTimeToDuration(formValue.workPerDay).toMillis(), + preFillEndTime: formValue.preFillEndTime, }); } } diff --git a/src/app/components/times/time-entry/time-entry.component.html b/src/app/components/times/time-entry/time-entry.component.html index d61f1fe..aba3ea2 100644 --- a/src/app/components/times/time-entry/time-entry.component.html +++ b/src/app/components/times/time-entry/time-entry.component.html @@ -1,4 +1,4 @@ - + @if (!isTodayMode) {
@@ -13,6 +13,14 @@
+
+ + +
No workday
diff --git a/src/app/components/times/time-entry/time-entry.component.ts b/src/app/components/times/time-entry/time-entry.component.ts index 8fc8e96..886afe7 100644 --- a/src/app/components/times/time-entry/time-entry.component.ts +++ b/src/app/components/times/time-entry/time-entry.component.ts @@ -1,14 +1,15 @@ -import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, Output } from '@angular/core'; +import { ChangeDetectionStrategy, Component, EventEmitter, inject, Input, OnInit, Output } from '@angular/core'; import { FormBuilder, FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms'; import { FormModel, Replace } from 'ngx-mf'; -import { TimeEntryCreate } from '../../../services/time-tracking/time.models'; +import { TimeEntryCreate, TimeEntryDescriptions } from '../../../services/time-tracking/time.models'; import { DateTime } from 'luxon'; import { validateTime } from '../../../validators/validate-time'; import { millisecondsToHumanReadable, parseTime } from '../../../services/time.utils'; import { validateStartEndGroup } from '../../../validators/validate-start-end-time-group'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { Settings } from '../../../services/settings/settings'; +import { KeyValuePipe } from '@angular/common'; type EntryModel = FormModel< TimeEntryCreate, @@ -32,12 +33,12 @@ const changeFormControlDisable = (state: boolean, ...formControls: FormControl(); + protected readonly TimeEntryDescriptions = TimeEntryDescriptions; protected readonly maximumDate = DateTime.now().toISODate(); private readonly formBuilder = inject(FormBuilder); protected readonly formGroup = this.formBuilder.group( @@ -56,6 +58,7 @@ export class TimeEntryComponent { }), start: new FormControl('', { nonNullable: true, validators: [validateTime, Validators.required] }), end: new FormControl('', { nonNullable: true, validators: [validateTime, Validators.required] }), + description: new FormControl(TimeEntryDescriptions[0], { nonNullable: true }), isNonWorkday: new FormControl(false, { nonNullable: true }), isADayOff: new FormControl(false, { nonNullable: true }), }, @@ -77,6 +80,12 @@ export class TimeEntryComponent { }); } + ngOnInit(): void { + if (this.isTodayMode && this.settings.preFillEndTime) { + this.formGroup.controls.end.setValue(DateTime.now().toFormat('HH:mm')); + } + } + submit(): void { if (!this.formGroup.valid) { return; @@ -85,14 +94,12 @@ export class TimeEntryComponent { const formValue = this.formGroup.value; this.timeEntry.emit({ - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion utcDate: DateTime.fromISO(formValue.utcDate!).toMillis(), start: parseTime(formValue.start ?? '00:00'), end: parseTime(formValue.end ?? millisecondsToHumanReadable(this.settings.workPerDay)), - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion isNonWorkday: formValue.isNonWorkday!, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion isADayOff: formValue.isADayOff!, + description: formValue.description!, }); this.formGroup.reset(this.formInitialState); diff --git a/src/app/components/times/time-table/time-table.component.html b/src/app/components/times/time-table/time-table.component.html index cf0cb05..e634596 100644 --- a/src/app/components/times/time-table/time-table.component.html +++ b/src/app/components/times/time-table/time-table.component.html @@ -3,10 +3,11 @@ [class.grid-cols-10]="delete.observed" [class.grid-cols-9]="!delete.observed" class="grid bg-gray-100 text-xs font-semibold uppercase text-gray-700"> -
Date
-
Start
-
End
-
Time
+
Date
+
Start
+
End
+
Time
+
Description
@if (delete.observed) {
} @@ -47,7 +48,7 @@ -
+
{{ time.utcDate | unixDate }} @if (time.isNonWorkday) { @@ -55,11 +56,14 @@ }
-
+
{{ time.start | millisecondsToTime }}
-
{{ time.end | millisecondsToTime }}
-
@if (time.isADayOff) { - }{{ time.duration | duration }}
+
{{ time.end | millisecondsToTime }}
+
@if (time.isADayOff) { - }{{ time.duration | duration }}
+
+ {{ time.description || 'n/a' }} +
@if (delete.observed) {
diff --git a/src/app/components/times/time-table/time-table.component.ts b/src/app/components/times/time-table/time-table.component.ts index 7993770..f7b8fdb 100644 --- a/src/app/components/times/time-table/time-table.component.ts +++ b/src/app/components/times/time-table/time-table.component.ts @@ -5,7 +5,7 @@ import { DurationPipe } from '../../../pipes/duration.pipe'; import { TimeEntryWithDuration } from '../../../services/time-tracking/time.models'; import { UnixDatePipe } from '../../../pipes/unix-date.pipe'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; -import { faTrashAlt } from '@fortawesome/free-regular-svg-icons'; +import { faNoteSticky, faTrashAlt } from '@fortawesome/free-regular-svg-icons'; import { CdkFixedSizeVirtualScroll, CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling'; import { faCheck, faClockRotateLeft, faLeaf } from '@fortawesome/free-solid-svg-icons'; import { ScrollViewportProviderDirective } from './scroll-viewport-provider.directive'; @@ -36,4 +36,5 @@ export class TimeTableComponent { protected readonly faCheck = faCheck; protected readonly faClockRotateLeft = faClockRotateLeft; protected readonly faLeaf = faLeaf; + protected readonly faNoteSticky = faNoteSticky; } diff --git a/src/app/services/settings/settings.table.ts b/src/app/services/settings/settings.table.ts index 15727d2..d7f1cef 100644 --- a/src/app/services/settings/settings.table.ts +++ b/src/app/services/settings/settings.table.ts @@ -11,6 +11,7 @@ interface SettingsEntity extends Settings { const defaultSettings: Settings = { workPerDay: 27_000_000, // 7.5 hours + preFillEndTime: true, }; @Injectable() @@ -28,7 +29,7 @@ export class SettingsTable implements DatabaseTable, DatabaseCle return defaultSettings; } - return { ...entity, id: undefined }; + return { ...entity, preFillEndTime: entity.preFillEndTime ?? true, id: undefined }; }), ).pipe(shareReplay()); diff --git a/src/app/services/settings/settings.ts b/src/app/services/settings/settings.ts index c807bca..64d2648 100644 --- a/src/app/services/settings/settings.ts +++ b/src/app/services/settings/settings.ts @@ -2,4 +2,5 @@ import { Milliseconds } from '../time.utils'; export interface Settings { workPerDay: Milliseconds; + preFillEndTime: boolean; } diff --git a/src/app/services/time-tracking/time.models.ts b/src/app/services/time-tracking/time.models.ts index f97d207..4e402e7 100644 --- a/src/app/services/time-tracking/time.models.ts +++ b/src/app/services/time-tracking/time.models.ts @@ -1,11 +1,14 @@ import { Duration } from 'luxon'; import { Milliseconds } from '../time.utils'; +export const TimeEntryDescriptions = ['Development', 'Meeting', 'Support (Development)', 'Support (Customer)']; + export interface TimeEntry { id: number; utcDate: Milliseconds; start: Milliseconds; end: Milliseconds; + description: string; isNonWorkday: boolean; isADayOff: boolean; } diff --git a/src/app/services/time-tracking/time.table.ts b/src/app/services/time-tracking/time.table.ts index 09b3c28..1c56deb 100644 --- a/src/app/services/time-tracking/time.table.ts +++ b/src/app/services/time-tracking/time.table.ts @@ -13,7 +13,7 @@ export const calculateDuration = ({ start, end }: TimeEntry): Duration => Durati export class TimeTable implements DatabaseTable { readonly definition = '++id, utcDate'; readonly name = 'times'; - readonly version = 3; + readonly version = 4; private times!: Table; items$(fromTimestamp: Milliseconds = 0, toTimestamp: Milliseconds = Number.MAX_SAFE_INTEGER): Observable {