feat: Add advanced contact filters (#3471)
Co-authored-by: Tejaswini <tejaswini@chatwoot.com> Co-authored-by: Pranav Raj S <pranav@chatwoot.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
@@ -53,6 +53,11 @@ class ContactAPI extends ApiClient {
|
||||
return axios.get(requestURL);
|
||||
}
|
||||
|
||||
filter(page = 1, sortAttr = 'name', queryPayload) {
|
||||
let requestURL = `${this.url}/filter?${buildContactParams(page, sortAttr)}`;
|
||||
return axios.post(requestURL, queryPayload);
|
||||
}
|
||||
|
||||
importContacts(file) {
|
||||
const formData = new FormData();
|
||||
formData.append('import_file', file);
|
||||
|
||||
@@ -11,6 +11,7 @@ describe('#ContactsAPI', () => {
|
||||
expect(contactAPI).toHaveProperty('update');
|
||||
expect(contactAPI).toHaveProperty('delete');
|
||||
expect(contactAPI).toHaveProperty('getConversations');
|
||||
expect(contactAPI).toHaveProperty('filter');
|
||||
});
|
||||
|
||||
describeWithAPIMock('API calls', context => {
|
||||
@@ -81,6 +82,24 @@ describe('#ContactsAPI', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
it('#filter', () => {
|
||||
const queryPayload = {
|
||||
payload: [
|
||||
{
|
||||
attribute_key: 'email',
|
||||
filter_operator: 'contains',
|
||||
values: ['fayaz'],
|
||||
query_operator: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
contactAPI.filter(1, 'name', queryPayload);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/contacts/filter?include_contact_inboxes=false&page=1&sort=name',
|
||||
queryPayload
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ export default {
|
||||
currentUserID: 'getCurrentUserID',
|
||||
activeInbox: 'getSelectedInbox',
|
||||
conversationStats: 'conversationStats/getStats',
|
||||
appliedFilters: 'getAppliedFilters',
|
||||
appliedFilters: 'getAppliedConversationFilters',
|
||||
}),
|
||||
hasAppliedFilters() {
|
||||
return this.appliedFilters.length;
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
:key="attribute.key"
|
||||
:value="attribute.key"
|
||||
>
|
||||
{{ $t(`FILTER.ATTRIBUTES.${attribute.attributeI18nKey}`) }}
|
||||
{{ attribute.name }}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
@@ -73,7 +73,6 @@
|
||||
</div>
|
||||
<woot-button
|
||||
icon="dismiss"
|
||||
icon-size="16"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
@click="removeFilter"
|
||||
@@ -49,9 +49,10 @@
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { required, requiredIf } from 'vuelidate/lib/validators';
|
||||
import FilterInputBox from './components/FilterInput.vue';
|
||||
import FilterInputBox from '../FilterInput.vue';
|
||||
import languages from './advancedFilterItems/languages';
|
||||
import countries from './advancedFilterItems/countries';
|
||||
import countries from '/app/javascript/shared/constants/countries.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -94,19 +95,18 @@ export default {
|
||||
return this.filterTypes.map(type => {
|
||||
return {
|
||||
key: type.attributeKey,
|
||||
name: type.attributeName,
|
||||
attributeI18nKey: type.attributeI18nKey,
|
||||
name: this.$t(`FILTER.ATTRIBUTES.${type.attributeI18nKey}`),
|
||||
};
|
||||
});
|
||||
},
|
||||
getAppliedFilters() {
|
||||
return this.$store.getters.getAppliedFilters;
|
||||
},
|
||||
...mapGetters({
|
||||
getAppliedConversationFilters: 'getAppliedConversationFilters',
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('campaigns/get');
|
||||
if (this.getAppliedFilters.length) {
|
||||
this.appliedFilters = this.getAppliedFilters;
|
||||
if (this.getAppliedConversationFilters.length) {
|
||||
this.appliedFilters = [...this.getAppliedConversationFilters];
|
||||
} else {
|
||||
this.appliedFilters.push({
|
||||
attribute_key: 'status',
|
||||
@@ -125,7 +125,6 @@ export default {
|
||||
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||
return type.filterOperators;
|
||||
},
|
||||
// eslint-disable-next-line consistent-return
|
||||
getDropdownValues(type) {
|
||||
const statusFilters = this.$t('CHAT_LIST.CHAT_STATUS_FILTER_ITEMS');
|
||||
switch (type) {
|
||||
@@ -169,7 +168,7 @@ export default {
|
||||
case 'country_code':
|
||||
return countries;
|
||||
default:
|
||||
break;
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
appendNewFilter() {
|
||||
@@ -190,11 +189,11 @@ export default {
|
||||
submitFilterQuery() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) return;
|
||||
this.appliedFilters[this.appliedFilters.length - 1].query_operator = null;
|
||||
this.$store.dispatch(
|
||||
'setConversationFilters',
|
||||
JSON.parse(JSON.stringify(this.appliedFilters))
|
||||
);
|
||||
this.appliedFilters[this.appliedFilters.length - 1].query_operator = null;
|
||||
this.$emit('applyFilter', this.appliedFilters);
|
||||
},
|
||||
resetFilter(index, currentFilter) {
|
||||
|
||||
@@ -1,257 +0,0 @@
|
||||
const countries = [
|
||||
{ name: 'Afghanistan', id: 'AF' },
|
||||
{ name: 'Åland Islands', id: 'AX' },
|
||||
{ name: 'Albania', id: 'AL' },
|
||||
{ name: 'Algeria', id: 'DZ' },
|
||||
{ name: 'American Samoa', id: 'AS' },
|
||||
{ name: 'Andorra', id: 'AD' },
|
||||
{ name: 'Angola', id: 'AO' },
|
||||
{ name: 'Anguilla', id: 'AI' },
|
||||
{ name: 'Antarctica', id: 'AQ' },
|
||||
{ name: 'Antigua and Barbuda', id: 'AG' },
|
||||
{ name: 'Argentina', id: 'AR' },
|
||||
{ name: 'Armenia', id: 'AM' },
|
||||
{ name: 'Aruba', id: 'AW' },
|
||||
{ name: 'Australia', id: 'AU' },
|
||||
{ name: 'Austria', id: 'AT' },
|
||||
{ name: 'Azerbaijan', id: 'AZ' },
|
||||
{ name: 'Bahamas', id: 'BS' },
|
||||
{ name: 'Bahrain', id: 'BH' },
|
||||
{ name: 'Bangladesh', id: 'BD' },
|
||||
{ name: 'Barbados', id: 'BB' },
|
||||
{ name: 'Belarus', id: 'BY' },
|
||||
{ name: 'Belgium', id: 'BE' },
|
||||
{ name: 'Belize', id: 'BZ' },
|
||||
{ name: 'Benin', id: 'BJ' },
|
||||
{ name: 'Bermuda', id: 'BM' },
|
||||
{ name: 'Bhutan', id: 'BT' },
|
||||
{ name: 'Bolivia (Plurinational State of)', id: 'BO' },
|
||||
{ name: 'Bonaire, Sint Eustatius and Saba', id: 'BQ' },
|
||||
{ name: 'Bosnia and Herzegovina', id: 'BA' },
|
||||
{ name: 'Botswana', id: 'BW' },
|
||||
{ name: 'Bouvet Island', id: 'BV' },
|
||||
{ name: 'Brazil', id: 'BR' },
|
||||
{ name: 'British Indian Ocean Territory', id: 'IO' },
|
||||
{ name: 'United States Minor Outlying Islands', id: 'UM' },
|
||||
{ name: 'Virgin Islands (British)', id: 'VG' },
|
||||
{ name: 'Virgin Islands (U.S.)', id: 'VI' },
|
||||
{ name: 'Brunei Darussalam', id: 'BN' },
|
||||
{ name: 'Bulgaria', id: 'BG' },
|
||||
{ name: 'Burkina Faso', id: 'BF' },
|
||||
{ name: 'Burundi', id: 'BI' },
|
||||
{ name: 'Cambodia', id: 'KH' },
|
||||
{ name: 'Cameroon', id: 'CM' },
|
||||
{ name: 'Canada', id: 'CA' },
|
||||
{ name: 'Cabo Verde', id: 'CV' },
|
||||
{ name: 'Cayman Islands', id: 'KY' },
|
||||
{ name: 'Central African Republic', id: 'CF' },
|
||||
{ name: 'Chad', id: 'TD' },
|
||||
{ name: 'Chile', id: 'CL' },
|
||||
{ name: 'China', id: 'CN' },
|
||||
{ name: 'Christmas Island', id: 'CX' },
|
||||
{ name: 'Cocos (Keeling) Islands', id: 'CC' },
|
||||
{ name: 'Colombia', id: 'CO' },
|
||||
{ name: 'Comoros', id: 'KM' },
|
||||
{ name: 'Congo', id: 'CG' },
|
||||
{ name: 'Congo (Democratic Republic of the)', id: 'CD' },
|
||||
{ name: 'Cook Islands', id: 'CK' },
|
||||
{ name: 'Costa Rica', id: 'CR' },
|
||||
{ name: 'Croatia', id: 'HR' },
|
||||
{ name: 'Cuba', id: 'CU' },
|
||||
{ name: 'Curaçao', id: 'CW' },
|
||||
{ name: 'Cyprus', id: 'CY' },
|
||||
{ name: 'Czech Republic', id: 'CZ' },
|
||||
{ name: 'Denmark', id: 'DK' },
|
||||
{ name: 'Djibouti', id: 'DJ' },
|
||||
{ name: 'Dominica', id: 'DM' },
|
||||
{ name: 'Dominican Republic', id: 'DO' },
|
||||
{ name: 'Ecuador', id: 'EC' },
|
||||
{ name: 'Egypt', id: 'EG' },
|
||||
{ name: 'El Salvador', id: 'SV' },
|
||||
{ name: 'Equatorial Guinea', id: 'GQ' },
|
||||
{ name: 'Eritrea', id: 'ER' },
|
||||
{ name: 'Estonia', id: 'EE' },
|
||||
{ name: 'Ethiopia', id: 'ET' },
|
||||
{ name: 'Falkland Islands (Malvinas)', id: 'FK' },
|
||||
{ name: 'Faroe Islands', id: 'FO' },
|
||||
{ name: 'Fiji', id: 'FJ' },
|
||||
{ name: 'Finland', id: 'FI' },
|
||||
{ name: 'France', id: 'FR' },
|
||||
{ name: 'French Guiana', id: 'GF' },
|
||||
{ name: 'French Polynesia', id: 'PF' },
|
||||
{ name: 'French Southern Territories', id: 'TF' },
|
||||
{ name: 'Gabon', id: 'GA' },
|
||||
{ name: 'Gambia', id: 'GM' },
|
||||
{ name: 'Georgia', id: 'GE' },
|
||||
{ name: 'Germany', id: 'DE' },
|
||||
{ name: 'Ghana', id: 'GH' },
|
||||
{ name: 'Gibraltar', id: 'GI' },
|
||||
{ name: 'Greece', id: 'GR' },
|
||||
{ name: 'Greenland', id: 'GL' },
|
||||
{ name: 'Grenada', id: 'GD' },
|
||||
{ name: 'Guadeloupe', id: 'GP' },
|
||||
{ name: 'Guam', id: 'GU' },
|
||||
{ name: 'Guatemala', id: 'GT' },
|
||||
{ name: 'Guernsey', id: 'GG' },
|
||||
{ name: 'Guinea', id: 'GN' },
|
||||
{ name: 'Guinea-Bissau', id: 'GW' },
|
||||
{ name: 'Guyana', id: 'GY' },
|
||||
{ name: 'Haiti', id: 'HT' },
|
||||
{ name: 'Heard Island and McDonald Islands', id: 'HM' },
|
||||
{ name: 'Vatican City', id: 'VA' },
|
||||
{ name: 'Honduras', id: 'HN' },
|
||||
{ name: 'Hungary', id: 'HU' },
|
||||
{ name: 'Hong Kong', id: 'HK' },
|
||||
{ name: 'Iceland', id: 'IS' },
|
||||
{ name: 'India', id: 'IN' },
|
||||
{ name: 'Indonesia', id: 'ID' },
|
||||
{ name: 'Ivory Coast', id: 'CI' },
|
||||
{ name: 'Iran (Islamic Republic of)', id: 'IR' },
|
||||
{ name: 'Iraq', id: 'IQ' },
|
||||
{ name: 'Ireland', id: 'IE' },
|
||||
{ name: 'Isle of Man', id: 'IM' },
|
||||
{ name: 'Israel', id: 'IL' },
|
||||
{ name: 'Italy', id: 'IT' },
|
||||
{ name: 'Jamaica', id: 'JM' },
|
||||
{ name: 'Japan', id: 'JP' },
|
||||
{ name: 'Jersey', id: 'JE' },
|
||||
{ name: 'Jordan', id: 'JO' },
|
||||
{ name: 'Kazakhstan', id: 'KZ' },
|
||||
{ name: 'Kenya', id: 'KE' },
|
||||
{ name: 'Kiribati', id: 'KI' },
|
||||
{ name: 'Kuwait', id: 'KW' },
|
||||
{ name: 'Kyrgyzstan', id: 'KG' },
|
||||
{ name: "Lao People's Democratic Republic", id: 'LA' },
|
||||
{ name: 'Latvia', id: 'LV' },
|
||||
{ name: 'Lebanon', id: 'LB' },
|
||||
{ name: 'Lesotho', id: 'LS' },
|
||||
{ name: 'Liberia', id: 'LR' },
|
||||
{ name: 'Libya', id: 'LY' },
|
||||
{ name: 'Liechtenstein', id: 'LI' },
|
||||
{ name: 'Lithuania', id: 'LT' },
|
||||
{ name: 'Luxembourg', id: 'LU' },
|
||||
{ name: 'Macao', id: 'MO' },
|
||||
{ name: 'North Macedonia', id: 'MK' },
|
||||
{ name: 'Madagascar', id: 'MG' },
|
||||
{ name: 'Malawi', id: 'MW' },
|
||||
{ name: 'Malaysia', id: 'MY' },
|
||||
{ name: 'Maldives', id: 'MV' },
|
||||
{ name: 'Mali', id: 'ML' },
|
||||
{ name: 'Malta', id: 'MT' },
|
||||
{ name: 'Marshall Islands', id: 'MH' },
|
||||
{ name: 'Martinique', id: 'MQ' },
|
||||
{ name: 'Mauritania', id: 'MR' },
|
||||
{ name: 'Mauritius', id: 'MU' },
|
||||
{ name: 'Mayotte', id: 'YT' },
|
||||
{ name: 'Mexico', id: 'MX' },
|
||||
{ name: 'Micronesia (Federated States of)', id: 'FM' },
|
||||
{ name: 'Moldova (Republic of)', id: 'MD' },
|
||||
{ name: 'Monaco', id: 'MC' },
|
||||
{ name: 'Mongolia', id: 'MN' },
|
||||
{ name: 'Montenegro', id: 'ME' },
|
||||
{ name: 'Montserrat', id: 'MS' },
|
||||
{ name: 'Morocco', id: 'MA' },
|
||||
{ name: 'Mozambique', id: 'MZ' },
|
||||
{ name: 'Myanmar', id: 'MM' },
|
||||
{ name: 'Namibia', id: 'NA' },
|
||||
{ name: 'Nauru', id: 'NR' },
|
||||
{ name: 'Nepal', id: 'NP' },
|
||||
{ name: 'Netherlands', id: 'NL' },
|
||||
{ name: 'New Caledonia', id: 'NC' },
|
||||
{ name: 'New Zealand', id: 'NZ' },
|
||||
{ name: 'Nicaragua', id: 'NI' },
|
||||
{ name: 'Niger', id: 'NE' },
|
||||
{ name: 'Nigeria', id: 'NG' },
|
||||
{ name: 'Niue', id: 'NU' },
|
||||
{ name: 'Norfolk Island', id: 'NF' },
|
||||
{ name: "Korea (Democratic People's Republic of)", id: 'KP' },
|
||||
{ name: 'Northern Mariana Islands', id: 'MP' },
|
||||
{ name: 'Norway', id: 'NO' },
|
||||
{ name: 'Oman', id: 'OM' },
|
||||
{ name: 'Pakistan', id: 'PK' },
|
||||
{ name: 'Palau', id: 'PW' },
|
||||
{ name: 'Palestine, State of', id: 'PS' },
|
||||
{ name: 'Panama', id: 'PA' },
|
||||
{ name: 'Papua New Guinea', id: 'PG' },
|
||||
{ name: 'Paraguay', id: 'PY' },
|
||||
{ name: 'Peru', id: 'PE' },
|
||||
{ name: 'Philippines', id: 'PH' },
|
||||
{ name: 'Pitcairn', id: 'PN' },
|
||||
{ name: 'Poland', id: 'PL' },
|
||||
{ name: 'Portugal', id: 'PT' },
|
||||
{ name: 'Puerto Rico', id: 'PR' },
|
||||
{ name: 'Qatar', id: 'QA' },
|
||||
{ name: 'Republic of Kosovo', id: 'XK' },
|
||||
{ name: 'Réunion', id: 'RE' },
|
||||
{ name: 'Romania', id: 'RO' },
|
||||
{ name: 'Russian Federation', id: 'RU' },
|
||||
{ name: 'Rwanda', id: 'RW' },
|
||||
{ name: 'Saint Barthélemy', id: 'BL' },
|
||||
{ name: 'Saint Helena, Ascension and Tristan da Cunha', id: 'SH' },
|
||||
{ name: 'Saint Kitts and Nevis', id: 'KN' },
|
||||
{ name: 'Saint Lucia', id: 'LC' },
|
||||
{ name: 'Saint Martin (French part)', id: 'MF' },
|
||||
{ name: 'Saint Pierre and Miquelon', id: 'PM' },
|
||||
{ name: 'Saint Vincent and the Grenadines', id: 'VC' },
|
||||
{ name: 'Samoa', id: 'WS' },
|
||||
{ name: 'San Marino', id: 'SM' },
|
||||
{ name: 'Sao Tome and Principe', id: 'ST' },
|
||||
{ name: 'Saudi Arabia', id: 'SA' },
|
||||
{ name: 'Senegal', id: 'SN' },
|
||||
{ name: 'Serbia', id: 'RS' },
|
||||
{ name: 'Seychelles', id: 'SC' },
|
||||
{ name: 'Sierra Leone', id: 'SL' },
|
||||
{ name: 'Singapore', id: 'SG' },
|
||||
{ name: 'Sint Maarten (Dutch part)', id: 'SX' },
|
||||
{ name: 'Slovakia', id: 'SK' },
|
||||
{ name: 'Slovenia', id: 'SI' },
|
||||
{ name: 'Solomon Islands', id: 'SB' },
|
||||
{ name: 'Somalia', id: 'SO' },
|
||||
{ name: 'South Africa', id: 'ZA' },
|
||||
{ name: 'South Georgia and the South Sandwich Islands', id: 'GS' },
|
||||
{ name: 'Korea (Republic of)', id: 'KR' },
|
||||
{ name: 'Spain', id: 'ES' },
|
||||
{ name: 'Sri Lanka', id: 'LK' },
|
||||
{ name: 'Sudan', id: 'SD' },
|
||||
{ name: 'South Sudan', id: 'SS' },
|
||||
{ name: 'Suriname', id: 'SR' },
|
||||
{ name: 'Svalbard and Jan Mayen', id: 'SJ' },
|
||||
{ name: 'Swaziland', id: 'SZ' },
|
||||
{ name: 'Sweden', id: 'SE' },
|
||||
{ name: 'Switzerland', id: 'CH' },
|
||||
{ name: 'Syrian Arab Republic', id: 'SY' },
|
||||
{ name: 'Taiwan', id: 'TW' },
|
||||
{ name: 'Tajikistan', id: 'TJ' },
|
||||
{ name: 'Tanzania, United Republic of', id: 'TZ' },
|
||||
{ name: 'Thailand', id: 'TH' },
|
||||
{ name: 'Timor-Leste', id: 'TL' },
|
||||
{ name: 'Togo', id: 'TG' },
|
||||
{ name: 'Tokelau', id: 'TK' },
|
||||
{ name: 'Tonga', id: 'TO' },
|
||||
{ name: 'Trinidad and Tobago', id: 'TT' },
|
||||
{ name: 'Tunisia', id: 'TN' },
|
||||
{ name: 'Turkey', id: 'TR' },
|
||||
{ name: 'Turkmenistan', id: 'TM' },
|
||||
{ name: 'Turks and Caicos Islands', id: 'TC' },
|
||||
{ name: 'Tuvalu', id: 'TV' },
|
||||
{ name: 'Uganda', id: 'UG' },
|
||||
{ name: 'Ukraine', id: 'UA' },
|
||||
{ name: 'United Arab Emirates', id: 'AE' },
|
||||
{
|
||||
name: 'United Kingdom of Great Britain and Northern Ireland',
|
||||
id: 'GB',
|
||||
},
|
||||
{ name: 'United States of America', id: 'US' },
|
||||
{ name: 'Uruguay', id: 'UY' },
|
||||
{ name: 'Uzbekistan', id: 'UZ' },
|
||||
{ name: 'Vanuatu', id: 'VU' },
|
||||
{ name: 'Venezuela (Bolivarian Republic of)', id: 'VE' },
|
||||
{ name: 'Vietnam', id: 'VN' },
|
||||
{ name: 'Wallis and Futuna', id: 'WF' },
|
||||
{ name: 'Western Sahara', id: 'EH' },
|
||||
{ name: 'Yemen', id: 'YE' },
|
||||
{ name: 'Zambia', id: 'ZM' },
|
||||
{ name: 'Zimbabwe', id: 'ZW' },
|
||||
];
|
||||
|
||||
export default countries;
|
||||
@@ -176,6 +176,7 @@
|
||||
"FIELDS": "Contact fields",
|
||||
"SEARCH_BUTTON": "Search",
|
||||
"SEARCH_INPUT_PLACEHOLDER": "Search for contacts",
|
||||
"FILTER_CONTACTS": "Filter",
|
||||
"LIST": {
|
||||
"LOADING_MESSAGE": "Loading contacts...",
|
||||
"404": "No contacts matches your search 🔍",
|
||||
|
||||
34
app/javascript/dashboard/i18n/locale/en/contactFilters.json
Normal file
34
app/javascript/dashboard/i18n/locale/en/contactFilters.json
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"CONTACTS_FILTER": {
|
||||
"TITLE": "Filter Contacts",
|
||||
"SUBTITLE": "Add filters below and hit 'Submit' to filter contacts.",
|
||||
"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",
|
||||
"CANCEL_BUTTON_LABEL": "Cancel",
|
||||
"CLEAR_BUTTON_LABEL": "Clear Filters",
|
||||
"EMPTY_VALUE_ERROR": "Value is required",
|
||||
"TOOLTIP_LABEL": "Filter contacts",
|
||||
"QUERY_DROPDOWN_LABELS": {
|
||||
"AND": "AND",
|
||||
"OR": "OR"
|
||||
},
|
||||
"OPERATOR_LABELS": {
|
||||
"equal_to": "Equal to",
|
||||
"not_equal_to": "Not equal to",
|
||||
"contains": "Contains",
|
||||
"does_not_contain": "Does not contain",
|
||||
"is_present": "Is present",
|
||||
"is_not_present": "Is not present"
|
||||
},
|
||||
"ATTRIBUTES": {
|
||||
"NAME": "Name",
|
||||
"EMAIL": "Email",
|
||||
"PHONE_NUMBER": "Phone number",
|
||||
"IDENTIFIER": "Identifier",
|
||||
"CITY": "City",
|
||||
"COUNTRY": "Country"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { default as _signup } from './signup.json';
|
||||
import { default as _teamsSettings } from './teamsSettings.json';
|
||||
import { default as _advancedFilters } from './advancedFilters.json';
|
||||
import { default as _automation } from './automation.json';
|
||||
import { default as _contactFilters } from './contactFilters.json';
|
||||
|
||||
export default {
|
||||
..._agentMgmt,
|
||||
@@ -44,4 +45,5 @@ export default {
|
||||
..._teamsSettings,
|
||||
..._advancedFilters,
|
||||
..._automation,
|
||||
..._contactFilters,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<div class="column">
|
||||
<woot-modal-header :header-title="$t('CONTACTS_FILTER.TITLE')">
|
||||
<p>{{ $t('CONTACTS_FILTER.SUBTITLE') }}</p>
|
||||
</woot-modal-header>
|
||||
<div class="row modal-content">
|
||||
<div class="medium-12 columns filters-wrap">
|
||||
<filter-input-box
|
||||
v-for="(filter, i) in appliedFilters"
|
||||
:key="i"
|
||||
v-model="appliedFilters[i]"
|
||||
:filter-attributes="filterAttributes"
|
||||
:input-type="getInputType(appliedFilters[i].attribute_key)"
|
||||
:operators="getOperators(appliedFilters[i].attribute_key)"
|
||||
:dropdown-values="getDropdownValues(appliedFilters[i].attribute_key)"
|
||||
:show-query-operator="i !== appliedFilters.length - 1"
|
||||
:show-user-input="showUserInput(appliedFilters[i].filter_operator)"
|
||||
:v="$v.appliedFilters.$each[i]"
|
||||
@resetFilter="resetFilter(i, appliedFilters[i])"
|
||||
@removeFilter="removeFilter(i)"
|
||||
/>
|
||||
<div class="filter-actions">
|
||||
<woot-button
|
||||
icon="add"
|
||||
color-scheme="success"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="appendNewFilter"
|
||||
>
|
||||
{{ $t('CONTACTS_FILTER.ADD_NEW_FILTER') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
v-if="hasAppliedFilters"
|
||||
icon="subtract"
|
||||
color-scheme="alert"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="clearFilters"
|
||||
>
|
||||
{{ $t('CONTACTS_FILTER.CLEAR_ALL_FILTERS') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="medium-12 columns">
|
||||
<div class="modal-footer justify-content-end w-full">
|
||||
<woot-button class="button clear" @click.prevent="onClose">
|
||||
{{ $t('CONTACTS_FILTER.CANCEL_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
<woot-button @click="submitFilterQuery">
|
||||
{{ $t('CONTACTS_FILTER.SUBMIT_BUTTON_LABEL') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import FilterInputBox from '../../../../components/widgets/FilterInput.vue';
|
||||
import countries from '/app/javascript/shared/constants/countries.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FilterInputBox,
|
||||
},
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
onClose: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
filterTypes: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
validations: {
|
||||
appliedFilters: {
|
||||
required,
|
||||
$each: {
|
||||
values: {
|
||||
required,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
appliedFilters: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filterAttributes() {
|
||||
return this.filterTypes.map(type => {
|
||||
return {
|
||||
key: type.attributeKey,
|
||||
name: this.$t(`CONTACTS_FILTER.ATTRIBUTES.${type.attributeI18nKey}`),
|
||||
};
|
||||
});
|
||||
},
|
||||
...mapGetters({
|
||||
getAppliedContactFilters: 'contacts/getAppliedContactFilters',
|
||||
}),
|
||||
hasAppliedFilters() {
|
||||
return this.getAppliedContactFilters.length;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (this.getAppliedContactFilters.length) {
|
||||
this.appliedFilters = [...this.getAppliedContactFilters];
|
||||
} else {
|
||||
this.appliedFilters.push({
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getInputType(key) {
|
||||
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||
return type.inputType;
|
||||
},
|
||||
getOperators(key) {
|
||||
const type = this.filterTypes.find(filter => filter.attributeKey === key);
|
||||
return type.filterOperators;
|
||||
},
|
||||
getDropdownValues(type) {
|
||||
switch (type) {
|
||||
case 'country_code':
|
||||
return countries;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
appendNewFilter() {
|
||||
this.appliedFilters.push({
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
values: '',
|
||||
query_operator: 'and',
|
||||
});
|
||||
},
|
||||
removeFilter(index) {
|
||||
if (this.appliedFilters.length <= 1) {
|
||||
this.showAlert(this.$t('CONTACTS_FILTER.FILTER_DELETE_ERROR'));
|
||||
} else {
|
||||
this.appliedFilters.splice(index, 1);
|
||||
}
|
||||
},
|
||||
submitFilterQuery() {
|
||||
this.$v.$touch();
|
||||
if (this.$v.$invalid) return;
|
||||
this.$store.dispatch(
|
||||
'contacts/setContactFilters',
|
||||
JSON.parse(JSON.stringify(this.appliedFilters))
|
||||
);
|
||||
this.appliedFilters[this.appliedFilters.length - 1].query_operator = null;
|
||||
this.$emit('applyFilter', this.appliedFilters);
|
||||
},
|
||||
resetFilter(index, currentFilter) {
|
||||
this.appliedFilters[index].filter_operator = this.filterTypes.find(
|
||||
filter => filter.attributeKey === currentFilter.attribute_key
|
||||
).filterOperators[0].value;
|
||||
this.appliedFilters[index].values = '';
|
||||
},
|
||||
showUserInput(operatorType) {
|
||||
if (operatorType === 'is_present' || operatorType === 'is_not_present')
|
||||
return false;
|
||||
return true;
|
||||
},
|
||||
clearFilters() {
|
||||
this.$emit('clearFilters');
|
||||
this.onClose();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.filters-wrap {
|
||||
padding: var(--space-normal);
|
||||
border-radius: var(--border-radius-large);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background-light);
|
||||
margin-bottom: var(--space-normal);
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
margin-top: var(--space-normal);
|
||||
}
|
||||
</style>
|
||||
@@ -8,6 +8,7 @@
|
||||
:on-input-search="onInputSearch"
|
||||
:on-toggle-create="onToggleCreate"
|
||||
:on-toggle-import="onToggleImport"
|
||||
:on-toggle-filter="onToggleFilters"
|
||||
:header-title="label"
|
||||
/>
|
||||
<contacts-table
|
||||
@@ -34,6 +35,19 @@
|
||||
<woot-modal :show.sync="showImportModal" :on-close="onToggleImport">
|
||||
<import-contacts v-if="showImportModal" :on-close="onToggleImport" />
|
||||
</woot-modal>
|
||||
<woot-modal
|
||||
:show.sync="showFiltersModal"
|
||||
:on-close="onToggleFilters"
|
||||
size="medium"
|
||||
>
|
||||
<contacts-advanced-filters
|
||||
v-if="showFiltersModal"
|
||||
:on-close="onToggleFilters"
|
||||
:filter-types="contactFilterItems"
|
||||
@applyFilter="onApplyFilter"
|
||||
@clearFilters="clearFilters"
|
||||
/>
|
||||
</woot-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -46,6 +60,9 @@ import ContactInfoPanel from './ContactInfoPanel';
|
||||
import CreateContact from 'dashboard/routes/dashboard/conversation/contact/CreateContact';
|
||||
import TableFooter from 'dashboard/components/widgets/TableFooter';
|
||||
import ImportContacts from './ImportContacts.vue';
|
||||
import ContactsAdvancedFilters from './ContactsAdvancedFilters.vue';
|
||||
import contactFilterItems from '../contactFilterItems';
|
||||
import filterQueryGenerator from '../../../../helper/filterQueryGenerator';
|
||||
|
||||
const DEFAULT_PAGE = 1;
|
||||
|
||||
@@ -57,6 +74,7 @@ export default {
|
||||
ContactInfoPanel,
|
||||
CreateContact,
|
||||
ImportContacts,
|
||||
ContactsAdvancedFilters,
|
||||
},
|
||||
props: {
|
||||
label: { type: String, default: '' },
|
||||
@@ -68,6 +86,13 @@ export default {
|
||||
showImportModal: false,
|
||||
selectedContactId: '',
|
||||
sortConfig: { name: 'asc' },
|
||||
showFiltersModal: false,
|
||||
contactFilterItems: contactFilterItems.map(filter => ({
|
||||
...filter,
|
||||
attributeName: this.$t(
|
||||
`CONTACTS_FILTER.ATTRIBUTES.${filter.attributeI18nKey}`
|
||||
),
|
||||
})),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -133,7 +158,7 @@ export default {
|
||||
fetchContacts(page) {
|
||||
this.updatePageParam(page);
|
||||
let value = '';
|
||||
if(this.searchQuery.charAt(0) === '+') {
|
||||
if (this.searchQuery.charAt(0) === '+') {
|
||||
value = this.searchQuery.substring(1);
|
||||
} else {
|
||||
value = this.searchQuery;
|
||||
@@ -188,6 +213,20 @@ export default {
|
||||
this.sortConfig = params;
|
||||
this.fetchContacts(this.meta.currentPage);
|
||||
},
|
||||
onToggleFilters() {
|
||||
this.showFiltersModal = !this.showFiltersModal;
|
||||
},
|
||||
onApplyFilter(payload) {
|
||||
this.closeContactInfoPanel();
|
||||
this.$store.dispatch('contacts/filter', {
|
||||
queryPayload: filterQueryGenerator(payload),
|
||||
});
|
||||
this.showFiltersModal = false;
|
||||
},
|
||||
clearFilters() {
|
||||
this.$store.dispatch('contacts/clearContactFilters');
|
||||
this.fetchContacts(this.pageParameter);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -19,17 +19,29 @@
|
||||
/>
|
||||
<woot-button
|
||||
:is-loading="false"
|
||||
class="clear"
|
||||
:class-names="searchButtonClass"
|
||||
@click="onSearchSubmit"
|
||||
>
|
||||
{{ $t('CONTACTS_PAGE.SEARCH_BUTTON') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
|
||||
<div class="filters__button-wrap">
|
||||
<div v-if="hasAppliedFilters" class="filters__applied-indicator" />
|
||||
<woot-button
|
||||
class="margin-right-small clear"
|
||||
color-scheme="secondary"
|
||||
data-testid="create-new-contact"
|
||||
icon="filter"
|
||||
@click="onToggleFilter"
|
||||
>
|
||||
{{ $t('CONTACTS_PAGE.FILTER_CONTACTS') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
<woot-button
|
||||
class="margin-right-small clear"
|
||||
color-scheme="success"
|
||||
icon="add-circle"
|
||||
class="margin-right-small"
|
||||
icon="person-add"
|
||||
data-testid="create-new-contact"
|
||||
@click="onToggleCreate"
|
||||
>
|
||||
@@ -38,7 +50,8 @@
|
||||
|
||||
<woot-button
|
||||
color-scheme="info"
|
||||
icon="cloud-backup"
|
||||
icon="upload"
|
||||
class="clear"
|
||||
@click="onToggleImport"
|
||||
>
|
||||
{{ $t('IMPORT_CONTACTS.BUTTON_LABEL') }}
|
||||
@@ -49,6 +62,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
headerTitle: {
|
||||
@@ -75,6 +90,10 @@ export default {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
onToggleFilter: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -86,6 +105,12 @@ export default {
|
||||
searchButtonClass() {
|
||||
return this.searchQuery !== '' ? 'show' : '';
|
||||
},
|
||||
...mapGetters({
|
||||
getAppliedContactFilters: 'contacts/getAppliedContactFilters',
|
||||
}),
|
||||
hasAppliedFilters() {
|
||||
return this.getAppliedContactFilters.length;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -155,4 +180,17 @@ export default {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.filters__button-wrap {
|
||||
position: relative;
|
||||
|
||||
.filters__applied-indicator {
|
||||
position: absolute;
|
||||
height: var(--space-small);
|
||||
width: var(--space-small);
|
||||
top: var(--space-smaller);
|
||||
right: var(--space-slab);
|
||||
background-color: var(--s-500);
|
||||
border-radius: var(--border-radius-rounded);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
const filterTypes = [
|
||||
{
|
||||
attributeKey: 'name',
|
||||
attributeI18nKey: 'NAME',
|
||||
inputType: 'plain_text',
|
||||
dataType: 'text',
|
||||
filterOperators: [
|
||||
{
|
||||
value: 'equal_to',
|
||||
label: 'Equal to',
|
||||
},
|
||||
{
|
||||
value: 'not_equal_to',
|
||||
label: 'Not equal to',
|
||||
},
|
||||
{
|
||||
value: 'contains',
|
||||
label: 'Contains',
|
||||
},
|
||||
{
|
||||
value: 'does_not_contain',
|
||||
label: 'Does not contain',
|
||||
},
|
||||
],
|
||||
attribute_type: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: 'email',
|
||||
attributeI18nKey: 'EMAIL',
|
||||
inputType: 'plain_text',
|
||||
dataType: 'text',
|
||||
filterOperators: [
|
||||
{
|
||||
value: 'equal_to',
|
||||
label: 'Equal to',
|
||||
},
|
||||
{
|
||||
value: 'not_equal_to',
|
||||
label: 'Not equal to',
|
||||
},
|
||||
{
|
||||
value: 'contains',
|
||||
label: 'Contains',
|
||||
},
|
||||
{
|
||||
value: 'does_not_contain',
|
||||
label: 'Does not contain',
|
||||
},
|
||||
],
|
||||
attribute_type: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: 'phone_number',
|
||||
attributeI18nKey: 'PHONE_NUMBER',
|
||||
inputType: 'plain_text',
|
||||
dataType: 'text',
|
||||
filterOperators: [
|
||||
{
|
||||
value: 'equal_to',
|
||||
label: 'Equal to',
|
||||
},
|
||||
{
|
||||
value: 'not_equal_to',
|
||||
label: 'Not equal to',
|
||||
},
|
||||
{
|
||||
value: 'contains',
|
||||
label: 'Contains',
|
||||
},
|
||||
{
|
||||
value: 'does_not_contain',
|
||||
label: 'Does not contain',
|
||||
},
|
||||
],
|
||||
attribute_type: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: 'identifier',
|
||||
attributeI18nKey: 'IDENTIFIER',
|
||||
inputType: 'plain_text',
|
||||
dataType: 'number',
|
||||
filterOperators: [
|
||||
{
|
||||
value: 'equal_to',
|
||||
label: 'Equal to',
|
||||
},
|
||||
{
|
||||
value: 'not_equal_to',
|
||||
label: 'Not equal to',
|
||||
},
|
||||
],
|
||||
attribute_type: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: 'country_code',
|
||||
attributeI18nKey: 'COUNTRY',
|
||||
inputType: 'search_select',
|
||||
dataType: 'number',
|
||||
filterOperators: [
|
||||
{
|
||||
value: 'equal_to',
|
||||
label: 'Equal to',
|
||||
},
|
||||
{
|
||||
value: 'not_equal_to',
|
||||
label: 'Not equal to',
|
||||
},
|
||||
],
|
||||
attribute_type: 'standard',
|
||||
},
|
||||
{
|
||||
attributeKey: 'city',
|
||||
attributeI18nKey: 'CITY',
|
||||
inputType: 'plain_text',
|
||||
dataType: 'Number',
|
||||
filterOperators: [
|
||||
{
|
||||
value: 'equal_to',
|
||||
label: 'Equal to',
|
||||
},
|
||||
{
|
||||
value: 'not_equal_to',
|
||||
label: 'Not equal to',
|
||||
},
|
||||
{
|
||||
value: 'contains',
|
||||
label: 'Contains',
|
||||
},
|
||||
{
|
||||
value: 'does_not_contain',
|
||||
label: 'Does not contain',
|
||||
},
|
||||
],
|
||||
attribute_type: 'standard',
|
||||
},
|
||||
];
|
||||
|
||||
export default filterTypes;
|
||||
@@ -179,4 +179,27 @@ export const actions = {
|
||||
commit(types.SET_CONTACT_UI_FLAG, { isUpdating: false });
|
||||
}
|
||||
},
|
||||
|
||||
filter: async ({ commit }, { page = 1, sortAttr, queryPayload } = {}) => {
|
||||
commit(types.SET_CONTACT_UI_FLAG, { isFetching: true });
|
||||
try {
|
||||
const {
|
||||
data: { payload, meta },
|
||||
} = await ContactAPI.filter(page, sortAttr, queryPayload);
|
||||
commit(types.CLEAR_CONTACTS);
|
||||
commit(types.SET_CONTACTS, payload);
|
||||
commit(types.SET_CONTACT_META, meta);
|
||||
commit(types.SET_CONTACT_UI_FLAG, { isFetching: false });
|
||||
} catch (error) {
|
||||
commit(types.SET_CONTACT_UI_FLAG, { isFetching: false });
|
||||
}
|
||||
},
|
||||
|
||||
setContactFilters({ commit }, data) {
|
||||
commit(types.SET_CONTACT_FILTERS, data);
|
||||
},
|
||||
|
||||
clearContactFilters({ commit }) {
|
||||
commit(types.CLEAR_CONTACT_FILTERS);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,4 +12,7 @@ export const getters = {
|
||||
getMeta: $state => {
|
||||
return $state.meta;
|
||||
},
|
||||
getAppliedContactFilters: _state => {
|
||||
return _state.appliedFilters;
|
||||
},
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ const state = {
|
||||
isDeleting: false,
|
||||
},
|
||||
sortOrder: [],
|
||||
appliedFilters: [],
|
||||
};
|
||||
|
||||
export default {
|
||||
|
||||
@@ -66,4 +66,12 @@ export const mutations = {
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
[types.SET_CONTACT_FILTERS](_state, data) {
|
||||
_state.appliedFilters = data;
|
||||
},
|
||||
|
||||
[types.CLEAR_CONTACT_FILTERS](_state) {
|
||||
_state.appliedFilters = [];
|
||||
},
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ const getters = {
|
||||
return isChatMine;
|
||||
});
|
||||
},
|
||||
getAppliedFilters: _state => {
|
||||
getAppliedConversationFilters: _state => {
|
||||
return _state.appliedFilters;
|
||||
},
|
||||
getUnAssignedChats: _state => activeFilters => {
|
||||
|
||||
@@ -6,9 +6,21 @@ import {
|
||||
DuplicateContactException,
|
||||
ExceptionWithMessage,
|
||||
} from '../../../../../shared/helpers/CustomErrors';
|
||||
import { filterApiResponse } from './filterApiResponse';
|
||||
|
||||
const { actions } = Contacts;
|
||||
|
||||
const filterQueryData = {
|
||||
payload: [
|
||||
{
|
||||
attribute_key: 'email',
|
||||
filter_operator: 'contains',
|
||||
values: ['fayaz'],
|
||||
query_operator: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const commit = jest.fn();
|
||||
global.axios = axios;
|
||||
jest.mock('axios');
|
||||
@@ -228,7 +240,7 @@ describe('#actions', () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('#deleteCustomAttributes', () => {
|
||||
describe('#deleteCustomAttributes', () => {
|
||||
it('sends correct mutations if API is success', async () => {
|
||||
axios.post.mockResolvedValue({ data: { payload: contactList[0] } });
|
||||
await actions.deleteCustomAttributes(
|
||||
@@ -247,4 +259,43 @@ describe('#actions', () => {
|
||||
).rejects.toThrow(Error);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#fetchFilteredContacts', () => {
|
||||
it('fetches filtered conversations with a mock commit', async () => {
|
||||
axios.post.mockResolvedValue({
|
||||
data: filterApiResponse,
|
||||
});
|
||||
await actions.filter({ commit }, filterQueryData);
|
||||
expect(commit).toHaveBeenCalledTimes(5);
|
||||
expect(commit.mock.calls).toEqual([
|
||||
['SET_CONTACT_UI_FLAG', { isFetching: true }],
|
||||
['CLEAR_CONTACTS'],
|
||||
['SET_CONTACTS', filterApiResponse.payload],
|
||||
['SET_CONTACT_META', filterApiResponse.meta],
|
||||
['SET_CONTACT_UI_FLAG', { isFetching: false }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#setContactsFilter', () => {
|
||||
it('commits the correct mutation and sets filter state', () => {
|
||||
const filters = [
|
||||
{
|
||||
attribute_key: 'email',
|
||||
filter_operator: 'contains',
|
||||
values: ['fayaz'],
|
||||
query_operator: 'and',
|
||||
},
|
||||
];
|
||||
actions.setContactFilters({ commit }, filters);
|
||||
expect(commit.mock.calls).toEqual([[types.SET_CONTACT_FILTERS, filters]]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#clearContactFilters', () => {
|
||||
it('commits the correct mutation and clears filter state', () => {
|
||||
actions.clearContactFilters({ commit });
|
||||
expect(commit.mock.calls).toEqual([[types.CLEAR_CONTACT_FILTERS]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
export const filterApiResponse = {
|
||||
meta: {
|
||||
count: {
|
||||
all_count: 2,
|
||||
},
|
||||
current_page: '1',
|
||||
},
|
||||
payload: [
|
||||
{
|
||||
additional_attributes: {},
|
||||
availability_status: 'offline',
|
||||
email: 'fayaz@g.com',
|
||||
id: 8,
|
||||
name: 'fayaz',
|
||||
phone_number: null,
|
||||
identifier: null,
|
||||
thumbnail:
|
||||
'https://www.gravatar.com/avatar/f2e86d3a78353cdf51002f44cf6ea846?d=404',
|
||||
custom_attributes: {},
|
||||
conversations_count: 1,
|
||||
last_activity_at: 1631081845,
|
||||
},
|
||||
{
|
||||
additional_attributes: {},
|
||||
availability_status: 'offline',
|
||||
email: 'fayaz@gma.com',
|
||||
id: 9,
|
||||
name: 'fayaz',
|
||||
phone_number: null,
|
||||
identifier: null,
|
||||
thumbnail:
|
||||
'https://www.gravatar.com/avatar/792af86e3ad4591552e1025a6415baa6?d=404',
|
||||
custom_attributes: {},
|
||||
conversations_count: 1,
|
||||
last_activity_at: 1631614585,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -36,4 +36,18 @@ describe('#getters', () => {
|
||||
isUpdating: false,
|
||||
});
|
||||
});
|
||||
it('getAppliedContactFilters', () => {
|
||||
const filters = [
|
||||
{
|
||||
attribute_key: 'email',
|
||||
filter_operator: 'contains',
|
||||
values: 'a',
|
||||
query_operator: null,
|
||||
},
|
||||
];
|
||||
const state = {
|
||||
appliedFilters: filters,
|
||||
};
|
||||
expect(getters.getAppliedContactFilters(state)).toEqual(filters);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -64,4 +64,42 @@ describe('#mutations', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#SET_CONTACT_FILTERS', () => {
|
||||
it('set contact filter', () => {
|
||||
const appliedFilters = [
|
||||
{
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['fayaz'],
|
||||
query_operator: 'and',
|
||||
},
|
||||
];
|
||||
mutations[types.SET_CONTACT_FILTERS](appliedFilters);
|
||||
expect(appliedFilters).toEqual([
|
||||
{
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['fayaz'],
|
||||
query_operator: 'and',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
describe('#CLEAR_CONTACT_FILTERS', () => {
|
||||
it('clears applied contact filters', () => {
|
||||
const state = {
|
||||
appliedFilters: [
|
||||
{
|
||||
attribute_key: 'name',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['fayaz'],
|
||||
query_operator: 'and',
|
||||
},
|
||||
],
|
||||
};
|
||||
mutations[types.CLEAR_CONTACT_FILTERS](state);
|
||||
expect(state.appliedFilters).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,8 +115,8 @@ describe('#getters', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getAppliedFilters', () => {
|
||||
it('getAppliedFilters', () => {
|
||||
describe('#getAppliedConversationFilters', () => {
|
||||
it('getAppliedConversationFilters', () => {
|
||||
const filtersList = [
|
||||
{
|
||||
attribute_key: 'status',
|
||||
@@ -128,7 +128,7 @@ describe('#getters', () => {
|
||||
const state = {
|
||||
appliedFilters: filtersList,
|
||||
};
|
||||
expect(getters.getAppliedFilters(state)).toEqual(filtersList);
|
||||
expect(getters.getAppliedConversationFilters(state)).toEqual(filtersList);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,6 +108,8 @@ export default {
|
||||
EDIT_CONTACT: 'EDIT_CONTACT',
|
||||
DELETE_CONTACT: 'DELETE_CONTACT',
|
||||
UPDATE_CONTACTS_PRESENCE: 'UPDATE_CONTACTS_PRESENCE',
|
||||
SET_CONTACT_FILTERS: 'SET_CONTACT_FILTERS',
|
||||
CLEAR_CONTACT_FILTERS: 'CLEAR_CONTACT_FILTERS',
|
||||
|
||||
// Notifications
|
||||
SET_NOTIFICATIONS_META: 'SET_NOTIFICATIONS_META',
|
||||
|
||||
Reference in New Issue
Block a user