Skip to content

Commit

Permalink
feat: allow description & prefill end time
Browse files Browse the repository at this point in the history
  • Loading branch information
ManuelRauber committed Jun 24, 2024
1 parent 4864124 commit c46e861
Show file tree
Hide file tree
Showing 10 changed files with 54 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
<form (ngSubmit)="submit()" [formGroup]="formGroup">
<form (ngSubmit)="submit()" [formGroup]="formGroup" class="space-y-2">
<div>
<label class="kw-label" for="hoursPerWeek">Work hours per day:</label>
@if (workHoursPerWeek$ | async; as workHoursPerWeek) {
<div class="kw-hint">Work hours per week: {{ workHoursPerWeek | duration }}</div>
}
<input [formControl]="formGroup.controls.workPerDay" class="kw-input" id="hoursPerWeek" type="time" />
</div>
<div>
<label class="kw-label" for="preFillEndTime">Pre-fill end time:</label>
<div class="kw-hint">Will set the end time to "now" each time you create a new entry in the dashboard.</div>
<input [formControl]="formGroup.controls.preFillEndTime" class="kw-input" id="preFillEndTime" type="checkbox" />
</div>
<div class="mt-2">
<button class="kw-button">Save</button>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type SettingsModel = FormModel<
Settings,
{
workPerDay: Replace<FormControl<string>>;
preFillEndTime: Replace<FormControl<boolean>>;
}
>;

