feat: Set up store for teams (#1689)

This commit is contained in:
Nithin David Thomas 2021-03-15 18:35:56 +05:30 committed by GitHub
parent cadb246eaa
commit 941d4219f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1168 additions and 4 deletions

View file

@ -11,5 +11,6 @@ describe('#TeamsAPI', () => {
expect(teams).toHaveProperty('delete'); expect(teams).toHaveProperty('delete');
expect(teams).toHaveProperty('getAgents'); expect(teams).toHaveProperty('getAgents');
expect(teams).toHaveProperty('addAgents'); expect(teams).toHaveProperty('addAgents');
expect(teams).toHaveProperty('updateAgents');
}); });
}); });

View file

@ -15,6 +15,12 @@ export class TeamsAPI extends ApiClient {
user_ids: agentsList, user_ids: agentsList,
}); });
} }
updateAgents({ teamId, agentsList }) {
return axios.patch(`${this.url}/${teamId}/team_members`, {
user_ids: agentsList,
});
}
} }
export default new TeamsAPI(); export default new TeamsAPI();

View file

@ -178,7 +178,6 @@ export default {
icon: 'ion-ios-people', icon: 'ion-ios-people',
label: 'TEAMS', label: 'TEAMS',
hasSubMenu: true, hasSubMenu: true,
newLink: true,
key: 'team', key: 'team',
cssClass: 'menu-title align-justify teams-sidebar-menu', cssClass: 'menu-title align-justify teams-sidebar-menu',
toState: frontendURL(`accounts/${this.accountId}/settings/teams`), toState: frontendURL(`accounts/${this.accountId}/settings/teams`),

View file

@ -74,6 +74,7 @@ export default {
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
border-left: 1px solid var(--color-border); border-left: 1px solid var(--color-border);
background: var(--color-background-light);
} }
.messages-and-sidebar { .messages-and-sidebar {

View file

@ -75,6 +75,13 @@ export const getSidebarItems = accountId => ({
'settings_integrations_integration', 'settings_integrations_integration',
'general_settings', 'general_settings',
'general_settings_index', 'general_settings_index',
'settings_teams_list',
'settings_teams_new',
'settings_teams_add_agents',
'settings_teams_finish',
'settings_teams_edit',
'settings_teams_edit_members',
'settings_teams_edit_finish',
], ],
menuItems: { menuItems: {
back: { back: {
@ -91,6 +98,13 @@ export const getSidebarItems = accountId => ({
toState: frontendURL(`accounts/${accountId}/settings/agents/list`), toState: frontendURL(`accounts/${accountId}/settings/agents/list`),
toStateName: 'agent_list', toStateName: 'agent_list',
}, },
teams: {
icon: 'ion-ios-people',
label: 'TEAMS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/settings/teams/list`),
toStateName: 'settings_teams_list',
},
inboxes: { inboxes: {
icon: 'ion-archive', icon: 'ion-archive',
label: 'INBOXES', label: 'INBOXES',

View file

@ -13,6 +13,7 @@ import { default as _settings } from './settings.json';
import { default as _signup } from './signup.json'; import { default as _signup } from './signup.json';
import { default as _integrations } from './integrations.json'; import { default as _integrations } from './integrations.json';
import { default as _generalSettings } from './generalSettings.json'; import { default as _generalSettings } from './generalSettings.json';
import { default as _teamsSettings } from './teamsSettings.json';
export default { export default {
..._agentMgmt, ..._agentMgmt,
@ -30,4 +31,5 @@ export default {
..._signup, ..._signup,
..._integrations, ..._integrations,
..._generalSettings, ..._generalSettings,
..._teamsSettings,
}; };

View file

@ -0,0 +1,123 @@
{
"TEAMS_SETTINGS": {
"NEW_TEAM": "Create new team",
"HEADER": "Teams",
"SIDEBAR_TXT": "<p><b>Teams</b></p> <p>Teams let you organize your agents into groups based on their responsibilities. <br /> A user can be part of multiple teams. You can assign conversations to a team when you are working collaboratively. </p>",
"LIST": {
"404": "There are no teams created on this account.",
"EDIT_TEAM": "Edit team"
},
"CREATE_FLOW": {
"CREATE": {
"TITLE": "Create a new team",
"DESC": "Add a title and description to your new team."
},
"AGENTS": {
"BUTTON_TEXT": "Add agents to team",
"TITLE": "Add agents to team - %{teamName}",
"DESC": "Add Agents to your newly created team. This lets you collaborate as a team on conversations, get notified on new events in the same conversation."
},
"WIZARD": [{
"title": "Create",
"route": "settings_teams_new",
"body": "Create a new team of agents."
},
{
"title": "Add Agents",
"route": "settings_teams_add_agents",
"body": "Add agents to the team."
},
{
"title": "Finish",
"route": "settings_teams_finish",
"body": "You are all set to go!"
}
]
},
"EDIT_FLOW": {
"CREATE": {
"TITLE": "Edit your team details",
"DESC": "Edit title and description to your team.",
"BUTTON_TEXT": "Update team"
},
"AGENTS": {
"BUTTON_TEXT": "Update agents in team",
"TITLE": "Add agents to team - %{teamName}",
"DESC": "Add Agents to your newly created team. All the added agents will be notified when a conversation is assigned to this team."
},
"WIZARD": [{
"title": "Team details",
"route": "settings_teams_edit",
"body": "Change name, description and other details."
},
{
"title": "Edit Agents",
"route": "settings_teams_edit_members",
"body": "Edit agents in your team."
},
{
"title": "Finish",
"route": "settings_teams_edit_finish",
"body": "You are all set to go!"
}
]
},
"TEAM_FORM": {
"ERROR_MESSAGE": "Couldn't save the team details. Try again."
},
"AGENTS": {
"AGENT": "AGENT",
"EMAIL": "EMAIL",
"BUTTON_TEXT": "Add agents",
"ADD_AGENTS": "Adding Agents to your Team...",
"SELECT": "select",
"SELECT_ALL": "select all agents",
"SELECTED_COUNT": "%{selected} out of %{total} agents selected."
},
"ADD": {
"TITLE": "Add agents to team - %{teamName}",
"DESC": "Add Agents to your newly created team. This lets you collaborate as a team on conversations, get notified on new events in the same conversation.",
"SELECT": "select",
"SELECT_ALL": "select all agents",
"SELECTED_COUNT": "%{selected} out of %{total} agents selected.",
"BUTTON_TEXT": "Add agents",
"AGENT_VALIDATION_ERROR": "Select atleaset one agent."
},
"FINISH": {
"TITLE": "Your team is ready!",
"MESSAGE": "You can now collaborate as a team on conversations. Happy supporting ",
"BUTTON_TEXT": "Finish"
},
"DELETE": {
"BUTTON_TEXT": "Delete",
"API": {
"SUCCESS_MESSAGE": "Team deleted successfully.",
"ERROR_MESSAGE": "Couldn't delete the team. Try again."
},
"CONFIRM": {
"TITLE": "Are you sure want to delete - %{teamName}",
"MESSAGE": "Deleting the team will remove the team assignment from the conversations assigned to this team.",
"YES": "Delete ",
"NO": "Cancel"
}
},
"SETTINGS": "Settings",
"FORM": {
"UPDATE": "Update team",
"CREATE": "Create team",
"NAME": {
"LABEL": "Team name",
"PLACEHOLDER": "Example: Sales, Customer Support"
},
"DESCRIPTION": {
"LABEL": "Team Description",
"PLACEHOLDER": "Short description about this team."
},
"AUTO_ASSIGN": {
"LABEL": "Allow auto assign for this team."
},
"SUBMIT_CREATE": "Create team"
}
}
}

View file

@ -404,6 +404,7 @@ export default {
this.selectedTabIndex = 0; this.selectedTabIndex = 0;
this.selectedAgents = []; this.selectedAgents = [];
this.$store.dispatch('agents/get'); this.$store.dispatch('agents/get');
this.$store.dispatch('teams/get');
this.$store.dispatch('inboxes/get').then(() => { this.$store.dispatch('inboxes/get').then(() => {
this.fetchAttachedAgents(); this.fetchAttachedAgents();
this.avatarUrl = this.inbox.avatar_url; this.avatarUrl = this.inbox.avatar_url;

View file

@ -7,6 +7,7 @@ import integrations from './integrations/integrations.routes';
import labels from './labels/labels.routes'; import labels from './labels/labels.routes';
import profile from './profile/profile.routes'; import profile from './profile/profile.routes';
import reports from './reports/reports.routes'; import reports from './reports/reports.routes';
import teams from './teams/teams.routes';
import store from '../../../store'; import store from '../../../store';
export default { export default {
@ -30,5 +31,6 @@ export default {
...labels.routes, ...labels.routes,
...profile.routes, ...profile.routes,
...reports.routes, ...reports.routes,
...teams.routes,
], ],
}; };

View file

@ -0,0 +1,179 @@
<template>
<div>
<div class="add-agents__header"></div>
<table class="woot-table">
<thead>
<tr>
<td>
<div class="checkbox-wrap">
<input
name="select-all-agents"
type="checkbox"
:checked="allAgentsSelected ? 'checked' : ''"
:title="$t('TEAMS_SETTINGS.AGENTS.SELECT_ALL')"
@click.self="selectAllAgents"
/>
</div>
</td>
<td>{{ $t('TEAMS_SETTINGS.AGENTS.AGENT') }}</td>
<td>{{ $t('TEAMS_SETTINGS.AGENTS.EMAIL') }}</td>
</tr>
</thead>
<tbody>
<tr
v-for="agent in agentList"
:key="agent.id"
:class="agentRowClass(agent.id)"
>
<td class="checkbox-cell">
<div class="checkbox-wrap">
<input
type="checkbox"
:checked="isAgentSelected(agent.id)"
@click.self="() => handleSelectAgent(agent.id)"
/>
</div>
</td>
<td>
<div class="user-info-wrap">
<thumbnail
:src="agent.thumbnail"
size="24px"
:username="agent.name"
:status="agent.availability_status"
/>
<h4 class="sub-block-title user-name">
{{ agent.name }}
</h4>
</div>
</td>
<td>
{{ agent.email || '---' }}
</td>
</tr>
</tbody>
</table>
<div class="add-agents__footer">
<p>
{{
$t('TEAMS_SETTINGS.AGENTS.SELECTED_COUNT', {
selected: selectedAgents.length,
total: agentList.length,
})
}}
</p>
<woot-submit-button
:button-text="submitButtonText"
:loading="isWorking"
:disabled="disableSubmitButton"
/>
</div>
</div>
</template>
<script>
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
export default {
components: {
Thumbnail,
},
props: {
agentList: {
type: Array,
default: () => [],
},
selectedAgents: {
type: Array,
default: () => [],
},
updateSelectedAgents: {
type: Function,
default: () => {},
},
isWorking: {
type: Boolean,
default: false,
},
submitButtonText: {
type: String,
default: '',
},
},
data() {
return {};
},
computed: {
selectedAgentCount() {
return this.selectedAgents.length;
},
allAgentsSelected() {
return this.selectedAgents.length === this.agentList.length;
},
disableSubmitButton() {
return this.selectedAgentCount === 0;
},
},
methods: {
isAgentSelected(agentId) {
return this.selectedAgents.includes(agentId);
},
handleSelectAgent(agentId) {
const shouldRemove = this.isAgentSelected(agentId);
let result = [];
if (shouldRemove) {
result = this.selectedAgents.filter(item => item !== agentId);
} else {
result = [...this.selectedAgents, agentId];
}
this.updateSelectedAgents(result);
},
selectAllAgents() {
const result = this.agentList.map(item => item.id);
this.updateSelectedAgents(result);
},
agentRowClass(agentId) {
return { 'is-active': this.isAgentSelected(agentId) };
},
},
};
</script>
<style scoped lang="scss">
.table__meta {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-small);
}
.user-info-wrap {
display: flex;
align-items: center;
}
.user-name {
margin-bottom: 0;
margin-left: var(--space-small);
}
.add-agents__footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.checkbox-wrap {
display: flex;
align-items: center;
input {
margin: 0;
}
}
.checkbox-cell {
width: var(--space-larger);
}
</style>

View file

@ -0,0 +1,117 @@
<template>
<div class="wizard-body columns content-box small-9">
<form class="row" @submit.prevent="addAgents">
<div class="medium-12 columns">
<page-header
:header-title="headerTitle"
:header-content="$t('TEAMS_SETTINGS.ADD.DESC')"
/>
</div>
<div class="medium-12 columns">
<div v-if="$v.selectedAgents.$error">
<p class="error-message">
{{ $t('TEAMS_SETTINGS.ADD.AGENT_VALIDATION_ERROR') }}
</p>
</div>
<agent-selector
:agent-list="agentList"
:selected-agents="selectedAgents"
:update-selected-agents="updateSelectedAgents"
:is-working="isCreating"
:submit-button-text="$t('TEAMS_SETTINGS.ADD.BUTTON_TEXT')"
/>
</div>
</form>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
import router from '../../../../index';
import PageHeader from '../../SettingsSubPageHeader';
import AgentSelector from '../AgentSelector';
export default {
components: {
PageHeader,
AgentSelector,
},
mixins: [alertMixin],
props: {
team: {
type: Object,
default: () => {},
},
},
validations: {
selectedAgents: {
isEmpty() {
return !!this.selectedAgents.length;
},
},
},
data() {
return {
selectedAgents: [],
isCreating: false,
};
},
computed: {
...mapGetters({
agentList: 'agents/getAgents',
}),
teamId() {
return this.$route.params.teamId;
},
headerTitle() {
return this.$t('TEAMS_SETTINGS.ADD.TITLE', {
teamName: this.currentTeam.name,
});
},
currentTeam() {
return this.$store.getters['teams/getTeam'](this.teamId);
},
},
mounted() {
this.$store.dispatch('agents/get');
},
methods: {
updateSelectedAgents(newAgentList) {
this.$v.selectedAgents.$touch();
this.selectedAgents = [...newAgentList];
},
selectAllAgents() {
this.selectedAgents = this.agentList.map(agent => agent.id);
},
async addAgents() {
this.isCreating = true;
const { teamId, selectedAgents } = this;
try {
await this.$store.dispatch('teamMembers/create', {
teamId,
agentsList: selectedAgents,
});
router.replace({
name: 'settings_teams_finish',
params: {
page: 'new',
teamId,
},
});
} catch (error) {
this.showAlert(error.message);
}
this.isCreating = false;
},
},
};
</script>

View file

@ -0,0 +1,54 @@
<template>
<div class="wizard-body small-9 columns">
<page-header
:header-title="$t('TEAMS_SETTINGS.CREATE_FLOW.CREATE.TITLE')"
:header-content="$t('TEAMS_SETTINGS.CREATE_FLOW.CREATE.DESC')"
/>
<div class="row channels">
<team-form
:on-submit="createTeam"
:submit-in-progress="false"
:submit-button-text="$t('TEAMS_SETTINGS.FORM.SUBMIT_CREATE')"
/>
</div>
</div>
</template>
<script>
import TeamForm from '../TeamForm';
import router from '../../../../index';
import PageHeader from '../../SettingsSubPageHeader';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {
TeamForm,
PageHeader,
},
mixins: [alertMixin],
data() {
return {
enabledFeatures: {},
};
},
methods: {
async createTeam(data) {
try {
const team = await this.$store.dispatch('teams/create', {
...data,
});
router.replace({
name: 'settings_teams_add_agents',
params: {
page: 'new',
teamId: team.id,
},
});
} catch (error) {
this.showAlert(this.$t('TEAMS_SETTINGS.TEAM_FORM.ERROR_MESSAGE'));
}
},
},
};
</script>

View file

@ -0,0 +1,17 @@
<template>
<div class="row content-box full-height">
<woot-wizard class="small-3 columns" :items="items" />
<router-view></router-view>
</div>
</template>
<script>
export default {
computed: {
items() {
const data = this.$t('TEAMS_SETTINGS.CREATE_FLOW.WIZARD');
return data;
},
},
};
</script>

View file

@ -0,0 +1,139 @@
<template>
<div class="wizard-body columns content-box small-9">
<form class="row" @submit.prevent="addAgents">
<div class="medium-12 columns">
<page-header
:header-title="headerTitle"
:header-content="$t('TEAMS_SETTINGS.EDIT_FLOW.AGENTS.DESC')"
/>
</div>
<div class="medium-12 columns">
<div v-if="$v.selectedAgents.$error">
<p class="error-message">
{{ $t('TEAMS_SETTINGS.ADD.AGENT_VALIDATION_ERROR') }}
</p>
</div>
<agent-selector
v-if="showAgentsList"
:agent-list="agentList"
:selected-agents="selectedAgents"
:update-selected-agents="updateSelectedAgents"
:is-working="isCreating"
:submit-button-text="
$t('TEAMS_SETTINGS.EDIT_FLOW.AGENTS.BUTTON_TEXT')
"
/>
<spinner v-else />
</div>
</form>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import Spinner from 'shared/components/Spinner';
import alertMixin from 'shared/mixins/alertMixin';
import router from '../../../../index';
import PageHeader from '../../SettingsSubPageHeader';
import AgentSelector from '../AgentSelector';
export default {
components: {
Spinner,
PageHeader,
AgentSelector,
},
mixins: [alertMixin],
props: {
team: {
type: Object,
default: () => {},
},
},
validations: {
selectedAgents: {
isEmpty() {
return !!this.selectedAgents.length;
},
},
},
data() {
return {
selectedAgents: [],
isCreating: false,
};
},
computed: {
...mapGetters({
agentList: 'agents/getAgents',
uiFlags: 'teamMembers/getUIFlags',
}),
teamId() {
return this.$route.params.teamId;
},
headerTitle() {
return this.$t('TEAMS_SETTINGS.EDIT_FLOW.AGENTS.TITLE', {
teamName: this.currentTeam.name,
});
},
currentTeam() {
return this.$store.getters['teams/getTeam'](this.teamId);
},
teamMembers() {
return this.$store.getters['teamMembers/getTeamMembers'](this.teamId);
},
showAgentsList() {
const { id } = this.currentTeam;
return id && !this.uiFlags.isFetching;
},
},
async mounted() {
const { teamId } = this.$route.params;
this.$store.dispatch('agents/get');
try {
await this.$store.dispatch('teamMembers/get', {
teamId,
});
const members = this.teamMembers.map(item => item.id);
this.updateSelectedAgents(members);
} catch {
this.updateSelectedAgents([]);
}
},
methods: {
updateSelectedAgents(newAgentList) {
this.$v.selectedAgents.$touch();
this.selectedAgents = [...newAgentList];
},
async addAgents() {
this.isCreating = true;
const { teamId, selectedAgents } = this;
try {
await this.$store.dispatch('teamMembers/update', {
teamId,
agentsList: selectedAgents,
});
router.replace({
name: 'settings_teams_edit_finish',
params: {
page: 'edit',
teamId,
},
});
} catch (error) {
this.showAlert(error.message);
}
this.isCreating = false;
},
},
};
</script>

View file

@ -0,0 +1,77 @@
<template>
<div class="wizard-body small-9 columns">
<page-header
:header-title="$t('TEAMS_SETTINGS.EDIT_FLOW.CREATE.TITLE')"
:header-content="$t('TEAMS_SETTINGS.EDIT_FLOW.CREATE.DESC')"
/>
<div class="row channels">
<team-form
v-if="showTeamForm"
:on-submit="updateTeam"
:submit-in-progress="false"
:submit-button-text="$t('TEAMS_SETTINGS.EDIT_FLOW.CREATE.BUTTON_TEXT')"
:form-data="teamData"
/>
<spinner v-else />
</div>
</div>
</template>
<script>
import TeamForm from '../TeamForm';
import router from '../../../../index';
import PageHeader from '../../SettingsSubPageHeader';
import alertMixin from 'shared/mixins/alertMixin';
import { mapGetters } from 'vuex';
import Spinner from 'shared/components/Spinner';
export default {
components: {
TeamForm,
PageHeader,
Spinner,
},
mixins: [alertMixin],
data() {
return {
enabledFeatures: {},
};
},
computed: {
teamData() {
const { teamId } = this.$route.params;
return this.$store.getters['teams/getTeam'](teamId);
},
showTeamForm() {
const { id } = this.teamData;
return id && !this.uiFlags.isFetching;
},
...mapGetters({
uiFlags: 'teams/getUIFlags',
}),
},
methods: {
async updateTeam(data) {
try {
const { teamId } = this.$route.params;
await this.$store.dispatch('teams/update', {
id: teamId,
...data,
});
router.replace({
name: 'settings_teams_edit_members',
params: {
page: 'edit',
teamId,
},
});
} catch (error) {
this.showAlert(this.$t('TEAMS_SETTINGS.TEAM_FORM.ERROR_MESSAGE'));
}
},
},
};
</script>

View file

@ -0,0 +1,17 @@
<template>
<div class="row content-box full-height">
<woot-wizard class="small-3 columns" :items="items" />
<router-view></router-view>
</div>
</template>
<script>
export default {
computed: {
items() {
const data = this.$t('TEAMS_SETTINGS.EDIT_FLOW.WIZARD');
return data;
},
},
};
</script>

View file

@ -0,0 +1,38 @@
<template>
<div class="wizard-body columns content-box small-9">
<empty-state
:title="$t('TEAMS_SETTINGS.FINISH.TITLE')"
:message="$t('TEAMS_SETTINGS.FINISH.MESSAGE')"
:button-text="$t('TEAMS_SETTINGS.FINISH.BUTTON_TEXT')"
>
<div class="medium-12 columns text-center">
<router-link
class="button success nice"
:to="{
name: 'settings_teams_list',
}"
>
{{ $t('TEAMS_SETTINGS.FINISH.BUTTON_TEXT') }}
</router-link>
</div>
</empty-state>
</div>
</template>
<script>
import EmptyState from '../../../../components/widgets/EmptyState';
export default {
components: {
EmptyState,
},
};
</script>
<style lang="scss" scoped>
@import '~dashboard/assets/scss/variables';
.website--code {
margin: $space-normal auto;
max-width: 70%;
}
</style>

View file

@ -0,0 +1,140 @@
<template>
<div class="column content-box">
<div class="row">
<div class="small-8 columns">
<p v-if="!teamsList.length" class="no-items-error-message">
{{ $t('TEAMS_SETTINGS.LIST.404') }}
<router-link
v-if="isAdmin"
:to="addAccountScoping('settings/teams/new')"
>
{{ $t('TEAMS_SETTINGS.NEW_TEAM') }}
</router-link>
</p>
<table v-if="teamsList.length" class="woot-table">
<tbody>
<tr v-for="item in teamsList" :key="item.id">
<td>
<span class="agent-name">{{ item.name }}</span>
<p>{{ item.description }}</p>
</td>
<td>
<div class="button-wrapper">
<router-link
:to="addAccountScoping(`settings/teams/${item.id}/edit`)"
>
<woot-submit-button
v-if="isAdmin"
:button-text="$t('TEAMS_SETTINGS.LIST.EDIT_TEAM')"
icon-class="ion-gear-b"
button-class="link hollow grey-btn"
/>
</router-link>
<woot-submit-button
v-if="isAdmin"
:button-text="$t('TEAMS_SETTINGS.DELETE.BUTTON_TEXT')"
:loading="loading[item.id]"
icon-class="ion-close-circled"
button-class="link hollow grey-btn"
@click="openDelete(item)"
/>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="small-4 columns">
<span
v-html="
$t('TEAMS_SETTINGS.SIDEBAR_TXT', {
installationName: globalConfig.installationName,
})
"
/>
</div>
</div>
<woot-delete-modal
:show.sync="showDeletePopup"
:on-close="closeDelete"
:on-confirm="confirmDeletion"
:title="deleteTitle"
:message="$t('TEAMS_SETTINGS.DELETE.CONFIRM.MESSAGE')"
:confirm-text="deleteConfirmText"
:reject-text="deleteRejectText"
/>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import adminMixin from '../../../../mixins/isAdmin';
import accountMixin from '../../../../mixins/account';
import alertMixin from 'shared/mixins/alertMixin';
export default {
components: {},
mixins: [adminMixin, accountMixin, alertMixin],
data() {
return {
loading: {},
showSettings: false,
showDeletePopup: false,
selectedTeam: {},
};
},
computed: {
...mapGetters({
teamsList: 'teams/getTeams',
globalConfig: 'globalConfig/get',
}),
deleteConfirmText() {
return `${this.$t('TEAMS_SETTINGS.DELETE.CONFIRM.YES')} ${
this.selectedTeam.name
}`;
},
deleteRejectText() {
return this.$t('TEAMS_SETTINGS.DELETE.CONFIRM.NO');
},
deleteTitle() {
return this.$t('TEAMS_SETTINGS.DELETE.CONFIRM.TITLE', {
teamName: this.selectedTeam.name,
});
},
},
methods: {
async deleteTeam({ id }) {
try {
await this.$store.dispatch('teams/delete', id);
this.showAlert(this.$t('TEAMS_SETTINGS.DELETE.API.SUCCESS_MESSAGE'));
} catch (error) {
this.showAlert(this.$t('TEAMS_SETTINGS.DELETE.API.ERROR_MESSAGE'));
}
},
confirmDeletion() {
this.deleteTeam(this.selectedTeam);
this.closeDelete();
},
openDelete(team) {
this.showDeletePopup = true;
this.selectedTeam = team;
},
closeDelete() {
this.showDeletePopup = false;
this.selectedTeam = {};
},
},
};
</script>
<style lang="scss" scoped>
.button-wrapper {
min-width: unset;
justify-content: flex-end;
padding-right: var(--space-large);
}
</style>

View file

@ -0,0 +1,99 @@
<template>
<div class="row">
<div class="small-12 medium-8 columns">
<form class="row" @submit.prevent="handleSubmit">
<woot-input
v-model.trim="title"
:class="{ error: $v.title.$error }"
class="medium-12 columns"
:label="$t('TEAMS_SETTINGS.FORM.NAME.LABEL')"
:placeholder="$t('TEAMS_SETTINGS.FORM.NAME.PLACEHOLDER')"
@input="$v.title.$touch"
/>
<woot-input
v-model.trim="description"
:class="{ error: $v.description.$error }"
class="medium-12 columns"
:label="$t('TEAMS_SETTINGS.FORM.DESCRIPTION.LABEL')"
:placeholder="$t('TEAMS_SETTINGS.FORM.DESCRIPTION.PLACEHOLDER')"
@input="$v.description.$touch"
/>
<div class="medium-12">
<input v-model="allowAutoAssign" type="checkbox" :value="true" />
<label for="conversation_creation">
{{ $t('TEAMS_SETTINGS.FORM.AUTO_ASSIGN.LABEL') }}
</label>
</div>
<div class="modal-footer">
<div class="medium-12 columns">
<woot-submit-button
:disabled="$v.title.$invalid || submitInProgress"
:button-text="submitButtonText"
:loading="submitInProgress"
/>
</div>
</div>
</form>
</div>
</div>
</template>
<script>
import WootSubmitButton from '../../../../components/buttons/FormSubmitButton';
import validations from './helpers/validations';
export default {
components: {
WootSubmitButton,
},
props: {
onSubmit: {
type: Function,
default: () => {},
},
submitInProgress: {
type: Boolean,
default: false,
},
formData: {
type: Object,
default: () => {},
},
submitButtonText: {
type: String,
default: '',
},
},
data() {
const formData = this.formData || {};
const {
description = '',
name: title = '',
allow_auto_assign: allowAutoAssign = true,
} = formData;
return {
description,
title,
allowAutoAssign,
};
},
validations,
methods: {
handleSubmit() {
this.$v.$touch();
if (this.$v.$invalid) {
return;
}
this.onSubmit({
description: this.description,
name: this.title,
allow_auto_assign: this.allowAutoAssign,
});
},
},
};
</script>

View file

@ -0,0 +1,10 @@
import { required, minLength } from 'vuelidate/lib/validators';
export default {
title: {
required,
minLength: minLength(2),
},
description: {},
showOnSidebar: {},
};

View file

@ -0,0 +1,91 @@
/* eslint arrow-body-style: 0 */
import SettingsContent from '../Wrapper';
import TeamsHome from './Index';
import CreateStepWrap from './Create/Index';
import EditStepWrap from './Edit/Index';
import CreateTeam from './Create/CreateTeam';
import EditTeam from './Edit/EditTeam';
import AddAgents from './Create/AddAgents';
import EditAgents from './Edit/EditAgents';
import FinishSetup from './FinishSetup';
import { frontendURL } from '../../../../helper/URLHelper';
export default {
routes: [
{
path: frontendURL('accounts/:accountId/settings/teams'),
component: SettingsContent,
props: params => {
const showBackButton = params.name !== 'settings_teams_list';
return {
headerTitle: 'TEAMS_SETTINGS.HEADER',
headerButtonText: 'TEAMS_SETTINGS.NEW_TEAM',
icon: 'ion-ios-people',
newButtonRoutes: ['settings_teams_new'],
showBackButton,
};
},
children: [
{
path: '',
name: 'settings_teams',
redirect: 'list',
},
{
path: 'list',
name: 'settings_teams_list',
component: TeamsHome,
roles: ['administrator'],
},
{
path: 'new',
component: CreateStepWrap,
children: [
{
path: '',
name: 'settings_teams_new',
component: CreateTeam,
roles: ['administrator'],
},
{
path: ':teamId/finish',
name: 'settings_teams_finish',
component: FinishSetup,
roles: ['administrator'],
},
{
path: ':teamId/agents',
name: 'settings_teams_add_agents',
roles: ['administrator'],
component: AddAgents,
},
],
},
{
path: ':teamId/edit',
component: EditStepWrap,
children: [
{
path: '',
name: 'settings_teams_edit',
component: EditTeam,
roles: ['administrator'],
},
{
path: 'agents',
name: 'settings_teams_edit_members',
component: EditAgents,
roles: ['administrator'],
},
{
path: 'finish',
name: 'settings_teams_edit_finish',
roles: ['administrator'],
component: FinishSetup,
},
],
},
],
},
],
};

View file

@ -56,4 +56,27 @@ describe('#actions', () => {
]); ]);
}); });
}); });
describe('#update', () => {
it('sends correct actions if API is success', async () => {
axios.patch.mockResolvedValue({ data: teamMembers });
await actions.update({ commit }, { agentsList: teamMembers, teamId: 1 });
expect(commit.mock.calls).toEqual([
[SET_TEAM_MEMBERS_UI_FLAG, { isUpdating: true }],
[ADD_AGENTS_TO_TEAM, { data: teamMembers }],
[SET_TEAM_MEMBERS_UI_FLAG, { isUpdating: false }],
]);
});
it('sends correct actions if API is error', async () => {
axios.patch.mockRejectedValue({ message: 'Incorrect header' });
await expect(
actions.update({ commit }, { agentsList: teamMembers, teamId: 1 })
).rejects.toThrow(Error);
expect(commit.mock.calls).toEqual([
[SET_TEAM_MEMBERS_UI_FLAG, { isUpdating: true }],
[SET_TEAM_MEMBERS_UI_FLAG, { isUpdating: false }],
]);
});
});
}); });

