Merge pull request #2424 from aignatov-bio/ai-sci-add-calendar-widget

Add calendar widget [SCI-4373][SCI-4371][SCI-4372]
This commit is contained in:
aignatov-bio 2020-02-26 12:04:57 +01:00 committed by GitHub
commit df51140d2d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 381 additions and 3 deletions

View file

@ -67,7 +67,7 @@ gem 'down', '~> 5.0'
gem 'faker' # Generate fake data
gem 'fastimage' # Light gem to get image resolution
gem 'httparty', '~> 0.13.1'
gem 'i18n-js', '~> 3.0' # Localization in javascript files
gem 'i18n-js', '~> 3.6' # Localization in javascript files
gem 'jbuilder' # JSON structures via a Builder-style DSL
gem 'logging', '~> 2.0.0'
gem 'nested_form_fields'

View file

@ -281,7 +281,7 @@ GEM
multi_xml (>= 0.5.2)
i18n (1.6.0)
concurrent-ruby (~> 1.0)
i18n-js (3.3.0)
i18n-js (3.6.0)
i18n (>= 0.6.6)
image_processing (1.9.3)
mini_magick (>= 4.9.5, < 5)
@ -631,7 +631,7 @@ DEPENDENCIES
figaro
hammerjs-rails
httparty (~> 0.13.1)
i18n-js (~> 3.0)
i18n-js (~> 3.6)
image_processing (~> 1.2)
jbuilder
jquery-rails

View file

@ -31,6 +31,7 @@
//= require bootstrap-select
//= require_directory ./repository_columns/columns_initializers
//= require datatables
//= require clndr.min
//= require ajax-bootstrap-select.min
//= require underscore
//= require i18n.js

View file

@ -0,0 +1,88 @@
/* global I18n */
/* eslint-disable no-underscore-dangle */
var DasboardCalendarWidget = (function() {
function calendarTemplate() {
return `<script id="calendar-template" type="text/template">
<div class="controls">
<div class="clndr-previous-button">
<div class="btn btn-light icon-btn"><i class="fas fa-angle-double-left"></i></div>
</div>
<div class="clndr-title"><%= month %> <%= year %></div>
<div class="clndr-next-button">
<div class="btn btn-light icon-btn"><i class="fas fa-angle-double-right"></i></div>
</div>
</div>
<div class="days-container">
<% _.each(daysOfTheWeek, function(day) { %>
<div class="day-header"><%= day %></div>
<% }); %>
<% _.each(days, function(day) { %>
<% if (day.classes.includes('event')){ %>
<div class="<%= day.classes %>" id="<%= day.id %>">
<div class="event-day" data-toggle="dropdown"><%= day.day %></div>
<div class="dropdown-menu events-container dropdown-menu-right" role="menu">
<div class="title">${I18n.t('dashboard.calendar.due_on')} <%= day.date.format(formatJS) %></div>
<div class="tasks"></div>
</div>
</div>
<% } else { %>
<div class="<%= day.classes %>" id="<%= day.id %>"><%= day.day %></div>
<% } %>
<% }); %>
</div>
</script>`;
}
function getMonthEventsList(date, clndrInstance) {
var getUrl = $('.dashboard-calendar').data('month-events-url');
$.get(getUrl, { date: date }, function(result) {
clndrInstance.setEvents(result.events);
});
}
function initCalendar() {
var dayOfWeek = [
I18n.t('dashboard.calendar.dow.su'),
I18n.t('dashboard.calendar.dow.mo'),
I18n.t('dashboard.calendar.dow.tu'),
I18n.t('dashboard.calendar.dow.we'),
I18n.t('dashboard.calendar.dow.th'),
I18n.t('dashboard.calendar.dow.fr'),
I18n.t('dashboard.calendar.dow.sa')
];
var clndrInstance = $('.dashboard-calendar').clndr({
template: $(calendarTemplate()).html(),
daysOfTheWeek: dayOfWeek,
forceSixRows: true,
clickEvents: {
click: function(target) {
var getDayUrl = $('.dashboard-calendar').data('day-events-url');
if ($(target.element).hasClass('event')) {
$.get(getDayUrl, { date: target.date._i }, function(result) {
$(target.element).find('.tasks').html(result.html);
});
}
},
onMonthChange: function(month) {
getMonthEventsList(month._d, clndrInstance);
}
}
});
getMonthEventsList((new Date()), clndrInstance);
}
return {
init: () => {
if ($('.current-tasks-widget').length) {
initCalendar();
}
}
};
}());
$(document).on('turbolinks:load', function() {
DasboardCalendarWidget.init();
});