Expand All @@ -28,6 +29,7 @@ export class SettingsFormComponent implements OnInit {
private readonly formBuilder = inject(FormBuilder);
protected formGroup = this.formBuilder.group<SettingsModel['controls']>({
workPerDay: new FormControl<string>('', { nonNullable: true }),
preFillEndTime: new FormControl<boolean>(true, { nonNullable: true }),
});
protected readonly workHoursPerWeek$ = this.formGroup.controls.workPerDay.valueChanges.pipe(
map(workPerDay => multiplyDuration(parseTimeToDuration(workPerDay))),
Expand All @@ -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,
}),
);
}
Expand All @@ -52,6 +55,7 @@ export class SettingsFormComponent implements OnInit {

this.settingsChange.next({
workPerDay: parseTimeToDuration(formValue.workPerDay).toMillis(),
preFillEndTime: formValue.preFillEndTime,
});
}
}
10 changes: 9 additions & 1 deletion src/app/components/times/time-entry/time-entry.component.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<form (ngSubmit)="submit()" [formGroup]="formGroup" class="flex items-end space-x-3">
<form (ngSubmit)="submit()" [formGroup]="formGroup" class="flex items-stretch space-x-3">
@if (!isTodayMode) {
<div>
<label class="kw-label" for="utcDate">Date</label>
Expand All @@ -13,6 +13,14 @@
<label class="kw-label" for="end">End</label>
<input [formControl]="formGroup.controls.end" class="kw-input" id="end" type="time" />
</div>
<div class="flex flex-col justify-between">
<label class="kw-label" for="description">Description</label>
<select [formControl]="formGroup.controls.description" class="kw-input mb-2" id="description">
@for (description of TimeEntryDescriptions; track description) {
<option [ngValue]="description">{{ description }}</option>
}
</select>
</div>
<div>
<span class="kw-label">No workday</span>
<div class="kw-input kw-toggle-input">
Expand Down
21 changes: 14 additions & 7 deletions src/app/components/times/time-entry/time-entry.component.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -32,19 +33,20 @@ const changeFormControlDisable = (state: boolean, ...formControls: FormControl<u
@Component({
selector: 'kw-time-entry',
standalone: true,
imports: [FormsModule, ReactiveFormsModule],
imports: [FormsModule, ReactiveFormsModule, KeyValuePipe],
templateUrl: './time-entry.component.html',
styleUrls: ['./time-entry.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TimeEntryComponent {
export class TimeEntryComponent implements OnInit {
/**
* Enabled the today mode hides the date entry.
*/
@Input() isTodayMode = false;
@Input({ required: true }) settings!: Settings;
@Output() timeEntry = new EventEmitter<TimeEntryCreate>();

protected readonly TimeEntryDescriptions = TimeEntryDescriptions;
protected readonly maximumDate = DateTime.now().toISODate();
private readonly formBuilder = inject(FormBuilder);
protected readonly formGroup = this.formBuilder.group<EntryModel['controls']>(
Expand All @@ -56,6 +58,7 @@ export class TimeEntryComponent {
}),
start: new FormControl<string>('', { nonNullable: true, validators: [validateTime, Validators.required] }),
end: new FormControl<string>('', { nonNullable: true, validators: [validateTime, Validators.required] }),
description: new FormControl<string>(TimeEntryDescriptions[0], { nonNullable: true }),
isNonWorkday: new FormControl<boolean>(false, { nonNullable: true }),
isADayOff: new FormControl<boolean>(false, { nonNullable: true }),
},
Expand All @@ -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;
Expand All @@ -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);
Expand Down
20 changes: 12 additions & 8 deletions src/app/components/times/time-table/time-table.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -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">
<div class="col-span-3 px-6 py-3">Date</div>
<div class="col-span-2 px-6 py-3">Start</div>
<div class="col-span-2 px-6 py-3">End</div>
<div class="col-span-2 px-6 py-3">Time</div>
<div class="col-span-2 px-6 py-3">Date</div>
<div class="col-span-1 px-6 py-3">Start</div>
<div class="col-span-1 px-6 py-3">End</div>
<div class="col-span-1 px-6 py-3">Time</div>
<div class="col-span-4 px-6 py-3">Description</div>
@if (delete.observed) {
<div></div>
}
Expand Down Expand Up @@ -47,19 +48,22 @@
</ng-template>

<ng-template #itemTemplate let-odd="odd" let-time>
<div [class.bg-gray-50]="odd" class="col-span-3 flex h-[50px] items-center px-6">
<div [class.bg-gray-50]="odd" class="col-span-2 flex h-[50px] items-center px-6">
{{ time.utcDate | unixDate }}
@if (time.isNonWorkday) {
<fa-icon [icon]="faClockRotateLeft" class="ml-2 text-green" title="Non Workday"></fa-icon>
} @if (time.isADayOff) {
<fa-icon [icon]="faLeaf" class="ml-2 text-green" title="Is a day off"></fa-icon>
}
</div>
<div [class.bg-gray-50]="odd" class="col-span-2 flex h-[50px] items-center px-6">
<div [class.bg-gray-50]="odd" class="col-span-1 flex h-[50px] items-center px-6">
{{ time.start | millisecondsToTime }}
</div>
<div [class.bg-gray-50]="odd" class="col-span-2 flex h-[50px] items-center px-6">{{ time.end | millisecondsToTime }}</div>
<div [class.bg-gray-50]="odd" class="col-span-2 flex h-[50px] items-center px-6">@if (time.isADayOff) { - }{{ time.duration | duration }}</div>
<div [class.bg-gray-50]="odd" class="col-span-1 flex h-[50px] items-center px-6">{{ time.end | millisecondsToTime }}</div>
<div [class.bg-gray-50]="odd" class="col-span-1 flex h-[50px] items-center px-6">@if (time.isADayOff) { - }{{ time.duration | duration }}</div>
<div [class.bg-gray-50]="odd" class="col-span-4 flex items-center px-10">
<fa-icon [icon]="faNoteSticky" class="pr-2" />{{ time.description || 'n/a' }}
</div>
@if (delete.observed) {
<div class="flex items-center px-6" [class.bg-gray-50]="odd">
<fa-icon (click)="delete.next(time)" [icon]="faTrashAlt" class="cursor-pointer"></fa-icon>
Expand Down
3 changes: 2 additions & 1 deletion src/app/components/times/time-table/time-table.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -36,4 +36,5 @@ export class TimeTableComponent {
protected readonly faCheck = faCheck;
protected readonly faClockRotateLeft = faClockRotateLeft;
protected readonly faLeaf = faLeaf;
protected readonly faNoteSticky = faNoteSticky;
}
3 changes: 2 additions & 1 deletion src/app/services/settings/settings.table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface SettingsEntity extends Settings {

const defaultSettings: Settings = {
workPerDay: 27_000_000, // 7.5 hours
preFillEndTime: true,
};

@Injectable()
Expand All @@ -28,7 +29,7 @@ export class SettingsTable implements DatabaseTable<SettingsEntity>, DatabaseCle
return defaultSettings;
}

return { ...entity, id: undefined };
return { ...entity, preFillEndTime: entity.preFillEndTime ?? true, id: undefined };
}),
).pipe(shareReplay());

Expand Down
1 change: 1 addition & 0 deletions src/app/services/settings/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import { Milliseconds } from '../time.utils';

export interface Settings {
workPerDay: Milliseconds;
preFillEndTime: boolean;
}
3 changes: 3 additions & 0 deletions src/app/services/time-tracking/time.models.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/services/time-tracking/time.table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const calculateDuration = ({ start, end }: TimeEntry): Duration => Durati
export class TimeTable implements DatabaseTable<TimeEntry> {
readonly definition = '++id, utcDate';
readonly name = 'times';
readonly version = 3;
readonly version = 4;
private times!: Table<TimeEntry, number>;

items$(fromTimestamp: Milliseconds = 0, toTimestamp: Milliseconds = Number.MAX_SAFE_INTEGER): Observable<TimeEntryWithDuration[]> {
Expand Down

0 comments on commit c46e861

Please sign in to comment.