mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2025-03-04 19:53:19 +08:00
Add projects search results component [SCI-10469]
This commit is contained in:
parent
30318da3f1
commit
478da52272
7 changed files with 226 additions and 30 deletions
|
@ -11,37 +11,56 @@ class SearchController < ApplicationController
|
|||
format.json do
|
||||
redirect_to new_search_path unless @search_query
|
||||
|
||||
@search_id = params[:search_id] ? params[:search_id] : generate_search_id
|
||||
case params[:group]
|
||||
when 'projects'
|
||||
@project_search_count = fetch_cached_count Project
|
||||
search_projects
|
||||
if params[:preview] == 'true'
|
||||
results = @project_results.limit(4)
|
||||
else
|
||||
results = @project_results.page(params[:page]).per(Constants::SEARCH_LIMIT)
|
||||
end
|
||||
|
||||
count_search_results
|
||||
|
||||
search_projects if @search_category == :projects
|
||||
search_project_folders if @search_category == :project_folders
|
||||
search_experiments if @search_category == :experiments
|
||||
search_modules if @search_category == :modules
|
||||
search_results if @search_category == :results
|
||||
search_tags if @search_category == :tags
|
||||
search_reports if @search_category == :reports
|
||||
search_protocols if @search_category == :protocols
|
||||
search_steps if @search_category == :steps
|
||||
search_checklists if @search_category == :checklists
|
||||
if @search_category == :repositories && params[:repository]
|
||||
search_repository
|
||||
render json: results,
|
||||
each_serializer: GlobalSearch::ProjectSerializer,
|
||||
meta: {
|
||||
total: @search_count,
|
||||
next_page: (results.next_page if results.respond_to?(:next_page)),
|
||||
}
|
||||
return
|
||||
end
|
||||
search_assets if @search_category == :assets
|
||||
search_tables if @search_category == :tables
|
||||
search_comments if @search_category == :comments
|
||||
|
||||
@search_pages = (@search_count.to_f / Constants::SEARCH_LIMIT.to_f).ceil
|
||||
@start_page = @search_page - 2
|
||||
@start_page = 1 if @start_page < 1
|
||||
@end_page = @start_page + 4
|
||||
#@search_id = params[:search_id] ? params[:search_id] : generate_search_id
|
||||
#
|
||||
#count_search_results
|
||||
#
|
||||
#search_projects if @search_category == :projects
|
||||
#search_project_folders if @search_category == :project_folders
|
||||
#search_experiments if @search_category == :experiments
|
||||
#search_modules if @search_category == :modules
|
||||
#search_results if @search_category == :results
|
||||
#search_tags if @search_category == :tags
|
||||
#search_reports if @search_category == :reports
|
||||
#search_protocols if @search_category == :protocols
|
||||
#search_steps if @search_category == :steps
|
||||
#search_checklists if @search_category == :checklists
|
||||
#if @search_category == :repositories && params[:repository]
|
||||
# search_repository
|
||||
#end
|
||||
#search_assets if @search_category == :assets
|
||||
#search_tables if @search_category == :tables
|
||||
#search_comments if @search_category == :comments
|
||||
|
||||
if @end_page > @search_pages
|
||||
@end_page = @search_pages
|
||||
@start_page = @end_page - 4
|
||||
@start_page = 1 if @start_page < 1
|
||||
end
|
||||
#@search_pages = (@search_count.to_f / Constants::SEARCH_LIMIT.to_f).ceil
|
||||
#@start_page = @search_page - 2
|
||||
#@start_page = 1 if @start_page < 1
|
||||
#@end_page = @start_page + 4
|
||||
|
||||
#if @end_page > @search_pages
|
||||
# @end_page = @search_pages
|
||||
# @start_page = @end_page - 4
|
||||
# @start_page = 1 if @start_page < 1
|
||||
#end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-white rounded p-4 flex gap-2.5 items-center mb-4">
|
||||
<div class="bg-white rounded p-4 flex gap-2.5 items-center mb-4 sticky top-0">
|
||||
<div class="left-icon sci-input-container-v2 w-72 input-sm" :title="i18n.t('nav.search')">
|
||||
<input ref="searchField" type="text" class="!pr-9" v-model="localQuery" :placeholder="i18n.t('nav.search')" @keyup.enter="saveQuery"/>
|
||||
<input ref="searchField" type="text" class="!pr-9" :value="localQuery" @change="changeQuery" :placeholder="i18n.t('nav.search')"/>
|
||||
<i class="sn-icon sn-icon-search"></i>
|
||||
<i v-if="localQuery.length > 0" class="sn-icon cursor-pointer sn-icon-close absolute right-0 -top-0.5" @click="localQuery = ''"></i>
|
||||
</div>
|
||||
|
@ -43,6 +43,8 @@
|
|||
v-if="activeGroup === group || !activeGroup"
|
||||
:selected="activeGroup === group"
|
||||
:query="localQuery"
|
||||
:searchUrl="searchUrl"
|
||||
@selectGroup="setActiveGroup"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -67,6 +69,10 @@ export default {
|
|||
query: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
searchUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -108,6 +114,9 @@ export default {
|
|||
} else {
|
||||
this.activeGroup = group;
|
||||
}
|
||||
},
|
||||
changeQuery(event) {
|
||||
this.localQuery = event.target.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,12 +3,57 @@
|
|||
<h2 class="flex items-center gap-2 mt-0 mb-4">
|
||||
<i class="sn-icon sn-icon-projects"></i>
|
||||
{{ i18n.t('search.index.projects') }}
|
||||
[{{ total }}]
|
||||
</h2>
|
||||
<div>
|
||||
<div class="grid grid-cols-[auto_80px_auto_auto_auto] items-center">
|
||||
<template v-for="row in preparedResults" :key="row.id">
|
||||
<a :href="row.attributes.url" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
|
||||
<StringWithEllipsis class="w-full" :text="row.attributes.name"></StringWithEllipsis>
|
||||
</a>
|
||||
<div class="h-full py-2 px-4 flex items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey">
|
||||
<b class="shrink-0">{{ i18n.t('search.index.id') }}:</b>
|
||||
<span class="shrink-0">{{ row.attributes.code }}</span>
|
||||
</div>
|
||||
<div class="h-full py-2 px-4 flex items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey">
|
||||
<b class="shrink-0">{{ i18n.t('search.index.created_at') }}:</b>
|
||||
<span class="shrink-0">{{ row.attributes.created_at }}</span>
|
||||
</div>
|
||||
<div class="h-full py-2 px-4 grid grid-cols-[auto_1fr] items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey">
|
||||
<b class="shrink-0">{{ i18n.t('search.index.team') }}:</b>
|
||||
<a :href="row.attributes.team.url" class="shrink-0 overflow-hidden">
|
||||
<StringWithEllipsis class="w-full" :text="row.attributes.team.name"></StringWithEllipsis>
|
||||
</a>
|
||||
</div>
|
||||
<div class="h-full py-2 px-4 border-0 border-b border-solid border-sn-light-grey">
|
||||
<template v-if="row.attributes.folder">
|
||||
<div class="grid grid-cols-[auto_1fr] items-center gap-1 text-xs w-full">
|
||||
<b class="shrink-0">{{ i18n.t('search.index.folder') }}:</b>
|
||||
<a :href="row.attributes.folder.url" class="shrink-0 overflow-hidden">
|
||||
<StringWithEllipsis class="w-full" :text="row.attributes.folder.name"></StringWithEllipsis>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-if="!selected && total > 4" class="mt-4">
|
||||
<button class="btn btn-light" @click="$emit('selectGroup', 'ProjectsComponent')">View all</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import searchMixin from './search_mixin';
|
||||
|
||||
export default {
|
||||
name: 'ProjectsComponent'
|
||||
name: 'ProjectsComponent',
|
||||
mixins: [searchMixin],
|
||||
data() {
|
||||
return {
|
||||
group: 'projects'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
87
app/javascript/vue/global_search/groups/search_mixin.js
Normal file
87
app/javascript/vue/global_search/groups/search_mixin.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
import axios from '../../../packs/custom_axios.js';
|
||||
import StringWithEllipsis from '../../shared/string_with_ellipsis.vue';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
searchUrl: String,
|
||||
query: String,
|
||||
selected: Boolean
|
||||
},
|
||||
components: {
|
||||
StringWithEllipsis
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
results: [],
|
||||
total: 0,
|
||||
loading: false,
|
||||
page: 1,
|
||||
fullDataLoaded: false
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
selected() {
|
||||
if (this.selected) {
|
||||
if (!this.fullDataLoaded) {
|
||||
this.results = [];
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
},
|
||||
query() {
|
||||
this.results = [];
|
||||
this.page = 1;
|
||||
this.fullDataLoaded = false;
|
||||
this.loadData();
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadData();
|
||||
window.addEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
unmounted() {
|
||||
window.removeEventListener('scroll', this.handleScroll);
|
||||
},
|
||||
computed: {
|
||||
preparedResults() {
|
||||
if (this.selected) {
|
||||
return this.results;
|
||||
}
|
||||
return this.results.slice(0, 4);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleScroll() {
|
||||
if (this.loading) return;
|
||||
|
||||
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
|
||||
if (this.results.length < this.total) {
|
||||
this.loadData();
|
||||
}
|
||||
}
|
||||
},
|
||||
loadData() {
|
||||
if (this.loading && this.page) return;
|
||||
|
||||
this.loading = true;
|
||||
axios.get(this.searchUrl, {
|
||||
params: {
|
||||
q: this.query,
|
||||
group: this.group,
|
||||
preview: !this.selected,
|
||||
page: this.page
|
||||
}
|
||||
})
|
||||
.then((response) => {
|
||||
if (this.selected) this.fullDataLoaded = true;
|
||||
this.results = this.results.concat(response.data.data);
|
||||
this.total = response.data.meta.total;
|
||||
this.loading = false;
|
||||
this.page = response.data.meta.next_page;
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
31
app/serializers/global_search/project_serializer.rb
Normal file
31
app/serializers/global_search/project_serializer.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class GlobalSearch::ProjectSerializer < ActiveModel::Serializer
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
attributes :id, :name, :code, :created_at, :team, :folder, :archived, :url
|
||||
|
||||
def team
|
||||
{
|
||||
name: object.team.name,
|
||||
url: projects_path(team: object.team)
|
||||
}
|
||||
end
|
||||
|
||||
def created_at
|
||||
I18n.l(object.created_at, format: :full_date)
|
||||
end
|
||||
|
||||
def url
|
||||
project_path(object)
|
||||
end
|
||||
|
||||
def folder
|
||||
if object.project_folder
|
||||
{
|
||||
name: object.project_folder.name,
|
||||
url: project_folder_path(object.project_folder)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@
|
|||
<div id="GlobalSearch" class="contents">
|
||||
<global_search
|
||||
:query="'<%= @display_query %>'"
|
||||
:search-url="'<%= search_path(format: :json) %>'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -455,6 +455,10 @@ en:
|
|||
reports: "Reports"
|
||||
more_search_options: "More search options"
|
||||
clear_filters: "Clear filters"
|
||||
id: "ID"
|
||||
created_at: "Created on"
|
||||
team: "Team"
|
||||
folder: "Folder"
|
||||
comments:
|
||||
save_changes: "Save changes"
|
||||
empty_state:
|
||||
|
|
Loading…
Reference in a new issue