Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
084a470f3b | ||
|
|
3b5f5b41ad | ||
|
|
fe5670832a | ||
|
|
f9b330fa3f | ||
|
|
462064bfc0 | ||
|
|
7db7ae0ed8 |
@@ -4,6 +4,7 @@ import { mapGetters } from 'vuex';
|
||||
import { useUISettings } from 'dashboard/composables/useUISettings';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { useKeyboardEvents } from 'dashboard/composables/useKeyboardEvents';
|
||||
import { useFilter } from 'shared/composables/useFilter';
|
||||
import VirtualList from 'vue-virtual-scroll-list';
|
||||
|
||||
import ChatListHeader from './ChatListHeader.vue';
|
||||
@@ -16,7 +17,6 @@ import filterQueryGenerator from '../helper/filterQueryGenerator.js';
|
||||
import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews.vue';
|
||||
import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews.vue';
|
||||
import ConversationBulkActions from './widgets/conversation/conversationBulkActions/Index.vue';
|
||||
import filterMixin from 'shared/mixins/filterMixin';
|
||||
import languages from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||
import countries from 'shared/constants/countries';
|
||||
import { generateValuesForEditCustomViews } from 'dashboard/helper/customViewsHelper';
|
||||
@@ -41,7 +41,6 @@ export default {
|
||||
IntersectionObserver,
|
||||
VirtualList,
|
||||
},
|
||||
mixins: [filterMixin],
|
||||
provide() {
|
||||
return {
|
||||
// Actions to be performed on virtual list item and context menu.
|
||||
@@ -91,6 +90,15 @@ export default {
|
||||
|
||||
const conversationListRef = ref(null);
|
||||
|
||||
const {
|
||||
setFilterAttributes,
|
||||
initializeStatusAndAssigneeFilterToModal,
|
||||
initializeInboxTeamAndLabelFilterToModal,
|
||||
} = useFilter({
|
||||
filteri18nKey: 'FILTER',
|
||||
attributeModel: 'conversation_attribute',
|
||||
});
|
||||
|
||||
const getKeyboardListenerParams = () => {
|
||||
const allConversations = conversationListRef.value.querySelectorAll(
|
||||
'div.conversations-list div.conversation'
|
||||
@@ -146,6 +154,9 @@ export default {
|
||||
return {
|
||||
uiSettings,
|
||||
conversationListRef,
|
||||
setFilterAttributes,
|
||||
initializeStatusAndAssigneeFilterToModal,
|
||||
initializeInboxTeamAndLabelFilterToModal,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
@@ -860,6 +871,25 @@ export default {
|
||||
onContextMenuToggle(state) {
|
||||
this.isContextMenuOpen = state;
|
||||
},
|
||||
initializeExistingFilterToModal() {
|
||||
const statusFilter = this.initializeStatusAndAssigneeFilterToModal(
|
||||
this.activeStatus,
|
||||
this.currentUserDetails,
|
||||
this.activeAssigneeTab
|
||||
);
|
||||
if (statusFilter) {
|
||||
this.appliedFilter.push(statusFilter);
|
||||
}
|
||||
|
||||
const otherFilters = this.initializeInboxTeamAndLabelFilterToModal(
|
||||
this.conversationInbox,
|
||||
this.inbox,
|
||||
this.teamId,
|
||||
this.activeTeam,
|
||||
this.label
|
||||
);
|
||||
this.appliedFilter.push(...otherFilters);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
+15
-3
@@ -5,7 +5,7 @@ import languages from './advancedFilterItems/languages';
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { filterAttributeGroups } from './advancedFilterItems';
|
||||
import filterMixin from 'shared/mixins/filterMixin';
|
||||
import { useFilter } from 'shared/composables/useFilter';
|
||||
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
|
||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import { validateConversationOrContactFilters } from 'dashboard/helper/validations.js';
|
||||
@@ -14,7 +14,6 @@ export default {
|
||||
components: {
|
||||
FilterInputBox,
|
||||
},
|
||||
mixins: [filterMixin],
|
||||
props: {
|
||||
onClose: {
|
||||
type: Function,
|
||||
@@ -37,6 +36,15 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { setFilterAttributes } = useFilter({
|
||||
filteri18nKey: 'FILTER',
|
||||
attributeModel: 'conversation_attribute',
|
||||
});
|
||||
return {
|
||||
setFilterAttributes,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
@@ -67,7 +75,11 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setFilterAttributes();
|
||||
const { filterGroups, filterTypes } = this.setFilterAttributes();
|
||||
|
||||
this.filterTypes = [...this.filterTypes, ...filterTypes];
|
||||
this.filterGroups = filterGroups;
|
||||
|
||||
this.$store.dispatch('campaigns/get');
|
||||
if (this.getAppliedConversationFilters.length) {
|
||||
this.appliedFilters = [];
|
||||
|
||||
+14
-3
@@ -4,7 +4,7 @@ import FilterInputBox from '../../../../components/widgets/FilterInput/Index.vue
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { filterAttributeGroups } from '../contactFilterItems';
|
||||
import filterMixin from 'shared/mixins/filterMixin';
|
||||
import { useFilter } from 'shared/composables/useFilter';
|
||||
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
|
||||
import { CONTACTS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
import { validateConversationOrContactFilters } from 'dashboard/helper/validations.js';
|
||||
@@ -13,7 +13,6 @@ export default {
|
||||
components: {
|
||||
FilterInputBox,
|
||||
},
|
||||
mixins: [filterMixin],
|
||||
props: {
|
||||
onClose: {
|
||||
type: Function,
|
||||
@@ -36,6 +35,15 @@ export default {
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { setFilterAttributes } = useFilter({
|
||||
filteri18nKey: 'CONTACTS_FILTER',
|
||||
attributeModel: 'contact_attribute',
|
||||
});
|
||||
return {
|
||||
setFilterAttributes,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
@@ -69,7 +77,10 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.setFilterAttributes();
|
||||
const { filterGroups, filterTypes } = this.setFilterAttributes();
|
||||
this.filterTypes = [...this.filterTypes, ...filterTypes];
|
||||
this.filterGroups = filterGroups;
|
||||
|
||||
if (this.getAppliedContactFilters.length) {
|
||||
this.appliedFilters = [...this.getAppliedContactFilters];
|
||||
} else if (!this.isSegmentsView) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useVuelidate } from '@vuelidate/core';
|
||||
import { required } from '@vuelidate/validators';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
import campaignMixin from 'shared/mixins/campaignMixin';
|
||||
import { useCampaign } from 'shared/composables/useCampaign';
|
||||
import WootDateTimePicker from 'dashboard/components/ui/DateTimePicker.vue';
|
||||
import { URLPattern } from 'urlpattern-polyfill';
|
||||
import { CAMPAIGNS_EVENTS } from '../../../../helper/AnalyticsHelper/events';
|
||||
@@ -14,9 +14,9 @@ export default {
|
||||
WootDateTimePicker,
|
||||
WootMessageEditor,
|
||||
},
|
||||
mixins: [campaignMixin],
|
||||
setup() {
|
||||
return { v$: useVuelidate() };
|
||||
const { campaignType, isOngoingType, isOneOffType } = useCampaign();
|
||||
return { v$: useVuelidate(), campaignType, isOngoingType, isOneOffType };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import campaignMixin from 'shared/mixins/campaignMixin';
|
||||
import { useCampaign } from 'shared/composables/useCampaign';
|
||||
import CampaignsTable from './CampaignsTable.vue';
|
||||
import EditCampaign from './EditCampaign.vue';
|
||||
export default {
|
||||
@@ -9,13 +9,16 @@ export default {
|
||||
CampaignsTable,
|
||||
EditCampaign,
|
||||
},
|
||||
mixins: [campaignMixin],
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { campaignType } = useCampaign();
|
||||
return { campaignType };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showEditPopup: false,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
import campaignMixin from 'shared/mixins/campaignMixin';
|
||||
import { useCampaign } from 'shared/composables/useCampaign';
|
||||
import CampaignCard from './CampaignCard.vue';
|
||||
|
||||
export default {
|
||||
@@ -10,9 +10,6 @@ export default {
|
||||
Spinner,
|
||||
CampaignCard,
|
||||
},
|
||||
|
||||
mixins: [campaignMixin],
|
||||
|
||||
props: {
|
||||
campaigns: {
|
||||
type: Array,
|
||||
@@ -27,7 +24,10 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { isOngoingType } = useCampaign();
|
||||
return { isOngoingType };
|
||||
},
|
||||
computed: {
|
||||
currentInboxId() {
|
||||
return this.$route.params.inboxId;
|
||||
|
||||
@@ -4,14 +4,13 @@ import { useVuelidate } from '@vuelidate/core';
|
||||
import { required } from '@vuelidate/validators';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
import campaignMixin from 'shared/mixins/campaignMixin';
|
||||
import { useCampaign } from 'shared/composables/useCampaign';
|
||||
import { URLPattern } from 'urlpattern-polyfill';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
WootMessageEditor,
|
||||
},
|
||||
mixins: [campaignMixin],
|
||||
props: {
|
||||
selectedCampaign: {
|
||||
type: Object,
|
||||
@@ -19,7 +18,8 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return { v$: useVuelidate() };
|
||||
const { isOngoingType } = useCampaign();
|
||||
return { v$: useVuelidate(), isOngoingType };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import campaignMixin from 'shared/mixins/campaignMixin';
|
||||
import { useCampaign } from 'shared/composables/useCampaign';
|
||||
import Campaign from './Campaign.vue';
|
||||
import AddCampaign from './AddCampaign.vue';
|
||||
|
||||
@@ -8,7 +8,10 @@ export default {
|
||||
Campaign,
|
||||
AddCampaign,
|
||||
},
|
||||
mixins: [campaignMixin],
|
||||
setup() {
|
||||
const { isOngoingType } = useCampaign();
|
||||
return { isOngoingType };
|
||||
},
|
||||
data() {
|
||||
return { showAddPopup: false };
|
||||
},
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { useCampaign } from '../useCampaign';
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { CAMPAIGN_TYPES } from '../../constants/campaign';
|
||||
|
||||
// Mock the useRoute composable
|
||||
vi.mock('dashboard/composables/route', () => ({
|
||||
useRoute: vi.fn(),
|
||||
}));
|
||||
|
||||
describe('useCampaign', () => {
|
||||
it('returns the correct campaign type for ongoing campaigns', () => {
|
||||
useRoute.mockReturnValue({ name: 'ongoing_campaigns' });
|
||||
const { campaignType } = useCampaign();
|
||||
expect(campaignType.value).toBe(CAMPAIGN_TYPES.ONGOING);
|
||||
});
|
||||
|
||||
it('returns the correct campaign type for one-off campaigns', () => {
|
||||
useRoute.mockReturnValue({ name: 'one_off' });
|
||||
const { campaignType } = useCampaign();
|
||||
expect(campaignType.value).toBe(CAMPAIGN_TYPES.ONE_OFF);
|
||||
});
|
||||
|
||||
it('isOngoingType returns true for ongoing campaigns', () => {
|
||||
useRoute.mockReturnValue({ name: 'ongoing_campaigns' });
|
||||
const { isOngoingType } = useCampaign();
|
||||
expect(isOngoingType.value).toBe(true);
|
||||
});
|
||||
|
||||
it('isOngoingType returns false for one-off campaigns', () => {
|
||||
useRoute.mockReturnValue({ name: 'one_off' });
|
||||
const { isOngoingType } = useCampaign();
|
||||
expect(isOngoingType.value).toBe(false);
|
||||
});
|
||||
|
||||
it('isOneOffType returns true for one-off campaigns', () => {
|
||||
useRoute.mockReturnValue({ name: 'one_off' });
|
||||
const { isOneOffType } = useCampaign();
|
||||
expect(isOneOffType.value).toBe(true);
|
||||
});
|
||||
|
||||
it('isOneOffType returns false for ongoing campaigns', () => {
|
||||
useRoute.mockReturnValue({ name: 'ongoing_campaigns' });
|
||||
const { isOneOffType } = useCampaign();
|
||||
expect(isOneOffType.value).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,145 @@
|
||||
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
||||
import { useFilter } from '../useFilter';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
|
||||
// Mock the dependencies
|
||||
vi.mock('dashboard/composables/store');
|
||||
vi.mock('dashboard/composables/useI18n');
|
||||
|
||||
describe('useFilter', () => {
|
||||
// Setup mocks
|
||||
beforeEach(() => {
|
||||
vi.mocked(useStore).mockReturnValue({
|
||||
getters: {
|
||||
'attributes/getAttributesByModel': vi.fn(),
|
||||
},
|
||||
});
|
||||
vi.mocked(useI18n).mockReturnValue({
|
||||
t: vi.fn(key => key),
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the correct functions', () => {
|
||||
const {
|
||||
setFilterAttributes,
|
||||
initializeStatusAndAssigneeFilterToModal,
|
||||
initializeInboxTeamAndLabelFilterToModal,
|
||||
} = useFilter({ filteri18nKey: 'TEST', attributeModel: 'conversation' });
|
||||
|
||||
expect(setFilterAttributes).toBeDefined();
|
||||
expect(initializeStatusAndAssigneeFilterToModal).toBeDefined();
|
||||
expect(initializeInboxTeamAndLabelFilterToModal).toBeDefined();
|
||||
});
|
||||
|
||||
describe('setFilterAttributes', () => {
|
||||
it('should return filterGroups and filterTypes', () => {
|
||||
const mockAttributes = [
|
||||
{
|
||||
attribute_key: 'test_key',
|
||||
attribute_display_name: 'Test Name',
|
||||
attribute_display_type: 'text',
|
||||
},
|
||||
];
|
||||
vi.mocked(useStore)().getters[
|
||||
'attributes/getAttributesByModel'
|
||||
].mockReturnValue(mockAttributes);
|
||||
|
||||
const { setFilterAttributes } = useFilter({
|
||||
filteri18nKey: 'TEST',
|
||||
attributeModel: 'conversation',
|
||||
});
|
||||
const result = setFilterAttributes();
|
||||
|
||||
expect(result).toHaveProperty('filterGroups');
|
||||
expect(result).toHaveProperty('filterTypes');
|
||||
expect(result.filterGroups.length).toBeGreaterThan(0);
|
||||
expect(result.filterTypes.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeStatusAndAssigneeFilterToModal', () => {
|
||||
it('should return status filter when activeStatus is provided', () => {
|
||||
const { initializeStatusAndAssigneeFilterToModal } = useFilter({
|
||||
filteri18nKey: 'TEST',
|
||||
attributeModel: 'conversation',
|
||||
});
|
||||
const result = initializeStatusAndAssigneeFilterToModal('open', {}, '');
|
||||
|
||||
expect(result).toEqual({
|
||||
attribute_key: 'status',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
{ id: 'open', name: 'CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.open.TEXT' },
|
||||
],
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
});
|
||||
|
||||
it('should return null when no active filters', () => {
|
||||
const { initializeStatusAndAssigneeFilterToModal } = useFilter({
|
||||
filteri18nKey: 'TEST',
|
||||
attributeModel: 'conversation',
|
||||
});
|
||||
const result = initializeStatusAndAssigneeFilterToModal('', {}, '');
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeInboxTeamAndLabelFilterToModal', () => {
|
||||
it('should return filters for inbox, team, and label when provided', () => {
|
||||
const { initializeInboxTeamAndLabelFilterToModal } = useFilter({
|
||||
filteri18nKey: 'TEST',
|
||||
attributeModel: 'conversation',
|
||||
});
|
||||
const result = initializeInboxTeamAndLabelFilterToModal(
|
||||
1,
|
||||
{ name: 'Inbox 1' },
|
||||
2,
|
||||
[{ id: 2, name: 'Team 2' }],
|
||||
'Label 1'
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(3);
|
||||
expect(result[0]).toHaveProperty('attribute_key', 'inbox_id');
|
||||
expect(result[1]).toHaveProperty('attribute_key', 'team_id');
|
||||
expect(result[2]).toHaveProperty('attribute_key', 'labels');
|
||||
});
|
||||
|
||||
it('should return empty array when no filters are provided', () => {
|
||||
const { initializeInboxTeamAndLabelFilterToModal } = useFilter({
|
||||
filteri18nKey: 'TEST',
|
||||
attributeModel: 'conversation',
|
||||
});
|
||||
const result = initializeInboxTeamAndLabelFilterToModal(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return only inbox filter when only inbox is provided', () => {
|
||||
const { initializeInboxTeamAndLabelFilterToModal } = useFilter({
|
||||
filteri18nKey: 'TEST',
|
||||
attributeModel: 'conversation',
|
||||
});
|
||||
const result = initializeInboxTeamAndLabelFilterToModal(
|
||||
1,
|
||||
{ name: 'Inbox 1' },
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0]).toHaveProperty('attribute_key', 'inbox_id');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'dashboard/composables/route';
|
||||
import { CAMPAIGN_TYPES } from '../constants/campaign';
|
||||
|
||||
/**
|
||||
* Composable to manage campaign types.
|
||||
*
|
||||
* @returns {Object} - Computed properties for campaign type and its checks.
|
||||
*/
|
||||
export const useCampaign = () => {
|
||||
const route = useRoute();
|
||||
|
||||
/**
|
||||
* Computed property to determine the current campaign type based on the route name.
|
||||
*/
|
||||
const campaignType = computed(() => {
|
||||
const campaignTypeMap = {
|
||||
ongoing_campaigns: CAMPAIGN_TYPES.ONGOING,
|
||||
one_off: CAMPAIGN_TYPES.ONE_OFF,
|
||||
};
|
||||
return campaignTypeMap[route.name];
|
||||
});
|
||||
|
||||
/**
|
||||
* Computed property to check if the current campaign type is 'ongoing'.
|
||||
*/
|
||||
const isOngoingType = computed(() => {
|
||||
return campaignType.value === CAMPAIGN_TYPES.ONGOING;
|
||||
});
|
||||
|
||||
/**
|
||||
* Computed property to check if the current campaign type is 'one-off'.
|
||||
*/
|
||||
const isOneOffType = computed(() => {
|
||||
return campaignType.value === CAMPAIGN_TYPES.ONE_OFF;
|
||||
});
|
||||
|
||||
return {
|
||||
campaignType,
|
||||
isOngoingType,
|
||||
isOneOffType,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,175 @@
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { useStore } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'dashboard/composables/useI18n';
|
||||
import { filterAttributeGroups } from 'dashboard/components/widgets/conversation/advancedFilterItems';
|
||||
import * as OPERATORS from 'dashboard/components/widgets/FilterInput/FilterOperatorTypes.js';
|
||||
|
||||
const customAttributeInputType = key => {
|
||||
switch (key) {
|
||||
case 'date':
|
||||
return 'date';
|
||||
case 'text':
|
||||
return 'plain_text';
|
||||
case 'list':
|
||||
return 'search_select';
|
||||
case 'checkbox':
|
||||
return 'search_select';
|
||||
default:
|
||||
return 'plain_text';
|
||||
}
|
||||
};
|
||||
|
||||
const getOperatorTypes = key => {
|
||||
switch (key) {
|
||||
case 'list':
|
||||
return OPERATORS.OPERATOR_TYPES_1;
|
||||
case 'text':
|
||||
return OPERATORS.OPERATOR_TYPES_3;
|
||||
case 'number':
|
||||
return OPERATORS.OPERATOR_TYPES_1;
|
||||
case 'link':
|
||||
return OPERATORS.OPERATOR_TYPES_1;
|
||||
case 'date':
|
||||
return OPERATORS.OPERATOR_TYPES_4;
|
||||
case 'checkbox':
|
||||
return OPERATORS.OPERATOR_TYPES_1;
|
||||
default:
|
||||
return OPERATORS.OPERATOR_TYPES_1;
|
||||
}
|
||||
};
|
||||
|
||||
export const useFilter = ({ filteri18nKey, attributeModel }) => {
|
||||
const { t: $t } = useI18n();
|
||||
const { getters } = useStore();
|
||||
|
||||
const setFilterAttributes = () => {
|
||||
const allCustomAttributes =
|
||||
getters['attributes/getAttributesByModel'](attributeModel);
|
||||
|
||||
const customAttributesFormatted = {
|
||||
name: $t(`${filteri18nKey}.GROUPS.CUSTOM_ATTRIBUTES`),
|
||||
attributes: allCustomAttributes.map(attr => {
|
||||
return {
|
||||
key: attr.attribute_key,
|
||||
name: attr.attribute_display_name,
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
const allFilterGroups = filterAttributeGroups.map(group => {
|
||||
return {
|
||||
name: $t(`${filteri18nKey}.GROUPS.${group.i18nGroup}`),
|
||||
attributes: group.attributes.map(attribute => {
|
||||
return {
|
||||
key: attribute.key,
|
||||
name: $t(`${filteri18nKey}.ATTRIBUTES.${attribute.i18nKey}`),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const customAttributeTypes = allCustomAttributes.map(attr => {
|
||||
return {
|
||||
attributeKey: attr.attribute_key,
|
||||
attributeI18nKey: `CUSTOM_ATTRIBUTE_${attr.attribute_display_type.toUpperCase()}`,
|
||||
inputType: customAttributeInputType(attr.attribute_display_type),
|
||||
filterOperators: getOperatorTypes(attr.attribute_display_type),
|
||||
attributeModel: 'custom_attributes',
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
filterGroups: [...allFilterGroups, customAttributesFormatted],
|
||||
filterTypes: [...customAttributeTypes],
|
||||
};
|
||||
};
|
||||
|
||||
const initializeStatusAndAssigneeFilterToModal = (
|
||||
activeStatus,
|
||||
currentUserDetails,
|
||||
activeAssigneeTab
|
||||
) => {
|
||||
if (activeStatus !== '') {
|
||||
return {
|
||||
attribute_key: 'status',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
{
|
||||
id: activeStatus,
|
||||
name: $t(`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${activeStatus}.TEXT`),
|
||||
},
|
||||
],
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
};
|
||||
}
|
||||
if (activeAssigneeTab === wootConstants.ASSIGNEE_TYPE.ME) {
|
||||
return {
|
||||
attribute_key: 'assignee_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: currentUserDetails,
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const initializeInboxTeamAndLabelFilterToModal = (
|
||||
conversationInbox,
|
||||
inbox,
|
||||
teamId,
|
||||
activeTeam,
|
||||
label
|
||||
) => {
|
||||
const filters = [];
|
||||
if (conversationInbox) {
|
||||
filters.push({
|
||||
attribute_key: 'inbox_id',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
{
|
||||
id: conversationInbox,
|
||||
name: inbox.name,
|
||||
},
|
||||
],
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
}
|
||||
if (teamId) {
|
||||
filters.push({
|
||||
attribute_key: 'team_id',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: activeTeam,
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
}
|
||||
if (label) {
|
||||
filters.push({
|
||||
attribute_key: 'labels',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
{
|
||||
id: label,
|
||||
name: label,
|
||||
},
|
||||
],
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
}
|
||||
return filters;
|
||||
};
|
||||
|
||||
return {
|
||||
setFilterAttributes,
|
||||
initializeStatusAndAssigneeFilterToModal,
|
||||
initializeInboxTeamAndLabelFilterToModal,
|
||||
};
|
||||
};
|
||||
@@ -1,19 +0,0 @@
|
||||
import { CAMPAIGN_TYPES } from '../constants/campaign';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
campaignType() {
|
||||
const campaignTypeMap = {
|
||||
ongoing_campaigns: CAMPAIGN_TYPES.ONGOING,
|
||||
one_off: CAMPAIGN_TYPES.ONE_OFF,
|
||||
};
|
||||
return campaignTypeMap[this.$route.name];
|
||||
},
|
||||
isOngoingType() {
|
||||
return this.campaignType === CAMPAIGN_TYPES.ONGOING;
|
||||
},
|
||||
isOneOffType() {
|
||||
return this.campaignType === CAMPAIGN_TYPES.ONE_OFF;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,119 +0,0 @@
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
|
||||
export default {
|
||||
methods: {
|
||||
setFilterAttributes() {
|
||||
const allCustomAttributes = this.$store.getters[
|
||||
'attributes/getAttributesByModel'
|
||||
](this.attributeModel);
|
||||
const customAttributesFormatted = {
|
||||
name: this.$t(`${this.filtersFori18n}.GROUPS.CUSTOM_ATTRIBUTES`),
|
||||
attributes: allCustomAttributes.map(attr => {
|
||||
return {
|
||||
key: attr.attribute_key,
|
||||
name: attr.attribute_display_name,
|
||||
};
|
||||
}),
|
||||
};
|
||||
const allFilterGroups = this.filterAttributeGroups.map(group => {
|
||||
return {
|
||||
name: this.$t(`${this.filtersFori18n}.GROUPS.${group.i18nGroup}`),
|
||||
attributes: group.attributes.map(attribute => {
|
||||
return {
|
||||
key: attribute.key,
|
||||
name: this.$t(
|
||||
`${this.filtersFori18n}.ATTRIBUTES.${attribute.i18nKey}`
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
const customAttributeTypes = allCustomAttributes.map(attr => {
|
||||
return {
|
||||
attributeKey: attr.attribute_key,
|
||||
attributeI18nKey: `CUSTOM_ATTRIBUTE_${attr.attribute_display_type.toUpperCase()}`,
|
||||
inputType: this.customAttributeInputType(attr.attribute_display_type),
|
||||
filterOperators: this.getOperatorTypes(attr.attribute_display_type),
|
||||
attributeModel: 'custom_attributes',
|
||||
};
|
||||
});
|
||||
this.filterTypes = [...this.filterTypes, ...customAttributeTypes];
|
||||
this.filterGroups = [...allFilterGroups, customAttributesFormatted];
|
||||
},
|
||||
|
||||
initializeExistingFilterToModal() {
|
||||
this.initializeStatusAndAssigneeFilterToModal();
|
||||
this.initializeInboxTeamAndLabelFilterToModal();
|
||||
},
|
||||
initializeStatusAndAssigneeFilterToModal() {
|
||||
if (this.activeStatus !== '') {
|
||||
this.appliedFilter.push({
|
||||
attribute_key: 'status',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
{
|
||||
id: this.activeStatus,
|
||||
name: this.$t(
|
||||
`CHAT_LIST.CHAT_STATUS_FILTER_ITEMS.${this.activeStatus}.TEXT`
|
||||
),
|
||||
},
|
||||
],
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
}
|
||||
if (this.activeAssigneeTab === wootConstants.ASSIGNEE_TYPE.ME) {
|
||||
this.appliedFilter.push({
|
||||
attribute_key: 'assignee_id',
|
||||
filter_operator: 'equal_to',
|
||||
values: this.currentUserDetails,
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
}
|
||||
},
|
||||
initializeInboxTeamAndLabelFilterToModal() {
|
||||
if (this.conversationInbox) {
|
||||
this.appliedFilter.push({
|
||||
attribute_key: 'inbox_id',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
{
|
||||
id: this.conversationInbox,
|
||||
name: this.inbox.name,
|
||||
},
|
||||
],
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
}
|
||||
if (this.teamId) {
|
||||
this.appliedFilter.push({
|
||||
attribute_key: 'team_id',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: this.activeTeam,
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
}
|
||||
if (this.label) {
|
||||
this.appliedFilter.push({
|
||||
attribute_key: 'labels',
|
||||
attribute_model: 'standard',
|
||||
filter_operator: 'equal_to',
|
||||
values: [
|
||||
{
|
||||
id: this.label,
|
||||
name: this.label,
|
||||
},
|
||||
],
|
||||
query_operator: 'and',
|
||||
custom_attribute_type: '',
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,34 +0,0 @@
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import campaignMixin from '../campaignMixin';
|
||||
|
||||
const buildComponent = routeName => ({
|
||||
template: '<div></div>',
|
||||
computed: {
|
||||
$route() {
|
||||
return { name: routeName };
|
||||
},
|
||||
},
|
||||
mixins: [campaignMixin],
|
||||
});
|
||||
|
||||
describe('campaignMixin', () => {
|
||||
beforeEach(() => {
|
||||
global.window = Object.create(window);
|
||||
});
|
||||
it('returns the correct campaign type', () => {
|
||||
const Component = buildComponent('one_off');
|
||||
const wrapper = shallowMount(Component);
|
||||
expect(wrapper.vm.campaignType).toBe('one_off');
|
||||
});
|
||||
it('isOneOffType returns true if path is one_off', () => {
|
||||
const Component = buildComponent('one_off');
|
||||
const wrapper = shallowMount(Component);
|
||||
expect(wrapper.vm.isOneOffType).toBe(true);
|
||||
});
|
||||
|
||||
it('isOngoingType returns true if path is ongoing_campaigns', () => {
|
||||
const Component = buildComponent('ongoing_campaigns');
|
||||
const wrapper = shallowMount(Component);
|
||||
expect(wrapper.vm.isOngoingType).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -1,17 +0,0 @@
|
||||
import filterMixin from '../filterMixin';
|
||||
import { shallowMount } from '@vue/test-utils';
|
||||
import MockComponent from './MockComponent.vue';
|
||||
|
||||
describe('Test mixin function', () => {
|
||||
const wrapper = shallowMount(MockComponent, {
|
||||
mixins: [filterMixin],
|
||||
});
|
||||
|
||||
it('should return proper value from bool', () => {
|
||||
expect(wrapper.vm.setFilterAttributes).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return proper value from bool', () => {
|
||||
expect(wrapper.vm.initializeExistingFilterToModal).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,7 @@ import { mapGetters, mapActions } from 'vuex';
|
||||
import { setHeader } from 'widget/helpers/axios';
|
||||
import addHours from 'date-fns/addHours';
|
||||
import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||
import configMixin from './mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
import { getLocale } from './helpers/urlParamsHelper';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
@@ -27,7 +27,13 @@ export default {
|
||||
components: {
|
||||
Spinner,
|
||||
},
|
||||
mixins: [availabilityMixin, configMixin, routerMixin, darkModeMixin],
|
||||
mixins: [availabilityMixin, routerMixin, darkModeMixin],
|
||||
setup() {
|
||||
const { shouldShowPreChatForm } = useConfig();
|
||||
return {
|
||||
shouldShowPreChatForm,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isMobile: false,
|
||||
|
||||
@@ -8,8 +8,8 @@ import VideoBubble from 'widget/components/VideoBubble.vue';
|
||||
import FileBubble from 'widget/components/FileBubble.vue';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||
import configMixin from '../mixins/configMixin';
|
||||
import messageMixin from '../mixins/messageMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
import { useMessage } from '../composables/useMessage';
|
||||
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
import ReplyToChip from 'widget/components/ReplyToChip.vue';
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
MessageReplyButton,
|
||||
ReplyToChip,
|
||||
},
|
||||
mixins: [configMixin, messageMixin, darkModeMixin],
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
message: {
|
||||
type: Object,
|
||||
@@ -39,6 +39,19 @@ export default {
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { messageContentAttributes, hasAttachments } = useMessage(
|
||||
props.message
|
||||
);
|
||||
const { useInboxAvatarForBot, inboxAvatarUrl, channelConfig } = useConfig();
|
||||
return {
|
||||
messageContentAttributes,
|
||||
hasAttachments,
|
||||
useInboxAvatarForBot,
|
||||
inboxAvatarUrl,
|
||||
channelConfig,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
hasImageError: false,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mapGetters } from 'vuex';
|
||||
|
||||
import ChatAttachmentButton from 'widget/components/ChatAttachment.vue';
|
||||
import ChatSendButton from 'widget/components/ChatSendButton.vue';
|
||||
import configMixin from '../mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin.js';
|
||||
@@ -19,7 +19,7 @@ export default {
|
||||
FluentIcon,
|
||||
ResizableTextArea,
|
||||
},
|
||||
mixins: [configMixin, darkModeMixin],
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
onSendMessage: {
|
||||
type: Function,
|
||||
@@ -30,7 +30,13 @@ export default {
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { hasEmojiPickerEnabled, hasAttachmentsEnabled } = useConfig();
|
||||
return {
|
||||
hasEmojiPickerEnabled,
|
||||
hasAttachmentsEnabled,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
userInput: '',
|
||||
|
||||
@@ -4,13 +4,13 @@ import { IFrameHelper, RNHelper } from 'widget/helpers/utils';
|
||||
import { popoutChatWindow } from '../helpers/popoutHelper';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
import { CONVERSATION_STATUS } from 'shared/constants/messages';
|
||||
|
||||
export default {
|
||||
name: 'HeaderActions',
|
||||
components: { FluentIcon },
|
||||
mixins: [configMixin, darkModeMixin],
|
||||
mixins: [darkModeMixin],
|
||||
props: {
|
||||
showPopoutButton: {
|
||||
type: Boolean,
|
||||
@@ -21,6 +21,12 @@ export default {
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { hasEndConversationEnabled } = useConfig();
|
||||
return {
|
||||
hasEndConversationEnabled,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
conversationAttributes: 'conversationAttributes/getConversationParams',
|
||||
|
||||
@@ -8,14 +8,14 @@ import { getRegexp } from 'shared/helpers/Validators';
|
||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomButton,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [routerMixin, darkModeMixin, configMixin],
|
||||
mixins: [routerMixin, darkModeMixin, messageFormatterMixin],
|
||||
props: {
|
||||
options: {
|
||||
type: Object,
|
||||
@@ -23,8 +23,10 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { preChatFormEnabled } = useConfig();
|
||||
const { formatMessage } = useMessageFormatter();
|
||||
return {
|
||||
preChatFormEnabled,
|
||||
formatMessage,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mapGetters } from 'vuex';
|
||||
import { getContrastingTextColor } from '@chatwoot/utils';
|
||||
import nextAvailabilityTime from 'widget/mixins/nextAvailabilityTime';
|
||||
import AvailableAgents from 'widget/components/AvailableAgents.vue';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
import availabilityMixin from 'widget/mixins/availability';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import { IFrameHelper } from 'widget/helpers/utils';
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
AvailableAgents,
|
||||
FluentIcon,
|
||||
},
|
||||
mixins: [configMixin, nextAvailabilityTime, availabilityMixin],
|
||||
mixins: [nextAvailabilityTime, availabilityMixin],
|
||||
props: {
|
||||
availableAgents: {
|
||||
type: Array,
|
||||
@@ -26,7 +26,12 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
setup() {
|
||||
const { channelConfig } = useConfig();
|
||||
return {
|
||||
channelConfig,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script>
|
||||
import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import configMixin from '../mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
import {
|
||||
ON_CAMPAIGN_MESSAGE_CLICK,
|
||||
@@ -11,7 +11,7 @@ import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
export default {
|
||||
name: 'UnreadMessage',
|
||||
components: { Thumbnail },
|
||||
mixins: [configMixin, darkModeMixin],
|
||||
mixins: [messageFormatterMixin, darkModeMixin],
|
||||
props: {
|
||||
message: {
|
||||
type: String,
|
||||
@@ -31,8 +31,12 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { useInboxAvatarForBot, inboxAvatarUrl, channelConfig } = useConfig();
|
||||
const { formatMessage } = useMessageFormatter();
|
||||
return {
|
||||
useInboxAvatarForBot,
|
||||
inboxAvatarUrl,
|
||||
channelConfig,
|
||||
formatMessage,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import configMixin from '../mixins/configMixin';
|
||||
import { ON_UNREAD_MESSAGE_CLICK } from '../constants/widgetBusEvents';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import UnreadMessage from 'widget/components/UnreadMessage.vue';
|
||||
@@ -12,7 +11,6 @@ export default {
|
||||
FluentIcon,
|
||||
UnreadMessage,
|
||||
},
|
||||
mixins: [configMixin],
|
||||
props: {
|
||||
messages: {
|
||||
type: Array,
|
||||
|
||||
@@ -3,7 +3,7 @@ import Banner from '../Banner.vue';
|
||||
import Branding from 'shared/components/Branding.vue';
|
||||
import ChatHeader from '../ChatHeader.vue';
|
||||
import ChatHeaderExpanded from '../ChatHeaderExpanded.vue';
|
||||
import configMixin from '../../mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { IFrameHelper } from 'widget/helpers/utils';
|
||||
|
||||
@@ -14,7 +14,12 @@ export default {
|
||||
ChatHeader,
|
||||
ChatHeaderExpanded,
|
||||
},
|
||||
mixins: [configMixin],
|
||||
setup() {
|
||||
const { channelConfig } = useConfig();
|
||||
return {
|
||||
channelConfig,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showPopoutButton: false,
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { useConfig } from '../useConfig';
|
||||
|
||||
describe('useConfig composable', () => {
|
||||
beforeEach(() => {
|
||||
global.window.chatwootWebChannel = {
|
||||
avatarUrl: 'https://test.url',
|
||||
hasAConnectedAgentBot: 'AgentBot',
|
||||
enabledFeatures: [
|
||||
'emoji_picker',
|
||||
'attachments',
|
||||
'end_conversation',
|
||||
'use_inbox_avatar_for_bot',
|
||||
],
|
||||
preChatFormOptions: {
|
||||
pre_chat_message: '',
|
||||
pre_chat_fields: [
|
||||
{
|
||||
label: 'Email Id',
|
||||
name: 'emailAddress',
|
||||
type: 'email',
|
||||
field_type: 'standard',
|
||||
required: false,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: 'Full name',
|
||||
name: 'fullName',
|
||||
type: 'text',
|
||||
field_type: 'standard',
|
||||
required: true,
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
preChatFormEnabled: true,
|
||||
};
|
||||
});
|
||||
|
||||
it('should return correct values for computed properties', () => {
|
||||
const {
|
||||
useInboxAvatarForBot,
|
||||
hasAConnectedAgentBot,
|
||||
inboxAvatarUrl,
|
||||
channelConfig,
|
||||
hasEmojiPickerEnabled,
|
||||
hasAttachmentsEnabled,
|
||||
hasEndConversationEnabled,
|
||||
preChatFormEnabled,
|
||||
preChatFormOptions,
|
||||
shouldShowPreChatForm,
|
||||
} = useConfig();
|
||||
|
||||
expect(useInboxAvatarForBot.value).toBe(true);
|
||||
expect(hasAConnectedAgentBot.value).toBe(true);
|
||||
expect(inboxAvatarUrl.value).toBe('https://test.url');
|
||||
|
||||
expect(channelConfig.value).toEqual({
|
||||
avatarUrl: 'https://test.url',
|
||||
hasAConnectedAgentBot: 'AgentBot',
|
||||
enabledFeatures: [
|
||||
'emoji_picker',
|
||||
'attachments',
|
||||
'end_conversation',
|
||||
'use_inbox_avatar_for_bot',
|
||||
],
|
||||
preChatFormOptions: {
|
||||
pre_chat_message: '',
|
||||
pre_chat_fields: [
|
||||
{
|
||||
label: 'Email Id',
|
||||
name: 'emailAddress',
|
||||
type: 'email',
|
||||
field_type: 'standard',
|
||||
required: false,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: 'Full name',
|
||||
name: 'fullName',
|
||||
type: 'text',
|
||||
field_type: 'standard',
|
||||
required: true,
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
preChatFormEnabled: true,
|
||||
});
|
||||
expect(hasEmojiPickerEnabled.value).toBe(true);
|
||||
expect(hasAttachmentsEnabled.value).toBe(true);
|
||||
expect(hasEndConversationEnabled.value).toBe(true);
|
||||
expect(preChatFormEnabled.value).toBe(true);
|
||||
|
||||
expect(preChatFormOptions.value).toEqual({
|
||||
preChatMessage: '',
|
||||
preChatFields: [
|
||||
{
|
||||
label: 'Email Id',
|
||||
name: 'emailAddress',
|
||||
type: 'email',
|
||||
field_type: 'standard',
|
||||
required: false,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: 'Full name',
|
||||
name: 'fullName',
|
||||
type: 'text',
|
||||
field_type: 'standard',
|
||||
required: true,
|
||||
enabled: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
expect(shouldShowPreChatForm.value).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import { computed } from 'vue';
|
||||
|
||||
/**
|
||||
* Composable for handling config-related computations.
|
||||
* @returns {Object} An object containing computed properties for various configuration settings.
|
||||
*/
|
||||
export function useConfig() {
|
||||
/**
|
||||
* Computed property to get the channel config.
|
||||
* @returns {Object} The channel configuration object.
|
||||
*/
|
||||
const channelConfig = computed(() => window.chatwootWebChannel);
|
||||
|
||||
const createFeatureChecker = feature =>
|
||||
computed(() => channelConfig.value.enabledFeatures.includes(feature));
|
||||
|
||||
// Use the helper function for feature checks
|
||||
const useInboxAvatarForBot = createFeatureChecker('use_inbox_avatar_for_bot');
|
||||
const hasEmojiPickerEnabled = createFeatureChecker('emoji_picker');
|
||||
const hasAttachmentsEnabled = createFeatureChecker('attachments');
|
||||
const hasEndConversationEnabled = createFeatureChecker('end_conversation');
|
||||
|
||||
/**
|
||||
* Computed property to check if the agent bot is connected.
|
||||
* @returns {Boolean} True if the agent bot is connected, false otherwise.
|
||||
*/
|
||||
const hasAConnectedAgentBot = computed(
|
||||
() => !!channelConfig.value.hasAConnectedAgentBot
|
||||
);
|
||||
|
||||
/**
|
||||
* Computed property to get the inbox avatar URL.
|
||||
* @returns {String} The inbox avatar URL.
|
||||
*/
|
||||
const inboxAvatarUrl = computed(() => channelConfig.value.avatarUrl);
|
||||
|
||||
/**
|
||||
* Computed property to check if the pre-chat form is enabled.
|
||||
* @returns {Boolean} True if the pre-chat form is enabled, false otherwise.
|
||||
*/
|
||||
const preChatFormEnabled = computed(
|
||||
() => channelConfig.value.preChatFormEnabled
|
||||
);
|
||||
|
||||
/**
|
||||
* Computed property to get the pre-chat form options.
|
||||
* @returns {Object} An object containing pre-chat message and fields.
|
||||
*/
|
||||
const preChatFormOptions = computed(() => {
|
||||
const options = channelConfig.value.preChatFormOptions || {};
|
||||
const preChatMessage = options.pre_chat_message || '';
|
||||
const preChatFields = options.pre_chat_fields || [];
|
||||
return {
|
||||
preChatMessage,
|
||||
preChatFields,
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Computed property to determine if the pre-chat form should be shown.
|
||||
* @returns {Boolean} True if the pre-chat form should be shown, false otherwise.
|
||||
*/
|
||||
const shouldShowPreChatForm = computed(() => {
|
||||
if (!preChatFormEnabled.value) return false;
|
||||
const { preChatFields } = preChatFormOptions.value;
|
||||
return preChatFields.some(field => field.enabled);
|
||||
});
|
||||
|
||||
return {
|
||||
useInboxAvatarForBot,
|
||||
hasAConnectedAgentBot,
|
||||
inboxAvatarUrl,
|
||||
channelConfig,
|
||||
hasEmojiPickerEnabled,
|
||||
hasAttachmentsEnabled,
|
||||
hasEndConversationEnabled,
|
||||
preChatFormEnabled,
|
||||
preChatFormOptions,
|
||||
shouldShowPreChatForm,
|
||||
};
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
export default {
|
||||
computed: {
|
||||
useInboxAvatarForBot() {
|
||||
return this.channelConfig.enabledFeatures.includes(
|
||||
'use_inbox_avatar_for_bot'
|
||||
);
|
||||
},
|
||||
hasAConnectedAgentBot() {
|
||||
return !!window.chatwootWebChannel.hasAConnectedAgentBot;
|
||||
},
|
||||
inboxAvatarUrl() {
|
||||
return window.chatwootWebChannel.avatarUrl;
|
||||
},
|
||||
channelConfig() {
|
||||
return window.chatwootWebChannel;
|
||||
},
|
||||
hasEmojiPickerEnabled() {
|
||||
return this.channelConfig.enabledFeatures.includes('emoji_picker');
|
||||
},
|
||||
hasAttachmentsEnabled() {
|
||||
return this.channelConfig.enabledFeatures.includes('attachments');
|
||||
},
|
||||
hasEndConversationEnabled() {
|
||||
return this.channelConfig.enabledFeatures.includes('end_conversation');
|
||||
},
|
||||
preChatFormEnabled() {
|
||||
return window.chatwootWebChannel.preChatFormEnabled;
|
||||
},
|
||||
preChatFormOptions() {
|
||||
let preChatMessage = '';
|
||||
const options = window.chatwootWebChannel.preChatFormOptions || {};
|
||||
preChatMessage = options.pre_chat_message;
|
||||
const { pre_chat_fields: preChatFields = [] } = options;
|
||||
return {
|
||||
preChatMessage,
|
||||
preChatFields,
|
||||
};
|
||||
},
|
||||
shouldShowPreChatForm() {
|
||||
const { preChatFields } = this.preChatFormOptions;
|
||||
// Check if at least one enabled field in pre-chat fields
|
||||
const hasEnabledFields =
|
||||
preChatFields.filter(field => field.enabled).length > 0;
|
||||
return this.preChatFormEnabled && hasEnabledFields;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
import { createWrapper } from '@vue/test-utils';
|
||||
import configMixin from '../configMixin';
|
||||
import Vue from 'vue';
|
||||
const preChatFields = [
|
||||
{
|
||||
label: 'Email Id',
|
||||
name: 'emailAddress',
|
||||
type: 'email',
|
||||
field_type: 'standard',
|
||||
required: false,
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
label: 'Full name',
|
||||
name: 'fullName',
|
||||
type: 'text',
|
||||
field_type: 'standard',
|
||||
required: true,
|
||||
enabled: true,
|
||||
},
|
||||
];
|
||||
global.chatwootWebChannel = {
|
||||
avatarUrl: 'https://test.url',
|
||||
hasAConnectedAgentBot: 'AgentBot',
|
||||
enabledFeatures: [
|
||||
'emoji_picker',
|
||||
'attachments',
|
||||
'end_conversation',
|
||||
'use_inbox_avatar_for_bot',
|
||||
],
|
||||
preChatFormOptions: { pre_chat_fields: preChatFields, pre_chat_message: '' },
|
||||
preChatFormEnabled: true,
|
||||
};
|
||||
|
||||
describe('configMixin', () => {
|
||||
test('returns config', () => {
|
||||
const Component = {
|
||||
render() {},
|
||||
title: 'TestComponent',
|
||||
mixins: [configMixin],
|
||||
};
|
||||
const Constructor = Vue.extend(Component);
|
||||
const vm = new Constructor().$mount();
|
||||
const wrapper = createWrapper(vm);
|
||||
expect(wrapper.vm.hasEmojiPickerEnabled).toBe(true);
|
||||
expect(wrapper.vm.hasEndConversationEnabled).toBe(true);
|
||||
expect(wrapper.vm.hasAttachmentsEnabled).toBe(true);
|
||||
expect(wrapper.vm.hasAConnectedAgentBot).toBe(true);
|
||||
expect(wrapper.vm.useInboxAvatarForBot).toBe(true);
|
||||
expect(wrapper.vm.inboxAvatarUrl).toBe('https://test.url');
|
||||
|
||||
expect(wrapper.vm.channelConfig).toEqual({
|
||||
avatarUrl: 'https://test.url',
|
||||
hasAConnectedAgentBot: 'AgentBot',
|
||||
enabledFeatures: [
|
||||
'emoji_picker',
|
||||
'attachments',
|
||||
'end_conversation',
|
||||
'use_inbox_avatar_for_bot',
|
||||
],
|
||||
preChatFormOptions: {
|
||||
pre_chat_message: '',
|
||||
pre_chat_fields: preChatFields,
|
||||
},
|
||||
preChatFormEnabled: true,
|
||||
});
|
||||
expect(wrapper.vm.preChatFormOptions).toEqual({
|
||||
preChatMessage: '',
|
||||
preChatFields: preChatFields,
|
||||
});
|
||||
expect(wrapper.vm.preChatFormEnabled).toEqual(true);
|
||||
expect(wrapper.vm.shouldShowPreChatForm).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -6,7 +6,7 @@ import ArticleCardSkeletonLoader from 'widget/components/ArticleCardSkeletonLoad
|
||||
import { mapGetters } from 'vuex';
|
||||
import darkModeMixin from 'widget/mixins/darkModeMixin';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
@@ -15,7 +15,13 @@ export default {
|
||||
TeamAvailability,
|
||||
ArticleCardSkeletonLoader,
|
||||
},
|
||||
mixins: [configMixin, routerMixin, darkModeMixin],
|
||||
mixins: [routerMixin, darkModeMixin],
|
||||
setup() {
|
||||
const { preChatFormEnabled } = useConfig();
|
||||
return {
|
||||
preChatFormEnabled,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
availableAgents: 'agent/availableAgents',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import PreChatForm from '../components/PreChat/Form.vue';
|
||||
import configMixin from '../mixins/configMixin';
|
||||
import { useConfig } from 'widget/composables/useConfig';
|
||||
import routerMixin from '../mixins/routerMixin';
|
||||
import { isEmptyObject } from 'widget/helpers/utils';
|
||||
import { ON_CONVERSATION_CREATED } from '../constants/widgetBusEvents';
|
||||
@@ -9,7 +9,13 @@ export default {
|
||||
components: {
|
||||
PreChatForm,
|
||||
},
|
||||
mixins: [configMixin, routerMixin],
|
||||
mixins: [routerMixin],
|
||||
setup() {
|
||||
const { preChatFormOptions } = useConfig();
|
||||
return {
|
||||
preChatFormOptions,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$emitter.on(ON_CONVERSATION_CREATED, () => {
|
||||
// Redirect to messages page after conversation is created
|
||||
|
||||
Reference in New Issue
Block a user