Skip to content

Commit c862b31

Browse files
Merge pull request #1 from DennisSnijder/feature/no-status-column
[Feature] Collapsible "No status" column
2 parents 946a948 + 78fbb35 commit c862b31

File tree

9 files changed

+107
-20
lines changed

9 files changed

+107
-20
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const config = buildConfig({
2525
{value: 'ready-for-review', label: 'Ready for review'},
2626
{value: 'published', label: 'Published'},
2727
],
28-
defaultStatus: 'draft'
28+
defaultStatus: 'draft',
29+
hideNoStatusColumn: false
2930
}
3031
})
3132
],
@@ -44,7 +45,7 @@ Upcoming Features / Ideas. Have a suggestion for the plugin? Feel free to open a
4445

4546
- [ ] Customize card properties (currently displays `title` and `createdAt`)
4647
- [ ] Edit relationships directly from the card (e.g., assigning users to a document)
47-
- [ ] Toggleable column for posts without a workflow status (Currently, documents lacking `workflowStatus` aren't visible on the board)
48+
- [X] Toggleable column for posts without a workflow status (Currently, documents lacking `workflowStatus` aren't visible on the board)
4849
- [ ] Lazy loading of column contents when scrolling (Currently, board only shows `defaultLimit` amount of cards)
4950
- [ ] Permissions for changing statuses
5051
- [ ] Allowed transitions between statuses

dev/src/payload.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ export default buildConfig({
4141
{value: 'ready-for-review', label: 'Ready for review'},
4242
{value: 'published', label: 'Published'},
4343
],
44-
defaultStatus: 'draft'
44+
defaultStatus: 'draft',
45+
hideNoStatusColumn: false
4546
}
4647
})
4748
],

preview.png

-660 KB
Loading

src/components/Board/Board.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@ import { DragDropContext, Droppable, DropResult } from "react-beautiful-dnd";
33
import BoardColumn from "./../BoardColumn/BoardColumn";
44
import { CollectionConfig, SelectField } from "payload/types";
55
import { LexoRank } from "lexorank";
6-
import { sortAndFilterDocumentsForStatus } from "../../utils/documents.util";
6+
import { sortAndFilterDocumentsForStatus, sortAndFilterDocumentsWithoutStatus } from "../../utils/documents.util";
77

88
import './styles.scss';
99

1010
interface BoardInterface {
1111
collection: CollectionConfig;
1212
statusDefinition: SelectField;
13+
hideNoStatusColumn?: boolean;
1314
documents: any[];
1415
onDocumentWorkflowStatusChange: (documentId: string, workflowStatus: string, orderRank: string) => void;
1516
}
1617

