sanity-plugin-workflow 1.0.0-beta.4 → 1.0.0-beta.6

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/README.md CHANGED
@@ -12,6 +12,8 @@ With Sanity Studio you can [customize your content tools to support arbitrary wo
12
12
 
13
13
  This plugin is distributed as a **reference implementation** of these customization APIs and is not considered to be a feature-complete implementation of what workflow management requires in production. It is a starting point intended to be forked and customized to the needs of your organization and content creators.
14
14
 
15
+ A key intention of this plugin is that it **does not influence or modify whether a document is in draft or published**. It only tracks the values of a separate "metadata" document. In this implementation, an "Approved" document could be a draft but will still need publishing. "Approving" the document simply removes it from the Workflow process. You will decide if Publishing the document happens in the Studio like normal, using the [Scheduled Publishing plugin](https://www.sanity.io/plugins/scheduled-publishing) or the [Scheduling API](https://www.sanity.io/docs/scheduling-api#fa3bb95f83ed).
16
+
15
17
  ![Screenshot 2023-03-21 at 12 11 24](https://user-images.githubusercontent.com/9684022/226602179-5bd3d91a-9c27-431e-be18-3c70f06c6ccb.png)
16
18
 
17
19
  ## Features
@@ -51,7 +53,7 @@ Add it as a plugin in sanity.config.ts (or .js):
51
53
  // schemaTypes: ['article', 'product'],
52
54
  schemaTypes: [],
53
55
  // Optional, see below
54
- states: [],
56
+ // states: [],
55
57
  })
56
58
  ]
57
59
  })
@@ -71,10 +73,13 @@ Documents can be promoted and demoted in the Workflow with the provided Document
71
73
  // Optional settings:
72
74
  // Used for the color of the Document Badge
73
75
  color: 'success',
74
- // Will enable document actions and drag-and-drop for only users with these Role
76
+ // Will limit document actions and drag-and-drop for only users with these Role
75
77
  roles: ['publisher', 'administrator'],
76
78
  // Requires the user to be "assigned" in order to update to this State
77
79
  requireAssignment: true,
80
+ // Requires the document to be valid before being promoted out of this State
81
+ // Warning: With many documents in the Kanban view this can negatively impact performance
82
+ requireValidation: true,
78
83
  // Defines which States a document can be moved to from this one
79
84
  transitions: ['changesRequested', 'approved']
80
85
  }
@@ -98,10 +103,12 @@ Once the Workflow is complete, the metadata can be removed by using the "Complet
98
103
 
99
104
  This plugin is largely based on the original Workflow Demo built into a Sanity Studio v2 project. The major differences are:
100
105
 
101
- * This plugin can be more easily installed and configured, not just code examples built into a Studio project
102
- * Documents must "opt-in" to and removed from the Workflow, in the previous version all documents were in the workflow which would fill up the interface
103
- * User Roles and Assignments can affect the Workflow. Set rules to enforce which States documents can move between and if being assigned to a document is required to move it to a new State
104
- * Ability to filter Schema types and assigned Users
106
+ * This plugin is not concerned with nor will modify whether a document is in draft or published.
107
+ * This plugin can be more easily installed and configured, not just code examples built into a Studio project.
108
+ * Documents must "opt-in" to and be removed from the Workflow. In the previous version, all documents were in the workflow which would fill up the interface and negatively affect performance.
109
+ * Document validation status can be used as a way to prevent movement through the workflow.
110
+ * User Roles and Assignments can affect the Workflow. Set rules to enforce which States documents can move between and if being assigned to a document is required to move it to a new State.
111
+ * This plugin can filter Schema types and assigned Users.
105
112
 
106
113
  ## License
107
114
 
package/lib/index.d.ts CHANGED
@@ -6,6 +6,7 @@ declare type State = {
6
6
  title: string
7
7
  roles?: string[]
8
8
  requireAssignment?: boolean
9
+ requireValidation?: boolean
9
10
  color?: 'primary' | 'success' | 'warning' | 'danger'
10
11
  }
11
12
 
package/lib/index.esm.js CHANGED
@@ -1,9 +1,9 @@
1
1
  var _templateObject, _templateObject2, _templateObject3;
