Adding side pane filter and top pane tags [SCI - 3008, 3009, 3010, 3011, 3012, 3018, 3019, 3020] (#1521)

* Adding side pane filtering and top pane
This commit is contained in:
aignatov-bio 2019-03-06 10:34:04 +01:00 committed by GitHub
parent af5cc4a5bb
commit ec2dbb897f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 597 additions and 37 deletions

View file

@ -0,0 +1,175 @@
// Common code
Date.prototype.date_to_string = function() {
return this.getFullYear() + '-' + (this.getMonth() + 1) + '-' + this.getDate();
};
// GA code
function GlobalActivitiesFiltersGetDates() {
var fromDate = $('#calendar-from-date').data('DateTimePicker').date()._d.date_to_string();
var toDate = $('#calendar-to-date').data('DateTimePicker').date()._d.date_to_string();
return { from: fromDate, to: toDate };
}
function GlobalActivitiesFilterPrepareArray() {
var teamFilter = ($('.ga-side .team-selector select').val() || [])
.map(e => { return parseInt(e, 10); });
var userFilter = ($('.ga-side .user-selector select').val() || [])
.map(e => { return parseInt(e, 10); });
var activityFilter = ($('.ga-side .activity-selector select').val() || [])
.map(e => { return parseInt(e, 10); });
var subjectFilter = {};
$.each(($('.ga-side .subject-selector select').val() || []), function(index, object) {
var splitObject = object.split('_');
if (subjectFilter[splitObject[0]] === undefined) subjectFilter[splitObject[0]] = [];
subjectFilter[splitObject[0]].push(parseInt(splitObject[1], 10));
});
return {
teams: JSON.stringify(teamFilter),
users: JSON.stringify(userFilter),
types: JSON.stringify(activityFilter),
subjects: JSON.stringify(subjectFilter),
from_date: GlobalActivitiesFiltersGetDates().from,
to_date: GlobalActivitiesFiltersGetDates().to
};
}
function GlobalActivitiesUpdateTopPaneTags(event) {
$('.ga-top .ga-tags').children().remove();
$('<li class="select2-selection__choice">'
+ 'Activity created: '
+ $('.ga-side .date-selector.filter-block')[0].dataset.periodSelect
+ '</li>').appendTo('.ga-top .ga-tags');
$.each($('.ga-side .select2-selection__choice'), function(index, tag) {
var newTag = $(tag.outerHTML).appendTo('.ga-top .ga-tags');
var selectedValues = [];
var parentSelector = $(tag).parents('.select2-container').prev();
var elementToDelete = null;
newTag.find('.select2-selection__choice__remove')
.click(() => {
if (event && event.type === 'select2:select') {
// Adding remove action for native blocks
selectedValues = parentSelector.val();
elementToDelete = $(tag).find('span.select2-block-body')[0].dataset.selectId;
selectedValues = $.grep(selectedValues, v => { return v !== elementToDelete; });
parentSelector.val(selectedValues).change();
} else {
$(tag).find('.select2-selection__choice__remove').click();
}
});
});
}
$(function() {
var selectors = ['team', 'activity', 'user'];
// Ajax request for object search
var subjectAjaxQuery = {
url: '/global_activities/search_subjects',
dataType: 'json',
data: function(params) {
return {
query: params.term // search term
};
},
// preparing results
processResults: function(data) {
var result = [];
$.each(data, (key, items) => {
var updateItems = items.map(item => {
return {
id: key + '_' + item.id,
text: item.name,
label: key
};
});
result.push({ text: key, children: updateItems });
});
return {
results: result
};
}
};
// custom display function
var subjectCustomDisplay = (state) => {
return state.label + ': ' + state.text;
};
// Common selection intialize
$.each(selectors, (index, e) => {
$('.ga-side .' + e + '-selector select').select2Multiple({ singleDisplay: true })
.on('change', function() { GlobalActivitiesUpdateTopPaneTags(); });
$('.ga-side .' + e + '-selector .clear').click(function() {
$('.ga-side .' + e + '-selector select').select2MultipleClearAll();
});
});
// Object selection intialize
$('.ga-side .subject-selector select').select2Multiple({
ajax: subjectAjaxQuery,
customSelection: subjectCustomDisplay,
unlimitedSize: true
})
.on('change select2:select', function(e) { GlobalActivitiesUpdateTopPaneTags(e); });
$('.ga-side .subject-selector .clear').click(function() {
$('.ga-side .subject-selector select').select2MultipleClearAll();
});
$('.ga-tags-container .clear-container span').click(function() {
$.each(selectors, (index, e) => { $('.ga-side .' + e + '-selector select').select2MultipleClearAll(); });
$('.ga-side .subject-selector select').select2MultipleClearAll();
});
$('#calendar-from-date').on('dp.change', function(e) {
var dateContainer = $('.ga-side .date-selector.filter-block');
$('#calendar-to-date').data('DateTimePicker').minDate(e.date);
dateContainer[0].dataset.periodSelect = $('#calendar-from-date').val() + ' - ' + $('#calendar-to-date').val();
GlobalActivitiesUpdateTopPaneTags();
});
$('#calendar-to-date').on('dp.change', function(e) {
var dateContainer = $('.ga-side .date-selector.filter-block');
$('#calendar-from-date').data('DateTimePicker').maxDate(e.date);
dateContainer[0].dataset.periodSelect = $('#calendar-from-date').val() + ' - ' + $('#calendar-to-date').val();
GlobalActivitiesUpdateTopPaneTags();
});
GlobalActivitiesUpdateTopPaneTags();
});
$('.date-selector .hot-button').click(function() {
var selectPeriod = this.dataset.period;
var dateContainer = $('.ga-side .date-selector.filter-block');
var fromDate = $('#calendar-from-date').data('DateTimePicker');
var toDate = $('#calendar-to-date').data('DateTimePicker');
var today = new Date();
var yesterday = new Date(new Date().setDate(today.getDate() - 1));
var weekDay = today.getDay();
var monday = new Date(new Date().setDate(today.getDate() - weekDay + (weekDay === 0 ? -6 : 1)));
var sunday = new Date(new Date().setDate(new Date(monday).getDate() + 6));
var lastWeek = new Date(new Date().setDate(today.getDate() - 6));
var firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
var lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
var lastMonth = new Date(new Date().setDate(today.getDate() - 30));
if (selectPeriod === 'today') {
toDate.date(today);
fromDate.date(today);
} else if (selectPeriod === 'yesterday') {
fromDate.date(yesterday);
toDate.date(yesterday);
} else if (selectPeriod === 'this_week') {
toDate.date(sunday);
fromDate.date(monday);
} else if (selectPeriod === 'last_week') {
toDate.date(today);
fromDate.date(lastWeek);
} else if (selectPeriod === 'this_month') {
toDate.date(lastDay);
fromDate.date(firstDay);
} else if (selectPeriod === 'last_month') {
toDate.date(today);
fromDate.date(lastMonth);
}
dateContainer[0].dataset.periodSelect = this.innerHTML;
GlobalActivitiesUpdateTopPaneTags();
});

View file

@ -13,6 +13,17 @@ $.fn.extend({
ajax: config.ajax,
templateSelection: templateSelection
});
// Add dynamic size
select2.next().css('width', '100%');
// unlimited size
if (config.unlimitedSize) {
this[0].dataset.unlimitedSize = true;
select2.next().find('.select2-selection').css('max-height', 'none');
select2.next().find('.select2-selection .select2-selection__rendered').css('width', '100%');
}
// select all check
this[0].dataset.singleDisplay = config.singleDisplay || false;
if (this[0].dataset.selectAll === 'true') {
@ -40,12 +51,16 @@ $.fn.extend({
})
// Prevent shake bug with multiple select
.on('select2:open select2:close', function() {
if ($(this).val() != null && $(this).val().length > 3) {
$('.select2-selection').scrollTo(0);
/* if (
($(this).val() != null && $(this).val().length > 3) ||
this.dataset.unlimitedSize !== 'true'
) {
$(this).next().find('.select2-search__field')[0].disabled = true;
} else {
$(this).next().find('.select2-search__field')[0].disabled = false;
}
$('.select2-selection').scrollTo(0);
} */
})
// Prevent opening window when deleteing selection
.on('select2:unselect', function() {

View file

@ -0,0 +1,225 @@
@import "constants";
@import "mixins";
.global-activities-container {
background: $color-white;
margin-top: 2em;
padding: 1em;
.ga-main {
.ga-activities-list {
grid-area: activities;
min-height: 600px;
}
}
}
.ga-side {
border-left: 1px solid $gray-lighter;
float: right;
.filter-block {
padding: 10px 0;
width: 100%;
.title {
float: left;
line-height: 20px;
}
.clear {
cursor: pointer;
float: right;
line-height: 12px;
padding-top: 8px;
}
.select-container {
width: 100%;
}
}
.date-selector {
display: inline-block;
position: relative;
width: 100%;
.hot-buttons {
display: inline-block;
margin-bottom: 10px;
width: 100%;
.hot-button {
border: 0;
cursor: pointer;
float: left;
margin: 5px;
text-align: left;
width: calc(50% - 25px);
&:nth-child(even) {
margin-left: 20px;
}
&:nth-child(odd) {
margin-right: 20px;
}
}
}
.from,
.to {
float: left;
position: relative;
width: calc(50% - 15px);
}
.separator {
background: $color-alto;
float: left;
height: 2px;
margin: 16px 10px;
position: relative;
width: 10px;
}
}
}
.ga-top {
border-bottom: 1px solid $gray-lighter;
.ga-title {
border-bottom: 1px solid $color-gainsboro;
left: -20px;
margin-bottom: 10px;
padding-bottom: 10px;
padding-left: 20px;
position: relative;
width: calc(100% + 40px);
}
.ga-top-actions {
float: left;
line-height: 34px;
margin: 5px;
position: relative;
width: 200px;
span {
display: inline-block;
text-align: center;
width: 95px;
}
}
.ga-search-container {
float: left;
margin: 5px;
max-width: 700px;
position: relative;
width: calc(100% - 220px);
}
.ga-tags-container {
display: inline-block;
margin: 5px 0;
width: 100%;
.clear-container {
float: right;
position: relative;
text-align: right;
width: 100px;
span {
cursor: pointer;
display: inline-block;
line-height: 18px;
margin-top: 5px;
i {
float: left;
font-size: 18px;
margin-right: 10px;
position: relative;
}
}
}
.ga-tags {
float: left;
margin: 0;
min-height: 50px;
padding-left: 0;
position: elative;
width: calc(100% - 100px);
li {
background: $color-concrete;
border: 1px solid $color-alto;
border-radius: 4px;
cursor: default;
float: left;
list-style: none;
margin-right: 5px;
margin-top: 5px;
padding: 0 5px;
.select2-selection__choice__remove {
color: $color-dove-gray;
cursor: pointer;
display: inline-block;
float: right;
font-weight: bold;
margin: 0;
margin-left: 5px;
}
}
}
}
}
@media (max-width: 1199px) {
.ga-side {
.filter-block {
&.small-left {
float: left;
width: calc(60% - 10px);
.title {
width: 120px;
}
.select-container {
float: left;
width: calc(100% - 200px);
.select2 {
margin: 4px 0;
}
}
.clear {
line-height: 20px;
padding: 0;
width: 70px;
}
}
&.small-right {
float: right;
width: 40%;
}
}
}
}
// Common code
.datetime-picker-container {
position: relative;
}

View file

@ -5,6 +5,8 @@ class GlobalActivitiesController < ApplicationController
teams = activity_filters[:teams]
teams = current_user.teams if teams.blank?
@teams = teams
@activity_types = Activity.activity_types_list
@users = UserTeam.my_employees(current_user)
@grouped_activities, more_activities =
ActivitiesService.load_activities(teams, activity_filters)
respond_to do |format|

View file

@ -19,6 +19,15 @@ class Activity < ApplicationRecord
validates :subject_type, inclusion: { in: Extends::ACTIVITY_SUBJECT_TYPES,
allow_blank: true }
def self.activity_types_list
type_ofs.map do |key, value|
{
id: value,
name: key.tr('_', ' ').capitalize
}
end.sort_by { |a| a[:name] }
end
def old_activity?
subject.nil?
end

View file

@ -7,6 +7,7 @@ class Team < ApplicationRecord
include ActionView::Helpers::NumberHelper
after_create :generate_template_project
scope :teams_select, -> { select(:id, :name).order(name: :asc) }
auto_strip_attributes :name, :description, nullify: false
validates :name,

View file

@ -1,6 +1,8 @@
class UserTeam < ApplicationRecord
enum role: { guest: 0, normal_user: 1, admin: 2 }
scope :my_teams, -> { where(role: 2) }
validates :role, presence: true
validates :user, presence: true
validates :team, presence: true
@ -19,6 +21,15 @@ class UserTeam < ApplicationRecord
I18n.t("user_teams.enums.role.#{role}")
end
def self.my_employees(user)
users = where(team_id: user.user_teams.my_teams.pluck(:team_id))
.joins(:user).select(:full_name, 'users.id as id').as_json.uniq
if users.empty?
users = [user.as_json.select { |k| %w(id full_name).include? k }]
end
users
end
def create_samples_table_state
SamplesTable.create_samples_table_state(self)
end

View file

@ -0,0 +1,23 @@
<div class="datetime-picker-container" id="<%= id %>">
<% if label %>
<label class="control-label required" for="calendar-<%= id %>"><%= label %></label>
<% end %>
<input type="datetime" class="form-control" name="calendar[<%= id %>]" id="calendar-<%= id %>" readonly="" data-ts="" />
<%
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')
%>
<script type="text/javascript">
$(function () {
var dt = $('#calendar-<%= id %>');
dt.datetimepicker({ ignoreReadonly: true, locale: '<%= I18n.locale %>', format: '<%= js_format %>' });
$('#calendar-<%= id %>').data('DateTimePicker').date(new Date);
});
</script>
</div>

View file

@ -0,0 +1,67 @@
<div class="team-selector filter-block small-left">
<h4 class="title"><strong><%= t('global_activities.index.teams') %></strong></h4>
<h6 class="clear"><%= t('global_activities.index.clear') %></h6>
<div class="select-container">
<%= select_tag "team", options_from_collection_for_select(@teams, :id, :name, current_user.current_team_id),{
'data-select-all-button': t('global_activities.index.all_teams'),
'data-select-multiple-name': t('global_activities.index.l_teams'),
'data-select-multiple-all-selected': t('global_activities.index.all_teams')
} %>
</div>
</div>
<div class="date-selector filter-block small-right" data-period-select="Today">
<h4 class="title"><strong><%= t('global_activities.index.activity') %></strong></h4>
<div class="date-selector">
<div class="hot-buttons">
<div class="hot-button btn btn-default" data-period="today"><%= t('global_activities.index.today') %></div>
<div class="hot-button btn btn-default" data-period="yesterday"><%= t('global_activities.index.yesterday') %></div>
<div class="hot-button btn btn-default" data-period="this_week"><%= t('global_activities.index.this_week') %></div>
<div class="hot-button btn btn-default" data-period="last_week"><%= t('global_activities.index.last_week') %></div>
<div class="hot-button btn btn-default" data-period="this_month"><%= t('global_activities.index.this_month') %></div>
<div class="hot-button btn btn-default" data-period="last_month"><%= t('global_activities.index.last_month') %></div>
</div>
<div class="from">
<%= render partial: "date_picker", locals: {id: 'from-date', label: false} %>
</div>
<div class="separator"></div>
<div class="to">
<%= render partial: "date_picker", locals: {id: 'to-date', label: false} %>
</div>
</div>
</div>
<div class="activity-selector filter-block small-left">
<h4 class="title"><strong><%= t('global_activities.index.activity_type') %></strong></h4>
<h6 class="clear"><%= t('global_activities.index.clear') %></h6>
<div class="select-container">
<%= select_tag "activity", options_for_select(@activity_types.map{|i| [i[:name], i[:id]]}),{
'data-select-all-button': t('global_activities.index.all_activities'),
'data-select-all': 'true',
'data-select-multiple-name': t('global_activities.index.l_activities'),
'data-select-multiple-all-selected': t('global_activities.index.all_activities')
} %>
</div>
</div>
<div class="user-selector filter-block small-left">
<h4 class="title"><strong><%= t('global_activities.index.user') %></strong></h4>
<h6 class="clear"><%= t('global_activities.index.clear') %></h6>
<div class="select-container">
<%= select_tag "user", options_for_select(@users.map{|i| [i['full_name'], i['id']]}),{
'data-select-all-button': t('global_activities.index.all_users'),
'data-select-all': 'true',
'data-select-multiple-name': t('global_activities.index.l_users'),
'data-select-multiple-all-selected': t('global_activities.index.all_users')
} %>
</div>
</div>
<div class="subject-selector filter-block small-left">
<h4 class="title"><strong><%= t('global_activities.index.object') %></strong></h4>
<h6 class="clear"><%= t('global_activities.index.clear') %></h6>
<div class="select-container">
<select name="subject" data-select-all="false"></select>
</div>
</div>

View file

@ -1,4 +0,0 @@
<h5>Team</h5>
<select name="team">
<%=options_for_select @teams %>
</select>

View file

@ -0,0 +1,22 @@
<h3 class="ga-title">Global activities</h3>
<div class="ga-top-actions">
<span><i class="fas fa-caret-square-down"></i> Expand All</span>
<span><i class="fas fa-caret-square-up"></i> Colapse All</span>
</div>
<div class="ga-search-container">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search..." aria-describedby="basic-addon1">
<span class="input-group-addon" id="basic-addon1">
<i class="fas fa-search"></i>
</span>
</div>
</div>
<div class="ga-tags-container">
<ul class="ga-tags"></ul>
<div class="clear-container">
<span>
<i class="fas fa-times"></i>
<%= t('global_activities.index.clear_filters') %>
</span>
</div>
</div>

View file

@ -1,31 +1,22 @@
<div class="global-activities__container">
<br>
<div class="global-activities__top">
<h2 class="global-activiteis__title">Global activities</h2>
</div>
<div class="global-activities__main">
<div class="global-activities__top-actions">
<span><i class="fas fa-caret-square-down"></i> Expand All</span>
&nbsp;&nbsp;
<span><i class="fas fa-caret-square-up"></i> Colapse All</span>
</div>
<div class="global-activities__search-container">
<div class="input-group">
<input type="text" class="form-control" placeholder="Search..." aria-describedby="basic-addon1">
<span class="input-group-addon" id="basic-addon1">
<i class="fas fa-search"></i>
</span>
</div>
</div>
<div class="global-activities_activities-list">
<h2>list of activities</h2>
<div class="global-activities-container container-flex">
<div class="row">
<div class="ga-top col-md-12">
<%= render partial: "top_pane" %>
</div>
<div class="ga-side col-lg-3 col-md-12 col-xl-2">
<%= render partial: "side_filters" %>
</div>
<div class="ga-main col-lg-9 col-md-12 col-xl-10">
<div class="ga-activities-list">
<h2>list of activities</h2>
</div>
</div>
</div>
<div class="global-activities__side">
<%= render "team_selection" %>
</div>
</div>
<%= javascript_include_tag("global_activities/side_pane") %>

View file

@ -25,6 +25,8 @@ module Scinote
# Load all model concerns, including subfolders
config.autoload_paths += Dir["#{Rails.root}/app/models/concerns/**/*.rb"]
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
config.encoding = 'utf-8'
config.active_job.queue_adapter = :delayed_job

View file

@ -65,6 +65,7 @@ Rails.application.config.assets.precompile +=
%w(protocols/import_export/export.js)
Rails.application.config.assets.precompile += %w(datatables.js)
Rails.application.config.assets.precompile += %w(search/index.js)
Rails.application.config.assets.precompile += %w(global_activities/side_pane.js)
Rails.application.config.assets.precompile += %w(navigation.js)
Rails.application.config.assets.precompile += %w(secondary_navigation.js)
Rails.application.config.assets.precompile += %w(datatables.css)

View file

@ -0,0 +1,22 @@
en:
global_activities:
index:
teams: Team
activity: Actvity created
today: Today
yesterday: Yesterday
this_week: This week
last_week: Last week
this_month: This month
last_month: Last month
clear: clear
activity_type: Activity type
user: User
object: Object
all_teams: All teams
l_teams: teams
all_users: All users
l_users: users
all_activities: All activities
l_activities: activities
clear_filters: Clear filters

View file

@ -648,10 +648,8 @@ Rails.application.routes.draw do
end
end
resources :global_activities, only: [:index]
namespace :global_activities do
get 'search_subjects', to: 'global_activities#search_subjects',
as: 'search_subjects'
resources :global_activities, only: [:index] do
get :search_subjects, on: :collection
end
constraints WopiSubdomain do