Overview
The Angular 10 timesheet component displays tasks in a grid with one day per row
Tasks can be created and modified usign drag an drop
Each task displays a description and task duration.
Weekends and non-business hours are hidden.
The Angular 10 project with all required boilerplate was generated using DayPilot UI Builder.
Includes a trial version of DayPilot Pro for JavaScript (see 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 Timesheet Component
Our Angular timesheet component is created using Angular Scheduler component from DayPilot Pro for JavaScript. In order to activate the timesheet mode (with hours on the horizontal axis and days on the vertical axis) we need to set the viewType
config property to 'Days'
.
This is the minimum timesheet component configuration:
import {Component, ViewChild, AfterViewInit} from '@angular/core';
import {DayPilot, DayPilotSchedulerComponent} from 'daypilot-pro-angular';
@Component({
selector: 'timesheet-component',
template: `<daypilot-scheduler [config]="config" [events]="events" #timesheet></daypilot-scheduler>`,
styles: [``]
})
export class TimesheetComponent {
@ViewChild('timesheet')
timesheet: DayPilotSchedulerComponent;
events: DayPilot.EventData[] = [];
config: DayPilot.SchedulerConfig = {
viewType: 'Days',
// ...
};
}
Timesheet Time Header
In the next step, we will configure the time header.
The timesheet component will display 15-minute blocks in the grid (
scale: 'CellDuration'
andcellDuration: 15
config options).The first row of the time header will show hours and the second row will show the starting minutes of each grid cell.
config: DayPilot.SchedulerConfig = {
timeHeaders: [
{groupBy: 'Hour'},
{groupBy: 'Cell', format: 'mm'}
],
scale: 'CellDuration',
cellDuration: 15,
// ...
}
We set the business hours using businessBeginsHour and businessEndsHour properties (9 am to 5 pm):
config: DayPilot.SchedulerConfig = {
// ...
businessBeginsHour: 9,
businessEndsHour: 17,
// ...
}
With this configuration, most of the hours of day (16 of 24) are now non-business hours. We can hide the non-business hours using showNonBusiness property. This will also hide weekend rows:
config: DayPilot.SchedulerConfig = {
// ...
showNonBusiness: false,
// ...
}
In order to avoid the horizontal scrollbar, we activate the auto cell width mode. This will calculate the cell width automatically so that the full timesheet width is used:
config: DayPilot.SchedulerConfig = {
// ...
cellWidthSpec: "Auto",
// ...
}
Timesheet Task Duration
By default, each task displays the description which is specified using the text
property of the event data object (see DayPilot.Event.data).
We want to display the task duration on the right side. We will add it by adding a special active area (areas
property of the event data object). The position of the active area is set using right
, top
, bottom
and width
properties. The content (duration string) is set using html
property. We will also add custom CSS style that will center the text vertically (style
property).
config: DayPilot.SchedulerConfig = {
// ...
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'}
];
}
// ...
};
Date Headers with Totals
The vertical axis shows the days and the date is displayed in the row header. We want to add one more column to the row header that will display the row total.
First, we need to define the columns using rowHeaderColumns property:
config: DayPilot.SchedulerConfig = {
rowHeaderColumns: [
{title: "Date"},
{title: "Total"}
],
// ...
}
The daily totals are calculated using onBeforeRowHeaderRender event handler:
config: DayPilot.SchedulerConfig = {
onBeforeRowHeaderRender: args => {
const duration = args.row.events.totalDuration();
if (duration.totalSeconds() === 0) {
return;
}
let str = duration.toString('H:mm');
if (duration.totalDays() >= 1) {
str = Math.floor(duration.totalHours()) + ':' + duration.toString('mm');
}
args.row.columns[1].html = str + ' hours';
},
// ...
};
Full Source Code
This is the full source code of our Angular 10 timesheet component. You can find it in the downloadable project in timesheet.component.ts
file.
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: DayPilot.EventData[] = [];
config: DayPilot.SchedulerConfig = {
viewType: 'Days',
showNonBusiness: false,
businessBeginsHour: 9,
businessEndsHour: 17,
rowHeaderColumns: [
{title: 'Date'},
{title: 'Total'}
],
cellWidthSpec: 'Auto',
timeHeaders: [
{groupBy: 'Hour'},
{groupBy: 'Cell', format: 'mm'}
],
scale: 'CellDuration',
cellDuration: 15,
days: new DayPilot.Date('2020-08-01').daysInMonth(),
startDate: '2020-08-01',
heightSpec: 'Max',
height: 300,
onTimeRangeSelected : args => {
DayPilot.Modal.prompt('Create a new task:', 'Task 1').then(modal => {
const dp = this.timesheet.control;
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 duration = args.row.events.totalDuration();
if (duration.totalSeconds() === 0) {
return;
}
let str = duration.toString('H:mm');
if (duration.totalDays() >= 1) {
str = Math.floor(duration.totalHours()) + ':' + duration.toString('mm');
}
args.row.columns[1].html = str + ' 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 = '#d0e0e3';
args.data.backColor = '#fce5cd';
args.data.borderColor = 'darker';
args.data.barColor = '#e69138';
}
};
constructor(private ds: DataService) {
}
ngAfterViewInit(): void {
const from = this.timesheet.control.visibleStart();
const to = this.timesheet.control.visibleEnd();
this.ds.getEvents(from, to).subscribe(result => {
this.events = result;
});
}
}