import { ChangeDetectionStrategy, Component } from '@angular/core';
import { TypedBaseListComponent, SharedModule } from '@vendure/admin-ui/core';
// This is the TypedDocumentNode generated by GraphQL Code Generator
import { graphql } from '../../gql';
const getReviewListDocument = graphql(`
query GetReviewList($options: ReviewListOptions) {
reviews(options: $options) {
items {
id
createdAt
updatedAt
title
rating
text
authorName
productId
}
totalItems
}
}
`);
@Component({
selector: 'review-list',
templateUrl: './review-list.component.html',
styleUrls: ['./review-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: true,
imports: [SharedModule],
})
export class ReviewListComponent extends TypedBaseListComponent<typeof getReviewListDocument, 'reviews'> {
// Here we set up the filters that will be available
// to use in the data table
readonly filters = this.createFilterCollection()
.addIdFilter()
.addDateFilters()
.addFilter({
name: 'title',
type: {kind: 'text'},
label: 'Title',
filterField: 'title',
})
.addFilter({
name: 'rating',
type: {kind: 'number'},
label: 'Rating',
filterField: 'rating',
})
.addFilter({
name: 'authorName',
type: {kind: 'text'},
label: 'Author',
filterField: 'authorName',
})
.connectToRoute(this.route);
// Here we set up the sorting options that will be available
// to use in the data table
readonly sorts = this.createSortCollection()
.defaultSort('createdAt', 'DESC')
.addSort({name: 'createdAt'})
.addSort({name: 'updatedAt'})
.addSort({name: 'title'})
.addSort({name: 'rating'})
.addSort({name: 'authorName'})
.connectToRoute(this.route);
constructor() {
super();
super.configure({
document: getReviewListDocument,
getItems: data => data.reviews,
setVariables: (skip, take) => ({
options: {
skip,
take,
filter: {
title: {
contains: this.searchTermControl.value,
},
...this.filters.createFilterInput(),
},
sort: this.sorts.createSortInput(),
},
}),
refreshListOnChanges: [this.filters.valueChanges, this.sorts.valueChanges],
});
}
}
<!-- optional if you want some buttons at the top -->
<vdr-page-block>
<vdr-action-bar>
<vdr-ab-left></vdr-ab-left>
<vdr-ab-right>
<a class="btn btn-primary" *vdrIfPermissions="['CreateReview']" [routerLink]="['./', 'create']">
<clr-icon shape="plus"></clr-icon>
Create a review
</a>
</vdr-ab-right>
</vdr-action-bar>
</vdr-page-block>
<!-- The data table -->
<vdr-data-table-2
id="review-list"
[items]="items$ | async"
[itemsPerPage]="itemsPerPage$ | async"
[totalItems]="totalItems$ | async"
[currentPage]="currentPage$ | async"
[filters]="filters"
(pageChange)="setPageNumber($event)"
(itemsPerPageChange)="setItemsPerPage($event)"
>
<!-- optional if you want to support bulk actions -->
<vdr-bulk-action-menu
locationId="review-list"
[hostComponent]="this"
[selectionManager]="selectionManager"
/>
<!-- Adds a search bar -->
<vdr-dt2-search
[searchTermControl]="searchTermControl"
searchTermPlaceholder="Filter by title"
/>
<!-- Here we define all the available columns -->
<vdr-dt2-column id="id" [heading]="'common.id' | translate" [hiddenByDefault]="true">
<ng-template let-review="item">
{{ review.id }}
</ng-template>
</vdr-dt2-column>
<vdr-dt2-column
id="created-at"
[heading]="'common.created-at' | translate"
[hiddenByDefault]="true"
[sort]="sorts.get('createdAt')"
>
<ng-template let-review="item">
{{ review.createdAt | localeDate : 'short' }}
</ng-template>
</vdr-dt2-column>
<vdr-dt2-column
id="updated-at"
[heading]="'common.updated-at' | translate"
[hiddenByDefault]="true"
[sort]="sorts.get('updatedAt')"
>
<ng-template let-review="item">
{{ review.updatedAt | localeDate : 'short' }}
</ng-template>
</vdr-dt2-column>
<vdr-dt2-column id="title" heading="Title" [optional]="false" [sort]="sorts.get('title')">
<ng-template let-review="item">
<a class="button-ghost" [routerLink]="['./', review.id]"
><span>{{ review.title }}</span>
<clr-icon shape="arrow right"></clr-icon>
</a>
</ng-template>
</vdr-dt2-column>
<vdr-dt2-column id="rating" heading="Rating" [sort]="sorts.get('rating')">
<ng-template let-review="item"><my-star-rating-component [rating]="review.rating" /></ng-template>
</vdr-dt2-column>
<vdr-dt2-column id="author" heading="Author" [sort]="sorts.get('authorName')">
<ng-template let-review="item">{{ review.authorName }}</ng-template>
</vdr-dt2-column>
</vdr-data-table-2>
import { registerRouteComponent } from '@vendure/admin-ui/core';
import { ReviewListComponent } from './components/review-list/review-list.component';
export default [
registerRouteComponent({
path: '',
component: ReviewListComponent,
breadcrumb: 'Product reviews',
}),
]
import {
Button,
DashboardRouteDefinition,
ListPage,
PageActionBarRight,
DetailPageButton,
} from '@vendure/dashboard';
import { Link } from '@tanstack/react-router';
import { PlusIcon } from 'lucide-react';
// This function is generated for you by the `vendureDashboardPlugin` in your Vite config.
// It uses gql-tada to generate TypeScript types which give you type safety as you write
// your queries and mutations.
import { graphql } from '@/gql';
// The fields you select here will be automatically used to generate the appropriate columns in the
// data table below.
const getArticleList = graphql(`
query GetArticles($options: ArticleListOptions) {
articles(options: $options) {
items {
id
createdAt
updatedAt
isPublished
title
slug
body
customFields
}
totalItems
}
}
`);
const deleteArticleDocument = graphql(`
mutation DeleteArticle($id: ID!) {
deleteArticle(id: $id) {
result
}
}
`);
export const articleList: DashboardRouteDefinition = {
navMenuItem: {
sectionId: 'catalog',
id: 'articles',
url: '/articles',
title: 'CMS Articles',
},
path: '/articles',
loader: () => ({
breadcrumb: 'Articles',
}),
component: route => (
<ListPage
pageId="article-list"
title="Articles"
listQuery={getArticleList}
deleteMutation={deleteArticleDocument}
route={route}
customizeColumns={{
title: {
cell: ({ row }) => {
const post = row.original;
return <DetailPageButton id={post.id} label={post.title} />;
},
},
}}
defaultVisibility={{
type: true,
summary: true,
state: true,
rating: true,
authorName: true,
authorLocation: true,
}}
defaultColumnOrder={[
'type',
'summary',
'authorName',
'authorLocation',
'rating',
]}
>
<PageActionBarRight>
<Button asChild>
<Link to="./new">
<PlusIcon className="mr-2 h-4 w-4" />
New article
</Link>
</Button>
</PageActionBarRight>
</ListPage>
),
};
Important:
- When using `defaultVisibility`, specify the specific visible ones with `true`. *Do not* mix
true and false values. It is implicit that any not specified will default to `false`.
- The `id`, `createdAt` and `updatedAt` never need to be specified in `customizeColumns`, defaultVisibility` or `defaultColumnOrder`.
They are handled correctly by default.
- By default the DataTable will handle column names based on the field name,
e.g. `authorName` -> `Author Name`, `rating` -> `Rating`, so an explicit cell header is
not needed unless the column header title must significantly differ from the field name.
- If a custom `cell` function needs to access fields _other_ than the one being rendered,
those other fields *must* be declared as dependencies:
```tsx
customizeColumns={{
name: {
// Note, we DO NOT need to declare "name" as a dependency here,
// since we are handling the `name` column already.
meta: { dependencies: ['reviewCount'] },
cell: ({ row }) => {
const { name, reviewCount } = row.original;
return <Badge variant="outline">{name} ({reviewCount})</Badge>
},
},
}}
```