2
2
  function _taggedTemplateLiteral(strings, raw) { if (!raw) { raw = strings.slice(0); } return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }
3
- import { useClient, useValidationStatus, useCurrentUser, useSchema, Preview, useFormValue, defineType, defineField, UserAvatar, useTimeAgo, TextWithTone, definePlugin } from 'sanity';
3
+ import { useClient, useCurrentUser, useValidationStatus, useSchema, Preview, useFormValue, defineType, defineField, UserAvatar, useTimeAgo, TextWithTone, definePlugin } from 'sanity';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { UsersIcon, SplitVerticalIcon, CheckmarkIcon, ArrowRightIcon, ArrowLeftIcon, EditIcon, AddIcon, PublishIcon, ErrorOutlineIcon, WarningOutlineIcon, DragHandleIcon, UserIcon, ResetIcon, InfoOutlineIcon } from '@sanity/icons';
6
- import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react';
6
+ import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react';
7
7
  import { UserSelectMenu, useListeningQuery, useProjectUsers, Feedback } from 'sanity-plugin-utils';
8
8
  import { useToast, Button, Spinner, Card, Flex, Box, Text, useClickOutside, Popover, Grid, Tooltip, useTheme, Stack, MenuButton, Menu, Badge, Container } from '@sanity/ui';
9
9
  import { LexoRank } from 'lexorank';
@@ -36,8 +36,8 @@ const DEFAULT_CONFIG = {
36
36
  title: "Approved",
37
37
  color: "success",
38
38
  roles: ["administrator"],
39
- requireAssignment: true,
40
- transitions: ["changesRequested"]
39
+ transitions: ["changesRequested"],
40
+ requireAssignment: true
41
41
  }])
42
42
  };