View file

@ -1,11 +1,134 @@
// scss-lint:disable SelectorDepth
// scss-lint:disable NestingDepth
.dashboard-container .calendar-widget {
grid-column: 10 / span 3;
grid-row: 1 / span 6;
.dashboard-calendar {
height: 100%;
width: 100%;
}
.clndr {
display: flex;
flex-direction: column;
height: 100%;
.controls {
border-bottom: $border-default;
display: flex;
flex-basis: 42px;
padding: 3px;
.clndr-title {
@include font-h3;
align-items: center;
display: flex;
flex-grow: 1;
justify-content: center;
}
}
.days-container {
align-items: center;
display: grid;
flex-grow: 1;
grid-column-gap: 6px;
grid-row-gap: 6px;
grid-template-columns: repeat(7, 1fr);
grid-template-rows: repeat(7, 1fr);
justify-items: center;
padding: 6px;
.day-header {
@include font-button;
color: $color-silver-chalice;
font-weight: bold;
}
.day {
@include font-button;
align-items: center;
animation-timing-function: $timing-function-sharp;
border-radius: 50%;
display: flex;
height: 32px;
justify-content: center;
position: relative;
transition: .3s;
user-select: none;
width: 32px;
&.adjacent-month {
color: $color-alto;
}
&.today {
border: $border-primary;
}
&.event {
.event-day {
align-items: center;
border-radius: 50%;
cursor: pointer;
display: flex;
height: 32px;
justify-content: center;
width: 32px;
&:hover {
background: $color-alto;
color: inherit;
}
}
&::after {
background: $brand-danger;
border-radius: 50%;
content: "";
height: 4px;
left: 14px;
position: absolute;
top: 24px;
width: 4px;
}
}
.events-container {
color: $color-black;
padding: 8px;
width: 280px;
.title {
@include font-h3;
margin-bottom: 8px;
}
}
}
}
}
}
@media (max-width: 1250px) {
.dashboard-container .calendar-widget {
grid-column: 9 / span 4;
}
}
@media (max-width: 1000px) {
.dashboard-container .calendar-widget {
grid-column: 1 / span 6;
grid-row: 5 / span 4;
.clndr {
.events-container {
left: 0;
right: auto;
}
}
}
}

View file

