diff --git a/app/assets/javascripts/projects/index.js b/app/assets/javascripts/projects/index.js index fd05178d9..4ebd61ef2 100644 --- a/app/assets/javascripts/projects/index.js +++ b/app/assets/javascripts/projects/index.js @@ -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($( + `` + )); + }); + } 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 ""; - } - }, { - 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'); diff --git a/app/assets/stylesheets/projects.scss b/app/assets/stylesheets/projects.scss index adb91f96a..6bb8457e7 100644 --- a/app/assets/stylesheets/projects.scss +++ b/app/assets/stylesheets/projects.scss @@ -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 { } } } - diff --git a/app/services/projects_overview_service.rb b/app/services/projects_overview_service.rb index cfd7d1027..477a922da 100644 --- a/app/services/projects_overview_service.rb +++ b/app/services/projects_overview_service.rb @@ -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 diff --git a/app/views/projects/index/_toolbar.html.erb b/app/views/projects/index/_toolbar.html.erb index ca3ab6449..45639b2e1 100644 --- a/app/views/projects/index/_toolbar.html.erb +++ b/app/views/projects/index/_toolbar.html.erb @@ -41,12 +41,8 @@
- - - - -