Overview

  • This Angular 12 Timesheet project was created using Angular Scheduler component from DayPilot Pro for JavaScript.

  • You can switch the Scheduler component to a timesheet mode. The timesheet mode will display days on the Y axis and time of day on the X axis.

  • Download this Angular 12 project and use it as a starting point for your own implementation.

  • The project uses a trial version of DayPilot Pro for JavaScript (see License below).

License

Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript. Buy a license.

How to add the Angular timesheet component to your project

To add the Angular timesheet to your project, follow these steps:

  • Download the attached project and copy the src/app/timesheet directory to your Angular project. It includes three files: data.service.ts, timesheet.component.ts, and timesheet.module.ts.

  • Add the DayPilot Angular NPM package to your project using a link from npm.daypilot.org.

To play with the timesheet configuration, you can also use the Timesheet UI Builder application and generate a pre-configured Angular project.

How to add columns to the Angular 12 Timesheet header

angular 12 timesheet quick start project columns

The attached Angular timesheet project defines custom columns that will be displayed on the right side of the timesheet component:

  • Date (this column will display the default content - the date)

  • Day (day of week)

  • Total (total duration of all record in a row)

You can define custom columns using rowHeaderColumns property of the config object:

config: DayPilot.SchedulerConfig = {

  rowHeaderColumns: [
    {title: 'Date'},
    {title: 'Day', width: 30},
    {title: 'Total'}
  ],
  // ...
}

How to calculate timesheet totals

angular 12 timesheet quick start project totals

To calculate and display the row totals, you need to customize the row header content using onBeforeRowHeaderRender event handler.

The following example gets the total duration of all records in a row using args.row.events.totalDuration() method. The duration is converted to a formatted string in “hours:minutes” format and displayed in the third column.

config: DayPilot.SchedulerConfig = {

  onBeforeRowHeaderRender: args => {
    const duration = args.row.events.totalDuration();
    if (duration.totalSeconds() === 0) {
      return;
    }

    let hours = duration.toString('H:mm');
    if (duration.totalDays() >= 1) {
      hours = Math.floor(duration.totalHours()) + ':' + duration.toString('mm');
    }
    args.row.columns[2].text = `${hours} hours`;
  },
  
  // ...
};

How to load Angular timesheet records

angular 12 timesheet quick start project load records

You can load the Angular timesheet records using the [events] attribute. This attribute specifies an array with timesheet records. Angular will watch this array for changes and update the timesheet content when a change is detected.

@Component({
  selector: 'timesheet-component',
  template: `<daypilot-scheduler [config]="config" [events]="events" #timesheet></daypilot-scheduler>`,
  styles: [``]
})
export class TimesheetComponent implements AfterViewInit {

  @ViewChild('timesheet', {static: false})
  timesheet!: DayPilotSchedulerComponent;

  events: any[] = [];

  config: DayPilot.SchedulerConfig = {
    // ...
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {

    this.timesheet.control.scrollTo(DayPilot.Date.today().firstDayOfMonth().addHours(9));

    var from = this.timesheet.control.visibleStart();
    var to = this.timesheet.control.visibleEnd();
    this.ds.getEvents(from, to).subscribe(result => {
      this.events = result;
    });
  }

}

In this project, we use a custom DataService class to provide the timesheet data:

import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {DayPilot} from 'daypilot-pro-angular';
import {HttpClient} from '@angular/common/http';

@Injectable()
export class DataService {

  events: any[] = [
    {
      id: 1,
      start: "2021-08-02T09:15:00",
      end: "2021-08-02T11:00:00",
      text: 'Task 1'
    },
    {
      id: 2,
      start: "2021-08-02T12:00:00",
      end: "2021-08-02T15:00:00",
      text: 'Task 2'
    },
    {
      id: 3,
      start: "2021-08-05T10:00:00",
      end: "2021-08-05T12:00:00",
      text: 'Task 3'
    },
  ];

  constructor(private http: HttpClient) {
  }

  getEvents(from: DayPilot.Date, to: DayPilot.Date): Observable<any[]> {

    // simulating an HTTP request
    return new Observable(observer => {
      setTimeout(() => {
        observer.next(this.events);
      }, 200);
    });

    // return this.http.get("/api/events?from=" + from.toString() + "&to=" + to.toString());
  }


}

Full Source Code

And here is the complete source code of the Angular timesheet component:

import {Component, ViewChild, AfterViewInit} from '@angular/core';
import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular';
import {DataService} from "./data.service";

@Component({
  selector: 'timesheet-component',
  template: `<daypilot-scheduler [config]="config" [events]="events" #timesheet></daypilot-scheduler>`,
  styles: [``]
})
export class TimesheetComponent implements AfterViewInit {

  @ViewChild('timesheet', {static: false})
  timesheet!: DayPilotSchedulerComponent;

  events: any[] = [];

  config: DayPilot.SchedulerConfig = {
    locale: "en-us",
    timeHeaders: [{"groupBy":"Hour"},{"groupBy":"Cell","format":"mm"}],
    scale: "CellDuration",
    cellDuration: 15,
    days: 31,
    viewType: "Days",
    startDate: "2021-08-01",
    showNonBusiness: true,
    businessWeekends: false,
    allowEventOverlap: false,
    rowHeaderColumns: [
      {title: 'Date'},
      {title: 'Day', width: 30},
      {title: 'Total'}
    ],
    onTimeRangeSelected: async (args) => {
      const dp = args.control;
      const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
      dp.clearSelection();
      if (modal.canceled) { return; }
      dp.events.add({
        start: args.start,
        end: args.end,
        id: DayPilot.guid(),
        resource: args.resource,
        text: modal.result
      });
    },
    onBeforeRowHeaderRender: args => {
      const day = args.row.start.toString("ddd");
      args.row.columns[1].text = `${day}`;

      const duration = args.row.events.totalDuration();
      if (duration.totalSeconds() === 0) {
        return;
      }

      let hours = duration.toString('H:mm');
      if (duration.totalDays() >= 1) {
        hours = Math.floor(duration.totalHours()) + ':' + duration.toString('mm');
      }
      args.row.columns[2].text = `${hours} hours`;
    },
    onBeforeEventRender: args => {
      const duration = new DayPilot.Duration(args.data.start, args.data.end);
      args.data.areas = [
        { right: 2, top: 0, bottom: 0, width: 30, html: duration.toString('h:mm'), style: 'display: flex; align-items: center'}
      ];
      args.data.backColor = '#b6d7a8';
      args.data.borderColor = 'darker';
      args.data.barColor = '#6aa84f';
    }
  };

  constructor(private ds: DataService) {
  }

  ngAfterViewInit(): void {

    this.timesheet.control.scrollTo(DayPilot.Date.today().firstDayOfMonth().addHours(9));

    var from = this.timesheet.control.visibleStart();
    var to = this.timesheet.control.visibleEnd();
    this.ds.getEvents(from, to).subscribe(result => {
      this.events = result;
    });
  }

}