@ -159,6 +159,12 @@
}
}
@media (max-width: 1250px) {
.dashboard-container .current-tasks-widget {
grid-column: 1 / span 8;
}
}
@media (max-width: 1000px) {
.dashboard-container .current-tasks-widget {
grid-column: 1 / span 12;

View file

@ -0,0 +1,55 @@
// scss-lint:disable SelectorDepth
// scss-lint:disable NestingDepth
.my-modules-list-partial {
width: 100%;
.task-group:not(:first-child) {
border-top: $border-tertiary;
}
.header {
@include font-small;
align-items: center;
color: $color-silver-chalice;
display: flex;
height: 20px;
margin-top: 5px;
width: 100%;
.project,
.experiment {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.slash {
flex-basis: 20px;
text-align: center;
}
}
.tasks {
@include font-button;
margin-bottom: 5px;
.task {
align-items: center;
display: flex;
line-height: 25px;
.task-icon {
margin-right: 9px;
path {
fill: $brand-primary;
}
}
.task-link {
line-height: 24px;
}
}
}
}

View file

@ -8,6 +8,7 @@ $border-default: 1px solid $color-alto;
$border-secondary: 1px solid $color-silver-chalice;
$border-tertiary: 1px solid $color-concrete;
$border-primary: 1px solid $brand-primary;
$border-focus: 1px solid $brand-focus;
$border-success: 1px solid $brand-success;
$border-danger: 1px solid $brand-danger;

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Dashboard
class CalendarsController < ApplicationController
include IconsHelper
def show
date = DateTime.parse(params[:date])
start_date = date.at_beginning_of_month.utc - 7.days
end_date = date.at_end_of_month.utc + 14.days
due_dates = current_user.my_modules.active.uncomplete
.where('due_date > ? AND due_date < ?', start_date, end_date)
.joins(:protocols).where('protocols.team_id = ?', current_team.id)
.pluck(:due_date)
render json: { events: due_dates.map { |i| { date: i } } }
end
def day
date = DateTime.parse(params[:date]).utc
my_modules = current_user.my_modules.active.uncomplete
.where('DATE(my_modules.due_date) = DATE(?)', date)
.where('projects.team_id = ?', current_team.id)
.my_modules_list_partial
render json: {
html: render_to_string(partial: 'shared/my_modules_list_partial.html.erb', locals: { task_groups: my_modules })
}
end
end
end

View file

@ -13,6 +13,8 @@ module IconsHelper
when 'shared-read'
title = "<title>#{t('repositories.icon_title.shared_read', team_name: team.name)}</title>"
icon = '<path fill="#A0A0A0" fill-rule="evenodd" d="M6.6 8.922c-.688 0-1.34-.172-1.925-.515a3.478 3.478 0 0 1-1.41-1.41 3.758 3.758 0 0 1-.515-1.925c0-.687.172-1.306.516-1.925.343-.584.79-1.065 1.409-1.41A3.758 3.758 0 0 1 6.6 1.223a3.85 3.85 0 0 1 1.925.516 3.955 3.955 0 0 1 1.41 1.41 3.85 3.85 0 0 1 .515 1.924c0 .688-.172 1.34-.516 1.925-.343.619-.825 1.066-1.409 1.41a3.85 3.85 0 0 1-1.925.515zm2.647 1.1c.687 0 1.34.207 1.96.55.618.344 1.1.825 1.443 1.444.281.506.47 1.036.53 1.588a7.217 7.217 0 0 0-2.564 2.568 2.64 2.64 0 0 0-.218.45H1.65c-.481 0-.86-.137-1.169-.481-.343-.31-.481-.687-.481-1.169v-.997c0-.687.172-1.34.516-1.959.343-.619.825-1.1 1.443-1.444.62-.343 1.272-.55 1.994-.55h.275c.756.378 1.547.55 2.372.55a5.43 5.43 0 0 0 2.372-.55h.275zm11.43 3.546c.442.263.85.562 1.22.898.068-.18.103-.376.103-.594a3.85 3.85 0 0 0-.516-1.925 3.955 3.955 0 0 0-1.409-1.41 3.849 3.849 0 0 0-1.925-.515h-.137a4.156 4.156 0 0 1-1.513.275c-.481 0-.997-.069-1.512-.275h-.138c-.688 0-1.306.172-1.925.516.412.481.756 1.031.997 1.615.125.306.223.62.288.94a7.268 7.268 0 0 1 2.748-.527c1.326 0 2.577.34 3.704.994l.014.008zM16.5 8.922c-.928 0-1.719-.31-2.338-.962-.653-.619-.962-1.41-.962-2.338 0-.894.31-1.684.963-2.337a3.216 3.216 0 0 1 2.337-.963c.894 0 1.684.344 2.337.963.62.653.963 1.443.963 2.337 0 .928-.344 1.719-.963 2.338a3.193 3.193 0 0 1-2.337.962zm5.347 8.6a.932.932 0 0 0-.119-.407 5.658 5.658 0 0 0-1.986-1.969 5.473 5.473 0 0 0-2.784-.747 5.428 5.428 0 0 0-2.784.747 5.39 5.39 0 0 0-1.986 1.97.75.75 0 0 0-.119.407c0 .152.034.288.12.407a5.148 5.148 0 0 0 1.985 1.97c.85.509 1.766.746 2.784.746 1.002 0 1.936-.237 2.784-.747a5.39 5.39 0 0 0 1.986-1.969.818.818 0 0 0 .12-.407zm-3.734 2.004a2.303 2.303 0 0 1-1.155.305c-.424 0-.814-.101-1.154-.305a2.264 2.264 0 0 1-.849-.849 2.214 2.214 0 0 1-.305-1.154c0-.408.101-.798.305-1.155.204-.34.492-.628.849-.831.34-.204.73-.323 1.154-.323.408 0 .798.119 1.155.323.34.203.628.492.831.831.204.357.323.747.323 1.155 0 .424-.119.815-.323 1.154a2.346 2.346 0 0 1-.831.849zm.085-3.242c.339.34.526.764.526 1.239 0 .492-.187.916-.526 1.256-.34.34-.764.51-1.24.51-.492 0-.916-.17-1.256-.51-.34-.34-.51-.764-.51-1.256 0-.289.069-.56.205-.832 0 .204.067.39.203.526a.73.73 0 0 0 .527.204.691.691 0 0 0 .509-.204.745.745 0 0 0 .22-.526.706.706 0 0 0-.22-.51.706.706 0 0 0-.51-.22c.255-.136.527-.204.832-.204.476 0 .9.187 1.24.527z" clip-rule="evenodd"/>'
when 'task-icon'
return '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" class="task-icon"><path d="M6.646 14V7.215H4V5h7.938v2.215H9.292V14h4.144c.26 0 .434-.009.542-.022.013-.107.022-.282.022-.542V2.564c0-.26-.009-.435-.022-.542A4.762 4.762 0 0 0 13.436 2H2.564c-.26 0-.435.009-.542.022A4.762 4.762 0 0 0 2 2.564v10.872c0 .26.009.434.022.542.107.013.282.022.542.022h4.082zM2.564 0h10.872c.892 0 1.215.093 1.54.267.327.174.583.43.757.756.174.326.267.65.267 1.54v10.873c0 .892-.093 1.215-.267 1.54-.174.327-.43.583-.756.757-.326.174-.65.267-1.54.267H2.563c-.892 0-1.215-.093-1.54-.267a1.817 1.817 0 0 1-.757-.756C.093 14.65 0 14.327 0 13.437V2.563c0-.892.093-1.215.267-1.54.174-.327.43-.583.756-.757C1.35.093 1.673 0 2.563 0z" fill="#B3B3B3" fill-rule="evenodd"/></svg>'.html_safe
end
('<svg class="fas-custom" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 22 22">' + title + icon + '</svg>').html_safe
end

View file

@ -514,6 +514,21 @@ class MyModule < ApplicationRecord
self.completed_on = nil
end
def self.my_modules_list_partial
ungrouped_tasks = joins(experiment: :project)
.select('experiments.name as experiment_name,
projects.name as project_name,
my_modules.name as task_name,
my_modules.id')
ungrouped_tasks.group_by { |i| [i[:project_name], i[:experiment_name]] }.map do |group, tasks|
{
project_name: group[0],
experiment_name: group[1],
tasks: tasks.map { |task| { id: task.id, task_name: task.task_name } }
}
end
end
private
def create_blank_protocol

View file

@ -1,2 +1,21 @@
<div class="calendar-widget basic-widget">
<div class="dashboard-calendar"
data-month-events-url="<%= dashboard_calendar_path %>"
data-day-events-url="<%= day_dashboard_calendar_path %>"
></div>
</div>
<script type="text/javascript" charset="utf-8">
<%
js_format = I18n.backend.date_format.dup
js_format.gsub!(/%-d/, 'D')
js_format.gsub!(/%d/, 'DD')
js_format.gsub!(/%-m/, 'M')
js_format.gsub!(/%m/, 'MM')
js_format.gsub!(/%b/, 'MMM')
js_format.gsub!(/%B/, 'MMMM')
js_format.gsub!('%Y', 'YYYY')
%>
var formatJS = "<%= js_format %>"
</script>

View file

@ -0,0 +1,19 @@
<div class="my-modules-list-partial">
<% task_groups.each do |task_group| %>
<div class="task-group">
<div class="header">
<span class="project" title="<%= task_group[:project_name] %>"><%= task_group[:project_name] %></span>
<span class="slash">/</span>
<span class="experiment" title="<%= task_group[:experiment_name] %>"><%= task_group[:experiment_name] %></span>
</div>
<div class="tasks">
<% task_group[:tasks].each do |task| %>
<div class="task">
<%= draw_custom_icon('task-icon') %>
<%= link_to(task[:task_name], protocols_my_module_path(task[:id]), {class: "task-link", title: task[:task_name]}) %>
</div>
<% end %>
</div>
<% end %>
</div>
</div>

View file

@ -22,3 +22,13 @@ en:
overdue: "Overdue: %{steps} / %{total_steps}"
completed: "Completed"
locked: "Locked"
calendar:
due_on: Due on
dow:
su: 'Su'
mo: 'Mo'
tu: 'Tu'
we: 'We'
th: 'Th'
fr: 'Fr'
sa: 'Sa'

View file

@ -245,6 +245,10 @@ Rails.application.routes.draw do
get :project_filter
get :experiment_filter
end
resource :calendar, module: 'dashboard', only: [:show] do
get :day
end
end
resources :projects, except: [:new, :destroy] do

File diff suppressed because one or more lines are too long