payload-kanban-board
Version:
A kanban board plugin for Payload CMS
178 lines (177 loc) • 9.19 kB
JavaScript
'use client';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { LexoRank } from 'lexorank';
import { useEffect, useState } from 'react';
import { DragDropContext } from 'react-beautiful-dnd';
import { toast } from '@payloadcms/ui';
import BoardColumn from '../BoardColumn/BoardColumn.js';
import { sortAndFilterDocumentsForStatus, sortAndFilterDocumentsWithoutStatus } from '../utils/documents.util.js';
import './styles.scss';
const Board = (props)=>{
const { statusDefinition, documents: initDocuments, hideNoStatusColumn, onDocumentkanbanStatusChange, collection, dragEnabled, collectionSlug } = props;
const [documents, setDocuments] = useState(initDocuments ?? []);
useEffect(()=>{
setDocuments(initDocuments);
}, [
initDocuments
]);
const updateDocument = async (documentId, destinationStatus, orderRank, sourceId)=>{
try {
// Store the original document state before updating
const originalDocuments = [
...documents
];
const documentIndex = originalDocuments.findIndex((doc)=>doc.id === documentId);
const originalDocumentState = documentIndex !== -1 ? {
...originalDocuments[documentIndex]
} : null;
// Update the UI optimistically
setDocuments((prev)=>{
const updatedDocumentIndex = prev.findIndex((_doc)=>_doc.id === documentId);
if (updatedDocumentIndex === -1) {
return prev;
}
const newDocuments = [
...prev
];
newDocuments[updatedDocumentIndex] = {
...newDocuments[updatedDocumentIndex],
kanbanStatus: destinationStatus,
kanbanOrderRank: orderRank
};
return newDocuments;
});
// Call the status change function and wait for the status code
const response = await onDocumentkanbanStatusChange(documentId, destinationStatus, orderRank);
const statusCode = response.status;
if (statusCode !== 200) {
const res = await response.json();
const errorMessage = res.errors[0].message;
// Revert the UI state if the API call fails
toast.error(errorMessage ? errorMessage : 'You are not authorised to update document status');
// Restore the original state for this document
if (originalDocumentState) {
setDocuments((prev)=>{
const revertIndex = prev.findIndex((_doc)=>_doc.id === documentId);
if (revertIndex === -1) {
return prev;
}
const revertedDocuments = [
...prev
];
revertedDocuments[revertIndex] = originalDocumentState;
return revertedDocuments;
});
}
return false;
}
return true;
} catch (error) {
console.error('Error updating document:', error);
toast.error('Something went wrong');
// Revert to original documents on error
setDocuments(initDocuments);
return false;
}
};
const onDragEnd = async (result)=>{
if (!dragEnabled) {
toast.error('You are not authorized to perform this action');
return;
}
if (!result.destination) {
return;
}
const source = result.source;
const destination = result.destination;
if (source.droppableId === destination.droppableId && source.index === destination.index) {
return;
}
const documentId = result.draggableId;
const sourceStatus = source.droppableId;
const destinationStatus = destination.droppableId;
const destinationIndex = destination.index;
try {
const destinationStatusGroup = sortAndFilterDocumentsForStatus(documents, destinationStatus);
const minOrderRank = documents[0]?.kanbanOrderRank ?? LexoRank.min().toString();
const maxOrderRank = documents[documents.length - 1]?.kanbanOrderRank ?? LexoRank.max().toString();
// First in entire collection when added to empty group
if (destinationStatusGroup.length === 0 && documents.findIndex((_doc)=>_doc.id === documentId) === 0) {
return updateDocument(documentId, destinationStatus, LexoRank.min().toString(), source.droppableId);
}
// First in list on empty group
if (destinationStatusGroup.length === 0 && destinationIndex === 0) {
return updateDocument(documentId, destinationStatus, LexoRank.min().genNext().toString(), source.droppableId);
}
// First in list
if (destinationIndex === 0) {
const previousFirstDoc = [
...destinationStatusGroup
].shift();
// If the value has not been set, set a default value
if (!(typeof previousFirstDoc?.kanbanOrderRank === 'string')) {
const updatedOrderRank = LexoRank.parse(minOrderRank).between(LexoRank.max()).toString();
return updateDocument(documentId, destinationStatus, updatedOrderRank, source.droppableId);
}
const updatedOrderRank = LexoRank.parse(previousFirstDoc.kanbanOrderRank).genPrev();
return updateDocument(documentId, destinationStatus, updatedOrderRank.toString(), source.droppableId);
}
// Last in the list
if (sourceStatus === destinationStatus && destinationIndex + 1 === destinationStatusGroup.length || sourceStatus !== destinationStatus && destinationIndex === destinationStatusGroup.length) {
const previousLastDoc = [
...destinationStatusGroup
].pop();
// If the value has not been set, set a default value
if (!(typeof previousLastDoc?.kanbanOrderRank === 'string')) {
const updatedOrderRank = LexoRank.parse(maxOrderRank).between(LexoRank.min()).toString();
return updateDocument(documentId, destinationStatus, updatedOrderRank, source.droppableId);
}
const updatedOrderRank = LexoRank.parse(previousLastDoc.kanbanOrderRank).genNext();
return updateDocument(documentId, destinationStatus, updatedOrderRank.toString(), source.droppableId);
}
// Between 2 documents
let documentBefore = destinationStatusGroup[destinationIndex - 1];
let documentAfter = destinationStatusGroup[destinationIndex];
// Within the same list re-ordering to the bottom, switch the document before and after
if (sourceStatus === destinationStatus && source.index < destinationIndex) {
documentBefore = destinationStatusGroup[destinationIndex];
documentAfter = destinationStatusGroup[destinationIndex + 1];
}
const documentBeforeRank = LexoRank.parse(documentBefore.kanbanOrderRank);
const documentAfterRank = LexoRank.parse(documentAfter.kanbanOrderRank);
// Status change accepted
return updateDocument(documentId, destinationStatus, documentBeforeRank.between(documentAfterRank).toString(), source.droppableId);
} catch (error) {
console.error('Error updating document:', error);
toast.error('Something went wrong');
}
};
return /*#__PURE__*/ _jsx(DragDropContext, {
onDragEnd: (result)=>onDragEnd(result),
children: /*#__PURE__*/ _jsx("div", {
className: "scrumboard",
children: /*#__PURE__*/ _jsxs("div", {
className: "scrumboard-body",
children: [
hideNoStatusColumn ? /*#__PURE__*/ _jsx(_Fragment, {}) : /*#__PURE__*/ _jsx(BoardColumn, {
collection: collection,
title: 'No status',
identifier: 'null',
contents: sortAndFilterDocumentsWithoutStatus(documents),
collapsible: true,
dragEnabled: dragEnabled
}),
statusDefinition?.options.map((status)=>/*#__PURE__*/ _jsx(BoardColumn, {
collection: collection,
title: status.label,
identifier: status.value,
dragEnabled: dragEnabled,
contents: sortAndFilterDocumentsForStatus(documents, status.value)
}, status.value))
]
})
})
});
};
export { Board };
//# sourceMappingURL=Board.js.map