WEEK PLANNER CALENDAR

by whelkaholism

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.27.0/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<div class="events">
  <div class="event new">
    <input type="text" data-bind="value: NewText" /> -
    <input type="date" data-bind="value: NewFrom" /> -
    <input type="date" data-bind="value: NewTo" />
    <label><input type="checkbox" data-bind="checked: NewIsConfirmed" />
      Confirmed?
    </label>
    <button data-bind="click: addEvent">+ Add</button>
  </div>
</div>
<div class="weeks">
<div class="week legend">
<div class="day">Mon</div>
<div class="day">Tue</div>
<div class="day">Wed</div>
<div class="day">Thu</div>
<div class="day">Fri</div>
<div class="day">Sat</div>
<div class="day">Sun</div>
</div>
<!-- ko foreach: Weeks -->
<div class="week">
<!-- ko foreach: Days -->
<div class="day" data-bind="css: Day + (IsFirstOfMonth ? ' first-of-month' : '') + (IsEvenMonth ? ' even-month' : '')">
<div class="label">
<span class="date" data-bind="text: Date"></span>
<span class="month" data-bind="text: Month, visible: IsFirstOfMonth"></span>
<div class="day-events">
<!-- ko foreach: $parents[1].getDayEvents($data) -->
  <span class="day-event" data-bind="text: Text, style: { 'background-color': IsConfirmed ? Color : null, border: IsConfirmed ? null : '1px dotted ' + Color, color: IsConfirmed ? '#ffffff' : Color }, css: { confirmed: IsConfirmed }"></span>
<!-- /ko -->
</div>
</div>
</div>
<!-- /ko -->
</div><!-- /ko -->
</div>

CSS

body {
  padding: 10px;
  font-family: Arial, Helvetica, sans-serif;
}

div {
  box-sizing: border-box;
}

.weeks { 
  display: flex; 
  flex-direction: column;
  width: 100%;
}

.week { 
  display: flex; 
  justify-content: stretch;
  height: calc(10vh);
}

.week.legend {
  border-bottom: 3px solid #808080;
}

.day { 
  display: flex; 
  width: calc(100% / 7);
  flex-grow: 0;
  flex-shrink: 0;
  border-left: 1px solid #808080;
  border-bottom: 1px solid #808080;
  overflow: hidden;
}

.day.first-of-month {
  border-left: 6px solid #808080 !important;
  font-weight: bold;
}

.day.Saturday, .day.Sunday {
  background: #dddddd;
}

.day.even-month {
  background: #eeeeee;  
}

.day.even-month.Saturday, .day.even-month.Sunday {
  background: #cccccc;
}

.day.Saturday {
  border-left: 3px solid #808080;
}

.day:last-child {
  border-right: 1px solid #808080;
}

.week:first-child .day {
  border-top: 1px solid #808080;
}

.day .label, .week.legend .day {
  padding: 10px;
}

.day-events {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.day-event {
  width: 100%;
  font-size: 11px;
  padding: 2px;
  margin-bottom: 2px;
}

.day-event:last-child {
  margin-bottom: 0;
}

JavaScript

moment.updateLocale('en', {
    week : {
        dow : 1
     }
});

var Model = function() 
{
	var 	_this = this;
  var _colors = [
  '#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075'
  ];
  var _coloridx = 0;
  
  this.Start = ko.observable({ dt: ko.observable('2020-07-20') });
  this.End = ko.observable({ dt: ko.observable('2020-09-02') });
    
  this.NewText = ko.observable();
  this.NewFrom = ko.observable();
  this.NewTo = ko.observable();
  this.NewIsConfirmed = ko.observable();
  
  this.Weeks = ko.observableArray();
  this.Events = ko.observableArray();
  var _eventcolors = {};
  
  this.ActualStart = ko.computed(
  	function()
  	{
    	var mom = moment(_this.Start().dt());
      mom.startOf('week');
      
  		return { dt: ko.observable(mom.format('YYYY-MM-DD')) };
  	}
  );

  this.ActualEnd = ko.computed(
  	function()
  	{
    	var mom = moment(_this.End().dt());
      mom.endOf('week');
      
  		return { dt: ko.observable(mom.format('YYYY-MM-DD')) };
  	}
  );
  
  this.addEvent = function()
  {
      var m = /^([^\(]+)/.exec(_this.NewText());
      var label = m[1];
      label = label.replace(/ +$/, '');
      
      var color = _eventcolors[label];
      
      if(!color)
      {
      	color = _colors[(_coloridx++) % _colors.length];
        _eventcolors[label] = color;
      }

  	_this.Events.push({
    	Text: _this.NewText(),
      Label: label,
      Color: color,
    	From: { dt: ko.observable(_this.NewFrom()) },
      To: { dt: ko.observable(_this.NewTo()) },
      IsConfirmed: _this.NewIsConfirmed()
    });
               
    _this.NewText('');
    _this.NewFrom('');
    _this.NewTo('');
    _this.NewIsConfirmed(false);
  };
  
  this.getDayEvents = function(day)
  {
  	var res = [];
    
  	_this.Events().forEach(function(evt) 
    {   
   ...