2
0
mirror of https://github.com/frappe/books.git synced 2025-01-08 17:24:05 +00:00

feat: FilterDropdown in ListView

This commit is contained in:
Faris Ansari 2019-11-22 00:50:30 +05:30
parent f52ee12e66
commit 5cc57d3748
7 changed files with 276 additions and 21 deletions

View File

@ -24,9 +24,18 @@ module.exports = {
});
}
},
// {
// label: _('View Invoices'),
// action: console.log
// }
{
label: _('View Invoices'),
action: customer => {
router.push({
path: `/list/SalesInvoice`,
query: {
filters: {
customer: customer.name
}
}
});
}
}
]
};

View File

@ -0,0 +1,189 @@
<template>
<Popover :right="true" @hide="emitFilterChange">
<template v-slot:target="{ toggleDropdown }">
<Button :icon="true" @click="toggleDropdown()">
<span class="flex items-center">
<Icon name="filter" size="12" class="stroke-current text-gray-800" />
<span class="ml-2 text-base text-black">
<template v-if="activeFilterCount > 0">
{{ filterAppliedMessage }}
</template>
<template v-else>
{{ _('Filter') }}
</template>
</span>
</span>
</Button>
</template>
<div slot="content">
<div class="p-3">
<template v-if="filters.length">
<div
:key="filter.fieldname + filter.conditions + filter.value"
v-for="(filter, i) in filters"
class="flex items-center justify-between text-base"
:class="i !== 0 && 'mt-2'"
>
<div class="flex">
<div class="w-24">
<FormControl
size="small"
:background="true"
:df="{
placeholder: 'Field',
fieldname: 'fieldname',
fieldtype: 'Select',
options: fieldOptions
}"
:value="filter.fieldname"
@change="value => (filter.fieldname = value)"
/>
</div>
<div class="ml-2 w-24">
<FormControl
size="small"
:background="true"
:df="{
placeholder: 'Condition',
fieldname: 'condition',
fieldtype: 'Select',
options: conditions
}"
:value="filter.condition"
@change="value => (filter.condition = value)"
/>
</div>
<div class="ml-2 w-24">
<FormControl
size="small"
:background="true"
:df="{
placeholder: 'Value',
fieldname: 'value',
fieldtype: 'Data'
}"
:value="filter.value"
@change="value => (filter.value = value)"
/>
</div>
</div>
<div
class="ml-2 cursor-pointer w-5 h-5 flex items-center justify-center hover:bg-gray-100 rounded"
>
<feather-icon
name="x"
class="w-4 h-4"
@click="removeFilter(filter)"
/>
</div>
</div>
</template>
<template v-else>
<span class="text-base text-gray-600">{{
_('No filters selected')
}}</span>
</template>
</div>
<div
class="text-base border-t px-3 py-2 flex items-center text-gray-600 cursor-pointer hover:bg-gray-100"
@click="addNewFilter"
>
<feather-icon name="plus" class="w-4 h-4" />
<span class="ml-2">{{ _('Add a filter') }}</span>
</div>
</div>
</Popover>
</template>
<script>
import Popover from './Popover';
import Button from './Button';
import Icon from './Icon';
import FormControl from './Controls/FormControl';
let conditions = [
{ label: 'Is', value: '=' },
{ label: 'Is Not', value: '!=' },
{ label: 'Contains', value: 'like' },
{ label: 'Does Not Contain', value: 'not like' },
{ label: 'Greater Than', value: '>' },
{ label: 'Less Than', value: '<' },
{ label: 'Is Empty', value: 'is null' },
{ label: 'Is Not Empty', value: 'is not null' }
];
export default {
name: 'FilterDropdown',
components: {
Popover,
Button,
Icon,
FormControl
},
props: ['fields'],
data() {
return {
filters: []
};
},
created() {
this.addNewFilter();
},
methods: {
addNewFilter() {
let df = this.fields[0];
this.addFilter(df.fieldname, 'like', '');
},
addFilter(fieldname, condition, value) {
this.filters.push({ fieldname, condition, value });
},
removeFilter(filter) {
this.filters = this.filters.filter(f => f !== filter);
},
setFilter(filters) {
this.filters = [];
Object.keys(filters).map(fieldname => {
let parts = filters[fieldname];
let condition, value;
if (Array.isArray(parts)) {
condition = parts[0];
value = parts[1];
} else {
condition = '=';
value = parts;
}
this.addFilter(fieldname, condition, value);
});
this.emitFilterChange();
},
emitFilterChange() {
let filters = this.filters.reduce((acc, filter) => {
if (filter.value === '' && filter.condition) {
return acc;
}
acc[filter.fieldname] = [filter.condition, filter.value];
return acc;
}, {});
this.$emit('change', filters);
}
},
computed: {
fieldOptions() {
return this.fields.map(df => ({ label: df.label, value: df.fieldname }));
},
conditions() {
return conditions;
},
activeFilterCount() {
return this.filters.filter(filter => filter.value).length;
},
filterAppliedMessage() {
if (this.activeFilterCount === 1) {
return this._('1 filter applied');
}
return this._('{0} filters applied', [this.activeFilterCount]);
}
}
};
</script>

