Features

  • Angular 5 project displaying a timesheet

  • Days on the vertical (Y) axis

  • Time of day on the horizontal (X) axis

  • An option to hide non-business hours (horizontally) and weekends (vertically)

  • The timesheet component created using Angular Scheduler from DayPilot Pro for JavaScript

  • This Angular 5 project was created using the online Timesheet UI Builder - a configurator application that lets you customize the timesheet appearance and behavior (with a live preview) and generate an Angular 5 project

  • Includes a trial version of DayPilot Pro for JavaScript (see also License below)

New version of Angular timesheet project

There is a new version of this tutorial available:

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.

Angular 5 Project Structure

The project uses standard Angular project stucture generated using Angular CLI (1.5). All timesheet-specific code can be found in TimesheetModule (src/app/timesheet folder):

src/app/timesheet
- data.service.ts
- timesheet.component.ts
- timesheet.module.ts

Prerequisites

This project requires standard Angular infrastructure:

This project is based on Angular CLI, that means Angular CLI is required as well (1.5+).

Initialization and Running

The required packages are not included (node_modules). Before running the Angular project, it's necessary to download the dependencies:

npm install

Then you can start the project:

npm run start

This command will start the embedded web server. The Angular timesheet application will be available at http://localhost:4200.

Angular 5 Timesheet Component

The timesheet.component.ts file include TypeScript source code of the timesheet component (including the HTML and CSS). The timesheet component is built using DayPilot Angular Scheduler component which is switched to the timesheet view using viewType property ("Days").

We have used the Timesheet UI Builder to configure the component. It uses the default configuration with the following modifications:

  • Non-Business Time: "Hide"

  • Business Weekends: "Enabled"

These settings will display time between 9 AM and 5 PM. Weekends (Saturday and Sunday) will be displayed as well.

We have also made the following changes to the generated configuration:

  • heightSpec: "Max" (this will limit the timesheet height; a vertical scrollbar will be added)

  • height: 300 (the height is limited to 300 pixels, see also other Scheduler height options)

And we will also mark the weekends with a different color using onBeforeCellRender event handler (see also cell customization):

onBeforeCellRender: args => {
  let saturday = 6;
  let sunday = 0;
  let dayOfWeek = args.cell.start.dayOfWeek();
  if (dayOfWeek === sunday || dayOfWeek === saturday) {
    args.cell.backColor = "#fcfcfc"; // apply highlighting
  }
},

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

  @ViewChild("timesheet")
  timesheet: DayPilotSchedulerComponent;

  events: any[] = [];

  config: any = {
    locale: "en-us",
    cellWidthSpec: "Auto",
    crosshairType: "Header",
    heightSpec: "Max",
    height: 400,
    timeHeaders: [{"groupBy":"Hour"},{"groupBy":"Cell","format":"mm"}],
    scale: "CellDuration",
    cellDuration: 15,
    viewType: "Days",
    onBeforeRowHeaderRender: args => {
      args.row.horizontalAlignment = "center";
    },
    onBeforeCellRender: args => {
      let saturday = 6;
      let sunday = 0;
      let dayOfWeek = args.cell.start.dayOfWeek();
      if (dayOfWeek === sunday || dayOfWeek === saturday) {
        args.cell.backColor = "#fcfcfc"; // apply highlighting
      }
    },
    days: DayPilot.Date.today().daysInMonth(),
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    showNonBusiness: false,
    businessWeekends: true,
    floatingEvents: true,
    eventHeight: 30,
    eventMovingStartEndEnabled: false,
    eventResizingStartEndEnabled: false,
    timeRangeSelectingStartEndEnabled: false,
    groupConcurrentEvents: false,
    eventStackingLineHeight: 100,
    allowEventOverlap: true,
    timeRangeSelectedHandling: "Enabled",
    onTimeRangeSelected: function (args) {
      var dp = this;
      DayPilot.Modal.prompt("Create a new event:", "Event 1").then(function(modal) {
        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
        }));
      });
    },
    eventMoveHandling: "Update",
    onEventMoved: function (args) {
      this.message("Event moved");
    },
    eventResizeHandling: "Update",
    onEventResized: function (args) {
      this.message("Event resized");
    },
    eventDeleteHandling: "Update",
    onEventDeleted: function (args) {
      this.message("Event deleted");
    },
    eventClickHandling: "Disabled",
    eventHoverHandling: "Disabled",
    contextMenu: new DayPilot.Menu({
      items: [
        { text: "Delete", onClick: function(args) { var dp = args.source.calendar; dp.events.remove(args.source); } }
      ]
    }),
  };

  constructor(private ds: DataService) {
  }

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

}

Loading Timesheet Data

The data service returns sample data. It uses a static array defined on the client side. Usually you will want to replace getEvents() method with your own implementation that loads the timesheet data from the server side.

data.service.ts

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

@Injectable()
export class DataService {

  events: any[] = [
    {
      id: 1,
      start: DayPilot.Date.today().addHours(9),
      end: DayPilot.Date.today().addHours(11),
      text: "Event 1"
    },
    {
      id: 2,
      start: DayPilot.Date.today().addHours(12),
      end: DayPilot.Date.today().addHours(15),
      text: "Event 2"
    }
  ];

  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());
  }

}

Timesheet Module

The TimesheetModule wraps the timesheet implementation:

import {DataService} from "./data.service";
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";
import {HttpClientModule} from "@angular/common/http";

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

Project Configuration

package.json

{
  "name": "angular5-timesheet",
  "version": "0.0.0",
  "license": "SEE LICENSE IN license/LicenseAgreement.pdf",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^5.0.0",
    "@angular/common": "^5.0.0",
    "@angular/compiler": "^5.0.0",
    "@angular/core": "^5.0.0",
    "@angular/forms": "^5.0.0",
    "@angular/http": "^5.0.0",
    "@angular/platform-browser": "^5.0.0",
    "@angular/platform-browser-dynamic": "^5.0.0",
    "@angular/router": "^5.0.0",
    "core-js": "^2.4.1",
    "daypilot-pro-angular": "https://npm.daypilot.org/daypilot-pro-angular/trial/2018.1.3156.tar.gz",
    "rxjs": "^5.5.2",
    "web-animations-js": "^2.3.1",
    "zone.js": "^0.8.14"
  },
  "devDependencies": {
    "@angular-devkit/core": "0.0.26",
    "@angular/cli": "1.5.0",
    "@angular/compiler-cli": "^5.0.0",
    "@angular/language-service": "^5.0.0",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "~4.0.0",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.2.0",
    "tslint": "~5.7.0",
    "typescript": "~2.4.2"
  }
}