Features

  • HTML5 monthly timesheet, one day per row
  • Uses DayPilot JavaScript Scheduler control
  • Supports multiple users, switching using a <select> control
  • Displays day summary (total time) in day headers
  • Highlights business hours
  • Pure JavaScript solution with no third party libraries required
  • Sample PHP backend with SQLite database
  • Compatible with jQuery
  • Drag and drop record creating, moving and resizing

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 Initialization

html5-timesheet-default-configuration.png

We will display the timesheet using DayPilot Scheduler switched to timesheet view (viewType="Days"). The timesheet view displays days on the vertical (Y) axis and hours of day on the horizontal (X) axis.

<div id="dp"></div>

<script type="text/javascript">
  var dp = new DayPilot.Scheduler("dp");
  dp.viewType = "Days";
  dp.init();
</script>

This code will display the timesheet using the default configuration:

  • A single row showing today.
  • Fixed cell width (40 pixels).

Monthly Timesheet

html5-monthly-timesheet.png

We want to adjust the timesheet to display the current month. We will use .startDate and .days properties:

<div id="dp"></div>

<script type="text/javascript">
  var dp = new DayPilot.Scheduler("dp");
  dp.viewType = "Days";

  dp.startDate = new DayPilot.Date().firstDayOfMonth();
  dp.days = dp.startDate.daysInMonth();

  dp.init();
</script>

Timesheet Headers

html5-timesheet-time-headers.png

The first row of the X axis header displays the first day (6/1/2014). We will modify the timesheet headers to display the current month (June 2014) in the first row and hours in the second row.

<div id="dp"></div>

<script type="text/javascript">
  var dp = new DayPilot.Scheduler("dp");
  dp.viewType = "Days";

  dp.startDate = new DayPilot.Date().firstDayOfMonth();
  dp.days = dp.startDate.daysInMonth();

  dp.timeHeaders = [
    { groupBy: "Day", format: "MMMM yyyy" },
    { groupBy: "Hour"}
  ];

  dp.init();
</script>

Loading List of Employees

html5-timesheet-employee-list.png

Now we want to display the timesheet for a specific employee. Let's add a simple <select> control:

<div class="space">
  Employee:
  <select id="employee"></select>
</div>

And fill it with data:

$(document).ready(function() {
  loadResources();
  $("#employee").change(function() {
      loadEvents();
  });
});

function loadResources() {
  $.post("backend_resources.php", function(data) {
      for (var i = 0; i < data.length; i++) {
          var item = data[i];
          $("#employee").append($('<option/>', { 
              value: item.id,
              text : item.name
          }));
      }
      loadEvents();
  });
}

The backend_resources.php PHP script returns a JSON array:

<?php
require_once '_db.php';
    
class Resource {}

$resources = array();

$stmt = $db->prepare('SELECT * FROM [resources] ORDER BY [name]');
$stmt->execute();
$timesheet_resources = $stmt->fetchAll();  

foreach($timesheet_resources as $resource) {
  $r = new Resource();
  $r->id = $resource['id'];
  $r->name = $resource['name'];
  $resources[] = $r;
}

header('Content-Type: application/json');
echo json_encode($resources);

?>

Example JSON array:

[
  {"id":"1","name":"Person 1"},
  {"id":"2","name":"Person 2"},
  {"id":"3","name":"Person 3"},
  {"id":"4","name":"Person 4"}
]

Loading Timesheet Data

html5-timesheet-loading-data.png

Now we can load the employee data. We pass the selected employee id and visible start and end to the AJAX endpoint so the records can be selected from the database:

function loadEvents() {
  var url = "backend_events.php?resource=" + $("#employee").val() + "&start=" + dp.startDate + "&end=" + dp.startDate.addDays(dp.days);
  dp.events.load(url, function() {
      dp.message("Events for " + $("#employee option:selected").text() + " loaded.");
  });
}

The backend_events.php script returns a JSON array with event details.

<?php
require_once '_db.php';

if ($_GET["resource"]) {
    $stmt = $db->prepare('SELECT * FROM [events] WHERE [resource_id] = :resource AND NOT ((end <= :start) OR (start >= :end))');
    $stmt->bindParam(':resource', $_GET['resource']);
}
else {
    $stmt = $db->prepare('SELECT * FROM [events] WHERE NOT ((end <= :start) OR (start >= :end))');
}
$stmt->bindParam(':start', $_GET['start']);
$stmt->bindParam(':end', $_GET['end']);
$stmt->execute();
$result = $stmt->fetchAll();

class Event {}
$events = array();

foreach($result as $row) {
  $e = new Event();
  $e->id = $row['id'];
  $e->text = $row['name'];
  $e->start = $row['start'];
  $e->end = $row['end'];
  $e->resource = $row['resource_id'];
  $e->bubbleHtml = "Event details: <br/>".$e->text;
  $events[] = $e;
}

header('Content-Type: application/json');
echo json_encode($events);

?>

Example JSON output:

[
  {"id":"22","text":"Project #452","start":"2014-06-03T01:00:00","end":"2014-06-03T05:00:00","resource":"1","bubbleHtml":"Event details: <br\/>Project #452"}
]

Calculating Day Totals

html5-timesheet-day-totals.png

We want to display the totals for each day (row) in an additional header column. You can define the header columns using .rowHeaderColumns property.

dp.rowHeaderColumns = [
  { title: "Day", width: 100},
  { title: "Total", width: 100}
];

Update the row totals:

function updateTotals() {
  dp.rows.each(function(item) {
      var duration = item.events.totalDuration();
      var str;
      if (duration.totalDays() >= 1) {
          str = Math.floor(duration.totalHours()) + ":" + duration.toString("mm");
      }
      else {
          str = duration.toString("H:mm");
      }
      
      item.column(1).html(str + " hours");
  });
}