JSFiddle - React, Tailwind, and code Playground

HTML

<div id="timesheet"></div>

CSS

.timesheet {
    width: 720px;
    height: 292px;
    margin: 0 auto
}

.timesheet {
    border-top: 1px solid rgba(250,250,250,0.5);
    background-color: #333;
    position: relative
}

.timesheet.color-scheme-default .bubble-default {
    background-color: RGBA(252,70,74,1)
}

.timesheet.color-scheme-default .bubble-lorem {
    background-color: RGBA(154,202,39,1)
}

.timesheet.color-scheme-default .bubble-ipsum {
    background-color: RGBA(60,182,227,1)
}

.timesheet.color-scheme-default .bubble-dolor {
    background-color: RGBA(244,207,48,1)
}

.timesheet.color-scheme-default .bubble-sit {
    background-color: RGBA(169,105,202,1)
}

.timesheet.color-scheme-alternative .bubble-default {
    background-color: #f3552e
}

.timesheet.color-scheme-alternative .bubble-lorem {
    background-color: #88c33a
}

.timesheet.color-scheme-alternative .bubble-ipsum {
    background-color: #436ae0
}

.timesheet.color-scheme-alternative .bubble-dolor {
    background-color: #f4d234
}

.timesheet.color-scheme-alternative .bubble-sit {
    background-color: #707d86
}

.timesheet .scale {
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    float: left
}

.timesheet .scale section {
    float: left;
    width: 59px;
    text-align: center;
    color: rgba(250,250,250,0.8);
    font-family: "Signika Negative";
    font-size: 13px;
    line-height: 24px;
    font-weight: lighter;
    border-left: 1px dashed rgba(250,250,250,0.2);
    height: 100%
}

.timesheet .data {
    margin: 28px 0 0 0;
    padding: 0;
    text-align: left;
    list-style-type: none;
    color: rgba(250,250,250,0.8);
    font-family: "Signika Negative";
    font-size: 13px;
    overflow: hidden
}

.timesheet .data li {
    margin: 0 0 3px 0;
    line-height: 22px;
    height: 21px;
    display: block;
    clear: both;
    position: relative;
    white-space: nowrap
}

.timesheet .data li:hover .bubble {
    opacity: 1
}

.timesheet .data li .date {
    color: #b5b5b5;
    font-size:...

JavaScript

! function() {
    "use strict";
    var Timesheet = function(container, min, max, data) {
        this.data = [], this.year = {
            min: min,
            max: max
        }, this.parse(data || []), "undefined" != typeof document && (this.container = "string" == typeof container ? document.querySelector("#" + container) : container, this.drawSections(), this.insertData())
    };
    Timesheet.prototype.insertData = function() {
        for (var html = [], widthMonth = this.container.querySelector(".scale section").offsetWidth, n = 0, m = this.data.length; m > n; n++) {
            var cur = this.data[n],
                bubble = this.createBubble(widthMonth, this.year.min, cur.start, cur.end),
                line = ['<span style="margin-left: ' + bubble.getStartOffset() + "px; width: " + bubble.getWidth() + 'px;" class="bubble bubble-' + (cur.type || "default") + '" data-duration="' + (cur.end ? Math.round((cur.end - cur.start) / 1e3 / 60 / 60 / 24 / 39) : "") + '"></span>', '<span class="date">' + bubble.getDateLabel() + "</span> ", '<span class="label">' + cur.label + "</span>"].join("");
            html.push("<li>" + line + "</li>")
        }
        this.container.innerHTML += '<ul class="data">' + html.join("") + "</ul>"
    }, Timesheet.prototype.drawSections = function() {
        for (var html = [], c = this.year.min; c <= this.year.max; c++) html.push("<section>" + c + "</section>");
        this.container.className = "timesheet color-scheme-default", this.container.innerHTML = '<div class="scale">' + html.join("") + "</div>"
    }, Timesheet.prototype.parseDate = function(date) {
        return -1 === date.indexOf("/") ? (date = new Date(parseInt(date, 10), 0, 1), date.hasMonth = !1) : (date = date.split("/"), date = new Date(parseInt(date[1], 10), parseInt(date[0], 10) - 1, 1), date.hasMonth = !0), date
    }, Timesheet.prototype.parse = function(data) {
        for (var n = 0, m = data.length; m > n; n++) {
            var beg =...