## List Pages ### Old (Angular) ```ts 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 { // 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], }); } } ``` ```html Create a review {{ review.id }} {{ review.createdAt | localeDate : 'short' }} {{ review.updatedAt | localeDate : 'short' }} {{ review.title }} {{ review.authorName }} ``` ```ts 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', }), ] ``` ### New (React Dashboard) ```tsx 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 => ( { const post = row.original; return ; }, }, }} defaultVisibility={{ type: true, summary: true, state: true, rating: true, authorName: true, authorLocation: true, }} defaultColumnOrder={[ 'type', 'summary', 'authorName', 'authorLocation', 'rating', ]} > ), }; ``` 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 {name} ({reviewCount}) }, }, }} ```