feat: builds revisions list view

This commit is contained in:
James
2021-12-01 10:36:23 -05:00
parent b31f43f838
commit 1920a937b2
10 changed files with 319 additions and 52 deletions

View File

@@ -204,6 +204,27 @@ const Routes = () => {
return null;
})}
{collections.map((collection) => {
if (collection.revisions) {
return (
<Route
key={`${collection.slug}-view-revision`}
path={`${match.url}/collections/${collection.slug}/:id/revisions/:revisionID`}
exact
render={(routeProps) => {
if (permissions?.collections?.[collection.slug]?.readRevisions?.permission) {
return null;
}
return <Unauthorized />;
}}
/>
);
}
return null;
})}
{globals && globals.map((global) => (
<Route
key={`${global.slug}`}

View File

@@ -0,0 +1,11 @@
@import '../../../scss/styles';
.id-label {
font-size: base(.75);
font-weight: normal;
color: $color-gray;
background: $color-background-gray;
padding: base(.25) base(.5);
border-radius: $style-radius-m;
display: inline-flex;
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import './index.scss';
const baseClass = 'id-label';
const IDLabel: React.FC<{ id: string }> = ({ id }) => (
<div className={baseClass}>
ID:&nbsp;&nbsp;
{id}
</div>
);
export default IDLabel;

View File

@@ -1,12 +0,0 @@
@import '../../../scss/styles.scss';
.render-title {
&--id-as-title {
font-size: base(.75);
font-weight: normal;
color: $color-gray;
background: $color-background-gray;
padding: base(.25) base(.5);
border-radius: $style-radius-m;
}
}

View File

@@ -1,8 +1,7 @@
import React, { Fragment } from 'react';
import React from 'react';
import { Props } from './types';
import useTitle from '../../../hooks/useTitle';
import './index.scss';
import IDLabel from '../IDLabel';
const baseClass = 'render-title';
@@ -25,18 +24,14 @@ const RenderTitle : React.FC<Props> = (props) => {
const idAsTitle = title === data?.id;
const classes = [
baseClass,
idAsTitle && `${baseClass}--id-as-title`,
].filter(Boolean).join(' ');
if (idAsTitle) {
return (
<IDLabel id={data?.id} />
);
}
return (
<span className={classes}>
{idAsTitle && (
<Fragment>
ID:&nbsp;&nbsp;
</Fragment>
)}
<span className={baseClass}>
{title}
</span>
);

View File

@@ -0,0 +1,64 @@
import React from 'react';
import { Link, useRouteMatch } from 'react-router-dom';
import { useConfig } from '@payloadcms/config-provider';
import format from 'date-fns/format';
import { Column } from '../../../elements/Table/types';
import SortColumn from '../../../elements/SortColumn';
import { SanitizedCollectionConfig } from '../../../../../collections/config/types';
type CreatedAtCellProps = {
id: string
date: string
collection: SanitizedCollectionConfig
}
const CreatedAtCell: React.FC<CreatedAtCellProps> = ({ collection, id, date }) => {
const { routes: { admin }, admin: { dateFormat } } = useConfig();
const { params: { id: docID } } = useRouteMatch<{ id: string }>();
return (
<Link to={`${admin}/collections/${collection.slug}/${docID}/revisions/${id}`}>
{date && format(new Date(date), dateFormat)}
</Link>
);
};
const IDCell: React.FC = ({ children }) => (
<span>
{children}
</span>
);
export const getColumns = (collection: SanitizedCollectionConfig): Column[] => [
{
accessor: 'createdAt',
components: {
Heading: (
<SortColumn
label="Created At"
name="createdAt"
/>
),
renderCell: (row, data) => (
<CreatedAtCell
collection={collection}
id={row?.id}
date={data}
/>
),
},
},
{
accessor: 'id',
components: {
Heading: (
<SortColumn
label="ID"
disable
name="id"
/>
),
renderCell: (row, data) => <IDCell>{data}</IDCell>,
},
},
];

View File

@@ -0,0 +1,65 @@
@import '../../../../scss/styles';
.collection-revisions {
width: 100%;
margin-bottom: base(2);
&__wrap {
padding: base(3);
margin-right: base(2);
background: white;
}
&__header {
margin-bottom: $baseline;
}
.table {
table {
width: 100%;
overflow: auto;
}
}
&__page-controls {
width: 100%;
display: flex;
align-items: center;
}
.paginator {
margin-bottom: 0;
}
&__page-info {
margin-right: base(1);
margin-left: auto;
}
@include mid-break {
&__wrap {
padding: $baseline 0;
margin-right: 0;
}
&__header,
.table,
&__page-controls {
padding-left: $baseline;
padding-right: $baseline;
}
&__page-controls {
flex-wrap: wrap;
}
&__page-info {
margin-left: 0;
}
.paginator {
width: 100%;
margin-bottom: $baseline;
}
}
}

View File

@@ -1,5 +1,5 @@
import { useConfig } from '@payloadcms/config-provider';
import React, { useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { useRouteMatch } from 'react-router-dom';
import usePayloadAPI from '../../../../hooks/usePayloadAPI';
import Eyebrow from '../../../elements/Eyebrow';
@@ -8,25 +8,30 @@ import { useStepNav } from '../../../elements/StepNav';
import { StepNavItem } from '../../../elements/StepNav/types';
import Meta from '../../../utilities/Meta';
import { Props } from './types';
import IDLabel from '../../../elements/IDLabel';
import { getColumns } from './columns';
import Table from '../../../elements/Table';
import Paginator from '../../../elements/Paginator';
import PerPage from '../../../elements/PerPage';
const baseClass = 'revisions';
import './index.scss';
import { useSearchParams } from '../../../utilities/SearchParams';
const baseClass = 'collection-revisions';
const Revisions: React.FC<Props> = ({ collection }) => {
const { serverURL, routes: { admin } } = useConfig();
const { serverURL, routes: { admin, api } } = useConfig();
const { setStepNav } = useStepNav();
const { params: { id } } = useRouteMatch<{ id: string }>();
const [tableColumns] = useState(() => getColumns(collection));
const [fetchURL, setFetchURL] = useState('');
const { page, sort, limit } = useSearchParams();
const [{ data: doc }] = usePayloadAPI(`${serverURL}/api/${collection.slug}/${id}`);
const [{ data: revisionsData, isLoading: isLoadingRevisions }] = usePayloadAPI(`${serverURL}/api/${collection.slug}/revisions`, {
initialParams: {
where: {
parent: {
equals: id,
},
},
},
});
const [{ data: revisionsData, isLoading: isLoadingRevisions }, { setParams }] = usePayloadAPI(fetchURL);
const useAsTitle = collection.admin.useAsTitle || 'id';
useEffect(() => {
const nav: StepNavItem[] = [
@@ -35,7 +40,7 @@ const Revisions: React.FC<Props> = ({ collection }) => {
label: collection.labels.plural,
},
{
label: doc ? doc[collection.admin.useAsTitle || 'id'] : '',
label: doc ? doc[useAsTitle] : '',
url: `${admin}/collections/${collection.slug}/${id}`,
},
{
@@ -44,26 +49,94 @@ const Revisions: React.FC<Props> = ({ collection }) => {
];
setStepNav(nav);
}, [setStepNav, collection, doc, admin, id]);
}, [setStepNav, collection, useAsTitle, doc, admin, id]);
useEffect(() => {
const params = {
depth: 1,
page: undefined,
sort: undefined,
where: {
parent: {
equals: id,
},
},
limit,
};
if (page) params.page = page;
if (sort) params.sort = sort;
// Performance enhancement
// Setting the Fetch URL this way
// prevents a double-fetch
setFetchURL(`${serverURL}${api}/${collection.slug}/revisions`);
setParams(params);
}, [setParams, page, sort, collection, limit, serverURL, api, id]);
const useIDLabel = doc[useAsTitle] === doc?.id;
return (
<div className={baseClass}>
<Meta
title={`Revisions - ${doc[collection.admin.useAsTitle] ? doc[collection.admin.useAsTitle] : doc?.id} - ${collection.labels.singular}`}
description={`Viewing revisions for the ${collection.labels.singular} ${doc[collection.admin.useAsTitle] ? doc[collection.admin.useAsTitle] : doc?.id}`}
title={`Revisions - ${doc[useAsTitle]} - ${collection.labels.singular}`}
description={`Viewing revisions for the ${collection.labels.singular} ${doc[useAsTitle]}`}
keywords={`Revisions, ${collection.labels.singular}, Payload, CMS`}
/>
<Eyebrow />
{isLoadingRevisions && (
<Loading />
)}
{revisionsData?.docs && (
<React.Fragment>
<h1>Revisions</h1>
{revisionsData?.docs?.length}
</React.Fragment>
)}
<div className={`${baseClass}__wrap`}>
<header className={`${baseClass}__header`}>
Showing revisions for:
{useIDLabel && (
<IDLabel id={doc?.id} />
)}
{!useIDLabel && (
<h1>
{doc[useAsTitle]}
</h1>
)}
</header>
{isLoadingRevisions && (
<Loading />
)}
{revisionsData?.docs && (
<React.Fragment>
<Table
data={revisionsData?.docs}
columns={tableColumns}
/>
<div className={`${baseClass}__page-controls`}>
<Paginator
limit={revisionsData.limit}
totalPages={revisionsData.totalPages}
page={revisionsData.page}
hasPrevPage={revisionsData.hasPrevPage}
hasNextPage={revisionsData.hasNextPage}
prevPage={revisionsData.prevPage}
nextPage={revisionsData.nextPage}
numberOfNeighbors={1}
/>
{revisionsData?.totalDocs > 0 && (
<React.Fragment>
<div className={`${baseClass}__page-info`}>
{(revisionsData.page * revisionsData.limit) - (revisionsData.limit - 1)}
-
{revisionsData.totalPages > 1 && revisionsData.totalPages !== revisionsData.page ? (revisionsData.limit * revisionsData.page) : revisionsData.totalDocs}
{' '}
of
{' '}
{revisionsData.totalDocs}
</div>
<PerPage
collection={collection}
limit={limit ? Number(limit) : 10}
/>
</React.Fragment>
)}
</div>
</React.Fragment>
)}
</div>
</div>
);
};