mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-09-20 14:45:56 +08:00
Connecte filters modal [SCI-10536]
This commit is contained in:
parent
d101f8794c
commit
01abb78d4e
|
@ -24,24 +24,19 @@
|
|||
{{ i18n.t('search.index.task_results') }}
|
||||
</button>
|
||||
</div>
|
||||
<GeneralDropdown ref="filterFlyout" position="right">
|
||||
<template v-slot:field>
|
||||
<button class="btn btn-light btn-sm">
|
||||
<i class="sn-icon sn-icon-search-options"></i>
|
||||
{{ i18n.t('search.index.more_search_options') }}
|
||||
</button>
|
||||
</template>
|
||||
<template v-slot:flyout >
|
||||
<SearchFilters
|
||||
:teamsUrl="teamsUrl"
|
||||
:usersUrl="usersUrl"
|
||||
@cancel="this.$refs.container.close()"
|
||||
></SearchFilters>
|
||||
</template>
|
||||
</GeneralDropdown>
|
||||
<template v-if="activeGroup">
|
||||
<button class="btn btn-light btn-sm" @click="filterModalOpened = true">
|
||||
<i class="sn-icon sn-icon-search-options"></i>
|
||||
{{ i18n.t('search.index.more_search_options') }}
|
||||
<span
|
||||
v-if="activeFilters.length > 0"
|
||||
class="absolute -right-1 -top-1 rounded-full bg-sn-science-blue text-white flex items-center justify-center w-4 h-4 text-[9px]"
|
||||
>
|
||||
{{ activeFilters.length }}
|
||||
</span>
|
||||
</button>
|
||||
<template v-if="activeFilters.length > 0">
|
||||
<div class="h-4 w-[1px] bg-sn-grey"></div>
|
||||
<button class="btn btn-light btn-sm" @click="activeGroup = null">
|
||||
<button class="btn btn-light btn-sm" @click="resetFilters">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
{{ i18n.t('search.index.clear_filters') }}
|
||||
</button>
|
||||
|
@ -55,9 +50,21 @@
|
|||
:selected="activeGroup === group"
|
||||
:query="localQuery"
|
||||
:searchUrl="searchUrl"
|
||||
:filters="filters"
|
||||
@selectGroup="setActiveGroup"
|
||||
/>
|
||||
</template>
|
||||
<teleport to='body'>
|
||||
<FiltersModal
|
||||
v-if="filterModalOpened"
|
||||
:teamsUrl="teamsUrl"
|
||||
:usersUrl="usersUrl"
|
||||
:filters="filters"
|
||||
:currentTeam="currentTeam"
|
||||
@search="applyFilters"
|
||||
@close="filterModalOpened = false"
|
||||
/>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -73,10 +80,11 @@ import RepositoryRowsComponent from './groups/repository_rows.vue';
|
|||
import ProtocolsComponent from './groups/protocols.vue';
|
||||
import LabelTemplatesComponent from './groups/label_templates.vue';
|
||||
import ReportsComponent from './groups/reports.vue';
|
||||
import SearchFilters from './filters.vue';
|
||||
import FiltersModal from './filters_modal.vue';
|
||||
import GeneralDropdown from '../shared/general_dropdown.vue';
|
||||
|
||||
export default {
|
||||
emits: ['search', 'selectGroup'],
|
||||
name: 'GlobalSearch',
|
||||
props: {
|
||||
query: {
|
||||
|
@ -94,6 +102,10 @@ export default {
|
|||
usersUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
currentTeam: {
|
||||
type: Number || String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -108,12 +120,14 @@ export default {
|
|||
ProtocolsComponent,
|
||||
LabelTemplatesComponent,
|
||||
ReportsComponent,
|
||||
SearchFilters,
|
||||
FiltersModal,
|
||||
GeneralDropdown
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
filters: {},
|
||||
localQuery: this.query,
|
||||
filterModalOpened: false,
|
||||
activeGroup: null,
|
||||
searchGroups: [
|
||||
'FoldersComponent',
|
||||
|
@ -130,6 +144,48 @@ export default {
|
|||
]
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
activeFilters() {
|
||||
return Object.keys(this.filters).filter((key) => {
|
||||
if (key === 'created_at' || key === 'updated_at') {
|
||||
return this.filters[key].on || this.filters[key].from || this.filters[key].to;
|
||||
} if (key === 'teams' || key === 'users') {
|
||||
return this.filters[key].length > 0;
|
||||
}
|
||||
return this.filters[key];
|
||||
});
|
||||
}
|
||||
},
|
||||
created() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.filters = {
|
||||
created_at: {
|
||||
on: null,
|
||||
from: null,
|
||||
to: null
|
||||
},
|
||||
updated_at: {
|
||||
on: null,
|
||||
from: null,
|
||||
to: null
|
||||
},
|
||||
include_archived: urlParams.get('include_archived') === 'true',
|
||||
teams: urlParams.getAll('teams[]').map((team) => parseInt(team, 10)),
|
||||
users: urlParams.getAll('users[]').map((user) => parseInt(user, 10)),
|
||||
group: urlParams.get('group')
|
||||
};
|
||||
['created_at', 'updated_at'].forEach((key) => {
|
||||
['on', 'from', 'to', 'mode'].forEach((subKey) => {
|
||||
if (urlParams.get(`${key}[${subKey}]`)) {
|
||||
this.filters[key][subKey] = subKey !== 'mode' ? new Date(urlParams.get(`${key}[${subKey}]`)) : urlParams.get(`${key}[${subKey}]`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (this.filters.group) {
|
||||
this.activeGroup = this.filters.group;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setActiveGroup(group) {
|
||||
if (group === this.activeGroup) {
|
||||
|
@ -137,9 +193,36 @@ export default {
|
|||
} else {
|
||||
this.activeGroup = group;
|
||||
}
|
||||
|
||||
this.filters.group = this.activeGroup;
|
||||
},
|
||||
changeQuery(event) {
|
||||
this.localQuery = event.target.value;
|
||||
},
|
||||
applyFilters(filters) {
|
||||
this.filters = filters;
|
||||
this.filterModalOpened = false;
|
||||
|
||||
this.activeGroup = this.filters.group;
|
||||
},
|
||||
resetFilters() {
|
||||
this.filters = {
|
||||
created_at: {
|
||||
on: null,
|
||||
from: null,
|
||||
to: null
|
||||
},
|
||||
updated_at: {
|
||||
on: null,
|
||||
from: null,
|
||||
to: null
|
||||
},
|
||||
include_archived: false,
|
||||
teams: [],
|
||||
users: [],
|
||||
group: null
|
||||
};
|
||||
this.activeGroup = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="max-w-[600px] p-3.5">
|
||||
<div class="max-w-[600px] py-3.5">
|
||||
<div class="sci-label mb-2">{{ i18n.t('search.filters.by_type') }}</div>
|
||||
<div class="flex items-center gap-2 flex-wrap mb-6">
|
||||
<template v-for="group in searchGroups" :key="group.value">
|
||||
|
@ -75,7 +75,8 @@ export default {
|
|||
type: String,
|
||||
required: true
|
||||
},
|
||||
currentTeam: Number,
|
||||
filters: Object,
|
||||
currentTeam: Number || String,
|
||||
searchUrl: String,
|
||||
searchQuery: String
|
||||
},
|
||||
|
@ -84,6 +85,17 @@ export default {
|
|||
if (this.currentTeam) {
|
||||
this.selectedTeams = [this.currentTeam];
|
||||
}
|
||||
|
||||
if (this.filters) {
|
||||
this.createdAt = this.filters.created_at;
|
||||
this.updatedAt = this.filters.updated_at;
|
||||
this.selectedTeams = this.filters.teams;
|
||||
this.$nextTick(() => {
|
||||
this.selectedUsers = this.filters.users;
|
||||
});
|
||||
this.includeArchived = this.filters.include_archived;
|
||||
this.activeGroup = this.filters.group;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedTeams() {
|
||||
|
@ -175,6 +187,15 @@ export default {
|
|||
search() {
|
||||
if (this.searchUrl) {
|
||||
this.openSearchPage();
|
||||
} else {
|
||||
this.$emit('search', {
|
||||
created_at: this.createdAt,
|
||||
updated_at: this.updatedAt,
|
||||
teams: this.selectedTeams,
|
||||
users: this.selectedUsers,
|
||||
include_archived: this.includeArchived,
|
||||
group: this.activeGroup
|
||||
});
|
||||
}
|
||||
},
|
||||
openSearchPage() {
|
||||
|
@ -182,9 +203,11 @@ export default {
|
|||
'created_at[on]': this.createdAt.on || '',
|
||||
'created_at[from]': this.createdAt.from || '',
|
||||
'created_at[to]': this.createdAt.to || '',
|
||||
'created_at[mode]': this.createdAt.mode || '',
|
||||
'updated_at[on]': this.updatedAt.on || '',
|
||||
'updated_at[from]': this.updatedAt.from || '',
|
||||
'updated_at[to]': this.updatedAt.to || '',
|
||||
'updated_at[mode]': this.updatedAt.mode || '',
|
||||
include_archived: this.includeArchived,
|
||||
group: this.activeGroup || '',
|
||||
q: this.searchQuery
|
||||
|
|
|
@ -66,28 +66,44 @@ export default {
|
|||
|
||||
switch (this.selectedOption) {
|
||||
case 'today':
|
||||
this.newDate = { on: today, from: null, to: null };
|
||||
this.newDate = {
|
||||
on: today, from: null, to: null, mode: 'today'
|
||||
};
|
||||
break;
|
||||
case 'yesterday':
|
||||
this.newDate = { on: yesterday, from: null, to: null };
|
||||
this.newDate = {
|
||||
on: yesterday, from: null, to: null, mode: 'yesterday'
|
||||
};
|
||||
break;
|
||||
case 'last_week':
|
||||
this.newDate = { on: null, from: lastWeekStart, to: lastWeekEnd };
|
||||
this.newDate = {
|
||||
on: null, from: lastWeekStart, to: lastWeekEnd, mode: 'last_week'
|
||||
};
|
||||
break;
|
||||
case 'this_month':
|
||||
this.newDate = { on: null, from: firstMonthDay, to: today };
|
||||
this.newDate = {
|
||||
on: null, from: firstMonthDay, to: today, mode: 'this_month'
|
||||
};
|
||||
break;
|
||||
case 'this_year':
|
||||
this.newDate = { on: null, from: firstYearDay, to: today };
|
||||
this.newDate = {
|
||||
on: null, from: firstYearDay, to: today, mode: 'this_year'
|
||||
};
|
||||
break;
|
||||
case 'last_year':
|
||||
this.newDate = { on: null, from: lastYearStart, to: lastYearEnd };
|
||||
this.newDate = {
|
||||
on: null, from: lastYearStart, to: lastYearEnd, mode: 'last_year'
|
||||
};
|
||||
break;
|
||||
case 'on':
|
||||
this.newDate = { on: null, from: null, to: null };
|
||||
this.newDate = {
|
||||
on: null, from: null, to: null, mode: 'on'
|
||||
};
|
||||
break;
|
||||
case 'custom':
|
||||
this.newDate = { on: null, from: null, to: null };
|
||||
this.newDate = {
|
||||
on: null, from: null, to: null, mode: 'custom'
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
@ -99,7 +115,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
newDate: this.date,
|
||||
selectedOption: 'on',
|
||||
selectedOption: (this.date.mode || 'on'),
|
||||
dateOptions: [
|
||||
['today', 'Today'],
|
||||
['yesterday', 'Yesterday'],
|
||||
|
@ -114,15 +130,19 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
setOn(v) {
|
||||
this.newDate = { on: v, from: null, to: null };
|
||||
this.newDate = {
|
||||
on: v, from: null, to: null, mode: 'on'
|
||||
};
|
||||
this.$emit('change', this.newDate);
|
||||
},
|
||||
setFrom(v) {
|
||||
this.newDate.mode = 'custom';
|
||||
this.newDate.on = null;
|
||||
this.newDate.from = v;
|
||||
this.$emit('change', this.newDate);
|
||||
},
|
||||
setTo(v) {
|
||||
this.newDate.mode = 'custom';
|
||||
this.newDate.on = null;
|
||||
this.newDate.to = v;
|
||||
this.$emit('change', this.newDate);
|
||||
|
|
49
app/javascript/vue/global_search/filters_modal.vue
Normal file
49
app/javascript/vue/global_search/filters_modal.vue
Normal file
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<div ref="modal" class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog" role="document">
|
||||
<form @submit.prevent="submit">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<i class="sn-icon sn-icon-close"></i>
|
||||
</button>
|
||||
<h4 class="modal-title truncate !block" id="edit-project-modal-label">
|
||||
{{ i18n.t('search.filters.title') }}
|
||||
</h4>
|
||||
</div>
|
||||
<div class="modal-body !pb-0">
|
||||
<p class="text-sn-dark-grey">
|
||||
{{ i18n.t('search.filters.sub_title') }}
|
||||
</p>
|
||||
<Filters
|
||||
:teams-url="teamsUrl"
|
||||
:users-url="usersUrl"
|
||||
:filters="filters"
|
||||
:currentTeam="currentTeam"
|
||||
@search="(newFilters) => { this.$emit('search', newFilters); }"
|
||||
@close="close" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modalMixin from '../shared/modal_mixin';
|
||||
import Filters from './filters.vue';
|
||||
|
||||
export default {
|
||||
name: 'FiltersModal',
|
||||
props: {
|
||||
teamsUrl: String,
|
||||
usersUrl: String,
|
||||
filters: Object,
|
||||
currentTeam: Number || String
|
||||
},
|
||||
components: {
|
||||
Filters
|
||||
},
|
||||
mixins: [modalMixin]
|
||||
};
|
||||
</script>
|
|
@ -6,7 +6,8 @@ export default {
|
|||
props: {
|
||||
searchUrl: String,
|
||||
query: String,
|
||||
selected: Boolean
|
||||
selected: Boolean,
|
||||
filters: Object
|
||||
},
|
||||
components: {
|
||||
StringWithEllipsis
|
||||
|
@ -57,7 +58,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
handleScroll() {
|
||||
if (this.loading) return;
|
||||
if (this.loading || !this.selected) return;
|
||||
|
||||
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
|
||||
if (this.results.length < this.total) {
|
||||
|
@ -72,6 +73,7 @@ export default {
|
|||
axios.get(this.searchUrl, {
|
||||
params: {
|
||||
q: this.query,
|
||||
filters: this.filters,
|
||||
group: this.group,
|
||||
preview: !this.selected,
|
||||
page: this.page
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
</template>
|
||||
<template v-slot:flyout >
|
||||
<SearchFilters
|
||||
class="px-3.5"
|
||||
v-if="filtersOpened"
|
||||
:teamsUrl="teamsUrl"
|
||||
:usersUrl="usersUrl"
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<i v-if="canClear" @click="clear" class="sn-icon ml-auto sn-icon-close"></i>
|
||||
<i v-else class="sn-icon ml-auto self-start"
|
||||
<i v-else class="sn-icon ml-auto"
|
||||
:class="{ 'sn-icon-down': !isOpen, 'sn-icon-up': isOpen, 'text-sn-grey': disabled}"></i>
|
||||
</div>
|
||||
<template v-if="isOpen">
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
:search-url="'<%= search_path(format: :json) %>'"
|
||||
teams-url="<%= visible_teams_teams_path %>"
|
||||
users-url="<%= visible_users_teams_path %>"
|
||||
current-team="<%= current_team.id %>"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -439,6 +439,8 @@ en:
|
|||
match_case: "Match case sensitive"
|
||||
object_id: "ID:"
|
||||
filters:
|
||||
title: "More search options"
|
||||
sub_filters: "Refine your search by applying the filters below."
|
||||
by_type: "Filter by type"
|
||||
by_created_date: "Filter by created date"
|
||||
by_updated_date: "Filter by updated date"
|
||||
|
|
Loading…
Reference in a new issue