mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-26 09:42:46 +08:00
Add recent work widget
This commit is contained in:
parent
d940c0a122
commit
e729aa043e
12 changed files with 330 additions and 9 deletions
|
@ -154,10 +154,8 @@ var DasboardCurrentTasksWidget = (function() {
|
|||
}
|
||||
|
||||
function initNavbar() {
|
||||
$('.navbar-assigned, .navbar-all').on('click', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$('.current-tasks-navbar').find('a').removeClass('active');
|
||||
$('.current-tasks-navbar .navbar-link').on('click', function() {
|
||||
$(this).parent().find('.navbar-link').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
loadCurrentTasksList(true);
|
||||
});
|
||||
|
|
64
app/assets/javascripts/dashboard/recent_work.js
Normal file
64
app/assets/javascripts/dashboard/recent_work.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
/* global I18n PerfectSb InfiniteScroll */
|
||||
/* eslint-disable no-param-reassign */
|
||||
|
||||
var DasboardRecentWorkWidget = (function() {
|
||||
function initNavbar() {
|
||||
$('.recent-work-navbar .navbar-link').on('click', function() {
|
||||
$(this).parent().find('.navbar-link').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
$('.recent-work-container').empty();
|
||||
InfiniteScroll.resetScroll('.recent-work-container');
|
||||
PerfectSb().update_all();
|
||||
});
|
||||
}
|
||||
|
||||
function renderRecentWorkItem(json, container) {
|
||||
$.each(json.data, (i, item) => {
|
||||
var recentWorkItem = `<a href="${item.url}" class="recent-work-item">
|
||||
<div class="object-name">${item.name}</div>
|
||||
<div class="object-type">${I18n.t('dashboard.recent_work.subject_type.' + item.subject_type)}</div>
|
||||
<div class="object-changed">${item.last_change}</div>
|
||||
</a>`;
|
||||
$(container).append(recentWorkItem);
|
||||
});
|
||||
}
|
||||
|
||||
function initRecentWork() {
|
||||
InfiniteScroll.init('.recent-work-container', {
|
||||
url: $('.recent-work-container').data('url'),
|
||||
loadFirstPage: true,
|
||||
customResponse: (json, container) => {
|
||||
renderRecentWorkItem(json, container);
|
||||
},
|
||||
customParams: (params) => {
|
||||
params.mode = $('.recent-work-navbar .active').data('mode');
|
||||
return params;
|
||||
},
|
||||
afterAction: (json, container) => {
|
||||
if (json.data.length === 0) {
|
||||
$(container).append(` <div class="no-results">
|
||||
<div class="no-results-title">${I18n.t('dashboard.recent_work.no_results.title')}</div>
|
||||
<div class="no-results-description">${I18n.t('dashboard.recent_work.no_results.description')}</div>
|
||||
<div class="no-results-arrow">
|
||||
<i class="fas fa-angle-double-left"></i>
|
||||
</div>
|
||||
</div>`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
init: () => {
|
||||
if ($('.recent-work-widget').length) {
|
||||
initNavbar();
|
||||
initRecentWork();
|
||||
}
|
||||
}
|
||||
};
|
||||
}());
|
||||
|
||||
$(document).on('turbolinks:load', function() {
|
||||
DasboardRecentWorkWidget.init();
|
||||
});
|
|
@ -30,6 +30,10 @@ var InfiniteScroll = (function() {
|
|||
}
|
||||
$container.removeClass('loading');
|
||||
|
||||
if ($container.data('config').afterAction) {
|
||||
$container.data('config').afterAction(result, $container);
|
||||
}
|
||||
|
||||
if (scrollNotVisible($container)) {
|
||||
loadData($container, $container.data('next-page'));
|
||||
}
|
||||
|
@ -59,7 +63,7 @@ var InfiniteScroll = (function() {
|
|||
initScroll(object, config);
|
||||
},
|
||||
resetScroll: (object) => {
|
||||
$(object).data('next-page', 2).removeClass('last-page');
|
||||
$(object).data('next-page', $(object).data('config').loadFirstPage ? 1 : 2).removeClass('last-page');
|
||||
if (scrollNotVisible($(object))) {
|
||||
loadData($(object), $(object).data('next-page'));
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
}
|
||||
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
@media (max-width: 1300px) {
|
||||
.dashboard-container .quick-start-widget {
|
||||
grid-column: 1 / span 4;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,72 @@
|
|||
// scss-lint:disable SelectorDepth
|
||||
// scss-lint:disable NestingDepth
|
||||
|
||||
.dashboard-container .recent-work-widget {
|
||||
grid-column: 3 / span 7;
|
||||
grid-row: 7 / span 6;
|
||||
|
||||
.widget-title {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.recent-work-container {
|
||||
height: 100%;
|
||||
padding: 0 8px;
|
||||
position: relative;
|
||||
|
||||
.recent-work-item {
|
||||
color: $color-volcano;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
line-height: 28px;
|
||||
padding: 0 8px;
|
||||
text-decoration: none;
|
||||
|
||||
.object-name {
|
||||
flex-grow: 1;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
padding-right: 15px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.object-type {
|
||||
@include font-small;
|
||||
color: $color-silver-chalice;
|
||||
flex-basis: 120px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.object-changed {
|
||||
@include font-small;
|
||||
flex-basis: 160px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: $color-concrete;
|
||||
}
|
||||
}
|
||||
|
||||
.no-results {
|
||||
color: $color-alto;
|
||||
padding: 24px;
|
||||
|
||||
.no-results-title {
|
||||
@include font-h1;
|
||||
}
|
||||
|
||||
.no-results-description {
|
||||
@include font-main;
|
||||
}
|
||||
|
||||
.no-results-arrow {
|
||||
font-size: 32px;
|
||||
padding-top: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1700px) {
|
||||
|
@ -10,7 +76,7 @@
|
|||
}
|
||||
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
@media (max-width: 1300px) {
|
||||
.dashboard-container .recent-work-widget {
|
||||
grid-column: 5 / span 8;
|
||||
}
|
||||
|
@ -20,5 +86,11 @@
|
|||
.dashboard-container .recent-work-widget {
|
||||
grid-column: 1 / span 12;
|
||||
grid-row: 9 / span 4;
|
||||
|
||||
.no-results {
|
||||
.no-results-arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@include font-small;
|
||||
align-items: center;
|
||||
color: $color-silver-chalice;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 0 16px;
|
||||
|
|
12
app/controllers/dashboard/recent_works_controller.rb
Normal file
12
app/controllers/dashboard/recent_works_controller.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class RecentWorksController < ApplicationController
|
||||
def show
|
||||
activities = Dashboard::RecentWorkService.new(current_user, current_team, params[:mode]).call
|
||||
page = (params[:page] || 1).to_i
|
||||
activities = Kaminari.paginate_array(activities).page(page).per(Constants::INFINITE_SCROLL_LIMIT)
|
||||
render json: { data: activities, next_page: activities.next_page }
|
||||
end
|
||||
end
|
||||
end
|
135
app/services/dashboard/recent_work_service.rb
Normal file
135
app/services/dashboard/recent_work_service.rb
Normal file
|
@ -0,0 +1,135 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Dashboard
|
||||
class RecentWorkService
|
||||
include InputSanitizeHelper
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
def initialize(user, team, mode)
|
||||
@user = user
|
||||
@team = team
|
||||
@mode = mode
|
||||
end
|
||||
|
||||
def call
|
||||
visible_projects = Project.viewable_by_user(@user, @team)
|
||||
visible_by_team = Activity.where(project: nil, team_id: @team.id)
|
||||
.where('created_at > ?', (DateTime.now - 1.month))
|
||||
.select('MAX(created_at) as last_change,
|
||||
percentile_disc(0) WITHIN GROUP (ORDER BY values) as values,
|
||||
subject_id,
|
||||
subject_type')
|
||||
.group(:subject_id, :subject_type)
|
||||
.order(last_change: :desc)
|
||||
visible_by_projects = Activity.where(project_id: visible_projects.pluck(:id))
|
||||
.where('created_at > ?', (DateTime.now - 1.month))
|
||||
.select('MAX(created_at) as last_change,
|
||||
percentile_disc(0) WITHIN GROUP (ORDER BY values) as values,
|
||||
subject_id,
|
||||
subject_type')
|
||||
.group(:subject_id, :subject_type)
|
||||
.order(last_change: :desc)
|
||||
|
||||
query = Activity.from("((#{visible_by_team.to_sql}) UNION ALL (#{visible_by_projects.to_sql})) AS activities")
|
||||
|
||||
# Join subjects
|
||||
if %w(all projects).include? @mode
|
||||
query = query.joins("
|
||||
LEFT JOIN projects ON
|
||||
subject_type = 'Project'
|
||||
AND subject_id = projects.id
|
||||
AND projects.archived = 'false'
|
||||
LEFT JOIN experiments ON
|
||||
subject_type = 'Experiment'
|
||||
AND subject_id = experiments.id
|
||||
AND experiments.archived = 'false'
|
||||
LEFT JOIN my_modules ON
|
||||
subject_type = 'MyModule'
|
||||
AND subject_id = my_modules.id
|
||||
AND my_modules.archived = 'false'
|
||||
LEFT JOIN results ON
|
||||
subject_type = 'Result'
|
||||
AND subject_id = results.id
|
||||
LEFT JOIN my_modules my_modules_result ON
|
||||
my_modules_result.id = results.my_module_id
|
||||
AND my_modules_result.archived = 'false'
|
||||
LEFT JOIN my_modules my_modules_protocol ON
|
||||
subject_type = 'Protocol'
|
||||
AND (values #>> '{message_items, my_module, id}') :: BIGINT = my_modules_protocol.id
|
||||
AND my_modules_protocol.archived = 'false'
|
||||
").select('
|
||||
projects.name as project_name,
|
||||
experiments.name as experiment_name,
|
||||
my_modules.name as my_module_name,
|
||||
my_modules_protocol.name as my_module_protocol_name,
|
||||
my_modules_protocol.id as my_module_protocol_id,
|
||||
my_modules_result.name as my_module_result_name,
|
||||
my_modules_result.id as my_module_result_id
|
||||
')
|
||||
end
|
||||
|
||||
if %w(all protocols).include? @mode
|
||||
query = query.joins("LEFT JOIN protocols ON subject_type = 'Protocol'
|
||||
AND subject_id = protocols.id AND protocols.my_module_id IS NULL
|
||||
AND protocols.protocol_type != 4")
|
||||
.select('protocols.name as protocol_name')
|
||||
end
|
||||
|
||||
if %w(all repositories).include? @mode
|
||||
query = query.joins("LEFT JOIN repositories ON subject_type = 'Repository' AND subject_id = repositories.id")
|
||||
.select('repositories.name as repository_name')
|
||||
end
|
||||
|
||||
if %w(all reports).include? @mode
|
||||
query = query.joins("LEFT JOIN reports ON subject_type = 'Report' AND subject_id = reports.id")
|
||||
.select('reports.name as report_name, reports.project_id as report_project_id')
|
||||
end
|
||||
|
||||
query = query.select(:subject_id, :subject_type, :last_change).order(last_change: :desc)
|
||||
|
||||
activities = query.as_json.map do |activity|
|
||||
activity.deep_symbolize_keys!
|
||||
object_name = nil
|
||||
activity.delete_if { |_k, v| v.nil? }
|
||||
if activity[:my_module_protocol_name]
|
||||
activity[:subject_type] = 'MyModule'
|
||||
activity[:subject_id] = activity.delete :my_module_protocol_id
|
||||
end
|
||||
if activity[:my_module_result_name]
|
||||
activity[:subject_type] = 'MyModule'
|
||||
activity[:subject_id] = activity.delete :my_module_result_id
|
||||
end
|
||||
activity.each do |key, _value|
|
||||
object_name = activity.delete key if key.to_s.include? 'name'
|
||||
end
|
||||
activity[:last_change] = I18n.l(DateTime.parse(activity[:last_change]), format: :full_with_comma)
|
||||
activity[:name] = escape_input(object_name)
|
||||
activity[:url] = generate_url(activity)
|
||||
activity unless activity[:name].empty?
|
||||
end.compact
|
||||
|
||||
activities.uniq! { |activity| [activity[:subject_type], activity[:subject_id]].join(':') }
|
||||
|
||||
activities
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def generate_url(activity)
|
||||
case activity[:subject_type]
|
||||
when 'MyModule'
|
||||
protocols_my_module_path(activity[:subject_id])
|
||||
when 'Experiment'
|
||||
canvas_experiment_path(activity[:subject_id])
|
||||
when 'Project'
|
||||
project_path(activity[:subject_id])
|
||||
when 'Protocol'
|
||||
edit_protocol_path(activity[:subject_id])
|
||||
when 'Repository'
|
||||
repository_path(activity[:subject_id])
|
||||
when 'Report'
|
||||
edit_project_report_path(activity[:report_project_id], activity[:subject_id]) if activity[:report_project_id]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -53,8 +53,8 @@
|
|||
</div>
|
||||
|
||||
<div class="sci-secondary-navbar current-tasks-navbar">
|
||||
<a class="navbar-link navbar-assigned active" href="" data-remote="true" data-mode="assigned"><%= t("dashboard.current_tasks.navbar.assigned") %></a>
|
||||
<a class="navbar-link navbar-all" href="" data-remote="true" data-mode="team"><%= t("dashboard.current_tasks.navbar.all") %></a>
|
||||
<span class="navbar-link active" data-mode="assigned"><%= t("dashboard.current_tasks.navbar.assigned") %></span>
|
||||
<span class="navbar-link" data-mode="team"><%= t("dashboard.current_tasks.navbar.all") %></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,2 +1,17 @@
|
|||
<div class="recent-work-widget basic-widget">
|
||||
<div class="widget-header">
|
||||
<div class="widget-title">
|
||||
<%= t('dashboard.recent_work.title') %>
|
||||
</div>
|
||||
<div class="sci-secondary-navbar recent-work-navbar">
|
||||
<span class="navbar-link active" data-mode="all"><%= t('dashboard.recent_work.modes.all') %></span>
|
||||
<span class="navbar-link" data-mode="projects"><%= t('dashboard.recent_work.modes.projects') %></span>
|
||||
<span class="navbar-link" data-mode="repositories"><%= t('dashboard.recent_work.modes.repositories') %></span>
|
||||
<span class="navbar-link" data-mode="protocols"><%= t('dashboard.recent_work.modes.protocols') %></span>
|
||||
<span class="navbar-link" data-mode="reports"><%= t('dashboard.recent_work.modes.reports') %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="widget-body">
|
||||
<div class="recent-work-container perfect-scrollbar" data-url="<%= dashboard_recent_works_path %>"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -60,3 +60,21 @@ en:
|
|||
experiment_disabled_placeholder: "Select Project to enable Experiments"
|
||||
filter_create_new: "Create"
|
||||
create_task: "Create Task"
|
||||
recent_work:
|
||||
title: "Recent work"
|
||||
no_results:
|
||||
title: "You have not worked on anything recently."
|
||||
description: "Use the Quick start section to begin creating."
|
||||
modes:
|
||||
all: "All"
|
||||
projects: "PROJECTS"
|
||||
repositories: "INVENTORIES"
|
||||
protocols: "PROTOCOLS"
|
||||
reports: "REPORTS"
|
||||
subject_type:
|
||||
Project: "Project"
|
||||
Experiment: "Experiment"
|
||||
MyModule: "Task"
|
||||
Repository: "Inventory"
|
||||
Protocol: "Protocol"
|
||||
Report: "Report"
|
||||
|
|
|
@ -255,6 +255,8 @@ Rails.application.routes.draw do
|
|||
resource :calendar, module: 'dashboard', only: [:show] do
|
||||
get :day
|
||||
end
|
||||
|
||||
resource :recent_works, module: 'dashboard', only: [:show]
|
||||
end
|
||||
|
||||
resources :projects, except: [:new, :destroy] do
|
||||
|
|
Loading…
Reference in a new issue