View File

@ -4,7 +4,7 @@
stroke-linecap="round"
stroke-linejoin="round"
d="M11.2 1.5L.8 1.5M3 5.75L9 5.75M5 10.5L7 10.5"
transform="translate(0 -1)"
transform="translate(0 -2)"
/>
</svg>
</template>

View File

@ -0,0 +1,44 @@
<template>
<div class="relative" v-on-outside-click="() => toggleDropdown(false)">
<div class="h-full">
<slot name="target" :toggleDropdown="toggleDropdown"></slot>
</div>
<div
:class="right ? 'right-0' : 'left-0'"
class="mt-1 absolute z-10 bg-white rounded-5px border min-w-40 shadow-md"
v-if="isShown"
>
<slot name="content"></slot>
</div>
</div>
</template>
<script>
export default {
name: 'Popover',
props: ['right'],
data() {
return {
isShown: false
};
},
watch: {
isShown(newVal, oldVal) {
if (newVal === false) {
this.$emit('hide');
}
if (newVal === true) {
this.$emit('show');
}
}
},
methods: {
toggleDropdown(flag, from) {
if (flag == null) {
flag = !this.isShown;
}
this.isShown = Boolean(flag);
}
}
};
</script>

View File

@ -104,7 +104,6 @@ export default {
frappe.db.on(`change:${this.listConfig.doctype}`, obj => {
this.updateData();
});
frappe.listView.on('filterList', this.updateData.bind(this));
},
methods: {
async setupColumnsAndData() {
@ -127,8 +126,6 @@ export default {
},
async updateData(filters) {
if (!filters) filters = this.getFilters();
// since passing filters as URL params which is String
filters = this.formatFilters(filters);
this.data = await frappe.db.getAll({
doctype: this.doctype,
fields: ['*'],

View File

@ -34,7 +34,6 @@ export default {
methods: {
removeFilter(filter) {
delete this.currentFilters[filter];
frappe.listView.trigger('filterList', this.currentFilters);
this.usedToReRender += 1;
}
}

View File

@ -3,14 +3,24 @@
<PageHeader>
<h1 slot="title" class="text-2xl font-bold" v-if="title">{{ title }}</h1>
<template slot="actions">
<Button :icon="true" type="primary" @click="makeNewDoc">
<FilterDropdown
ref="filterDropdown"
@change="applyFilter"
:fields="meta.fields"
/>
<Button class="ml-2" :icon="true" type="primary" @click="makeNewDoc">
<feather-icon name="plus" class="w-4 h-4 text-white" />
</Button>
<SearchBar class="ml-2" />
</template>
</PageHeader>
<div class="flex-1 flex h-full">
<List :listConfig="listConfig" :filters="filters" class="flex-1" />
<List
ref="list"
:listConfig="listConfig"
:filters="filters"
class="flex-1"
/>
</div>
</div>
</template>
@ -22,6 +32,8 @@ import Button from '@/components/Button';
import SearchBar from '@/components/SearchBar';
import List from './List';
import listConfigs from './listConfig';
import Icon from '@/components/Icon';
import FilterDropdown from '@/components/FilterDropdown';
export default {
name: 'ListView',
@ -30,10 +42,14 @@ export default {
PageHeader,
List,
Button,
SearchBar
SearchBar,
Icon,
FilterDropdown
},
created() {
frappe.listView = new Observable();
mounted() {
if (typeof this.filters === 'object') {
this.$refs.filterDropdown.setFilter(this.filters);
}
},
methods: {
async makeNewDoc() {
@ -52,6 +68,9 @@ export default {
this.$router.replace(path);
});
},
applyFilter(filters) {
this.$refs.list.updateData(filters);
},
getFormPath(name) {
if (this.listConfig.formRoute) {
let path = this.listConfig.formRoute(name);
@ -68,6 +87,9 @@ export default {
}
},
computed: {
meta() {
return frappe.getMeta(this.doctype);
},
listConfig() {
if (listConfigs[this.doctype]) {
return listConfigs[this.doctype];
@ -75,17 +97,12 @@ export default {
return {
title: this.doctype,
doctype: this.doctype,
columns: frappe.getMeta(this.doctype).getKeywordFields()
columns: this.meta.getKeywordFields()
};
}
},
title() {
if (this.listConfig) {
return typeof this.listConfig.title === 'function'
? this.listConfig.title(this.filters)
: this.listConfig.title;
}
return this.doctype;
return this.listConfig.title || this.doctype;
}
}
};