mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 11:37:58 +00:00
feat: Adds the ability to edit saved segments (#7254)
* feat: Ability to edit saved filters * chore: Adds edit contact segment * chore: Minor fixes * fix: code climate * chore: Minor fixes * chore: Adds ability to custom view name * chore: Minor fixes * chore: Adds spec for helper * chore: Revert contact filter to split to new PR * Delete editSegmentMixin.js * chore: Revert fixes * Update app/javascript/dashboard/i18n/locale/en/advancedFilters.json Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> * Update app/javascript/dashboard/i18n/locale/en/advancedFilters.json Co-authored-by: Shivam Mishra <scm.mymail@gmail.com> * chore: Moved from mixin to helper for reusing in segments * Delete editFolderMixin.js * chore: Fix specs and added new specs * chore: review comment fixes * chore: Minor fixes * fix: Not resetting applied filter * feat: Adds the ability to edit saved segments * feat: Adds specs for API part --------- Co-authored-by: Shivam Mishra <scm.mymail@gmail.com>
This commit is contained in:
@@ -211,6 +211,7 @@
|
||||
"FILTER_CONTACTS": "Filter",
|
||||
"FILTER_CONTACTS_SAVE": "Save filter",
|
||||
"FILTER_CONTACTS_DELETE": "Delete filter",
|
||||
"FILTER_CONTACTS_EDIT": "Edit segment",
|
||||
"LIST": {
|
||||
"LOADING_MESSAGE": "Loading contacts...",
|
||||
"404": "No contacts matches your search 🔍",
|
||||
|
||||
@@ -2,13 +2,18 @@
|
||||
"CONTACTS_FILTER": {
|
||||
"TITLE": "Filter Contacts",
|
||||
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
|
||||
"EDIT_CUSTOM_SEGMENT": "Edit Segment",
|
||||
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your segment.",
|
||||
"ADD_NEW_FILTER": "Add Filter",
|
||||
"CLEAR_ALL_FILTERS": "Clear All Filters",
|
||||
"FILTER_DELETE_ERROR": "You should have atleast one filter to save",
|
||||
"SUBMIT_BUTTON_LABEL": "Submit",
|
||||
"UPDATE_BUTTON_LABEL": "Update Segment",
|
||||
"CANCEL_BUTTON_LABEL": "Cancel",
|
||||
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
||||
"EMPTY_VALUE_ERROR": "Value is required",
|
||||
"SEGMENT_LABEL": "Segment Name",
|
||||
"SEGMENT_QUERY_LABEL": "Segment Query",
|
||||
"TOOLTIP_LABEL": "Filter contacts",
|
||||
"QUERY_DROPDOWN_LABELS": {
|
||||
"AND": "AND",
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
<template>
|
||||
<div class="column">
|
||||
<woot-modal-header :header-title="$t('CONTACTS_FILTER.TITLE')">
|
||||
<p>{{ $t('CONTACTS_FILTER.SUBTITLE') }}</p>
|
||||
<woot-modal-header :header-title="filterModalHeaderTitle">
|
||||
<p>{{ filterModalSubTitle }}</p>
|
||||
</woot-modal-header>
|
||||
<div class="row modal-content">
|
||||
<div class="column modal-content">
|
||||
<div v-if="isSegmentsView" class="columns">
|
||||
<label class="input-label" :class="{ error: !activeSegmentNewName }">
|
||||
{{ $t('CONTACTS_FILTER.SEGMENT_LABEL') }}
|
||||
<input
|
||||
v-model="activeSegmentNewName"
|
||||
type="text"
|
||||
class="name-input"
|
||||
/>
|
||||
<span v-if="!activeSegmentNewName" class="message">
|
||||
{{ $t('CONTACTS_FILTER.EMPTY_VALUE_ERROR') }}
|
||||
</span>
|
||||
</label>
|
||||
<label class="input-label">
|
||||
{{ $t('CONTACTS_FILTER.SEGMENT_QUERY_LABEL') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="medium-12 columns filters-wrap">
|
||||
<filter-input-box
|
||||
v-for="(filter, i) in appliedFilters"
|
||||
@@ -36,7 +52,7 @@
|
||||
{{ $t('CONTACTS_FILTER.ADD_NEW_FILTER') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="hasAppliedFilters"
|
||||
v-if="hasAppliedFilters && !isSegmentsView"
|
||||
icon="subtract"
|
||||
color-scheme="alert"
|
||||
variant="smooth"
|
||||
@@ -52,7 +68,14 @@
|
||||
<woot-button class="button clear" @click.prevent="onClose">
|
||||
{{ $t('CONTACTS_FILTER.CANCEL_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
<woot-button @click="submitFilterQuery">
|
||||
<woot-button
|
||||
v-if="isSegmentsView"
|
||||
:disabled="!activeSegmentNewName"
|
||||
@click="updateSegment"
|
||||
>
|
||||
{{ $t('CONTACTS_FILTER.UPDATE_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
<woot-button v-else @click="submitFilterQuery">
|
||||
{{ $t('CONTACTS_FILTER.SUBMIT_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
@@ -85,6 +108,18 @@ export default {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
initialAppliedFilters: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
isSegmentsView: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
activeSegmentName: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
appliedFilters: {
|
||||
@@ -105,7 +140,8 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
appliedFilters: [],
|
||||
appliedFilters: this.initialAppliedFilters,
|
||||
activeSegmentNewName: this.activeSegmentName,
|
||||
filterTypes: this.initialFilterTypes,
|
||||
filterGroups: [],
|
||||
allCustomAttributes: [],
|
||||
@@ -121,12 +157,22 @@ export default {
|
||||
hasAppliedFilters() {
|
||||
return this.getAppliedContactFilters.length;
|
||||
},
|
||||
filterModalHeaderTitle() {
|
||||
return !this.isSegmentsView
|
||||
? this.$t('CONTACTS_FILTER.TITLE')
|
||||
: this.$t('CONTACTS_FILTER.EDIT_CUSTOM_SEGMENT');
|
||||
},
|
||||
filterModalSubTitle() {
|
||||
return !this.isSegmentsView
|
||||
? this.$t('CONTACTS_FILTER.SUBTITLE')
|
||||
: this.$t('CONTACTS_FILTER.CUSTOM_VIEWS_SUBTITLE');
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setFilterAttributes();
|
||||
if (this.getAppliedContactFilters.length) {
|
||||
this.appliedFilters = [...this.getAppliedContactFilters];
|
||||
} else {
|
||||
} else if (!this.isSegmentsView) {
|
||||
this.appliedFilters.push({
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
@@ -177,11 +223,11 @@ export default {
|
||||
if (key === 'created_at' || key === 'last_activity_at')
|
||||
if (operator === 'days_before') return 'plain_text';
|
||||
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||
return type.inputType;
|
||||
return type?.inputType;
|
||||
},
|
||||
getOperators(key) {
|
||||
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||
return type.filterOperators;
|
||||
return type?.filterOperators;
|
||||
},
|
||||
getDropdownValues(type) {
|
||||
const allCustomAttributes = this.$store.getters[
|
||||
@@ -230,11 +276,30 @@ export default {
|
||||
}
|
||||
},
|
||||
appendNewFilter() {
|
||||
this.appliedFilters.push({
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
if (this.isSegmentsView) {
|
||||
this.setQueryOperatorOnLastQuery();
|
||||
} else {
|
||||
this.appliedFilters.push({
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
}
|
||||
},
|
||||
setQueryOperatorOnLastQuery() {
|
||||
const lastItemIndex = this.appliedFilters.length - 1;
|
||||
this.appliedFilters[lastItemIndex] = {
|
||||
...this.appliedFilters[lastItemIndex],
|
||||
query_operator: 'and',
|
||||
};
|
||||
this.$nextTick(() => {
|
||||
this.appliedFilters.push({
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
});
|
||||
},
|
||||
removeFilter(index) {
|
||||
@@ -259,6 +324,13 @@ export default {
|
||||
})),
|
||||
});
|
||||
},
|
||||
updateSegment() {
|
||||
this.$emit(
|
||||
'updateSegment',
|
||||
this.appliedFilters,
|
||||
this.activeSegmentNewName
|
||||
);
|
||||
},
|
||||
resetFilter(index, currentFilter) {
|
||||
this.appliedFilters[index].filter_operator = this.filterTypes.find(
|
||||
filter => filter.attributeKey === currentFilter.attribute_key
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="contacts-page row">
|
||||
<div class="left-wrap" :class="wrapClas">
|
||||
<div class="left-wrap" :class="wrapClass">
|
||||
<contacts-header
|
||||
:search-query="searchQuery"
|
||||
:segments-id="segmentsId"
|
||||
@@ -13,6 +13,7 @@
|
||||
:header-title="pageTitle"
|
||||
@on-toggle-save-filter="onToggleSaveFilters"
|
||||
@on-toggle-delete-filter="onToggleDeleteFilters"
|
||||
@on-toggle-edit-filter="onToggleFilters"
|
||||
/>
|
||||
<contacts-table
|
||||
:contacts="records"
|
||||
@@ -58,14 +59,18 @@
|
||||
</woot-modal>
|
||||
<woot-modal
|
||||
:show.sync="showFiltersModal"
|
||||
:on-close="onToggleFilters"
|
||||
:on-close="closeAdvanceFiltersModal"
|
||||
size="medium"
|
||||
>
|
||||
<contacts-advanced-filters
|
||||
v-if="showFiltersModal"
|
||||
:on-close="onToggleFilters"
|
||||
:on-close="closeAdvanceFiltersModal"
|
||||
:initial-filter-types="contactFilterItems"
|
||||
:initial-applied-filters="appliedFilter"
|
||||
:active-segment-name="activeSegmentName"
|
||||
:is-segments-view="hasActiveSegments"
|
||||
@applyFilter="onApplyFilter"
|
||||
@updateSegment="onUpdateSegment"
|
||||
@clearFilters="clearFilters"
|
||||
/>
|
||||
</woot-modal>
|
||||
@@ -87,6 +92,8 @@ import filterQueryGenerator from '../../../../helper/filterQueryGenerator';
|
||||
import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews';
|
||||
import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews';
|
||||
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper';
|
||||
|
||||
const DEFAULT_PAGE = 1;
|
||||
const FILTER_TYPE_CONTACT = 1;
|
||||
@@ -128,6 +135,7 @@ export default {
|
||||
filterType: FILTER_TYPE_CONTACT,
|
||||
showAddSegmentsModal: false,
|
||||
showDeleteSegmentsModal: false,
|
||||
appliedFilter: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -175,7 +183,7 @@ export default {
|
||||
showContactViewPane() {
|
||||
return this.selectedContactId !== '';
|
||||
},
|
||||
wrapClas() {
|
||||
wrapClass() {
|
||||
return this.showContactViewPane ? 'medium-9' : 'medium-12';
|
||||
},
|
||||
pageParameter() {
|
||||
@@ -194,6 +202,9 @@ export default {
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
activeSegmentName() {
|
||||
return this.activeSegment?.name;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
label() {
|
||||
@@ -345,7 +356,14 @@ export default {
|
||||
});
|
||||
},
|
||||
onToggleFilters() {
|
||||
this.showFiltersModal = !this.showFiltersModal;
|
||||
if (this.hasActiveSegments) {
|
||||
this.initializeSegmentToFilterModal(this.activeSegment);
|
||||
}
|
||||
this.showFiltersModal = true;
|
||||
},
|
||||
closeAdvanceFiltersModal() {
|
||||
this.showFiltersModal = false;
|
||||
this.appliedFilter = [];
|
||||
},
|
||||
onApplyFilter(payload) {
|
||||
this.closeContactInfoPanel();
|
||||
@@ -355,10 +373,59 @@ export default {
|
||||
});
|
||||
this.showFiltersModal = false;
|
||||
},
|
||||
onUpdateSegment(payload, segmentName) {
|
||||
const payloadData = {
|
||||
...this.activeSegment,
|
||||
name: segmentName,
|
||||
query: filterQueryGenerator(payload),
|
||||
};
|
||||
this.$store.dispatch('customViews/update', payloadData);
|
||||
this.closeAdvanceFiltersModal();
|
||||
},
|
||||
clearFilters() {
|
||||
this.$store.dispatch('contacts/clearContactFilters');
|
||||
this.fetchContacts(this.pageParameter);
|
||||
},
|
||||
setParamsForEditSegmentModal() {
|
||||
// Here we are setting the params for edit segment modal to show the existing values.
|
||||
|
||||
// For custom attributes we get only attribute key.
|
||||
// So we are mapping it to find the input type of the attribute to show in the edit segment modal.
|
||||
const params = {
|
||||
countries: countries,
|
||||
filterTypes: contactFilterItems,
|
||||
allCustomAttributes: this.$store.getters[
|
||||
'attributes/getAttributesByModel'
|
||||
]('contact_attribute'),
|
||||
};
|
||||
return params;
|
||||
},
|
||||
initializeSegmentToFilterModal(activeSegment) {
|
||||
// Here we are setting the params for edit segment modal.
|
||||
// To show the existing values. when we click on edit segment button.
|
||||
|
||||
// Here we get the query from the active segment.
|
||||
// And we are mapping the query to the actual values.
|
||||
// To show in the edit segment modal by the help of generateValuesForEditCustomViews helper.
|
||||
const query = activeSegment?.query?.payload;
|
||||
if (!Array.isArray(query)) return;
|
||||
|
||||
this.appliedFilter.push(
|
||||
...query.map(filter => ({
|
||||
attribute_key: filter.attribute_key,
|
||||
attribute_model: filter.attribute_model,
|
||||
filter_operator: filter.filter_operator,
|
||||
values: Array.isArray(filter.values)
|
||||
? generateValuesForEditCustomViews(
|
||||
filter,
|
||||
this.setParamsForEditSegmentModal()
|
||||
)
|
||||
: [],
|
||||
query_operator: filter.query_operator,
|
||||
custom_attribute_type: filter.custom_attribute_type,
|
||||
}))
|
||||
);
|
||||
},
|
||||
openSavedItemInSegment() {
|
||||
const lastItemInSegments = this.segments[this.segments.length - 1];
|
||||
const lastItemId = lastItemInSegments.id;
|
||||
|
||||
@@ -27,15 +27,24 @@
|
||||
{{ $t('CONTACTS_PAGE.SEARCH_BUTTON') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<woot-button
|
||||
v-if="hasActiveSegments"
|
||||
class="margin-right-1 clear"
|
||||
color-scheme="alert"
|
||||
icon="delete"
|
||||
@click="onToggleDeleteSegmentsModal"
|
||||
>
|
||||
{{ $t('CONTACTS_PAGE.FILTER_CONTACTS_DELETE') }}
|
||||
</woot-button>
|
||||
<div v-if="hasActiveSegments">
|
||||
<woot-button
|
||||
class="margin-right-1 clear"
|
||||
color-scheme="secondary"
|
||||
icon="edit"
|
||||
@click="onToggleEditSegmentsModal"
|
||||
>
|
||||
{{ $t('CONTACTS_PAGE.FILTER_CONTACTS_EDIT') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
class="margin-right-1 clear"
|
||||
color-scheme="alert"
|
||||
icon="delete"
|
||||
@click="onToggleDeleteSegmentsModal"
|
||||
>
|
||||
{{ $t('CONTACTS_PAGE.FILTER_CONTACTS_DELETE') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<div v-if="!hasActiveSegments" class="filters__button-wrap">
|
||||
<div v-if="hasAppliedFilters" class="filters__applied-indicator" />
|
||||
<woot-button
|
||||
@@ -147,6 +156,9 @@ export default {
|
||||
onToggleSegmentsModal() {
|
||||
this.$emit('on-toggle-save-filter');
|
||||
},
|
||||
onToggleEditSegmentsModal() {
|
||||
this.$emit('on-toggle-edit-filter');
|
||||
},
|
||||
onToggleDeleteSegmentsModal() {
|
||||
this.$emit('on-toggle-delete-filter');
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user