View file

@ -60,7 +60,7 @@ describe('#actions', () => {
describe('#update', () => { describe('#update', () => {
it('sends correct actions if API is success', async () => { it('sends correct actions if API is success', async () => {
axios.patch.mockResolvedValue({ data: { payload: teamsList[1] } }); axios.patch.mockResolvedValue({ data: teamsList[1] });
await actions.update({ commit }, teamsList[1]); await actions.update({ commit }, teamsList[1]);
expect(commit.mock.calls).toEqual([ expect(commit.mock.calls).toEqual([

View file

@ -46,6 +46,20 @@ export const actions = {
commit(SET_TEAM_MEMBERS_UI_FLAG, { isCreating: false }); commit(SET_TEAM_MEMBERS_UI_FLAG, { isCreating: false });
} }
}, },
update: async ({ commit }, { agentsList, teamId }) => {
commit(SET_TEAM_MEMBERS_UI_FLAG, { isUpdating: true });
try {
const response = await TeamsAPI.updateAgents({
agentsList,
teamId,
});
commit(ADD_AGENTS_TO_TEAM, response);
} catch (error) {
throw new Error(error);
} finally {
commit(SET_TEAM_MEMBERS_UI_FLAG, { isUpdating: false });
}
},
}; };
export const mutations = { export const mutations = {

View file

@ -56,7 +56,7 @@ export const actions = {
commit(SET_TEAM_UI_FLAG, { isUpdating: true }); commit(SET_TEAM_UI_FLAG, { isUpdating: true });
try { try {
const response = await TeamsAPI.update(id, updateObj); const response = await TeamsAPI.update(id, updateObj);
commit(EDIT_TEAM, response.data.payload); commit(EDIT_TEAM, response.data);
} catch (error) { } catch (error) {
throw new Error(error); throw new Error(error);
} finally { } finally {

View file

@ -11790,4 +11790,4 @@ yauzl@2.10.0, yauzl@^2.10.0:
integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
dependencies: dependencies:
buffer-crc32 "~0.2.3" buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0" fd-slicer "~1.1.0"