mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-02-01 20:48:23 +08:00
Merge pull request #2984 from okriuchykhin/ok_SCI_5204
Project filters: add text field to the filters [SCI-5204]
This commit is contained in:
commit
fcb82fd0d2
5 changed files with 104 additions and 268 deletions
|
@ -34,11 +34,10 @@
|
|||
|
||||
var projectsViewMode = 'cards';
|
||||
var projectsViewFilter = $('.projects-view-filter.active').data('filter');
|
||||
var projectsViewSearch;
|
||||
var projectsChanged = false;
|
||||
var projectsViewSort = $('#sortMenuDropdown a.disabled').data('sort');
|
||||
|
||||
var TABLE;
|
||||
|
||||
// Arrays with selected project and folder IDs shared between both views
|
||||
var selectedProjects = [];
|
||||
var selectedProjectFolders = [];
|
||||
|
@ -483,11 +482,8 @@
|
|||
}
|
||||
|
||||
function refreshCurrentView() {
|
||||
if (projectsViewMode === 'cards') {
|
||||
loadCardsView();
|
||||
} else {
|
||||
TABLE.draw();
|
||||
}
|
||||
loadCardsView();
|
||||
|
||||
// Also refresh sidebar tree navigation
|
||||
$.ajax({
|
||||
url: $('#projects-cards-view').data('projects-sidebar-url'),
|
||||
|
@ -511,7 +507,8 @@
|
|||
dataType: 'json',
|
||||
data: {
|
||||
filter: projectsViewFilter,
|
||||
sort: projectsViewSort
|
||||
sort: projectsViewSort,
|
||||
search: projectsViewSearch
|
||||
},
|
||||
success: function(data) {
|
||||
viewContainer.find('.card').remove();
|
||||
|
@ -535,12 +532,7 @@
|
|||
$(this).addClass('active');
|
||||
selectedProjects = [];
|
||||
projectsViewFilter = $(this).data('filter');
|
||||
projectsViewFilterChanged = true;
|
||||
if ($('#projects-cards-view').hasClass('active')) {
|
||||
loadCardsView();
|
||||
} else if (!$.isEmptyObject(TABLE)) {
|
||||
TABLE.draw();
|
||||
}
|
||||
loadCardsView();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -582,6 +574,7 @@
|
|||
let $foldersCB = $('#folder_search', $projectsFilter);
|
||||
let $createdOnFilter = $('#calendarStartDate', $projectsFilter);
|
||||
let $dueFilter = $('#calendarDueDate', $projectsFilter);
|
||||
let $textFilter = $('#textSearchFilterInput', $projectsFilter);
|
||||
|
||||
dropdownSelector.init($membersFilter, {
|
||||
optionClass: 'checkbox-icon users-dropdown-list',
|
||||
|
@ -595,6 +588,68 @@
|
|||
tagClass: 'users-dropdown-list'
|
||||
});
|
||||
|
||||
$textFilter.click((e) => {
|
||||
e.stopPropagation();
|
||||
$('#textSearchFilterHistory').toggle();
|
||||
}).on('input', () => {
|
||||
$('#textSearchFilterHistory').hide();
|
||||
});
|
||||
|
||||
$projectsFilter.on('click', '.projects-search-keyword', function(e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
$textFilter.val($(this).data('keyword'));
|
||||
$('#textSearchFilterHistory').hide();
|
||||
});
|
||||
|
||||
$('.project-filters-dropdown').on('show.bs.dropdown', function() {
|
||||
let teamId = $projectsFilter.data('team-id');
|
||||
$('#textSearchFilterHistory').find('li').remove();
|
||||
|
||||
try {
|
||||
let storagePath = `project_filters_per_team/${teamId}/recent_search_keywords`;
|
||||
let recentSearchKeywords = JSON.parse(localStorage.getItem(storagePath));
|
||||
$.each(recentSearchKeywords, function(i, keyword) {
|
||||
$('#textSearchFilterHistory').append($(
|
||||
`<li class="dropdown-item">
|
||||
<a class="projects-search-keyword" href="#" data-keyword="${keyword}">
|
||||
<i class="fas fa-history"></i>
|
||||
${keyword}
|
||||
</a>
|
||||
</li>`
|
||||
));
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
$('#applyProjectFiltersButton').click((e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
let teamId = $('.projects-filters').data('team-id');
|
||||
projectsViewSearch = $('#textSearchFilterInput').closest('.select-block').find('input[type=text]').val();
|
||||
try {
|
||||
let storagePath = `project_filters_per_team/${teamId}/recent_search_keywords`;
|
||||
let recentSearchKeywords = JSON.parse(localStorage.getItem(storagePath));
|
||||
if (!Array.isArray(recentSearchKeywords)) recentSearchKeywords = [];
|
||||
if (recentSearchKeywords.indexOf(projectsViewSearch) !== -1) {
|
||||
recentSearchKeywords.splice(recentSearchKeywords.indexOf(projectsViewSearch), 1);
|
||||
}
|
||||
if (recentSearchKeywords.length > 4) {
|
||||
recentSearchKeywords = recentSearchKeywords.slice(0, 4);
|
||||
}
|
||||
recentSearchKeywords.unshift(projectsViewSearch);
|
||||
localStorage.setItem(storagePath, JSON.stringify(recentSearchKeywords));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
$('.projects-filters').dropdown('toggle');
|
||||
refreshCurrentView();
|
||||
});
|
||||
|
||||
// Clear filters
|
||||
$('.clear-button', $projectsFilter).click((e) => {
|
||||
e.stopPropagation();
|
||||
|
@ -604,6 +659,7 @@
|
|||
$createdOnFilter.data('DateTimePicker').clear();
|
||||
$dueFilter.data('DateTimePicker').clear();
|
||||
$foldersCB.prop('checked', false);
|
||||
$textFilter.val('');
|
||||
});
|
||||
|
||||
// Prevent filter window close
|
||||
|
@ -616,206 +672,6 @@
|
|||
});
|
||||
}
|
||||
|
||||
// Updates "Select all" control in a data table
|
||||
function updateDataTableSelectAllCtrl() {
|
||||
var $table = TABLE.table().node();
|
||||
var $header = TABLE.table().header();
|
||||
var $chkboxAll = $('.project-row-selector', $table);
|
||||
var $chkboxChecked = $('.project-row-selector:checked', $table);
|
||||
var chkboxSelectAll = $('input[name="select_all"]', $header).get(0);
|
||||
|
||||
// If none of the checkboxes are checked
|
||||
if ($chkboxChecked.length === 0) {
|
||||
chkboxSelectAll.checked = false;
|
||||
if ('indeterminate' in chkboxSelectAll) {
|
||||
chkboxSelectAll.indeterminate = false;
|
||||
}
|
||||
|
||||
// If all of the checkboxes are checked
|
||||
} else if ($chkboxChecked.length === $chkboxAll.length) {
|
||||
chkboxSelectAll.checked = true;
|
||||
if ('indeterminate' in chkboxSelectAll) {
|
||||
chkboxSelectAll.indeterminate = false;
|
||||
}
|
||||
|
||||
// If some of the checkboxes are checked
|
||||
} else {
|
||||
chkboxSelectAll.checked = true;
|
||||
if ('indeterminate' in chkboxSelectAll) {
|
||||
chkboxSelectAll.indeterminate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initRowSelection() {
|
||||
// Handle clicks on checkbox
|
||||
$('.dt-body-center .project-row-selector').change(function(e) {
|
||||
// Get row ID
|
||||
var $row = $(this).closest('tr');
|
||||
var data = TABLE.row($row).data();
|
||||
var rowId = data.DT_RowId;
|
||||
|
||||
// Determine whether row ID is in the list of selected project IDs
|
||||
var index = $.inArray(rowId, selectedProjects);
|
||||
|
||||
// If checkbox is checked and row ID is not in list of selected project IDs
|
||||
if (this.checked && index === -1) {
|
||||
selectedProjects.push(rowId);
|
||||
exportProjectsBtn.removeAttr('disabled');
|
||||
// Otherwise, if checkbox is not checked and ID is in list of selected IDs
|
||||
} else if (!this.checked && index !== -1) {
|
||||
selectedProjects.splice(index, 1);
|
||||
|
||||
if (selectedProjects.length === 0) {
|
||||
exportProjectsBtn.attr('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
updateDataTableSelectAllCtrl();
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
// Handle click on "Select all" control
|
||||
$('.dataTables_scrollHead input[name="select_all"]').change(function(e) {
|
||||
if (this.checked) {
|
||||
$('.project-row-selector:not(:checked)').trigger('click');
|
||||
} else {
|
||||
$('.project-row-selector:checked').trigger('click');
|
||||
}
|
||||
// Prevent click event from propagating to parent
|
||||
e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
function updateSelectedRows() {
|
||||
TABLE.rows().every(function() {
|
||||
var rowSelector = $(this.node()).find('input[type="checkbox"]');
|
||||
var rowId = this.data().DT_RowId;
|
||||
|
||||
if ($.inArray(rowId, selectedProjects) !== -1) {
|
||||
rowSelector.prop('checked', true);
|
||||
} else {
|
||||
rowSelector.prop('checked', false);
|
||||
}
|
||||
});
|
||||
|
||||
updateDataTableSelectAllCtrl();
|
||||
}
|
||||
|
||||
function dataTableInit() {
|
||||
var TABLE_ID = '#projects-overview-table';
|
||||
TABLE = $(TABLE_ID).DataTable({
|
||||
dom: "R<'row'<'col-sm-9-custom toolbar'l><'col-sm-3-custom'f>><'row'<'col-sm-12't>><'row'<'col-sm-7'i><'col-sm-5'p>>",
|
||||
stateSave: true,
|
||||
stateDuration: 0,
|
||||
processing: true,
|
||||
serverSide: true,
|
||||
scrollY: '64vh',
|
||||
scrollCollapse: true,
|
||||
destroy: true,
|
||||
ajax: {
|
||||
url: $(TABLE_ID).data('source'),
|
||||
global: false,
|
||||
type: 'POST',
|
||||
data: function(params) {
|
||||
params.filter = projectsViewFilter;
|
||||
// return { ...params, ...{ filter: projectsViewFilter } };
|
||||
}
|
||||
},
|
||||
colReorder: {
|
||||
fixedColumnsLeft: 9
|
||||
},
|
||||
columnDefs: [{
|
||||
// Checkbox column needs special handling
|
||||
targets: 0,
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
className: 'dt-body-center',
|
||||
sWidth: '1%',
|
||||
render: function() {
|
||||
return "<input class='project-row-selector' type='checkbox'>";
|
||||
}
|
||||
}, {
|
||||
targets: 8,
|
||||
searchable: false,
|
||||
orderable: false,
|
||||
className: 'dt-body-center',
|
||||
sWidth: '1%'
|
||||
}],
|
||||
oLanguage: {
|
||||
sSearch: I18n.t('general.filter')
|
||||
},
|
||||
rowCallback: function(row, data) {
|
||||
// Get row ID
|
||||
var rowId = data.DT_RowId;
|
||||
var dropdown = $(row).find('.dropdown');
|
||||
var dropdownCell = dropdown.closest('td');
|
||||
// If row ID is in the list of selected row IDs
|
||||
if ($.inArray(rowId, selectedProjects) !== -1) {
|
||||
$(row).find('input[type="checkbox"]').prop('checked', true);
|
||||
}
|
||||
|
||||
initEditProjectButton($(row));
|
||||
initArchiveRestoreButton($(row));
|
||||
|
||||
dropdown.off().on('show.bs.dropdown', function() {
|
||||
$('body').append(dropdown.css({
|
||||
left: dropdown.offset().left,
|
||||
position: 'absolute',
|
||||
top: dropdown.offset().top
|
||||
}).detach());
|
||||
});
|
||||
dropdown.off().on('hidden.bs.dropdown', function() {
|
||||
dropdownCell.append(dropdown.removeAttr('style').detach());
|
||||
});
|
||||
},
|
||||
order: [[2, 'asc']],
|
||||
columns: [
|
||||
{ data: 'checkbox' },
|
||||
{ data: 'status' },
|
||||
{ data: 'name' },
|
||||
{ data: 'start' },
|
||||
{ data: 'visibility' },
|
||||
{ data: 'users' },
|
||||
{ data: 'experiments' },
|
||||
{ data: 'tasks' },
|
||||
{ data: 'actions' }
|
||||
],
|
||||
fnDrawCallback: function() {
|
||||
animateSpinner(this, false);
|
||||
updateDataTableSelectAllCtrl();
|
||||
initRowSelection();
|
||||
initFormSubmitLinks($(this));
|
||||
},
|
||||
stateLoadCallback: function(settings, callback) {
|
||||
$.ajax({
|
||||
url: $(TABLE_ID).data('state-load-source'),
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
success: function(json) {
|
||||
callback(json.state);
|
||||
}
|
||||
});
|
||||
},
|
||||
stateSaveCallback: function() {
|
||||
// Don't do anything, state will be updated at backend, based on params
|
||||
}
|
||||
});
|
||||
|
||||
// Handle click on table cells with checkboxes
|
||||
$(TABLE_ID).off().on('click', 'tbody td', function(e) {
|
||||
if ($(e.target).is(
|
||||
'.project-row-selector, .active-project-link, button, span'
|
||||
)) {
|
||||
// Skip if clicking on selector checkbox, links and buttons
|
||||
return;
|
||||
}
|
||||
$(this).parent().find('.project-row-selector').trigger('click');
|
||||
});
|
||||
|
||||
return TABLE;
|
||||
}
|
||||
|
||||
$('.projects-view-mode-switch a').off().on('shown.bs.tab', function(event) {
|
||||
if ($(event.target).data('mode') === 'table') {
|
||||
$('#cards-wrapper').addClass('list');
|
||||
|
|
|
@ -955,6 +955,16 @@ li.module-hover {
|
|||
}
|
||||
}
|
||||
|
||||
.recent-searches {
|
||||
label {
|
||||
@include font-small;
|
||||
font-weight: bold;
|
||||
margin-bottom: .3em;
|
||||
padding: 0 1.5em;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
align-items: center;
|
||||
border-top: $border-default;
|
||||
|
@ -1031,4 +1041,3 @@ li.module-hover {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -101,12 +101,12 @@ class ProjectsOverviewService
|
|||
def filter_project_records(records)
|
||||
records = records.where(archived: true) if @params[:filter] == 'archived'
|
||||
records = records.where(archived: false) if @params[:filter] == 'active'
|
||||
records = Project.search_by_name(@user, @team, @params[:search]) if @params[:search].present?
|
||||
records = records.where_attributes_like('projects.name', @params[:search]) if @params[:search].present?
|
||||
records
|
||||
end
|
||||
|
||||
def filter_project_folder_records(records)
|
||||
records = ProjectFolder.search_by_name(@user, @team, @params[:search]) if @params[:search].present?
|
||||
records = records.where_attributes_like('project_folders.name', @params[:search]) if @params[:search].present?
|
||||
records
|
||||
end
|
||||
|
||||
|
@ -153,49 +153,4 @@ class ProjectsOverviewService
|
|||
records
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_dt_records
|
||||
projects = @team.projects.joins(
|
||||
'LEFT OUTER JOIN user_projects ON user_projects.project_id = projects.id'
|
||||
)
|
||||
exp_join =
|
||||
'LEFT OUTER JOIN experiments ON experiments.project_id = projects.id'\
|
||||
' AND ((projects.archived = true)'\
|
||||
' OR (projects.archived = false AND experiments.archived = false))'
|
||||
task_join =
|
||||
'LEFT OUTER JOIN my_modules ON my_modules.experiment_id = experiments.id'\
|
||||
' AND ((projects.archived = true)'\
|
||||
' OR (projects.archived = false AND my_modules.archived = false))'
|
||||
projects = projects.joins(exp_join).joins(task_join)
|
||||
|
||||
# Only admins see all projects of the team
|
||||
unless @user.is_admin_of_team?(@team)
|
||||
projects = projects.where(
|
||||
'visibility = 1 OR user_projects.user_id = :user_id', user_id: @user.id
|
||||
)
|
||||
end
|
||||
projects
|
||||
.select('projects.*')
|
||||
.select('(SELECT COUNT(DISTINCT user_projects.id) FROM user_projects '\
|
||||
'WHERE user_projects.project_id = projects.id) AS user_count')
|
||||
.select('COUNT(DISTINCT experiments.id) AS experiment_count')
|
||||
.select('COUNT(DISTINCT my_modules.id) AS task_count')
|
||||
.group('projects.id')
|
||||
end
|
||||
|
||||
def search(records, value)
|
||||
records.where_attributes_like('projects.name', value)
|
||||
end
|
||||
|
||||
def sortable_columns
|
||||
{
|
||||
'1' => 'projects.archived',
|
||||
'2' => 'projects.name',
|
||||
'3' => 'projects.created_at',
|
||||
'4' => 'projects.visibility',
|
||||
'5' => 'user_count',
|
||||
'6' => 'experiment_count',
|
||||
'7' => 'task_count'
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -41,12 +41,8 @@
|
|||
</div>
|
||||
|
||||
<div class="pull-right">
|
||||
<!-- project search -->
|
||||
<a class="projects-search-btn">
|
||||
<i class="fas fa-search"></i>
|
||||
</a>
|
||||
<!-- project filter -->
|
||||
<div class="filter-container dropdown">
|
||||
<div class="filter-container dropdown project-filters-dropdown">
|
||||
<div class="btn btn-light icon-btn filter-button" data-toggle="dropdown"><i class="fas fa-filter"></i></div>
|
||||
<div class="dropdown-menu dropdown-menu-right projects-filters" role="menu" data-team-id="<%= current_team.id %>">
|
||||
<div class="header">
|
||||
|
@ -55,6 +51,22 @@
|
|||
<i class="fas fa-times-circle"></i><%= t("projects.index.filters_modal.clear_btn") %></div>
|
||||
</div>
|
||||
|
||||
<div class="select-block dropdown-selector-container">
|
||||
<label><%= t('projects.index.filters_modal.text.label') %></label>
|
||||
<div class="input-field dropdown">
|
||||
<input id="textSearchFilterInput"
|
||||
class="dropdown-toggle search-field"
|
||||
type="text"
|
||||
name="search"
|
||||
autocomplete="off"
|
||||
data-toggle="dropdown"
|
||||
data-placeholder="<%= t('projects.index.filters_modal.text.placeholder') %>">
|
||||
</div>
|
||||
<div id="textSearchFilterHistory" class="dropdown-menu recent-searches" aria-labelledby="textSearchFilterInput2">
|
||||
<label><%= t("projects.index.filters_modal.recent_searches_label") %></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="select-block">
|
||||
<div class="created-on-label">
|
||||
<label><%= t("projects.index.filters_modal.created_on.label") %></label>
|
||||
|
@ -109,7 +121,7 @@
|
|||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<div class="btn btn-primary apply-filters"><%= t("projects.index.filters_modal.show_btn.one") %></div>
|
||||
<div id="applyProjectFiltersButton" class="btn btn-primary"><%= t("projects.index.filters_modal.show_btn.one") %></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -363,6 +363,9 @@ en:
|
|||
contact_admins: "To invite additional users to team %{team}, contact its administrator/s."
|
||||
filters_modal:
|
||||
title: "Filters"
|
||||
text:
|
||||
label: "Contains text"
|
||||
placeholder: "Enter search terms..."
|
||||
created_on:
|
||||
label: "Created on"
|
||||
from_placeholder: "From"
|
||||
|
@ -373,6 +376,7 @@ en:
|
|||
folders:
|
||||
label: "Look inside folders"
|
||||
tooltip: "Hint should appear here"
|
||||
recent_searches_label: "Recent searches"
|
||||
show_btn:
|
||||
many: "Show %{num} results"
|
||||
none: "No results"
|
||||
|
|
Loading…
Reference in a new issue