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"
}
}