43
43
  function UserAssignment(props) {
@@ -291,11 +291,6 @@ function UpdateWorkflow(props, allStates, actionState) {
291
291
  id,
292
292
  type
293
293
  } = props;
294
- const {
295
- validation,
296
- isValidating
297
- } = useValidationStatus(id, type);
298
- const hasValidationErrors = !isValidating && (validation == null ? void 0 : validation.length) > 0 && validation.find(v => v.level === "error");
299
294
  const user = useCurrentUser();
300
295
  const client = useClient({
301
296
  apiVersion: API_VERSION
@@ -313,6 +308,11 @@ function UpdateWorkflow(props, allStates, actionState) {
313
308
  const {
314
309
  assignees = []
315
310
  } = (_a = data == null ? void 0 : data.metadata) != null ? _a : {};
311
+ const {
312
+ validation,
313
+ isValidating
314
+ } = useValidationStatus(id, type);
315
+ const hasValidationErrors = (currentState == null ? void 0 : currentState.requireValidation) && !isValidating && (validation == null ? void 0 : validation.length) > 0 && validation.find(v => v.level === "error");
316
316
  if (error) {
317
317
  console.error(error);
318
318
  }
@@ -342,40 +342,39 @@ function UpdateWorkflow(props, allStates, actionState) {
342
342
  const direction = actionStateIndex > currentStateIndex ? "promote" : "demote";
343
343
  const DirectionIcon = direction === "promote" ? ArrowRightIcon : ArrowLeftIcon;
344
344
  const directionLabel = direction === "promote" ? "Promote" : "Demote";
345
- let title = "".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
346
345
  const userRoleCanUpdateState = ((_b = user == null ? void 0 : user.roles) == null ? void 0 : _b.length) && ((_c = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _c.length) ?
347
346
  // If the Action state is limited to specific roles
348
347
  // check that the current user has one of those roles
349
348
  arraysContainMatchingString(user.roles.map(r => r.name), actionState.roles) :
350
349
  // No roles specified on the next state, so anyone can update
351
350
  ((_d = actionState == null ? void 0 : actionState.roles) == null ? void 0 : _d.length) !== 0;
352
- if (!userRoleCanUpdateState) {
353
- title = "Your User role cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
354
- }
355
351
  const actionStateIsAValidTransition = (currentState == null ? void 0 : currentState.id) && currentState.transitions.length ?
356
352
  // If the Current State limits transitions to specific States
357
353
  // Check that the Action State is in Current State's transitions array
358
354
  currentState.transitions.includes(actionState.id) :
359
355
  // Otherwise this isn't a problem
360
356
  true;
361
- if (!actionStateIsAValidTransition) {
362
- title = "You cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\" from \"").concat(currentState == null ? void 0 : currentState.title, "\"");
363
- }
364
357
  const userAssignmentCanUpdateState = actionState.requireAssignment ?
365
358
  // If the Action State requires assigned users
366
359
  // Check the current user ID is in the assignees array
367
360
  currentUser && assignees.length && assignees.includes(currentUser.id) :
368
361
  // Otherwise this isn't a problem
369
362
  true;
370
- if (!userAssignmentCanUpdateState) {
363
+ let title = "".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
364
+ if (!userRoleCanUpdateState) {
365
+ title = "Your User role cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
366
+ } else if (!actionStateIsAValidTransition) {
367
+ title = "You cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\" from \"").concat(currentState == null ? void 0 : currentState.title, "\"");
368
+ } else if (!userAssignmentCanUpdateState) {
371
369
  title = "You must be assigned to the document to ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
372
- }
373
- if (hasValidationErrors) {
370
+ } else if ((currentState == null ? void 0 : currentState.requireValidation) && isValidating) {
371
+ title = "Document is validating, cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
372
+ } else if (hasValidationErrors) {
374
373
  title = "Document has validation errors, cannot ".concat(directionLabel, " State to \"").concat(actionState.title, "\"");
375
374
  }
376
375
  return {
377
376
  icon: DirectionIcon,
378
- disabled: loading || error || isValidating || hasValidationErrors || !currentState || !userRoleCanUpdateState || !actionStateIsAValidTransition || !userAssignmentCanUpdateState,
377
+ disabled: loading || error || (currentState == null ? void 0 : currentState.requireValidation) && isValidating || hasValidationErrors || !currentState || !userRoleCanUpdateState || !actionStateIsAValidTransition || !userAssignmentCanUpdateState,
379
378
  title,
380
379
  label: actionState.title,
381
380
  onHandle: () => onHandle(id, actionState)
@@ -634,7 +633,7 @@ function useWorkflowDocuments(schemaTypes) {
634
633
  setLocalDocuments(data);
635
634
  }
636
635
  }, [data]);
637
- const move = React.useCallback((draggedId, destination, states, newOrder) => {
636
+ const move = React.useCallback(async (draggedId, destination, states, newOrder) => {
638
637
  const currentLocalData = localDocuments;
639
638
  const newLocalDocuments = localDocuments.map(item => {
640
639
  var _a;
@@ -681,10 +680,9 @@ function useWorkflowDocuments(schemaTypes) {
681
680
  _type
682
681
  } = document;
683
682
  const {
684
- _rev,
685
683
  documentId
686
684
  } = document._metadata || {};
687
- client.patch("workflow-metadata.".concat(documentId)).ifRevisionId(_rev).set({
685
+ await client.patch("workflow-metadata.".concat(documentId)).set({
688
686
  state: newStateId,
689
687
  orderRank: newOrder
690
688
  }).commit().then(() => {
@@ -693,11 +691,12 @@ function useWorkflowDocuments(schemaTypes) {
693
691
  title: "Moved to \"".concat((_a = newState == null ? void 0 : newState.title) != null ? _a : newStateId, "\""),
694
692
  status: "success"
695
693
  });
696
- }).catch(() => {
694
+ }).catch(err => {
697
695
  var _a;
698
696
  setLocalDocuments(currentLocalData);
699
697
  return toast.push({
700
698
  title: "Failed to move to \"".concat((_a = newState == null ? void 0 : newState.title) != null ? _a : newStateId, "\""),
699
+ description: err.message,
701
700
  status: "error"
702
701
  });
703
702
  });
@@ -908,6 +907,24 @@ function PublishedStatus(props) {
908
907
  })
909
908
  });
910
909
  }
910
+ function Validate(props) {
911
+ const {
912
+ documentId,
913
+ type,
914
+ onChange
915
+ } = props;
916
+ const {
917
+ isValidating,
918
+ validation = []
919
+ } = useValidationStatus(documentId, type);
920
+ useEffect(() => {
921
+ onChange({
922
+ isValidating,
923
+ validation
924
+ });
925
+ }, [onChange, isValidating, validation]);
926
+ return null;
927
+ }
911
928
  function ValidationStatus(props) {
912
929
  const {
913
930
  validation = []
@@ -933,7 +950,7 @@ function ValidationStatus(props) {
933
950
  });
934
951
  }
935
952
  function DocumentCard(props) {
936
- var _a;
953
+ var _a, _b;
937
954
  const {
938
955
  isDragDisabled,
939
956
  userRoleCanDrop,
@@ -948,18 +965,29 @@ function DocumentCard(props) {
948
965
  documentId
949
966
  } = (_a = item._metadata) != null ? _a : {};
950
967
  const schema = useSchema();
968
+ const state = states.find(s => {
969
+ var _a2;
970
+ return s.id === ((_a2 = item._metadata) == null ? void 0 : _a2.state);
971
+ });
951
972
  const isDarkMode = useTheme().sanity.color.dark;
952
973
  const defaultCardTone = isDarkMode ? "transparent" : "default";
974
+ const [optimisticValidation, setOptimisticValidation] = useState({
975
+ isValidating: (_b = state == null ? void 0 : state.requireValidation) != null ? _b : false,
976
+ validation: []
977
+ });
953
978
  const {
954
- validation = [],
955
- isValidating
956
- } = useValidationStatus(documentId != null ? documentId : "", item._type);
979
+ isValidating,
980
+ validation
981
+ } = optimisticValidation;
982
+ const handleValidation = useCallback(updates => {
983
+ setOptimisticValidation(updates);
984
+ }, []);
957
985
  const cardTone = useMemo(() => {
958
986
  let tone = defaultCardTone;
959
987
  if (!userRoleCanDrop) return isDarkMode ? "default" : "transparent";
960
988
  if (!documentId) return tone;
961
989
  if (isDragging) tone = "positive";
962
- if (!isValidating && validation.length > 0) {
990
+ if ((state == null ? void 0 : state.requireValidation) && !isValidating && validation.length > 0) {
963
991
  if (validation.some(v => v.level === "error")) {
964
992
  tone = "critical";
965
993
  } else {
@@ -967,7 +995,7 @@ function DocumentCard(props) {
967
995
  }
968
996
  }
969
997
  return tone;
970
- }, [isDarkMode, userRoleCanDrop, defaultCardTone, documentId, isDragging, validation, isValidating]);
998
+ }, [defaultCardTone, userRoleCanDrop, isDarkMode, documentId, isDragging, isValidating, validation, state == null ? void 0 : state.requireValidation]);
971
999
  useEffect(() => {
972
1000
  if (!isValidating && validation.length > 0) {
973
1001
  if (validation.some(v => v.level === "error")) {
@@ -984,75 +1012,81 @@ function DocumentCard(props) {
984
1012
  var _a2;
985
1013
  return states[states.length - 1].id === ((_a2 = item._metadata) == null ? void 0 : _a2.state);
986
1014
  }, [states, item._metadata.state]);
987
- return /* @__PURE__ */jsx(Box, {
988
- paddingBottom: 3,
989
- paddingX: 3,
990
- children: /* @__PURE__ */jsx(Card, {
991
- radius: 2,
992
- shadow: isDragging ? 3 : 1,
993
- tone: cardTone,
994
- children: /* @__PURE__ */jsxs(Stack, {
995
- children: [/* @__PURE__ */jsx(Card, {
996
- borderBottom: true,
997
- radius: 2,
998
- padding: 3,
999
- paddingLeft: 2,
1000
- tone: cardTone,
1001
- style: {
1002
- pointerEvents: "none"
1003
- },
1004
- children: /* @__PURE__ */jsxs(Flex, {
1005
- align: "center",
1006
- justify: "space-between",
1007
- gap: 1,
1008
- children: [/* @__PURE__ */jsx(Box, {
1009
- flex: 1,
1010
- children: /* @__PURE__ */jsx(Preview, {
1011
- layout: "default",
1012
- value: item,
1013
- schemaType: schema.get(item._type)
1014
- })
1015
- }), /* @__PURE__ */jsx(Box, {
1016
- style: {
1017
- flexShrink: 0
1018
- },
1019
- children: hasError || isDragDisabled ? null : /* @__PURE__ */jsx(DragHandleIcon, {})
1020
- })]
1021
- })
1022
- }), /* @__PURE__ */jsx(Card, {
1023
- padding: 2,
1024
- radius: 2,
1025
- tone: "inherit",
1026
- children: /* @__PURE__ */jsxs(Flex, {
1027
- align: "center",
1028
- justify: "space-between",
1029
- gap: 3,
1030
- children: [/* @__PURE__ */jsx(Box, {
1031
- flex: 1,
1032
- children: documentId && /* @__PURE__ */jsx(UserDisplay, {
1033
- userList,
1034
- assignees,
1015
+ return /* @__PURE__ */jsxs(Fragment, {
1016
+ children: [(state == null ? void 0 : state.requireValidation) ? /* @__PURE__ */jsx(Validate, {
1017
+ documentId,
1018
+ type: item._type,
1019
+ onChange: handleValidation
1020
+ }) : null, /* @__PURE__ */jsx(Box, {
1021
+ paddingBottom: 3,
1022
+ paddingX: 3,
1023
+ children: /* @__PURE__ */jsx(Card, {
1024
+ radius: 2,
1025
+ shadow: isDragging ? 3 : 1,
1026
+ tone: cardTone,
1027
+ children: /* @__PURE__ */jsxs(Stack, {
1028
+ children: [/* @__PURE__ */jsx(Card, {
1029
+ borderBottom: true,
1030
+ radius: 2,
1031
+ padding: 3,
1032
+ paddingLeft: 2,
1033
+ tone: cardTone,
1034
+ style: {
1035
+ pointerEvents: "none"
1036
+ },
1037
+ children: /* @__PURE__ */jsxs(Flex, {
1038
+ align: "center",
1039
+ justify: "space-between",
1040
+ gap: 1,
1041
+ children: [/* @__PURE__ */jsx(Box, {
1042
+ flex: 1,
1043
+ children: /* @__PURE__ */jsx(Preview, {
1044
+ layout: "default",
1045
+ value: item,
1046
+ schemaType: schema.get(item._type)
1047
+ })
1048
+ }), /* @__PURE__ */jsx(Box, {
1049
+ style: {
1050
+ flexShrink: 0
1051
+ },
1052
+ children: hasError || isDragDisabled ? null : /* @__PURE__ */jsx(DragHandleIcon, {})
1053
+ })]
1054
+ })
1055
+ }), /* @__PURE__ */jsx(Card, {
1056
+ padding: 2,
1057
+ radius: 2,
1058
+ tone: "inherit",
1059
+ children: /* @__PURE__ */jsxs(Flex, {
1060
+ align: "center",
1061
+ justify: "space-between",
1062
+ gap: 3,
1063
+ children: [/* @__PURE__ */jsx(Box, {
1064
+ flex: 1,
1065
+ children: documentId && /* @__PURE__ */jsx(UserDisplay, {
1066
+ userList,
1067
+ assignees,
1068
+ documentId,
1069
+ disabled: !userRoleCanDrop
1070
+ })
1071
+ }), validation.length > 0 ? /* @__PURE__ */jsx(ValidationStatus, {
1072
+ validation
1073
+ }) : null, /* @__PURE__ */jsx(DraftStatus, {
1074
+ document: item
1075
+ }), /* @__PURE__ */jsx(PublishedStatus, {
1076
+ document: item
1077
+ }), /* @__PURE__ */jsx(EditButton, {
1078
+ id: item._id,
1079
+ type: item._type,
1080
+ disabled: !userRoleCanDrop
1081
+ }), isLastState ? /* @__PURE__ */jsx(CompleteButton, {
1035
1082
  documentId,
1036
1083
  disabled: !userRoleCanDrop
1037
- })
1038
- }), validation.length > 0 ? /* @__PURE__ */jsx(ValidationStatus, {
1039
- validation
1040
- }) : null, /* @__PURE__ */jsx(DraftStatus, {
1041
- document: item
1042
- }), /* @__PURE__ */jsx(PublishedStatus, {
1043
- document: item
1044
- }), /* @__PURE__ */jsx(EditButton, {
1045
- id: item._id,
1046
- type: item._type,
1047
- disabled: !userRoleCanDrop
1048
- }), isLastState ? /* @__PURE__ */jsx(CompleteButton, {
1049
- documentId,
1050
- disabled: !userRoleCanDrop
1051
- }) : null]
1052
- })
1053
- })]
1084
+ }) : null]
1085
+ })
1086
+ })]
1087
+ })
1054
1088
  })
1055
- })
1089
+ })]
1056
1090
  });
1057
1091
  }
1058
1092
  function DocumentList(props) {
@@ -1080,7 +1114,7 @@ function DocumentList(props) {
1080
1114
  return (_c = (_b = (_a = dataFiltered[index]) == null ? void 0 : _a._metadata) == null ? void 0 : _b.documentId) != null ? _c : index;
1081
1115
  },
1082
1116
  estimateSize: () => 113,
1083
- overscan: 5
1117
+ overscan: 10
1084
1118
  });
1085
1119
  if (!data.length) {
1086
1120
  return null;
@@ -1292,7 +1326,8 @@ function StateTitle(props) {
1292
1326
  requireAssignment,
1293
1327
  userRoleCanDrop,
1294
1328
  isDropDisabled,
1295
- draggingFrom
1329
+ draggingFrom,
1330
+ documentCount
1296
1331
  } = props;
1297
1332
  let tone = "default";
1298
1333
  const isSource = draggingFrom === state.id;
@@ -1317,7 +1352,15 @@ function StateTitle(props) {
1317
1352
  }), requireAssignment ? /* @__PURE__ */jsx(Status, {
1318
1353
  text: "You must be assigned to the document to move documents to this State",
1319
1354
  icon: UserIcon
1320
- }) : null]
1355
+ }) : null, /* @__PURE__ */jsx(Box, {
1356
+ flex: 1,
1357
+ children: documentCount > 0 ? /* @__PURE__ */jsx(Text, {
1358
+ weight: "semibold",
1359
+ align: "right",
1360
+ size: 1,
1361
+ children: documentCount
1362
+ }) : null
1363
+ })]
1321
1364
  })
1322
1365
  });
1323
1366
  }
@@ -1351,12 +1394,12 @@ function FloatingCard(_ref3) {
1351
1394
  }, "floater") : null
1352
1395
  });
1353
1396
  }
