feat: Article list view page (#5122)
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<template>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="row--article-block">
|
||||
<div class="article-block">
|
||||
<h6 class="sub-block-title text-truncate">
|
||||
<router-link class="article-name" :to="articlePath">
|
||||
{{ title }}
|
||||
</router-link>
|
||||
</h6>
|
||||
<div class="author">
|
||||
<span class="by">{{ $t('HELP_CENTER.TABLE.COLUMNS.BY') }}</span>
|
||||
<span class="name">{{ articleAuthorName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ category }}</td>
|
||||
<td>{{ readCount }}</td>
|
||||
<td>
|
||||
<Label :title="status" :color-scheme="labelColor" />
|
||||
</td>
|
||||
<td>{{ lastUpdatedAt }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
<script>
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
import Label from 'dashboard/components/ui/Label';
|
||||
import timeMixin from 'dashboard/mixins/time';
|
||||
export default {
|
||||
components: {
|
||||
Label,
|
||||
},
|
||||
mixins: [timeMixin],
|
||||
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
required: true,
|
||||
},
|
||||
author: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
category: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
readCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
default: 'draft',
|
||||
values: ['archived', 'draft', 'published'],
|
||||
},
|
||||
updatedAt: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
lastUpdatedAt() {
|
||||
return this.dynamicTime(this.updatedAt);
|
||||
},
|
||||
articleAuthorName() {
|
||||
return this.author.name;
|
||||
},
|
||||
labelColor() {
|
||||
switch (this.status) {
|
||||
case 'archived':
|
||||
return 'secondary';
|
||||
case 'draft':
|
||||
return 'warning';
|
||||
default:
|
||||
return 'success';
|
||||
}
|
||||
},
|
||||
articlePath() {
|
||||
return frontendURL(`accounts/${this.accountId}/hc/articles/${this.id}`);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
td {
|
||||
font-weight: var(--font-weight-normal);
|
||||
color: var(--s-700);
|
||||
font-size: var(--font-size-mini);
|
||||
padding-left: 0;
|
||||
}
|
||||
.row--article-block {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
text-align: left;
|
||||
|
||||
.article-block {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sub-block-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.article-name {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-default);
|
||||
margin: 0;
|
||||
text-transform: capitalize;
|
||||
color: var(--s-900);
|
||||
}
|
||||
.author {
|
||||
.by {
|
||||
font-weight: var(--font-weight-normal);
|
||||
color: var(--s-500);
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.name {
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--s-600);
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="article-container">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{{ $t('HELP_CENTER.TABLE.HEADERS.TITLE') }}</th>
|
||||
<th scope="col">{{ $t('HELP_CENTER.TABLE.HEADERS.CATEGORY') }}</th>
|
||||
<th scope="col">{{ $t('HELP_CENTER.TABLE.HEADERS.READ_COUNT') }}</th>
|
||||
<th scope="col">{{ $t('HELP_CENTER.TABLE.HEADERS.STATUS') }}</th>
|
||||
<th scope="col">{{ $t('HELP_CENTER.TABLE.HEADERS.LAST_EDITED') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr>
|
||||
<td colspan="100%" class="horizontal-line" />
|
||||
</tr>
|
||||
<tbody>
|
||||
<ArticleItem
|
||||
v-for="article in articles"
|
||||
:key="article.id"
|
||||
:title="article.title"
|
||||
:author="article.author"
|
||||
:category="article.category"
|
||||
:read-count="article.readCount"
|
||||
:status="article.status"
|
||||
:updated-at="article.updatedAt"
|
||||
/>
|
||||
</tbody>
|
||||
</table>
|
||||
<table-footer
|
||||
:on-page-change="onPageChange"
|
||||
:current-page="Number(currentPage)"
|
||||
:total-count="articleCount"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArticleItem from './ArticleItem.vue';
|
||||
import TableFooter from 'dashboard/components/widgets/TableFooter';
|
||||
export default {
|
||||
components: {
|
||||
ArticleItem,
|
||||
TableFooter,
|
||||
},
|
||||
props: {
|
||||
articles: {
|
||||
type: Array,
|
||||
default: () => {},
|
||||
},
|
||||
articleCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
onPageChange() {
|
||||
this.$emit('onPageChange');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.article-container {
|
||||
width: 100%;
|
||||
|
||||
table thead th {
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-transform: capitalize;
|
||||
color: var(--s-700);
|
||||
font-size: var(--font-size-small);
|
||||
padding-left: 0;
|
||||
}
|
||||
.horizontal-line {
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
.footer {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -6,8 +6,6 @@
|
||||
@open-key-shortcut-modal="toggleKeyShortcutModal"
|
||||
@close-key-shortcut-modal="closeKeyShortcutModal"
|
||||
/>
|
||||
|
||||
<!-- TO BE REPLACED WITH HELPCENTER SIDEBAR -->
|
||||
<div class="margin-right-small">
|
||||
<help-center-sidebar
|
||||
header-title="Help Center"
|
||||
@@ -16,8 +14,6 @@
|
||||
:additional-secondary-menu-items="additionalSecondaryMenuItems"
|
||||
/>
|
||||
</div>
|
||||
<!-- END: TO BE REPLACED WITH HELPCENTER SIDEBAR -->
|
||||
|
||||
<section class="app-content columns">
|
||||
<router-view />
|
||||
<command-bar />
|
||||
@@ -61,24 +57,23 @@ export default {
|
||||
...mapGetters({
|
||||
accountId: 'getCurrentAccountId',
|
||||
}),
|
||||
// For testing
|
||||
accessibleMenuItems() {
|
||||
return [
|
||||
{
|
||||
icon: 'book',
|
||||
label: 'HELP_CENTER.ALL_ARTICLES',
|
||||
key: 'helpcenter_all',
|
||||
key: 'list_all_locale_articles',
|
||||
count: 199,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/portals/:portalSlug/:locale/articles`
|
||||
),
|
||||
toolTip: 'All Articles',
|
||||
toStateName: 'all_locale_articles',
|
||||
toStateName: 'list_all_locale_articles',
|
||||
},
|
||||
{
|
||||
icon: 'pen',
|
||||
label: 'HELP_CENTER.MY_ARTICLES',
|
||||
key: 'helpcenter_mine',
|
||||
key: 'mine_articles',
|
||||
count: 112,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/portals/:portalSlug/:locale/articles/mine`
|
||||
@@ -89,24 +84,24 @@ export default {
|
||||
{
|
||||
icon: 'draft',
|
||||
label: 'HELP_CENTER.DRAFT',
|
||||
key: 'helpcenter_draft',
|
||||
key: 'list_draft_articles',
|
||||
count: 32,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/portals/:portalSlug/:locale/articles/draft`
|
||||
),
|
||||
toolTip: 'Draft',
|
||||
toStateName: 'draft_articles',
|
||||
toStateName: 'list_draft_articles',
|
||||
},
|
||||
{
|
||||
icon: 'archive',
|
||||
label: 'HELP_CENTER.ARCHIVED',
|
||||
key: 'helpcenter_archive',
|
||||
key: 'list_archived_articles',
|
||||
count: 10,
|
||||
toState: frontendURL(
|
||||
`accounts/${this.accountId}/portals/:portalSlug/:locale/articles/archived`
|
||||
),
|
||||
toolTip: 'Archived',
|
||||
toStateName: 'archived_articles',
|
||||
toStateName: 'list_archived_articles',
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import ArticleItemComponent from '../ArticleItem.vue';
|
||||
const STATUS_LIST = {
|
||||
published: 'published',
|
||||
draft: 'draft',
|
||||
archived: 'archived',
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'Components/Help Center',
|
||||
component: ArticleItemComponent,
|
||||
argTypes: {
|
||||
title: {
|
||||
defaultValue: 'Setup your account',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
readCount: {
|
||||
defaultValue: 13,
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
category: {
|
||||
defaultValue: 'Getting started',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
status: {
|
||||
defaultValue: 'Status',
|
||||
control: {
|
||||
type: 'select',
|
||||
options: STATUS_LIST,
|
||||
},
|
||||
},
|
||||
updatedAt: {
|
||||
defaultValue: '1657255863',
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleItemComponent },
|
||||
template:
|
||||
'<article-item-component v-bind="$props" ></article-item-component>',
|
||||
});
|
||||
|
||||
export const ArticleItem = Template.bind({});
|
||||
ArticleItem.args = {
|
||||
title: 'Setup your account',
|
||||
author: {
|
||||
name: 'John Doe',
|
||||
},
|
||||
category: 'Getting started',
|
||||
readCount: 12,
|
||||
status: 'published',
|
||||
updatedAt: 1657255863,
|
||||
};
|
||||
@@ -0,0 +1,72 @@
|
||||
import ArticleTableComponent from '../ArticleTable.vue';
|
||||
import { action } from '@storybook/addon-actions';
|
||||
export default {
|
||||
title: 'Components/Help Center',
|
||||
component: ArticleTableComponent,
|
||||
argTypes: {
|
||||
articles: {
|
||||
defaultValue: [],
|
||||
control: {
|
||||
type: 'array',
|
||||
},
|
||||
},
|
||||
articleCount: {
|
||||
defaultValue: 10,
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
currentPage: {
|
||||
defaultValue: 1,
|
||||
control: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleTableComponent },
|
||||
template:
|
||||
'<article-table-component @onPageChange="onPageChange" v-bind="$props" ></article-table-component>',
|
||||
});
|
||||
|
||||
export const ArticleTable = Template.bind({});
|
||||
ArticleTable.args = {
|
||||
articles: [
|
||||
{
|
||||
title: 'Setup your account',
|
||||
author: {
|
||||
name: 'John Doe',
|
||||
},
|
||||
readCount: 13,
|
||||
category: 'Getting started',
|
||||
status: 'published',
|
||||
updatedAt: 1657255863,
|
||||
},
|
||||
{
|
||||
title: 'Docker Configuration',
|
||||
author: {
|
||||
name: 'Sam Manuel',
|
||||
},
|
||||
readCount: 13,
|
||||
category: 'Engineering',
|
||||
status: 'draft',
|
||||
updatedAt: 1656658046,
|
||||
},
|
||||
{
|
||||
title: 'Campaigns',
|
||||
author: {
|
||||
name: 'Sam Manuel',
|
||||
},
|
||||
readCount: 28,
|
||||
category: 'Engineering',
|
||||
status: 'archived',
|
||||
updatedAt: 1657590446,
|
||||
},
|
||||
],
|
||||
articleCount: 10,
|
||||
currentPage: 1,
|
||||
onPageChange: action('onPageChange'),
|
||||
};
|
||||
@@ -14,10 +14,7 @@ const ListCategoryArticles = () =>
|
||||
import('./pages/articles/ListCategoryArticles');
|
||||
|
||||
const ListAllArticles = () => import('./pages/articles/ListAllArticles');
|
||||
const ListArchivedArticles = () =>
|
||||
import('./pages/articles/ListArchivedArticles');
|
||||
const ListDraftArticles = () => import('./pages/articles/ListDraftArticles');
|
||||
const ListMyArticles = () => import('./pages/articles/ListMyArticles');
|
||||
|
||||
const NewArticle = () => import('./pages/articles/NewArticle');
|
||||
const EditArticle = () => import('./pages/articles/EditArticle');
|
||||
|
||||
@@ -55,31 +52,32 @@ const articleRoutes = [
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ListAllArticles,
|
||||
},
|
||||
{
|
||||
path: getPortalRoute(':portalSlug/:locale/articles/new'),
|
||||
name: 'new_article',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: NewArticle,
|
||||
},
|
||||
{
|
||||
path: getPortalRoute(':portalSlug/:locale/articles/mine'),
|
||||
name: 'list_mine_articles',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ListAllArticles,
|
||||
},
|
||||
{
|
||||
path: getPortalRoute(':portalSlug/:locale/articles/archived'),
|
||||
name: 'list_archived_articles',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ListArchivedArticles,
|
||||
component: ListAllArticles,
|
||||
},
|
||||
|
||||
{
|
||||
path: getPortalRoute(':portalSlug/:locale/articles/draft'),
|
||||
name: 'list_draft_articles',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ListDraftArticles,
|
||||
},
|
||||
{
|
||||
path: getPortalRoute(':portalSlug/:locale/articles/mine'),
|
||||
name: 'list_mine_articles',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: ListMyArticles,
|
||||
},
|
||||
{
|
||||
path: getPortalRoute(':portalSlug/:locale/articles/new'),
|
||||
name: 'new_article',
|
||||
roles: ['administrator', 'agent'],
|
||||
component: NewArticle,
|
||||
component: ListAllArticles,
|
||||
},
|
||||
|
||||
{
|
||||
path: getPortalRoute(':portalSlug/:locale/articles/:articleSlug'),
|
||||
name: 'edit_article',
|
||||
|
||||
@@ -1,19 +1,96 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<article-header
|
||||
header-title="All Articles"
|
||||
:count="199"
|
||||
:header-title="headerTitle"
|
||||
:count="articleCount"
|
||||
selected-value="Published"
|
||||
@newArticlePage="newArticlePage"
|
||||
/>
|
||||
<article-table :articles="articles" :article-count="articles.length" />
|
||||
<empty-state
|
||||
v-if="showSearchEmptyState"
|
||||
:title="$t('HELP_CENTER.TABLE.404')"
|
||||
/>
|
||||
<empty-state
|
||||
v-else-if="!isLoading && !articles.length"
|
||||
:title="$t('CONTACTS_PAGE.LIST.NO_CONTACTS')"
|
||||
/>
|
||||
<div v-if="isLoading" class="articles--loader">
|
||||
<spinner />
|
||||
<span>{{ $t('HELP_CENTER.TABLE.LOADING_MESSAGE') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import ArticleHeader from 'dashboard/components/helpCenter/Header/ArticleHeader';
|
||||
import EmptyState from 'dashboard/components/widgets/EmptyState.vue';
|
||||
import ArticleTable from '../../components/ArticleTable';
|
||||
export default {
|
||||
components: {
|
||||
ArticleHeader,
|
||||
ArticleTable,
|
||||
EmptyState,
|
||||
Spinner,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Dummy data will remove once the state is implemented.
|
||||
articles: [
|
||||
{
|
||||
title: 'Setup your account',
|
||||
author: {
|
||||
name: 'John Doe',
|
||||
},
|
||||
readCount: 13,
|
||||
category: 'Getting started',
|
||||
status: 'published',
|
||||
updatedAt: 1657255863,
|
||||
},
|
||||
{
|
||||
title: 'Docker Configuration',
|
||||
author: {
|
||||
name: 'Sam Manuel',
|
||||
},
|
||||
readCount: 13,
|
||||
category: 'Engineering',
|
||||
status: 'draft',
|
||||
updatedAt: 1656658046,
|
||||
},
|
||||
{
|
||||
title: 'Campaigns',
|
||||
author: {
|
||||
name: 'Sam Manuel',
|
||||
},
|
||||
readCount: 28,
|
||||
category: 'Engineering',
|
||||
status: 'archived',
|
||||
updatedAt: 1657590446,
|
||||
},
|
||||
],
|
||||
articleCount: 12,
|
||||
isLoading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
showSearchEmptyState() {
|
||||
return this.articles.length === 0;
|
||||
},
|
||||
articleType() {
|
||||
return this.$route.path.split('/').pop();
|
||||
},
|
||||
headerTitle() {
|
||||
switch (this.articleType) {
|
||||
case 'mine':
|
||||
return this.$t('HELP_CENTER.HEADER.TITLES.MINE');
|
||||
case 'draft':
|
||||
return this.$t('HELP_CENTER.HEADER.TITLES.DRAFT');
|
||||
case 'archived':
|
||||
return this.$t('HELP_CENTER.HEADER.TITLES.ARCHIVED');
|
||||
default:
|
||||
return this.$t('HELP_CENTER.HEADER.TITLES.ALL_ARTICLES');
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
newArticlePage() {
|
||||
@@ -27,5 +104,12 @@ export default {
|
||||
.container {
|
||||
padding: var(--space-small) var(--space-normal);
|
||||
width: 100%;
|
||||
.articles--loader {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: var(--font-size-default);
|
||||
justify-content: center;
|
||||
padding: var(--space-big);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div>Component to list archived articles in a portal</div>
|
||||
</template>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div>Component to list all drafts articles in a portal</div>
|
||||
</template>
|
||||
@@ -1,3 +0,0 @@
|
||||
<template>
|
||||
<div>Component to list my articles in a portal</div>
|
||||
</template>
|
||||
@@ -1,31 +1,3 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<article-header
|
||||
header-title="All Articles"
|
||||
:count="199"
|
||||
selected-value="Published"
|
||||
@newArticlePage="newArticlePage"
|
||||
/>
|
||||
</div>
|
||||
<div>List of portals</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ArticleHeader from 'dashboard/components/helpCenter/Header/ArticleHeader';
|
||||
export default {
|
||||
components: {
|
||||
ArticleHeader,
|
||||
},
|
||||
methods: {
|
||||
newArticlePage() {
|
||||
this.$router.push({ name: 'new_article' });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
padding: var(--space-small) var(--space-normal);
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user