mirror of
https://github.com/frappe/books.git
synced 2026-08-24 02:24:17 -05:00
Merge pull request #1183 from suhailanzar/fix-filter
fix: improve docs filtering & Added status wise Filtering
This commit is contained in:
@@ -696,11 +696,9 @@ export class Payment extends Transactional {
|
||||
},
|
||||
referenceType: {
|
||||
formula: () => {
|
||||
if (this.referenceType) {
|
||||
return;
|
||||
}
|
||||
return this.for![0].referenceType;
|
||||
return this.referenceType || undefined;
|
||||
},
|
||||
dependsOn: ['for'],
|
||||
},
|
||||
taxes: { formula: async () => await this.getTaxSummary() },
|
||||
};
|
||||
|
||||
@@ -333,6 +333,11 @@ export function getTransactionStatusColumn(): ColumnConfig {
|
||||
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||
metadata: {
|
||||
status,
|
||||
color,
|
||||
label,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -707,6 +712,11 @@ export function getDocStatusListColumn(): ColumnConfig {
|
||||
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${label}</Badge>`,
|
||||
metadata: {
|
||||
status,
|
||||
color,
|
||||
label,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<template>
|
||||
<Popover
|
||||
ref="filterPopover"
|
||||
v-if="fields.length"
|
||||
placement="bottom-end"
|
||||
@close="emitFilterChange"
|
||||
:close-on-click-outside="true"
|
||||
:close-on-click-content="false"
|
||||
>
|
||||
<template #target="{ togglePopover }">
|
||||
<Button :icon="true" @click="togglePopover()">
|
||||
@@ -54,7 +57,7 @@
|
||||
name="x"
|
||||
class="w-4 h-4 cursor-pointer"
|
||||
:button="true"
|
||||
@click="removeFilter(filter)"
|
||||
@click="removeFilter(i)"
|
||||
/>
|
||||
</span>
|
||||
<span class="group-hover:hidden">
|
||||
@@ -67,15 +70,17 @@
|
||||
class="w-24"
|
||||
:df="{
|
||||
label: t`Field`,
|
||||
placeholder: t`Field`,
|
||||
placeholder: t`field`,
|
||||
fieldname: 'fieldname',
|
||||
fieldtype: 'Select',
|
||||
options: fieldOptions,
|
||||
}"
|
||||
:value="filter.fieldname"
|
||||
:close-drop-down="false"
|
||||
@mousedown.stop
|
||||
@click.stop
|
||||
@change="(value) => updateNewFilters(i, 'fieldname', value)"
|
||||
/>
|
||||
|
||||
<Select
|
||||
:border="true"
|
||||
size="small"
|
||||
@@ -85,12 +90,15 @@
|
||||
placeholder: t`Condition`,
|
||||
fieldname: 'condition',
|
||||
fieldtype: 'Select',
|
||||
options: conditions,
|
||||
options: conditionsForDropdown,
|
||||
}"
|
||||
:value="filter.condition"
|
||||
:close-drop-down="false"
|
||||
@mousedown.stop
|
||||
@click.stop
|
||||
@change="(value) => updateNewFilters(i, 'condition', value)"
|
||||
/>
|
||||
|
||||
<Data
|
||||
:border="true"
|
||||
size="small"
|
||||
@@ -103,6 +111,8 @@
|
||||
}"
|
||||
:value="String(filter.value)"
|
||||
:close-drop-down="false"
|
||||
@mousedown.stop
|
||||
@click.stop
|
||||
@change="(value) => updateNewFilters(i, 'value', value)"
|
||||
/>
|
||||
</div>
|
||||
@@ -114,31 +124,74 @@
|
||||
}}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div
|
||||
class="
|
||||
text-base
|
||||
border-t
|
||||
dark:border-gray-800
|
||||
p-2
|
||||
flex
|
||||
items-center
|
||||
text-gray-600
|
||||
dark:text-gray-500
|
||||
cursor-pointer
|
||||
hover:bg-gray-100
|
||||
dark:hover:bg-gray-875
|
||||
"
|
||||
@click="addNewFilter"
|
||||
>
|
||||
<feather-icon name="plus" class="w-4 h-4" />
|
||||
<span class="ms-2">{{ t`Add a filter` }}</span>
|
||||
<div class="flex justify-between border-t dark:border-gray-800">
|
||||
<div
|
||||
class="
|
||||
text-base
|
||||
border-t
|
||||
dark:border-gray-800
|
||||
p-2
|
||||
flex
|
||||
items-center
|
||||
text-gray-600
|
||||
dark:text-gray-500
|
||||
cursor-pointer
|
||||
hover:bg-gray-100
|
||||
dark:hover:bg-gray-875
|
||||
"
|
||||
@click.stop="addNewFilter"
|
||||
>
|
||||
<feather-icon name="plus" class="w-4 h-4" />
|
||||
<span class="ms-2">{{ t`Add a filter` }}</span>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<div
|
||||
v-if="filters.length"
|
||||
class="
|
||||
text-base
|
||||
p-2
|
||||
flex
|
||||
items-center
|
||||
text-gray-600
|
||||
dark:text-gray-500
|
||||
cursor-pointer
|
||||
hover:bg-gray-100
|
||||
dark:hover:bg-gray-875
|
||||
"
|
||||
@click="clearAllFilters"
|
||||
>
|
||||
<feather-icon name="trash-2" class="w-4 h-4" />
|
||||
<span class="ms-2">{{ t`Clear` }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="filters.length"
|
||||
@click="applyFilters"
|
||||
class="
|
||||
text-base
|
||||
border-t
|
||||
dark:border-gray-800
|
||||
p-2
|
||||
flex
|
||||
items-center
|
||||
text-gray-600
|
||||
dark:text-gray-500
|
||||
cursor-pointer
|
||||
hover:bg-gray-100
|
||||
dark:hover:bg-gray-875
|
||||
"
|
||||
>
|
||||
<feather-icon name="search" class="w-4 h-4" />
|
||||
<span class="ml-2 text-sm">{{ t`Apply` }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { t } from 'fyo';
|
||||
import { Field, FieldTypeEnum } from 'schemas/types';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { getRandomString } from 'utils';
|
||||
@@ -149,6 +202,7 @@ import Select from './Controls/Select.vue';
|
||||
import Icon from './Icon.vue';
|
||||
import Popover from './Popover.vue';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
import { t } from 'fyo';
|
||||
|
||||
const conditions = [
|
||||
{ label: t`Is`, value: '=' },
|
||||
@@ -161,7 +215,7 @@ const conditions = [
|
||||
{ label: t`Is Not Empty`, value: 'is not null' },
|
||||
] as const;
|
||||
|
||||
type Condition = typeof conditions[number]['value'];
|
||||
type Condition = typeof conditions[number]['label'];
|
||||
|
||||
type Filter = {
|
||||
fieldname: string;
|
||||
@@ -194,8 +248,13 @@ export default defineComponent({
|
||||
FieldTypeEnum.Attachment,
|
||||
FieldTypeEnum.AttachImage,
|
||||
];
|
||||
|
||||
const listViewSettings =
|
||||
fyo.models[this.schemaName]?.getListViewSettings?.(fyo);
|
||||
const statusField = listViewSettings?.columns?.[1] as any;
|
||||
|
||||
const fields = fyo.schemaMap[this.schemaName]?.fields ?? [];
|
||||
return fields.filter((f) => {
|
||||
const filteredFields = fields.filter((f) => {
|
||||
if (f.filter) {
|
||||
return true;
|
||||
}
|
||||
@@ -210,16 +269,41 @@ export default defineComponent({
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (statusField && statusField.fieldname) {
|
||||
const statusFieldExists = filteredFields.some(
|
||||
(field) => field.fieldname === statusField.fieldname
|
||||
);
|
||||
|
||||
if (!statusFieldExists) {
|
||||
const originalStatusField = fields.find(
|
||||
(field) => field.fieldname === statusField.fieldname
|
||||
);
|
||||
if (originalStatusField) {
|
||||
filteredFields.unshift(originalStatusField);
|
||||
} else {
|
||||
filteredFields.unshift(statusField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return filteredFields;
|
||||
},
|
||||
fieldOptions(): { label: string; value: string }[] {
|
||||
return this.fields.map((df) => ({
|
||||
label: df.label,
|
||||
label: df.fieldname,
|
||||
value: df.fieldname,
|
||||
}));
|
||||
},
|
||||
conditions(): { label: string; value: string }[] {
|
||||
return [...conditions];
|
||||
},
|
||||
conditionsForDropdown(): { label: string; value: string }[] {
|
||||
return conditions.map((c) => ({
|
||||
label: c.label,
|
||||
value: c.label,
|
||||
}));
|
||||
},
|
||||
explicitFilters(): Filter[] {
|
||||
return this.filters.filter((f) => !f.implicit);
|
||||
},
|
||||
@@ -234,11 +318,19 @@ export default defineComponent({
|
||||
return this.t`${this.activeFilterCount} filters applied`;
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.addNewFilter();
|
||||
},
|
||||
|
||||
methods: {
|
||||
getRandomString,
|
||||
getConditionLabel(value: string): string {
|
||||
const condition = conditions.find((c) => c.value === value);
|
||||
return condition ? condition.label : value;
|
||||
},
|
||||
|
||||
getConditionValue(label: string): string {
|
||||
const condition = conditions.find((c) => c.label === label);
|
||||
return condition ? condition.value : label;
|
||||
},
|
||||
|
||||
addNewFilter(): void {
|
||||
const df = this.fields[0];
|
||||
if (!df) {
|
||||
@@ -249,24 +341,52 @@ export default defineComponent({
|
||||
},
|
||||
addFilter(
|
||||
fieldname: string,
|
||||
condition: Condition,
|
||||
condition: string,
|
||||
value: Filter['value'],
|
||||
implicit?: boolean
|
||||
): void {
|
||||
this.filters.push({ fieldname, condition, value, implicit: !!implicit });
|
||||
this.newFilters.push({
|
||||
const displayCondition = this.getConditionLabel(condition);
|
||||
const newFilter = {
|
||||
fieldname,
|
||||
condition,
|
||||
condition: displayCondition,
|
||||
value,
|
||||
implicit: !!implicit,
|
||||
});
|
||||
};
|
||||
this.filters.push(newFilter);
|
||||
this.newFilters.push(newFilter);
|
||||
},
|
||||
removeFilter(filter: Filter): void {
|
||||
this.filters = this.filters.filter((f) => f !== filter);
|
||||
|
||||
applyFilters() {
|
||||
this.emitFilterChange();
|
||||
},
|
||||
updateNewFilters(index: number, key: keyof Filter, value: Filter['value']) {
|
||||
this.newFilters![index][key] = value;
|
||||
|
||||
removeFilter(index: number): void {
|
||||
this.filters.splice(index, 1);
|
||||
this.newFilters.splice(index, 1);
|
||||
},
|
||||
|
||||
clearAllFilters(): void {
|
||||
this.filters = [];
|
||||
this.newFilters = [];
|
||||
|
||||
this.$emit('change', {});
|
||||
},
|
||||
|
||||
updateNewFilters<K extends keyof Filter>(
|
||||
index: number,
|
||||
key: K,
|
||||
value: Filter[K]
|
||||
) {
|
||||
if (key === 'condition') {
|
||||
const displayCondition = this.getConditionLabel(value as string);
|
||||
this.newFilters![index][key] = displayCondition as Filter[K];
|
||||
this.filters[index][key] = displayCondition as Filter[K];
|
||||
} else {
|
||||
this.newFilters![index][key] = value;
|
||||
this.filters[index][key] = value;
|
||||
}
|
||||
},
|
||||
|
||||
setFilter(filters: QueryFilter, implicit?: boolean): void {
|
||||
this.filters = [];
|
||||
this.newFilters = [];
|
||||
@@ -289,22 +409,31 @@ export default defineComponent({
|
||||
|
||||
this.emitFilterChange();
|
||||
},
|
||||
|
||||
emitFilterChange(): void {
|
||||
const filters: Record<string, [Condition, Filter['value']]> = {};
|
||||
|
||||
for (const { condition, value, fieldname } of this.newFilters) {
|
||||
if (value === '' && condition) {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
filters[fieldname] = [condition, value];
|
||||
const sqlCondition = this.getConditionValue(condition);
|
||||
|
||||
if (fieldname === 'numberSeries') {
|
||||
filters['name'] = [sqlCondition, value];
|
||||
} else {
|
||||
filters[fieldname] = [sqlCondition, value];
|
||||
}
|
||||
}
|
||||
|
||||
this.$emit('change', filters);
|
||||
this.filters = [...this.newFilters];
|
||||
|
||||
if (this.newFilters.length) {
|
||||
this.filters = this.filters.filter(
|
||||
(filter) => filter.condition && filter.value && filter.fieldname
|
||||
);
|
||||
|
||||
this.filters.push(this.newFilters[this.newFilters.length - 1]);
|
||||
}
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
}"
|
||||
:row="(row as RenderData)"
|
||||
:column="column"
|
||||
@status-found="handleStatusFound"
|
||||
/>
|
||||
</Row>
|
||||
</div>
|
||||
@@ -115,7 +116,7 @@ import Row from 'src/components/Row.vue';
|
||||
import { fyo } from 'src/initFyo';
|
||||
import { isNumeric } from 'src/utils';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
import { PropType, defineComponent } from 'vue';
|
||||
import { PropType, defineComponent, toRaw } from 'vue';
|
||||
import ListCell from './ListCell.vue';
|
||||
|
||||
export default defineComponent({
|
||||
@@ -144,6 +145,7 @@ export default defineComponent({
|
||||
data: [] as RenderData[],
|
||||
pageStart: 0,
|
||||
pageEnd: 0,
|
||||
statusMap: {} as Record<string, string>,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -186,6 +188,9 @@ export default defineComponent({
|
||||
this.setUpdateListeners();
|
||||
},
|
||||
methods: {
|
||||
handleStatusFound({ rowId, status }: { rowId: string; status: string }) {
|
||||
this.statusMap[rowId] = status;
|
||||
},
|
||||
isNumeric,
|
||||
setPageIndices({ start, end }: { start: number; end: number }) {
|
||||
this.pageStart = start;
|
||||
@@ -210,12 +215,20 @@ export default defineComponent({
|
||||
fyo.doc.observer.on(`rename:${this.schemaName}`, listener);
|
||||
},
|
||||
async updateData(filters?: Record<string, unknown>) {
|
||||
if (!filters) {
|
||||
filters = { ...this.filters };
|
||||
const baseFilters = cloneDeep(toRaw(this.filters));
|
||||
filters = cloneDeep({ ...baseFilters, ...filters });
|
||||
|
||||
let statusFilter: [string, string] | undefined;
|
||||
|
||||
if ('status' in filters) {
|
||||
statusFilter = filters['status'] as [string, string];
|
||||
}
|
||||
|
||||
// Unproxy the filters
|
||||
filters = cloneDeep(filters);
|
||||
const isStatusFilter =
|
||||
Array.isArray(statusFilter) && statusFilter[0] === 'like';
|
||||
if (isStatusFilter) {
|
||||
delete filters['status'];
|
||||
}
|
||||
|
||||
const orderBy = ['created'];
|
||||
if (fyo.db.fieldMap[this.schemaName]['date']) {
|
||||
@@ -228,7 +241,21 @@ export default defineComponent({
|
||||
orderBy,
|
||||
});
|
||||
|
||||
this.data = tableData.map((d) => ({
|
||||
let filteredData = tableData;
|
||||
|
||||
if (isStatusFilter && statusFilter?.[1]) {
|
||||
const lowercaseStatus = String(statusFilter[1]).toLowerCase();
|
||||
|
||||
const matchedNames = Object.entries(this.statusMap)
|
||||
.filter((entry) => entry[1].toLowerCase() === lowercaseStatus)
|
||||
.map((entry) => entry[0]);
|
||||
|
||||
filteredData = tableData.filter((row) =>
|
||||
matchedNames.includes(String(row.name))
|
||||
);
|
||||
}
|
||||
|
||||
this.data = filteredData.map((d) => ({
|
||||
...d,
|
||||
schema: fyo.schemaMap[this.schemaName],
|
||||
})) as RenderData[];
|
||||
|
||||
@@ -27,6 +27,7 @@ export default defineComponent({
|
||||
row: { type: Object as PropType<RenderData>, required: true },
|
||||
column: { type: Object as PropType<Column>, required: true },
|
||||
},
|
||||
emits: ['status-found'],
|
||||
computed: {
|
||||
columnValue(): string {
|
||||
const column = this.column;
|
||||
@@ -51,5 +52,23 @@ export default defineComponent({
|
||||
return isNumeric(this.column.fieldtype) ? 'justify-end' : '';
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
const { render } = this.column as ColumnConfig;
|
||||
if (render) {
|
||||
const result = render(this.row) as {
|
||||
template: string;
|
||||
metadata?: { status: string; color: string; label: string };
|
||||
};
|
||||
|
||||
if (result?.metadata) {
|
||||
this.$emit('status-found', {
|
||||
rowId: this.row.name || this.row.id,
|
||||
fieldname: this.column.fieldname,
|
||||
status: result.metadata.status,
|
||||
label: result.metadata.label,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -115,10 +115,6 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
activated() {
|
||||
if (typeof this.filters === 'object') {
|
||||
this.filterDropdown?.setFilter(this.filters, true);
|
||||
}
|
||||
|
||||
this.listConfig = getListConfig(this.schemaName);
|
||||
docsPathRef.value =
|
||||
docsPathMap[this.schemaName] ?? docsPathMap.Entries ?? '';
|
||||
|
||||
Reference in New Issue
Block a user