1354
- function Validators(_ref4) {
1355
- let {
1397
+ function Verify(props) {
1398
+ const {
1356
1399
  data,
1357
1400
  userList,
1358
1401
  states
1359
- } = _ref4;
1402
+ } = props;
1360
1403
  const client = useClient({
1361
1404
  apiVersion: API_VERSION
1362
1405
  });
@@ -1387,6 +1430,17 @@ function Validators(_ref4) {
1387
1430
  } = (_a = cur._metadata) != null ? _a : {};
1388
1431
  return !orderRank && documentId ? [...acc, documentId] : acc;
1389
1432
  }, []) : [];
1433
+ const documentsWithDuplicatedOrderIds = (data == null ? void 0 : data.length) ? data.reduce((acc, cur) => {
1434
+ var _a;
1435
+ const {
1436
+ documentId,
1437
+ orderRank
1438
+ } = (_a = cur._metadata) != null ? _a : {};
1439
+ return orderRank && data.filter(d => {
1440
+ var _a2;
1441
+ return ((_a2 = d._metadata) == null ? void 0 : _a2.orderRank) === orderRank;
1442
+ }).length > 1 && documentId ? [...acc, documentId] : acc;
1443
+ }, []) : [];
1390
1444
  const correctDocuments = React.useCallback(async ids => {
1391
1445
  toast.push({
1392
1446
  title: "Correcting...",
@@ -1487,6 +1541,10 @@ function Validators(_ref4) {
1487
1541
  tone: "caution",
1488
1542
  onClick: () => addOrderToDocuments(documentsWithoutOrderIds),
1489
1543
  text: documentsWithoutOrderIds.length === 1 ? "Set Order for 1 Document" : "Set Order for ".concat(documentsWithoutOrderIds.length, " Documents")
1544
+ }) : null, documentsWithDuplicatedOrderIds.length > 0 ? /* @__PURE__ */jsx(Button, {
1545
+ tone: "caution",
1546
+ onClick: () => addOrderToDocuments(documentsWithDuplicatedOrderIds),
1547
+ text: documentsWithDuplicatedOrderIds.length === 1 ? "Set Unique Order for 1 Document" : "Set Unique Order for ".concat(documentsWithDuplicatedOrderIds.length, " Documents")
1490
1548
  }) : null, orphanedMetadataDocumentIds.length > 0 ? /* @__PURE__ */jsx(Button, {
1491
1549
  text: "Cleanup orphaned metadata",
1492
1550
  onClick: handleOrphans,
@@ -1558,7 +1616,7 @@ function WorkflowTool(props) {
1558
1616
  setUndroppableStates(undroppableExceptSelf);
1559
1617
  }
1560
1618
  }, [data, states, user]);
1561
- const handleDragEnd = React.useCallback(result => {
1619
+ const handleDragEnd = React.useCallback(async result => {
1562
1620
  var _a2, _b2, _c2, _d, _e, _f;
1563
1621
  setUndroppableStates([]);
1564
1622
  setDraggingFrom("");
@@ -1587,10 +1645,10 @@ function WorkflowTool(props) {
1587
1645
  } else {
1588
1646
  const itemBefore = destinationStateItems[destination.index - 1];
1589
1647
  const itemBeforeRank = (_e = itemBefore == null ? void 0 : itemBefore._metadata) == null ? void 0 : _e.orderRank;
1590
- const itemBeforeRankParsed = itemBefore._metadata.orderRank ? LexoRank.parse(itemBeforeRank) : LexoRank.min();
1648
+ const itemBeforeRankParsed = itemBeforeRank ? LexoRank.parse(itemBeforeRank) : LexoRank.min();
1591
1649
  const itemAfter = destinationStateItems[destination.index];
1592
1650
  const itemAfterRank = (_f = itemAfter == null ? void 0 : itemAfter._metadata) == null ? void 0 : _f.orderRank;
1593
- const itemAfterRankParsed = itemAfter._metadata.orderRank ? LexoRank.parse(itemAfterRank) : LexoRank.max();
1651
+ const itemAfterRankParsed = itemAfterRank ? LexoRank.parse(itemAfterRank) : LexoRank.max();
1594
1652
  newOrder = itemBeforeRankParsed.between(itemAfterRankParsed).toString();
1595
1653
  }
1596
1654
  move(draggableId, destination, states, newOrder);
@@ -1646,7 +1704,7 @@ function WorkflowTool(props) {
1646
1704
  direction: "column",
1647
1705
  height: "fill",
1648
1706
  overflow: "hidden",
1649
- children: [/* @__PURE__ */jsx(Validators, {
1707
+ children: [/* @__PURE__ */jsx(Verify, {
1650
1708
  data,
1651
1709
  userList,
1652
1710
  states
@@ -1679,7 +1737,8 @@ function WorkflowTool(props) {
1679
1737
  requireAssignment: (_b2 = state.requireAssignment) != null ? _b2 : false,
1680
1738
  userRoleCanDrop,
1681
1739
  isDropDisabled,
1682
- draggingFrom
1740
+ draggingFrom,
1741
+ documentCount: filterItemsAndSort(data, state.id, selectedUserIds, selectedSchemaTypes).length
1683
1742
  }), /* @__PURE__ */jsx(Box, {
1684
1743
  flex: 1,
1685
1744
  children: /* @__PURE__ */jsx(Droppable, {