mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-02 12:08:01 +00:00
feat: Set up store for teams (#1689)
This commit is contained in:
committed by
GitHub
parent
cadb246eaa
commit
941d4219f0
@@ -11,5 +11,6 @@ describe('#TeamsAPI', () => {
|
||||
expect(teams).toHaveProperty('delete');
|
||||
expect(teams).toHaveProperty('getAgents');
|
||||
expect(teams).toHaveProperty('addAgents');
|
||||
expect(teams).toHaveProperty('updateAgents');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,6 +15,12 @@ export class TeamsAPI extends ApiClient {
|
||||
user_ids: agentsList,
|
||||
});
|
||||
}
|
||||
|
||||
updateAgents({ teamId, agentsList }) {
|
||||
return axios.patch(`${this.url}/${teamId}/team_members`, {
|
||||
user_ids: agentsList,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new TeamsAPI();
|
||||
|
||||
@@ -178,7 +178,6 @@ export default {
|
||||
icon: 'ion-ios-people',
|
||||
label: 'TEAMS',
|
||||
hasSubMenu: true,
|
||||
newLink: true,
|
||||
key: 'team',
|
||||
cssClass: 'menu-title align-justify teams-sidebar-menu',
|
||||
toState: frontendURL(`accounts/${this.accountId}/settings/teams`),
|
||||
|
||||
@@ -74,6 +74,7 @@ export default {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
border-left: 1px solid var(--color-border);
|
||||
background: var(--color-background-light);
|
||||
}
|
||||
|
||||
.messages-and-sidebar {
|
||||
|
||||
@@ -75,6 +75,13 @@ export const getSidebarItems = accountId => ({
|
||||
'settings_integrations_integration',
|
||||
'general_settings',
|
||||
'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: {
|
||||
back: {
|
||||
@@ -91,6 +98,13 @@ export const getSidebarItems = accountId => ({
|
||||
toState: frontendURL(`accounts/${accountId}/settings/agents/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: {
|
||||
icon: 'ion-archive',
|
||||
label: 'INBOXES',
|
||||
|
||||
@@ -13,6 +13,7 @@ import { default as _settings } from './settings.json';
|
||||
import { default as _signup } from './signup.json';
|
||||
import { default as _integrations } from './integrations.json';
|
||||
import { default as _generalSettings } from './generalSettings.json';
|
||||
import { default as _teamsSettings } from './teamsSettings.json';
|
||||
|
||||
export default {
|
||||
..._agentMgmt,
|
||||
@@ -30,4 +31,5 @@ export default {
|
||||
..._signup,
|
||||
..._integrations,
|
||||
..._generalSettings,
|
||||
..._teamsSettings,
|
||||
};
|
||||
|
||||
123
app/javascript/dashboard/i18n/locale/en/teamsSettings.json
Normal file
123
app/javascript/dashboard/i18n/locale/en/teamsSettings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -404,6 +404,7 @@ export default {
|
||||
this.selectedTabIndex = 0;
|
||||
this.selectedAgents = [];
|
||||
this.$store.dispatch('agents/get');
|
||||
this.$store.dispatch('teams/get');
|
||||
this.$store.dispatch('inboxes/get').then(() => {
|
||||
this.fetchAttachedAgents();
|
||||
this.avatarUrl = this.inbox.avatar_url;
|
||||
|
||||
@@ -7,6 +7,7 @@ import integrations from './integrations/integrations.routes';
|
||||
import labels from './labels/labels.routes';
|
||||
import profile from './profile/profile.routes';
|
||||
import reports from './reports/reports.routes';
|
||||
import teams from './teams/teams.routes';
|
||||
import store from '../../../store';
|
||||
|
||||
export default {
|
||||
@@ -30,5 +31,6 @@ export default {
|
||||
...labels.routes,
|
||||
...profile.routes,
|
||||
...reports.routes,
|
||||
...teams.routes,
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,10 @@
|
||||
import { required, minLength } from 'vuelidate/lib/validators';
|
||||
|
||||
export default {
|
||||
title: {
|
||||
required,
|
||||
minLength: minLength(2),
|
||||
},
|
||||
description: {},
|
||||
showOnSidebar: {},
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -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 }],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('#actions', () => {
|
||||
|
||||
describe('#update', () => {
|
||||
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]);
|
||||
|
||||
expect(commit.mock.calls).toEqual([
|
||||
|
||||
@@ -46,6 +46,20 @@ export const actions = {
|
||||
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 = {
|
||||
|
||||
@@ -56,7 +56,7 @@ export const actions = {
|
||||
commit(SET_TEAM_UI_FLAG, { isUpdating: true });
|
||||
try {
|
||||
const response = await TeamsAPI.update(id, updateObj);
|
||||
commit(EDIT_TEAM, response.data.payload);
|
||||
commit(EDIT_TEAM, response.data);
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user