187 lines
4.5 KiB
Vue
187 lines
4.5 KiB
Vue
<template lang="html">
|
|
<v-menu
|
|
v-model="menu"
|
|
:close-on-content-click="false"
|
|
>
|
|
<template #activator="{ on, attrs }">
|
|
<v-btn
|
|
v-bind="attrs"
|
|
icon
|
|
v-on="on"
|
|
>
|
|
<v-badge
|
|
:content="numFilters"
|
|
:value="numFilters"
|
|
color="primary"
|
|
overlap
|
|
>
|
|
<v-icon>mdi-magnify</v-icon>
|
|
</v-badge>
|
|
</v-btn>
|
|
</template>
|
|
|
|
<v-card>
|
|
<v-card-title>
|
|
Search
|
|
</v-card-title>
|
|
<v-card-text>
|
|
<v-select
|
|
v-model="typeFilterInput"
|
|
outlined
|
|
label="Type"
|
|
:items="filterOptions"
|
|
multiple
|
|
clearable
|
|
small-chips
|
|
deletable-chips
|
|
/>
|
|
<v-slide-x-transition group>
|
|
<div
|
|
v-for="(fieldFilter, index) in fieldFilters"
|
|
:key="index"
|
|
class="d-flex"
|
|
>
|
|
<v-text-field
|
|
v-model="fieldFilter.field"
|
|
class="text--mono"
|
|
label="Field"
|
|
outlined
|
|
/>
|
|
<v-text-field
|
|
v-model="fieldFilter.value"
|
|
label="Text"
|
|
class="ml-2"
|
|
outlined
|
|
/>
|
|
<v-btn
|
|
v-if="fieldFilters.length > 1"
|
|
icon
|
|
@click="fieldFilters.splice(index, 1)"
|
|
>
|
|
<v-icon>mdi-close</v-icon>
|
|
</v-btn>
|
|
</div>
|
|
</v-slide-x-transition>
|
|
<div
|
|
v-if="fieldFilters.length < 5"
|
|
class="d-flex"
|
|
>
|
|
<v-spacer />
|
|
<v-btn
|
|
icon
|
|
@click="fieldFilters.push({name: '', value: undefined})"
|
|
>
|
|
<v-icon>mdi-plus</v-icon>
|
|
</v-btn>
|
|
</div>
|
|
<v-card-actions>
|
|
<v-btn
|
|
text
|
|
@click="
|
|
fieldFilters = [{field: 'name', value: undefined}];
|
|
typeFilterInput = [];
|
|
menu = false;
|
|
"
|
|
>
|
|
<v-icon left>
|
|
mdi-close
|
|
</v-icon>
|
|
Clear
|
|
</v-btn>
|
|
<v-spacer />
|
|
<v-btn
|
|
text
|
|
color="primary"
|
|
@click="menu = false"
|
|
>
|
|
Find
|
|
</v-btn>
|
|
</v-card-actions>
|
|
</v-card-text>
|
|
</v-card>
|
|
</v-menu>
|
|
</template>
|
|
|
|
<script lang="js">
|
|
import PROPERTIES from '/imports/constants/PROPERTIES.js';
|
|
import escapeRegex from '/imports/api/utility/escapeRegex.js';
|
|
|
|
const filterOptions = [];
|
|
for (let key in PROPERTIES) {
|
|
if (key === 'reference') continue;
|
|
filterOptions.push({
|
|
text: PROPERTIES[key].name,
|
|
value: key,
|
|
});
|
|
}
|
|
|
|
export default {
|
|
props: {
|
|
value: {
|
|
type: Object,
|
|
default: undefined,
|
|
},
|
|
},
|
|
data(){return {
|
|
typeFilterInput: [],
|
|
fieldFilters: [{field: 'name', value: undefined}],
|
|
filterOptions,
|
|
menu: false,
|
|
}},
|
|
computed: {
|
|
filter() {
|
|
let filter = undefined;
|
|
if (this.typeFilterInput?.length) {
|
|
filter = filter || {};
|
|
filter.type = {$in: this.typeFilterInput};
|
|
}
|
|
this.fieldFilters?.forEach(fieldFilter => {
|
|
if (!fieldFilter.field || !fieldFilter.value) return;
|
|
const search = { $regex: escapeRegex(fieldFilter.value), '$options': 'i' };
|
|
filter = filter || {};
|
|
if (fieldFilter.field.includes('.')) {
|
|
// The user used dot notation, search exactly where they are looking
|
|
filter[fieldFilter.field] = search;
|
|
} else {
|
|
// No dot notation, search fields and their likely sub-fields
|
|
filter.$and = filter.$and || [];
|
|
filter.$and.push({
|
|
$or: [
|
|
{ [fieldFilter.field]: search },
|
|
{ [fieldFilter.field + '.calculation']: search },
|
|
{ [fieldFilter.field + '.text']: search },
|
|
],
|
|
});
|
|
}
|
|
});
|
|
return filter;
|
|
},
|
|
extraFields() {
|
|
let extraFields = [];
|
|
this.fieldFilters?.forEach(fieldFilter => {
|
|
if (!fieldFilter.field || !fieldFilter.value) return;
|
|
extraFields.push(fieldFilter.field);
|
|
});
|
|
return extraFields;
|
|
},
|
|
numFilters() {
|
|
let numFilters = 0;
|
|
if (this.typeFilterInput?.length) numFilters += 1;
|
|
numFilters += this.extraFields.length;
|
|
return numFilters;
|
|
}
|
|
},
|
|
watch: {
|
|
menu(val) {
|
|
if (!val) {
|
|
this.$emit('input', this.filter);
|
|
this.$emit('extra-fields-changed', this.extraFields);
|
|
}
|
|
}
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style lang="css" scoped>
|
|
</style>
|