sanity-plugin-workflow 1.0.0-beta.1 → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +71 -12
- package/lib/{src/index.d.ts → index.d.ts} +3 -3
- package/lib/index.esm.js +1691 -1
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1704 -1
- package/lib/index.js.map +1 -1
- package/package.json +48 -38
- package/src/actions/AssignWorkflow.tsx +48 -0
- package/src/actions/BeginWorkflow.tsx +68 -0
- package/src/actions/CompleteWorkflow.tsx +41 -0
- package/src/actions/RequestReviewAction.js +1 -7
- package/src/actions/UpdateWorkflow.tsx +142 -0
- package/src/badges/AssigneesBadge.tsx +52 -0
- package/src/badges/{index.tsx → StateBadge.tsx} +4 -8
- package/src/components/DocumentCard/AvatarGroup.tsx +12 -8
- package/src/components/DocumentCard/CompleteButton.tsx +53 -0
- package/src/components/DocumentCard/EditButton.tsx +3 -2
- package/src/components/DocumentCard/Field.tsx +38 -0
- package/src/components/DocumentCard/ValidationStatus.tsx +37 -0
- package/src/components/DocumentCard/core/DraftStatus.tsx +32 -0
- package/src/components/DocumentCard/core/PublishedStatus.tsx +32 -0
- package/src/components/DocumentCard/core/TimeAgo.tsx +11 -0
- package/src/components/DocumentCard/index.tsx +156 -50
- package/src/components/Filters.tsx +168 -0
- package/src/components/FloatingCard.tsx +29 -0
- package/src/components/StateTitle/Status.tsx +27 -0
- package/src/components/StateTitle/index.tsx +73 -0
- package/src/components/UserAssignment.tsx +57 -75
- package/src/components/UserAssignmentInput.tsx +27 -0
- package/src/components/UserDisplay.tsx +57 -0
- package/src/components/Validators.tsx +196 -0
- package/src/components/WorkflowTool.tsx +301 -160
- package/src/constants/index.ts +31 -0
- package/src/helpers/arraysContainMatchingString.ts +6 -0
- package/src/helpers/filterItemsAndSort.ts +39 -0
- package/src/helpers/initialRank.ts +13 -0
- package/src/hooks/useWorkflowDocuments.tsx +62 -70
- package/src/hooks/useWorkflowMetadata.tsx +0 -1
- package/src/index.ts +38 -58
- package/src/schema/workflow/workflow.metadata.ts +68 -0
- package/src/tools/index.ts +15 -0
- package/src/types/index.ts +27 -6
- package/src/actions/DemoteAction.tsx +0 -62
- package/src/actions/PromoteAction.tsx +0 -62
- package/src/components/Mutate.tsx +0 -54
- package/src/components/StateTimeline.tsx +0 -98
- package/src/components/UserSelectInput.tsx +0 -43
- package/src/schema/workflow/metadata.ts +0 -38
|
@@ -1,68 +1,44 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
Flex,
|
|
4
|
-
Card,
|
|
5
|
-
Box,
|
|
6
|
-
Grid,
|
|
7
|
-
Spinner,
|
|
8
|
-
Label,
|
|
9
|
-
useToast,
|
|
10
|
-
Container,
|
|
11
|
-
useTheme,
|
|
12
|
-
Button,
|
|
13
|
-
} from '@sanity/ui'
|
|
2
|
+
import {Flex, Card, Grid, Spinner, Container, useTheme} from '@sanity/ui'
|
|
14
3
|
import {Feedback, useProjectUsers} from 'sanity-plugin-utils'
|
|
15
|
-
import {Tool,
|
|
4
|
+
import {Tool, useCurrentUser} from 'sanity'
|
|
16
5
|
import {
|
|
17
6
|
DragDropContext,
|
|
18
7
|
Droppable,
|
|
19
8
|
Draggable,
|
|
20
9
|
DropResult,
|
|
10
|
+
DragStart,
|
|
21
11
|
} from 'react-beautiful-dnd'
|
|
22
12
|
|
|
23
|
-
import {
|
|
13
|
+
import {State, WorkflowConfig} from '../types'
|
|
24
14
|
import {DocumentCard} from './DocumentCard'
|
|
25
|
-
import Mutate from './Mutate'
|
|
26
15
|
import {useWorkflowDocuments} from '../hooks/useWorkflowDocuments'
|
|
16
|
+
import {API_VERSION} from '../constants'
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type WorkflowToolOptions = {
|
|
36
|
-
schemaTypes: string[]
|
|
37
|
-
states: State[]
|
|
38
|
-
}
|
|
18
|
+
import Validators from './Validators'
|
|
19
|
+
import Filters from './Filters'
|
|
20
|
+
import {filterItemsAndSort} from '../helpers/filterItemsAndSort'
|
|
21
|
+
import {arraysContainMatchingString} from '../helpers/arraysContainMatchingString'
|
|
22
|
+
import StateTitle from './StateTitle'
|
|
23
|
+
import {LexoRank} from 'lexorank'
|
|
39
24
|
|
|
40
25
|
type WorkflowToolProps = {
|
|
41
|
-
tool: Tool<
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
type MutateProps = {
|
|
45
|
-
_id: string
|
|
46
|
-
_type: string
|
|
47
|
-
state: State
|
|
48
|
-
documentId: string
|
|
26
|
+
tool: Tool<WorkflowConfig>
|
|
49
27
|
}
|
|
50
28
|
|
|
51
29
|
export default function WorkflowTool(props: WorkflowToolProps) {
|
|
52
30
|
const {schemaTypes = [], states = []} = props?.tool?.options ?? {}
|
|
53
31
|
|
|
54
|
-
const [mutatingDocs, setMutatingDocs] = React.useState<MutateProps[]>([])
|
|
55
|
-
const mutationFinished = React.useCallback((documentId: string) => {
|
|
56
|
-
setMutatingDocs((docs) => docs.filter((doc) => doc._id !== documentId))
|
|
57
|
-
}, [])
|
|
58
|
-
|
|
59
|
-
const client = useClient()
|
|
60
|
-
const toast = useToast()
|
|
61
|
-
|
|
62
32
|
const isDarkMode = useTheme().sanity.color.dark
|
|
63
33
|
const defaultCardTone = isDarkMode ? 'default' : 'transparent'
|
|
64
34
|
|
|
65
|
-
const userList = useProjectUsers()
|
|
35
|
+
const userList = useProjectUsers({apiVersion: API_VERSION})
|
|
36
|
+
|
|
37
|
+
const user = useCurrentUser()
|
|
38
|
+
const userRoleNames = user?.roles?.length
|
|
39
|
+
? user?.roles.map((r) => r.name)
|
|
40
|
+
: []
|
|
41
|
+
|
|
66
42
|
const {workflowData, operations} = useWorkflowDocuments(schemaTypes)
|
|
67
43
|
|
|
68
44
|
// Data to display in cards
|
|
@@ -71,54 +47,178 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
71
47
|
// Operations to perform on cards
|
|
72
48
|
const {move} = operations
|
|
73
49
|
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
50
|
+
const [undroppableStates, setUndroppableStates] = React.useState<string[]>([])
|
|
51
|
+
const [draggingFrom, setDraggingFrom] = React.useState(``)
|
|
52
|
+
|
|
53
|
+
// When drag starts, check for any States we should not allow dropping on
|
|
54
|
+
// Because of either:
|
|
55
|
+
// 1. The "destination" State requires user assignment and the user is not assigned to the dragged document
|
|
56
|
+
// 2. The "source" State State has a list of transitions and the "destination" State is not in that list
|
|
57
|
+
const handleDragStart = React.useCallback(
|
|
58
|
+
(start: DragStart) => {
|
|
59
|
+
const {draggableId, source} = start
|
|
60
|
+
const {droppableId: currentStateId} = source
|
|
61
|
+
setDraggingFrom(currentStateId)
|
|
62
|
+
|
|
63
|
+
const document = data.find(
|
|
64
|
+
(item) => item._metadata?.documentId === draggableId
|
|
65
|
+
)
|
|
66
|
+
const state = states.find((s) => s.id === currentStateId)
|
|
67
|
+
|
|
68
|
+
// This shouldn't happen but TypeScript
|
|
69
|
+
if (!document || !state) return
|
|
70
|
+
|
|
71
|
+
const undroppableStateIds = []
|
|
72
|
+
const statesThatRequireAssignmentIds = states
|
|
73
|
+
.filter((s) => s.requireAssignment)
|
|
74
|
+
.map((s) => s.id)
|
|
75
|
+
|
|
76
|
+
if (statesThatRequireAssignmentIds.length) {
|
|
77
|
+
const documentAssignees = document._metadata?.assignees ?? []
|
|
78
|
+
const userIsAssignedToDocument = user?.id
|
|
79
|
+
? documentAssignees.includes(user.id)
|
|
80
|
+
: false
|
|
81
|
+
|
|
82
|
+
if (!userIsAssignedToDocument) {
|
|
83
|
+
undroppableStateIds.push(...statesThatRequireAssignmentIds)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const statesThatCannotBeTransitionedToIds =
|
|
88
|
+
state.transitions && state.transitions.length
|
|
89
|
+
? states
|
|
90
|
+
.filter((s) => !state.transitions?.includes(s.id))
|
|
91
|
+
.map((s) => s.id)
|
|
92
|
+
: []
|
|
93
|
+
|
|
94
|
+
if (statesThatCannotBeTransitionedToIds.length) {
|
|
95
|
+
undroppableStateIds.push(...statesThatCannotBeTransitionedToIds)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Remove currentStateId from undroppableStates
|
|
99
|
+
const undroppableExceptSelf = undroppableStateIds.filter(
|
|
100
|
+
(id) => id !== currentStateId
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
if (undroppableExceptSelf.length) {
|
|
104
|
+
setUndroppableStates(undroppableExceptSelf)
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
[data, states, user]
|
|
108
|
+
)
|
|
100
109
|
|
|
101
110
|
const handleDragEnd = React.useCallback(
|
|
102
111
|
(result: DropResult) => {
|
|
112
|
+
// Reset undroppable states
|
|
113
|
+
setUndroppableStates([])
|
|
114
|
+
setDraggingFrom(``)
|
|
115
|
+
|
|
103
116
|
const {draggableId, source, destination} = result
|
|
104
|
-
console.log(
|
|
105
|
-
`sending ${draggableId} from ${source.droppableId} to ${destination?.droppableId}`
|
|
106
|
-
)
|
|
107
117
|
|
|
108
|
-
if (
|
|
118
|
+
if (
|
|
119
|
+
// No destination?
|
|
120
|
+
!destination ||
|
|
121
|
+
// No change in position?
|
|
122
|
+
(destination.droppableId === source.droppableId &&
|
|
123
|
+
destination.index === source.index)
|
|
124
|
+
) {
|
|
109
125
|
return
|
|
110
126
|
}
|
|
111
127
|
|
|
112
|
-
//
|
|
113
|
-
const
|
|
128
|
+
// Find all items in current state
|
|
129
|
+
const destinationStateItems = [
|
|
130
|
+
...filterItemsAndSort(data, destination.droppableId, [], null),
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
let newOrder
|
|
134
|
+
|
|
135
|
+
if (!destinationStateItems.length) {
|
|
136
|
+
// Only item in state
|
|
137
|
+
// New minimum rank
|
|
138
|
+
newOrder = LexoRank.min().toString()
|
|
139
|
+
} else if (destination.index === 0) {
|
|
140
|
+
// Now first item in order
|
|
141
|
+
const firstItemOrderRank = [...destinationStateItems].shift()?._metadata
|
|
142
|
+
?.orderRank
|
|
143
|
+
newOrder =
|
|
144
|
+
firstItemOrderRank && typeof firstItemOrderRank === 'string'
|
|
145
|
+
? LexoRank.parse(firstItemOrderRank).genPrev().toString()
|
|
146
|
+
: LexoRank.min().toString()
|
|
147
|
+
} else if (destination.index + 1 === destinationStateItems.length) {
|
|
148
|
+
// Now last item in order
|
|
149
|
+
const lastItemOrderRank = [...destinationStateItems].pop()?._metadata
|
|
150
|
+
?.orderRank
|
|
151
|
+
newOrder =
|
|
152
|
+
lastItemOrderRank && typeof lastItemOrderRank === 'string'
|
|
153
|
+
? LexoRank.parse(lastItemOrderRank).genNext().toString()
|
|
154
|
+
: LexoRank.min().toString()
|
|
155
|
+
} else {
|
|
156
|
+
// Must be between two items
|
|
157
|
+
const itemBefore = destinationStateItems[destination.index]
|
|
158
|
+
const itemBeforeRank = itemBefore?._metadata?.orderRank
|
|
159
|
+
const itemBeforeRankParsed = itemBefore._metadata.orderRank
|
|
160
|
+
? LexoRank.parse(itemBeforeRank)
|
|
161
|
+
: LexoRank.min()
|
|
162
|
+
const itemAfter = destinationStateItems[destination.index + 1]
|
|
163
|
+
const itemAfterRank = itemAfter?._metadata?.orderRank
|
|
164
|
+
const itemAfterRankParsed = itemAfter._metadata.orderRank
|
|
165
|
+
? LexoRank.parse(itemAfterRank)
|
|
166
|
+
: LexoRank.max()
|
|
114
167
|
|
|
115
|
-
|
|
116
|
-
// @ts-ignore
|
|
117
|
-
// @todo not sure if these types should be updated. will documentId every be undefined here?
|
|
118
|
-
setMutatingDocs((current) => [...current, mutatingDoc])
|
|
168
|
+
newOrder = itemBeforeRankParsed.between(itemAfterRankParsed).toString()
|
|
119
169
|
}
|
|
170
|
+
|
|
171
|
+
move(draggableId, destination, states, newOrder)
|
|
172
|
+
},
|
|
173
|
+
[data, move, states]
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
const uniqueAssignedUsers = React.useMemo(() => {
|
|
177
|
+
const uniqueUserIds = data.reduce((acc, item) => {
|
|
178
|
+
const {assignees = []} = item._metadata ?? {}
|
|
179
|
+
const newAssignees = assignees?.length
|
|
180
|
+
? assignees.filter((a) => !acc.includes(a))
|
|
181
|
+
: []
|
|
182
|
+
return newAssignees.length ? [...acc, ...newAssignees] : acc
|
|
183
|
+
}, [] as string[])
|
|
184
|
+
|
|
185
|
+
return userList.filter((u) => uniqueUserIds.includes(u.id))
|
|
186
|
+
}, [data, userList])
|
|
187
|
+
|
|
188
|
+
const [selectedUserIds, setSelectedUserIds] = React.useState<string[]>(
|
|
189
|
+
uniqueAssignedUsers.map((u) => u.id)
|
|
190
|
+
)
|
|
191
|
+
const toggleSelectedUser = React.useCallback((userId: string) => {
|
|
192
|
+
setSelectedUserIds((prev) =>
|
|
193
|
+
prev.includes(userId)
|
|
194
|
+
? prev.filter((u) => u !== userId)
|
|
195
|
+
: [...prev, userId]
|
|
196
|
+
)
|
|
197
|
+
}, [])
|
|
198
|
+
const resetSelectedUsers = React.useCallback(() => {
|
|
199
|
+
setSelectedUserIds([])
|
|
200
|
+
}, [])
|
|
201
|
+
|
|
202
|
+
const [selectedSchemaTypes, setSelectedSchemaTypes] =
|
|
203
|
+
React.useState<string[]>(schemaTypes)
|
|
204
|
+
const toggleSelectedSchemaType = React.useCallback((schemaType: string) => {
|
|
205
|
+
setSelectedSchemaTypes((prev) =>
|
|
206
|
+
prev.includes(schemaType)
|
|
207
|
+
? prev.filter((u) => u !== schemaType)
|
|
208
|
+
: [...prev, schemaType]
|
|
209
|
+
)
|
|
210
|
+
}, [])
|
|
211
|
+
|
|
212
|
+
const [invalidDocumentIds, setInvalidDocumentIds] = React.useState<string[]>(
|
|
213
|
+
[]
|
|
214
|
+
)
|
|
215
|
+
const toggleInvalidDocumentId = React.useCallback(
|
|
216
|
+
(docId: string, action: 'ADD' | 'REMOVE') => {
|
|
217
|
+
setInvalidDocumentIds((prev) =>
|
|
218
|
+
action === 'ADD' ? [...prev, docId] : prev.filter((id) => id !== docId)
|
|
219
|
+
)
|
|
120
220
|
},
|
|
121
|
-
[
|
|
221
|
+
[]
|
|
122
222
|
)
|
|
123
223
|
|
|
124
224
|
if (!states?.length) {
|
|
@@ -133,96 +233,137 @@ export default function WorkflowTool(props: WorkflowToolProps) {
|
|
|
133
233
|
)
|
|
134
234
|
}
|
|
135
235
|
|
|
136
|
-
if (error) {
|
|
236
|
+
if (error && !data.length) {
|
|
137
237
|
return (
|
|
138
238
|
<Container width={1} padding={5}>
|
|
139
|
-
<Feedback
|
|
239
|
+
<Feedback
|
|
240
|
+
tone="critical"
|
|
241
|
+
title="Error querying for Workflow documents"
|
|
242
|
+
/>
|
|
140
243
|
</Container>
|
|
141
244
|
)
|
|
142
245
|
}
|
|
143
246
|
|
|
144
247
|
return (
|
|
145
|
-
|
|
146
|
-
{
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
{documentsWithoutMetadataIds.length > 0 && (
|
|
158
|
-
<Box padding={5}>
|
|
159
|
-
<Card border padding={3} tone="caution">
|
|
160
|
-
<Flex align="center" justify="center">
|
|
161
|
-
<Button
|
|
162
|
-
onClick={() => importDocuments(documentsWithoutMetadataIds)}
|
|
163
|
-
>
|
|
164
|
-
Import {documentsWithoutMetadataIds.length} Missing{' '}
|
|
165
|
-
{documentsWithoutMetadataIds.length === 1
|
|
166
|
-
? `Document`
|
|
167
|
-
: `Documents`}{' '}
|
|
168
|
-
into Workflow
|
|
169
|
-
</Button>
|
|
170
|
-
</Flex>
|
|
171
|
-
</Card>
|
|
172
|
-
</Box>
|
|
173
|
-
)}
|
|
174
|
-
<DragDropContext onDragEnd={handleDragEnd}>
|
|
248
|
+
<Card height="fill" overflow="hidden">
|
|
249
|
+
<Validators data={data} userList={userList} states={states} />
|
|
250
|
+
<Filters
|
|
251
|
+
uniqueAssignedUsers={uniqueAssignedUsers}
|
|
252
|
+
selectedUserIds={selectedUserIds}
|
|
253
|
+
toggleSelectedUser={toggleSelectedUser}
|
|
254
|
+
resetSelectedUsers={resetSelectedUsers}
|
|
255
|
+
schemaTypes={schemaTypes}
|
|
256
|
+
selectedSchemaTypes={selectedSchemaTypes}
|
|
257
|
+
toggleSelectedSchemaType={toggleSelectedSchemaType}
|
|
258
|
+
/>
|
|
259
|
+
<DragDropContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
|
175
260
|
<Grid columns={states.length} height="fill">
|
|
176
|
-
{states.map((state: State, stateIndex: number) =>
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
261
|
+
{states.map((state: State, stateIndex: number) => {
|
|
262
|
+
const userRoleCanDrop = state?.roles?.length
|
|
263
|
+
? arraysContainMatchingString(state.roles, userRoleNames)
|
|
264
|
+
: true
|
|
265
|
+
const isDropDisabled =
|
|
266
|
+
!userRoleCanDrop || undroppableStates.includes(state.id)
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<Card
|
|
270
|
+
key={state.id}
|
|
271
|
+
borderLeft={stateIndex > 0}
|
|
272
|
+
tone={defaultCardTone}
|
|
273
|
+
height="fill"
|
|
274
|
+
overflow="auto"
|
|
275
|
+
>
|
|
276
|
+
<StateTitle
|
|
277
|
+
state={state}
|
|
278
|
+
requireAssignment={state.requireAssignment ?? false}
|
|
279
|
+
userRoleCanDrop={userRoleCanDrop}
|
|
280
|
+
// operation={state.operation}
|
|
281
|
+
isDropDisabled={isDropDisabled}
|
|
282
|
+
draggingFrom={draggingFrom}
|
|
283
|
+
/>
|
|
284
|
+
<Droppable
|
|
285
|
+
droppableId={state.id}
|
|
286
|
+
isDropDisabled={isDropDisabled}
|
|
287
|
+
>
|
|
288
|
+
{(provided, snapshot) => (
|
|
289
|
+
<Card
|
|
290
|
+
ref={provided.innerRef}
|
|
291
|
+
tone={
|
|
292
|
+
snapshot.isDraggingOver ? `primary` : defaultCardTone
|
|
293
|
+
}
|
|
294
|
+
height="fill"
|
|
295
|
+
paddingTop={1}
|
|
296
|
+
>
|
|
297
|
+
{loading ? (
|
|
298
|
+
<Flex padding={5} align="center" justify="center">
|
|
299
|
+
<Spinner muted />
|
|
300
|
+
</Flex>
|
|
301
|
+
) : null}
|
|
302
|
+
|
|
303
|
+
{data.length > 0 &&
|
|
304
|
+
filterItemsAndSort(
|
|
305
|
+
data,
|
|
306
|
+
state.id,
|
|
307
|
+
selectedUserIds,
|
|
308
|
+
selectedSchemaTypes
|
|
309
|
+
).map((item, itemIndex) => {
|
|
310
|
+
const isInvalid = invalidDocumentIds.includes(
|
|
311
|
+
String(item?._metadata?.documentId)
|
|
312
|
+
)
|
|
313
|
+
const meInAssignees = user?.id
|
|
314
|
+
? item?._metadata?.assignees?.includes(user.id)
|
|
315
|
+
: false
|
|
316
|
+
const isDragDisabled =
|
|
317
|
+
!userRoleCanDrop ||
|
|
318
|
+
isInvalid ||
|
|
319
|
+
!(state.requireAssignment
|
|
320
|
+
? state.requireAssignment && meInAssignees
|
|
321
|
+
: true)
|
|
322
|
+
const {documentId} = item._metadata ?? {}
|
|
323
|
+
|
|
324
|
+
if (!documentId) {
|
|
325
|
+
return null
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return (
|
|
329
|
+
<Draggable
|
|
330
|
+
// The metadata's documentId is always the published one to avoid rerendering
|
|
331
|
+
key={documentId}
|
|
332
|
+
draggableId={documentId}
|
|
333
|
+
index={itemIndex}
|
|
334
|
+
isDragDisabled={isDragDisabled}
|
|
335
|
+
>
|
|
336
|
+
{(draggableProvided, draggableSnapshot) => (
|
|
337
|
+
<div
|
|
338
|
+
ref={draggableProvided.innerRef}
|
|
339
|
+
{...draggableProvided.draggableProps}
|
|
340
|
+
{...draggableProvided.dragHandleProps}
|
|
341
|
+
>
|
|
342
|
+
<DocumentCard
|
|
343
|
+
userRoleCanDrop={userRoleCanDrop}
|
|
344
|
+
isDragDisabled={isDragDisabled}
|
|
345
|
+
isDragging={draggableSnapshot.isDragging}
|
|
346
|
+
item={item}
|
|
347
|
+
toggleInvalidDocumentId={
|
|
348
|
+
toggleInvalidDocumentId
|
|
349
|
+
}
|
|
350
|
+
userList={userList}
|
|
351
|
+
states={states}
|
|
352
|
+
/>
|
|
353
|
+
</div>
|
|
354
|
+
)}
|
|
355
|
+
</Draggable>
|
|
356
|
+
)
|
|
357
|
+
})}
|
|
358
|
+
{provided.placeholder}
|
|
359
|
+
</Card>
|
|
360
|
+
)}
|
|
361
|
+
</Droppable>
|
|
180
362
|
</Card>
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
<Card
|
|
184
|
-
ref={provided.innerRef}
|
|
185
|
-
tone={snapshot.isDraggingOver ? `primary` : defaultCardTone}
|
|
186
|
-
height="fill"
|
|
187
|
-
>
|
|
188
|
-
{loading ? (
|
|
189
|
-
<Flex padding={5} align="center" justify="center">
|
|
190
|
-
<Spinner muted />
|
|
191
|
-
</Flex>
|
|
192
|
-
) : null}
|
|
193
|
-
|
|
194
|
-
{data.length > 0 &&
|
|
195
|
-
filterItemsByState(data, state.id).map(
|
|
196
|
-
(item, itemIndex) => (
|
|
197
|
-
// The metadata's documentId is always the published one
|
|
198
|
-
<Draggable
|
|
199
|
-
key={item?._metadata?.documentId as string}
|
|
200
|
-
draggableId={item?._metadata?.documentId as string}
|
|
201
|
-
index={itemIndex}
|
|
202
|
-
>
|
|
203
|
-
{(draggableProvided, draggableSnapshot) => (
|
|
204
|
-
<div
|
|
205
|
-
ref={draggableProvided.innerRef}
|
|
206
|
-
{...draggableProvided.draggableProps}
|
|
207
|
-
{...draggableProvided.dragHandleProps}
|
|
208
|
-
>
|
|
209
|
-
<DocumentCard
|
|
210
|
-
isDragging={draggableSnapshot.isDragging}
|
|
211
|
-
item={item}
|
|
212
|
-
userList={userList}
|
|
213
|
-
/>
|
|
214
|
-
</div>
|
|
215
|
-
)}
|
|
216
|
-
</Draggable>
|
|
217
|
-
)
|
|
218
|
-
)}
|
|
219
|
-
</Card>
|
|
220
|
-
)}
|
|
221
|
-
</Droppable>
|
|
222
|
-
</Card>
|
|
223
|
-
))}
|
|
363
|
+
)
|
|
364
|
+
})}
|
|
224
365
|
</Grid>
|
|
225
366
|
</DragDropContext>
|
|
226
|
-
|
|
367
|
+
</Card>
|
|
227
368
|
)
|
|
228
369
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {WorkflowConfig, defineStates} from '../types'
|
|
2
|
+
|
|
3
|
+
export const API_VERSION = `2023-01-01`
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_CONFIG: WorkflowConfig = {
|
|
6
|
+
schemaTypes: [],
|
|
7
|
+
states: defineStates([
|
|
8
|
+
{
|
|
9
|
+
id: 'inReview',
|
|
10
|
+
title: 'In review',
|
|
11
|
+
color: 'primary',
|
|
12
|
+
roles: ['editor', 'administrator'],
|
|
13
|
+
transitions: ['changesRequested', 'approved'],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: 'changesRequested',
|
|
17
|
+
title: 'Changes requested',
|
|
18
|
+
color: 'warning',
|
|
19
|
+
roles: ['editor', 'administrator'],
|
|
20
|
+
transitions: ['approved'],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'approved',
|
|
24
|
+
title: 'Approved',
|
|
25
|
+
color: 'success',
|
|
26
|
+
roles: ['administrator'],
|
|
27
|
+
requireAssignment: true,
|
|
28
|
+
transitions: ['changesRequested'],
|
|
29
|
+
},
|
|
30
|
+
]),
|
|
31
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {SanityDocumentWithMetadata} from '../types'
|
|
2
|
+
|
|
3
|
+
export function filterItemsAndSort(
|
|
4
|
+
items: SanityDocumentWithMetadata[],
|
|
5
|
+
stateId: string,
|
|
6
|
+
selectedUsers: string[] = [],
|
|
7
|
+
selectedSchemaTypes: null | string[] = []
|
|
8
|
+
): SanityDocumentWithMetadata[] {
|
|
9
|
+
return (
|
|
10
|
+
items
|
|
11
|
+
// Only items of this state
|
|
12
|
+
.filter((item) => item?._metadata?.state === stateId)
|
|
13
|
+
// Only items with selected users, if the document has any assigned users
|
|
14
|
+
.filter((item) =>
|
|
15
|
+
selectedUsers.length && item._metadata?.assignees?.length
|
|
16
|
+
? item._metadata?.assignees.some((assignee) =>
|
|
17
|
+
selectedUsers.includes(assignee)
|
|
18
|
+
)
|
|
19
|
+
: !selectedUsers.length
|
|
20
|
+
)
|
|
21
|
+
// Only items of selected schema types, if any are selected
|
|
22
|
+
.filter((item) => {
|
|
23
|
+
if (!selectedSchemaTypes) {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return selectedSchemaTypes.length
|
|
28
|
+
? selectedSchemaTypes.includes(item._type)
|
|
29
|
+
: false
|
|
30
|
+
})
|
|
31
|
+
// Sort by metadata orderRank, a string field
|
|
32
|
+
.sort((a, b) => {
|
|
33
|
+
const aOrderRank = a._metadata?.orderRank || '0'
|
|
34
|
+
const bOrderRank = b._metadata?.orderRank || '0'
|
|
35
|
+
|
|
36
|
+
return aOrderRank.localeCompare(bOrderRank)
|
|
37
|
+
})
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import {LexoRank} from 'lexorank'
|
|
2
|
+
|
|
3
|
+
// Use in initial value field by passing in the rank value of the last document
|
|
4
|
+
// If not value passed, generate a sensibly low rank
|
|
5
|
+
export default function initialRank(lastRankValue = ``): string {
|
|
6
|
+
const lastRank =
|
|
7
|
+
lastRankValue && typeof lastRankValue === 'string'
|
|
8
|
+
? LexoRank.parse(lastRankValue)
|
|
9
|
+
: LexoRank.min()
|
|
10
|
+
const nextRank = lastRank.genNext().genNext()
|
|
11
|
+
|
|
12
|
+
return (nextRank as any).value
|
|
13
|
+
}
|