Skip to content

Commit

Permalink
Merge pull request #46 from webitel/fix/added-communacation-option
Browse files Browse the repository at this point in the history
Fix/add communacation option [WTEL-3883]
  • Loading branch information
dlohvinov authored Feb 14, 2024
2 parents b20044a + c765117 commit 98017be
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 134 deletions.
2 changes: 2 additions & 0 deletions src/app/locale/en/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ export default {
destination: 'Destination',
setAsPrimary: 'Set as primary',
emails: {
title: 'The email',
dummy: 'There are no email addresses yet',
},
phones: {
title: 'The phone number',
dummy: 'There are no phone numbers yet',
},
},
Expand Down
2 changes: 2 additions & 0 deletions src/app/locale/ru/ru.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ export default {
destination: 'Назначение',
setAsPrimary: 'Установить как основной',
emails: {
title: 'Электронный адрес',
dummy: 'Электронные адреса еще не добавлены',
},
phones: {
title: 'Номер телефона',
dummy: 'Телефонные номера еще не добавлены',
},
},
Expand Down
2 changes: 2 additions & 0 deletions src/app/locale/ua/ua.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ export default {
destination: 'Призначення',
setAsPrimary: 'Встановити як основний',
emails: {
title: 'Електронна адреса',
dummy: 'Електронні адреси ще не додано',
},
phones: {
title: 'Номер телефону',
dummy: 'Телефонні номери ще не додано',
},
},
Expand Down
12 changes: 6 additions & 6 deletions src/app/router/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const checkAppAccess = (to, from, next) => {
const checkRouteAccess = ((to, from, next) => {
// has Role Section Access AND (Select role permissions || ObAC permissions access)
const hasReadAccess = store.getters['userinfo/CHECK_OBJECT_ACCESS']({ route: to })
&& store.getters['userinfo/HAS_READ_ACCESS']({ name: 'contacts' });
&& store.getters['userinfo/HAS_READ_ACCESS']({ name: 'contacts' });
if (hasReadAccess) {
next();
} else {
Expand All @@ -49,11 +49,11 @@ const routes = [
beforeEnter: checkAppAccess,
children: [
{
path: 'contacts',
name: CrmSections.CONTACTS,
component: TheContacts,
beforeEnter: checkRouteAccess,
},
path: 'contacts',
name: CrmSections.CONTACTS,
component: TheContacts,
beforeEnter: checkRouteAccess,
},
{
path: 'contacts/:id',
name: `${CrmSections.CONTACTS}-edit`,
Expand Down
172 changes: 87 additions & 85 deletions src/modules/contacts/components/opened-contact-communication-popup.vue
Original file line number Diff line number Diff line change
@@ -1,37 +1,25 @@
<template>
<wt-popup
class="opened-contact-add-communication-popup"
class="opened-contact-communication-popup"
width="480"
overflow
@close="close"
>
<template #header>
{{ mode === 'update' ? t('reusable.edit') : t('reusable.add') }}
{{
t('contacts.communications.communications', 1)
.toLowerCase()
{{ item ? t('reusable.edit') : t('reusable.add') }}
{{ currentCommunication.locale.toLowerCase() ||
t('contacts.communications.communications', 1).toLowerCase()
}}
</template>
<template #main>
<form
class="opened-contact-add-communication-popup-form"
class="opened-contact-communication-popup-form"
>
<wt-select
:value="channel"
:options="channelOptions"
:disabled="mode === 'update'"
:v="v$.draft.channel"
:label="t('contacts.communications.channel')"
:clearable="false"
track-by="value"
required
@input="draft.channel = $event.value"
/>
<wt-select
ref="TypeSelect"
:value="draft.type"
:v="v$.draft.type"
:search-method="(params) => CommunicationTypesAPI.getLookup({...params, channel: channel.filterField })"
:search-method="(params) => CommunicationTypesAPI.getLookup({...params, channel: currentCommunication.filterField })"
:clearable="false"
:label="t('objects.communicationType', 1)"
required
Expand All @@ -47,88 +35,93 @@
</form>
</template>
<template #actions>
<wt-button
:disabled="v$.$invalid"
:loading="isLoading"
@click="save"
>
{{ t('reusable.save') }}
</wt-button>
<wt-button
color="secondary"
@click="close"
>
{{ t('reusable.cancel') }}
</wt-button>
<wt-button
:disabled="v$.$invalid"
:loading="isLoading"
@click="save"
>
{{ t('reusable.save') }}
</wt-button>
<wt-button
color="secondary"
@click="close"
>
{{ t('reusable.cancel') }}
</wt-button>
</template>
</wt-popup>
</template>

<script setup>
/*
Although this popup actually represents emails, phones or messaging item card,
I decided not to provide these modules with "card" store and features because
I think it would be too overcomplicated because these 3 modules have shared communications
popup (by UI design, "Add" button is higher on component hierarchy than entity table itself)
*/
import { computed, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { computed, onMounted, ref, watch, reactive } from 'vue';
import { useVuelidate } from '@vuelidate/core';
import { required, email } from '@vuelidate/validators';
import { EngineCommunicationChannels } from 'webitel-sdk';
import { useI18n } from 'vue-i18n';
import { useStore } from 'vuex';
import CommunicationTypesAPI from '../api/CommunicationTypesAPI';
import { EngineCommunicationChannels } from 'webitel-sdk';
const { t } = useI18n();
const store = useStore();
const props = defineProps({
editedInstance: {
type: Object,
item: {
// if item is passed, that's an edit
type: [Object, null],
},
callback: {
type: Function,
required: true,
},
initialChannel: {
channel: {
type: String,
default: 'number',
},
namespace: {
type: String,
required: true,
},
});
const emit = defineEmits([
'close',
]);
const emit = defineEmits(['close']);
const { t } = useI18n();
const isLoading = ref(false);
const isSaving = ref(false);
const TypeSelect = ref(null);
const channelOptions = [
{
value: 'number', // should be same as backend field for destination
locale: ['vocabulary.phones', 1],
filterField: EngineCommunicationChannels.Phone,
},
const communicationOptions = [
{
value: 'email', // should be same as backend field for destination
locale: ['vocabulary.emails', 1],
locale: t('contacts.communications.emails.title'),
filterField: EngineCommunicationChannels.Email,
addNamespace: `${props.namespace}/ADD_EMAIL`,
updateNamespace: `${props.namespace}/UPDATE_EMAIL`,
},
{
value: 'number',
locale: t('contacts.communications.phones.title'),
filterField: EngineCommunicationChannels.Phone,
addNamespace: `${props.namespace}/ADD_PHONE`,
updateNamespace: `${props.namespace}/UPDATE_PHONE`,
},
// {
// value: 'messaging',
// locale: 'vocabulary.messaging',
// locale: ,
// filterField: EngineCommunicationChannels.Messaging,
// namespace: ,
// },
];
const getDefaultDraft = () => ({
channel: channelOptions.find(({ value }) => value === props.initialChannel).value,
channel: props.channel,
type: {},
destination: '',
});
const draft = reactive(getDefaultDraft());
const isLoading = ref(false);
const TypeSelect = ref(null);
const currentCommunication = computed(() => {
return communicationOptions.find((option) => option.value === props.channel);
});
const v$ = useVuelidate(computed(() => {
const destination = { required };
if (draft.channel === 'email') destination.email = email;
const destination = props.channel === 'email' ? { required, email } : { required };
return {
draft: {
channel: { required },
Expand All @@ -138,42 +131,51 @@ const v$ = useVuelidate(computed(() => {
};
}), { draft }, { $autoDirty: true });
v$.value.$touch();
const mode = computed(() => (props.editedInstance ? 'update' : 'create'));
const channel = computed(() => channelOptions.find(({ value }) => draft.channel === value));
function initDraft() {
draft.channel = channelOptions.find(({ value }) => props.editedInstance[value]).value;
draft.destination = props.editedInstance[draft.channel];
draft.type = props.editedInstance.type;
draft.destination = props.item[props.channel];
draft.type = props.item.type;
}
if (mode.value === 'update') initDraft();
v$.value.$touch();
function close() {
emit('close');
}
if (props.item) initDraft();
async function save() {
try {
isLoading.value = true;
await props.callback(draft);
isSaving.value = true;
if (props.item) {
await updateItem(draft);
} else {
await addItem(draft);
}
close();
} finally {
isLoading.value = false;
isSaving.value = false;
}
}
watch(channel, () => {
draft.type = {}; // reset selected type, because it has another channel
TypeSelect.value.fetchOptions(); // load types by newly changed channel filter
});
function addItem({ type, destination }) {
const itemInstance = { type, [props.channel]: destination };
return store.dispatch(`${currentCommunication.value.addNamespace}`, { itemInstance });
}
function updateItem({ channel, destination, ...rest }) {
const itemInstance = { ...rest, [props.channel]: destination };
return store.dispatch(`${currentCommunication.value.updateNamespace}`, {
itemInstance,
etag: props.item.etag,
});
}
function close() {
emit('close');
}
</script>

<style lang="scss" scoped>
.opened-contact-add-communication-popup-form {
.opened-contact-communication-popup-form {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
Expand Down
21 changes: 0 additions & 21 deletions src/modules/contacts/components/opened-contact-communications.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
<template>
<section class="opened-contact-communications">
<header class="opened-contact-communications-header">
<add-communication-popup
v-if="isCommunicationPopup"
:initial-channel="currentTab.channel"
:callback="saveCommunication"
@close="isCommunicationPopup = false"
/>
<wt-button
v-for="(tab) of tabs"
:key="tab.value"
Expand All @@ -20,12 +14,6 @@
/>
{{ tab.label }}
</wt-button>
<wt-button
:disabled="!access.hasRbacEditAccess"
@click="isCommunicationPopup = true"
>
{{ t('reusable.add') }}
</wt-button>
</header>
<component
:is="currentTab.component"
Expand All @@ -40,7 +28,6 @@ import { useI18n } from 'vue-i18n';
import { useStore } from 'vuex';
import TheEmails from '../modules/emails/components/the-emails.vue';
import ThePhones from '../modules/phones/components/the-phones.vue';
import AddCommunicationPopup from './opened-contact-communication-popup.vue';
const access = inject('access');
Expand Down Expand Up @@ -78,14 +65,6 @@ const tabs = computed(() => [
const currentTab = ref(tabs.value[0]);
const isCommunicationPopup = ref(false);
function saveCommunication({ channel, type, destination }) {
const itemInstance = { type, [channel]: destination };
if (channel === 'email') return store.dispatch(`${emailsNamespace}/table/ADD_EMAIL`, { itemInstance });
if (channel === 'number') return store.dispatch(`${phonesNamespace}/table/ADD_PHONE`, { itemInstance });
throw TypeError(`Unknown communication channel: ${channel}`);
}
</script>

<style lang="scss" scoped>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
.contact-communication-tab {
flex-grow: 1;
display: flex;
flex-direction: column;
gap: var(--spacing-sm);

&-header {
display: flex;
justify-content: flex-end;
padding: 0 var(--spacing-xs);
}

.set-primary-btn {
opacity: 0;
Expand Down
Loading

0 comments on commit 98017be

Please sign in to comment.