1718
const Board = (props: BoardInterface) => {
18-
const {statusDefinition, documents: initDocuments, onDocumentWorkflowStatusChange, collection} = props;
19+
const {
20+
statusDefinition,
21+
documents: initDocuments,
22+
hideNoStatusColumn,
23+
onDocumentWorkflowStatusChange,
24+
collection
25+
} = props;
1926
const [ documents, setDocuments ] = useState(initDocuments ?? []);
2027

2128
useEffect(() => {
@@ -138,9 +145,20 @@ const Board = (props: BoardInterface) => {
138145
{ ...provided.droppableProps }
139146
>
140147
<div className="scrumboard-body">
148+
149+
{ !hideNoStatusColumn &&
150+
<BoardColumn
151+
collection={ collection }
152+
title={ 'No status' }
153+
identifier={ 'null' }
154+
contents={ sortAndFilterDocumentsWithoutStatus(documents) }
155+
collapsible={ true }
156+
/>
157+
}
158+
141159
{ statusDefinition.options.map((status: any) => (
142160
<BoardColumn
143-
collection={collection}
161+
collection={ collection }
144162
key={ status.value }
145163
title={ status.label }
146164
identifier={ status.value }

src/components/BoardColumn/BoardColumn.tsx

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,43 @@
1-
import React from 'react'
1+
import React, { useState } from 'react'
22
import BoardCardList from "./../BoardCardList/BoardCardList";
33

44
import './styles.scss';
55
import { CollectionConfig } from "payload/types";
6+
import { Chevron } from "payload/components";
7+
68
const baseClass = 'board-column';
79

810
interface BoardColumProps {
911
collection: CollectionConfig;
1012
title: string;
1113
identifier: string;
12-
contents: any[]
14+
contents: any[];
15+
collapsible?: boolean;
1316
}
1417

1518
const BoardColumn = (props: BoardColumProps) => {
16-
const {title, identifier, contents, collection} = props
19+
const {title, identifier, contents, collection, collapsible} = props
20+
21+
const [ isCollapsed, setCollapsed ] = useState(!!collapsible);
1722

1823
return (
1924
<div
20-
className={`${baseClass}__wrapper`}
25+
className={ `${ baseClass }__wrapper ${ isCollapsed ? `${ baseClass }__collapsed` : '' }` }
2126
>
22-
<div className={`${baseClass}__header`}>
27+
<div
28+
className={ `${ baseClass }__header ${ isCollapsed ? `${ baseClass }__header_collapsed` : '' }` }
29+
>
2330
<h4>{ title } <span>{ contents?.length }</span></h4>
31+
{ collapsible && <div onClick={ () => setCollapsed(!isCollapsed) } className={ `${ baseClass }__collapse` }>
32+
<Chevron/>
33+
</div> }
2434
</div>
2535

26-
<BoardCardList
27-
listId={ identifier }
28-
contents={ contents }
29-
collection={collection}
30-
/>
36+
{ !isCollapsed && <BoardCardList
37+
listId={ identifier }
38+
contents={ contents }
39+
collection={ collection }
40+
/> }
3141
</div>
3242
)
3343
}

src/components/BoardColumn/styles.scss

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
.board-column {
44

55
&__wrapper {
6+
transition-property: transform;
7+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
8+
transition-duration: 150ms;
9+
610
display: flex;
711
flex-direction: column;
812
margin-bottom: 3rem;
@@ -13,9 +17,48 @@
1317
border-right: 1px solid var(--theme-elevation-100);
1418
}
1519

20+
&__collapsed {
21+
min-width: auto;
22+
width: base(2.25);
23+
white-space: nowrap;
24+
}
25+
26+
&__collapse {
27+
cursor: pointer;
28+
29+
svg {
30+
margin-right: 1rem;
31+
transform: rotate(90deg);
32+
}
33+
}
34+
35+
&__header_collapsed {
36+
transform: translateY(-(base(1.125))) rotate(90deg); /* Rotate the container 90 degrees */
37+
transform-origin: bottom left; /* Set the rotation origin to the top left corner */
38+
margin: 0;
39+
flex-direction: row-reverse;
40+
justify-content: flex-end !important;
41+
42+
h4 {
43+
padding-left: 0 !important;
44+
}
45+
46+
svg {
47+
margin-right: 1rem;
48+
transform: rotate(180deg);
49+
}
50+
51+
52+
}
53+
1654
&__header {
55+
display: flex;
56+
justify-content: space-between;
57+
margin: 0 0 $baseline;
58+
1759
h4 {
1860
padding-left: 1rem;
61+
margin-bottom: 0;
1962

2063
span {
2164
font-size: 0.9em;

src/components/WorkflowView/WorkflowView.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@ import DefaultList from "payload/dist/admin/components/views/collections/List/De
1313
import WorkflowViewHeader from "../WorkflowViewHeader/WorkflowViewHeader";
1414
import { requests } from "payload/dist/admin/api";
1515
import Board from "../Board/Board";
16+
import { PluginCollectionConfig } from "../../index";
1617

1718
import './styles.scss';
1819

1920
const baseClass = 'scrumboard';
2021

21-
export const WorkflowView = (props: ListProps) => {
22+
export const WorkflowView = (config: PluginCollectionConfig) => (props: ListProps) => {
2223

2324
const {
2425
collection,
@@ -55,7 +56,7 @@ export const WorkflowView = (props: ListProps) => {
5556
const handleDocumentWorkflowStatusChange = (documentId: string, workflowStatus: string, workflowOrderRank: string) => {
5657
requests.patch(`${ serverURL }${ api }/${ collectionSlug }/${ documentId }`, {
5758
body: JSON.stringify({
58-
workflowStatus: workflowStatus,
59+
workflowStatus: workflowStatus === 'null' ? null : workflowStatus,
5960
workflowOrderRank: workflowOrderRank
6061
}),
6162
headers: {
@@ -110,6 +111,7 @@ export const WorkflowView = (props: ListProps) => {
110111
<Board
111112
collection={ collection }
112113
documents={ data.docs }
114+
hideNoStatusColumn={config.hideNoStatusColumn}
113115
statusDefinition={ statusDefinition }
114116
onDocumentWorkflowStatusChange={ handleDocumentWorkflowStatusChange }
115117
/>

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { generateOrderRank } from "./hooks/generateOrderRank";
77
export interface PluginCollectionConfig {
88
statuses: OptionObject[],
99
defaultStatus?: string;
10+
hideNoStatusColumn?: boolean;
1011
}
1112

1213

@@ -57,7 +58,7 @@ const extendCollectionConfig = (
5758
},
5859
components: {
5960
views: {
60-
List: WorkflowView
61+
List: WorkflowView(collectionPluginConfig)
6162
}
6263
}
6364
}

src/utils/documents.util.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,15 @@ export const sortAndFilterDocumentsForStatus = (documents: any[] = [], status: s
77

88
return aOrderRank.localeCompare(bOrderRank)
99
});
10-
};
10+
};
11+
12+
export const sortAndFilterDocumentsWithoutStatus = (documents: any[] = []) => {
13+
return documents
14+
.filter((_doc) => !_doc.workflowStatus || _doc.workflowStatus === 'null')
15+
.sort((a, b) => {
16+
const aOrderRank = a.workflowOrderRank || '0'
17+
const bOrderRank = b.workflowOrderRank || '0'
18+
19+
return aOrderRank.localeCompare(bOrderRank)
20+
});
21+
}

0 commit comments

Comments
 (0)