From 9bd2e8bec41f956789c939e153eeb99f1cd837c5 Mon Sep 17 00:00:00 2001 From: Mojca Lorber Date: Fri, 21 Feb 2020 10:41:42 +0100 Subject: [PATCH] Dashboard current tasks visual layout and backend --- Gemfile | 2 +- .../javascripts/dashboard/current_tasks.js | 86 +++++++-- .../stylesheets/dashboard/current_tasks.scss | 165 +++++++++++------- app/assets/stylesheets/dashboard/show.scss | 13 ++ .../shared_styles/elements/navigation.scss | 35 ++++ .../dashboard/current_tasks_controller.rb | 49 +++++- app/models/my_module.rb | 12 ++ app/views/dashboards/_current_tasks.html.erb | 95 ++++++---- config/locales/dashboard/en.yml | 14 +- 9 files changed, 358 insertions(+), 113 deletions(-) create mode 100644 app/assets/stylesheets/shared_styles/elements/navigation.scss diff --git a/Gemfile b/Gemfile index 05da21d5b..06c920dcd 100644 --- a/Gemfile +++ b/Gemfile @@ -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' diff --git a/app/assets/javascripts/dashboard/current_tasks.js b/app/assets/javascripts/dashboard/current_tasks.js index 0eef52983..930996340 100644 --- a/app/assets/javascripts/dashboard/current_tasks.js +++ b/app/assets/javascripts/dashboard/current_tasks.js @@ -1,16 +1,65 @@ -/* global dropdownSelector */ +/* global dropdownSelector I18n animateSpinner */ /* eslint-disable no-param-reassign */ var DasboardCurrentTasksWidget = (function() { - function initFilters() { - var sortFilter = '.curent-tasks-filters .sort-filter'; - var viewFilter = '.curent-tasks-filters .view-filter'; - var projectFilter = '.curent-tasks-filters .project-filter'; - var experimentFilter = '.curent-tasks-filters .experiment-filter'; + var sortFilter = '.curent-tasks-filters .sort-filter'; + var viewFilter = '.curent-tasks-filters .view-filter'; + var projectFilter = '.curent-tasks-filters .project-filter'; + var experimentFilter = '.curent-tasks-filters .experiment-filter'; - $('.curent-tasks-filters .clear-button').click(() => { + function loadCurrentTasksList() { + var $currentTasksList = $('.current-tasks-list'); + var params = { + project_id: dropdownSelector.getValues(projectFilter), + experiment_id: dropdownSelector.getValues(experimentFilter), + sort: dropdownSelector.getValues(sortFilter), + view: dropdownSelector.getValues(viewFilter), + mode: $('.current-tasks-navbar .active').data('mode') + }; + animateSpinner($currentTasksList, true); + $.get($currentTasksList.attr('data-tasks-list-url'), params, function(data) { + // Clear the list + $currentTasksList.find('.current-task-item').remove(); + $.each(data.tasks_list, (i, task) => { + var currentTaskItem; + var stepsPercentage = (task.steps_state === 0) ? '' : task.steps_state.percentage + '%'; + var stateText; + var dueDate = (task.due_date !== null) ? '' + + I18n.t('dashboard.current_tasks.due_date', { date: task.due_date }) : ''; + var overdue = (task.overdue) ? 'overdue' : ''; + if (task.state === 'completed') { + stateText = I18n.t('dashboard.current_tasks.progress_bar.completed'); + } else { + stateText = I18n.t('dashboard.current_tasks.progress_bar.in_progress'); + if (task.overdue) { stateText = I18n.t('dashboard.current_tasks.progress_bar.overdue'); } + if (task.steps_state !== 0) { + stateText += I18n.t('dashboard.current_tasks.progress_bar.completed_steps', + { steps: task.steps_state.completed_steps, total_steps: task.steps_state.all_steps }); + } + } + currentTaskItem = ` +
${task.project}/${task.experiment}
+
+
${task.name}
+
${dueDate}
+
+
+
${stateText}
+
+
+
`; + $currentTasksList.append(currentTaskItem); + }); + animateSpinner($currentTasksList, false); + }); + } + + function initFilters() { + $('.curent-tasks-filters .clear-button').click((e) => { + e.stopPropagation(); + e.preventDefault(); dropdownSelector.selectValue(sortFilter, 'date_asc'); - dropdownSelector.selectValue(viewFilter, 'active'); + dropdownSelector.selectValue(viewFilter, 'uncompleted'); dropdownSelector.clearData(projectFilter); dropdownSelector.clearData(experimentFilter); }); @@ -37,7 +86,7 @@ var DasboardCurrentTasksWidget = (function() { emptyOptionAjax: true, selectAppearance: 'simple', ajaxParams: (params) => { - params.mode = 'team'; + params.mode = $('.current-tasks-navbar .active').data('mode'); return params; }, onChange: () => { @@ -57,7 +106,7 @@ var DasboardCurrentTasksWidget = (function() { emptyOptionAjax: true, selectAppearance: 'simple', ajaxParams: (params) => { - params.mode = 'team'; + params.mode = $('.current-tasks-navbar .active').data('mode'); params.project_id = dropdownSelector.getValues(projectFilter); return params; } @@ -72,12 +121,29 @@ var DasboardCurrentTasksWidget = (function() { dropdownSelector.closeDropdown(projectFilter); dropdownSelector.closeDropdown(experimentFilter); }); + + $('.curent-tasks-filters .apply-filters').click((e) => { + $('.curent-tasks-filters').dropdown('toggle'); + e.stopPropagation(); + e.preventDefault(); + loadCurrentTasksList(); + }); + } + + function initNavbar() { + $('.navbar-assigned, .navbar-all').on('click', function() { + $('.current-tasks-navbar').find('a').removeClass('active'); + $(this).addClass('active'); + loadCurrentTasksList(); + }); } return { init: () => { if ($('.current-tasks-widget').length) { + initNavbar(); initFilters(); + loadCurrentTasksList(); } } }; diff --git a/app/assets/stylesheets/dashboard/current_tasks.scss b/app/assets/stylesheets/dashboard/current_tasks.scss index 29e572742..2023c8dec 100644 --- a/app/assets/stylesheets/dashboard/current_tasks.scss +++ b/app/assets/stylesheets/dashboard/current_tasks.scss @@ -5,19 +5,117 @@ grid-column: 1 / span 9; grid-row: 1 / span 6; + .title { + flex-shrink: 0; + } + + .actions-container { + display: flex; + flex-grow: 1; + } + + .filter-container { + height: 36px; + margin-right: 4px; + width: 36px; + + .curent-tasks-filters { + padding: 0; + width: 230px; + + .header { + align-items: center; + border-bottom: $border-default; + display: flex; + height: 44px; + margin-bottom: 16px; + padding: 0 5px 0 16px; + + .title { + @include font-h2; + flex-grow: 1; + user-select: none; + + } + } + + .select-block { + display: inline-block; + padding: 0 16px 16px; + position: relative; + width: 100%; + + label { + @include font-small; + display: inline-block; + font-weight: bold; + margin-bottom: 5px; + user-select: none; + } + } + + .footer { + align-items: center; + border-top: $border-default; + display: flex; + height: 68px; + justify-content: center; + position: relative; + width: 100%; + } + } + } + + .current-tasks-list { + display: flex; + flex-direction: column; + height: 293px; + overflow-y: auto; + padding: 0 16px; + + .current-task-item { + border-bottom: $border-tertiary; + color: $color-volcano; + padding: 6px 0; + text-decoration: none; + + .current-task-breadcrumbs { + @include font-small; + color: $color-silver-chalice; + line-height: 14px; + } + + .item-row{ + display: flex; + + .task-name { + flex-grow: 1; + font-size: $font-size-base; //not in styleguide + font-weight: bold; + } + + .task-due-date { + flex-basis: 260px; + font-size: 14px; + + .fas { + padding: 4px; + } + + &.overdue { + color: $brand-danger; + } + } + } + } + } + .task-progress-container { - max-width: 300px; + max-width: 250px; min-width: 150px; position: relative; width: 100%; - // Example - // - //
- //
- //
t('...')
- //
- &::after { @include font-small; @include font-awesome; @@ -106,57 +204,6 @@ } } } - - .filter-container { - height: 36px; - width: 36px; - - .curent-tasks-filters { - padding: 0; - width: 230px; - - .header { - align-items: center; - border-bottom: $border-default; - display: flex; - height: 44px; - margin-bottom: 16px; - padding: 0 5px 0 16px; - - .title { - @include font-h2; - flex-grow: 1; - user-select: none; - - } - } - - .select-block { - display: inline-block; - padding: 0 16px 16px; - position: relative; - width: 100%; - - label { - @include font-small; - display: inline-block; - font-weight: bold; - margin-bottom: 5px; - user-select: none; - } - } - - .footer { - align-items: center; - border-top: $border-default; - display: flex; - height: 68px; - justify-content: center; - position: relative; - width: 100%; - } - } - } } @media (max-width: 1000px) { diff --git a/app/assets/stylesheets/dashboard/show.scss b/app/assets/stylesheets/dashboard/show.scss index a8b04836d..c2cf952ee 100644 --- a/app/assets/stylesheets/dashboard/show.scss +++ b/app/assets/stylesheets/dashboard/show.scss @@ -14,6 +14,19 @@ border-radius: $border-radius-modal; box-shadow: $flyout-shadow; position: relative; + + .widget-header { + align-items: center; + border-bottom: $border-tertiary; + display: flex; + height: 44px; + + h2 { + line-height: 18px; + margin: 10px 16px; + padding: 4px 0; + } + } } } diff --git a/app/assets/stylesheets/shared_styles/elements/navigation.scss b/app/assets/stylesheets/shared_styles/elements/navigation.scss new file mode 100644 index 000000000..740329ea0 --- /dev/null +++ b/app/assets/stylesheets/shared_styles/elements/navigation.scss @@ -0,0 +1,35 @@ +.sci-secondary-navbar { + display: flex; + height: 100%; + + a { + @include font-small; + align-items: center; + color: $color-silver-chalice; + display: flex; + height: 100%; + padding: 0 16px; + position: relative; + text-decoration: none; + text-transform: uppercase; + + &:hover { + color: $color-volcano; + } + + &.active { + color: $color-volcano; + font-weight: bold; + + &::before { + background: $brand-primary; + bottom: 0; + content: ""; + height: 4px; + left: 0; + position: absolute; + width: 100%; + } + } + } +} diff --git a/app/controllers/dashboard/current_tasks_controller.rb b/app/controllers/dashboard/current_tasks_controller.rb index 3d0c040ec..6ca392548 100644 --- a/app/controllers/dashboard/current_tasks_controller.rb +++ b/app/controllers/dashboard/current_tasks_controller.rb @@ -4,7 +4,54 @@ module Dashboard class CurrentTasksController < ApplicationController include InputSanitizeHelper - def show; end + def show + if params[:project_id] + if params[:experiment_id] + tasks = MyModule.active.joins(:experiment).where('experiments.id': params[:experiment_id]) + else + tasks = MyModule.active.joins(:experiment).where('experiments.project_id': params[:project_id]) + end + else + tasks = MyModule.active.joins(experiment: :project).where('projects.team_id': current_team) + end + if params[:mode] == 'assigned' + tasks = tasks.left_outer_joins(:user_my_modules).where('user_my_modules.user_id': current_user.id) + end + tasks = tasks.where('my_modules.state': params[:view]) + + case params[:sort] + when 'date_desc' + tasks = tasks.order('my_modules.due_date': :desc) + when 'date_asc' + tasks = tasks.order('my_modules.due_date': :asc) + when 'atoz' + tasks = tasks.order('my_modules.name': :asc) + when 'ztoa' + tasks = tasks.order('my_modules.name': :desc) + else + tasks + end + + respond_to do |format| + format.json do + render json: { + tasks_list: tasks.map do |task| + due_date = I18n.l(task.due_date, format: :full_with_comma) if task.due_date.present? + { id: task.id, + link: protocols_my_module_path(task.id), + experiment: task.experiment.name, + project: task.experiment.project.name, + name: escape_input(task.name), + due_date: due_date, + overdue: task.is_overdue?, + state: task.state, + steps_state: task.completed_steps_percentage } + end, + status: :ok + } + end + end + end def project_filter projects = current_team.projects.search(current_user, false, params[:query], 1, current_team).select(:id, :name) diff --git a/app/models/my_module.rb b/app/models/my_module.rb index 486e30f10..971a3b9e1 100644 --- a/app/models/my_module.rb +++ b/app/models/my_module.rb @@ -492,6 +492,18 @@ class MyModule < ApplicationRecord state == 'completed' end + def completed_steps_percentage + if protocol && protocol.steps.count.positive? + { + completed_steps: protocol.steps.count(&:completed), + all_steps: protocol.steps.count, + percentage: (protocol.steps.count(&:completed).fdiv(protocol.steps.count) * 100).round + } + else + 0 + end + end + # Check if my_module is ready to become completed def check_completness_status if protocol && protocol.steps.count > 0 diff --git a/app/views/dashboards/_current_tasks.html.erb b/app/views/dashboards/_current_tasks.html.erb index 399230136..8ab54c346 100644 --- a/app/views/dashboards/_current_tasks.html.erb +++ b/app/views/dashboards/_current_tasks.html.erb @@ -1,44 +1,63 @@
-