Features

A boilerplate project that includes a basic Angular timesheet configuration. You can use the project as a starting point for your own implementation.

  • Timesheet displaying one day per row
  • Hiding non-business hours
  • Daily totals in a special column
  • Angular 4 project built using Angular CLI 1.0
  • Sample data loaded using a dummy data service class
  • Includes 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.

Timesheet Project Initialization

The project dependencies are specified in package.json file but the modules are not included in the download package.

It's necessary to install the dependencies using npm:

npm install

Running the Timesheet Project

You can run the project using built-in web server:

npm run start

The website is available at http://localhost:4200.

Timesheet Implementation

The timesheet implementation is based on Angular Scheduler component from DayPilot Pro for JavaScript package.

By default, the Scheduler displays resources on the Y axis. It can be switched to the timesheet view using viewType property. This mode will display up to 24 hours on the X axis and days on the Y axis.

  config: any = {
    viewType: "Days",
    // ...
  }

Timesheet Hour Header

angular4-timesheet-quick-start-project-hour-header.png

We need to customize the time header to display 15-minute blocks grouped by hour:

  config: any = {
    timeHeaders: [{"groupBy":"Hour"},{"groupBy":"Cell","format":"mm"}],
    scale: "CellDuration",
    cellDuration: 15,    
    // ...
  }

The X axis will be limited to business hours (9 am to 6 pm):

  config: any = {
    showNonBusiness: false,
    businessBeginsHour: 9,
    businessEndsHour: 18,    
    // ...
  }

And the cellWidthSpec: "Auto" mode will adjust the cell width automatically to avoid horizontal scrollbar:

  config: any = {
    cellWidthSpec: "Auto",    
    // ...
  }

Daily Totals

angular4-timesheet-quick-start-project-daily-totals.png

The timesheet application displays a summary for each row (day).

The row header colums are defined using rowHeaderColumns property:

  config: any = {
    rowHeaderColumns: [
      {title: "Date"},
      {title: "Total"}
    ],
    // ...
  }

The daily totals are calculated using onBeforeRowHeaderRender event handler:

  config: any = {
    onTimeRangeSelected : function (args) {
      DayPilot.Modal.prompt("Create a new task:", "Task 1").then(function(modal) {
        var dp = args.control;
        dp.clearSelection();
        if (!modal.result) { return; }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          resource: args.resource,
          text: modal.result
        }));
      });
    },
    // ...
  };

Source Code

The timesheet component implementation can be found in src/app/timesheet directory.

timesheet.component.ts

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" #scheduler></daypilot-scheduler>`,
  styles: [``]
})
export class TimesheetComponent implements AfterViewInit {

  @ViewChild("scheduler")
  scheduler: DayPilotSchedulerComponent;

  events: any[] = [];

  config: any = {
    viewType: "Days",
    showNonBusiness: false,
    businessBeginsHour: 9,
    businessEndsHour: 18,
    rowHeaderColumns: [
      {title: "Date"},
      {title: "Total"}
    ],
    eventHeight: 30,
    cellWidthSpec: "Auto",
    timeHeaders: [{"groupBy":"Hour"},{"groupBy":"Cell","format":"mm"}],
    scale: "CellDuration",
    cellDuration: 15,
    days: new DayPilot.Date("2017-07-01").daysInMonth(),
    startDate: "2017-07-01",
    heightSpec: "Max",
    height: 300,
    onTimeRangeSelected : args => {
      DayPilot.Modal.prompt("Create a new task:", "Task 1").then(function(modal) {
        var dp = args.control;
        dp.clearSelection();
        if (!modal.result) { return; }
        dp.events.add(new DayPilot.Event({
          start: args.start,
          end: args.end,
          id: DayPilot.guid(),
          resource: args.resource,
          text: modal.result
        }));
      });
    },
    onBeforeRowHeaderRender: args => {
      let duration = args.row.events.totalDuration();
      if (duration.totalSeconds() === 0) {
        return;
      }

      let str;
      if (duration.totalDays() >= 1) {
        str = Math.floor(duration.totalHours()) + ":" + duration.toString("mm");
      }
      else {
        str = duration.toString("H:mm");
      }

      args.row.columns[0].html = str + " hours";
    },
    onBeforeEventRender: args => {
      var duration = new DayPilot.Duration(args.data.start, args.data.end);
      args.data.areas = [
        { right: 2, top: 6, height: 20, width: 30, html: duration.toString("h:mm")}
      ];
    }
  };

  constructor(private ds: DataService) {
  }

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

}

timesheet.module.ts

import {DataService} from "./data.service";
import {HttpModule} from "@angular/http";
import {FormsModule} from "@angular/forms";
import {BrowserModule} from "@angular/platform-browser";
import {NgModule} from "@angular/core";
import {TimesheetComponent} from "./timesheet.component";
import {DayPilotModule} from "daypilot-pro-angular";

@NgModule({
  imports:      [
    BrowserModule,
    FormsModule,
    HttpModule,
    DayPilotModule
  ],
  declarations: [
    TimesheetComponent
  ],
  exports:      [ TimesheetComponent ],
  providers:    [ DataService ]
})
export class TimesheetModule { }

data.service.ts

import { Http, Response } from '@angular/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Rx';
import 'rxjs/Rx';
import {DayPilot} from 'daypilot-pro-angular';

@Injectable()
export class DataService {

  events: any[] = [
    {
      id: "1",
      start: "2017-07-02T09:30:00",
      end: "2017-07-02T11:45:00",
      text: "Task 1"
    },
    {
      id: "2",
      start: "2017-07-02T12:30:00",
      end: "2017-07-02T14:00:00",
      text: "Task 2"
    },
  ];

  constructor(private http : Http){
  }

  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()).map((response:Response) => response.json());
  }

}