feat: Adds the ability to edit/delete category (#5385)
This commit is contained in:
parent
a9801a3c76
commit
d47a0ae461
17 changed files with 667 additions and 37 deletions
|
@ -15,8 +15,11 @@ class CategoriesAPI extends PortalsAPI {
|
||||||
return axios.post(`${this.url}/${portalSlug}/categories`, categoryObj);
|
return axios.post(`${this.url}/${portalSlug}/categories`, categoryObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
update({ portalSlug, categoryObj }) {
|
update({ portalSlug, categoryId, categoryObj }) {
|
||||||
return axios.patch(`${this.url}/${portalSlug}/categories`, categoryObj);
|
return axios.patch(
|
||||||
|
`${this.url}/${portalSlug}/categories/${categoryId}`,
|
||||||
|
categoryObj
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete({ portalSlug, categoryId }) {
|
delete({ portalSlug, categoryId }) {
|
||||||
|
|
|
@ -139,6 +139,21 @@
|
||||||
"TITLE": "Locales"
|
"TITLE": "Locales"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"CATEGORIES": {
|
||||||
|
"TITLE": "Categories in",
|
||||||
|
"NEW_CATEGORY": "New category",
|
||||||
|
"TABLE": {
|
||||||
|
"NAME": "Name",
|
||||||
|
"DESCRIPTION": "Description",
|
||||||
|
"LOCALE": "Locale",
|
||||||
|
"ARTICLE_COUNT": "No. of articles",
|
||||||
|
"ACTION_BUTTON": {
|
||||||
|
"EDIT": "Edit category",
|
||||||
|
"DELETE": "Delete category"
|
||||||
|
},
|
||||||
|
"EMPTY_TEXT": "No categories found"
|
||||||
|
}
|
||||||
|
},
|
||||||
"EDIT_BASIC_INFO": {
|
"EDIT_BASIC_INFO": {
|
||||||
"BUTTON_TEXT": "Update basic settings"
|
"BUTTON_TEXT": "Update basic settings"
|
||||||
}
|
}
|
||||||
|
@ -353,6 +368,43 @@
|
||||||
"SUCCESS_MESSAGE": "Category created successfully",
|
"SUCCESS_MESSAGE": "Category created successfully",
|
||||||
"ERROR_MESSAGE": "Unable to create category"
|
"ERROR_MESSAGE": "Unable to create category"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"EDIT": {
|
||||||
|
"TITLE": "Edit a category",
|
||||||
|
"SUB_TITLE": "Editing a category will update the category in the public facing portal.",
|
||||||
|
"PORTAL": "Portal",
|
||||||
|
"LOCALE": "Locale",
|
||||||
|
"NAME": {
|
||||||
|
"LABEL": "Name",
|
||||||
|
"PLACEHOLDER": "Category name",
|
||||||
|
"HELP_TEXT": "The category name will be used in the public facing portal to categorize articles.",
|
||||||
|
"ERROR": "Name is required"
|
||||||
|
},
|
||||||
|
"SLUG": {
|
||||||
|
"LABEL": "Slug",
|
||||||
|
"PLACEHOLDER": "Category slug for urls",
|
||||||
|
"HELP_TEXT": "app.chatwoot.com/portal/my-portal/en-US/categories/my-slug",
|
||||||
|
"ERROR": "Slug is required"
|
||||||
|
},
|
||||||
|
"DESCRIPTION": {
|
||||||
|
"LABEL": "Description",
|
||||||
|
"PLACEHOLDER": "Give a short description about the category.",
|
||||||
|
"ERROR": "Description is required"
|
||||||
|
},
|
||||||
|
"BUTTONS": {
|
||||||
|
"CREATE": "Update category",
|
||||||
|
"CANCEL": "Cancel"
|
||||||
|
},
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Category updated successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to update category"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DELETE": {
|
||||||
|
"API": {
|
||||||
|
"SUCCESS_MESSAGE": "Category deleted successfully",
|
||||||
|
"ERROR_MESSAGE": "Unable to delete category"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
/>
|
/>
|
||||||
<add-category
|
<add-category
|
||||||
v-if="showAddCategoryModal"
|
v-if="showAddCategoryModal"
|
||||||
|
:show.sync="showAddCategoryModal"
|
||||||
:portal-name="selectedPortalName"
|
:portal-name="selectedPortalName"
|
||||||
:locale="selectedPortalLocale"
|
:locale="selectedPortalLocale"
|
||||||
@cancel="onClickCloseAddCategoryModal"
|
@cancel="onClickCloseAddCategoryModal"
|
||||||
|
@ -56,7 +57,7 @@ import CommandBar from 'dashboard/routes/dashboard/commands/commandbar.vue';
|
||||||
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal';
|
import WootKeyShortcutModal from 'dashboard/components/widgets/modal/WootKeyShortcutModal';
|
||||||
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel';
|
import NotificationPanel from 'dashboard/routes/dashboard/notifications/components/NotificationPanel';
|
||||||
import portalMixin from '../mixins/portalMixin';
|
import portalMixin from '../mixins/portalMixin';
|
||||||
import AddCategory from '../components/AddCategory.vue';
|
import AddCategory from '../pages/categories/AddCategory';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -182,6 +183,7 @@ export default {
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
additionalSecondaryMenuItems() {
|
additionalSecondaryMenuItems() {
|
||||||
|
if (!this.selectedPortal) return [];
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
|
|
|
@ -243,6 +243,7 @@ export default {
|
||||||
this.$emit('open-site');
|
this.$emit('open-site');
|
||||||
},
|
},
|
||||||
openSettings() {
|
openSettings() {
|
||||||
|
this.fetchPortalsAndItsCategories();
|
||||||
this.navigateToPortalEdit();
|
this.navigateToPortalEdit();
|
||||||
},
|
},
|
||||||
onClickOpenDeleteModal(portal) {
|
onClickOpenDeleteModal(portal) {
|
||||||
|
@ -252,6 +253,13 @@ export default {
|
||||||
closeDeletePopup() {
|
closeDeletePopup() {
|
||||||
this.showDeleteConfirmationPopup = false;
|
this.showDeleteConfirmationPopup = false;
|
||||||
},
|
},
|
||||||
|
fetchPortalsAndItsCategories() {
|
||||||
|
this.$store.dispatch('portals/index').then(() => {
|
||||||
|
this.$store.dispatch('categories/index', {
|
||||||
|
portalSlug: this.portal.slug,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
async onClickDeletePortal() {
|
async onClickDeletePortal() {
|
||||||
const { slug } = this.selectedPortalForDelete;
|
const { slug } = this.selectedPortalForDelete;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -92,8 +92,16 @@ export default {
|
||||||
this.selectedLocale = this.locale || this.portal?.meta?.default_locale;
|
this.selectedLocale = this.locale || this.portal?.meta?.default_locale;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
fetchPortalsAndItsCategories() {
|
||||||
|
this.$store.dispatch('portals/index').then(() => {
|
||||||
|
this.$store.dispatch('categories/index', {
|
||||||
|
portalSlug: this.portal.slug,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
onClick(event, code, portal) {
|
onClick(event, code, portal) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
this.fetchPortalsAndItsCategories();
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: 'list_all_locale_articles',
|
name: 'list_all_locale_articles',
|
||||||
params: {
|
params: {
|
||||||
|
|
|
@ -77,6 +77,12 @@ const portalRoutes = [
|
||||||
component: EditPortalCustomization,
|
component: EditPortalCustomization,
|
||||||
roles: ['administrator'],
|
roles: ['administrator'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: getPortalRoute(':portalSlug/edit/:locale/categories'),
|
||||||
|
name: 'list_all_locale_categories',
|
||||||
|
roles: ['administrator', 'agent'],
|
||||||
|
component: ListAllCategories,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -125,7 +131,7 @@ const articleRoutes = [
|
||||||
const categoryRoutes = [
|
const categoryRoutes = [
|
||||||
{
|
{
|
||||||
path: getPortalRoute(':portalSlug/:locale/categories'),
|
path: getPortalRoute(':portalSlug/:locale/categories'),
|
||||||
name: 'list_all_locale_categories',
|
name: 'all_locale_categories',
|
||||||
roles: ['administrator', 'agent'],
|
roles: ['administrator', 'agent'],
|
||||||
component: ListAllCategories,
|
component: ListAllCategories,
|
||||||
},
|
},
|
||||||
|
|
|
@ -132,11 +132,12 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
async addCategory() {
|
async addCategory() {
|
||||||
const { name, slug, description } = this;
|
const { name, slug, description, locale } = this;
|
||||||
const data = {
|
const data = {
|
||||||
name,
|
name,
|
||||||
slug,
|
slug,
|
||||||
description,
|
description,
|
||||||
|
locale,
|
||||||
};
|
};
|
||||||
this.$v.$touch();
|
this.$v.$touch();
|
||||||
if (this.$v.$invalid) {
|
if (this.$v.$invalid) {
|
|
@ -0,0 +1,123 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.EDIT.CATEGORIES.TABLE.NAME') }}
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.EDIT.CATEGORIES.TABLE.DESCRIPTION') }}
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.EDIT.CATEGORIES.TABLE.LOCALE') }}
|
||||||
|
</th>
|
||||||
|
<th scope="col">
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.EDIT.CATEGORIES.TABLE.ARTICLE_COUNT') }}
|
||||||
|
</th>
|
||||||
|
<th scope="col" />
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="100%" class="horizontal-line" />
|
||||||
|
</tr>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="category in categories" :key="category.id">
|
||||||
|
<td>
|
||||||
|
<span>{{ category.name }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span>{{ category.description }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span>{{ category.locale }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span>{{ category.meta.articles_count }}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<woot-button
|
||||||
|
v-tooltip.top-end="
|
||||||
|
$t(
|
||||||
|
'HELP_CENTER.PORTAL.EDIT.CATEGORIES.TABLE.ACTION_BUTTON.EDIT'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
size="tiny"
|
||||||
|
variant="smooth"
|
||||||
|
icon="edit"
|
||||||
|
color-scheme="secondary"
|
||||||
|
@click="editCategory(category)"
|
||||||
|
/>
|
||||||
|
<woot-button
|
||||||
|
v-tooltip.top-end="
|
||||||
|
$t(
|
||||||
|
'HELP_CENTER.PORTAL.EDIT.CATEGORIES.TABLE.ACTION_BUTTON.DELETE'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
size="tiny"
|
||||||
|
variant="smooth"
|
||||||
|
icon="delete"
|
||||||
|
color-scheme="alert"
|
||||||
|
@click="deleteCategory(category.id)"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<p v-if="categories.length === 0" class="empty-text">
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.EDIT.CATEGORIES.TABLE.EMPTY_TEXT') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
categories: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
editCategory(category) {
|
||||||
|
this.$emit('edit', category);
|
||||||
|
},
|
||||||
|
deleteCategory(categoryId) {
|
||||||
|
this.$emit('delete', categoryId);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
table {
|
||||||
|
thead tr th {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
text-transform: none;
|
||||||
|
color: var(--s-600);
|
||||||
|
padding-left: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
border-bottom: 0;
|
||||||
|
td {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.horizontal-line {
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--s-500);
|
||||||
|
font-size: var(--font-size-default);
|
||||||
|
margin-top: var(--space-three);
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,3 +1,198 @@
|
||||||
<template>
|
<template>
|
||||||
<div>Component to edit a category</div>
|
<woot-modal :show.sync="show" :on-close="onClose">
|
||||||
|
<woot-modal-header
|
||||||
|
:header-title="$t('HELP_CENTER.CATEGORY.EDIT.TITLE')"
|
||||||
|
:header-content="$t('HELP_CENTER.CATEGORY.EDIT.SUB_TITLE')"
|
||||||
|
/>
|
||||||
|
<form class="row" @submit.prevent="onUpdate">
|
||||||
|
<div class="medium-12 columns">
|
||||||
|
<div class="row article-info">
|
||||||
|
<div class="columns medium-6">
|
||||||
|
<label>
|
||||||
|
<span>{{ $t('HELP_CENTER.CATEGORY.EDIT.PORTAL') }}</span>
|
||||||
|
<p class="value">{{ portalName }}</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="columns medium-6">
|
||||||
|
<label>
|
||||||
|
<span>{{ $t('HELP_CENTER.CATEGORY.EDIT.LOCALE') }}</span>
|
||||||
|
<p class="value">{{ locale }}</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<woot-input
|
||||||
|
v-model.trim="name"
|
||||||
|
:class="{ error: $v.name.$error }"
|
||||||
|
class="medium-12 columns"
|
||||||
|
:error="nameError"
|
||||||
|
:label="$t('HELP_CENTER.CATEGORY.EDIT.NAME.LABEL')"
|
||||||
|
:placeholder="$t('HELP_CENTER.CATEGORY.EDIT.NAME.PLACEHOLDER')"
|
||||||
|
:help-text="$t('HELP_CENTER.CATEGORY.EDIT.NAME.HELP_TEXT')"
|
||||||
|
@input="onNameChange"
|
||||||
|
/>
|
||||||
|
<woot-input
|
||||||
|
v-model.trim="slug"
|
||||||
|
:class="{ error: $v.slug.$error }"
|
||||||
|
class="medium-12 columns"
|
||||||
|
:error="slugError"
|
||||||
|
:label="$t('HELP_CENTER.CATEGORY.EDIT.SLUG.LABEL')"
|
||||||
|
:placeholder="$t('HELP_CENTER.CATEGORY.EDIT.SLUG.PLACEHOLDER')"
|
||||||
|
:help-text="$t('HELP_CENTER.CATEGORY.EDIT.SLUG.HELP_TEXT')"
|
||||||
|
@input="$v.slug.$touch"
|
||||||
|
/>
|
||||||
|
<label>
|
||||||
|
{{ $t('HELP_CENTER.CATEGORY.EDIT.DESCRIPTION.LABEL') }}
|
||||||
|
<textarea
|
||||||
|
v-model="description"
|
||||||
|
rows="3"
|
||||||
|
type="text"
|
||||||
|
:placeholder="
|
||||||
|
$t('HELP_CENTER.CATEGORY.EDIT.DESCRIPTION.PLACEHOLDER')
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div class="medium-12 columns">
|
||||||
|
<div class="modal-footer justify-content-end w-full">
|
||||||
|
<woot-button class="button clear" @click.prevent="onClose">
|
||||||
|
{{ $t('HELP_CENTER.CATEGORY.EDIT.BUTTONS.CANCEL') }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button @click="editCategory">
|
||||||
|
{{ $t('HELP_CENTER.CATEGORY.EDIT.BUTTONS.CREATE') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</woot-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import { required, minLength } from 'vuelidate/lib/validators';
|
||||||
|
import { convertToCategorySlug } from 'dashboard/helper/commons.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [alertMixin],
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
portalName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
locale: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
category: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
selectedPortalSlug: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
id: this.category.id,
|
||||||
|
name: '',
|
||||||
|
slug: '',
|
||||||
|
description: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
validations: {
|
||||||
|
name: {
|
||||||
|
required,
|
||||||
|
minLength: minLength(2),
|
||||||
|
},
|
||||||
|
slug: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
nameError() {
|
||||||
|
if (this.$v.name.$error) {
|
||||||
|
return this.$t('HELP_CENTER.CATEGORY.ADD.NAME.ERROR');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
slugError() {
|
||||||
|
if (this.$v.slug.$error) {
|
||||||
|
return this.$t('HELP_CENTER.CATEGORY.ADD.SLUG.ERROR');
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.updateDataFromStore();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
updateDataFromStore() {
|
||||||
|
const { category } = this;
|
||||||
|
this.name = category.name;
|
||||||
|
this.slug = category.slug;
|
||||||
|
this.description = category.description;
|
||||||
|
},
|
||||||
|
onNameChange() {
|
||||||
|
this.slug = convertToCategorySlug(this.name);
|
||||||
|
},
|
||||||
|
onUpdate() {
|
||||||
|
this.$emit('update');
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
this.$emit('cancel');
|
||||||
|
},
|
||||||
|
async editCategory() {
|
||||||
|
const { id, name, slug, description } = this;
|
||||||
|
const data = {
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
description,
|
||||||
|
};
|
||||||
|
this.$v.$touch();
|
||||||
|
if (this.$v.$invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('categories/update', {
|
||||||
|
portalSlug: this.selectedPortalSlug,
|
||||||
|
categoryId: id,
|
||||||
|
categoryObj: data,
|
||||||
|
});
|
||||||
|
this.alertMessage = this.$t(
|
||||||
|
'HELP_CENTER.CATEGORY.EDIT.API.SUCCESS_MESSAGE'
|
||||||
|
);
|
||||||
|
this.onClose();
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error?.message;
|
||||||
|
this.alertMessage =
|
||||||
|
errorMessage ||
|
||||||
|
this.$t('HELP_CENTER.CATEGORY.EDIT.API.ERROR_MESSAGE');
|
||||||
|
} finally {
|
||||||
|
this.showAlert(this.alertMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.article-info {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 0 var(--space-normal);
|
||||||
|
.value {
|
||||||
|
color: var(--s-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container::v-deep {
|
||||||
|
margin: 0 0 var(--space-normal);
|
||||||
|
|
||||||
|
input {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -1,3 +1,194 @@
|
||||||
<template>
|
<template>
|
||||||
<div>Component to list all categories</div>
|
<div class="category-list--container">
|
||||||
|
<header>
|
||||||
|
<div class="header-left--wrap">
|
||||||
|
<label class="sub-block-title header-text">{{
|
||||||
|
$t('HELP_CENTER.PORTAL.EDIT.CATEGORIES.TITLE')
|
||||||
|
}}</label>
|
||||||
|
<select
|
||||||
|
:value="currentLocaleCode"
|
||||||
|
class="row small-2 select-locale"
|
||||||
|
@change="changeCurrentCategory"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="allowedLocaleCode in allowedLocaleCodes"
|
||||||
|
:key="allowedLocaleCode"
|
||||||
|
:value="allowedLocaleCode"
|
||||||
|
>
|
||||||
|
{{ allowedLocaleCode }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="header-right--wrap">
|
||||||
|
<woot-button
|
||||||
|
size="small"
|
||||||
|
variant="smooth"
|
||||||
|
color-scheme="primary"
|
||||||
|
icon="add"
|
||||||
|
@click="openAddCategoryModal"
|
||||||
|
>
|
||||||
|
{{ $t('HELP_CENTER.PORTAL.EDIT.CATEGORIES.NEW_CATEGORY') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="category-list">
|
||||||
|
<category-list-item
|
||||||
|
:categories="categoryByLocaleCode"
|
||||||
|
@delete="deleteCategory"
|
||||||
|
@edit="openEditCategoryModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<edit-category
|
||||||
|
v-if="showEditCategoryModal"
|
||||||
|
:show.sync="showEditCategoryModal"
|
||||||
|
:portal-name="currentPortalName"
|
||||||
|
:locale="selectedCategory.locale"
|
||||||
|
:category="selectedCategory"
|
||||||
|
:selected-portal-slug="currentPortalSlug"
|
||||||
|
@cancel="closeEditCategoryModal"
|
||||||
|
/>
|
||||||
|
<add-category
|
||||||
|
v-if="showAddCategoryModal"
|
||||||
|
:show.sync="showAddCategoryModal"
|
||||||
|
:portal-name="currentPortalName"
|
||||||
|
:locale="currentLocaleCode"
|
||||||
|
@cancel="closeAddCategoryModal"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import portalMixin from '../../mixins/portalMixin';
|
||||||
|
|
||||||
|
import CategoryListItem from './CategoryListItem';
|
||||||
|
import AddCategory from './AddCategory';
|
||||||
|
import EditCategory from './EditCategory';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
CategoryListItem,
|
||||||
|
AddCategory,
|
||||||
|
EditCategory,
|
||||||
|
},
|
||||||
|
mixins: [alertMixin, portalMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
selectedCategory: {},
|
||||||
|
selectedLocaleCode: '',
|
||||||
|
currentLocaleCode: 'en',
|
||||||
|
showEditCategoryModal: false,
|
||||||
|
showAddCategoryModal: false,
|
||||||
|
alertMessage: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
portals: 'portals/allPortals',
|
||||||
|
meta: 'portals/getMeta',
|
||||||
|
isFetching: 'portals/isFetchingPortals',
|
||||||
|
}),
|
||||||
|
currentPortalSlug() {
|
||||||
|
return this.$route.params.portalSlug;
|
||||||
|
},
|
||||||
|
categoryByLocaleCode() {
|
||||||
|
return this.$store.getters['categories/categoriesByLocaleCode'](
|
||||||
|
this.currentLocaleCode
|
||||||
|
);
|
||||||
|
},
|
||||||
|
currentPortal() {
|
||||||
|
const slug = this.currentPortalSlug;
|
||||||
|
if (slug) return this.$store.getters['portals/portalBySlug'](slug);
|
||||||
|
|
||||||
|
return this.$store.getters['portals/allPortals'][0];
|
||||||
|
},
|
||||||
|
currentPortalName() {
|
||||||
|
return this.currentPortal ? this.currentPortal.name : '';
|
||||||
|
},
|
||||||
|
currentPortalLocale() {
|
||||||
|
return this.currentPortal ? this.currentPortal?.meta?.default_locale : '';
|
||||||
|
},
|
||||||
|
allLocales() {
|
||||||
|
return this.currentPortal
|
||||||
|
? this.currentPortal.config.allowed_locales
|
||||||
|
: [];
|
||||||
|
},
|
||||||
|
allowedLocaleCodes() {
|
||||||
|
return this.allLocales.map(locale => locale.code);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openAddCategoryModal() {
|
||||||
|
this.showAddCategoryModal = true;
|
||||||
|
},
|
||||||
|
openEditCategoryModal(category) {
|
||||||
|
this.selectedCategory = category;
|
||||||
|
this.showEditCategoryModal = true;
|
||||||
|
},
|
||||||
|
closeAddCategoryModal() {
|
||||||
|
this.showAddCategoryModal = false;
|
||||||
|
},
|
||||||
|
closeEditCategoryModal() {
|
||||||
|
this.showEditCategoryModal = false;
|
||||||
|
},
|
||||||
|
async deleteCategory(categoryId) {
|
||||||
|
try {
|
||||||
|
await this.$store.dispatch('categories/delete', {
|
||||||
|
portalSlug: this.currentPortalSlug,
|
||||||
|
categoryId: categoryId,
|
||||||
|
});
|
||||||
|
this.alertMessage = this.$t(
|
||||||
|
'HELP_CENTER.CATEGORY.DELETE.API.SUCCESS_MESSAGE'
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error?.message;
|
||||||
|
this.alertMessage =
|
||||||
|
errorMessage ||
|
||||||
|
this.$t('HELP_CENTER.CATEGORY.DELETE.API.ERROR_MESSAGE');
|
||||||
|
} finally {
|
||||||
|
this.showAlert(this.alertMessage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
changeCurrentCategory(event) {
|
||||||
|
const localeCode = event.target.value;
|
||||||
|
this.currentLocaleCode = localeCode;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.category-list--container {
|
||||||
|
width: 100%;
|
||||||
|
padding-left: var(--space-normal);
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: var(--space-normal);
|
||||||
|
|
||||||
|
.header-left--wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.header-text {
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
margin-right: var(--space-slab);
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right--wrap {
|
||||||
|
flex: none;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.select-locale {
|
||||||
|
height: var(--space-large);
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-top: var(--space-micro);
|
||||||
|
padding-bottom: var(--space-micro);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -49,7 +49,8 @@ export default {
|
||||||
}),
|
}),
|
||||||
currentPortal() {
|
currentPortal() {
|
||||||
const slug = this.$route.params.portalSlug;
|
const slug = this.$route.params.portalSlug;
|
||||||
return this.$store.getters['portals/portalBySlug'](slug);
|
if (slug) return this.$store.getters['portals/portalBySlug'](slug);
|
||||||
|
return this.$store.getters['portals/allPortals'][0];
|
||||||
},
|
},
|
||||||
tabs() {
|
tabs() {
|
||||||
const tabs = [
|
const tabs = [
|
||||||
|
@ -64,7 +65,7 @@ export default {
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'categories',
|
key: `list_all_locale_categories`,
|
||||||
name: this.$t('HELP_CENTER.PORTAL.EDIT.TABS.CATEGORY_SETTINGS.TITLE'),
|
name: this.$t('HELP_CENTER.PORTAL.EDIT.TABS.CATEGORY_SETTINGS.TITLE'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -81,6 +82,9 @@ export default {
|
||||||
portalName() {
|
portalName() {
|
||||||
return this.currentPortal ? this.currentPortal.name : '';
|
return this.currentPortal ? this.currentPortal.name : '';
|
||||||
},
|
},
|
||||||
|
currentPortalLocale() {
|
||||||
|
return this.currentPortal ? this.currentPortal?.meta?.default_locale : '';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onTabChange(index) {
|
onTabChange(index) {
|
||||||
|
@ -89,7 +93,7 @@ export default {
|
||||||
|
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
name: nextRoute,
|
name: nextRoute,
|
||||||
params: { portalSlug: slug },
|
params: { portalSlug: slug, locale: this.currentPortalLocale },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -99,14 +103,7 @@ export default {
|
||||||
.wrapper {
|
.wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
.container {
|
::v-deep .tabs {
|
||||||
display: flex;
|
padding-left: 0;
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.wizard-box {
|
|
||||||
border-right: 1px solid var(--s-25);
|
|
||||||
::v-deep .item {
|
|
||||||
background: var(--white);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -9,6 +9,7 @@ export const actions = {
|
||||||
const {
|
const {
|
||||||
data: { payload },
|
data: { payload },
|
||||||
} = await categoriesAPI.get({ portalSlug });
|
} = await categoriesAPI.get({ portalSlug });
|
||||||
|
commit(types.CLEAR_CATEGORIES);
|
||||||
const categoryIds = payload.map(category => category.id);
|
const categoryIds = payload.map(category => category.id);
|
||||||
commit(types.ADD_MANY_CATEGORIES, payload);
|
commit(types.ADD_MANY_CATEGORIES, payload);
|
||||||
commit(types.ADD_MANY_CATEGORIES_ID, categoryIds);
|
commit(types.ADD_MANY_CATEGORIES_ID, categoryIds);
|
||||||
|
@ -39,8 +40,7 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
update: async ({ commit }, portalSlug, categoryObj) => {
|
update: async ({ commit }, { portalSlug, categoryId, categoryObj }) => {
|
||||||
const categoryId = categoryObj.id;
|
|
||||||
commit(types.ADD_CATEGORY_FLAG, {
|
commit(types.ADD_CATEGORY_FLAG, {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isUpdating: true,
|
isUpdating: true,
|
||||||
|
@ -48,8 +48,14 @@ export const actions = {
|
||||||
categoryId,
|
categoryId,
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
const { data } = await categoriesAPI.update({ portalSlug, categoryObj });
|
const {
|
||||||
commit(types.UPDATE_CATEGORY, data);
|
data: { payload },
|
||||||
|
} = await categoriesAPI.update({
|
||||||
|
portalSlug,
|
||||||
|
categoryId,
|
||||||
|
categoryObj,
|
||||||
|
});
|
||||||
|
commit(types.UPDATE_CATEGORY, payload);
|
||||||
return categoryId;
|
return categoryId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return throwErrorMessage(error);
|
return throwErrorMessage(error);
|
||||||
|
@ -63,7 +69,7 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: async ({ commit }, portalSlug, categoryId) => {
|
delete: async ({ commit }, { portalSlug, categoryId }) => {
|
||||||
commit(types.ADD_CATEGORY_FLAG, {
|
commit(types.ADD_CATEGORY_FLAG, {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isDeleting: true,
|
isDeleting: true,
|
||||||
|
|
|
@ -18,6 +18,13 @@ export const getters = {
|
||||||
});
|
});
|
||||||
return categories;
|
return categories;
|
||||||
},
|
},
|
||||||
|
categoriesByLocaleCode: (...getterArguments) => localeCode => {
|
||||||
|
const [state, _getters] = getterArguments;
|
||||||
|
const categories = state.categories.allIds.map(id => {
|
||||||
|
return _getters.categoryById(id);
|
||||||
|
});
|
||||||
|
return categories.filter(category => category.locale === localeCode);
|
||||||
|
},
|
||||||
getMeta: state => {
|
getMeta: state => {
|
||||||
return state.meta;
|
return state.meta;
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,7 @@ export const mutations = {
|
||||||
[types.CLEAR_CATEGORIES]: $state => {
|
[types.CLEAR_CATEGORIES]: $state => {
|
||||||
Vue.set($state.categories, 'byId', {});
|
Vue.set($state.categories, 'byId', {});
|
||||||
Vue.set($state.categories, 'allIds', []);
|
Vue.set($state.categories, 'allIds', []);
|
||||||
Vue.set($state.categories, 'uiFlags', {});
|
Vue.set($state.categories.uiFlags, 'byId', {});
|
||||||
},
|
},
|
||||||
[types.ADD_MANY_CATEGORIES]($state, categories) {
|
[types.ADD_MANY_CATEGORIES]($state, categories) {
|
||||||
const allCategories = { ...$state.categories.byId };
|
const allCategories = { ...$state.categories.byId };
|
||||||
|
|
|
@ -13,6 +13,7 @@ describe('#actions', () => {
|
||||||
await actions.index({ commit }, { portalSlug: 'room-rental' });
|
await actions.index({ commit }, { portalSlug: 'room-rental' });
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[types.default.SET_UI_FLAG, { isFetching: true }],
|
[types.default.SET_UI_FLAG, { isFetching: true }],
|
||||||
|
[types.default.CLEAR_CATEGORIES],
|
||||||
[types.default.ADD_MANY_CATEGORIES, categoriesPayload.payload],
|
[types.default.ADD_MANY_CATEGORIES, categoriesPayload.payload],
|
||||||
[types.default.ADD_MANY_CATEGORIES_ID, [1, 2]],
|
[types.default.ADD_MANY_CATEGORIES_ID, [1, 2]],
|
||||||
[types.default.SET_UI_FLAG, { isFetching: false }],
|
[types.default.SET_UI_FLAG, { isFetching: false }],
|
||||||
|
@ -57,21 +58,34 @@ 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: categoriesPayload.payload[0] });
|
axios.patch.mockResolvedValue({ data: categoriesPayload });
|
||||||
await actions.update(
|
await actions.update(
|
||||||
{ commit },
|
{ commit },
|
||||||
'web-docs',
|
{
|
||||||
categoriesPayload.payload[0]
|
portalSlug: 'room-rental',
|
||||||
|
categoryId: 1,
|
||||||
|
categoryObj: categoriesPayload.payload[0],
|
||||||
|
}
|
||||||
);
|
);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
types.default.ADD_CATEGORY_FLAG,
|
types.default.ADD_CATEGORY_FLAG,
|
||||||
{ uiFlags: { isUpdating: true }, categoryId: 1 },
|
{
|
||||||
|
uiFlags: {
|
||||||
|
isUpdating: true,
|
||||||
|
},
|
||||||
|
categoryId: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
[types.default.UPDATE_CATEGORY, categoriesPayload.payload[0]],
|
[types.default.UPDATE_CATEGORY, categoriesPayload.payload],
|
||||||
[
|
[
|
||||||
types.default.ADD_CATEGORY_FLAG,
|
types.default.ADD_CATEGORY_FLAG,
|
||||||
{ uiFlags: { isUpdating: false }, categoryId: 1 },
|
{
|
||||||
|
uiFlags: {
|
||||||
|
isUpdating: false,
|
||||||
|
},
|
||||||
|
categoryId: 1,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
@ -79,7 +93,14 @@ describe('#actions', () => {
|
||||||
it('sends correct actions if API is error', async () => {
|
it('sends correct actions if API is error', async () => {
|
||||||
axios.patch.mockRejectedValue({ message: 'Incorrect header' });
|
axios.patch.mockRejectedValue({ message: 'Incorrect header' });
|
||||||
await expect(
|
await expect(
|
||||||
actions.update({ commit }, 'web-docs', categoriesPayload.payload[0])
|
actions.update(
|
||||||
|
{ commit },
|
||||||
|
{
|
||||||
|
portalSlug: 'room-rental',
|
||||||
|
categoryId: 1,
|
||||||
|
categoryObj: categoriesPayload.payload[0],
|
||||||
|
}
|
||||||
|
)
|
||||||
).rejects.toThrow(Error);
|
).rejects.toThrow(Error);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
|
@ -96,11 +117,13 @@ describe('#actions', () => {
|
||||||
|
|
||||||
describe('#delete', () => {
|
describe('#delete', () => {
|
||||||
it('sends correct actions if API is success', async () => {
|
it('sends correct actions if API is success', async () => {
|
||||||
axios.delete.mockResolvedValue({ data: categoriesPayload.payload[0] });
|
axios.delete.mockResolvedValue({ data: categoriesPayload });
|
||||||
await actions.delete(
|
await actions.delete(
|
||||||
{ commit },
|
{ commit },
|
||||||
'portal-slug',
|
{
|
||||||
categoriesPayload.payload[0].id
|
portalSlug: 'room-rental',
|
||||||
|
categoryId: categoriesPayload.payload[0].id,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
[
|
[
|
||||||
|
@ -120,8 +143,10 @@ describe('#actions', () => {
|
||||||
await expect(
|
await expect(
|
||||||
actions.delete(
|
actions.delete(
|
||||||
{ commit },
|
{ commit },
|
||||||
'portal-slug',
|
{
|
||||||
categoriesPayload.payload[0].id
|
portalSlug: 'room-rental',
|
||||||
|
categoryId: categoriesPayload.payload[0].id,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
).rejects.toThrow(Error);
|
).rejects.toThrow(Error);
|
||||||
expect(commit.mock.calls).toEqual([
|
expect(commit.mock.calls).toEqual([
|
||||||
|
|
|
@ -19,6 +19,10 @@ describe('#getters', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('categoriesByLocaleCode', () => {
|
||||||
|
expect(getters.categoriesByLocaleCode(state, getters)('en_US')).toEqual([]);
|
||||||
|
});
|
||||||
|
|
||||||
it('isFetchingCategories', () => {
|
it('isFetchingCategories', () => {
|
||||||
expect(getters.isFetching(state)).toEqual(true);
|
expect(getters.isFetching(state)).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
|
@ -234,12 +234,14 @@ export default {
|
||||||
REMOVE_ARTICLE: 'REMOVE_ARTICLE',
|
REMOVE_ARTICLE: 'REMOVE_ARTICLE',
|
||||||
REMOVE_ARTICLE_ID: 'REMOVE_ARTICLE_ID',
|
REMOVE_ARTICLE_ID: 'REMOVE_ARTICLE_ID',
|
||||||
SET_UI_FLAG: 'SET_UI_FLAG',
|
SET_UI_FLAG: 'SET_UI_FLAG',
|
||||||
|
|
||||||
// Help Center -- Categories
|
// Help Center -- Categories
|
||||||
ADD_CATEGORY: 'ADD_CATEGORY',
|
ADD_CATEGORY: 'ADD_CATEGORY',
|
||||||
ADD_CATEGORY_ID: 'ADD_CATEGORY_ID',
|
ADD_CATEGORY_ID: 'ADD_CATEGORY_ID',
|
||||||
ADD_MANY_CATEGORIES: 'ADD_MANY_CATEGORIES',
|
ADD_MANY_CATEGORIES: 'ADD_MANY_CATEGORIES',
|
||||||
ADD_MANY_CATEGORIES_ID: 'ADD_MANY_CATEGORIES_ID',
|
ADD_MANY_CATEGORIES_ID: 'ADD_MANY_CATEGORIES_ID',
|
||||||
ADD_CATEGORY_FLAG: 'ADD_CATEGORY_FLAG',
|
ADD_CATEGORY_FLAG: 'ADD_CATEGORY_FLAG',
|
||||||
|
CLEAR_CATEGORIES: 'CLEAR_CATEGORIES',
|
||||||
UPDATE_CATEGORY: 'UPDATE_CATEGORY',
|
UPDATE_CATEGORY: 'UPDATE_CATEGORY',
|
||||||
REMOVE_CATEGORY: 'REMOVE_CATEGORY',
|
REMOVE_CATEGORY: 'REMOVE_CATEGORY',
|
||||||
REMOVE_CATEGORY_ID: 'REMOVE_CATEGORY_ID',
|
REMOVE_CATEGORY_ID: 'REMOVE_CATEGORY_ID',
|
||||||
|
|
Loading…
Reference in a new issue